Skip to content

refactor(agents): give present the exposed handles as another page would - #1731

Closed
loganj wants to merge 1 commit into
agent-host-keyed-readsfrom
agent-host-remote-handles
Closed

loganj wants to merge 1 commit into
agent-host-keyed-readsfrom
agent-host-remote-handles

Conversation

@loganj

@loganj loganj commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

🤖

Summary

  • Stacked on refactor(agents): keyed reads for harness Activity tabs #1728. A type's UI entry gets the handles its agent entry exposes, through present. Once agents run in another page, those handles will arrive over a port, where every call answers later. Today present gets the runtime's own objects, so a tab could rely on a synchronous answer, or reach a method it was never meant to, without anyone noticing.
  • present now gets the handles the way it will from another page: each call returns a promise, and stores and keyed reads stay synchronous. The harness tabs typecheck against that form. Behavior in the app stays the same: a call still runs at once; only its answer is wrapped.

Details

  • features/agents/handles.ts:
    • Remote<T>: the type a screen gets. Calls return promises; snapshot, subscribe, read and empty stay as they are.
    • local(expose): the in-page version of what a port client will give. It walks plain objects and arrays, wraps calls, and throws on any other object (for example a class instance), naming the path.
    • handle(object, names): a plain handle with only the named methods of an object.
  • service.ts: present takes (exposed: Remote<Exposed>) => ..., and the merge passes local(type.expose). A type whose expose fails the check still runs; it is listed without what it shows, and the error is logged, like a present that throws.
  • Claude, Codex, goose and Pi expose handle(...) of their setup, defaults, provider and history objects, with the methods their tabs' Pick types already named.
  • Also on refactor(agents): give screens a type's config functions through present #1727 (pushed there): two doc comments that refactor(agents): give screens a type's config functions through present #1727 had left on the wrong declarations are back in place.

Testing

  • service.test.ts: the presented type gets the count as JSON, the store's own snapshot, and a call that resolves with a promise. On the old code the call returned 2 directly, so the test fails there. New case: a type that exposes a class instance is listed but not shown, and present is not called.
  • handles.test.ts: local keeps stores and keyed reads synchronous and identical, turns a throwing call into a rejection, walks arrays, and refuses a class instance by path. handle calls through to the object.
  • tsc --noEmit passes. Full vitest run src: 11456 passed. The only failures are 4 emoji-related files (the Agents page avatar picker, emoji-mart, EmojiPicker appearance, StatusEditor emoji draft). Each fails because the emoji picker does not render, which happens in this worktree because its node_modules is a symlink. None of them touches agent code. I confirmed the Agents page and emoji-mart failures on the base branch earlier; CI runs all four.

🤖 Generated with Claude Code

A type's `present` now gets its `expose` the way it would get it from
another page: each call returns a promise, and stores and keyed reads
stay synchronous (`Remote<T>`). The service checks that `expose` holds
only plain objects, arrays, JSON, stores, keyed reads and calls; a type
exposing anything else, such as a class instance, is not shown.

Claude, Codex, goose and Pi expose `handle(object, names)` of their
setup, defaults, provider and history objects instead of the objects,
so screens reach only the methods their tabs use. The tabs' handle types
accept the remote form, so a tab that needed a call's synchronous result
would no longer typecheck.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj

loganj commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Closing: the Manager/Runner split replaces this design (#1748 → #1839, with more to come). The branch stays in place because #1737, #1743 and #1744 are still stacked on it. They will move onto the new stack in the hidden-page step.

@loganj loganj closed this Oct 28, 2026
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.

1 participant