Repository navigation
chore(sync): merge pingdotgg/t3code main - #383
Merged
Merged
Conversation
An OAuth client on /mcp is capped by the access it was approved with. When another environment calls on behalf of one of its agents, that agent may run under narrower modes, and the target has no way to know. /mcp now reads `T3-Mode-Limit: <runtimeMode>/<interactionMode>` from OAuth clients and caps the request at the narrower of the two. The header can only narrow: a broader one changes nothing, and a malformed one is refused with 400. The client's interaction mode can now be capped too; before, it was always default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A launch had no retry key, so an agent (or another environment calling on its behalf) that lost the response could only guess whether the thread was created, and a retry started a second one. t3_thread_launch now takes an optional clientRequestId. The command, thread and message ids derive from the caller and the key, and the launch service already replays a command it accepted, so a retry returns the first thread. Without a key every launch is new, as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An environment can now sign in to another one's /mcp as an outside agent, with a pairing code from that environment, without a browser. It drives the peer's existing MCP OAuth flow server-side, keeps the token in the server secret store and the rest in peer_environment_links (migration 059). The peer lists the link as "T3 Code · <label>" in Connections, where it can be revoked. The token is only ever sent to an address whose descriptor answers as the linked environment. PeerMcpClient calls the peer's tools and carries the caller's limits in T3-Mode-Limit on every request. Linking refuses this environment itself and peers that cannot honour that header. `t3 environment link|list|unlink` and the peerLinks.* RPCs manage links. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An agent can now work on a linked environment's threads through the same T3 MCP tools. t3_project_list, orchestrator_capabilities, t3_thread_launch, t3_thread_list, t3_thread_read, t3_thread_send, t3_thread_wait and t3_thread_interrupt take an optional environmentId; t3_environment_links lists the links. Each tool's own access declaration still runs here first, so a read-only or ended caller is refused before anything leaves. The forwarded call carries the caller's modes in T3-Mode-Limit, and the linked environment holds it to those and the link's access. Retry keys are hashed with the caller's namespace, since every caller here shares the link's one session there. A forwarded wait goes out in pieces of at most 50 s so no route drops it idle. A remote launch cannot carry attachments yet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
t3_project_create and t3_project_clone take environmentId, so an agent can bring a repository to a machine before launching or delegating work there. Both still need full access here, and the linked environment holds the call to the link's access. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A link's T3-Mode-Limit caps the modes of what it starts, but within those modes it could still steer the user's own threads or change this environment's projects and settings. Work a link starts is now fenced to that link. The linking side registers its OAuth client with the t3code-peer-link software id; the receiving side signs that into the session's claims. Every thread such a session launches carries an immutable linkOrigin, and so do the subagents, forks and create_threads it derives. A client's own thread.create cannot set one. In the shared access declarations, a linked caller may change only threads of the same link, and may not use writesEnvironment tools (projects, settings), schedule tasks, or change scheduled tasks. Reads are not fenced. Setup scripts are skipped for stamped threads in ProjectSetupScriptRunner, which every path that runs them goes through. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Settings → Connections gains a Linked environments section, shown when the server supports peer links and the session may manage access: each link with whether it answers, its access, and a warning in its last five days, plus a Link dialog (address, pairing code, access) and Forget. Mobile lists a connected environment's links under Connections. A thread a linked environment started shows "From <label>" next to its title on web, and in the thread subtitle on mobile. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A fleet host provisions machines by having an agent clone a repository there and register it, which the fence refused as a change to the environment. Adding a project leaves everything already there as it was, so linked work may create and clone projects with full access; it still cannot change or delete existing ones, settings, or scheduled tasks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s a linked environment Forwarding was wired tool by tool, so only the first eleven tools took environmentId. It now lives in McpToolAccess.toLayer: a tool whose parameters take OrchestratorMcpEnvironmentTarget runs in the linked environment it names once its declaration has checked the caller here. The local handler never sees environmentId. Tools that act as the calling thread itself cannot take one, and the layer refuses to build if they do. Queues, pending requests, thread settings and organizing, forks and merge-backs, scheduled tasks, pull requests, worktree refs, project read/update/delete and environment preferences now reach a link too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A link's agents run as the receiving environment's user, so at full access the fence only ever stopped T3's own tools, never what the agent could do. It also kept a host from driving machines it fully trusts: answering a pending question or steering a thread the user started there, updating a project, or running the project's setup script in a worktree it launched. The stamp now records the access the link was paired with, and only a link below full access is fenced. Its work still carries linkOrigin, so clients keep showing where it came from. Stamps from before access was recorded stay fenced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
delegate_task takes target.environmentId (and optionally target.projectId). The child then runs as an ordinary thread in the linked environment, launched through the link with a key derived from the caller's clientRequestId, in the project there with this thread's repository unless one is named. The parent here records the task with delegated_task.remote.request: its node, subagent row and turn item, with remoteChild instead of a local child thread. RemoteDelegation follows the thread there and completes the task with delegated_task.remote.complete, which reuses the parent half of a local finalize, so the parent wakes as it would for a local child. Open remote tasks are followed again at startup. A revoked or expired link fails the task with that reason; any other error backs off, up to five minutes. task_status and mode=wait answer from the row, task_cancel interrupts the thread there and completes the task as cancelled, and restart recovery no longer treats a remote task as abandoned provider work. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Read failed-run activity and prefer that run's error over an earlier assistant reply. Keep successful replies unchanged and retrieve long errors in full without taking diagnostics from a later run. Add failed-run, pagination, run-isolation and successful-recovery regressions for the existing review on pingdotgg#16719. Implemented with GPT-6.1-Sol through Codex in T3 Code on behalf of Lenoridev.
When the task's run on the linked environment ended, the follower completed the task with that run's reply, even while the thread there still waited on its own subagents, background tasks or a completion delivery that would wake it for the real answer. A local task waits for that work and takes its result from the run that answered after it. t3_thread_wait takes an optional untilDelegatedWorkSettled. With it, the wait also blocks until delegatedTaskProgress reports result_available for the thread, waking on task, provider-thread and run updates, and reports that result run as resultRun. The follower passes it and reads the result from resultRun. Both fields are optional: an older peer ignores the input and answers as before, and the follower then falls back to the launched run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…there Stop on a parent skipped a remote task that already had its result, so follow-up work on its thread in the linked environment (a message sent there with t3_thread_send, or a run that started after the result) kept running. A local child is stopped on every Stop, result or not, and cancelling a finished remote task already stops its thread there. Stop now records the owed stop with an internal delegated_task.remote.stop command: it sets remoteChild.stoppedThere to false and counts the stop, leaving the status, result and completion delivery alone. The follower, restarted from the live event tail or by the startup scan, sends that stop under a key per count and records it as "stopped"; a "ran-out" report never clears it. One follower runs per task, and a request while it runs makes it look once more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es keep the cause delegate_task with environmentId launched in the linked project's checkout even when isolation "worktree" was asked for. A worktree there would branch from a commit this checkout has and the other may not, so the call is refused before launching, pointing at t3_thread_launch with a worktree workspaceStrategy. Recording the task or its cancellation no longer builds the caller's message from the dispatch error; the cause goes to the log and the caller gets a fixed message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
delegate_task to a linked environment launches an ordinary thread there, marked delegatedFrom, so a client connected to both machines listed it in the sidebar as well as in the parent's lineage panel. Its approvals and questions also never reached the parent here. Lists now treat such a thread as a subagent when the client also lists the thread that delegated it: the web sidebar and its project ordering, mobile's thread lists, and notifications leave it out. A parent here is flagged, and shows the "subagent needs input" banner, when the task or one of its own subagents there asks. A task whose parent the client cannot list (that machine switched off, or the parent gone) stays listed on its own, so its work is never out of reach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ingdotgg#25731) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…s refetch by construction (pingdotgg#25732) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…5753) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
RemoteDelegation.start resumes every open remote task at once, and each follower read its parent's whole subagent list to find one task, then held it across a wait of up to ten minutes. A parent with 1,441 tasks (35 MB of payload) and 122 open ones made every restart decode ~170k records and hold 8 GB, so the server ran out of heap seconds after starting and crash-looped. getThreadRecords now takes a subagentIds filter. Followers, and task_status waits that re-read on every parent subagent.updated, read just their task. Against a copy of the affected database the startup fan-out drops from 8.2 GB retained over 21.6 s to 8 MB over 61 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…idget stops re-rendering on every presentation change (pingdotgg#25740) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…achable environment (pingdotgg#25703) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… pickers drop timer and sync effects (pingdotgg#25733) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: maria-rcks <maria@kuuro.net>
pingdotgg#25766) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…7-0510 # Conflicts: # apps/web/src/components/CommandPalette.logic.ts # apps/web/src/components/CommandPalette.tsx
…e store and canvas store
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.
Cause
Fork Sync run 40453584304 stopped on merge conflicts in the command palette. The batch has 9 upstream commits, up to 689763a "fix(mobile): isolate file tree state by workspace (pingdotgg#24270)". The conflict comes from pingdotgg#25733 "command palette state lives in a store". Its shortcuts move to
shortcuts/commandPaletteCommands.ts, and an open intent becomes the palette's starting view (openIntentView), resolved while it renders.Conflicts and resolution
CommandPalette.logic.ts: upstream'sopenWithIntent, with the forkthreadKindon the "new thread in" intent.commandPaletteBus.ts: merged cleanly but dispatched "new thread in" without the fork thread kind. It now passesdetail.threadKind ?? "chat".CommandPalette.tsx: upstream's store and starting view. The fork keeps its terminal thread actions, focus level items, navigation history items and message link shortcut. The fork key handler and intent effect go away with upstream's: their shortcuts now run through the command dispatcher, which already has the fork terminal focus rule."projects"with chat project items. The fork terminal view used that same group, so it would have listed chat projects. The terminal view now has its own group (terminal-projects), which the palette fills with terminal project items while it renders.openIntentViewopens it for a terminal intent. NewCommandPalette.logic.fork.test.tscovers both.Fork fixes
ForkTerminalThreadScreen.tsx,useThreadRightPanel.tsx(terminal threads): fix(web): thread details popover closes when the card moves inline pingdotgg/t3code#25731 givesChatCanvasa store, a thread and a preview, and the details card picks its own presentation.onPresentationChangegoes away.CommandPalette.logic.test.ts: upstream's test gets the forkthreadKind.Upstream merge
Merges pingdotgg/t3code main at 689763a.
Adopted upstream pull requests
The author rebased the whole linked environment stack onto upstream main at 71ba6b3, after the action tools of pingdotgg#25423. Each head is merged with a merge commit, and its
mergedHeadis updated in the same commit:070_PeerLinksstays out.eitherwording, itsenvironmentIdorder inthread/tools.tsand a schema comment replace mine. ItsMcpToolAccessand tool tests come in.071_PeerLinkFencestays out. Its link client and provenance tests come in.Effect.fn. A polling loop in its test becomes an awaited event. The evals importtaskPromptfromdelegatedTaskModes.ts, so the re-export added in #380 is removed.The conflicts came from the moved base. Each merge keeps the fork side and then checks
git diff --cached --stat HEAD. Hunks that repeat fork main code are restored to fork main: a contract schema, aresultRunfield, sidebar test fields, and tests the fork keeps in.fork.test.tsfiles. The provenance test gains the pull request'sdelegatedFromassertion.Upstream changes in fork areas
13 files changed on both sides. Besides the palette:
App.tsx: more app services install once at module load. The fork assistant panel host is not affected.Checks
checks.py: typecheck, migration check, adopted pull request check, format, lint and knip pass.fork_tests.py: 185 fork test files pass locally (121 root and 64 web). Theapps/serverfork test files are left to Fork CI.CommandPalette.logic.test.tsand the new fork test pass.Fork repair skill changes
SKILL.md: the section about new adopted heads now describes how to merge a rebased head:git apply --3way.