feat(agent): navigate tool + working folder (off-scene authoring) - #262
Merged
Conversation
Give the agent a mutable working folder — a cursor like a shell's cwd, decoupled from the user's on-screen layer — so it can author into a subfolder without moving the user's view. - ctx.rootId becomes the mutable working folder; add ctx.sceneRootId (the visible layer, fixed). mutatorFor routes by rootId vs sceneRootId. - HeadlessMutator: writes into an off-scene layer by reusing StoreMutator against a throwaway store seeded with the target layer's content, forwarding its change batches to the S7 sync intake (record + submitLocalBatch scene:false). Nothing renders; the store computes node.update prev correctly. - navigate(target) tool: folder_id | 'root' | 'up' — sets the working folder, returns the folder's contents (its ls), and switches write routing. Listed in the agent prompt. - link_notes resolves endpoints in the working folder; off-scene creates are excluded from the post-turn scene arrange/recenter.
Code review found the write path was working-folder aware but get_note, edit_note, write_note's rewrite check, and arrange_notes still read the visible ctx.store — so after navigating off-scene the agent couldn't read/edit/rewrite notes it just authored, and arrange_notes rearranged the USER's on-screen layer (breaking navigate's 'don't move the view' promise). - Add workingLayerStore(ctx): the off-scene layer store when navigated away, else the visible store. Route get_note / edit_note / write_note existence / arrange_notes through it. - HeadlessMutator.ensure() flushes persistence before re-seeding, so re-entering a folder in the same turn reflects this turn's own writes (fixes overlap + broken links on re-entry). Expose layerStore(). - Tests: off-scene read/edit/rewrite/arrange act on the working folder and never move the visible layer; re-entry sees prior writes.
Contributor
Author
Review response (code-review high)All 5 findings were valid and shared one root cause — the WRITE path was working-folder aware, but
Added regression tests: off-scene read/edit/rewrite/arrange act on the working folder and never move the visible layer; re-entry sees prior writes. Prompt line broadened to say every note tool operates inside the working folder. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
S8 of the board-authoring plan: gives the agent a working folder — a mutable cursor, like a shell's
cwd— decoupled from the user's on-screen layer. The agent cannavigateinto a folder and author there without moving the user's view, using the S7 headless sync intake.Design
ctx.rootIdbecomes the mutable working folder (mutated mid-turn bynavigate;ctxis a stable object across tool calls, so later calls see it). Addedctx.sceneRootId= the layer the visible store projects (the user's view, fixed for the turn).mutatorFor(ctx)routes per call: working folder == scene layer →StoreMutator(renders + syncs, unchanged); ≠ →HeadlessMutator(off-scene).HeadlessMutatorwrites into a non-visible layer by reusingStoreMutatorverbatim against a throwaway off-scenecreateCanvasStoreseeded (from the whole-board oplog) with the target layer's content. Its localchangebatches are forwarded to the sync-correct intake:persistence.record+getBoardSyncRef().submitLocalBatch(batch, { scene: false }). Nothing renders; the store computesnode.update.prevcorrectly, so no op is hand-built. clientId + id scheme match the scene store so batches/ids stay board-valid.navigate(target)tool (folder_id | "root" | "up"): resolves the destination, switches the working folder + write routing, and returns the folder's current contents (itsls). Listed in the agent prompt.link_notesresolves both endpoints in the working folder (the off-scene layer when headless), not just the visible store.Why reuse
StoreMutatorvia an off-scene store rather than hand-build ops:node.add/edge.addare trivial, butnode.updateneeds a correctprevslice that the store computes automatically — wrapping the store gets rewrite/patch parity and correctprevfor free.Scope
In scope:
navigate+ headless create/link/rewrite/patch into the working folder. Out of scope (→ S9):create_folder+ per-callparent_idoverrides. Deferred: mid-turn re-scoping of the search index (navigate's return is the read mechanism); an on-finish "worked in Folder X" receipt card.Test plan
HeadlessMutatortests: a create lands in the target layer off-scene (not in the visible store), is recorded to the whole-board oplog with the rightparentId, and enters the sync intakescene:false; sessions accumulate;createLinkjoins two off-scene notes; a fresh session seeds from the oplog so it can rewrite a note already in that folder.navigatetests: entering a folder sets the working folder + routes writes off-scene;"root"restores visible writes;"up"walks to the parent; a non-folder / unknown target errors without changing the working folder.npm run check-allpass.