@layorixinc/axterm 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +903 -506
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -46,6 +46,154 @@ var __export = (target, all) => {
46
46
  };
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
 
49
+ // packages/core/src/agent-registry.ts
50
+ function agentProfile(id) {
51
+ const known = PROFILES[id];
52
+ if (known)
53
+ return known;
54
+ return {
55
+ id,
56
+ displayName: id,
57
+ title: {
58
+ workingLabel: id,
59
+ permissionLabel: `${id} - action required`,
60
+ idleLabel: `${id} ready`
61
+ },
62
+ hostHookSource: id
63
+ };
64
+ }
65
+ function renderAgentTitle(profile, state, frame) {
66
+ if (state === undefined)
67
+ return;
68
+ const { title } = profile;
69
+ if (state === "working") {
70
+ if (title.synthesizeWorking === false)
71
+ return;
72
+ const spinner = SPINNER_FRAMES[(frame % SPINNER_FRAMES.length + SPINNER_FRAMES.length) % SPINNER_FRAMES.length];
73
+ return `${spinner} ${title.workingLabel}`;
74
+ }
75
+ if (title.synthesizeTerminal === false)
76
+ return;
77
+ return state === "done" ? title.idleLabel : title.permissionLabel;
78
+ }
79
+ function oscTitle(title) {
80
+ const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
81
+ return `\x1B]0;${safe}\x07`;
82
+ }
83
+ var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, REGISTERED_AGENTS, MAX_TITLE_LENGTH = 200;
84
+ var init_agent_registry = __esm(() => {
85
+ SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
86
+ PROFILES = {
87
+ omp: {
88
+ id: "omp",
89
+ displayName: "OMP",
90
+ title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
91
+ hostHookSource: "omp",
92
+ approvalBypassArgs: ["--approval-mode", "yolo"],
93
+ quota: "gauge",
94
+ resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
95
+ },
96
+ pi: {
97
+ id: "pi",
98
+ displayName: "Pi",
99
+ title: { workingLabel: "Pi", permissionLabel: "Pi - action required", idleLabel: "Pi ready" },
100
+ hostHookSource: "pi",
101
+ resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
102
+ },
103
+ codex: {
104
+ id: "codex",
105
+ displayName: "Codex",
106
+ title: {
107
+ workingLabel: "Codex",
108
+ permissionLabel: "Codex - action required",
109
+ idleLabel: "Codex ready",
110
+ synthesizeWorking: false
111
+ },
112
+ hostHookSource: "codex",
113
+ quota: "gauge",
114
+ approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
115
+ structuredApprovals: true,
116
+ resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
117
+ },
118
+ opencode: {
119
+ id: "opencode",
120
+ displayName: "OpenCode",
121
+ title: {
122
+ workingLabel: "OpenCode",
123
+ permissionLabel: "OpenCode - action required",
124
+ idleLabel: "OpenCode ready",
125
+ synthesizeTerminal: false
126
+ },
127
+ hostHookSource: "opencode",
128
+ resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
129
+ },
130
+ cursor: {
131
+ id: "cursor",
132
+ displayName: "Cursor Agent",
133
+ title: {
134
+ workingLabel: "Cursor Agent",
135
+ permissionLabel: "Cursor - action required",
136
+ idleLabel: "Cursor ready"
137
+ },
138
+ hostHookSource: "cursor",
139
+ resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
140
+ },
141
+ droid: {
142
+ id: "droid",
143
+ displayName: "Droid",
144
+ title: { workingLabel: "Droid", permissionLabel: "Droid - action required", idleLabel: "Droid ready" },
145
+ hostHookSource: "droid",
146
+ resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
147
+ },
148
+ claude: {
149
+ id: "claude",
150
+ displayName: "Claude Code",
151
+ title: {
152
+ workingLabel: "Claude",
153
+ permissionLabel: "Claude - action required",
154
+ idleLabel: "Claude ready",
155
+ synthesizeWorking: false,
156
+ synthesizeTerminal: false
157
+ },
158
+ hostHookSource: "claude",
159
+ quota: "event",
160
+ approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
161
+ resume: { argv: ["--resume", "{ref}"], ref: "sessionId", selfNamed: "uuid" }
162
+ },
163
+ gemini: {
164
+ id: "gemini",
165
+ displayName: "Gemini CLI",
166
+ title: {
167
+ workingLabel: "Gemini",
168
+ permissionLabel: "Gemini - action required",
169
+ idleLabel: "Gemini ready",
170
+ synthesizeWorking: false,
171
+ synthesizeTerminal: false
172
+ },
173
+ hostHookSource: "gemini",
174
+ approvalBypassArgs: ["--approval-mode", "yolo"],
175
+ hookAck: "{}",
176
+ approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
177
+ resume: { argv: ["--resume", "{ref}"], ref: "sessionId", selfNamed: "bare" }
178
+ }
179
+ };
180
+ REGISTERED_AGENTS = Object.keys(PROFILES);
181
+ });
182
+
183
+ // packages/core/src/session-id.ts
184
+ function uuidFromSessionId(sessionId) {
185
+ const hex = sessionId.replace(/-/g, "");
186
+ if (hex.length !== 32)
187
+ throw new Error(`session id is not 32 hex characters: ${sessionId}`);
188
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
189
+ }
190
+ function sessionIdFromUuid(ref) {
191
+ if (!ref.includes("-"))
192
+ return;
193
+ const stripped = ref.replace(/-/g, "");
194
+ return /^[0-9a-f]{4,32}$/i.test(stripped) ? stripped.toLowerCase() : undefined;
195
+ }
196
+
49
197
  // packages/core/src/mcp-config.ts
