Repository navigation
fix(app): restore desktop app dependency resolution (tinymemory-gate starship-battery 0.10) - #6852
Conversation
Agent-generated files (decks, documents, generated media) were written under the hidden per-account workspace, ~/.openhuman/users/<hash>/ workspace/artifacts/<uuid>/, where Finder and file pickers do not show them. Split the store: metadata (meta.json, args.json) stays in the hidden, per-account <workspace>/artifacts/<id>/, and only the file moves to ~/OpenHuman/projects/Files (config::default_files_dir). Files are named for people, <title>.<ext>, claimed with create_new and suffixed ' (2)' on collision, so two accounts sharing the folder never clobber each other and each account still lists only its own records. - ArtifactMeta gains file/file_root; legacy records resolve as before. - One resolver applies an escape guard to a recorded file (absolute, no '..', inside its file_root after resolving symlinks, never is_always_forbidden) and now rejects a legacy meta.path with '..', which the lexical starts_with check let through. - A Ready record whose file was moved or deleted reads as 'file missing'. - Regenerate overwrites the file it owns; a failed generation removes its empty placeholder; delete removes the file and the record. - ensure_agent_dirs creates the folder and runs a crash-safe, idempotent migration of legacy files (copy to a partial, claim a name and rename, atomic meta write, then delete the legacy bytes). - artifacts/ joins WORKSPACE_INTERNAL_DIRS. - The desktop Download command takes an artifact id and resolves it through the core instead of trusting a renderer path under the data dir. Part of tinyhumansai#5505
check_within ran the symlink escape check only when both the file and its files folder canonicalized, so a dangling or looping symlink, or a folder that could not be resolved, skipped the check instead of failing it. A later read could then follow the link wherever it came to point. Now, when anything is at the recorded path, both sides must resolve or the file is rejected; a path whose metadata cannot be read for any reason other than NotFound is rejected too. A path with nothing at it still passes the guard and reads as 'file missing'. Part of tinyhumansai#5505
…e limit Listing artifacts/ in WORKSPACE_INTERNAL_DIRS also blocked artifacts/tool-results/, where the agent reads its own large tool outputs back (tool_result_artifacts::threshold_persists_preview_and_ readable_file failed in CI). Drop the entry: a record's file is already bounded by the escape guard, including the is_always_forbidden floor. Also fold the ArtifactReady path doc back to its original length so core/events.rs stays at its 2005-line layout limit. Part of tinyhumansai#5505
Keeps tools/ops.rs at its 1341-line layout pin after the files folder argument: each producer now takes the host Config and derives both the metadata workspace and the files folder from it. Part of tinyhumansai#5505
Adds a 'Files folder' row to Settings > Agent OS access, so users can see where the agent saves the files it makes and pick another folder. - Config gains a persisted files_dir_override and Config::files_dir(), which falls back to ~/OpenHuman/projects/Files for a missing or relative value. The producers and the boot migration read it. - config.get_agent_paths reports files_dir, default_files_dir and files_dir_source; config.update_agent_paths accepts files_dir, where an empty string restores the default. - A chosen folder must be absolute, not a file, not a protected system or credential folder, and not inside the OpenHuman data folder (compared through the nearest existing ancestor, so macOS /var vs /private/var cannot slip past). A missing folder is created. - A change applies to new files only; existing artifacts keep the folder recorded in their metadata. - Strings are translated for every locale. Part of tinyhumansai#5505
…ct metadata A record's file_root came from the same meta.json the guard was checking, so a hand-edited record claiming file_root = ~ and file = ~/Documents/ private.pdf passed every within-root check and would be served by read_artifact_bytes and the desktop Download command. - FileRoots carries the folder new files go to plus every folder the core vouches for. resolve_file honours a record's file_root only when it canonically matches one of those; callers build it themselves (the producers' files_dir, FileRoots::from_config for the RPCs and the desktop shell), never from the record. - create/fail/read/delete/resolve_ready_file take the roots. - is_workspace_internal_path treats artifacts/<id>/ as internal state again, narrowly: artifacts/tool-results/ stays readable so the agent can read its own large tool outputs back. Part of tinyhumansai#5505
With PR 1's escape guard trusting only folders the core vouches for, a Settings change would have stranded every artifact made in the previous folder. Config now records the folders used before in files_dir_history (core-owned, never read from an artifact), and FileRoots::from_config trusts the current folder, the default and that history. The producers, the media tools and ai.regenerate build their roots from config. Part of tinyhumansai#5505
config.toml sits next to workspace_dir and holds the autonomy policy plus files_dir_override / files_dir_history, which the artifact escape guard now trusts. The workspace-internal boundary only covered paths inside the workspace, so a trusted root over the account or data dir let the agent's file tools rewrite it and vouch for an arbitrary folder. Treat <account>/config.toml as internal state; other files beside it stay reachable through such a grant. Part of tinyhumansai#5505
Adds Show in folder to every ready file in the chat Files panel and to the Files folder row in Settings > Agent OS access. The panel used to offer it only after a Download, and it opened the Downloads copy; it now resolves the artifact through the core and opens the file manager at the real file in the visible files folder, with a localized error when the file was moved or deleted. The settings row opens the configured folder. revealArtifact(artifactId) replaces revealArtifactInFileManager(path). New strings are translated for every locale, and the release smoke list gains a check for the visible files folder. Closes tinyhumansai#5505
The hosted `[UNAUTHORIZED]` code now reads as a lapsed session only when the auth refusal is the error's own: its class is `unauthorized: `, or the message is a bare adapter payload. An outage can quote that code, for example when the backend relays its upstream's 401 as a 503. That now reads `MEMORY_UNREACHABLE:`, and an invalid-input error that quotes the code keeps its text. OpenHuman's own `SESSION_EXPIRED:` marker and the backend's billing code still count wherever they appear.
Moves vendor/tinymemory to tinyhumansai/tinymemory#176 (feat/hosted-families, 4cbc096), which serves goals, tool rules, documents, the source sink and maintenance over the TinyHumans hosted wire. It changes only crates the core links directly (tinymemory-remote), not the loadable module, its contract or the bus, so the registry's module pin does not move for it.
…ions on their own The engine list gives tinyhumans its own capability row with goals, tool_memory, documents, sources and maintenance; cortex keeps its old row. Brain gated the coding-sessions card on sources. Hosted memory now serves sources but reads no local agent transcripts, so the card would call an RPC the engine refuses; it is gated on coding_sessions instead.
The hosted double keeps the caller's context (the families look records up by label), filters events by label and scopes by prefix, and serves events by id. The hosted suites now expect documents and tool rules to round-trip, the doctor to report the hosted service's health, and graph to stay refused.
tinymemory f7dcc2c (tinyhumansai/tinymemory#176): hosted memory serves retrieval scored by rank, ingest, profile, episodic, scoring and a tree drawn from the server's derived facts, beliefs and concepts. TreeSummary gains an optional preview (contract 4.2, a minor bump).
The tinyhumans row of expected_capabilities, which the UI's family gates read, gains retrieval, ingest, profile, episodic, scoring and tree. A new test builds the hosted driver offline and holds the row to exactly what it advertises, so the two cannot drift; it also pins that hosted memory keeps no local chunk store, which the UI's local-store controls key on.
Hosted CortexDB now serves the retrieval family, and its namespace hits carry a rank in score/final_score and no signal at all. The unscored-engine rules from phase 1 lived only on the no-retrieval branch, so with retrieval advertised the auto-recall notes leg would floor every hosted note away. memory::ops::fallback::rank_only names the mark: every hit has a positive final score and no similarity, keyword, graph, episodic or freshness signal, which a weighted sum of signals cannot produce. On it: - auto-recall keeps the notes in the engine's order (its first three), as for an unscored engine; - situational preferences and the contradiction check answer nothing, as they did on hosted before; - memory_hybrid_search keeps the engine's order and shows a rank instead of a percentage, rather than re-weighting zeros into no results.
Hosted memory serves Tree and Scoring but reaches no model: summarise and embed_text answer Unsupported. The archivist warned on every segment close for both. Unsupported is the driver's shape, not a failure, so it is now a debug line; the heuristic recap still applies, and other errors still warn.
Ported from the 6718 graph branch. A summary node served with its text inline (TreeSummary::preview, hosted memory's facts, beliefs and concepts) is labelled by that text and offers no file to open. A hosted driver's namespaces read as what they hold (Memory, Chat, Email, Documents), but only for the understanding tree kind, since the embedded engine has a global scope of its own. drop_stand_ins removes a document stand-in for a child the leaf listing also returned, so no item is drawn twice.
Hosted memory keeps what it is sent through its source sink and runs no source pipeline of its own, so folder, GitHub, RSS and web-page sources had nowhere to go: the Sync button and Apply all asked for a source_sync family the driver does not serve. memory::sources::hosted_sync reads such a source with the host's own readers (built explicitly per kind, not through reader_for) and hands the items to accept_source_items in batches of 25. It keeps the newest max_items within sync_depth_days, stops at max_tokens_per_sync and reports more pending, skips an unreadable item with a note, and marks a folder Internal and fetched content ExternalSync. The sink skips unchanged items, so a run over an unchanged folder writes nothing. A file removed from a folder stays in memory until the source is removed: the sink forgets a whole source, never one item. The Sync button and Apply all dispatch to it when the bound driver has a sink and no source_sync, recorded through run_recorded like any driver run. hosted_periodic (ported from the 6718 branch) schedules those sources on the user's sync interval, never more often than daily, timed from the run log after a restart; it starts with the channels and checks the driver bound at each tick, since the engine can be switched while the app runs. driver_run's stage events now carry their trigger (manual or periodic).
With hosted memory serving a tree, Brain's graph tab and its tree-gated panels now show for it. The reset, rebuild, build-trees and vault controls, the ingest pipeline status and the vault health checklist all work on the local engine's chunk store, which hosted memory does not keep. MemoryControls shows those controls only for an engine with chunks (failing open while the engine is unknown) and leaves Refresh for every engine; the pipeline status panel and the vault checklist are gated on chunks. The graph and the memory workspace stay on tree.
Changed the reflection persistence test to call the public `list` method instead of directly locking the internal `entries` field, ensuring the test exercises the same interface that production code uses. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The baseline count of ignored tests for the openhuman-core package is reduced from 68 to 62, reflecting that six previously ignored tests are no longer being skipped. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The ignored test count for the openhuman-core package has been decreased from 68 to 67, reflecting that one previously ignored test is no longer being skipped. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…ts/ci/module-pin-exemptions.jso Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Tiny Sweeper review
Last completed reportTiny Sweeper reviewTiny Sweeper reviewed this change across 6 lane(s) and found 0 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below. State: Incomplete Review snapshot
Completeness: Incomplete What changedThe review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below. FeaturesNone identified with supported citations. TestsNo supported feature-to-test mapping was produced. Test execution is not inferred. FindingsNo active actionable findings. Could not review: scripts/ci/module-pin-exemptions.json Before merge
Agent review detailscritique
security
tests
commits
description
e2e
Evidence and run details
|
…nit-b test: drop redundant tests in tools, integrations, flows, config, core, security and platform
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Warning Review limit reached
This review includes 277 billable files and costs up to $69.25. Or wait 3 minutes for your next included review. View limit detailsLimit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (277)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughThe vendored Changestinymemory Pin Update
Priority: ➖ Normal Estimated code review effort: 1 (Trivial) | ~5 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to The updated dependency pin and CI exemption align; no concrete merge-blocking issue remains. Architecture SummaryArchitecture risk: 🔵 Low · up to The change affects 1 system. Changed systems: Architecture concerns Review detailsSystems and components
Before / after behavior
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
A rabbit checks the pin, Comment |
run_recorded recorded every failed run with items: 0. A host-side sync that writes several batches and then has one refused had already stored those items, so its row under-reported them (CodeRabbit on tinyhumansai#6843). A run's failure can now carry what it did first: run_recorded takes any RunFailure. A driver's MemoryError carries nothing, as before; a PartialFailure carries the outcome so far. The failed row counts its records_ingested, and when the run had already stopped at its budget the message says more items were pending, since the row has no field for that. The run is still recorded and reported as failed.
A budget-limited host sync re-read and re-charged the same newest items on every run: the sink skipped them as unchanged, the budget stopped the run before older items, and repeating the sync never progressed (CodeRabbit on tinyhumansai#6843). hosted_sync now keeps a per-source record of what the sink accepted (item id, modified time, digest of title, text and URL) under <workspace>/state/hosted_sync: - an item whose modified time matches the record is neither read nor charged; one read with unchanged text is not sent; - max_items and max_tokens_per_sync cap the new or changed items a run sends, so a stopped run reports more pending and the next one carries on; - a batch is recorded only after the sink accepts it, so a refused batch is sent again and fails the run as a PartialFailure with what earlier batches wrote; - a missing or unreadable record is a full pass, which the sink keeps cheap; - one source's runs are serialized, so a manual and a scheduled sync cannot send the same items at once. Deleting a source's memory (memory_tree.delete_source) and removing a source from the registry clear its record, so a later sync starts over instead of trusting what was forgotten. Removing a source still keeps its memory, as on the local engine; the engines guide no longer says otherwise.
There was a problem hiding this comment.
tinysweeper found nothing blocking, but could not review everything, so this is not an approval: scripts/ci/module-pin-exemptions.json.
$0.0010 · 5,749 in / 3,438 out · 2,304 cached (40%) · ladder/vectors, deepseek/deepseek-v4-flash · 234 embedded
description: $0.0005 · 4,055 in / 1,908 out · 2,304 cached (57%) · deepseek/deepseek-v4-flash
…module mains Co-authored-by: Medulla <medulla@tinyhumans.ai>
test(raw_coverage): drop raw-coverage tests duplicated by domain or vendor tests
…ed module mains Co-authored-by: Medulla <medulla@tinyhumans.ai>
…nit-a test: drop redundant tests in agent, memory, threads, channels and web_chat
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.
…op-access fix(windows): normalize unknown permission states to not_required on Windows
…osted-family-parity feat(memory): serve every backable family on hosted memory
…k (plist) Co-authored-by: Medulla <medulla@tinyhumans.ai>
…facts-5505 feat(artifacts): write agent deliverables to a visible Files folder (tinyhumansai#5505, part 1/3)
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…facts-5505-settings feat(settings): choose the Files folder in Agent OS access (tinyhumansai#5505, part 2/3)
…facts-5505-reveal feat(artifacts): show agent files in their folder (tinyhumansai#5505, part 3/3)
…rged module mains Co-authored-by: Medulla <medulla@tinyhumans.ai>
Summary
On main,
cargo check --manifest-path crates/openhuman-app/Cargo.tomlfails to resolve. The scheduler gate moved totinymemory-gatein #6837, and that crate asked forstarship-battery 0.12. Version 0.12 requiresplist ~1.10.1(as a netbsd target dependency), which conflicts with theplist 1.8that Tauri locks.Depends on tinyhumansai/tinymemory#177. That PR pins
starship-batteryto 0.10, which is what the host used before the move; the battery probe API is unchanged. The gitlink points at its head,f5e9daa8(v1.19.0-1-gf5e9daa8).Changes
vendor/tinymemory: moved to the v1.19.0 release commit plus fix: onboarding flow - registry skills, config-based flag, dead code cleanup #177.module-pin-exemptions.json: the tinymemoryexpectis nowv1.19.0-1-gf5e9daa8. The previous value (v1.18.0-52) no longer described main's v1.19.0 pin.crates/openhuman-app/Cargo.lock: regenerated. Besidesstarship-battery, the regeneration picks up the path-crate version bumps that the wave 4 re-pins left stale: tinybox 0.1.10, tinychannels 0.1.7, tinycomputer 0.9, tinyconnectors 0.11 and others.Cargo.lock: regenerated the same way.Verification
cargo check --manifest-path crates/openhuman-app/Cargo.toml: Finished; it failed to resolve on main.cargo check -p openhumanwith product features passes, as do thescheduler_gatelib tests (9 pass).clippy -D warningsand tests (16) pass with--features battery.check-submodule-monotonic upstream/main HEADpasses.check-module-pinsreports no tinymemory failure.Summary by CodeRabbit