Skip to content

Commit dafa8ea

Browse files
DDKingerCopilot
andcommitted
Restore agent sessions with the persisted window layout
Windows Terminal already brings a window's tabs back after the window closes, after Terminal exits, and after Terminal crashes. That restore stopped at the shell: a pane that was running an agent CLI came back as a bare prompt, and the tab's agent pane came back empty. Carry the agent identity in the layout Terminal already persists, so both come back with it. * Stamp each tab's shell-pane agent bindings and its agent pane's ACP session, agent, custom command, view, open state, position and split size onto the actions GetWindowLayout serializes. Every existing writer picks them up, including the five-minute WindowEmperor timer that is what makes a crash recoverable at all. * Resume a shell pane's agent CLI on startup, gated on PersistedBufferPath so an agent session id arriving from a `wt` commandline still starts a normal shell. A resumed pane skips buffer restore because the CLI replays its own transcript. * Rebuild the agent pane and replay its conversation through a boot-time ACP session/load rather than from saved terminal output. * Record a WSL pane's directory, which is a Linux path and so never passed IsValidDirectory, through the new IsUsableStartingDirectory. * Never persist a pre-warmed helper that has no conversation: wta only projects a session id through TabSession::resumable_session_id once the conversation is meaningful. No new storage: this rides state.json and the existing buffer_{guid}.txt files. Design notes in doc/specs/agent-session-restore.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 63377204-36d8-4662-8114-584648f0bcd8
1 parent fa707a7 commit dafa8ea

47 files changed

Lines changed: 3087 additions & 191 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/specs/agent-session-restore.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Restoring agent sessions with a persisted window layout
2+
3+
## Abstract
4+
5+
Windows Terminal already brings a window's tabs back after the window closes,
6+
after Terminal exits, and after Terminal crashes. It replays a serialized list
7+
of actions from `state.json` and, with **Restore window layout and content**,
8+
seeds each pane's scrollback from a `buffer_{guid}.txt` file.
9+
10+
That restore stops at the shell. A pane that was running an agent CLI comes back
11+
as a bare prompt, and the tab's agent pane comes back empty — the conversation
12+
the user was in the middle of is gone even though the panes around it survived.
13+
14+
This describes how the persisted layout is extended to carry enough agent
15+
identity to put both back.
16+
17+
Keeping the shells themselves alive, saving individual closed tabs, and browsing
18+
a history of past sessions are separate features and are not described here.
19+
20+
## Storage
21+
22+
Nothing new is written. The agent metadata rides the two files Terminal already
23+
maintains:
24+
25+
| What | Where |
26+
| --- | --- |
27+
| Serialized tab layout, including agent metadata | `persistedWindowLayouts` in `state.json` |
28+
| Per-pane scrollback | `buffer_{guid}.txt` next to `state.json` |
29+
30+
Elevated windows already write to `elevated-state.json` and `elevated_{guid}.txt`,
31+
so an elevated session and an unelevated one never see each other's agents.
32+
33+
## Saving
34+
35+
`TerminalPage::GetWindowLayout` builds the same `WindowLayout` it always did,
36+
then `_AddAgentRestoreMetadata` stamps each tab's agent bindings onto the
37+
`NewTerminalArgs` of the actions that rebuild it. Because the metadata is part of
38+
the ordinary layout, every existing writer picks it up for free:
39+
40+
| Writer | Covers |
41+
| --- | --- |
42+
| `WindowEmperor::_persistState`, on a five-minute timer | **a crash**, and anything else that never runs shutdown code |
43+
| `WindowEmperor::_finalizeSessionPersistence` | closing the last window, quitting, sign-out, shutdown |
44+
| `TerminalPage::_SaveWorkspaceIfNeeded` | named windows, which persist as workspaces |
45+
46+
Two kinds of binding are recorded.
47+
48+
**Shell panes.** `_paneAgentSessions` holds the most recent agent session seen in
49+
each shell pane, keyed by the pane's connection `SessionId`. The binding arrives
50+
either from the agent hooks or, when no hook is installed, from wta's own
51+
`pane_agent_session_changed` event. It is kept after the CLI exits, so a save
52+
that happens later can still describe how to resume it.
53+
54+
**The agent pane.** Its ACP session, agent identity (including the WSL distro,
55+
folded into one `AgentPaneBackend` token), custom command, view, open state,
56+
position, and split size are recorded on the tab's first action.
57+
58+
`RemoveAgentPaneSessionFromShellBindings` then clears the agent pane's own ACP
59+
session from the shell pane that hosts the helper. Without it the restore would
60+
relaunch that CLI twice — once as an agent pane and once as a shell.
61+
62+
An empty pre-warmed helper is never recorded: wta only projects a session id
63+
through `TabSession::resumable_session_id` once the conversation is meaningful,
64+
so a tab the user never chatted in restores its pane layout and no conversation.
65+
66+
## Restoring
67+
68+
`TerminalWindow::Initialize` already replays `WindowLayout::TabLayout()` for both
69+
"Restore window layout" modes. Before handing the actions to `TerminalPage`, it
70+
calls `SetPersistedLayoutAgentRestorePaths`, which points each shell pane at its
71+
`buffer_{guid}.txt` through `NewTerminalArgs::PersistedBufferPath`.
72+
73+
That path does double duty. It is the file `ControlCore::RestoreFromPath` seeds
74+
the buffer from, and it is the marker that says "this pane came out of a
75+
persisted layout" — `ShouldResumeAgentSession` requires it, so an `AgentSessionId`
76+
arriving any other way (a `wt` commandline, say) starts a normal shell rather
77+
than silently re-attaching to an old conversation.
78+
79+
**Shell panes.** `_MakeTerminalPane` rewrites the pane's commandline to the
80+
agent's resume command, either the one recorded at save time or one rebuilt from
81+
the agent id. A resumed pane skips buffer restore: the CLI replays its own
82+
transcript, and seeding the buffer as well would show the conversation twice and
83+
compound it on every restart.
84+
85+
**The agent pane.** Panes whose agent session belongs to an agent pane are
86+
skipped by `SetPersistedLayoutAgentRestorePaths` for the same reason. The pane
87+
itself is rebuilt through `_pendingAgentPaneRestores` with its recorded view,
88+
open state, position, and size, and its conversation comes back through a
89+
boot-time ACP `session/load` driven by wta's `--initial-load-session-id`.
90+
91+
## Capabilities and limitations
92+
93+
* Layout and agent bindings survive a crash, because the five-minute timer has
94+
already written them. At most the last five minutes of arrangement is lost.
95+
* Scrollback does **not** survive a crash. `buffer_{guid}.txt` is only written on
96+
the way out, which is Windows Terminal's existing behavior and is unchanged
97+
here. A resumed agent CLI is unaffected — it replays its own history.
98+
* Running processes are not preserved. A build that was halfway through when the
99+
window closed is not halfway through when it comes back.
100+
* Restoring is gated on the existing **Settings → Startup → "When Terminal
101+
starts"** preference. `defaultProfile` restores nothing, as before.

0 commit comments

Comments
 (0)