Skip to content

fix: unwrap API envelope in memory tauriCommands and fix file path scope - #172

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
sanil-23:fix/memory-workspace-integration
Apr 1, 2026
Merged

senamakel merged 1 commit into
tinyhumansai:mainfrom
sanil-23:fix/memory-workspace-integration

Conversation

@sanil-23

@sanil-23 sanil-23 commented Apr 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Unwrap ApiEnvelope responses in memory tauriCommands.ts functions so the UI receives flat data
  • Fix workspace file path scope in ops.rs to allow reading files at workspace root

Problem

  • After refactor: migrate memory service to controller registry #138 (controller registry migration), memory methods return ApiEnvelope ({ data: { ... }, meta: { ... } }) but frontend expects flat arrays/strings — causes map is not a function crashes on the Intelligence page
  • resolve_existing_memory_path scoped to workspace/memory/ but workspace files (MEMORY.md, SOUL.md) live at workspace/ root — reading them was rejected with "memory path escapes the memory directory"

Solution

  • tauriCommands.ts: Unwrap envelope in memoryListDocuments, memoryListNamespaces, aiListMemoryFiles, aiReadMemoryFile with defensive checks for both envelope and flat formats
  • ops.rs: Change resolve_existing_memory_path and resolve_writable_memory_path to scope against workspace root instead of memory/ subdirectory. Path traversal still blocked by validate_memory_relative_path

Submission Checklist

  • Unit tests — cargo check + cargo fmt + yarn typecheck pass
  • E2E / integration — Manually verified: Memory Workspace loads namespaces, documents, files on Intelligence page
  • Doc comments — N/A (no new public APIs)
  • Inline comments — Envelope unwrap comments in tauriCommands.ts

Impact

  • Desktop: Memory Workspace on Intelligence page now loads real data from Rust core

Related

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced memory operations for document listing, namespace queries, and file access to handle and properly unwrap multiple API response formats and envelope structures.
    • Improved workspace path resolution and validation for memory operations with enhanced error reporting that better indicates the workspace directory context.

