Skip to content

chore(sync): merge pingdotgg/t3code main - #383

Merged
Yarden-zamir merged 41 commits into
mainfrom
fork-sync/2026-10-27-0510
Oct 27, 2026
Merged

Yarden-zamir merged 41 commits into
mainfrom
fork-sync/2026-10-27-0510

Conversation

@Yarden-zamir

Copy link
Copy Markdown
Owner

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's openWithIntent, with the fork threadKind on the "new thread in" intent.
  • commandPaletteBus.ts: merged cleanly but dispatched "new thread in" without the fork thread kind. It now passes detail.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.
  • Terminal threads, "New terminal thread in…": upstream fills any view whose group is "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. openIntentView opens it for a terminal intent. New CommandPalette.logic.fork.test.ts covers both.

Fork fixes

  • ForkTerminalThreadScreen.tsx, useThreadRightPanel.tsx (terminal threads): fix(web): thread details popover closes when the card moves inline pingdotgg/t3code#25731 gives ChatCanvas a store, a thread and a preview, and the details card picks its own presentation.
    • The terminal screen creates the canvas store, and the hook passes the shown mini player's canvas preview, as ChatView does.
    • The details sit outside the canvas, so their presentation is always "popover". The removed onPresentationChange goes away.
  • CommandPalette.logic.test.ts: upstream's test gets the fork threadKind.

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 mergedHead is updated in the same commit:

Pull request New head What changed
pingdotgg#16653 4fac0b1 Rebase only.
pingdotgg#16654 29cecbe Rebase only.
pingdotgg#16655 3a6791a Rebase only. Its migration 070_PeerLinks stays out.
pingdotgg#16684 8ae4629 The author's own adaptation to the action tools. It matches #382. Its either wording, its environmentId order in thread/tools.ts and a schema comment replace mine. Its McpToolAccess and tool tests come in.
pingdotgg#16695 338ade3 Rebase. Its migration 071_PeerLinkFence stays out. Its link client and provenance tests come in.
pingdotgg#16698 c9349ba Rebase. Its linked environments search and permission tests come in. Its permission keys were already on fork main.
pingdotgg#16719 e6b3aec The follow failure handler is a named Effect.fn. A polling loop in its test becomes an awaited event. The evals import taskPrompt from delegatedTaskModes.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, a resultRun field, sidebar test fields, and tests the fork keeps in .fork.test.ts files. The provenance test gains the pull request's delegatedFrom assertion.

Upstream changes in fork areas

13 files changed on both sides. Besides the palette:

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). The apps/server fork test files are left to Fork CI.
  • CommandPalette.logic.test.ts and 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:
    • keep the fork side;
    • read the staged diff;
    • leave out re-added migrations and restore hunks that repeat fork main;
    • apply the commits that changed in the range diff with git apply --3way.

juliusmarminge and others added 30 commits October 26, 2026 19:25
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>
…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>
@Yarden-zamir
Yarden-zamir merged commit b54a89e into main Oct 27, 2026
20 of 21 checks passed
@Yarden-zamir
Yarden-zamir deleted the fork-sync/2026-10-27-0510 branch October 27, 2026 06:13
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.

5 participants