50
198
  function mcpServerEntry(launch, contextPath) {
51
199
  return {
@@ -76,18 +224,6 @@ function codexMcpArgs(launch, contextPath) {
76
224
  var MCP_SERVER_NAME = "axterm", MCP_CONTEXT_ENV = "AXTERM_MCP_CONTEXT";
77
225
 
78
226
  // packages/core/src/agent-launch.ts
79
- function uuidFromSessionId(sessionId) {
80
- const hex = sessionId.replace(/-/g, "");
81
- if (hex.length !== 32)
82
- throw new Error(`session id is not 32 hex characters: ${sessionId}`);
83
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
84
- }
85
- function sessionIdFromUuid(ref) {
86
- if (!ref.includes("-"))
87
- return;
88
- const stripped = ref.replace(/-/g, "");
89
- return /^[0-9a-f]{4,32}$/i.test(stripped) ? stripped.toLowerCase() : undefined;
90
- }
91
227
  function withAppServer(argv, url) {
92
228
  const out = [];
93
229
  for (let i = 0;i < argv.length; i++) {
@@ -102,7 +238,13 @@ function withAppServer(argv, url) {
102
238
  return [out[0], "--remote", url, ...out.slice(1)];
103
239
  }
104
240
  function resumesAConversation(args) {
105
- return args.some((arg) => CLAUDE_RESUME_FLAGS.has(arg) || arg.startsWith("--resume=") || arg.startsWith("--continue="));
241
+ return args.some((arg) => RESUME_FLAGS.has(arg) || arg.startsWith("--resume=") || arg.startsWith("--continue="));
242
+ }
243
+ function namingFlag(agent, sessionId, extra) {
244
+ const named = agentProfile(agent).resume?.selfNamed;
245
+ if (named === undefined || resumesAConversation(extra))
246
+ return [];
247
+ return ["--session-id", named === "uuid" ? uuidFromSessionId(sessionId) : sessionId];
106
248
  }
107
249
  function buildAgentCommand(input) {
108
250
  const extra = [...input.extraArgs ?? []];
@@ -112,17 +254,14 @@ function buildAgentCommand(input) {
112
254
  "--settings",
113
255
  input.claudeSettingsPath,
114
256
  ...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
115
- "--session-id",
116
- uuidFromSessionId(input.sessionId),
117
- ...resumesAConversation(extra) ? ["--fork-session"] : [],
257
+ ...namingFlag("claude", input.sessionId, extra),
118
258
  ...extra
119
259
  ];
120
260
  }
121
261
  if (input.agent === "gemini") {
122
262
  return [
123
263
  "gemini",
124
- "--session-id",
125
- input.sessionId,
264
+ ...namingFlag("gemini", input.sessionId, extra),
126
265
  ...extra
127
266
  ];
128
267
  }
@@ -159,61 +298,16 @@ function resolveAgentArgs(input) {
159
298
  });
160
299
  return groups.filter((group, index) => group.flag === undefined || lastIndex.get(group.flag) === index).flatMap((group) => group.tokens);
161
300
  }
162
- function operatorArgsFrom(storedArgv) {
163
- if (storedArgv === undefined || storedArgv.length === 0)
164
- return [];
165
- const kept = [];
166
- for (let i = 1;i < storedArgv.length; i++) {
167
- const token = storedArgv[i];
168
- const width = AXTERM_OWNED_FLAGS[token];
169
- if (width !== undefined) {
170
- i += width - 1;
171
- continue;
172
- }
173
- if (/^--(resume|continue)=/.test(token))
174
- continue;
175
- kept.push(token);
176
- }
177
- return kept;
178
- }
179
- function buildAgentResumeCommand(input) {
180
- const operator = input.extraArgs ?? operatorArgsFrom(input.storedArgv);
181
- if (input.agent === "claude") {
182
- return [
183
- "claude",
184
- "--settings",
185
- input.claudeSettingsPath,
186
- ...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
187
- ...operator,
188
- "--resume",
189
- uuidFromSessionId(input.sessionId)
190
- ];
191
- }
192
- if (input.agent === "gemini") {
193
- return ["gemini", ...operator, "--resume", input.sessionId];
194
- }
195
- return buildAgentCommand({ ...input, extraArgs: operator });
196
- }
197
301
  function isAgentId(value) {
198
302
  return AGENT_IDS.includes(value);
199
303
  }
200
304
  function supportedAgents() {
201
305
  return AGENT_IDS;
202
306
  }
203
- var CLAUDE_RESUME_FLAGS, AXTERM_OWNED_FLAGS, AGENT_IDS;
307
+ var RESUME_FLAGS, AGENT_IDS;
204
308
  var init_agent_launch = __esm(() => {
205
- CLAUDE_RESUME_FLAGS = new Set(["--resume", "-r", "--continue", "-c"]);
206
- AXTERM_OWNED_FLAGS = {
207
- "--settings": 2,
208
- "--mcp-config": 2,
209
- "--session-id": 2,
210
- "--extension": 2,
211
- "--fork-session": 1,
212
- "--resume": 2,
213
- "-r": 2,
214
- "--continue": 1,
215
- "-c": 1
216
- };
309
+ init_agent_registry();
310
+ RESUME_FLAGS = new Set(["--resume", "-r", "--continue", "-c"]);
217
311
  AGENT_IDS = ["omp", "claude", "codex", "gemini"];
218
312
  });
219
313
 
@@ -944,6 +1038,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
944
1038
  "cli.session.sent": "sent",
945
1039
  "cli.session.sentKey": "sent {key}",
946
1040
  "cli.session.selected": "selected option {index}",
1041
+ "cli.session.selectedDialog": "answered the agent's first-run dialog",
947
1042
  "cli.resume.done": "resumed session {id} ({command})",
948
1043
  "cli.update.current": "axterm {version} is the latest version",
949
1044
  "cli.update.available": "axterm {latest} is available (you have {current})",
@@ -1013,7 +1108,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1013
1108
  "err.telegram.noToken": "no bot token is configured \u2014 run `axterm setup` or pass --token",
1014
1109
  "tg.help.general.header": "axterm commands (General topic):",
1015
1110
  "tg.help.general.projects": "/projects \u2014 registered projects",
1016
- "tg.help.general.new": "/new <project> [name] \u2014 new omp session",
1111
+ "tg.help.general.new": "/new <project> [name] [-- agent flags] \u2014 new session (e.g. --resume <conversation>)",
1017
1112
  "tg.help.general.wt": "/wt <project> <branch> \u2014 worktree session",
1018
1113
  "tg.help.general.fanout": "/fanout <project> <count|branches> <prompt> \u2014 run in parallel",
1019
1114
  "tg.help.general.worktrees": "/worktrees <project> \xB7 /rm <project> <branch> [force]",
@@ -1035,6 +1130,9 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1035
1130
  "tg.prompt.confirm": "the session is waiting on a confirmation:",
1036
1131
  "tg.prompt.secretAsk": "the session is asking for a secret. Reply with it and the message will be deleted:",
1037
1132
  "tg.prompt.secretDisabled": "the session is waiting for a password. Answering from chat is off (channels.telegram.secretPrompts); attach and type it, or use an SSH key",
1133
+ "tg.prompt.secretSubmitted": "the secret reached the session",
1134
+ "tg.prompt.secretStale": "that prompt is no longer waiting, so nothing was typed into the session",
1135
+ "tg.prompt.secretUndeleted": "\u26A0\uFE0F Telegram refused to delete that message \u2014 the secret is still in the chat; delete it yourself",
1038
1136
  "tg.session.screenFile": "terminal screen",
1039
1137
  "tg.session.none": "no sessions",
1040
1138
  "tg.prompt.failed": "\u26A0\uFE0F Delivery failed: {error}",
@@ -1154,6 +1252,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1154
1252
  "err.agent.unknown": "unknown agent '{agent}' \u2014 supported: {supported}",
1155
1253
  "err.staging.dir": "could not create the session's staging directory {dir} ({error}) \u2014 fix the permissions, or set defaults.staging to runtime",
1156
1254
  "err.staging.notIgnored": "git can see inside {dir}, so a staged file would show up in `git add -A` \u2014 let axterm write its own {file}, or set defaults.staging to runtime",
1255
+ "err.claude.conversationBusy": "session {session} is already in conversation {id} \u2014 two sessions writing one transcript would corrupt it; kill that one first",
1157
1256
  "err.claude.noConversation": "claude has no conversation {id} in its store on this machine \u2014 a conversation lives on the machine it ran on, so this one cannot be resumed here",
1158
1257
  "codex.approval.denied": "Denied from the axterm channel",
1159
1258
  "err.codex.noAuth": "codex is not logged in ({authFile} not found) \u2014 run `codex login` once, then start the session again",
@@ -1245,6 +1344,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1245
1344
  "cli.session.sent": "\uC804\uB2EC\uB428",
1246
1345
  "cli.session.sentKey": "{key} \uC804\uB2EC\uB428",
1247
1346
  "cli.session.selected": "{index}\uBC88 \uD56D\uBAA9 \uC120\uD0DD\uB428",
1347
+ "cli.session.selectedDialog": "\uC5D0\uC774\uC804\uD2B8\uC758 \uCCAB \uC2E4\uD589 \uB300\uD654\uC0C1\uC790\uC5D0 \uC751\uB2F5\uD588\uC2B5\uB2C8\uB2E4",
1248
1348
  "cli.resume.done": "\uC138\uC158 {id} \uC7AC\uAC1C\uB428 ({command})",
1249
1349
  "cli.update.current": "axterm {version} \uC774 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4",
1250
1350
  "cli.update.available": "axterm {latest} \uC744(\uB97C) \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (\uD604\uC7AC {current})",
@@ -1307,7 +1407,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1307
1407
  "cli.telegram.missingSettings": "telegram link: \uD1A0\uD070/\uADF8\uB8F9/\uC0AC\uC6A9\uC790 \uC815\uBCF4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --token/--group/--user \uD50C\uB798\uADF8, TELEGRAM_* \uD658\uACBD\uBCC0\uC218, \uB610\uB294 \uD604\uC7AC \uD3F4\uB354\uC758 .env \uD30C\uC77C \uC911 \uD558\uB098\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
1308
1408
  "tg.help.general.header": "axterm \uBA85\uB839 (General \uD1A0\uD53D):",
1309
1409
  "tg.help.general.projects": "/projects \u2014 \uB4F1\uB85D\uB41C \uD504\uB85C\uC81D\uD2B8",
1310
- "tg.help.general.new": "/new <\uD504\uB85C\uC81D\uD2B8> [\uC774\uB984] \u2014 \uC0C8 omp \uC138\uC158",
1410
+ "tg.help.general.new": "/new <\uD504\uB85C\uC81D\uD2B8> [\uC774\uB984] [\uC5D0\uC774\uC804\uD2B8 \uD50C\uB798\uADF8] \u2014 \uC0C8 \uC138\uC158 (\uC608: --resume <\uB300\uD654>)",
1311
1411
  "tg.help.general.wt": "/wt <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> \u2014 worktree \uC138\uC158",
1312
1412
  "tg.help.general.fanout": "/fanout <\uD504\uB85C\uC81D\uD2B8> <\uAC1C\uC218|\uBE0C\uB79C\uCE58\uB4E4> <\uD504\uB86C\uD504\uD2B8> \u2014 \uBCD1\uB82C \uC2E4\uD589",
1313
1413
  "tg.help.general.worktrees": "/worktrees <\uD504\uB85C\uC81D\uD2B8> \xB7 /rm <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> [force]",
@@ -1336,6 +1436,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
1336
1436
  "tg.prompt.confirm": "\uC138\uC158\uC774 \uD655\uC778\uC744 \uAE30\uB2E4\uB9AC\uACE0 \uC788\uC2B5\uB2C8\uB2E4:",
1337
1437
  "tg.prompt.secretAsk": "\uC138\uC158\uC774 \uBE44\uBC00\uAC12\uC744 \uC694\uAD6C\uD569\uB2C8\uB2E4. \uB2F5\uC7A5\uD558\uBA74 \uADF8 \uBA54\uC2DC\uC9C0\uB294 \uC0AD\uC81C\uB429\uB2C8\uB2E4:",
1338
1438
  "tg.prompt.secretDisabled": "\uC138\uC158\uC774 \uBE44\uBC00\uBC88\uD638\uB97C \uAE30\uB2E4\uB9AC\uACE0 \uC788\uC2B5\uB2C8\uB2E4. \uCC44\uD305 \uC785\uB825\uC740 \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4 (channels.telegram.secretPrompts). attach \uD574\uC11C \uC785\uB825\uD558\uAC70\uB098 SSH \uD0A4\uB97C \uC4F0\uC138\uC694",
1439
+ "tg.prompt.secretSubmitted": "\uBE44\uBC00\uAC12\uC774 \uC138\uC158\uC5D0 \uC804\uB2EC\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
1440
+ "tg.prompt.secretStale": "\uADF8 \uD504\uB86C\uD504\uD2B8\uB294 \uB354 \uC774\uC0C1 \uAE30\uB2E4\uB9AC\uACE0 \uC788\uC9C0 \uC54A\uC544 \uC138\uC158\uC5D0 \uC544\uBB34\uAC83\uB3C4 \uC785\uB825\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4",
1441
+ "tg.prompt.secretUndeleted": "\u26A0\uFE0F Telegram\uC774 \uBA54\uC2DC\uC9C0 \uC0AD\uC81C\uB97C \uAC70\uBD80\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uBE44\uBC00\uAC12\uC774 \uC544\uC9C1 \uCC44\uD305\uC5D0 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uC9C1\uC811 \uC0AD\uC81C\uD558\uC138\uC694",
1339
1442
  "tg.session.screenFile": "\uD130\uBBF8\uB110 \uD654\uBA74",
1340
1443
  "tg.session.none": "\uC138\uC158 \uC5C6\uC74C",
1341
1444
  "tg.prompt.failed": "\u26A0\uFE0F \uC804\uB2EC \uC2E4\uD328: {error}",
@@ -1455,6 +1558,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1455
1558
  "err.agent.unknown": "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uC774\uC804\uD2B8 '{agent}' \uC785\uB2C8\uB2E4. \uC9C0\uC6D0: {supported}",
1456
1559
  "err.staging.dir": "\uC138\uC158\uC758 \uC2A4\uD14C\uC774\uC9D5 \uB514\uB809\uD130\uB9AC {dir} \uC744(\uB97C) \uB9CC\uB4E4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 ({error}). \uAD8C\uD55C\uC744 \uACE0\uCE58\uAC70\uB098 defaults.staging \uC744 runtime \uC73C\uB85C \uC124\uC815\uD558\uC138\uC694",
1457
1560
  "err.staging.notIgnored": "git\uC774 {dir} \uC548\uC744 \uBCFC \uC218 \uC788\uC5B4\uC11C, \uC2A4\uD14C\uC774\uC9D5\uD55C \uD30C\uC77C\uC774 `git add -A` \uC5D0 \uC7A1\uD799\uB2C8\uB2E4. axterm\uC774 \uC790\uCCB4 {file} \uC744(\uB97C) \uC4F0\uAC8C \uD558\uAC70\uB098 defaults.staging \uC744 runtime \uC73C\uB85C \uC124\uC815\uD558\uC138\uC694",
1561
+ "err.claude.conversationBusy": "\uC138\uC158 {session} \uC774(\uAC00) \uC774\uBBF8 \uB300\uD654 {id} \uC5D0 \uB4E4\uC5B4\uAC00 \uC788\uC2B5\uB2C8\uB2E4. \uD55C transcript\uC5D0 \uB450 \uC138\uC158\uC774 \uC4F0\uBA74 \uC190\uC0C1\uB418\uBBC0\uB85C \uBA3C\uC800 \uADF8 \uC138\uC158\uC744 \uC885\uB8CC\uD558\uC138\uC694",
1458
1562
  "err.claude.noConversation": "\uC774 \uBA38\uC2E0\uC758 claude \uC800\uC7A5\uC18C\uC5D0 \uB300\uD654 {id} \uC774(\uAC00) \uC5C6\uC2B5\uB2C8\uB2E4. \uB300\uD654\uB294 \uC2E4\uD589\uB41C \uBA38\uC2E0\uC5D0 \uB0A8\uC73C\uBBC0\uB85C \uC5EC\uAE30\uC11C\uB294 \uC774\uC5B4\uAC08 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
1459
1563
  "codex.approval.denied": "axterm \uCC44\uB110\uC5D0\uC11C \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
1460
1564
  "err.codex.noAuth": "codex\uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 ({authFile} \uC5C6\uC74C). `codex login` \uC744 \uD55C \uBC88 \uC2E4\uD589\uD55C \uB4A4 \uC138\uC158\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC138\uC694",
@@ -1645,144 +1749,16 @@ var init_setup_wizard = __esm(() => {
1645
1749
  wizardConfig = new WeakMap;
1646
1750
  });
1647
1751
 
1648
- // packages/core/src/agent-registry.ts
1649
- function agentProfile(id) {
1650
- const known = PROFILES[id];
1651
- if (known)
1652
- return known;
1653
- return {
1654
- id,
1655
- displayName: id,
1656
- title: {
1657
- workingLabel: id,
1658
- permissionLabel: `${id} - action required`,
1659
- idleLabel: `${id} ready`
1660
- },
1661
- hostHookSource: id
1662
- };
1663
- }
1664
- function renderAgentTitle(profile, state, frame) {
1665
- if (state === undefined)
1666
- return;
1667
- const { title } = profile;
1668
- if (state === "working") {
1669
- if (title.synthesizeWorking === false)
1670
- return;
1671
- const spinner = SPINNER_FRAMES[(frame % SPINNER_FRAMES.length + SPINNER_FRAMES.length) % SPINNER_FRAMES.length];
1672
- return `${spinner} ${title.workingLabel}`;
1673
- }
1674
- if (title.synthesizeTerminal === false)
1675
- return;
1676
- return state === "done" ? title.idleLabel : title.permissionLabel;
1677
- }
1678
- function oscTitle(title) {
1679
- const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
1680
- return `\x1B]0;${safe}\x07`;
1681
- }
1682
- var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, REGISTERED_AGENTS, MAX_TITLE_LENGTH = 200;
1683
- var init_agent_registry = __esm(() => {
1684
- SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1685
- PROFILES = {
1686
- omp: {
1687
- id: "omp",
1688
- displayName: "OMP",
1689
- title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
1690
- hostHookSource: "omp",
1691
- approvalBypassArgs: ["--approval-mode", "yolo"],
1692
- quota: "gauge",
1693
- resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
1694
- },
1695
- pi: {
1696
- id: "pi",
1697
- displayName: "Pi",
1698
- title: { workingLabel: "Pi", permissionLabel: "Pi - action required", idleLabel: "Pi ready" },
1699
- hostHookSource: "pi",
1700
- resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
1701
- },
1702
- codex: {
1703
- id: "codex",
1704
- displayName: "Codex",
1705
- title: {
1706
- workingLabel: "Codex",
1707
- permissionLabel: "Codex - action required",
1708
- idleLabel: "Codex ready",
1709
- synthesizeWorking: false
1710
- },
1711
- hostHookSource: "codex",
1712
- quota: "gauge",
1713
- approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
1714
- structuredApprovals: true,
1715
- resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
1716
- },
1717
- opencode: {
1718
- id: "opencode",
1719
- displayName: "OpenCode",
1720
- title: {
1721
- workingLabel: "OpenCode",
1722
- permissionLabel: "OpenCode - action required",
1723
- idleLabel: "OpenCode ready",
1724
- synthesizeTerminal: false
1725
- },
1726
- hostHookSource: "opencode",
1727
- resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
1728
- },
1729
- cursor: {
1730
- id: "cursor",
1731
- displayName: "Cursor Agent",
1732
- title: {
1733
- workingLabel: "Cursor Agent",
1734
- permissionLabel: "Cursor - action required",
1735
- idleLabel: "Cursor ready"
1736
- },
1737
- hostHookSource: "cursor",
1738
- resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
1739
- },
1740
- droid: {
1741
- id: "droid",
1742
- displayName: "Droid",
1743
- title: { workingLabel: "Droid", permissionLabel: "Droid - action required", idleLabel: "Droid ready" },
1744
- hostHookSource: "droid",
1745
- resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
1746
- },
1747
- claude: {
1748
- id: "claude",
1749
- displayName: "Claude Code",
1750
- title: {
1751
- workingLabel: "Claude",
1752
- permissionLabel: "Claude - action required",
1753
- idleLabel: "Claude ready",
1754
- synthesizeWorking: false,
1755
- synthesizeTerminal: false
1756
- },
1757
- hostHookSource: "claude",
1758
- quota: "event",
1759
- approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
1760
- resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
1761
- },
1762
- gemini: {
1763
- id: "gemini",
1764
- displayName: "Gemini CLI",
1765
- title: {
1766
- workingLabel: "Gemini",
1767
- permissionLabel: "Gemini - action required",
1768
- idleLabel: "Gemini ready",
1769
- synthesizeWorking: false,
1770
- synthesizeTerminal: false
1771
- },
1772
- hostHookSource: "gemini",
1773
- approvalBypassArgs: ["--approval-mode", "yolo"],
1774
- hookAck: "{}",
1775
- approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
1776
- resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
1777
- }
1778
- };
1779
- REGISTERED_AGENTS = Object.keys(PROFILES);
1780
- });
1781
-
1782
1752
  // packages/core/src/agent-resume.ts
1783
1753
  function resumeReference(profile, source) {
1784
- const value = profile.resume?.ref === "sessionId" ? source.agentSessionId : source.agentSessionFile;
1785
- return value !== undefined && value.length > 0 ? value : undefined;
1754
+ const reported = profile.resume?.ref === "sessionId" ? source.agentSessionId : source.agentSessionFile;
1755
+ if (reported !== undefined && reported.length > 0)
1756
+ return reported;
1757
+ const named = profile.resume?.selfNamed;
1758
+ const own = source.ownSessionId;
1759
+ if (named === undefined || own === undefined || own.length === 0)
1760
+ return;
1761
+ return named === "uuid" ? uuidFromSessionId(own) : own;
1786
1762
  }
1787
1763
  function agentIdFromCommand(command) {
1788
1764
  const head = command[0] ?? "";
@@ -1809,17 +1785,23 @@ function withoutExistingResume(command, fragment) {
1809
1785
  }
1810
1786
  return command;
1811
1787
  }
1788
+ function withoutNamingFlag(command) {
1789
+ const at = command.indexOf("--session-id");
1790
+ return at < 0 ? command : [...command.slice(0, at), ...command.slice(at + 2)];
1791
+ }
1812
1792
  function buildResumeCommand(command, profile, reference) {
1813
1793
  const resume = profile.resume;
1814
1794
  const binary = command[0];
1815
1795
  if (!resume || binary === undefined || reference.length === 0)
1816
1796
  return;
1817
1797
  const template = resume.argv;
1818
- const base = withoutExistingResume(command, template);
1798
+ const stripped = withoutExistingResume(command, template);
1799
+ const base = resume.selfNamed === undefined ? stripped : withoutNamingFlag(stripped);
1819
1800
  const fragment = template.map((token) => token.replaceAll(REF, reference));
1820
1801
  return resume.form === "subcommand" ? [base[0], ...fragment, ...base.slice(1)] : [...base, ...fragment];
1821
1802
  }
1822
1803
  var REF = "{ref}";
1804
+ var init_agent_resume = () => {};
1823
1805
 
1824
1806
  // packages/core/src/self-spawn.ts
1825
1807
  function daemonLaunch(input) {
@@ -1848,9 +1830,9 @@ function ptyHostLaunch(input) {
1848
1830
  function isPtyHostInvocation(args) {
1849
1831
  return args[0] === PTY_HOST_ARGV_FLAG;
1850
1832
  }
1851
- function hookPostTarget(env, agent) {
1852
- const port = Number(env["AXTERM_HOOK_PORT"]);
1853
- const token = env["AXTERM_HOOK_TOKEN"];
1833
+ function hookPostTarget(env, agent, override) {
1834
+ const port = override?.port ?? Number(env["AXTERM_HOOK_PORT"]);
1835
+ const token = override?.token ?? env["AXTERM_HOOK_TOKEN"];
1854
1836
  const sessionId = env["AXTERM_SESSION_ID"];
1855
1837
  const launchToken = env["AXTERM_LAUNCH_TOKEN"];
1856
1838
  if (!Number.isSafeInteger(port) || port <= 0)
@@ -1867,6 +1849,13 @@ function hookPostTarget(env, agent) {
1867
1849
  }
1868
1850
  };
1869
1851
  }
1852
+ function parseHookEndpoint(text) {
1853
+ const port = Number(/^AXTERM_HOOK_PORT=(\d+)$/m.exec(text)?.[1]);
1854
+ const token = /^AXTERM_HOOK_TOKEN=(\S+)$/m.exec(text)?.[1];
1855
+ if (!Number.isSafeInteger(port) || port <= 0 || !token)
1856
+ return;
1857
+ return { port, token };
1858
+ }
1870
1859
  function isDaemonInvocation(args) {
1871
1860
  return args[0] === DAEMON_ARGV_FLAG;
1872
1861
  }
@@ -1992,6 +1981,7 @@ function planUpdateRestart(input) {
1992
1981
  var SEMVER;
1993
1982
  var init_update = __esm(() => {
1994
1983
  init_agent_registry();
1984
+ init_agent_resume();
1995
1985
  SEMVER = /^\d+\.\d+\.\d+/;
1996
1986
  });
1997
1987
 
@@ -2192,6 +2182,30 @@ function buildMenuSelectKeys(index, normalizePresses = MENU_NORMALIZE_PRESSES) {
2192
2182
  }
2193
2183
  var MENU_UP = "\x1B[A", MENU_DOWN = "\x1B[B", MENU_SELECT = "\r", MENU_NORMALIZE_PRESSES = 24;
2194
2184
 
2185
+ // packages/core/src/adopt.ts
2186
+ function resolveSessionCwd(input) {
2187
+ if (!input.preferConversation)
2188
+ return input.requested;
2189
+ const own = input.conversationCwd;
2190
+ return own !== undefined && own.length > 0 ? own : input.requested;
2191
+ }
2192
+ function conversationHeldBy(sessions, conversationId) {
2193
+ if (conversationId.length === 0)
2194
+ return;
2195
+ return sessions.find((session) => {
2196
+ if (session.state !== "running")
2197
+ return false;
2198
+ if (session.agentSessionId === conversationId)
2199
+ return true;
2200
+ const argv = session.agentCommand;
2201
+ return argv.some((token, index) => {
2202
+ if (token === "--resume" || token === "-r" || token === "--session-id")
2203
+ return argv[index + 1] === conversationId;
2204
+ return token === `--resume=${conversationId}`;
2205
+ });
2206
+ });
2207
+ }
2208
+
2195
2209
  // packages/core/src/agent-status.ts
2196
2210
  function next(prev, state, now) {
2197
2211
  return {
@@ -2201,17 +2215,29 @@ function next(prev, state, now) {
2201
2215
  stateStartedAt: prev?.state === state ? prev.stateStartedAt : now
2202
2216
  };
2203
2217
  }
2204
- function reduceAgentState(prev, payload, now) {
2218
+ function withConversationId(events, identity) {
2219
+ const id = identity.sessionId;
2220
+ const subagent = identity.subagentId;
2221
+ if (typeof id !== "string" || id === "" || typeof subagent === "string" && subagent !== "") {
2222
+ return [...events];
2223
+ }
2224
+ return events.map((event) => ({ session_id: id, ...event }));
2225
+ }
2226
+ function reduceAgentState(prev, payload, now) {
2227
+ const result = reduceState(prev, payload, now);
2228
+ const identity = {
2229
+ ...payload.session_id !== undefined ? { agentSessionId: payload.session_id } : {},
2230
+ ...payload.session_file !== undefined ? { agentSessionFile: payload.session_file } : {},
2231
+ ...payload.session_name !== undefined ? { sessionName: payload.session_name } : {}
2232
+ };
2233
+ if (Object.keys(identity).length === 0)
2234
+ return result;
2235
+ return { ...result, meta: { ...identity, ...result.meta } };
2236
+ }
2237
+ function reduceState(prev, payload, now) {
2205
2238
  switch (payload.event) {
2206
2239
  case "session_start":
2207
- return {
2208
- entry: prev,
2209
- meta: {
2210
- agentSessionId: payload.session_id,
2211
- agentSessionFile: payload.session_file,
2212
- sessionName: payload.session_name
2213
- }
2214
- };
2240
+ return { entry: prev };
2215
2241
  case "before_agent_start": {
2216
2242
  const entry = next(prev, prev?.state ?? "working", now);
2217
2243
  entry.prompt = payload.prompt ?? "";
@@ -2378,7 +2404,18 @@ var init_pty_protocol = __esm(() => {
2378
2404
  Data: 2
2379
2405
  };
2380
2406
  MAX_FRAME_BYTES = 8 * 1024 * 1024;
2381
- KNOWN_TYPES = new Set(["hello", "spawn", "resize", "kill", "welcome", "spawned", "exit", "error"]);
2407
+ KNOWN_TYPES = new Set([
2408
+ "hello",
2409
+ "spawn",
2410
+ "subscribe",
2411
+ "resize",
2412
+ "kill",
2413
+ "welcome",
2414
+ "spawned",
2415
+ "subscribed",
2416
+ "exit",
2417
+ "error"
2418
+ ]);
2382
2419
  });
2383
2420
 
2384
2421
  // packages/core/src/workspace.ts
@@ -2573,6 +2610,8 @@ var init_onboarding = __esm(() => {
2573
2610
  { kind: "authMethod", question: "How would you like to authenticate for this project?" }
2574
2611
  ],
2575
2612
  claude: [
2613
+ { kind: "folderTrust", question: "Quick safety check: Is this a project you created or one you trust?" },
2614
+ { kind: "autoMode", question: "Set up auto mode for your environment?" },
2576
2615
  { kind: "apiKey", question: "Do you want to use this API key?" },
2577
2616
  { kind: "loginMethod", question: "Select login method:" }
2578
2617
  ],
@@ -3614,6 +3653,9 @@ function sessionsByRecency(live, stored) {
3614
3653
  var asGiven = (target) => target;
3615
3654
  var init_session_scope = () => {};
3616
3655
 
3656
+ // packages/core/src/test-scratch.ts
3657
+ var init_test_scratch = () => {};
3658
+
3617
3659
  // packages/core/src/index.ts
3618
3660
  function isRpcEvent(f) {
3619
3661
  return typeof f.ev === "string";
@@ -3633,6 +3675,7 @@ var init_src = __esm(() => {
3633
3675
  init_config_paths();
3634
3676
  init_setup_wizard();
3635
3677
  init_agent_registry();
3678
+ init_agent_resume();
3636
3679
  init_self_spawn();
3637
3680
  init_channel_spi();
3638
3681
  init_update();
@@ -3660,19 +3703,20 @@ var init_src = __esm(() => {
3660
3703
  init_dotenv_scrub();
3661
3704
  init_prompt_detect();
3662
3705
  init_session_scope();
3706
+ init_test_scratch();
3663
3707
  SUBMIT_DELAY_MS = process.platform === "win32" ? 1500 : 500;
3664
3708
  PASTE_CHUNK_BYTES = 16 * 1024;
3665
3709
  });
3666
3710
 
3667
3711
  // packages/daemon/src/config-store.ts
3668
- import fs5 from "fs";
3712
+ import fs6 from "fs";
3669
3713
  import path6 from "path";
3670
3714
  function writeAtomic(target, contents, mode) {
3671
3715
  const tmp = `${target}.tmp`;
3672
- fs5.writeFileSync(tmp, contents, { mode });
3673
- fs5.renameSync(tmp, target);
3716
+ fs6.writeFileSync(tmp, contents, { mode });
3717
+ fs6.renameSync(tmp, target);
3674
3718
  if (process.platform !== "win32")
3675
- fs5.chmodSync(target, mode);
3719
+ fs6.chmodSync(target, mode);
3676
3720
  }
3677
3721
 
3678
3722
  class ConfigStore {
@@ -3688,16 +3732,16 @@ class ConfigStore {
3688
3732
  }
3689
3733
  load() {
3690
3734
  this.loadError = undefined;
3691
- fs5.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
3735
+ fs6.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
3692
3736
  if (process.platform !== "win32") {
3693
3737
  try {
3694
- fs5.chmodSync(this.paths.configDir, DIR_MODE);
3738
+ fs6.chmodSync(this.paths.configDir, DIR_MODE);
3695
3739
  } catch {}
3696
3740
  }
3697
3741
  this.config = structuredClone(DEFAULT_CONFIG);
3698
- if (fs5.existsSync(this.paths.configFile)) {
3742
+ if (fs6.existsSync(this.paths.configFile)) {
3699
3743
  try {
3700
- const parsed = parseConfig(JSON.parse(fs5.readFileSync(this.paths.configFile, "utf8")));
3744
+ const parsed = parseConfig(JSON.parse(fs6.readFileSync(this.paths.configFile, "utf8")));
3701
3745
  if ("errors" in parsed)
3702
3746
  this.loadError = parsed.errors.join("; ");
3703
3747
  else
@@ -3707,10 +3751,10 @@ class ConfigStore {
3707
3751
  }
3708
3752
  }
3709
3753
  this.secrets = {};
3710
- if (fs5.existsSync(this.paths.secretsFile)) {
3754
+ if (fs6.existsSync(this.paths.secretsFile)) {
3711
3755
  this.tightenPermissions(this.paths.secretsFile);
3712
3756
  try {
3713
- const raw = JSON.parse(fs5.readFileSync(this.paths.secretsFile, "utf8"));
3757
+ const raw = JSON.parse(fs6.readFileSync(this.paths.secretsFile, "utf8"));
3714
3758
  if (raw !== null && typeof raw === "object") {
3715
3759
  for (const [key, value] of Object.entries(raw)) {
3716
3760
  if (typeof value === "string")
@@ -3726,8 +3770,8 @@ class ConfigStore {
3726
3770
  if (process.platform === "win32")
3727
3771
  return;
3728
3772
  try {
3729
- if ((fs5.statSync(file).mode & 63) !== 0)
3730
- fs5.chmodSync(file, FILE_MODE);
3773
+ if ((fs6.statSync(file).mode & 63) !== 0)
3774
+ fs6.chmodSync(file, FILE_MODE);
3731
3775
  } catch {}
3732
3776
  }
3733
3777
  get() {
@@ -3735,7 +3779,7 @@ class ConfigStore {
3735
3779
  }
3736
3780
  health() {
3737
3781
  return configHealth({
3738
- fileExists: fs5.existsSync(this.paths.configFile),
3782
+ fileExists: fs6.existsSync(this.paths.configFile),
3739
3783
  parseError: this.loadError,
3740
3784
  config: this.resolved()
3741
3785
  });
@@ -3780,7 +3824,7 @@ class ConfigStore {
3780
3824
  delete this.secrets[dottedPath];
3781
3825
  else
3782
3826
  this.secrets[dottedPath] = value;
3783
- if (!fs5.existsSync(this.paths.configFile)) {
3827
+ if (!fs6.existsSync(this.paths.configFile)) {
3784
3828
  writeAtomic(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
3785
3829
  `, FILE_MODE);
3786
3830
  }
@@ -3810,7 +3854,7 @@ class ConfigStore {
3810
3854
  fingerprint() {
3811
3855
  const stamp = (file) => {
3812
3856
  try {
3813
- return String(fs5.statSync(file).mtimeMs);
3857
+ return String(fs6.statSync(file).mtimeMs);
3814
3858
  } catch {
3815
3859
  return "-";
3816
3860
  }
@@ -3852,7 +3896,7 @@ var init_config_store = __esm(() => {
3852
3896
  });
3853
3897
 
3854
3898
  // packages/daemon/src/shell.ts
3855
- import fs6 from "fs";
3899
+ import fs7 from "fs";
3856
3900
  import path7 from "path";
3857
3901
  function findExecutable(name, env) {
3858
3902
  const isWindows = env.platform === "win32";
@@ -3876,7 +3920,7 @@ function lookupOnPath(name) {
3876
3920
  platform: process.platform,
3877
3921
  pathEnv: process.env["PATH"] ?? "",
3878
3922
  pathExt: process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD",
3879
- exists: (candidate) => fs6.existsSync(candidate)
3923
+ exists: (candidate) => fs7.existsSync(candidate)
3880
3924
  });
3881
3925
  }
3882
3926
  function resolveShell(kind) {
@@ -3913,7 +3957,7 @@ function resolvePosixShell(kind) {
3913
3957
  candidates.push({ kind: "bash", exe: login });
3914
3958
  candidates.push({ kind: "bash", exe: "/bin/bash" }, { kind: "bash", exe: "/usr/bin/bash" });
3915
3959
  }
3916
- const found = candidates.find((c) => fs6.existsSync(c.exe));
3960
+ const found = candidates.find((c) => fs7.existsSync(c.exe));
3917
3961
  if (found)
3918
3962
  return found;
3919
3963
  return { kind: "sh", exe: "/bin/sh" };
@@ -3964,18 +4008,18 @@ var cachedPwsh;
3964
4008
  var init_shell = () => {};
3965
4009
 
3966
4010
  // packages/daemon/src/paths.ts
3967
- import fs7 from "fs";
4011
+ import fs8 from "fs";
3968
4012
  import os3 from "os";
3969
4013
  import path8 from "path";
3970
4014
  function ensureDataDirs() {
3971
4015
  for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir, SCROLLBACK_DIR, LOG_DIR, EXT_DIR]) {
3972
- fs7.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
4016
+ fs8.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
3973
4017
  }
3974
4018
  if (process.platform === "win32")
3975
4019
  return;
3976
4020
  for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir]) {
3977
4021
  try {
3978
- fs7.chmodSync(dir, DIR_MODE2);
4022
+ fs8.chmodSync(dir, DIR_MODE2);
3979
4023
  } catch {}
3980
4024
  }
3981
4025
  }
@@ -3983,14 +4027,14 @@ function restrictFile(file) {
3983
4027
  if (process.platform === "win32")
3984
4028
  return;
3985
4029
  try {
3986
- fs7.chmodSync(file, 384);
4030
+ fs8.chmodSync(file, 384);
3987
4031
  } catch {}
3988
4032
  }
3989
4033
  function log(message) {
3990
4034
  const line = `${new Date().toISOString()} ${message}
3991
4035
  `;
3992
4036
  try {
3993
- fs7.appendFileSync(LOG_FILE, line);
4037
+ fs8.appendFileSync(LOG_FILE, line);
3994
4038
  } catch {}
3995
4039
  }
3996
4040
  var PATHS, CONFIG_DIR, STATE_DIR, RUNTIME_DIR, SCROLLBACK_DIR, LOG_DIR, EXT_DIR, DB_PATH, RUNTIME_FILE2, HOOK_ENDPOINT_FILE, DIR_MODE2 = 448, PIPE_PATH, LOG_FILE;
@@ -4014,7 +4058,7 @@ var init_paths = __esm(() => {
4014
4058
  });
4015
4059
 
4016
4060
  // packages/daemon/src/store.ts
4017
- import fs8 from "fs";
4061
+ import fs9 from "fs";
4018
4062
  import path9 from "path";
4019
4063
  import { Database } from "bun:sqlite";
4020
4064
  function rowToInfo(row) {
@@ -4046,7 +4090,7 @@ function rowToInfo(row) {
4046
4090
  class Store {
4047
4091
  db;
4048
4092
  constructor(dbPath = DB_PATH) {
4049
- fs8.mkdirSync(path9.dirname(dbPath), { recursive: true });
4093
+ fs9.mkdirSync(path9.dirname(dbPath), { recursive: true });
4050
4094
  this.db = new Database(dbPath, { strict: true });
4051
4095
  this.db.run("PRAGMA journal_mode = WAL");
4052
4096
  this.db.run(`
@@ -4125,8 +4169,10 @@ class Store {
4125
4169
  this.db.prepare(`UPDATE sessions SET state = 'running', exit_code = NULL, exited_at = NULL,
4126
4170
  agent_command = ?, pty_pid = ? WHERE id = ?`).run(JSON.stringify(agentCommand), ptyPid ?? null, id);
4127
4171
  }
4128
- markStaleRunningAsExited() {
4129
- return this.db.prepare(`UPDATE sessions SET state = 'exited', exited_at = ? WHERE state != 'exited'`).run(Date.now()).changes;
4172
+ markStaleRunningAsExited(keep = []) {
4173
+ const holes = keep.map(() => "?").join(",");
4174
+ const sql = keep.length === 0 ? `UPDATE sessions SET state = 'exited', exited_at = ? WHERE state != 'exited'` : `UPDATE sessions SET state = 'exited', exited_at = ? WHERE state != 'exited' AND id NOT IN (${holes})`;
4175
+ return this.db.prepare(sql).run(Date.now(), ...keep).changes;
4130
4176
  }
4131
4177
  listSessions() {
4132
4178
  const rows = this.db.prepare(`SELECT * FROM sessions ORDER BY created_at DESC`).all();
@@ -4212,11 +4258,11 @@ var init_store = __esm(() => {
4212
4258
  });
4213
4259
 
4214
4260
  // packages/daemon/src/config-migrate.ts
4215
- import fs9 from "fs";
4261
+ import fs10 from "fs";
4216
4262
  import path10 from "path";
4217
4263
  import { Database as Database2 } from "bun:sqlite";
4218
4264
  function checkpointWal(dbFile) {
4219
- if (!fs9.existsSync(dbFile))
4265
+ if (!fs10.existsSync(dbFile))
4220
4266
  return;
4221
4267
  try {
4222
4268
  const db = new Database2(dbFile);
@@ -4226,30 +4272,30 @@ function checkpointWal(dbFile) {
4226
4272
  }
4227
4273
  function relocate(source, target) {
4228
4274
  try {
4229
- fs9.renameSync(source, target);
4275
+ fs10.renameSync(source, target);
4230
4276
  } catch {
4231
- fs9.cpSync(source, target, { recursive: true });
4277
+ fs10.cpSync(source, target, { recursive: true });
4232
4278
  }
4233
4279
  }
4234
4280
  function moveInto(from, to, notes) {
4235
4281
  checkpointWal(path10.join(from, "axterm.db"));
4236
- fs9.mkdirSync(to, { recursive: true });
4282
+ fs10.mkdirSync(to, { recursive: true });
4237
4283
  for (const entry of STATE_ENTRIES) {
4238
4284
  const source = path10.join(from, entry);
4239
4285
  const target = path10.join(to, entry);
4240
- if (!fs9.existsSync(source))
4286
+ if (!fs10.existsSync(source))
4241
4287
  continue;
4242
- if (!fs9.existsSync(target)) {
4288
+ if (!fs10.existsSync(target)) {
4243
4289
  relocate(source, target);
4244
4290
  notes.push(`moved ${entry}`);
4245
4291
  continue;
4246
4292
  }
4247
- if (!fs9.statSync(source).isDirectory() || !fs9.statSync(target).isDirectory())
4293
+ if (!fs10.statSync(source).isDirectory() || !fs10.statSync(target).isDirectory())
4248
4294
  continue;
4249
4295
  let moved = 0;
4250
- for (const child of fs9.readdirSync(source)) {
4296
+ for (const child of fs10.readdirSync(source)) {
4251
4297
  const childTarget = path10.join(target, child);
4252
- if (fs9.existsSync(childTarget))
4298
+ if (fs10.existsSync(childTarget))
4253
4299
  continue;
4254
4300
  relocate(path10.join(source, child), childTarget);
4255
4301
  moved += 1;
@@ -4261,29 +4307,29 @@ function moveInto(from, to, notes) {
4261
4307
  function migrateLegacyConfig(input) {
4262
4308
  const notes = [];
4263
4309
  const storePaths = defaultConfigStorePaths(input.configDir);
4264
- if (fs9.existsSync(storePaths.configFile))
4310
+ if (fs10.existsSync(storePaths.configFile))
4265
4311
  return { migrated: false, notes };
4266
- const configSource = input.legacyRoots.find((dir) => fs9.existsSync(path10.join(dir, "config.json")));
4312
+ const configSource = input.legacyRoots.find((dir) => fs10.existsSync(path10.join(dir, "config.json")));
4267
4313
  if (configSource !== undefined) {
4268
- fs9.mkdirSync(input.configDir, { recursive: true });
4269
- fs9.copyFileSync(path10.join(configSource, "config.json"), storePaths.configFile);
4314
+ fs10.mkdirSync(input.configDir, { recursive: true });
4315
+ fs10.copyFileSync(path10.join(configSource, "config.json"), storePaths.configFile);
4270
4316
  const secrets = path10.join(configSource, "secrets.json");
4271
- if (fs9.existsSync(secrets)) {
4272
- fs9.copyFileSync(secrets, storePaths.secretsFile);
4317
+ if (fs10.existsSync(secrets)) {
4318
+ fs10.copyFileSync(secrets, storePaths.secretsFile);
4273
4319
  try {
4274
- fs9.chmodSync(storePaths.secretsFile, 384);
4320
+ fs10.chmodSync(storePaths.secretsFile, 384);
4275
4321
  } catch {}
4276
4322
  }
4277
- fs9.writeFileSync(path10.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
4323
+ fs10.writeFileSync(path10.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
4278
4324
  ${input.configDir}
4279
4325
  ` + `These files are no longer used. secrets.json still holds your bot token \u2014 delete this directory once you are satisfied.
4280
4326
  `);
4281
4327
  notes.push(`adopted configuration from ${configSource}`);
4282
4328
  }
4283
- const legacy = input.legacyRoots.find((dir) => fs9.existsSync(path10.join(dir, "axterm.db")));
4329
+ const legacy = input.legacyRoots.find((dir) => fs10.existsSync(path10.join(dir, "axterm.db")));
4284
4330
  if (legacy !== undefined) {
4285
4331
  moveInto(legacy, input.stateDir, notes);
4286
- fs9.writeFileSync(path10.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
4332
+ fs10.writeFileSync(path10.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
4287
4333
  ${input.stateDir}
4288
4334
  and configuration in:
4289
4335
  ${input.configDir}
@@ -4293,7 +4339,7 @@ and configuration in:
4293
4339
  if (configSource !== undefined)
4294
4340
  return { migrated: true, ...legacy ? { from: legacy } : {}, notes };
4295
4341
  const dbFile = path10.join(input.stateDir, "axterm.db");
4296
- if (!fs9.existsSync(dbFile))
4342
+ if (!fs10.existsSync(dbFile))
4297
4343
  return { migrated: notes.length > 0, ...legacy ? { from: legacy } : {}, notes };
4298
4344
  const store = new Store(dbFile);
4299
4345
  const machine2 = store.getConfig("axterm_machine");
@@ -5131,13 +5177,13 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
5131
5177
  var init_axterm_agent_status = () => {};
5132
5178
 
5133
5179
  // packages/daemon/src/ext-install.ts
5134
- import fs10 from "fs";
5180
+ import fs11 from "fs";
5135
5181
  import path11 from "path";
5136
5182
  function installExtensionAsset(targetDir) {
5137
- fs10.mkdirSync(targetDir, { recursive: true });
5183
+ fs11.mkdirSync(targetDir, { recursive: true });
5138
5184
  const target = path11.join(targetDir, "axterm-agent-status.mjs");
5139
5185
  const js = new Bun.Transpiler({ loader: "ts" }).transformSync(extensionSource);
5140
- fs10.writeFileSync(target, MANAGED_HEADER + js);
5186
+ fs11.writeFileSync(target, MANAGED_HEADER + js);
5141
5187
  return target;
5142
5188
  }
5143
5189
  var extensionSource, MANAGED_HEADER = `// @axterm-managed-omp-extension
@@ -5150,7 +5196,7 @@ var init_ext_install = __esm(() => {
5150
5196
 
5151
5197
  // packages/daemon/src/workspace-local.ts
5152
5198
  import { spawn as spawn2 } from "child_process";
5153
- import fs11 from "fs/promises";
5199
+ import fs12 from "fs/promises";
5154
5200
  import path12 from "path";
5155
5201
  function isAbsent(error) {
5156
5202
  const code = error?.code;
@@ -5164,10 +5210,10 @@ class LocalWorkspace {
5164
5210
  this.stagingDir = stagingDir;
5165
5211
  }
5166
5212
  async list(dir) {
5167
- const dirents = await fs11.readdir(dir, { withFileTypes: true });
5213
+ const dirents = await fs12.readdir(dir, { withFileTypes: true });
5168
5214
  const stats = await Promise.all(dirents.map(async (dirent) => {
5169
5215
  try {
5170
- return await fs11.lstat(path12.join(dir, dirent.name));
5216
+ return await fs12.lstat(path12.join(dir, dirent.name));
5171
5217
  } catch (error) {
5172
5218
  if (isAbsent(error))
5173
5219
  return;
@@ -5191,7 +5237,7 @@ class LocalWorkspace {
5191
5237
  }
5192
5238
  async stat(target) {
5193
5239
  try {
5194
- const stat = await fs11.stat(target);
5240
+ const stat = await fs12.stat(target);
5195
5241
  const kind = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "other";
5196
5242
  return { kind, size: stat.size, mtimeMs: stat.mtimeMs };
5197
5243
  } catch (error) {
@@ -5202,8 +5248,8 @@ class LocalWorkspace {
5202
5248
  }
5203
5249
  async read(file, limit) {
5204
5250
  if (limit === undefined)
5205
- return await fs11.readFile(file);
5206
- const handle = await fs11.open(file, "r");
5251
+ return await fs12.readFile(file);
5252
+ const handle = await fs12.open(file, "r");
5207
5253
  try {
5208
5254
  const buffer = Buffer.allocUnsafe(Math.min(limit, (await handle.stat()).size));
5209
5255
  let filled = 0;
@@ -5222,10 +5268,10 @@ class LocalWorkspace {
5222
5268
  return Promise.resolve(file);
5223
5269
  }
5224
5270
  async mkdirp(dir, mode) {
5225
- await fs11.mkdir(dir, mode === undefined ? { recursive: true } : { recursive: true, mode });
5271
+ await fs12.mkdir(dir, mode === undefined ? { recursive: true } : { recursive: true, mode });
5226
5272
  }
5227
5273
  async write(file, bytes, mode) {
5228
- await fs11.writeFile(file, bytes, mode === undefined ? undefined : { mode });
5274
+ await fs12.writeFile(file, bytes, mode === undefined ? undefined : { mode });
5229
5275
  }
5230
5276
  exec(argv, cwd) {
5231
5277
  const [command, ...args] = argv;
@@ -5254,7 +5300,7 @@ var init_workspace_local = () => {};
5254
5300
 
5255
5301
  // packages/daemon/src/hook-host.ts
5256
5302
  import { randomBytes } from "crypto";
5257
- import fs12 from "fs";
5303
+ import fs13 from "fs";
5258
5304
  import http from "http";
5259
5305
  import path13 from "path";
5260
5306
  function header(req, name) {
@@ -5270,6 +5316,7 @@ class HookHost {
5270
5316
  }
5271
5317
  sessions = new Map;
5272
5318
  pendingAsks = new Map;
5319
+ refusedHooks = new Set;
5273
5320
  endpointFile;
5274
5321
  server;
5275
5322
  askOpenHandler;
@@ -5296,16 +5343,19 @@ class HookHost {
5296
5343
  return promise;
5297
5344
  }
5298
5345
  writeEndpointFile(port) {
5299
- fs12.mkdirSync(path13.dirname(this.endpointFile), { recursive: true });
5346
+ fs13.mkdirSync(path13.dirname(this.endpointFile), { recursive: true });
5300
5347
  const tmp = `${this.endpointFile}.tmp`;
5301
- fs12.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
5348
+ fs13.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
5302
5349
  AXTERM_HOOK_TOKEN=${this.token}
5303
5350
  `);
5304
- fs12.renameSync(tmp, this.endpointFile);
5351
+ fs13.renameSync(tmp, this.endpointFile);
5305
5352
  }
5306
5353
  registerSession(sessionId, launchToken, onEvent) {
5307
5354
  this.sessions.set(sessionId, { launchToken, onEvent });
5308
5355
  }
5356
+ launchMatches(session, presented) {
5357
+ return session.launchToken === undefined || session.launchToken === presented;
5358
+ }
5309
5359
  unregisterSession(sessionId) {
5310
5360
  this.sessions.delete(sessionId);
5311
5361
  for (const [askId, pending] of this.pendingAsks) {
@@ -5394,7 +5444,7 @@ AXTERM_HOOK_TOKEN=${this.token}
5394
5444
  res.writeHead(404).end();
5395
5445
  return;
5396
5446
  }
5397
- if (body.launchToken !== session.launchToken) {
5447
+ if (!this.launchMatches(session, body.launchToken)) {
5398
5448
  res.writeHead(403).end();
5399
5449
  return;
5400
5450
  }
@@ -5429,7 +5479,9 @@ AXTERM_HOOK_TOKEN=${this.token}
5429
5479
  return;
5430
5480
  }
5431
5481
  res.writeHead(200).end();
5432
- this.dispatchHookPayloads(auth.session, map(input), agent);
5482
+ const named = input;
5483
+ const events = withConversationId(map(input), { sessionId: named.session_id, subagentId: named.agent_id });
5484
+ this.dispatchHookPayloads(auth.session, events, agent);
5433
5485
  }
5434
5486
  handleCodex(req, raw, res) {
5435
5487
  const auth = this.agentHookSession(req);
@@ -5485,8 +5537,15 @@ AXTERM_HOOK_TOKEN=${this.token}
5485
5537
  const sessionId = header(req, "x-axterm-session");
5486
5538
  const launchToken = header(req, "x-axterm-launch");
5487
5539
  const session = sessionId === undefined ? undefined : this.sessions.get(sessionId);
5488
- if (sessionId === undefined || !session || session.launchToken !== launchToken)
5540
+ if (sessionId === undefined || !session || !this.launchMatches(session, launchToken)) {
5541
+ const why = sessionId === undefined ? "no session header" : !session ? "unknown session" : "launch token mismatch";
5542
+ const key = `${req.url}\x00${sessionId ?? ""}\x00${why}`;
5543
+ if (!this.refusedHooks.has(key)) {
5544
+ this.refusedHooks.add(key);
5545
+ log(`refused hook post to ${req.url}: ${why} (session ${sessionId ?? "-"}) \u2014 reported once per session`);
5546
+ }
5489
5547
  return;
5548
+ }
5490
5549
  return { sessionId, session };
5491
5550
  }
5492
5551
  dispatchHookPayloads(session, payloads, agent) {
@@ -5576,7 +5635,7 @@ var init_hook_host = __esm(() => {
5576
5635
  });
5577
5636
 
5578
5637
  // packages/daemon/src/rpc-server.ts
5579
- import fs13 from "fs";
5638
+ import fs14 from "fs";
5580
5639
  import net2 from "net";
5581
5640
  function writeFrame(socket, frame) {
5582
5641
  if (!socket.destroyed)
@@ -5686,6 +5745,8 @@ function startRpcServer(options) {
5686
5745
  session.write(Buffer.from(p["dataB64"], "base64").toString("utf8"));
5687
5746
  return {};
5688
5747
  }
5748
+ case "session.choose":
5749
+ return { answered: manager.answerScreenChoice(p["id"], p["index"]) };
5689
5750
  case "attach": {
5690
5751
  const session = manager.resolveLive(p["id"]);
5691
5752
  cleanup();
@@ -5812,7 +5873,7 @@ function startRpcServer(options) {
5812
5873
  });
5813
5874
  if (process.platform !== "win32") {
5814
5875
  try {
5815
- fs13.unlinkSync(pipePath);
5876
+ fs14.unlinkSync(pipePath);
5816
5877
  } catch {}
5817
5878
  }
5818
5879
  server.listen(pipePath, () => {
@@ -5827,18 +5888,35 @@ var init_rpc_server = __esm(() => {
5827
5888
  });
5828
5889
 
5829
5890
  // packages/daemon/src/codex-home.ts
5830
- import fs14 from "fs";
5891
+ import fs15 from "fs";
5831
5892
  import path14 from "path";
5832
5893
  function codexTrustKeyPath(home) {
5833
5894
  const resolved = (() => {
5834
5895
  try {
5835
- return fs14.realpathSync.native(home);
5896
+ return fs15.realpathSync.native(home);
5836
5897
  } catch {
5837
5898
  return path14.resolve(home);
5838
5899
  }
5839
5900
  })();
5840
5901
  return path14.join(resolved, "hooks.json");
5841
5902
  }
5903
+ function referenceCredential(source, target, ops) {
5904
+ const symlink = ops?.symlink ?? ((s, t2) => fs15.symlinkSync(s, t2, "file"));
5905
+ const link = ops?.link ?? fs15.linkSync;
5906
+ try {
5907
+ symlink(source, target);
5908
+ return "symlink";
5909
+ } catch {
5910
+ try {
5911
+ link(source, target);
5912
+ return "hardlink";
5913
+ } catch {
5914
+ fs15.copyFileSync(source, target);
5915
+ fs15.chmodSync(target, 384);
5916
+ return "copy";
5917
+ }
5918
+ }
5919
+ }
5842
5920
  var init_codex_home = () => {};
5843
5921
 
5844
5922
  // packages/daemon/src/host-env.ts
@@ -5853,15 +5931,36 @@ function stripHostAttribution(env) {
5853
5931
  }
5854
5932
  var ATTRIBUTION_PREFIXES, ATTRIBUTION_KEEP;
5855
5933
  var init_host_env = __esm(() => {
5856
- ATTRIBUTION_PREFIXES = ["ORCA_"];
5934
+ ATTRIBUTION_PREFIXES = ["ORCA_", "AXTERM_"];
5857
5935
  ATTRIBUTION_KEEP = new Set([
5858
5936
  "ORCA_CODEX_HOME",
5859
5937
  "ORCA_OPENCODE_CONFIG_DIR",
5860
5938
  "ORCA_USER_DATA_PATH",
5861
- "ORCA_APP_VERSION"
5939
+ "ORCA_APP_VERSION",
5940
+ "AXTERM_CONFIG_DIR",
5941
+ "AXTERM_STATE_DIR",
5942
+ "AXTERM_RUNTIME_DIR",
5943
+ "AXTERM_LANG",
5944
+ "AXTERM_PTY_HOST",
5945
+ "AXTERM_PTY_HOST_MAIN"
5862
5946
  ]);
5863
5947
  });
5864
5948
 
5949
+ // packages/daemon/src/session-env.ts
5950
+ function sessionEnv(base, identity) {
5951
+ return {
5952
+ ...stripHostAttribution(base),
5953
+ AXTERM_SESSION_ID: identity.sessionId,
5954
+ AXTERM_LAUNCH_TOKEN: identity.launchToken,
5955
+ AXTERM_HOOK_PORT: String(identity.hookPort),
5956
+ AXTERM_HOOK_TOKEN: identity.hookToken,
5957
+ AXTERM_HOOK_ENDPOINT: identity.endpointFile
5958
+ };
5959
+ }
5960
+ var init_session_env = __esm(() => {
5961
+ init_host_env();
5962
+ });
5963
+
5865
5964
  // packages/daemon/src/git-porcelain.ts
5866
5965
  function parseWorktreeList(porcelain) {
5867
5966
  const entries = [];
@@ -5900,7 +5999,7 @@ function slugifyBranch(branch) {
5900
5999
  var MAX_SLUG_LENGTH = 60;
5901
6000
 
5902
6001
  // packages/daemon/src/git.ts
5903
- import fs15 from "fs";
6002
+ import fs16 from "fs";
5904
6003
  import path15 from "path";
5905
6004
  async function git(ws, cwd, args) {
5906
6005
  const run = await ws.exec(["git", ...args], cwd);
@@ -5920,7 +6019,7 @@ async function gitOrThrow(ws, cwd, args) {
5920
6019
  function canonicalPath(target) {
5921
6020
  let resolved = path15.resolve(target);
5922
6021
  try {
5923
- resolved = fs15.realpathSync.native(resolved);
6022
+ resolved = fs16.realpathSync.native(resolved);
5924
6023
  } catch {}
5925
6024
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
5926
6025
  }
@@ -5944,7 +6043,7 @@ async function createWorktree(ws, options) {
5944
6043
  const existing = (await listWorktrees(ws, repoRoot)).find((w) => canonicalPath(w.path) === canonicalPath(target));
5945
6044
  if (existing)
5946
6045
  return { path: target, branch, createdBranch: false };
5947
- fs15.mkdirSync(path15.dirname(target), { recursive: true });
6046
+ fs16.mkdirSync(path15.dirname(target), { recursive: true });
5948
6047
  const exists = (await git(ws, repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])).ok;
5949
6048
  const args = exists ? ["worktree", "add", target, branch] : ["worktree", "add", "-b", branch, target, ...base !== undefined ? [base] : []];
5950
6049
  await gitOrThrow(ws, repoRoot, args);
@@ -6035,7 +6134,7 @@ var init_git = __esm(() => {
6035
6134
  });
6036
6135
 
6037
6136
  // packages/daemon/src/claude-store.ts
6038
- import fs16 from "fs";
6137
+ import fs17 from "fs";
6039
6138
  import os4 from "os";
6040
6139
  import path16 from "path";
6041
6140
  function claudeConfigDir(env = process.env) {
@@ -6061,19 +6160,55 @@ function claudeConversationExists(configDir, conversationId) {
6061
6160
  const projects = path16.join(configDir, "projects");
6062
6161
  let entries;
6063
6162
  try {
6064
- entries = fs16.readdirSync(projects);
6163
+ entries = fs17.readdirSync(projects);
6065
6164
  } catch {
6066
6165
  return;
6067
6166
  }
6068
6167
  const file = `${conversationId}.jsonl`;
6069
6168
  for (const entry of entries) {
6070
6169
  try {
6071
- if (fs16.existsSync(path16.join(projects, entry, file)))
6170
+ if (fs17.existsSync(path16.join(projects, entry, file)))
6072
6171
  return true;
6073
6172
  } catch {}
6074
6173
  }
6075
6174
  return false;
6076
6175
  }
6176
+ function claudeConversationCwd(configDir, conversationId) {
6177
+ const projects = path16.join(configDir, "projects");
6178
+ let entries;
6179
+ try {
6180
+ entries = fs17.readdirSync(projects);
6181
+ } catch {
6182
+ return;
6183
+ }
6184
+ for (const entry of entries) {
6185
+ const file = path16.join(projects, entry, `${conversationId}.jsonl`);
6186
+ let head;
6187
+ try {
6188
+ const handle = fs17.openSync(file, "r");
6189
+ try {
6190
+ const buffer = Buffer.alloc(64 * 1024);
6191
+ const read = fs17.readSync(handle, buffer, 0, buffer.byteLength, 0);
6192
+ head = buffer.subarray(0, read).toString("utf8");
6193
+ } finally {
6194
+ fs17.closeSync(handle);
6195
+ }
6196
+ } catch {
6197
+ continue;
6198
+ }
6199
+ for (const line of head.split(/\r?\n/)) {
6200
+ if (line.length === 0)
6201
+ continue;
6202
+ try {
6203
+ const record = JSON.parse(line);
6204
+ const cwd = record.cwd;
6205
+ if (typeof cwd === "string" && cwd.length > 0)
6206
+ return cwd;
6207
+ } catch {}
6208
+ }
6209
+ }
6210
+ return;
6211
+ }
6077
6212
  var CONVERSATION_ID;
6078
6213
  var init_claude_store = __esm(() => {
6079
6214
  CONVERSATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -6194,6 +6329,7 @@ function connectPtyHost(options) {
6194
6329
  const decoder = new FrameDecoder;
6195
6330
  const ptys = new Map;
6196
6331
  const pending = new Map;
6332
+ const subscriptions = new Map;
6197
6333
  const dataListeners = [];
6198
6334
  const rawDecoders = new Map;
6199
6335
  let welcomed = false;
@@ -6207,6 +6343,9 @@ function connectPtyHost(options) {
6207
6343
  for (const waiter of pending.values())
6208
6344
  waiter.reject(error);
6209
6345
  pending.clear();
6346
+ for (const waiter of subscriptions.values())
6347
+ waiter.reject(error);
6348
+ subscriptions.clear();
6210
6349
  socket.destroy();
6211
6350
  };
6212
6351
  const timer = setTimeout(() => fail(new Error(`pty host did not greet within ${options.timeoutMs ?? 5000}ms`)), options.timeoutMs ?? 5000);
@@ -6273,6 +6412,15 @@ function connectPtyHost(options) {
6273
6412
  term: spawnOptions.term
6274
6413
  }));
6275
6414
  }),
6415
+ adopt: (session) => new Promise((done, failed) => {
6416
+ const pty = ptys.get(session.id) ?? new RemotePty(session.id, session.pid, send);
6417
+ ptys.set(session.id, pty);
6418
+ subscriptions.set(session.id, {
6419
+ resolve: () => done(pty),
6420
+ reject: failed
6421
+ });
6422
+ send(controlFrame({ t: "subscribe", id: session.id, replay: true }));
6423
+ }),
6276
6424
  onData: (listener) => dataListeners.push(listener),
6277
6425
  close: () => socket.destroy()
6278
6426
  });
@@ -6288,10 +6436,17 @@ function connectPtyHost(options) {
6288
6436
  pending.delete(message.id);
6289
6437
  continue;
6290
6438
  }
6439
+ if (message.t === "subscribed") {
6440
+ subscriptions.get(message.id)?.resolve();
6441
+ subscriptions.delete(message.id);
6442
+ continue;
6443
+ }
6291
6444
  if (message.t === "exit") {
6292
6445
  const pty = ptys.get(message.id);
6293
6446
  ptys.delete(message.id);
6294
6447
  rawDecoders.delete(message.id);
6448
+ subscriptions.get(message.id)?.reject(new Error(`session ${message.id} exited`));
6449
+ subscriptions.delete(message.id);
6295
6450
  pty?.finish(message.code);
6296
6451
  }
6297
6452
  }
@@ -6304,15 +6459,18 @@ var init_pty_host_client = __esm(() => {
6304
6459
 
6305
6460
  // packages/daemon/src/pty-host-launch.ts
6306
6461
  import { spawn as spawn3 } from "child_process";
6307
- import fs17 from "fs";
6462
+ import fs18 from "fs";
6308
6463
  import os5 from "os";
6309
6464
  import path17 from "path";
6310
6465
  function hostFile(runtimeDir) {
6311
6466
  return path17.join(runtimeDir, "pty-host.json");
6312
6467
  }
6468
+ function readPtyHostIn(runtimeDir) {
6469
+ return readPtyHostFile(hostFile(runtimeDir));
6470
+ }
6313
6471
  function readPtyHostFile(file) {
6314
6472
  try {
6315
- const parsed = JSON.parse(fs17.readFileSync(file, "utf8"));
6473
+ const parsed = JSON.parse(fs18.readFileSync(file, "utf8"));
6316
6474
  if (typeof parsed !== "object" || parsed === null)
6317
6475
  return;
6318
6476
  const { pid, endpoint, token, version: version2 } = parsed;
@@ -6344,7 +6502,7 @@ async function ensurePtyHost(location) {
6344
6502
  log(`pty host: handoff file names a dead pid ${existing.pid}, starting a new host`);
6345
6503
  const endpoint = hostEndpoint(location.runtimeDir);
6346
6504
  if (process.platform !== "win32")
6347
- fs17.rmSync(endpoint, { force: true });
6505
+ fs18.rmSync(endpoint, { force: true });
6348
6506
  const token = shortHash(`${location.runtimeDir}:${Date.now()}:${Math.random()}`) + shortHash(String(process.pid));
6349
6507
  const launch = ptyHostLaunch({ execPath: process.execPath, mainScript: location.mainScript ?? Bun.main });
6350
6508
  const child = spawn3(launch.command, launch.args, {
@@ -6373,8 +6531,8 @@ async function ensurePtyHost(location) {
6373
6531
  }
6374
6532
  }
6375
6533
  function writePtyHostFile(file, info) {
6376
- fs17.mkdirSync(path17.dirname(file), { recursive: true, mode: 448 });
6377
- fs17.writeFileSync(file, `${JSON.stringify(info, null, 2)}
6534
+ fs18.mkdirSync(path17.dirname(file), { recursive: true, mode: 448 });
6535
+ fs18.writeFileSync(file, `${JSON.stringify(info, null, 2)}
6378
6536
  `, { mode: 384 });
6379
6537
  restrictFile(file);
6380
6538
  }
@@ -6494,7 +6652,7 @@ var init_pty = () => {};
6494
6652
 
6495
6653
  // packages/daemon/src/occupancy.ts
6496
6654
  import { spawn as spawn4 } from "child_process";
6497
- import fs18 from "fs/promises";
6655
+ import fs19 from "fs/promises";
6498
6656
  function shellOccupied(shellPid) {
6499
6657
  return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
6500
6658
  }
@@ -6539,11 +6697,11 @@ async function occupiedFromSnapshot(shellPid) {
6539
6697
  }
6540
6698
  async function childrenViaProc(pid) {
6541
6699
  try {
6542
- const children = await fs18.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
6700
+ const children = await fs19.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
6543
6701
  return children.trim().length > 0;
6544
6702
  } catch {
6545
6703
  try {
6546
- await fs18.access(`/proc/${pid}`);
6704
+ await fs19.access(`/proc/${pid}`);
6547
6705
  return;
6548
6706
  } catch {
6549
6707
  return false;
@@ -6607,20 +6765,20 @@ var init_occupancy = __esm(() => {
6607
6765
  });
6608
6766
 
6609
6767
  // packages/daemon/src/agent-store.ts
6610
- import fs19 from "fs";
6768
+ import fs20 from "fs";
6611
6769
  import os6 from "os";
6612
6770
  import path19 from "path";
6613
6771
  import { Database as Database3 } from "bun:sqlite";
6614
6772
  function readTailLines(file, maxBytes) {
6615
6773
  let fd;
6616
6774
  try {
6617
- fd = fs19.openSync(file, "r");
6618
- const size = fs19.fstatSync(fd).size;
6775
+ fd = fs20.openSync(file, "r");
6776
+ const size = fs20.fstatSync(fd).size;
6619
6777
  if (size === 0)
6620
6778
  return [];
6621
6779
  const length = Math.min(size, maxBytes);
6622
6780
  const buffer = Buffer.allocUnsafe(length);
6623
- fs19.readSync(fd, buffer, 0, length, size - length);
6781
+ fs20.readSync(fd, buffer, 0, length, size - length);
6624
6782
  const text = buffer.toString("utf8");
6625
6783
  const lines = text.split(`
6626
6784
  `);
@@ -6633,7 +6791,7 @@ function readTailLines(file, maxBytes) {
6633
6791
  return [];
6634
6792
  } finally {
6635
6793
  if (fd !== undefined)
6636
- fs19.closeSync(fd);
6794
+ fs20.closeSync(fd);
6637
6795
  }
6638
6796
  }
6639
6797
  function claudeConfigDir2() {
@@ -6651,7 +6809,7 @@ function findCodexRollout(sessionId) {
6651
6809
  const dir = path19.join(root, ...shard2.split("/"));
6652
6810
  let entries;
6653
6811
  try {
6654
- entries = fs19.readdirSync(dir);
6812
+ entries = fs20.readdirSync(dir);
6655
6813
  } catch {
6656
6814
  continue;
6657
6815
  }
@@ -6667,7 +6825,7 @@ function newestCodexRollout() {
6667
6825
  const walk = (dir, depth) => {
6668
6826
  let entries;
6669
6827
  try {
6670
- entries = fs19.readdirSync(dir, { withFileTypes: true });
6828
+ entries = fs20.readdirSync(dir, { withFileTypes: true });
6671
6829
  } catch {
6672
6830
  return;
6673
6831
  }
@@ -6681,7 +6839,7 @@ function newestCodexRollout() {
6681
6839
  if (!entry.name.endsWith(".jsonl"))
6682
6840
  continue;
6683
6841
  try {
6684
- const { mtimeMs } = fs19.statSync(full);
6842
+ const { mtimeMs } = fs20.statSync(full);
6685
6843
  if (!best || mtimeMs > best.mtimeMs)
6686
6844
  best = { file: full, mtimeMs };
6687
6845
  } catch {}
@@ -6692,7 +6850,7 @@ function newestCodexRollout() {
6692
6850
  }
6693
6851
  function ompQuotaFacts() {
6694
6852
  const file = ompAgentDb();
6695
- if (!fs19.existsSync(file))
6853
+ if (!fs20.existsSync(file))
6696
6854
  return { agent: "omp", kind: "gauge" };
6697
6855
  let db;
6698
6856
  try {
@@ -6760,7 +6918,7 @@ function storeQuietForMs(agent, cwd, agentSessionId, nowMs) {
6760
6918
  if (file === undefined)
6761
6919
  return;
6762
6920
  try {
6763
- return Math.max(0, nowMs - fs19.statSync(file).mtimeMs);
6921
+ return Math.max(0, nowMs - fs20.statSync(file).mtimeMs);
6764
6922
  } catch {
6765
6923
  return;
6766
6924
  }
@@ -6770,15 +6928,15 @@ function readFileRange(file, from, to) {
6770
6928
  return "";
6771
6929
  let fd;
6772
6930
  try {
6773
- fd = fs19.openSync(file, "r");
6931
+ fd = fs20.openSync(file, "r");
6774
6932
  const buffer = Buffer.allocUnsafe(to - from);
6775
- const read = fs19.readSync(fd, buffer, 0, to - from, from);
6933
+ const read = fs20.readSync(fd, buffer, 0, to - from, from);
6776
6934
  return buffer.subarray(0, read).toString("utf8");
6777
6935
  } catch {
6778
6936
  return "";
6779
6937
  } finally {
6780
6938
  if (fd !== undefined)
6781
- fs19.closeSync(fd);
6939
+ fs20.closeSync(fd);
6782
6940
  }
6783
6941
  }
6784
6942
  var QUOTA_TAIL_BYTES;
@@ -11229,15 +11387,55 @@ var require_addon_serialize = __commonJS((exports, module) => {
11229
11387
  })());
11230
11388
  });
11231
11389
 
11390
+ // packages/daemon/src/terminal-mirror.ts
11391
+ class TerminalMirror {
11392
+ scrollbackLines;
11393
+ term;
11394
+ serializeAddon;
11395
+ constructor(cols, rows, scrollbackLines) {
11396
+ this.scrollbackLines = scrollbackLines;
11397
+ this.term = new import_headless.Terminal({ cols, rows, scrollback: scrollbackLines, allowProposedApi: true });
11398
+ this.serializeAddon = new import_addon_serialize.SerializeAddon;
11399
+ this.term.loadAddon(this.serializeAddon);
11400
+ }
11401
+ write(data) {
11402
+ this.term.write(data);
11403
+ }
11404
+ resize(cols, rows) {
11405
+ this.term.resize(cols, rows);
11406
+ }
11407
+ serialize(maxChars = Number.POSITIVE_INFINITY) {
11408
+ const ansi = this.serializeAddon.serialize({ scrollback: this.scrollbackLines });
11409
+ return ansi.length > maxChars ? ansi.slice(-maxChars) : ansi;
11410
+ }
11411
+ text(maxLines) {
11412
+ const buffer = this.term.buffer.active;
11413
+ let last = buffer.length - 1;
11414
+ while (last >= 0 && (buffer.getLine(last)?.translateToString(true).trim() ?? "") === "")
11415
+ last--;
11416
+ const start = Math.max(0, last - maxLines + 1);
11417
+ const lines = [];
11418
+ for (let y = start;y <= last; y++) {
11419
+ lines.push(buffer.getLine(y)?.translateToString(true).trimEnd() ?? "");
11420
+ }
11421
+ return lines.join(`
11422
+ `);
11423
+ }
11424
+ }
11425
+ var import_headless, import_addon_serialize;
11426
+ var init_terminal_mirror = __esm(() => {
11427
+ import_headless = __toESM(require_xterm_headless(), 1);
11428
+ import_addon_serialize = __toESM(require_addon_serialize(), 1);
11429
+ });
11430
+
11232
11431
  // packages/daemon/src/pty-session.ts
11233
- import fs20 from "fs";
11432
+ import fs21 from "fs";
11234
11433
  import path20 from "path";
11235
11434
 
11236
11435
  class PtySession {
11237
11436
  info;
11238
11437
  pty;
11239
- term;
11240
- serializeAddon;
11438
+ mirror;
11241
11439
  dataListeners = new Set;
11242
11440
  exitListeners = new Set;
11243
11441
  saveTimer;
@@ -11254,26 +11452,23 @@ class PtySession {
11254
11452
  cols: spawn5.cols,
11255
11453
  rows: spawn5.rows,
11256
11454
  env: spawn5.env,
11257
- term: "xterm-256color"
11455
+ term: "xterm-256color",
11456
+ id: info.id
11258
11457
  });
11259
11458
  return new PtySession(info, spawn5, pty);
11260
11459
  }
11460
+ static adopt(info, pty, cols, rows) {
11461
+ return new PtySession(info, { exe: "", argv: [], cwd: info.cwd, cols, rows, env: {} }, pty);
11462
+ }
11261
11463
  constructor(info, spawn5, pty) {
11262
11464
  this.info = info;
11263
- this.term = new import_headless.Terminal({
11264
- cols: spawn5.cols,
11265
- rows: spawn5.rows,
11266
- scrollback: SCROLLBACK_LINES,
11267
- allowProposedApi: true
11268
- });
11269
- this.serializeAddon = new import_addon_serialize.SerializeAddon;
11270
- this.term.loadAddon(this.serializeAddon);
11465
+ this.mirror = new TerminalMirror(spawn5.cols, spawn5.rows, SCROLLBACK_LINES);
11271
11466
  this.pty = pty;
11272
11467
  info.ptyPid = this.pty.pid;
11273
11468
  this.pty.onData((data) => {
11274
11469
  this.seq += 1;
11275
11470
  this.lastOutputAt = Date.now();
11276
- this.term.write(data);
11471
+ this.mirror.write(data);
11277
11472
  if (!this.pasteReady) {
11278
11473
  const window2 = this.readyScanTail + data;
11279
11474
  if (window2.includes("\x1B[?2004h"))
@@ -11318,27 +11513,16 @@ class PtySession {
11318
11513
  this.info.rows = rows;
11319
11514
  try {
11320
11515
  this.pty.resize(cols, rows);
11321
- this.term.resize(cols, rows);
11516
+ this.mirror.resize(cols, rows);
11322
11517
  } catch (err) {
11323
11518
  log(`resize failed for ${this.info.shortId}: ${String(err)}`);
11324
11519
  }
11325
11520
  }
11326
11521
  screenText(maxLines = 60) {
11327
- const buffer = this.term.buffer.active;
11328
- let last = buffer.length - 1;
11329
- while (last >= 0 && (buffer.getLine(last)?.translateToString(true).trim() ?? "") === "")
11330
- last--;
11331
- const start = Math.max(0, last - maxLines + 1);
11332
- const lines = [];
11333
- for (let y = start;y <= last; y++) {
11334
- lines.push(buffer.getLine(y)?.translateToString(true).trimEnd() ?? "");
11335
- }
11336
- return lines.join(`
11337
- `);
11522
+ return this.mirror.text(maxLines);
11338
11523
  }
11339
11524
  serializeBuffer() {
11340
- const ansi = this.serializeAddon.serialize({ scrollback: SCROLLBACK_LINES });
11341
- return ansi.length > SCROLLBACK_MAX_CHARS ? ansi.slice(-SCROLLBACK_MAX_CHARS) : ansi;
11525
+ return this.mirror.serialize(SCROLLBACK_MAX_CHARS);
11342
11526
  }
11343
11527
  scheduleSave() {
11344
11528
  if (this.exited || this.saveTimer)
@@ -11353,8 +11537,8 @@ class PtySession {
11353
11537
  try {
11354
11538
  const file = path20.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
11355
11539
  const tmp = `${file}.tmp`;
11356
- fs20.writeFileSync(tmp, this.serializeBuffer(), "utf8");
11357
- fs20.renameSync(tmp, file);
11540
+ fs21.writeFileSync(tmp, this.serializeBuffer(), "utf8");
11541
+ fs21.renameSync(tmp, file);
11358
11542
  } catch (err) {
11359
11543
  log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
11360
11544
  }
@@ -11365,18 +11549,18 @@ class PtySession {
11365
11549
  this.pty.kill();
11366
11550
  }
11367
11551
  }
11368
- var import_headless, import_addon_serialize, SCROLLBACK_LINES = 5000, SAVE_DEBOUNCE_MS = 5000, SCROLLBACK_MAX_CHARS;
11552
+ var SCROLLBACK_LINES = 5000, SAVE_DEBOUNCE_MS = 5000, SCROLLBACK_MAX_CHARS;
11369
11553
  var init_pty_session = __esm(() => {
11370
11554
  init_paths();
11371
11555
  init_pty();
11372
- import_headless = __toESM(require_xterm_headless(), 1);
11373
- import_addon_serialize = __toESM(require_addon_serialize(), 1);
11556
+ init_terminal_mirror();
11374
11557
  SCROLLBACK_MAX_CHARS = 2 * 1024 * 1024;
11375
11558
  });
11376
11559
 
11377
11560
  // packages/daemon/src/session-manager.ts
11378
11561
  import { randomBytes as randomBytes2, randomUUID } from "crypto";
11379
- import fs21 from "fs";
11562
+ import { spawn as spawn5 } from "child_process";
11563
+ import fs22 from "fs";
11380
11564
  import os7 from "os";
11381
11565
  import path21 from "path";
11382
11566
  function sessionScratchRoot(info) {
@@ -11420,6 +11604,14 @@ async function sendableNow(runtime, info, probe, now) {
11420
11604
  occupied
11421
11605
  }, now);
11422
11606
  }
11607
+ function pidIsRunning(pid) {
11608
+ try {
11609
+ process.kill(pid, 0);
11610
+ return true;
11611
+ } catch (err) {
11612
+ return err.code === "EPERM";
11613
+ }
11614
+ }
11423
11615
 
11424
11616
  class SessionManager {
11425
11617
  store;
@@ -11436,14 +11628,58 @@ class SessionManager {
11436
11628
  this.store = store;
11437
11629
  this.hooks = hooks;
11438
11630
  this.configuredDefaults = configuredDefaults;
11439
- const stale = store.markStaleRunningAsExited();
11440
- if (stale > 0)
11441
- log(`boot: marked ${stale} stale session(s) as exited`);
11442
11631
  this.sessionWatch = setInterval(() => this.tickSessions(), SESSION_TICK_MS);
11443
11632
  this.sessionWatch.unref();
11444
11633
  }
11634
+ async recover() {
11635
+ const running = this.store.listSessions().filter((info) => info.state !== "exited");
11636
+ const held = await this.hostSessions();
11637
+ const adoptedIds = [];
11638
+ for (const info of running) {
11639
+ const description = held.get(info.id);
11640
+ if (description === undefined)
11641
+ continue;
11642
+ try {
11643
+ const handle = await this.ptyHost.adopt(description);
11644
+ info.state = "running";
11645
+ delete info.exitCode;
11646
+ delete info.exitedAt;
11647
+ const session = PtySession.adopt(info, handle, description.cols, description.rows);
11648
+ this.wire(session, undefined);
11649
+ this.hostOwned.add(info.id);
11650
+ this.store.markResumed(info.id, info.agentCommand, info.ptyPid);
11651
+ if (agentIdFromCommand(info.agentCommand) === "codex")
11652
+ this.adoptCodexAppServer(info.id);
11653
+ if (agentIdFromCommand(info.agentCommand) === "gemini")
11654
+ this.skipGeminiTelemetryHistory(info.id);
11655
+ adoptedIds.push(info.id);
11656
+ log(`boot: adopted session ${info.shortId} (pty ${description.pid}) from the terminal host`);
11657
+ } catch (err) {
11658
+ log(`boot: could not adopt ${info.shortId}: ${err instanceof Error ? err.message : String(err)}`);
11659
+ }
11660
+ }
11661
+ const sweptExited = this.store.markStaleRunningAsExited(adoptedIds);
11662
+ if (sweptExited > 0)
11663
+ log(`boot: marked ${sweptExited} stale session(s) as exited`);
11664
+ return { adopted: adoptedIds.length, sweptExited };
11665
+ }
11666
+ async hostSessions() {
11667
+ if (process.env["AXTERM_PTY_HOST"] !== "1")
11668
+ return new Map;
11669
+ const existing = readPtyHostIn(RUNTIME_DIR);
11670
+ if (existing === undefined)
11671
+ return new Map;
11672
+ try {
11673
+ this.ptyHost ??= await connectPtyHost({ endpoint: existing.endpoint, token: existing.token });
11674
+ return new Map(this.ptyHost.sessions.map((s) => [s.id, s]));
11675
+ } catch (err) {
11676
+ log(`boot: the terminal host named in pty-host.json did not answer: ${err instanceof Error ? err.message : String(err)}`);
11677
+ return new Map;
11678
+ }
11679
+ }
11445
11680
  sessionWatch;
11446
11681
  ptyHost;
11682
+ hostOwned = new Set;
11447
11683
  ptyHostUnavailable = false;
11448
11684
  codexServers = new Map;
11449
11685
  workspaceFor(_sessionId) {
@@ -11523,7 +11759,7 @@ class SessionManager {
11523
11759
  }
11524
11760
  async create(params) {
11525
11761
  const requestedCwd = params.cwd ? path21.resolve(params.cwd) : os7.homedir();
11526
- if (!fs21.existsSync(requestedCwd)) {
11762
+ if (!fs22.existsSync(requestedCwd)) {
11527
11763
  throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
11528
11764
  }
11529
11765
  let worktree;
@@ -11567,11 +11803,27 @@ class SessionManager {
11567
11803
  const missing = unresolvableAgent(params.commandOverride, agentCommand, lookupOnPath);
11568
11804
  if (missing !== undefined)
11569
11805
  throw new AxtermError("err.agent.notInstalled", { command: missing });
11806
+ const adopted = agent === "claude" ? resumedConversationId(agentCommand) : undefined;
11807
+ let sessionCwd = cwd;
11808
+ if (adopted !== undefined) {
11809
+ const holder = conversationHeldBy(this.allSessions(), adopted);
11810
+ if (holder !== undefined) {
11811
+ throw new AxtermError("err.claude.conversationBusy", { id: adopted, session: holder.shortId });
11812
+ }
11813
+ if (worktree === undefined) {
11814
+ const recorded = claudeConversationCwd(claudeConfigDir(), adopted);
11815
+ sessionCwd = resolveSessionCwd({
11816
+ requested: cwd,
11817
+ preferConversation: params.preferConversationCwd === true,
11818
+ ...recorded !== undefined && fs22.existsSync(recorded) ? { conversationCwd: recorded } : {}
11819
+ });
11820
+ }
11821
+ }
11570
11822
  const info = {
11571
11823
  id,
11572
11824
  shortId: id.slice(0, 6),
11573
- name: params.name ?? worktree?.branch ?? path21.basename(cwd),
11574
- cwd,
11825
+ name: params.name ?? worktree?.branch ?? path21.basename(sessionCwd),
11826
+ cwd: sessionCwd,
11575
11827
  shell: shell.kind,
11576
11828
  agentCommand,
11577
11829
  state: "running",
@@ -11580,8 +11832,7 @@ class SessionManager {
11580
11832
  createdAt: Date.now(),
11581
11833
  ...worktree !== undefined ? { worktreePath: worktree.path, branch: worktree.branch, repoRoot: worktree.repoRoot } : {}
11582
11834
  };
11583
- const appServerUrl = agent === "codex" ? await this.startCodexAppServer(id, this.ensureCodexHome(), cwd) : undefined;
11584
- await this.launch(info, shell, agentCommand, appServerUrl);
11835
+ await this.launch(info, shell, agentCommand);
11585
11836
  this.store.insertSession(info);
11586
11837
  log(`session ${info.shortId} created shell=${shell.kind} cmd=${agentCommand.join(" ") || "(shell only)"}`);
11587
11838
  this.emit({ t: "session.created", info });
@@ -11595,7 +11846,7 @@ class SessionManager {
11595
11846
  launchToken
11596
11847
  });
11597
11848
  const file = this.claudeSettingsPath(id);
11598
- fs21.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
11849
+ fs22.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
11599
11850
  }
11600
11851
  writeGeminiSettings(sessionId) {
11601
11852
  try {
@@ -11608,69 +11859,104 @@ class SessionManager {
11608
11859
  });
11609
11860
  const file = this.geminiSettingsPath();
11610
11861
  const tmp = `${file}.tmp`;
11611
- fs21.writeFileSync(tmp, JSON.stringify(settings, null, 2));
11612
- fs21.renameSync(tmp, file);
11862
+ fs22.writeFileSync(tmp, JSON.stringify(settings, null, 2));
11863
+ fs22.renameSync(tmp, file);
11613
11864
  } catch (err) {
11614
11865
  log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
11615
11866
  }
11616
11867
  }
11617
- async startCodexAppServer(id, codexHome2, cwd) {
11868
+ async startCodexAppServer(id, codexHome2, cwd, identity) {
11618
11869
  try {
11870
+ const inherited = this.adoptCodexAppServer(id);
11871
+ if (inherited !== undefined)
11872
+ return inherited;
11619
11873
  const port = await freeLoopbackPort();
11620
11874
  const url = `ws://127.0.0.1:${port}`;
11621
- const proc = Bun.spawn(["codex", "app-server", "--listen", url], {
11875
+ const child = spawn5("codex", ["app-server", "--listen", url], {
11622
11876
  cwd,
11623
- env: { ...stripHostAttribution(process.env), CODEX_HOME: codexHome2 },
11624
- stdout: "ignore",
11625
- stderr: "ignore",
11626
- stdin: "ignore"
11877
+ detached: true,
11878
+ windowsHide: true,
11879
+ stdio: "ignore",
11880
+ env: { ...sessionEnv(process.env, identity), CODEX_HOME: codexHome2 }
11627
11881
  });
11882
+ child.unref();
11883
+ const pid = child.pid;
11884
+ if (pid === undefined)
11885
+ throw new Error("the app-server was not given a pid");
11628
11886
  if (!await waitForPort(port)) {
11629
- proc.kill();
11887
+ this.killCodexAppServer(pid);
11630
11888
  log(`codex app-server did not open ${port}; session ${id.slice(0, 6)} keeps its hooks`);
11631
11889
  return;
11632
11890
  }
11633
- const client = new CodexAppServerClient(url, (payload) => this.ingestHookEvent(id, payload));
11634
- client.connect();
11635
- this.codexServers.set(id, {
11636
- stop: () => {
11637
- client.close();
11638
- try {
11639
- killTree(proc.pid, "codex.exe");
11640
- } catch {}
11641
- try {
11642
- proc.kill();
11643
- } catch {}
11644
- }
11645
- });
11646
- log(`codex app-server on ${url} for session ${id.slice(0, 6)} in ${cwd}`);
11891
+ this.watchCodexAppServer(id, url, pid);
11892
+ fs22.writeFileSync(this.codexAppServerFile(id), JSON.stringify({ pid, url }), { mode: 384 });
11893
+ log(`codex app-server on ${url} (pid ${pid}) for session ${id.slice(0, 6)} in ${cwd}`);
11647
11894
  return url;
11648
11895
  } catch (err) {
11649
11896
  log(`codex app-server unavailable (${String(err)}); session ${id.slice(0, 6)} keeps its hooks`);
11650
11897
  return;
11651
11898
  }
11652
11899
  }
11900
+ codexAppServerFile(id) {
11901
+ return path21.join(RUNTIME_DIR, `codex-app-${id}.json`);
11902
+ }
11903
+ adoptCodexAppServer(id) {
11904
+ const file = this.codexAppServerFile(id);
11905
+ let record;
11906
+ try {
11907
+ record = JSON.parse(fs22.readFileSync(file, "utf8"));
11908
+ } catch {
11909
+ return;
11910
+ }
11911
+ const pid = record.pid;
11912
+ const url = record.url;
11913
+ if (typeof pid !== "number" || typeof url !== "string")
11914
+ return;
11915
+ if (!pidIsRunning(pid)) {
11916
+ fs22.rmSync(file, { force: true });
11917
+ return;
11918
+ }
11919
+ this.watchCodexAppServer(id, url, pid);
11920
+ log(`codex app-server: reconnected to ${url} (pid ${pid}) for session ${id.slice(0, 6)}`);
11921
+ return url;
11922
+ }
11923
+ watchCodexAppServer(id, url, pid) {
11924
+ const client = new CodexAppServerClient(url, (payload) => this.ingestHookEvent(id, payload));
11925
+ client.connect();
11926
+ this.codexServers.set(id, {
11927
+ stop: () => {
11928
+ client.close();
11929
+ this.killCodexAppServer(pid);
11930
+ fs22.rmSync(this.codexAppServerFile(id), { force: true });
11931
+ }
11932
+ });
11933
+ }
11934
+ killCodexAppServer(pid) {
11935
+ try {
11936
+ killTree(pid, "codex.exe");
11937
+ } catch {}
11938
+ }
11653
11939
  stopCodexAppServer(id) {
11654
11940
  this.codexServers.get(id)?.stop();
11655
11941
  this.codexServers.delete(id);
11656
11942
  }
11657
11943
  ensureCodexHome() {
11658
11944
  const home = path21.join(STATE_DIR, "codex-home");
11659
- fs21.mkdirSync(home, { recursive: true, mode: 448 });
11945
+ fs22.mkdirSync(home, { recursive: true, mode: 448 });
11660
11946
  this.linkCodexAuth(home);
11661
11947
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
11662
11948
  const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
11663
11949
  const hooksPath = codexTrustKeyPath(home);
11664
11950
  const tmp = `${hooksPath}.tmp`;
11665
- fs21.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
11666
- fs21.renameSync(tmp, hooksPath);
11951
+ fs22.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
11952
+ fs22.renameSync(tmp, hooksPath);
11667
11953
  const entries = codexHookTrust(hooksPath, hooksFile);
11668
11954
  const configPath = path21.join(home, "config.toml");
11669
- const existing = fs21.existsSync(configPath) ? fs21.readFileSync(configPath, "utf8") : "";
11955
+ const existing = fs22.existsSync(configPath) ? fs22.readFileSync(configPath, "utf8") : "";
11670
11956
  const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
11671
11957
  if (missing.length > 0) {
11672
11958
  const block = codexTrustStateToml(missing);
11673
- fs21.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
11959
+ fs22.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
11674
11960
  `) ? `
11675
11961
  ${block}` : block);
11676
11962
  }
@@ -11684,20 +11970,20 @@ ${block}` : block);
11684
11970
  }
11685
11971
  linkCodexAuth(home) {
11686
11972
  const target = path21.join(home, "auth.json");
11687
- if (fs21.existsSync(target))
11973
+ if (fs22.existsSync(target))
11688
11974
  return;
11689
11975
  const operatorHome = process.env["CODEX_HOME"] ?? path21.join(os7.homedir(), ".codex");
11690
11976
  const source = path21.join(operatorHome, "auth.json");
11691
- if (!fs21.existsSync(source))
11977
+ if (!fs22.existsSync(source))
11692
11978
  throw new AxtermError("err.codex.noAuth", { authFile: source });
11979
+ let how;
11693
11980
  try {
11694
- fs21.symlinkSync(source, target, "file");
11695
- } catch {
11696
- try {
11697
- fs21.linkSync(source, target);
11698
- } catch (err) {
11699
- throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
11700
- }
11981
+ how = referenceCredential(source, target);
11982
+ } catch (err) {
11983
+ throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
11984
+ }
11985
+ if (how === "copy") {
11986
+ log(`codex auth: neither a symlink nor a hard link to ${source} was possible (state dir on another volume?) \u2014 ` + `copied instead, so a token refresh will not follow; delete ${target} to re-reference it`);
11701
11987
  }
11702
11988
  }
11703
11989
  doctorReport() {
@@ -11709,24 +11995,24 @@ ${block}` : block);
11709
11995
  const configPath = path21.join(home, "config.toml");
11710
11996
  const read = (file) => {
11711
11997
  try {
11712
- return fs21.existsSync(file) ? fs21.readFileSync(file, "utf8") : undefined;
11998
+ return fs22.existsSync(file) ? fs22.readFileSync(file, "utf8") : undefined;
11713
11999
  } catch {
11714
12000
  return;
11715
12001
  }
11716
12002
  };
11717
12003
  return [
11718
- ...evaluateBridgeDoctor({ command: bridge.command, exists: fs21.existsSync(bridge.command) }),
12004
+ ...evaluateBridgeDoctor({ command: bridge.command, exists: fs22.existsSync(bridge.command) }),
11719
12005
  ...evaluateOmpDoctor({
11720
12006
  binary: lookupOnPath("omp"),
11721
12007
  extensionPath: this.hooks.extensionPath,
11722
- extensionExists: fs21.existsSync(this.hooks.extensionPath)
12008
+ extensionExists: fs22.existsSync(this.hooks.extensionPath)
11723
12009
  }),
11724
12010
  ...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
11725
12011
  ...evaluateCodexDoctor({
11726
12012
  binary: lookupOnPath("codex"),
11727
12013
  authSourcePath: authSource,
11728
- authSourceExists: fs21.existsSync(authSource),
11729
- homeExists: fs21.existsSync(home),
12014
+ authSourceExists: fs22.existsSync(authSource),
12015
+ homeExists: fs22.existsSync(home),
11730
12016
  expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
11731
12017
  configToml: read(configPath)
11732
12018
  }),
@@ -11825,6 +12111,18 @@ ${block}` : block);
11825
12111
  session.write(digit);
11826
12112
  return true;
11827
12113
  }
12114
+ answerScreenChoice(idOrPrefix, optionIndex) {
12115
+ const session = this.resolveLive(idOrPrefix);
12116
+ const dialog = detectOnboarding(agentIdFromCommand(session.info.agentCommand), session.screenText(30));
12117
+ const digit = dialog?.options[optionIndex]?.digit;
12118
+ if (digit === undefined) {
12119
+ session.write(buildMenuSelectKeys(optionIndex));
12120
+ return "menu";
12121
+ }
12122
+ this.pendingPrompt.delete(session.info.id);
12123
+ session.write(digit);
12124
+ return "onboarding";
12125
+ }
11828
12126
  sinceLastCapture(id) {
11829
12127
  const at = this.lastCaptureAt.get(id);
11830
12128
  return at === undefined ? undefined : Date.now() - at;
@@ -11852,12 +12150,12 @@ ${block}` : block);
11852
12150
  writeScreenFile(idOrPrefix, lines) {
11853
12151
  const session = this.resolveLive(idOrPrefix);
11854
12152
  const file = path21.join(path21.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
11855
- fs21.writeFileSync(file, session.screenText(lines), { mode: 384 });
12153
+ fs22.writeFileSync(file, session.screenText(lines), { mode: 384 });
11856
12154
  return file;
11857
12155
  }
11858
12156
  removeScreenFile(id) {
11859
12157
  try {
11860
- fs21.rmSync(path21.join(path21.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
12158
+ fs22.rmSync(path21.join(path21.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
11861
12159
  } catch {}
11862
12160
  }
11863
12161
  async stageInboundFile(idOrPrefix, suggestedName, bytes) {
@@ -11877,8 +12175,8 @@ ${block}` : block);
11877
12175
  }
11878
12176
  async stageInRuntimeDir(info, suggestedName, bytes) {
11879
12177
  const file = path21.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
11880
- await fs21.promises.mkdir(path21.dirname(file), { recursive: true, mode: 448 });
11881
- await fs21.promises.writeFile(file, bytes, { mode: 384 });
12178
+ await fs22.promises.mkdir(path21.dirname(file), { recursive: true, mode: 448 });
12179
+ await fs22.promises.writeFile(file, bytes, { mode: 384 });
11882
12180
  log(`session ${info.shortId} staged ${path21.basename(file)} (${bytes.byteLength} bytes, runtime dir)`);
11883
12181
  return file;
11884
12182
  }
@@ -11903,24 +12201,24 @@ ${block}` : block);
11903
12201
  }
11904
12202
  removeStagedFiles(id, info) {
11905
12203
  try {
11906
- fs21.rmSync(path21.join(MATERIALISED_DIR, id), { recursive: true, force: true });
12204
+ fs22.rmSync(path21.join(MATERIALISED_DIR, id), { recursive: true, force: true });
11907
12205
  } catch {}
11908
12206
  if (info === undefined)
11909
12207
  return;
11910
12208
  const scratch = path21.resolve(scratchDir(sessionScratchRoot(info)));
11911
12209
  try {
11912
- fs21.rmSync(path21.resolve(inboundStagingDir(sessionScratchRoot(info)), id), { recursive: true, force: true });
11913
- fs21.rmdirSync(path21.join(scratch, "inbound"));
11914
- fs21.rmSync(path21.join(scratch, SCRATCH_IGNORE_NAME), { force: true });
11915
- fs21.rmdirSync(scratch);
12210
+ fs22.rmSync(path21.resolve(inboundStagingDir(sessionScratchRoot(info)), id), { recursive: true, force: true });
12211
+ fs22.rmdirSync(path21.join(scratch, "inbound"));
12212
+ fs22.rmSync(path21.join(scratch, SCRATCH_IGNORE_NAME), { force: true });
12213
+ fs22.rmdirSync(scratch);
11916
12214
  } catch {}
11917
12215
  }
11918
12216
  removeClaudeSettings(id) {
11919
12217
  try {
11920
- fs21.rmSync(this.claudeSettingsPath(id), { force: true });
11921
- fs21.rmSync(this.mcpConfigPath(id), { force: true });
11922
- fs21.rmSync(this.mcpContextPath(id), { force: true });
11923
- fs21.rmSync(this.geminiTelemetryPath(id), { force: true });
12218
+ fs22.rmSync(this.claudeSettingsPath(id), { force: true });
12219
+ fs22.rmSync(this.mcpConfigPath(id), { force: true });
12220
+ fs22.rmSync(this.mcpContextPath(id), { force: true });
12221
+ fs22.rmSync(this.geminiTelemetryPath(id), { force: true });
11924
12222
  this.stopCodexAppServer(id);
11925
12223
  } catch {}
11926
12224
  }
@@ -11936,7 +12234,7 @@ ${block}` : block);
11936
12234
  writeMcpContext(id, launchToken) {
11937
12235
  const file = this.mcpContextPath(id);
11938
12236
  try {
11939
- fs21.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
12237
+ fs22.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
11940
12238
  return file;
11941
12239
  } catch (err) {
11942
12240
  log(`mcp context write failed; session starts without axterm's tools: ${String(err)}`);
@@ -11949,7 +12247,7 @@ ${block}` : block);
11949
12247
  writeMcpConfig(id, contextPath) {
11950
12248
  const file = this.mcpConfigPath(id);
11951
12249
  try {
11952
- fs21.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
12250
+ fs22.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
11953
12251
  return file;
11954
12252
  } catch (err) {
11955
12253
  log(`mcp config write failed; session starts without axterm's tools: ${String(err)}`);
@@ -11964,7 +12262,7 @@ ${block}` : block);
11964
12262
  }
11965
12263
  async ptySpawner() {
11966
12264
  if (process.env["AXTERM_PTY_HOST"] !== "1" || this.ptyHostUnavailable)
11967
- return spawnPty;
12265
+ return { spawn: spawnPty, hostOwned: false };
11968
12266
  try {
11969
12267
  if (this.ptyHost === undefined) {
11970
12268
  const override = process.env["AXTERM_PTY_HOST_MAIN"];
@@ -11973,11 +12271,11 @@ ${block}` : block);
11973
12271
  log(`pty host: connected (pid ${info.pid})`);
11974
12272
  }
11975
12273
  const host = this.ptyHost;
11976
- return (options) => host.spawn(options);
12274
+ return { spawn: (options) => host.spawn(options), hostOwned: true };
11977
12275
  } catch (err) {
11978
12276
  this.ptyHostUnavailable = true;
11979
12277
  log(`pty host unavailable, owning ptys in this process: ${err instanceof Error ? err.message : String(err)}`);
11980
- return spawnPty;
12278
+ return { spawn: spawnPty, hostOwned: false };
11981
12279
  }
11982
12280
  }
11983
12281
  requireClaudeConversation(agentCommand) {
@@ -11988,7 +12286,7 @@ ${block}` : block);
11988
12286
  throw new AxtermError("err.claude.noConversation", { id: conversation });
11989
12287
  }
11990
12288
  }
11991
- async launch(info, shell, agentCommand, appServerUrl) {
12289
+ async launch(info, shell, agentCommand) {
11992
12290
  const id = info.id;
11993
12291
  const launchToken = randomBytes2(16).toString("hex");
11994
12292
  const agentId = agentIdFromCommand(info.agentCommand);
@@ -11996,11 +12294,22 @@ ${block}` : block);
11996
12294
  this.writeClaudeSettings(id, launchToken);
11997
12295
  if (agentId !== "omp")
11998
12296
  this.writeMcpContext(id, launchToken);
12297
+ if (agentId === "claude")
12298
+ this.writeMcpConfig(id, this.mcpContextPath(id));
11999
12299
  if (agentId === "gemini")
12000
12300
  this.writeGeminiSettings(id);
12001
12301
  const codexHome2 = agentId === "codex" ? this.ensureCodexHome() : undefined;
12002
12302
  if (agentId === "claude")
12003
12303
  this.requireClaudeConversation(agentCommand);
12304
+ const identity = {
12305
+ sessionId: id,
12306
+ launchToken,
12307
+ hookPort: this.hooks.port,
12308
+ hookToken: this.hooks.host.token,
12309
+ endpointFile: this.hooks.endpointFile
12310
+ };
12311
+ const appServerUrl = codexHome2 === undefined ? undefined : await this.startCodexAppServer(id, codexHome2, info.cwd, identity);
12312
+ const spawner = await this.ptySpawner();
12004
12313
  const session = await PtySession.open(info, {
12005
12314
  exe: shell.exe,
12006
12315
  argv: buildShellArgv(shell, withAppServer(agentCommand, appServerUrl)),
@@ -12008,12 +12317,7 @@ ${block}` : block);
12008
12317
  cols: info.cols,
12009
12318
  rows: info.rows,
12010
12319
  env: {
12011
- ...stripHostAttribution(process.env),
12012
- AXTERM_SESSION_ID: id,
12013
- AXTERM_LAUNCH_TOKEN: launchToken,
12014
- AXTERM_HOOK_PORT: String(this.hooks.port),
12015
- AXTERM_HOOK_TOKEN: this.hooks.host.token,
12016
- AXTERM_HOOK_ENDPOINT: this.hooks.endpointFile,
12320
+ ...sessionEnv(process.env, identity),
12017
12321
  ...agentId === "gemini" ? {
12018
12322
  GEMINI_CLI_SYSTEM_DEFAULTS_PATH: this.geminiSettingsPath(),
12019
12323
  GEMINI_TELEMETRY_ENABLED: "true",
@@ -12024,7 +12328,15 @@ ${block}` : block);
12024
12328
  } : {},
12025
12329
  ...codexHome2 !== undefined ? { CODEX_HOME: codexHome2 } : {}
12026
12330
  }
12027
- }, await this.ptySpawner());
12331
+ }, spawner.spawn);
12332
+ if (spawner.hostOwned)
12333
+ this.hostOwned.add(id);
12334
+ this.wire(session, launchToken);
12335
+ return session;
12336
+ }
12337
+ wire(session, launchToken) {
12338
+ const id = session.info.id;
12339
+ const info = session.info;
12028
12340
  this.agentRuntime.set(id, { agentExited: false, choiceSeq: 0, dialogOpen: false });
12029
12341
  this.hooks.host.registerSession(id, launchToken, (payload) => this.ingestHookEvent(id, payload));
12030
12342
  session.onData(() => {
@@ -12051,11 +12363,11 @@ ${block}` : block);
12051
12363
  this.pendingPrompt.delete(id);
12052
12364
  this.live.delete(id);
12053
12365
  this.agentRuntime.delete(id);
12366
+ this.hostOwned.delete(id);
12054
12367
  log(`session ${info.shortId} exited code=${code}`);
12055
12368
  this.emit({ t: "session.exit", sessionId: id, code });
12056
12369
  });
12057
12370
  this.live.set(id, session);
12058
- return session;
12059
12371
  }
12060
12372
  emitActivity(id, payload) {
12061
12373
  const dropped = payload.dropped;
@@ -12226,38 +12538,14 @@ ${block}` : block);
12226
12538
  allSessions() {
12227
12539
  return sessionsByRecency([...this.live.values()].map((s) => s.info), this.store.listSessions());
12228
12540
  }
12229
- async resumeNamed(stored, agent) {
12230
- const mcpConfigPath = agent === "claude" ? this.writeMcpConfig(stored.id, this.mcpContextPath(stored.id)) : undefined;
12231
- const agentCommand = buildAgentResumeCommand({
12232
- agent,
12233
- sessionId: stored.id,
12234
- extensionPath: this.hooks.extensionPath,
12235
- claudeSettingsPath: this.claudeSettingsPath(stored.id),
12236
- ...mcpConfigPath === undefined ? {} : { mcpConfigPath },
12237
- storedArgv: stored.agentCommand
12238
- });
12239
- const info = { ...stored, agentCommand, state: "running" };
12240
- delete info.exitCode;
12241
- delete info.exitedAt;
12242
- delete info.agentState;
12243
- delete info.agentStateSource;
12244
- await this.launch(info, resolveShell(info.shell), agentCommand);
12245
- this.store.markResumed(info.id, agentCommand, info.ptyPid);
12246
- log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
12247
- this.emit({ t: "session.created", info });
12248
- return info;
12249
- }
12250
12541
  async resume(idOrPrefix) {
12251
12542
  const stored = this.store.findByPrefix(idOrPrefix);
12252
12543
  if (!stored)
12253
12544
  throw new AxtermError("err.session.unknown", { id: idOrPrefix });
12254
12545
  if (this.live.has(stored.id))
12255
12546
  throw new AxtermError("err.session.running", { id: stored.shortId });
12256
- const storedAgent = agentIdFromCommand(stored.agentCommand);
12257
- if (storedAgent === "claude" || storedAgent === "gemini")
12258
- return this.resumeNamed(stored, storedAgent);
12259
12547
  const profile = agentProfile(agentIdFromCommand(stored.agentCommand));
12260
- const reference = resumeReference(profile, stored);
12548
+ const reference = resumeReference(profile, { ...stored, ownSessionId: stored.id });
12261
12549
  if (reference === undefined) {
12262
12550
  throw new AxtermError("err.session.noConversation", {
12263
12551
  id: stored.shortId,
@@ -12268,7 +12556,7 @@ ${block}` : block);
12268
12556
  if (agentCommand === undefined) {
12269
12557
  throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
12270
12558
  }
12271
- if (!fs21.existsSync(stored.cwd))
12559
+ if (!fs22.existsSync(stored.cwd))
12272
12560
  throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
12273
12561
  const info = { ...stored, agentCommand, state: "running" };
12274
12562
  delete info.exitCode;
@@ -12276,8 +12564,7 @@ ${block}` : block);
12276
12564
  delete info.agentState;
12277
12565
  delete info.agentStateSource;
12278
12566
  const shell = resolveShell(info.shell);
12279
- const resumedAppServer = agentIdFromCommand(agentCommand) === "codex" ? await this.startCodexAppServer(info.id, this.ensureCodexHome(), info.cwd) : undefined;
12280
- await this.launch(info, shell, agentCommand, resumedAppServer);
12567
+ await this.launch(info, shell, agentCommand);
12281
12568
  this.store.markResumed(info.id, agentCommand, info.ptyPid);
12282
12569
  log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
12283
12570
  this.emit({ t: "session.created", info });
@@ -12343,7 +12630,7 @@ ${block}` : block);
12343
12630
  removeScrollback(id) {
12344
12631
  for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
12345
12632
  try {
12346
- fs21.rmSync(path21.join(SCROLLBACK_DIR, file), { force: true });
12633
+ fs22.rmSync(path21.join(SCROLLBACK_DIR, file), { force: true });
12347
12634
  } catch {}
12348
12635
  }
12349
12636
  }
@@ -12351,6 +12638,14 @@ ${block}` : block);
12351
12638
  this.drainGeminiTelemetry();
12352
12639
  this.watchForStalls();
12353
12640
  }
12641
+ skipGeminiTelemetryHistory(id) {
12642
+ const runtime = this.agentRuntime.get(id);
12643
+ if (runtime === undefined)
12644
+ return;
12645
+ try {
12646
+ runtime.telemetryOffset = fs22.statSync(this.geminiTelemetryPath(id)).size;
12647
+ } catch {}
12648
+ }
12354
12649
  drainGeminiTelemetry() {
12355
12650
  for (const [id, session] of this.live) {
12356
12651
  if (agentIdFromCommand(session.info.agentCommand) !== "gemini")
@@ -12361,7 +12656,7 @@ ${block}` : block);
12361
12656
  const file = this.geminiTelemetryPath(id);
12362
12657
  let size = 0;
12363
12658
  try {
12364
- size = fs21.statSync(file).size;
12659
+ size = fs22.statSync(file).size;
12365
12660
  } catch {
12366
12661
  continue;
12367
12662
  }
@@ -12423,10 +12718,17 @@ ${block}` : block);
12423
12718
  clearInterval(this.sessionWatch);
12424
12719
  for (const id of [...this.codexServers.keys()])
12425
12720
  this.stopCodexAppServer(id);
12426
- for (const session of this.live.values()) {
12721
+ let handedOn = 0;
12722
+ for (const [id, session] of this.live) {
12427
12723
  session.saveScrollback();
12724
+ if (this.hostOwned.has(id)) {
12725
+ handedOn += 1;
12726
+ continue;
12727
+ }
12428
12728
  session.kill();
12429
12729
  }
12730
+ if (handedOn > 0)
12731
+ log(`left ${handedOn} session(s) running in the terminal host for the next daemon`);
12430
12732
  this.live.clear();
12431
12733
  this.agentRuntime.clear();
12432
12734
  }
@@ -12435,7 +12737,7 @@ var DEFAULT_COLS = 120, DEFAULT_ROWS = 30, MATERIALISED_DIR, ACTIVITY_ARG_CHARS
12435
12737
  var init_session_manager = __esm(() => {
12436
12738
  init_src();
12437
12739
  init_codex_home();
12438
- init_host_env();
12740
+ init_session_env();
12439
12741
  init_git();
12440
12742
  init_claude_store();
12441
12743
  init_injector();
@@ -12481,6 +12783,9 @@ class FetchTelegramApi {
12481
12783
  throw new Error(`telegram file download failed: ${res.status}`);
12482
12784
  return new Uint8Array(await res.arrayBuffer());
12483
12785
  }
12786
+ async deleteMessage(chatId, messageId) {
12787
+ await this.call("deleteMessage", { chat_id: chatId, message_id: messageId });
12788
+ }
12484
12789
  async call(method, params = {}) {
12485
12790
  const attempt = async () => {
12486
12791
  const res = await fetch(`https://api.telegram.org/bot${this.botToken}/${method}`, {
@@ -12599,11 +12904,11 @@ var init_acknowledge = __esm(() => {
12599
12904
  });
12600
12905
 
12601
12906
  // packages/channel-telegram/src/attach.ts
12602
- import fs22 from "fs";
12907
+ import fs23 from "fs";
12603
12908
  import path22 from "path";
12604
12909
  function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
12605
12910
  const name = path22.basename(filePath);
12606
- const bytes = fs22.statSync(filePath).size;
12911
+ const bytes = fs23.statSync(filePath).size;
12607
12912
  const plan = planAttachment({ name, bytes }, limits);
12608
12913
  if (plan.kind === "rejected")
12609
12914
  throw new Error(plan.reason);
@@ -12613,7 +12918,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
12613
12918
  form.append("message_thread_id", String(topicId));
12614
12919
  if (caption !== undefined && caption.length > 0)
12615
12920
  form.append("caption", caption);
12616
- form.append(field, new Blob([fs22.readFileSync(filePath)]), name);
12921
+ form.append(field, new Blob([fs23.readFileSync(filePath)]), name);
12617
12922
  return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
12618
12923
  }
12619
12924
  function isPhotoRejection(error) {
@@ -12627,7 +12932,7 @@ function asDocumentUpload(filePath, chatId, topicId, caption) {
12627
12932
  form.append("message_thread_id", String(topicId));
12628
12933
  if (caption !== undefined && caption.length > 0)
12629
12934
  form.append("caption", caption);
12630
- form.append("document", new Blob([fs22.readFileSync(filePath)]), name);
12935
+ form.append("document", new Blob([fs23.readFileSync(filePath)]), name);
12631
12936
  return { method: "sendDocument", field: "document", form };
12632
12937
  }
12633
12938
  var TELEGRAM_LIMITS;
@@ -13523,6 +13828,26 @@ ${code(prompt.line)}`,
13523
13828
  });
13524
13829
  }
13525
13830
  awaitingSecret = new Map;
13831
+ async captureSecret(sessionId, msg, text) {
13832
+ const seq = this.awaitingSecret.get(sessionId);
13833
+ if (seq === undefined)
13834
+ return false;
13835
+ this.awaitingSecret.delete(sessionId);
13836
+ const accepted = this.control.answerShellPrompt(sessionId, seq, text);
13837
+ let deleted = false;
13838
+ if (this.api.deleteMessage && msg.chat && msg.message_id !== undefined) {
13839
+ try {
13840
+ await this.api.deleteMessage(msg.chat.id, msg.message_id);
13841
+ deleted = true;
13842
+ } catch (err) {
13843
+ this.log(`secret reply message ${msg.message_id} not deleted: ${String(err)}`);
13844
+ }
13845
+ }
13846
+ await this.sendToSession(sessionId, tHtml(accepted ? "tg.prompt.secretSubmitted" : "tg.prompt.secretStale"));
13847
+ if (!deleted)
13848
+ await this.sendToSession(sessionId, tHtml("tg.prompt.secretUndeleted"));
13849
+ return true;
13850
+ }
13526
13851
  async onChoiceRequest(sessionId, seq, questions, askId, onboarding2 = false) {
13527
13852
  const binding = this.control.findBindingBySession(sessionId);
13528
13853
  if (!binding || questions.length === 0)
@@ -13686,6 +14011,8 @@ ${code(prompt.line)}`,
13686
14011
  }
13687
14012
  if (!binding)
13688
14013
  return;
14014
+ if (await this.captureSecret(binding.sessionId, msg, text))
14015
+ return;
13689
14016
  if (await this.captureFreeText(binding.sessionId, text))
13690
14017
  return;
13691
14018
  const chatId = msg.chat.id;
@@ -13916,8 +14243,15 @@ ${code(prompt.line)}`,
13916
14243
  const project = await resolveProject(args[0]);
13917
14244
  if (!project)
13918
14245
  return;
13919
- const name = args.slice(1).join(" ") || undefined;
13920
- await this.control.createSession({ cwd: project.path, ...name !== undefined ? { name } : {} });
14246
+ const tail = args.slice(1);
14247
+ const flagAt = tail.findIndex((token) => token.startsWith("-"));
14248
+ const name = (flagAt < 0 ? tail : tail.slice(0, flagAt)).join(" ") || undefined;
14249
+ const agentArgs = flagAt < 0 ? [] : tail.slice(flagAt);
14250
+ await this.control.createSession({
14251
+ cwd: project.path,
14252
+ ...name !== undefined ? { name } : {},
14253
+ ...agentArgs.length > 0 ? { agentArgs } : {}
14254
+ });
13921
14255
  return;
13922
14256
  }
13923
14257
  case "/wt": {
@@ -14205,13 +14539,15 @@ class TelegramService {
14205
14539
  config;
14206
14540
  store;
14207
14541
  manager;
14542
+ apiFactory;
14208
14543
  channel;
14209
14544
  unsubscribe;
14210
14545
  runningConfig;
14211
- constructor(config, store, manager) {
14546
+ constructor(config, store, manager, apiFactory = (token) => new FetchTelegramApi(token)) {
14212
14547
  this.config = config;
14213
14548
  this.store = store;
14214
14549
  this.manager = manager;
14550
+ this.apiFactory = apiFactory;
14215
14551
  }
14216
14552
  readSettings() {
14217
14553
  const telegram = this.config.resolved().channels.telegram;
@@ -14260,7 +14596,7 @@ class TelegramService {
14260
14596
  const token = botToken ?? this.readSettings().botToken;
14261
14597
  if (!token)
14262
14598
  throw new AxtermError("err.telegram.noToken");
14263
- const api = new FetchTelegramApi(token);
14599
+ const api = this.apiFactory(token);
14264
14600
  const updates = await api.call("getUpdates", { timeout: 0 });
14265
14601
  return discoverTelegramTargets(updates);
14266
14602
  }
@@ -14275,7 +14611,7 @@ class TelegramService {
14275
14611
  log("telegram: not configured, channel not started");
14276
14612
  return false;
14277
14613
  }
14278
- const api = new FetchTelegramApi(s.botToken);
14614
+ const api = this.apiFactory(s.botToken);
14279
14615
  const config = {
14280
14616
  groupId: s.groupId,
14281
14617
  allowedUserIds: [s.allowUserId],
@@ -14423,7 +14759,7 @@ var init_telegram_service = __esm(() => {
14423
14759
  // packages/daemon/src/index.ts
14424
14760
  var exports_src = {};
14425
14761
  import { randomBytes as randomBytes3 } from "crypto";
14426
- import fs23 from "fs";
14762
+ import fs24 from "fs";
14427
14763
  import net5 from "net";
14428
14764
  function isPidAlive(pid) {
14429
14765
  try {
@@ -14435,9 +14771,9 @@ function isPidAlive(pid) {
14435
14771
  }
14436
14772
  async function main() {
14437
14773
  ensureDataDirs();
14438
- if (fs23.existsSync(RUNTIME_FILE2)) {
14774
+ if (fs24.existsSync(RUNTIME_FILE2)) {
14439
14775
  try {
14440
- const existing = JSON.parse(fs23.readFileSync(RUNTIME_FILE2, "utf8"));
14776
+ const existing = JSON.parse(fs24.readFileSync(RUNTIME_FILE2, "utf8"));
14441
14777
  if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
14442
14778
  log(`daemon already running (pid ${existing.pid}); exiting`);
14443
14779
  process.exit(0);
@@ -14460,7 +14796,7 @@ async function main() {
14460
14796
  version: productVersion(),
14461
14797
  startedAt: Date.now()
14462
14798
  };
14463
- fs23.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
14799
+ fs24.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
14464
14800
  restrictFile(RUNTIME_FILE2);
14465
14801
  const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
14466
14802
  if (config.loadError !== undefined)
@@ -14512,12 +14848,15 @@ async function main() {
14512
14848
  hookHost.stop();
14513
14849
  store.close();
14514
14850
  try {
14515
- const current = JSON.parse(fs23.readFileSync(RUNTIME_FILE2, "utf8"));
14851
+ const current = JSON.parse(fs24.readFileSync(RUNTIME_FILE2, "utf8"));
14516
14852
  if (current.pid === process.pid)
14517
- fs23.rmSync(RUNTIME_FILE2);
14853
+ fs24.rmSync(RUNTIME_FILE2);
14518
14854
  } catch {}
14519
14855
  process.exit(0);
14520
14856
  };
14857
+ const recovered = await manager.recover();
14858
+ if (recovered.adopted > 0)
14859
+ log(`boot: ${recovered.adopted} session(s) adopted from the terminal host`);
14521
14860
  startRpcServer({
14522
14861
  pipePath: PIPE_PATH,
14523
14862
  token,
@@ -14603,7 +14942,7 @@ function startPtyHost(options) {
14603
14942
  send(socket, {
14604
14943
  t: "welcome",
14605
14944
  v: PTY_PROTOCOL_VERSION,
14606
- sessions: [...sessions.entries()].filter(([, s]) => !s.exited).map(([id, s]) => ({ id, pid: s.handle.pid }))
14945
+ sessions: [...sessions.entries()].filter(([, s]) => !s.exited).map(([id, s]) => ({ id, pid: s.handle.pid, cols: s.cols, rows: s.rows }))
14607
14946
  });
14608
14947
  continue;
14609
14948
  }
@@ -14620,9 +14959,18 @@ function startPtyHost(options) {
14620
14959
  env: message.env,
14621
14960
  term: message.term
14622
14961
  });
14623
- const session = { handle, cols: message.cols, rows: message.rows, exited: false };
14962
+ const session = {
14963
+ handle,
14964
+ cols: message.cols,
14965
+ rows: message.rows,
14966
+ exited: false,
14967
+ mirror: new TerminalMirror(message.cols, message.rows, REPLAY_SCROLLBACK_LINES)
14968
+ };
14624
14969
  sessions.set(message.id, session);
14625
- handle.onData((text) => broadcast(encodeFrame(FrameKind.Data, message.id, new TextEncoder().encode(text))));
14970
+ handle.onData((text) => {
14971
+ session.mirror.write(text);
14972
+ broadcast(encodeFrame(FrameKind.Data, message.id, new TextEncoder().encode(text)));
14973
+ });
14626
14974
  handle.onExit((code2) => {
14627
14975
  session.exited = true;
14628
14976
  for (const client of clients)
@@ -14637,6 +14985,21 @@ function startPtyHost(options) {
14637
14985
  }
14638
14986
  continue;
14639
14987
  }
14988
+ if (message.t === "subscribe") {
14989
+ const session = sessions.get(message.id);
14990
+ if (session === undefined) {
14991
+ send(socket, { t: "error", id: message.id, message: "no such session" });
14992
+ continue;
14993
+ }
14994
+ if (message.replay) {
14995
+ const ansi = session.mirror.serialize(REPLAY_MAX_CHARS);
14996
+ if (ansi.length > 0 && !socket.destroyed) {
14997
+ socket.write(encodeFrame(FrameKind.Data, message.id, new TextEncoder().encode(ansi)));
14998
+ }
14999
+ }
15000
+ send(socket, { t: "subscribed", id: message.id });
15001
+ continue;
15002
+ }
14640
15003
  if (message.t === "resize") {
14641
15004
  const session = sessions.get(message.id);
14642
15005
  if (session === undefined || session.exited)
@@ -14644,6 +15007,7 @@ function startPtyHost(options) {
14644
15007
  session.cols = message.cols;
14645
15008
  session.rows = message.rows;
14646
15009
  session.handle.resize(message.cols, message.rows);
15010
+ session.mirror.resize(message.cols, message.rows);
14647
15011
  continue;
14648
15012
  }
14649
15013
  if (message.t === "kill") {
@@ -14674,10 +15038,13 @@ function startPtyHost(options) {
14674
15038
  });
14675
15039
  });
14676
15040
  }
15041
+ var REPLAY_MAX_CHARS, REPLAY_SCROLLBACK_LINES = 5000;
14677
15042
  var init_pty_host = __esm(() => {
14678
15043
  init_src();
14679
15044
  init_paths();
14680
15045
  init_pty();
15046
+ init_terminal_mirror();
15047
+ REPLAY_MAX_CHARS = 2 * 1024 * 1024;
14681
15048
  });
14682
15049
 
14683
15050
  // packages/daemon/src/pty-host-main.ts
@@ -14730,7 +15097,7 @@ var init_pty_host_main = __esm(() => {
14730
15097
 
14731
15098
  // packages/cli/src/index.ts
14732
15099
  init_src();
14733
- import fs24 from "fs";
15100
+ import fs25 from "fs";
14734
15101
  import path23 from "path";
14735
15102
 
14736
15103
  // packages/cli/src/attach.ts
@@ -15333,6 +15700,7 @@ async function ensureDaemon() {
15333
15700
 
15334
15701
  // packages/cli/src/hook-bridge.ts
15335
15702
  init_src();
15703
+ import fs3 from "fs";
15336
15704
  var POST_TIMEOUT_MS = 1500;
15337
15705
  function isCodexApproval(agent, body) {
15338
15706
  if (agent !== "codex")
@@ -15357,23 +15725,43 @@ async function relayHookEvent(agent) {
15357
15725
  if (body === "")
15358
15726
  return;
15359
15727
  const approval = isCodexApproval(agent, body);
15360
- const res = await fetch(target.url, {
15361
- method: "POST",
15362
- headers: target.headers,
15363
- body,
15364
- signal: AbortSignal.timeout(approval ? CODEX_APPROVAL_POST_TIMEOUT_MS : POST_TIMEOUT_MS)
15365
- });
15366
- if (approval && res.ok) {
15728
+ const timeout = approval ? CODEX_APPROVAL_POST_TIMEOUT_MS : POST_TIMEOUT_MS;
15729
+ const post = (to) => fetch(to.url, { method: "POST", headers: to.headers, body, signal: AbortSignal.timeout(timeout) });
15730
+ let res;
15731
+ try {
15732
+ res = await post(target);
15733
+ } catch {}
15734
+ if (res === undefined || res.status === 401 || res.status === 404) {
15735
+ if (approval && res === undefined)
15736
+ return;
15737
+ const fresh = currentEndpoint();
15738
+ const retarget = fresh === undefined ? undefined : hookPostTarget(process.env, agent, fresh);
15739
+ if (retarget !== undefined && retarget.url !== target.url)
15740
+ res = await post(retarget);
15741
+ else if (res === undefined)
15742
+ return;
15743
+ }
15744
+ if (approval && res !== undefined && res.ok) {
15367
15745
  const decision = await res.text();
15368
15746
  if (decision !== "")
15369
15747
  process.stdout.write(decision);
15370
15748
  }
15371
15749
  } catch {}
15372
15750
  }
15751
+ function currentEndpoint() {
15752
+ const file = process.env["AXTERM_HOOK_ENDPOINT"];
15753
+ if (file === undefined)
15754
+ return;
15755
+ try {
15756
+ return parseHookEndpoint(fs3.readFileSync(file, "utf8"));
15757
+ } catch {
15758
+ return;
15759
+ }
15760
+ }
15373
15761
 
15374
15762
  // packages/cli/src/mcp-server.ts
15375
15763
  init_src();
15376
- import fs3 from "fs";
15764
+ import fs4 from "fs";
15377
15765
  import path4 from "path";
15378
15766
  var ASK_TIMEOUT_MS = 10 * 60000;
15379
15767
  var POST_TIMEOUT_MS2 = 5000;
@@ -15490,7 +15878,7 @@ class McpServer {
15490
15878
  const contextFile = this.io.env[MCP_CONTEXT_ENV];
15491
15879
  if (contextFile !== undefined) {
15492
15880
  try {
15493
- const context = JSON.parse(fs3.readFileSync(contextFile, "utf8"));
15881
+ const context = JSON.parse(fs4.readFileSync(contextFile, "utf8"));
15494
15882
  if (context.port && context.token && context.sessionId) {
15495
15883
  return {
15496
15884
  url: `http://127.0.0.1:${context.port}`,
@@ -15647,13 +16035,13 @@ function runMcpServer() {
15647
16035
  init_src();
15648
16036
  import { spawnSync } from "child_process";
15649
16037
  import { createHash as createHash2 } from "crypto";
15650
- import fs4 from "fs";
16038
+ import fs5 from "fs";
15651
16039
  import path5 from "path";
15652
16040
  import { stdin as stdin2, stdout as stdout2 } from "process";
15653
16041
  // packages/cli/package.json
15654
16042
  var package_default = {
15655
16043
  name: "@layorixinc/axterm",
15656
- version: "0.5.0",
16044
+ version: "0.6.0",
15657
16045
  type: "module",
15658
16046
  bin: {
15659
16047
  axterm: "dist/cli.js"
@@ -15727,7 +16115,7 @@ function assetName(platform, arch, isMusl) {
15727
16115
  function usesMusl() {
15728
16116
  if (process.platform !== "linux")
15729
16117
  return false;
15730
- if (fs4.existsSync("/etc/alpine-release"))
16118
+ if (fs5.existsSync("/etc/alpine-release"))
15731
16119
  return true;
15732
16120
  const ldd = spawnSync("ldd", ["--version"], { encoding: "utf8" });
15733
16121
  return `${ldd.stdout ?? ""}${ldd.stderr ?? ""}`.toLowerCase().includes("musl");
@@ -15741,7 +16129,7 @@ function firstLine(command, args) {
15741
16129
  function detectOrigin(execPath) {
15742
16130
  const resolved = (() => {
15743
16131
  try {
15744
- return fs4.realpathSync(execPath);
16132
+ return fs5.realpathSync(execPath);
15745
16133
  } catch {
15746
16134
  return execPath;
15747
16135
  }
@@ -15783,7 +16171,7 @@ function checksumFor(sums, asset) {
15783
16171
  return;
15784
16172
  }
15785
16173
  function sha256(file) {
15786
- return createHash2("sha256").update(fs4.readFileSync(file)).digest("hex");
16174
+ return createHash2("sha256").update(fs5.readFileSync(file)).digest("hex");
15787
16175
  }
15788
16176
  function ask2(question) {
15789
16177
  if (stdin2.isTTY !== true)
@@ -15830,7 +16218,7 @@ async function runUpdate(client, current, options) {
15830
16218
  }
15831
16219
  const asset = assetName(process.platform, process.arch, usesMusl());
15832
16220
  const base = `${RELEASES}/v${latest.version}`;
15833
- const staging = fs4.mkdtempSync(`${process.execPath}.update-`);
16221
+ const staging = fs5.mkdtempSync(`${process.execPath}.update-`);
15834
16222
  const downloaded = path5.join(staging, asset);
15835
16223
  try {
15836
16224
  console.log(t("cli.update.downloading", { asset }));
@@ -15844,7 +16232,7 @@ async function runUpdate(client, current, options) {
15844
16232
  return;
15845
16233
  }
15846
16234
  if (process.platform !== "win32")
15847
- fs4.chmodSync(downloaded, 493);
16235
+ fs5.chmodSync(downloaded, 493);
15848
16236
  const reported = spawnSync(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
15849
16237
  if (reported !== latest.version) {
15850
16238
  console.error(t("cli.update.wrongVersion", { got: reported ?? "nothing", want: latest.version }));
@@ -15884,7 +16272,7 @@ async function runUpdate(client, current, options) {
15884
16272
  console.log(t("cli.update.restoreFailed", { id, reason: (result.stderr ?? "").trim() || "unknown" }));
15885
16273
  }
15886
16274
  } finally {
15887
- fs4.rmSync(staging, { recursive: true, force: true });
16275
+ fs5.rmSync(staging, { recursive: true, force: true });
15888
16276
  }
15889
16277
  }
15890
16278
  function applySwap(target, downloaded) {
@@ -15893,16 +16281,16 @@ function applySwap(target, downloaded) {
15893
16281
  try {
15894
16282
  for (const step of steps) {
15895
16283
  if (step.op === "chmod")
15896
- fs4.chmodSync(step.from, 493);
16284
+ fs5.chmodSync(step.from, 493);
15897
16285
  else if (step.op === "backup") {
15898
- fs4.renameSync(step.from, step.to);
16286
+ fs5.renameSync(step.from, step.to);
15899
16287
  backup = step.to;
15900
16288
  } else
15901
- fs4.renameSync(step.from, step.to);
16289
+ fs5.renameSync(step.from, step.to);
15902
16290
  }
15903
16291
  } catch (err) {
15904
- if (backup !== undefined && !fs4.existsSync(target))
15905
- fs4.renameSync(backup, target);
16292
+ if (backup !== undefined && !fs5.existsSync(target))
16293
+ fs5.renameSync(backup, target);
15906
16294
  throw err;
15907
16295
  }
15908
16296
  }
@@ -15975,7 +16363,7 @@ function printSessionTable(sessions) {
15975
16363
  console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
15976
16364
  }
15977
16365
  }
15978
- var realPath = (target) => fs24.realpathSync.native(target);
16366
+ var realPath = (target) => fs25.realpathSync.native(target);
15979
16367
  function scopedHere(sessions) {
15980
16368
  return sessionsInScope(sessions, process.cwd(), realPath);
15981
16369
  }
@@ -15988,7 +16376,7 @@ var help = () => t("cli.help");
15988
16376
  function readDotEnv(file = ".env") {
15989
16377
  const out = {};
15990
16378
  try {
15991
- for (const line of fs24.readFileSync(file, "utf8").split(/\r?\n/)) {
16379
+ for (const line of fs25.readFileSync(file, "utf8").split(/\r?\n/)) {
15992
16380
  const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
15993
16381
  if (m)
15994
16382
  out[m[1]] = m[2].trim();
@@ -15999,7 +16387,7 @@ function readDotEnv(file = ".env") {
15999
16387
  function configuredLocale() {
16000
16388
  try {
16001
16389
  const file = resolvePaths(currentPathEnv()).configFile;
16002
- const raw = JSON.parse(fs24.readFileSync(file, "utf8"));
16390
+ const raw = JSON.parse(fs25.readFileSync(file, "utf8"));
16003
16391
  return typeof raw.locale === "string" ? raw.locale : undefined;
16004
16392
  } catch {
16005
16393
  return;
@@ -16055,6 +16443,7 @@ async function main2() {
16055
16443
  const commandOverride = typeof flags.get("cmd") === "string" ? flags.get("cmd").split(/\s+/).filter(Boolean) : undefined;
16056
16444
  const info = await client.call("session.create", {
16057
16445
  cwd: flags.get("cwd") ?? process.cwd(),
16446
+ preferConversationCwd: flags.get("cwd") === undefined,
16058
16447
  name: flags.get("name"),
16059
16448
  shell: flags.get("shell"),
16060
16449
  agent: flags.get("agent"),
@@ -16148,9 +16537,17 @@ async function main2() {
16148
16537
  const [id, indexArg] = positional;
16149
16538
  if (!id || indexArg === undefined)
16150
16539
  throw new Error(t("cli.usage.choose"));
16151
- const keys2 = buildMenuSelectKeys(Number(indexArg));
16152
- await client.call("terminal.write", { id, dataB64: Buffer.from(keys2, "utf8").toString("base64") });
16540
+ const index = Number(indexArg);
16541
+ const answered = await client.call("session.choose", { id, index }).catch(async (err) => {
16542
+ if (!/unknown method/i.test(String(err)))
16543
+ throw err;
16544
+ const keys2 = buildMenuSelectKeys(index);
16545
+ await client.call("terminal.write", { id, dataB64: Buffer.from(keys2, "utf8").toString("base64") });
16546
+ return { answered: "menu" };
16547
+ });
16153
16548
  console.log(t("cli.session.selected", { index: indexArg }));
16549
+ if (answered.answered === "onboarding")
16550
+ console.log(t("cli.session.selectedDialog"));
16154
16551
  break;
16155
16552
  }
16156
16553
  case "setup": {