Summary
The workspace sandbox terminal and sandbox provisioning seams now live in @tangle-network/agent-app, and creative-agent consumes them (PR tangle-network/creative-agent#396). While migrating, an analysis of agent-app vs. gtm-agent found a few generic terminal pieces that still live in each consuming app instead of in agent-app — causing (or about to cause) duplication, and at least one latent bug. This issue tracks hoisting those pieces into agent-app so consumers stop reimplementing them.
Proposed approach (lands in agent-app)
- Hoist the per-tab connection-id helper. Move creative-agent's
tabTerminalConnectionId (sessionStorage-based, derives a stable-per-tab / unique-per-client terminal connection id) into @tangle-network/agent-app/web-react, next to useSandboxTerminalConnection. It is 100% generic.
- Fixes a latent bug:
gtm-agent passes no connectionId to TerminalView, so every tab/client fights over one connection id and reconnects kick each other. This helper is the fix.
- Hoist a configurable terminal panel component. creative-agent's
WorkspaceTerminalPanel (status badge, connect/retry states, lazy TerminalView, header) is structurally generic — only copy strings and a status-tone map are app-specific. Move it into agent-app/web-react (or sandbox-ui, where TerminalView lives) with title / subtitle / status-tone as props. gtm-agent has a near-identical hand-rolled copy.
- (Optional) Collapse the triple dependency-injection.
server.ts (WS upgrade) + the connection route + the runtime-proxy route each pass the same requireUser / requireWorkspaceAccess / credentials / token-secret closures. Expose a single createWorkspaceSandboxTerminal(deps) in agent-app that returns all three handlers from one deps object.
- Migrate
gtm-agent onto the new seam. gtm-agent is still on the older terminal model (browser connects directly to the sidecar; no runtime-proxy / WS-upgrade through the worker, hand-rolled /api/sandbox/connection, no useSandboxTerminalConnection). After 1–3 land, migrate gtm-agent to the shared WS terminal + the hoisted helper/panel.
Acceptance criteria
Out of scope (stays per-app)
The product SandboxRuntimeConfig shell (env, tool files, model provider, snapshot/restore, bootstrap), the thin DI route wrappers, the layout mount point that keeps the panel alive across tab switches, auth trusted-origins, and tool install paths.
Context
- creative-agent PR: tangle-network/creative-agent#396 (
linh/agent-app-sandbox-terminal) — first consumer of the new WS terminal seam.
- agent-app already provides:
createSandboxTerminalToken/verify, createWorkspaceSandbox{Connection,RuntimeProxy,TerminalUpgrade}Handler, ensureWorkspaceSandbox + SandboxRuntimeConfig, and the useSandboxTerminalConnection hook. It does not provide any React terminal panel or per-tab connection-id helper today.
- Tech-debt / refactor — schedule after creative-agent #396 settles.
Summary
The workspace sandbox terminal and sandbox provisioning seams now live in
@tangle-network/agent-app, andcreative-agentconsumes them (PR tangle-network/creative-agent#396). While migrating, an analysis of agent-app vs.gtm-agentfound a few generic terminal pieces that still live in each consuming app instead of in agent-app — causing (or about to cause) duplication, and at least one latent bug. This issue tracks hoisting those pieces into agent-app so consumers stop reimplementing them.Proposed approach (lands in
agent-app)tabTerminalConnectionId(sessionStorage-based, derives a stable-per-tab / unique-per-client terminal connection id) into@tangle-network/agent-app/web-react, next touseSandboxTerminalConnection. It is 100% generic.gtm-agentpasses noconnectionIdtoTerminalView, so every tab/client fights over one connection id and reconnects kick each other. This helper is the fix.WorkspaceTerminalPanel(status badge, connect/retry states, lazyTerminalView, header) is structurally generic — only copy strings and a status-tone map are app-specific. Move it intoagent-app/web-react(orsandbox-ui, whereTerminalViewlives) withtitle/subtitle/ status-tone as props.gtm-agenthas a near-identical hand-rolled copy.server.ts(WS upgrade) + the connection route + the runtime-proxy route each pass the samerequireUser/requireWorkspaceAccess/ credentials / token-secret closures. Expose a singlecreateWorkspaceSandboxTerminal(deps)in agent-app that returns all three handlers from one deps object.gtm-agentonto the new seam. gtm-agent is still on the older terminal model (browser connects directly to the sidecar; no runtime-proxy / WS-upgrade through the worker, hand-rolled/api/sandbox/connection, nouseSandboxTerminalConnection). After 1–3 land, migrate gtm-agent to the shared WS terminal + the hoisted helper/panel.Acceptance criteria
tabTerminalConnectionId(or equivalent) is exported fromagent-app/web-react; creative-agent imports it instead of defining it locally.createWorkspaceSandboxTerminal(deps)factory returns the connection + runtime-proxy + WS-upgrade handlers.gtm-agentis migrated onto the shared seam and the hoisted pieces; the multi-tab connection-id bug is gone.Out of scope (stays per-app)
The product
SandboxRuntimeConfigshell (env, tool files, model provider, snapshot/restore, bootstrap), the thin DI route wrappers, the layout mount point that keeps the panel alive across tab switches, auth trusted-origins, and tool install paths.Context
linh/agent-app-sandbox-terminal) — first consumer of the new WS terminal seam.createSandboxTerminalToken/verify,createWorkspaceSandbox{Connection,RuntimeProxy,TerminalUpgrade}Handler,ensureWorkspaceSandbox+SandboxRuntimeConfig, and theuseSandboxTerminalConnectionhook. It does not provide any React terminal panel or per-tab connection-id helper today.