After the controller registry migration (tinyhumansai#138), memory RPC methods
return ApiEnvelope responses but the frontend expected flat data.
This caused "map is not a function" errors on the Intelligence page.

- Unwrap envelope in memoryListDocuments, memoryListNamespaces,
  aiListMemoryFiles, and aiReadMemoryFile in tauriCommands.ts
- Fix resolve_existing_memory_path and resolve_writable_memory_path
  to scope against workspace root instead of memory/ subdirectory —
  workspace files (MEMORY.md, SOUL.md) live at the root

Closes tinyhumansai#170
@coderabbitai

coderabbitai Bot commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Updates to response unwrapping logic in TypeScript command handlers for memory operations, handling varying envelope formats. Simultaneously refactors Rust path resolution functions to canonicalize paths relative to the workspace directory instead of memory root parent directory.

Changes

Cohort / File(s) Summary
TypeScript Response Unwrapping
app/src/utils/tauriCommands.ts
Functions memoryListDocuments, memoryListNamespaces, aiListMemoryFiles, and aiReadMemoryFile updated to handle flexible response envelope formats from backend, extracting data from nested structures ({ data: ... }, { namespaces }, { content }) and providing safe fallback values ([], '').
Rust Path Resolution
src/openhuman/memory/ops.rs
Functions resolve_existing_memory_path and resolve_writable_memory_path refactored to resolve paths against current_workspace_dir() directly rather than deriving from parent of memory root; includes updated canonicalization logic and error messaging.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Responses now flexible, paths so true,
Envelopes unwrapped with graceful view,
Workspace root anchors our journey's way,
Handling all formats—come what may!
🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: unwrapping API envelopes in memory Tauri commands and fixing file path scope issues in the workspace.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/openhuman/memory/ops.rs (1)

352-368: ⚠️ Potential issue | 🔴 Critical

Check the canonical parent before create_dir_all.

Line 362 mutates the filesystem before Line 367 enforces the workspace boundary. If any existing component under the workspace is a symlink to an external directory, create_dir_all(parent) can create directories outside the workspace and only then return the escape error.

Suggested fix
 async fn resolve_writable_memory_path(relative_path: &str) -> Result<PathBuf, String> {
     validate_memory_relative_path(relative_path)?;
     let workspace_dir = current_workspace_dir().await?;
     let canonical_workspace = workspace_dir
         .canonicalize()
         .map_err(|e| format!("resolve workspace dir {}: {e}", workspace_dir.display()))?;
     let full_path = workspace_dir.join(relative_path);
     let parent = full_path
         .parent()
         .ok_or_else(|| "memory path must include a file name".to_string())?;
+    let mut existing_ancestor = parent;
+    while !existing_ancestor.exists() {
+        existing_ancestor = existing_ancestor
+            .parent()
+            .ok_or_else(|| "memory path escapes the workspace directory".to_string())?;
+    }
+    let canonical_existing_ancestor = existing_ancestor
+        .canonicalize()
+        .map_err(|e| format!("resolve memory parent {}: {e}", existing_ancestor.display()))?;
+    if !canonical_existing_ancestor.starts_with(&canonical_workspace) {
+        return Err("memory path escapes the workspace directory".to_string());
+    }
     std::fs::create_dir_all(parent)
         .map_err(|e| format!("create memory path {}: {e}", parent.display()))?;
     let resolved_parent = parent
         .canonicalize()
         .map_err(|e| format!("resolve memory parent {}: {e}", parent.display()))?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/memory/ops.rs` around lines 352 - 368, In
resolve_writable_memory_path: avoid creating directories before verifying they
won't escape the workspace by canonicalizing an existing ancestor first; after
validate_memory_relative_path and computing
workspace_dir/canonical_workspace/full_path and parent, walk up from parent to
find the nearest existing ancestor, canonicalize that ancestor and assert it
starts_with canonical_workspace, then call std::fs::create_dir_all(parent), and
finally canonicalize the created parent (resolved_parent) and re-check
starts_with canonical_workspace to be safe; reference
resolve_writable_memory_path, parent, resolved_parent, canonical_workspace,
create_dir_all.
🧹 Nitpick comments (2)
src/openhuman/memory/ops.rs (1)

336-349: Add debug/trace logs to the new workspace-root path resolution flow.

These helpers now enforce the read/write trust boundary, but failures only surface as generic strings. Logging the requested relative path, canonical workspace, and reject reason will make field debugging much easier.

As per coding guidelines, "Add substantial debug logging on new/changed flows using log/tracing at debug or trace level in Rust".

Also applies to: 352-368

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/memory/ops.rs` around lines 336 - 349, In
resolve_existing_memory_path add trace/debug logging around the workspace-root
resolution: before canonicalizing log the incoming relative_path and
workspace_dir (call sites: validate_memory_relative_path and
current_workspace_dir), after computing canonical_workspace and resolved log
both canonical paths, and on each error branch include the underlying error and
a clear reject reason (e.g., canonicalize workspace failure, canonicalize memory
path failure, or path escapes workspace) using the project’s tracing/log macros
so failures surface with the relative path, canonical workspace, resolved path,
and error details; apply the same pattern to the similar flow in the neighboring
function handling lines ~352-368.
app/src/utils/tauriCommands.ts (1)

220-228: Centralize the response-shape normalizer and log fallback paths.

The four branches duplicate anonymous envelope types and silently swallow unknown shapes with [] / ''. A small typed normalizer (RpcOutcome<T> + ApiEnvelope<T>) with namespaced debug logs when a fallback path is taken would keep the contract in one place and make future drift obvious.

As per coding guidelines, "Prefer interface for defining object shapes in TypeScript" and "Add substantial debug logging on new/changed flows using namespaced debug logs in React/app code".

Also applies to: 235-243, 341-351, 358-366

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/utils/tauriCommands.ts` around lines 220 - 228, Create a single typed
normalizer to centralize the envelope-unwrapping logic (define RpcOutcome<T> and
ApiEnvelope<T> interfaces and a function like normalizeRpcResponse<T>(resp:
unknown): RpcOutcome<T>) and replace the duplicated ad-hoc branches around the
openhuman.memory_list_documents call (and the other occurrences at the noted
ranges) with calls to that normalizer; inside the normalizer perform tight type
checks, return a consistent typed result, and emit namespaced debug logs (e.g.,
"app:tauriCommands:normalizeRpcResponse") whenever you take a fallback path or
encounter an unexpected shape so the fallback to []/"" is logged and the
contract is enforced in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/utils/tauriCommands.ts`:
- Around line 220-228: memoryListDocuments (and the other three similar helpers)
are reading the registry envelope directly but must first unwrap the RpcOutcome
wrapper; update each helper that calls callCoreRpc to check if the returned resp
is an object with a top-level "result" property and, if so, replace resp with
resp.result before performing the existing envelope checks for
"data"/"files"/"content". Concretely: in memoryListDocuments (and the three
analogous functions near the later diffs), after awaiting callCoreRpc, do a safe
check like if (resp && typeof resp === 'object' && !Array.isArray(resp) &&
'result' in resp) resp = (resp as Record<string, unknown>).result; then continue
the existing logic that unwraps resp.data / resp.files / resp.content so callers
receive the inner ApiEnvelope payload instead of the RpcOutcome wrapper; follow
the same pattern already used in memoryGraphQuery.

---

Outside diff comments:
In `@src/openhuman/memory/ops.rs`:
- Around line 352-368: In resolve_writable_memory_path: avoid creating
directories before verifying they won't escape the workspace by canonicalizing
an existing ancestor first; after validate_memory_relative_path and computing
workspace_dir/canonical_workspace/full_path and parent, walk up from parent to
find the nearest existing ancestor, canonicalize that ancestor and assert it
starts_with canonical_workspace, then call std::fs::create_dir_all(parent), and
finally canonicalize the created parent (resolved_parent) and re-check
starts_with canonical_workspace to be safe; reference
resolve_writable_memory_path, parent, resolved_parent, canonical_workspace,
create_dir_all.

---

Nitpick comments:
In `@app/src/utils/tauriCommands.ts`:
- Around line 220-228: Create a single typed normalizer to centralize the
envelope-unwrapping logic (define RpcOutcome<T> and ApiEnvelope<T> interfaces
and a function like normalizeRpcResponse<T>(resp: unknown): RpcOutcome<T>) and
replace the duplicated ad-hoc branches around the
openhuman.memory_list_documents call (and the other occurrences at the noted
ranges) with calls to that normalizer; inside the normalizer perform tight type
checks, return a consistent typed result, and emit namespaced debug logs (e.g.,
"app:tauriCommands:normalizeRpcResponse") whenever you take a fallback path or
encounter an unexpected shape so the fallback to []/"" is logged and the
contract is enforced in one place.

In `@src/openhuman/memory/ops.rs`:
- Around line 336-349: In resolve_existing_memory_path add trace/debug logging
around the workspace-root resolution: before canonicalizing log the incoming
relative_path and workspace_dir (call sites: validate_memory_relative_path and
current_workspace_dir), after computing canonical_workspace and resolved log
both canonical paths, and on each error branch include the underlying error and
a clear reject reason (e.g., canonicalize workspace failure, canonicalize memory
path failure, or path escapes workspace) using the project’s tracing/log macros
so failures surface with the relative path, canonical workspace, resolved path,
and error details; apply the same pattern to the similar flow in the neighboring
function handling lines ~352-368.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a09cc54-d70b-4e64-92e3-a58e5a4808d6

📥 Commits

Reviewing files that changed from the base of the PR and between 2383d51 and 9caa861.

📒 Files selected for processing (2)
  • app/src/utils/tauriCommands.ts
  • src/openhuman/memory/ops.rs

Comment thread app/src/utils/tauriCommands.ts
@senamakel
senamakel merged commit f5b66de into tinyhumansai:main Apr 1, 2026
8 checks passed
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
fix: resolve race condition and message persistence bugs in conversat…
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
…ope (tinyhumansai#172)

After the controller registry migration (tinyhumansai#138), memory RPC methods
return ApiEnvelope responses but the frontend expected flat data.
This caused "map is not a function" errors on the Intelligence page.

- Unwrap envelope in memoryListDocuments, memoryListNamespaces,
  aiListMemoryFiles, and aiReadMemoryFile in tauriCommands.ts
- Fix resolve_existing_memory_path and resolve_writable_memory_path
  to scope against workspace root instead of memory/ subdirectory —
  workspace files (MEMORY.md, SOUL.md) live at the root

Closes tinyhumansai#170
senamakel pushed a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
…solve race condition and message persistence bugs in conversat…\n
senamakel pushed a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
…ope (tinyhumansai#172)\n\nAfter the controller registry migration (tinyhumansai#138), memory RPC methods\nreturn ApiEnvelope responses but the frontend expected flat data.\nThis caused "map is not a function" errors on the Intelligence page.\n\n- Unwrap envelope in memoryListDocuments, memoryListNamespaces,\n  aiListMemoryFiles, and aiReadMemoryFile in tauriCommands.ts\n- Fix resolve_existing_memory_path and resolve_writable_memory_path\n  to scope against workspace root instead of memory/ subdirectory —\n  workspace files (MEMORY.md, SOUL.md) live at the root\n\nCloses tinyhumansai#170\n
YellowSnnowmann added a commit to YellowSnnowmann/openhuman that referenced this pull request Oct 1, 2026
tinymemory v1.20.0 ships tinyhumansai/tinymemory#176 (the hosted families, TreeSummary::preview, contract 4.2) on top of v1.18.0 and v1.19.0, so the module and the contract this host compiles against are one release again:
- vendor/tinymemory sits on the v1.20.0 tag;
- the registry record names v1.20.0 and its 11 archives, with digests copied from the release's checksum.toml;
- the seven CI fetches pin the ubuntu-22.04-x86_64 archive and its digest (checked against the published asset);
- ARTIFACT_CAPABILITIES_PIN is 1.20.0; the capability families are unchanged since v1.17.0, so the list stays;
- the tinymemory module-pin exemption is gone, as its own reason asked once tinyhumansai#170, tinyhumansai#172, tinyhumansai#173 and tinyhumansai#174 shipped in a release.

The module-pin gate now passes for tinymemory. It still fails on main's pins for other modules, which this PR does not touch.
senamakel added a commit that referenced this pull request Oct 18, 2026
ci: run the layout migration e2e in the storage lanes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants