@layorixinc/axterm 0.6.0 → 0.6.1

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 +1371 -404
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -46,6 +46,15 @@ var __export = (target, all) => {
46
46
  };
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
 
49
+ // packages/core/src/menu-keys.ts
50
+ function buildMenuSelectKeys(index, normalizePresses = MENU_NORMALIZE_PRESSES) {
51
+ if (!Number.isInteger(index) || index < 0) {
52
+ throw new RangeError(`menu index must be a non-negative integer, got ${index}`);
53
+ }
54
+ return MENU_UP.repeat(normalizePresses) + MENU_DOWN.repeat(index) + MENU_SELECT;
55
+ }
56
+ var MENU_UP = "\x1B[A", MENU_DOWN = "\x1B[B", MENU_SELECT = "\r", MENU_NORMALIZE_PRESSES = 24;
57
+
49
58
  // packages/core/src/agent-registry.ts
50
59
  function agentProfile(id) {
51
60
  const known = PROFILES[id];
@@ -62,6 +71,13 @@ function agentProfile(id) {
62
71
  hostHookSource: id
63
72
  };
64
73
  }
74
+ function menuChoiceKeys(profile, optionIndex) {
75
+ const keys = profile.approvalKeys;
76
+ if (keys !== undefined) {
77
+ return optionIndex === 0 ? keys.allow : optionIndex === 1 ? keys.deny : undefined;
78
+ }
79
+ return profile.clampingApprovalMenu === true ? buildMenuSelectKeys(optionIndex) : undefined;
80
+ }
65
81
  function renderAgentTitle(profile, state, frame) {
66
82
  if (state === undefined)
67
83
  return;
@@ -87,8 +103,15 @@ var init_agent_registry = __esm(() => {
87
103
  omp: {
88
104
  id: "omp",
89
105
  displayName: "OMP",
90
- title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
106
+ title: {
107
+ workingLabel: "OMP",
108
+ permissionLabel: "OMP - action required",
109
+ idleLabel: "OMP ready",
110
+ synthesizeWorking: false,
111
+ synthesizeTerminal: false
112
+ },
91
113
  hostHookSource: "omp",
114
+ clampingApprovalMenu: true,
92
115
  approvalBypassArgs: ["--approval-mode", "yolo"],
93
116
  quota: "gauge",
94
117
  resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
@@ -158,6 +181,8 @@ var init_agent_registry = __esm(() => {
158
181
  hostHookSource: "claude",
159
182
  quota: "event",
160
183
  approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
184
+ approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
185
+ structuredApprovals: true,
161
186
  resume: { argv: ["--resume", "{ref}"], ref: "sessionId", selfNamed: "uuid" }
162
187
  },
163
188
  gemini: {
@@ -1011,6 +1036,7 @@ usage:
1011
1036
  axterm rm <id\u2026> [--force] forget a session: its row, scrollback and topic binding
1012
1037
  axterm rm --exited [--all] forget every exited session this listing would show
1013
1038
  axterm daemon status|stop
1039
+ axterm service install|status|uninstall start at login and restart daemon failures
1014
1040
  axterm project add <dir> [alias] | list | rm <alias> register a project for channel use
1015
1041
  axterm run [--worktree BRANCH] [--base REF] ... worktree-backed session
1016
1042
  axterm fanout --count 3 [--prefix try] "prompt"
@@ -1039,6 +1065,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1039
1065
  "cli.session.sentKey": "sent {key}",
1040
1066
  "cli.session.selected": "selected option {index}",
1041
1067
  "cli.session.selectedDialog": "answered the agent's first-run dialog",
1068
+ "cli.choose.daemonTooOld": "this daemon is too old to answer a dialog safely \u2014 its `choose` sent keystrokes that land on the row below on claude and gemini, which is a wider grant. Restart the daemon, or send the keys yourself with `axterm key`",
1042
1069
  "cli.resume.done": "resumed session {id} ({command})",
1043
1070
  "cli.update.current": "axterm {version} is the latest version",
1044
1071
  "cli.update.available": "axterm {latest} is available (you have {current})",
@@ -1082,6 +1109,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1082
1109
  "cli.usage.worktree": "usage: axterm worktree list|rm <branch> [--force]",
1083
1110
  "cli.usage.worktreeRm": "usage: axterm worktree rm <branch> [--force]",
1084
1111
  "cli.usage.daemon": "usage: axterm daemon status|stop",
1112
+ "cli.usage.service": "usage: axterm service install|status|uninstall",
1085
1113
  "cli.usage.telegram": "usage: axterm telegram link|doctor|status|discover",
1086
1114
  "cli.usage.fanout": "usage: axterm fanout --count N <prompt>",
1087
1115
  "cli.config.set": "set {path}",
@@ -1098,6 +1126,13 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1098
1126
  "cli.worktree.refused": "refused: {reason}",
1099
1127
  "cli.daemon.stopping": "daemon stopping",
1100
1128
  "cli.daemon.notReady": "daemon did not become ready within 15s (see {log})",
1129
+ "cli.service.installed": "installed {kind} service; the daemon is running under the supervisor",
1130
+ "cli.service.uninstalled": "removed service registration; the current daemon keeps running",
1131
+ "cli.service.status": "service: {kind}; installed={installed}; active={active}",
1132
+ "cli.service.yes": "yes",
1133
+ "cli.service.no": "no",
1134
+ "cli.service.commandFailed": "service command failed ({command}): {error}",
1135
+ "cli.service.notReady": "the service started but the daemon was not ready within 15s",
1101
1136
  "cli.telegram.missingSettings": "telegram link: no token/group/user found. Provide --token/--group/--user flags, TELEGRAM_* environment variables, or a .env file in the current directory.",
1102
1137
  "cli.telegram.discoverHint": "Create a group with Topics enabled, add your bot to it, then run this again. Being added is enough \u2014 you do not have to send anything.",
1103
1138
  "cli.telegram.discoverNone": "the bot cannot see any group yet.",
@@ -1128,6 +1163,8 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1128
1163
  "tg.status.inferred": "\u2139\uFE0F Guessed from what is running under the shell \u2014 this agent reports nothing, so there are no approval buttons or replies here (axterm attach shows everything).",
1129
1164
  "tg.session.emptyScreen": "(empty screen)",
1130
1165
  "tg.prompt.confirm": "the session is waiting on a confirmation:",
1166
+ "tg.poll.conflict": "\u26A0\uFE0F {machine} cannot receive messages: another axterm is polling this bot token, and Telegram allows only one. Stop the other instance, or give this machine its own bot.",
1167
+ "tg.poll.failed": "\u26A0\uFE0F {machine} cannot receive messages right now: {reason}",
1131
1168
  "tg.prompt.secretAsk": "the session is asking for a secret. Reply with it and the message will be deleted:",
1132
1169
  "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
1170
  "tg.prompt.secretSubmitted": "the secret reached the session",
@@ -1177,6 +1214,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1177
1214
  "tg.answer.pickAtLeastOne": "pick at least one",
1178
1215
  "tg.answer.confirmed": "confirmed",
1179
1216
  "tg.answer.unknownOption": "no such option",
1217
+ "tg.answer.noSafeKey": "axterm cannot press this row safely \u2014 use /key or /type",
1180
1218
  "tg.answer.picked": "{label} selected",
1181
1219
  "tg.answer.toggledOn": "{label} selected",
1182
1220
  "tg.answer.toggledOff": "{label} cleared",
@@ -1225,6 +1263,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1225
1263
  "doctor.codex.trustStale": "codex hooks: {stale}/{total} trust hashes stale \u2014 codex reports them Modified and silently skips them; re-grant via /hooks in the codex TUI",
1226
1264
  "doctor.codex.trustPending": "codex hooks: {pending}/{total} not yet granted \u2014 appended at the next codex launch",
1227
1265
  "doctor.telegram.ok": "telegram: connected",
1266
+ "doctor.telegram.pollFailing": "telegram cannot receive: {reason}",
1228
1267
  "doctor.telegram.off": "telegram: not running",
1229
1268
  "doctor.telegram.problems": "telegram: {problems}",
1230
1269
  "quota.gauge": "{agent}: {percent}% of {window} used, resets in {resets}",
@@ -1250,11 +1289,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
1250
1289
  "err.session.dialogOpen": "session {id} is waiting on a dialog ({state}); answer it with the buttons or /esc first",
1251
1290
  "err.agent.notInstalled": "the agent command '{command}' was not found on PATH \u2014 install it, or pass --cmd to run something else",
1252
1291
  "err.agent.unknown": "unknown agent '{agent}' \u2014 supported: {supported}",
1292
+ "err.choose.noSafeKey": "axterm cannot select row {index} of a {agent} dialog: its menu wraps, so an absolute row cannot be addressed from an unknown cursor position \u2014 use `axterm key` for the keys you want",
1253
1293
  "err.staging.dir": "could not create the session's staging directory {dir} ({error}) \u2014 fix the permissions, or set defaults.staging to runtime",
1254
1294
  "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
1295
  "err.claude.conversationBusy": "session {session} is already in conversation {id} \u2014 two sessions writing one transcript would corrupt it; kill that one first",
1256
1296
  "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",
1257
- "codex.approval.denied": "Denied from the axterm channel",
1297
+ "agent.approval.denied": "Denied from the axterm channel",
1258
1298
  "err.codex.noAuth": "codex is not logged in ({authFile} not found) \u2014 run `codex login` once, then start the session again",
1259
1299
  "err.codex.authLink": "could not link codex credentials into axterm's CODEX_HOME ({reason}) \u2014 refusing to start a logged-out agent",
1260
1300
  "health.config.invalid": "the configuration file could not be understood",
@@ -1317,6 +1357,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1317
1357
  axterm rm <id\u2026> [--force] \uC138\uC158\uC744 \uC78A\uB294\uB2E4: \uBAA9\uB85D\xB7\uC2A4\uD06C\uB864\uBC31\xB7\uD1A0\uD53D \uC5F0\uACB0\uAE4C\uC9C0
1318
1358
  axterm rm --exited [--all] \uC774 \uBAA9\uB85D\uC5D0 \uBCF4\uC774\uB294 \uC885\uB8CC\uB41C \uC138\uC158\uC744 \uBAA8\uB450 \uC78A\uB294\uB2E4
1319
1359
  axterm daemon status|stop
1360
+ axterm service install|status|uninstall \uB85C\uADF8\uC778 \uC2DC \uC2DC\uC791\uD558\uACE0 \uB370\uBAAC \uC2E4\uD328 \uC2DC \uC7AC\uC2DC\uC791
1320
1361
  axterm project add <\uD3F4\uB354> [\uBCC4\uCE6D] | list | rm <\uBCC4\uCE6D> \uCC44\uB110\uC5D0\uC11C \uC4F8 \uD504\uB85C\uC81D\uD2B8 \uB4F1\uB85D
1321
1362
  axterm run [--worktree \uBE0C\uB79C\uCE58] [--base REF] ... worktree \uAE30\uBC18 \uC138\uC158
1322
1363
  axterm fanout --count 3 [--prefix try] "\uD504\uB86C\uD504\uD2B8"
@@ -1345,6 +1386,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1345
1386
  "cli.session.sentKey": "{key} \uC804\uB2EC\uB428",
1346
1387
  "cli.session.selected": "{index}\uBC88 \uD56D\uBAA9 \uC120\uD0DD\uB428",
1347
1388
  "cli.session.selectedDialog": "\uC5D0\uC774\uC804\uD2B8\uC758 \uCCAB \uC2E4\uD589 \uB300\uD654\uC0C1\uC790\uC5D0 \uC751\uB2F5\uD588\uC2B5\uB2C8\uB2E4",
1389
+ "cli.choose.daemonTooOld": "\uC774 \uB370\uBAAC\uC740 \uB300\uD654\uC0C1\uC790\uB97C \uC548\uC804\uD558\uAC8C \uC751\uB2F5\uD560 \uC218 \uC5C6\uB294 \uAD6C\uBC84\uC804\uC785\uB2C8\uB2E4. \uC608\uC804 `choose` \uB294 claude \uC640 gemini \uC5D0\uC11C \uD55C \uD589 \uC544\uB798\uB97C \uC120\uD0DD\uD588\uACE0 \uADF8\uAC83\uC740 \uB354 \uB113\uC740 \uC2B9\uC778\uC785\uB2C8\uB2E4. \uB370\uBAAC\uC744 \uC7AC\uC2DC\uC791\uD558\uAC70\uB098 `axterm key` \uB85C \uC9C1\uC811 \uD0A4\uB97C \uBCF4\uB0B4\uC138\uC694",
1348
1390
  "cli.resume.done": "\uC138\uC158 {id} \uC7AC\uAC1C\uB428 ({command})",
1349
1391
  "cli.update.current": "axterm {version} \uC774 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4",
1350
1392
  "cli.update.available": "axterm {latest} \uC744(\uB97C) \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (\uD604\uC7AC {current})",
@@ -1388,6 +1430,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1388
1430
  "cli.usage.worktree": "\uC0AC\uC6A9\uBC95: axterm worktree list|rm <\uBE0C\uB79C\uCE58> [--force]",
1389
1431
  "cli.usage.worktreeRm": "\uC0AC\uC6A9\uBC95: axterm worktree rm <\uBE0C\uB79C\uCE58> [--force]",
1390
1432
  "cli.usage.daemon": "\uC0AC\uC6A9\uBC95: axterm daemon status|stop",
1433
+ "cli.usage.service": "\uC0AC\uC6A9\uBC95: axterm service install|status|uninstall",
1391
1434
  "cli.usage.telegram": "\uC0AC\uC6A9\uBC95: axterm telegram link|doctor|status|discover",
1392
1435
  "cli.usage.fanout": "\uC0AC\uC6A9\uBC95: axterm fanout --count N <\uD504\uB86C\uD504\uD2B8>",
1393
1436
  "cli.config.set": "{path} \uC124\uC815\uB428",
@@ -1404,6 +1447,13 @@ usage: /fanout <project> <count|branches> <prompt>`,
1404
1447
  "cli.worktree.refused": "\uAC70\uBD80\uB428: {reason}",
1405
1448
  "cli.daemon.stopping": "\uB370\uBAAC \uC885\uB8CC \uC911",
1406
1449
  "cli.daemon.notReady": "15\uCD08 \uC548\uC5D0 \uB370\uBAAC\uC774 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 ({log} \uD655\uC778)",
1450
+ "cli.service.installed": "{kind} \uC11C\uBE44\uC2A4 \uC124\uCE58\uB428. \uB370\uBAAC\uC740 supervisor \uC544\uB798\uC5D0\uC11C \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4",
1451
+ "cli.service.uninstalled": "\uC11C\uBE44\uC2A4 \uB4F1\uB85D\uC744 \uC81C\uAC70\uD588\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uB370\uBAAC\uC740 \uACC4\uC18D \uC2E4\uD589\uB429\uB2C8\uB2E4",
1452
+ "cli.service.status": "\uC11C\uBE44\uC2A4: {kind}; \uC124\uCE58={installed}; \uD65C\uC131={active}",
1453
+ "cli.service.yes": "\uC608",
1454
+ "cli.service.no": "\uC544\uB2C8\uC694",
1455
+ "cli.service.commandFailed": "\uC11C\uBE44\uC2A4 \uBA85\uB839 \uC2E4\uD328 ({command}): {error}",
1456
+ "cli.service.notReady": "\uC11C\uBE44\uC2A4\uAC00 \uC2DC\uC791\uB410\uC9C0\uB9CC 15\uCD08 \uC548\uC5D0 \uB370\uBAAC\uC774 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4",
1407
1457
  "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.",
1408
1458
  "tg.help.general.header": "axterm \uBA85\uB839 (General \uD1A0\uD53D):",
1409
1459
  "tg.help.general.projects": "/projects \u2014 \uB4F1\uB85D\uB41C \uD504\uB85C\uC81D\uD2B8",
@@ -1434,6 +1484,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
1434
1484
  "tg.status.inferred": "\u2139\uFE0F \uC178\uC5D0\uC11C \uC2E4\uD589 \uC911\uC778 \uD504\uB85C\uC138\uC2A4\uB85C \uCD94\uC815\uD55C \uC0C1\uD0DC\uC785\uB2C8\uB2E4 \u2014 \uC774 \uC5D0\uC774\uC804\uD2B8\uB294 \uC0C1\uD0DC\uB97C \uBCF4\uACE0\uD558\uC9C0 \uC54A\uC544 \uC2B9\uC778 \uBC84\uD2BC\uB3C4, \uB2F5\uBCC0\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC804\uCCB4\uB294 axterm attach\uB85C \uD655\uC778\uD558\uC138\uC694).",
1435
1485
  "tg.session.emptyScreen": "(\uBE48 \uD654\uBA74)",
1436
1486
  "tg.prompt.confirm": "\uC138\uC158\uC774 \uD655\uC778\uC744 \uAE30\uB2E4\uB9AC\uACE0 \uC788\uC2B5\uB2C8\uB2E4:",
1487
+ "tg.poll.conflict": "\u26A0\uFE0F {machine} \uC774 \uBA54\uC2DC\uC9C0\uB97C \uBC1B\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: \uB2E4\uB978 axterm \uC774 \uC774 \uBD07 \uD1A0\uD070\uC744 \uD3F4\uB9C1\uD558\uACE0 \uC788\uACE0 Telegram \uC740 \uD558\uB098\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4. \uB2E4\uB978 \uC778\uC2A4\uD134\uC2A4\uB97C \uB044\uAC70\uB098 \uC774 \uBA38\uC2E0\uC5D0 \uC804\uC6A9 \uBD07\uC744 \uC8FC\uC138\uC694.",
1488
+ "tg.poll.failed": "\u26A0\uFE0F {machine} \uC774 \uC9C0\uAE08 \uBA54\uC2DC\uC9C0\uB97C \uBC1B\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {reason}",
1437
1489
  "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:",
1438
1490
  "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
1491
  "tg.prompt.secretSubmitted": "\uBE44\uBC00\uAC12\uC774 \uC138\uC158\uC5D0 \uC804\uB2EC\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
@@ -1483,6 +1535,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1483
1535
  "tg.answer.pickAtLeastOne": "\uCD5C\uC18C 1\uAC1C\uB294 \uC120\uD0DD\uD558\uC138\uC694",
1484
1536
  "tg.answer.confirmed": "\uD655\uC815\uB428",
1485
1537
  "tg.answer.unknownOption": "\uC5C6\uB294 \uC120\uD0DD\uC9C0",
1538
+ "tg.answer.noSafeKey": "\uC774 \uD589\uC740 \uC548\uC804\uD558\uAC8C \uB204\uB97C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 /key \uB610\uB294 /type\uC744 \uC4F0\uC138\uC694",
1486
1539
  "tg.answer.picked": "{label} \uC120\uD0DD\uB428",
1487
1540
  "tg.answer.toggledOn": "{label} \uC120\uD0DD",
1488
1541
  "tg.answer.toggledOff": "{label} \uD574\uC81C",
@@ -1531,6 +1584,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1531
1584
  "doctor.codex.trustStale": "codex \uD6C5: {stale}/{total} \uC2E0\uB8B0 \uD574\uC2DC \uBD88\uC77C\uCE58 \u2014 codex\uAC00 Modified\uB85C \uBCF4\uACE0\uD558\uACE0 \uC870\uC6A9\uD788 \uAC74\uB108\uB701\uB2C8\uB2E4; codex TUI\uC758 /hooks\uC5D0\uC11C \uC7AC\uC2B9\uC778\uD558\uC138\uC694",
1532
1585
  "doctor.codex.trustPending": "codex \uD6C5: {pending}/{total} \uBBF8\uC2B9\uC778 \u2014 \uB2E4\uC74C codex \uC2E4\uD589 \uC2DC \uAE30\uB85D\uB429\uB2C8\uB2E4",
1533
1586
  "doctor.telegram.ok": "\uD154\uB808\uADF8\uB7A8: \uC5F0\uACB0\uB428",
1587
+ "doctor.telegram.pollFailing": "\uD154\uB808\uADF8\uB7A8 \uC218\uC2E0 \uBD88\uAC00: {reason}",
1534
1588
  "doctor.telegram.off": "\uD154\uB808\uADF8\uB7A8: \uBBF8\uC2E4\uD589",
1535
1589
  "doctor.telegram.problems": "\uD154\uB808\uADF8\uB7A8: {problems}",
1536
1590
  "quota.gauge": "{agent}: {window} \uD55C\uB3C4\uC758 {percent}% \uC0AC\uC6A9, {resets} \uD6C4 \uCD08\uAE30\uD654",
@@ -1556,11 +1610,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
1556
1610
  "err.session.dialogOpen": "\uC138\uC158 {id} \uC774(\uAC00) \uB2E4\uC774\uC5BC\uB85C\uADF8\uB97C \uAE30\uB2E4\uB9AC\uB294 \uC911\uC785\uB2C8\uB2E4 ({state}). \uBC84\uD2BC\uC73C\uB85C \uB2F5\uD558\uAC70\uB098 /esc \uB97C \uBA3C\uC800 \uBCF4\uB0B4\uC138\uC694",
1557
1611
  "err.agent.notInstalled": "\uC5D0\uC774\uC804\uD2B8 \uBA85\uB839 '{command}' \uC744(\uB97C) PATH \uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC124\uCE58\uD558\uAC70\uB098 --cmd \uB85C \uB2E4\uB978 \uBA85\uB839\uC744 \uC9C0\uC815\uD558\uC138\uC694",
1558
1612
  "err.agent.unknown": "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uC774\uC804\uD2B8 '{agent}' \uC785\uB2C8\uB2E4. \uC9C0\uC6D0: {supported}",
1613
+ "err.choose.noSafeKey": "{agent} \uB300\uD654\uC0C1\uC790\uC758 {index}\uBC88 \uD589\uC740 \uC120\uD0DD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA54\uB274\uAC00 \uC21C\uD658\uD558\uBBC0\uB85C \uCEE4\uC11C \uC704\uCE58\uB97C \uBAA8\uB974\uB294 \uC0C1\uD0DC\uC5D0\uC11C \uC808\uB300 \uD589\uC744 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC6D0\uD558\uB294 \uD0A4\uB294 `axterm key`\uB85C \uBCF4\uB0B4\uC138\uC694",
1559
1614
  "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",
1560
1615
  "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
1616
  "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",
1562
1617
  "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",
1563
- "codex.approval.denied": "axterm \uCC44\uB110\uC5D0\uC11C \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
1618
+ "agent.approval.denied": "axterm \uCC44\uB110\uC5D0\uC11C \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
1564
1619
  "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",
1565
1620
  "err.codex.authLink": "codex \uC790\uACA9 \uC99D\uBA85\uC744 axterm\uC758 CODEX_HOME\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 ({reason}). \uB85C\uADF8\uC544\uC6C3 \uC0C1\uD0DC\uC758 \uC5D0\uC774\uC804\uD2B8\uB294 \uC2DC\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
1566
1621
  "health.config.invalid": "\uC124\uC815 \uD30C\uC77C\uC744 \uC774\uD574\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
@@ -1864,6 +1919,222 @@ var init_self_spawn = __esm(() => {
1864
1919
  RUNTIME_NAMES = new Set(["bun", "bun.exe", "bun-debug", "bun-debug.exe", "node", "node.exe"]);
1865
1920
  });
1866
1921
 
1922
+ // packages/core/src/service.ts
1923
+ import path2 from "path";
1924
+ function xml(value) {
1925
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
1926
+ }
1927
+ function systemdArgument(value) {
1928
+ return `"${value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%")}"`;
1929
+ }
1930
+ function windowsArgument(value) {
1931
+ if (value !== "" && !/[\s"]/u.test(value))
1932
+ return value;
1933
+ let out = '"';
1934
+ let slashes = 0;
1935
+ for (const char of value) {
1936
+ if (char === "\\") {
1937
+ slashes++;
1938
+ continue;
1939
+ }
1940
+ if (char === '"') {
1941
+ out += "\\".repeat(slashes * 2 + 1) + '"';
1942
+ slashes = 0;
1943
+ continue;
1944
+ }
1945
+ out += "\\".repeat(slashes) + char;
1946
+ slashes = 0;
1947
+ }
1948
+ return out + "\\".repeat(slashes * 2) + '"';
1949
+ }
1950
+ function windowsCommandLine(args) {
1951
+ return args.map(windowsArgument).join(" ");
1952
+ }
1953
+ function environmentEntries(input) {
1954
+ return Object.entries(input.environment ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => {
1955
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key))
1956
+ throw new Error(`invalid service environment key: ${key}`);
1957
+ if (/[\0\r\n]/u.test(value))
1958
+ throw new Error(`invalid service environment value for ${key}`);
1959
+ return [key, value];
1960
+ });
1961
+ }
1962
+ function systemdEnvironment(input) {
1963
+ return environmentEntries(input).map(([key, value]) => `Environment="${`${key}=${value}`.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%")}"`).join(`
1964
+ `);
1965
+ }
1966
+ function launchdEnvironment(input) {
1967
+ const entries = environmentEntries(input);
1968
+ if (entries.length === 0)
1969
+ return "";
1970
+ return ` <key>EnvironmentVariables</key>
1971
+ <dict>
1972
+ ${entries.map(([key, value]) => ` <key>${xml(key)}</key>
1973
+ <string>${xml(value)}</string>`).join(`
1974
+ `)}
1975
+ </dict>
1976
+ `;
1977
+ }
1978
+ function systemdDefinition(input) {
1979
+ const argv = [input.launch.command, ...input.launch.args].map(systemdArgument).join(" ");
1980
+ const environment = systemdEnvironment(input);
1981
+ return {
1982
+ kind: "systemd",
1983
+ id: SYSTEMD_SERVICE,
1984
+ file: path2.posix.join(input.homeDir, ".config/systemd/user", SYSTEMD_SERVICE),
1985
+ contents: `[Unit]
1986
+ Description=axterm daemon
1987
+ After=network-online.target
1988
+ Wants=network-online.target
1989
+
1990
+ [Service]
1991
+ Type=simple
1992
+ ExecStart=:${argv}
1993
+ ${environment ? `${environment}
1994
+ ` : ""}Restart=on-failure
1995
+ RestartSec=2
1996
+ TimeoutStopSec=20
1997
+ # The PTY host is detached but remains in this cgroup. Stop only the daemon.
1998
+ KillMode=process
1999
+
2000
+ [Install]
2001
+ WantedBy=default.target
2002
+ `
2003
+ };
2004
+ }
2005
+ function launchdDefinition(input) {
2006
+ const argumentsXml = [input.launch.command, ...input.launch.args].map((arg) => ` <string>${xml(arg)}</string>`).join(`
2007
+ `);
2008
+ const logFile = path2.posix.join(input.stateDir, "logs/service.log");
2009
+ return {
2010
+ kind: "launchd",
2011
+ id: LAUNCHD_LABEL,
2012
+ file: path2.posix.join(input.homeDir, "Library/LaunchAgents", `${LAUNCHD_LABEL}.plist`),
2013
+ contents: `<?xml version="1.0" encoding="UTF-8"?>
2014
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2015
+ <plist version="1.0">
2016
+ <dict>
2017
+ <key>Label</key>
2018
+ <string>${LAUNCHD_LABEL}</string>
2019
+ <key>ProgramArguments</key>
2020
+ <array>
2021
+ ${argumentsXml}
2022
+ </array>
2023
+ <key>WorkingDirectory</key>
2024
+ <string>${xml(input.homeDir)}</string>
2025
+ ${launchdEnvironment(input)} <key>RunAtLoad</key>
2026
+ <true/>
2027
+ <key>KeepAlive</key>
2028
+ <dict>
2029
+ <key>SuccessfulExit</key>
2030
+ <false/>
2031
+ </dict>
2032
+ <key>ThrottleInterval</key>
2033
+ <integer>2</integer>
2034
+ <key>AbandonProcessGroup</key>
2035
+ <true/>
2036
+ <key>StandardOutPath</key>
2037
+ <string>${xml(logFile)}</string>
2038
+ <key>StandardErrorPath</key>
2039
+ <string>${xml(logFile)}</string>
2040
+ </dict>
2041
+ </plist>
2042
+ `
2043
+ };
2044
+ }
2045
+ function powershellLiteral(value) {
2046
+ return `'${value.replaceAll("'", "''")}'`;
2047
+ }
2048
+ function taskDefinition(input) {
2049
+ if (!input.userId)
2050
+ throw new Error("Task Scheduler requires userId");
2051
+ const entries = environmentEntries(input);
2052
+ let command = input.launch.command;
2053
+ let argumentsLine = windowsCommandLine(input.launch.args);
2054
+ let companionFiles;
2055
+ if (entries.length > 0) {
2056
+ const wrapperFile = path2.win32.join(input.stateDir, "service", "axterm-service.ps1");
2057
+ const environment = entries.map(([key, value]) => `$env:${key} = ${powershellLiteral(value)}`).join(`\r
2058
+ `);
2059
+ const invocation = [input.launch.command, ...input.launch.args].map(powershellLiteral).join(" ");
2060
+ companionFiles = [
2061
+ {
2062
+ file: wrapperFile,
2063
+ contents: `$ErrorActionPreference = 'Stop'\r
2064
+ ${environment}\r
2065
+ & ${invocation}\r
2066
+ exit $LASTEXITCODE\r
2067
+ `
2068
+ }
2069
+ ];
2070
+ command = "powershell.exe";
2071
+ argumentsLine = windowsCommandLine([
2072
+ "-NoProfile",
2073
+ "-NonInteractive",
2074
+ "-ExecutionPolicy",
2075
+ "Bypass",
2076
+ "-File",
2077
+ wrapperFile
2078
+ ]);
2079
+ }
2080
+ return {
2081
+ kind: "task-scheduler",
2082
+ id: WINDOWS_TASK,
2083
+ file: path2.win32.join(input.stateDir, "service", "axterm-task.xml"),
2084
+ contents: `<?xml version="1.0" encoding="UTF-16"?>
2085
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
2086
+ <RegistrationInfo>
2087
+ <Description>axterm daemon</Description>
2088
+ </RegistrationInfo>
2089
+ <Triggers>
2090
+ <LogonTrigger>
2091
+ <Enabled>true</Enabled>
2092
+ <UserId>${xml(input.userId)}</UserId>
2093
+ </LogonTrigger>
2094
+ </Triggers>
2095
+ <Principals>
2096
+ <Principal id="Author">
2097
+ <UserId>${xml(input.userId)}</UserId>
2098
+ <LogonType>InteractiveToken</LogonType>
2099
+ <RunLevel>LeastPrivilege</RunLevel>
2100
+ </Principal>
2101
+ </Principals>
2102
+ <Settings>
2103
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
2104
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
2105
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
2106
+ <AllowHardTerminate>false</AllowHardTerminate>
2107
+ <StartWhenAvailable>true</StartWhenAvailable>
2108
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
2109
+ <RestartOnFailure>
2110
+ <Interval>PT1M</Interval>
2111
+ <Count>255</Count>
2112
+ </RestartOnFailure>
2113
+ </Settings>
2114
+ <Actions Context="Author">
2115
+ <Exec>
2116
+ <Command>${xml(command)}</Command>
2117
+ <Arguments>${xml(argumentsLine)}</Arguments>
2118
+ <WorkingDirectory>${xml(input.homeDir)}</WorkingDirectory>
2119
+ </Exec>
2120
+ </Actions>
2121
+ </Task>
2122
+ `,
2123
+ ...companionFiles ? { companionFiles } : {}
2124
+ };
2125
+ }
2126
+ function serviceDefinition(input) {
2127
+ if (input.platform === "linux")
2128
+ return systemdDefinition(input);
2129
+ if (input.platform === "darwin")
2130
+ return launchdDefinition(input);
2131
+ if (input.platform === "win32")
2132
+ return taskDefinition(input);
2133
+ throw new Error(`service management is not supported on ${input.platform}`);
2134
+ }
2135
+ var SYSTEMD_SERVICE = "axterm.service", LAUNCHD_LABEL = "com.layorix.axterm.daemon", WINDOWS_TASK = "axterm-daemon";
2136
+ var init_service = () => {};
2137
+
1867
2138
  // packages/core/src/attachment.ts
1868
2139
  function megabytes(bytes) {
1869
2140
  return `${Math.round(bytes / (1024 * 1024))} MB`;
@@ -2054,6 +2325,22 @@ function parseProjectRef(raw) {
2054
2325
  return { machine: text.slice(0, split), alias: text.slice(split + 1) };
2055
2326
  return { alias: text };
2056
2327
  }
2328
+ function parseCommandAddress(token) {
2329
+ const at = token.indexOf("@");
2330
+ if (at < 0)
2331
+ return { command: token };
2332
+ const target = token.slice(at + 1);
2333
+ if (!/^[A-Za-z0-9_]+$/.test(target))
2334
+ return { command: token };
2335
+ return { command: token.slice(0, at), target };
2336
+ }
2337
+ function commandIsForBot(target, self) {
2338
+ if (target === undefined)
2339
+ return true;
2340
+ if (self === undefined)
2341
+ return false;
2342
+ return target.toLowerCase() === self.toLowerCase();
2343
+ }
2057
2344
  var MAX_NAME_LENGTH = 32;
2058
2345
 
2059
2346
  // packages/core/src/fanout.ts
@@ -2163,6 +2450,9 @@ function buildApprovalQuestion(toolName) {
2163
2450
  function isModalState(state) {
2164
2451
  return state === "blocked" || state === "waiting";
2165
2452
  }
2453
+ function offersKeystrokeApproval(profile, state) {
2454
+ return state === "blocked" && profile.structuredApprovals !== true;
2455
+ }
2166
2456
  function dialogTransition(nextState, dialogOpen) {
2167
2457
  if (isModalState(nextState))
2168
2458
  return { open: true, resolved: false };
@@ -2173,15 +2463,6 @@ function isPromptInjectable(state) {
2173
2463
  }
2174
2464
  var init_choice = () => {};
2175
2465
 
2176
- // packages/core/src/menu-keys.ts
2177
- function buildMenuSelectKeys(index, normalizePresses = MENU_NORMALIZE_PRESSES) {
2178
- if (!Number.isInteger(index) || index < 0) {
2179
- throw new RangeError(`menu index must be a non-negative integer, got ${index}`);
2180
- }
2181
- return MENU_UP.repeat(normalizePresses) + MENU_DOWN.repeat(index) + MENU_SELECT;
2182
- }
2183
- var MENU_UP = "\x1B[A", MENU_DOWN = "\x1B[B", MENU_SELECT = "\r", MENU_NORMALIZE_PRESSES = 24;
2184
-
2185
2466
  // packages/core/src/adopt.ts
2186
2467
  function resolveSessionCwd(input) {
2187
2468
  if (!input.preferConversation)
@@ -2548,7 +2829,7 @@ function mapClaudeHook(input) {
2548
2829
  }
2549
2830
  }
2550
2831
  function buildClaudeSettings(input) {
2551
- const handler = {
2832
+ const handler = (timeout) => ({
2552
2833
  type: "http",
2553
2834
  url: `http://127.0.0.1:${input.port}/hook/claude`,
2554
2835
  headers: {
@@ -2556,16 +2837,36 @@ function buildClaudeSettings(input) {
2556
2837
  "x-axterm-session": input.sessionId,
2557
2838
  "x-axterm-launch": input.launchToken
2558
2839
  },
2559
- timeout: HOOK_TIMEOUT_SECONDS
2560
- };
2840
+ timeout
2841
+ });
2842
+ const observe = handler(HOOK_TIMEOUT_SECONDS);
2561
2843
  const hooks = {};
2562
- for (const event of TOOL_EVENTS)
2563
- hooks[event] = [{ matcher: "*", hooks: [handler] }];
2844
+ for (const event of TOOL_EVENTS) {
2845
+ const single = event === "PermissionRequest" ? handler(APPROVAL_TIMEOUT_SECONDS) : observe;
2846
+ hooks[event] = [{ matcher: "*", hooks: [single] }];
2847
+ }
2564
2848
  for (const event of PLAIN_EVENTS)
2565
- hooks[event] = [{ hooks: [handler] }];
2849
+ hooks[event] = [{ hooks: [observe] }];
2566
2850
  return { hooks };
2567
2851
  }
2568
- var MAX_RESULT_TEXT = 2000, HOOK_TIMEOUT_SECONDS = 5, TOOL_EVENTS, PLAIN_EVENTS;
2852
+ function hookPermissionDecision(question, answers, denyMessage) {
2853
+ const picked = answers?.find((a) => a.questionIndex === 0)?.selected[0];
2854
+ if (picked === undefined)
2855
+ return;
2856
+ if (picked === question.options[0]?.label) {
2857
+ return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "allow" } } };
2858
+ }
2859
+ if (picked === question.options[1]?.label) {
2860
+ return {
2861
+ hookSpecificOutput: {
2862
+ hookEventName: "PermissionRequest",
2863
+ decision: { behavior: "deny", ...denyMessage !== undefined ? { message: denyMessage } : {} }
2864
+ }
2865
+ };
2866
+ }
2867
+ return;
2868
+ }
2869
+ var MAX_RESULT_TEXT = 2000, HOOK_TIMEOUT_SECONDS = 5, APPROVAL_TIMEOUT_SECONDS = 120, APPROVAL_HOLD_MS = 110000, TOOL_EVENTS, PLAIN_EVENTS;
2569
2870
  var init_claude_hooks = __esm(() => {
2570
2871
  TOOL_EVENTS = ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest"];
2571
2872
  PLAIN_EVENTS = [
@@ -2591,18 +2892,25 @@ function detectOnboarding(agent, screen) {
2591
2892
  if (!dialog)
2592
2893
  continue;
2593
2894
  const options = [];
2895
+ let lastOption = -1;
2594
2896
  const end = Math.min(lines.length, i + 1 + OPTION_WINDOW_LINES + 1);
2595
2897
  for (let j = i + 1;j < end; j++) {
2596
2898
  const row = OPTION_ROW.exec(lines[j]);
2597
- if (row)
2899
+ if (row) {
2598
2900
  options.push({ digit: row[1], label: row[2].trim() });
2901
+ lastOption = j;
2902
+ }
2599
2903
  }
2600
- if (options.length >= 2)
2601
- return { kind: dialog.kind, question: dialog.question, options };
2904
+ if (options.length < 2)
2905
+ continue;
2906
+ const noise = lines.slice(lastOption + 1).filter((row) => row !== "" && !DIALOG_FOOTER.test(row));
2907
+ if (noise.length > QUIET_AFTER_OPTIONS)
2908
+ continue;
2909
+ return { kind: dialog.kind, question: dialog.question, options };
2602
2910
  }
2603
2911
  return;
2604
2912
  }
2605
- var DIALOGS, OPTION_ROW, OPTION_WINDOW_LINES = 8;
2913
+ var DIALOGS, OPTION_ROW, OPTION_WINDOW_LINES = 8, QUIET_AFTER_OPTIONS = 3, DIALOG_FOOTER;
2606
2914
  var init_onboarding = __esm(() => {
2607
2915
  DIALOGS = {
2608
2916
  gemini: [
@@ -2618,6 +2926,97 @@ var init_onboarding = __esm(() => {
2618
2926
  codex: [{ kind: "folderTrust", question: "Do you trust the contents of this directory?" }]
2619
2927
  };
2620
2928
  OPTION_ROW = /^[\u25CF\u25CB\u276F>\u203A]?\s*(\d)\.\s+(.+)$/;
2929
+ DIALOG_FOOTER = /^[\s\u2502\u2500\u2570\u256F\u256D\u256E\u250C\u2510\u2514\u2518|]*$|enter to (confirm|continue|select)|esc to cancel|use enter to select|press enter|\u2191\/\u2193|arrow keys|tab to|^\(.*\)$/i;
2930
+ });
2931
+
2932
+ // packages/core/src/diff-view.ts
2933
+ function scanStructure(lines) {
2934
+ const meta = new Set;
2935
+ let proven = false;
2936
+ let inHunk = false;
2937
+ for (let i = 0;i < lines.length; i++) {
2938
+ const line = lines[i] ?? "";
2939
+ if (line.startsWith("@@")) {
2940
+ inHunk = true;
2941
+ proven ||= HUNK_HEADER.test(line);
2942
+ continue;
2943
+ }
2944
+ if (FILE_SECTION_START.test(line)) {
2945
+ inHunk = false;
2946
+ proven ||= line.startsWith("diff --git ");
2947
+ continue;
2948
+ }
2949
+ if (inHunk || !line.startsWith("--- "))
2950
+ continue;
2951
+ if ((lines[i + 1] ?? "").startsWith("+++ ")) {
2952
+ meta.add(i);
2953
+ meta.add(i + 1);
2954
+ i += 1;
2955
+ }
2956
+ }
2957
+ return { meta, proven };
2958
+ }
2959
+ function countTail(lines) {
2960
+ let added = 0;
2961
+ let removed = 0;
2962
+ for (const line of lines) {
2963
+ if (line.startsWith("+++ ") || line.startsWith("--- "))
2964
+ continue;
2965
+ if (line.startsWith("+"))
2966
+ added += 1;
2967
+ else if (line.startsWith("-"))
2968
+ removed += 1;
2969
+ }
2970
+ return { added, removed };
2971
+ }
2972
+ function summarizePatch(patch, maxChars = DIFF_MAX_CHARS, maxLines = DIFF_MAX_LINES) {
2973
+ const totalChars = patch.length;
2974
+ if (totalChars === 0)
2975
+ return;
2976
+ const clipped = patch.slice(0, maxChars);
2977
+ const all = clipped.split(`
2978
+ `);
2979
+ const cutChars = totalChars > maxChars;
2980
+ const cutLines = all.length > maxLines;
2981
+ const kept = all.slice(0, maxLines);
2982
+ const { meta, proven } = scanStructure(kept);
2983
+ if (!proven)
2984
+ return;
2985
+ let added = 0;
2986
+ let removed = 0;
2987
+ const lines = kept.map((line, i) => {
2988
+ if (meta.has(i) || line.startsWith("@@") || FILE_SECTION_START.test(line)) {
2989
+ return { kind: "meta", text: line };
2990
+ }
2991
+ if (line.startsWith("+")) {
2992
+ added += 1;
2993
+ return { kind: "add", text: line.slice(1) };
2994
+ }
2995
+ if (line.startsWith("-")) {
2996
+ removed += 1;
2997
+ return { kind: "del", text: line.slice(1) };
2998
+ }
2999
+ return { kind: "context", text: line };
3000
+ });
3001
+ if (added + removed === 0)
3002
+ return;
3003
+ const tail = countTail(all.slice(maxLines));
3004
+ added += tail.added;
3005
+ removed += tail.removed;
3006
+ const truncated = cutChars ? "chars" : cutLines ? "lines" : undefined;
3007
+ return {
3008
+ lines: truncated === undefined ? lines : [...lines.slice(0, maxLines - 1), TRUNCATED],
3009
+ added,
3010
+ removed,
3011
+ ...truncated === undefined ? {} : { truncated },
3012
+ totalChars
3013
+ };
3014
+ }
3015
+ var DIFF_MAX_CHARS = 8000, DIFF_MAX_LINES = 80, TRUNCATED, HUNK_HEADER, FILE_SECTION_START;
3016
+ var init_diff_view = __esm(() => {
3017
+ TRUNCATED = { kind: "meta", text: "\u2026 diff truncated \u2026" };
3018
+ HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/;
3019
+ FILE_SECTION_START = /^(?:diff |index |old mode |new mode |new file mode |deleted file mode |similarity index |dissimilarity index |rename |copy |Binary files )/;
2621
3020
  });
2622
3021
 
2623
3022
  // packages/core/src/doctor.ts
@@ -2863,7 +3262,7 @@ var SEQUENCES, KEY_NAMES;
2863
3262
  var init_keys = __esm(() => {
2864
3263
  SEQUENCES = {
2865
3264
  approve: "\r",
2866
- deny: "\x1B[B\r",
3265
+ deny: "\x1B",
2867
3266
  enter: "\r",
2868
3267
  esc: "\x1B",
2869
3268
  up: "\x1B[A",
@@ -3339,24 +3738,7 @@ trusted_hash = "${e.hash}"
3339
3738
  }).join(`
3340
3739
  `);
3341
3740
  }
3342
- function codexPermissionDecision(question, answers, denyMessage) {
3343
- const picked = answers?.find((a) => a.questionIndex === 0)?.selected[0];
3344
- if (picked === undefined)
3345
- return;
3346
- if (picked === question.options[0]?.label) {
3347
- return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "allow" } } };
3348
- }
3349
- if (picked === question.options[1]?.label) {
3350
- return {
3351
- hookSpecificOutput: {
3352
- hookEventName: "PermissionRequest",
3353
- decision: { behavior: "deny", ...denyMessage !== undefined ? { message: denyMessage } : {} }
3354
- }
3355
- };
3356
- }
3357
- return;
3358
- }
3359
- var OBSERVATION_TIMEOUT_SECONDS = 15, CODEX_PERMISSION_TIMEOUT_SECONDS = 120, CODEX_APPROVAL_HOLD_MS = 110000, CODEX_APPROVAL_POST_TIMEOUT_MS = 115000, SESSION_END_TIMEOUT_SECONDS = 2, OBSERVATION_EVENTS, EVENT_KEY_LABELS;
3741
+ var OBSERVATION_TIMEOUT_SECONDS = 15, CODEX_PERMISSION_TIMEOUT_SECONDS = 120, CODEX_APPROVAL_POST_TIMEOUT_MS = 115000, SESSION_END_TIMEOUT_SECONDS = 2, OBSERVATION_EVENTS, EVENT_KEY_LABELS;
3360
3742
  var init_codex_hooks = __esm(() => {
3361
3743
  OBSERVATION_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
3362
3744
  EVENT_KEY_LABELS = {
@@ -3610,11 +3992,11 @@ var init_prompt_detect = __esm(() => {
3610
3992
  });
3611
3993
 
3612
3994
  // packages/core/src/session-scope.ts
3613
- import path2 from "path";
3995
+ import path3 from "path";
3614
3996
  function comparable(target, resolve) {
3615
- let real = path2.resolve(target);
3997
+ let real = path3.resolve(target);
3616
3998
  try {
3617
- real = path2.resolve(resolve(real));
3999
+ real = path3.resolve(resolve(real));
3618
4000
  } catch {}
3619
4001
  const trimmed = real.replace(/[\\/]+$/, "");
3620
4002
  return process.platform === "win32" ? trimmed.toLowerCase() : trimmed;
@@ -3622,7 +4004,7 @@ function comparable(target, resolve) {
3622
4004
  function pathWithin(parent, child, resolve = asGiven) {
3623
4005
  const from = comparable(parent, resolve);
3624
4006
  const to = comparable(child, resolve);
3625
- return to === from || to.startsWith(from + path2.sep);
4007
+ return to === from || to.startsWith(from + path3.sep);
3626
4008
  }
3627
4009
  function sessionInScope(session, scope, resolve = asGiven) {
3628
4010
  if (pathWithin(scope, session.cwd, resolve))
@@ -3677,6 +4059,7 @@ var init_src = __esm(() => {
3677
4059
  init_agent_registry();
3678
4060
  init_agent_resume();
3679
4061
  init_self_spawn();
4062
+ init_service();
3680
4063
  init_channel_spi();
3681
4064
  init_update();
3682
4065
  init_errors();
@@ -3688,6 +4071,7 @@ var init_src = __esm(() => {
3688
4071
  init_pty_protocol();
3689
4072
  init_claude_hooks();
3690
4073
  init_onboarding();
4074
+ init_diff_view();
3691
4075
  init_doctor();
3692
4076
  init_stall();
3693
4077
  init_codex_stream();
@@ -3709,14 +4093,14 @@ var init_src = __esm(() => {
3709
4093
  });
3710
4094
 
3711
4095
  // packages/daemon/src/config-store.ts
3712
- import fs6 from "fs";
3713
- import path6 from "path";
3714
- function writeAtomic(target, contents, mode) {
4096
+ import fs7 from "fs";
4097
+ import path8 from "path";
4098
+ function writeAtomic2(target, contents, mode) {
3715
4099
  const tmp = `${target}.tmp`;
3716
- fs6.writeFileSync(tmp, contents, { mode });
3717
- fs6.renameSync(tmp, target);
4100
+ fs7.writeFileSync(tmp, contents, { mode });
4101
+ fs7.renameSync(tmp, target);
3718
4102
  if (process.platform !== "win32")
3719
- fs6.chmodSync(target, mode);
4103
+ fs7.chmodSync(target, mode);
3720
4104
  }
3721
4105
 
3722
4106
  class ConfigStore {
@@ -3732,16 +4116,16 @@ class ConfigStore {
3732
4116
  }
3733
4117
  load() {
3734
4118
  this.loadError = undefined;
3735
- fs6.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
4119
+ fs7.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
3736
4120
  if (process.platform !== "win32") {
3737
4121
  try {
3738
- fs6.chmodSync(this.paths.configDir, DIR_MODE);
4122
+ fs7.chmodSync(this.paths.configDir, DIR_MODE);
3739
4123
  } catch {}
3740
4124
  }
3741
4125
  this.config = structuredClone(DEFAULT_CONFIG);
3742
- if (fs6.existsSync(this.paths.configFile)) {
4126
+ if (fs7.existsSync(this.paths.configFile)) {
3743
4127
  try {
3744
- const parsed = parseConfig(JSON.parse(fs6.readFileSync(this.paths.configFile, "utf8")));
4128
+ const parsed = parseConfig(JSON.parse(fs7.readFileSync(this.paths.configFile, "utf8")));
3745
4129
  if ("errors" in parsed)
3746
4130
  this.loadError = parsed.errors.join("; ");
3747
4131
  else
@@ -3751,10 +4135,10 @@ class ConfigStore {
3751
4135
  }
3752
4136
  }
3753
4137
  this.secrets = {};
3754
- if (fs6.existsSync(this.paths.secretsFile)) {
4138
+ if (fs7.existsSync(this.paths.secretsFile)) {
3755
4139
  this.tightenPermissions(this.paths.secretsFile);
3756
4140
  try {
3757
- const raw = JSON.parse(fs6.readFileSync(this.paths.secretsFile, "utf8"));
4141
+ const raw = JSON.parse(fs7.readFileSync(this.paths.secretsFile, "utf8"));
3758
4142
  if (raw !== null && typeof raw === "object") {
3759
4143
  for (const [key, value] of Object.entries(raw)) {
3760
4144
  if (typeof value === "string")
@@ -3770,8 +4154,8 @@ class ConfigStore {
3770
4154
  if (process.platform === "win32")
3771
4155
  return;
3772
4156
  try {
3773
- if ((fs6.statSync(file).mode & 63) !== 0)
3774
- fs6.chmodSync(file, FILE_MODE);
4157
+ if ((fs7.statSync(file).mode & 63) !== 0)
4158
+ fs7.chmodSync(file, FILE_MODE);
3775
4159
  } catch {}
3776
4160
  }
3777
4161
  get() {
@@ -3779,7 +4163,7 @@ class ConfigStore {
3779
4163
  }
3780
4164
  health() {
3781
4165
  return configHealth({
3782
- fileExists: fs6.existsSync(this.paths.configFile),
4166
+ fileExists: fs7.existsSync(this.paths.configFile),
3783
4167
  parseError: this.loadError,
3784
4168
  config: this.resolved()
3785
4169
  });
@@ -3812,7 +4196,7 @@ class ConfigStore {
3812
4196
  const parsed = parseConfig(candidate);
3813
4197
  if ("errors" in parsed)
3814
4198
  throw new Error(parsed.errors.join("; "));
3815
- writeAtomic(this.paths.configFile, `${JSON.stringify(parsed.config, null, 2)}
4199
+ writeAtomic2(this.paths.configFile, `${JSON.stringify(parsed.config, null, 2)}
3816
4200
  `, FILE_MODE);
3817
4201
  this.config = parsed.config;
3818
4202
  this.loadError = undefined;
@@ -3824,11 +4208,11 @@ class ConfigStore {
3824
4208
  delete this.secrets[dottedPath];
3825
4209
  else
3826
4210
  this.secrets[dottedPath] = value;
3827
- if (!fs6.existsSync(this.paths.configFile)) {
3828
- writeAtomic(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
4211
+ if (!fs7.existsSync(this.paths.configFile)) {
4212
+ writeAtomic2(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
3829
4213
  `, FILE_MODE);
3830
4214
  }
3831
- writeAtomic(this.paths.secretsFile, `${JSON.stringify(this.secrets, null, 2)}
4215
+ writeAtomic2(this.paths.secretsFile, `${JSON.stringify(this.secrets, null, 2)}
3832
4216
  `, FILE_MODE);
3833
4217
  }
3834
4218
  hasSecret(dottedPath) {
@@ -3854,7 +4238,7 @@ class ConfigStore {
3854
4238
  fingerprint() {
3855
4239
  const stamp = (file) => {
3856
4240
  try {
3857
- return String(fs6.statSync(file).mtimeMs);
4241
+ return String(fs7.statSync(file).mtimeMs);
3858
4242
  } catch {
3859
4243
  return "-";
3860
4244
  }
@@ -3876,8 +4260,8 @@ function applyPath(config, dottedPath, value) {
3876
4260
  function defaultConfigStorePaths(configDir) {
3877
4261
  return {
3878
4262
  configDir,
3879
- configFile: path6.join(configDir, "config.json"),
3880
- secretsFile: path6.join(configDir, "secrets.json")
4263
+ configFile: path8.join(configDir, "config.json"),
4264
+ secretsFile: path8.join(configDir, "secrets.json")
3881
4265
  };
3882
4266
  }
3883
4267
  var ENV_ALIASES, DIR_MODE = 448, FILE_MODE = 384;
@@ -3896,13 +4280,13 @@ var init_config_store = __esm(() => {
3896
4280
  });
3897
4281
 
3898
4282
  // packages/daemon/src/shell.ts
3899
- import fs7 from "fs";
3900
- import path7 from "path";
4283
+ import fs8 from "fs";
4284
+ import path9 from "path";
3901
4285
  function findExecutable(name, env) {
3902
4286
  const isWindows = env.platform === "win32";
3903
- const join = isWindows ? path7.win32.join : path7.posix.join;
4287
+ const join = isWindows ? path9.win32.join : path9.posix.join;
3904
4288
  const separator = isWindows ? ";" : ":";
3905
- const extensions = isWindows ? path7.win32.extname(name).length > 0 ? [""] : env.pathExt.split(";").filter((e) => e.length > 0) : [""];
4289
+ const extensions = isWindows ? path9.win32.extname(name).length > 0 ? [""] : env.pathExt.split(";").filter((e) => e.length > 0) : [""];
3906
4290
  for (const rawDir of env.pathEnv.split(separator)) {
3907
4291
  const dir = rawDir.trim().replace(/^"|"$/g, "");
3908
4292
  if (dir.length === 0)
@@ -3920,16 +4304,16 @@ function lookupOnPath(name) {
3920
4304
  platform: process.platform,
3921
4305
  pathEnv: process.env["PATH"] ?? "",
3922
4306
  pathExt: process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD",
3923
- exists: (candidate) => fs7.existsSync(candidate)
4307
+ exists: (candidate) => fs8.existsSync(candidate)
3924
4308
  });
3925
4309
  }
3926
4310
  function resolveShell(kind) {
3927
4311
  if (process.platform !== "win32")
3928
4312
  return resolvePosixShell(kind);
3929
4313
  const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
3930
- const windowsPowershell = path7.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
4314
+ const windowsPowershell = path9.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
3931
4315
  if (kind === "cmd") {
3932
- return { kind: "cmd", exe: path7.join(systemRoot, "System32", "cmd.exe") };
4316
+ return { kind: "cmd", exe: path9.join(systemRoot, "System32", "cmd.exe") };
3933
4317
  }
3934
4318
  if (kind === "powershell") {
3935
4319
  return { kind: "powershell", exe: windowsPowershell };
@@ -3957,7 +4341,7 @@ function resolvePosixShell(kind) {
3957
4341
  candidates.push({ kind: "bash", exe: login });
3958
4342
  candidates.push({ kind: "bash", exe: "/bin/bash" }, { kind: "bash", exe: "/usr/bin/bash" });
3959
4343
  }
3960
- const found = candidates.find((c) => fs7.existsSync(c.exe));
4344
+ const found = candidates.find((c) => fs8.existsSync(c.exe));
3961
4345
  if (found)
3962
4346
  return found;
3963
4347
  return { kind: "sh", exe: "/bin/sh" };
@@ -4002,24 +4386,24 @@ function ipcEndpoint(platform, user, env) {
4002
4386
  const suffix = `-${shortHash(env.runtimeDir)}`;
4003
4387
  return `\\\\.\\pipe\\axterm-${safeUser}${suffix}`;
4004
4388
  }
4005
- return path7.posix.join(env.runtimeDir, "axterm.sock");
4389
+ return path9.posix.join(env.runtimeDir, "axterm.sock");
4006
4390
  }
4007
4391
  var cachedPwsh;
4008
4392
  var init_shell = () => {};
4009
4393
 
4010
4394
  // packages/daemon/src/paths.ts
4011
- import fs8 from "fs";
4012
- import os3 from "os";
4013
- import path8 from "path";
4395
+ import fs9 from "fs";
4396
+ import os4 from "os";
4397
+ import path10 from "path";
4014
4398
  function ensureDataDirs() {
4015
4399
  for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir, SCROLLBACK_DIR, LOG_DIR, EXT_DIR]) {
4016
- fs8.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
4400
+ fs9.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
4017
4401
  }
4018
4402
  if (process.platform === "win32")
4019
4403
  return;
4020
4404
  for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir]) {
4021
4405
  try {
4022
- fs8.chmodSync(dir, DIR_MODE2);
4406
+ fs9.chmodSync(dir, DIR_MODE2);
4023
4407
  } catch {}
4024
4408
  }
4025
4409
  }
@@ -4027,14 +4411,14 @@ function restrictFile(file) {
4027
4411
  if (process.platform === "win32")
4028
4412
  return;
4029
4413
  try {
4030
- fs8.chmodSync(file, 384);
4414
+ fs9.chmodSync(file, 384);
4031
4415
  } catch {}
4032
4416
  }
4033
4417
  function log(message) {
4034
4418
  const line = `${new Date().toISOString()} ${message}
4035
4419
  `;
4036
4420
  try {
4037
- fs8.appendFileSync(LOG_FILE, line);
4421
+ fs9.appendFileSync(LOG_FILE, line);
4038
4422
  } catch {}
4039
4423
  }
4040
4424
  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;
@@ -4051,15 +4435,15 @@ var init_paths = __esm(() => {
4051
4435
  DB_PATH = PATHS.dbFile;
4052
4436
  RUNTIME_FILE2 = PATHS.runtimeFile;
4053
4437
  HOOK_ENDPOINT_FILE = PATHS.hookEndpointFile;
4054
- PIPE_PATH = ipcEndpoint(process.platform, os3.userInfo().username, {
4438
+ PIPE_PATH = ipcEndpoint(process.platform, os4.userInfo().username, {
4055
4439
  runtimeDir: PATHS.runtimeDir
4056
4440
  });
4057
- LOG_FILE = path8.join(LOG_DIR, "daemon.log");
4441
+ LOG_FILE = path10.join(LOG_DIR, "daemon.log");
4058
4442
  });
4059
4443
 
4060
4444
  // packages/daemon/src/store.ts
4061
- import fs9 from "fs";
4062
- import path9 from "path";
4445
+ import fs10 from "fs";
4446
+ import path11 from "path";
4063
4447
  import { Database } from "bun:sqlite";
4064
4448
  function rowToInfo(row) {
4065
4449
  return {
@@ -4090,7 +4474,7 @@ function rowToInfo(row) {
4090
4474
  class Store {
4091
4475
  db;
4092
4476
  constructor(dbPath = DB_PATH) {
4093
- fs9.mkdirSync(path9.dirname(dbPath), { recursive: true });
4477
+ fs10.mkdirSync(path11.dirname(dbPath), { recursive: true });
4094
4478
  this.db = new Database(dbPath, { strict: true });
4095
4479
  this.db.run("PRAGMA journal_mode = WAL");
4096
4480
  this.db.run(`
@@ -4258,11 +4642,11 @@ var init_store = __esm(() => {
4258
4642
  });
4259
4643
 
4260
4644
  // packages/daemon/src/config-migrate.ts
4261
- import fs10 from "fs";
4262
- import path10 from "path";
4645
+ import fs11 from "fs";
4646
+ import path12 from "path";
4263
4647
  import { Database as Database2 } from "bun:sqlite";
4264
4648
  function checkpointWal(dbFile) {
4265
- if (!fs10.existsSync(dbFile))
4649
+ if (!fs11.existsSync(dbFile))
4266
4650
  return;
4267
4651
  try {
4268
4652
  const db = new Database2(dbFile);
@@ -4272,32 +4656,32 @@ function checkpointWal(dbFile) {
4272
4656
  }
4273
4657
  function relocate(source, target) {
4274
4658
  try {
4275
- fs10.renameSync(source, target);
4659
+ fs11.renameSync(source, target);
4276
4660
  } catch {
4277
- fs10.cpSync(source, target, { recursive: true });
4661
+ fs11.cpSync(source, target, { recursive: true });
4278
4662
  }
4279
4663
  }
4280
4664
  function moveInto(from, to, notes) {
4281
- checkpointWal(path10.join(from, "axterm.db"));
4282
- fs10.mkdirSync(to, { recursive: true });
4665
+ checkpointWal(path12.join(from, "axterm.db"));
4666
+ fs11.mkdirSync(to, { recursive: true });
4283
4667
  for (const entry of STATE_ENTRIES) {
4284
- const source = path10.join(from, entry);
4285
- const target = path10.join(to, entry);
4286
- if (!fs10.existsSync(source))
4668
+ const source = path12.join(from, entry);
4669
+ const target = path12.join(to, entry);
4670
+ if (!fs11.existsSync(source))
4287
4671
  continue;
4288
- if (!fs10.existsSync(target)) {
4672
+ if (!fs11.existsSync(target)) {
4289
4673
  relocate(source, target);
4290
4674
  notes.push(`moved ${entry}`);
4291
4675
  continue;
4292
4676
  }
4293
- if (!fs10.statSync(source).isDirectory() || !fs10.statSync(target).isDirectory())
4677
+ if (!fs11.statSync(source).isDirectory() || !fs11.statSync(target).isDirectory())
4294
4678
  continue;
4295
4679
  let moved = 0;
4296
- for (const child of fs10.readdirSync(source)) {
4297
- const childTarget = path10.join(target, child);
4298
- if (fs10.existsSync(childTarget))
4680
+ for (const child of fs11.readdirSync(source)) {
4681
+ const childTarget = path12.join(target, child);
4682
+ if (fs11.existsSync(childTarget))
4299
4683
  continue;
4300
- relocate(path10.join(source, child), childTarget);
4684
+ relocate(path12.join(source, child), childTarget);
4301
4685
  moved += 1;
4302
4686
  }
4303
4687
  if (moved > 0)
@@ -4307,29 +4691,29 @@ function moveInto(from, to, notes) {
4307
4691
  function migrateLegacyConfig(input) {
4308
4692
  const notes = [];
4309
4693
  const storePaths = defaultConfigStorePaths(input.configDir);
4310
- if (fs10.existsSync(storePaths.configFile))
4694
+ if (fs11.existsSync(storePaths.configFile))
4311
4695
  return { migrated: false, notes };
4312
- const configSource = input.legacyRoots.find((dir) => fs10.existsSync(path10.join(dir, "config.json")));
4696
+ const configSource = input.legacyRoots.find((dir) => fs11.existsSync(path12.join(dir, "config.json")));
4313
4697
  if (configSource !== undefined) {
4314
- fs10.mkdirSync(input.configDir, { recursive: true });
4315
- fs10.copyFileSync(path10.join(configSource, "config.json"), storePaths.configFile);
4316
- const secrets = path10.join(configSource, "secrets.json");
4317
- if (fs10.existsSync(secrets)) {
4318
- fs10.copyFileSync(secrets, storePaths.secretsFile);
4698
+ fs11.mkdirSync(input.configDir, { recursive: true });
4699
+ fs11.copyFileSync(path12.join(configSource, "config.json"), storePaths.configFile);
4700
+ const secrets = path12.join(configSource, "secrets.json");
4701
+ if (fs11.existsSync(secrets)) {
4702
+ fs11.copyFileSync(secrets, storePaths.secretsFile);
4319
4703
  try {
4320
- fs10.chmodSync(storePaths.secretsFile, 384);
4704
+ fs11.chmodSync(storePaths.secretsFile, 384);
4321
4705
  } catch {}
4322
4706
  }
4323
- fs10.writeFileSync(path10.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
4707
+ fs11.writeFileSync(path12.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
4324
4708
  ${input.configDir}
4325
4709
  ` + `These files are no longer used. secrets.json still holds your bot token \u2014 delete this directory once you are satisfied.
4326
4710
  `);
4327
4711
  notes.push(`adopted configuration from ${configSource}`);
4328
4712
  }
4329
- const legacy = input.legacyRoots.find((dir) => fs10.existsSync(path10.join(dir, "axterm.db")));
4713
+ const legacy = input.legacyRoots.find((dir) => fs11.existsSync(path12.join(dir, "axterm.db")));
4330
4714
  if (legacy !== undefined) {
4331
4715
  moveInto(legacy, input.stateDir, notes);
4332
- fs10.writeFileSync(path10.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
4716
+ fs11.writeFileSync(path12.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
4333
4717
  ${input.stateDir}
4334
4718
  and configuration in:
4335
4719
  ${input.configDir}
@@ -4338,8 +4722,8 @@ and configuration in:
4338
4722
  }
4339
4723
  if (configSource !== undefined)
4340
4724
  return { migrated: true, ...legacy ? { from: legacy } : {}, notes };
4341
- const dbFile = path10.join(input.stateDir, "axterm.db");
4342
- if (!fs10.existsSync(dbFile))
4725
+ const dbFile = path12.join(input.stateDir, "axterm.db");
4726
+ if (!fs11.existsSync(dbFile))
4343
4727
  return { migrated: notes.length > 0, ...legacy ? { from: legacy } : {}, notes };
4344
4728
  const store = new Store(dbFile);
4345
4729
  const machine2 = store.getConfig("axterm_machine");
@@ -5177,13 +5561,13 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
5177
5561
  var init_axterm_agent_status = () => {};
5178
5562
 
5179
5563
  // packages/daemon/src/ext-install.ts
5180
- import fs11 from "fs";
5181
- import path11 from "path";
5564
+ import fs12 from "fs";
5565
+ import path13 from "path";
5182
5566
  function installExtensionAsset(targetDir) {
5183
- fs11.mkdirSync(targetDir, { recursive: true });
5184
- const target = path11.join(targetDir, "axterm-agent-status.mjs");
5567
+ fs12.mkdirSync(targetDir, { recursive: true });
5568
+ const target = path13.join(targetDir, "axterm-agent-status.mjs");
5185
5569
  const js = new Bun.Transpiler({ loader: "ts" }).transformSync(extensionSource);
5186
- fs11.writeFileSync(target, MANAGED_HEADER + js);
5570
+ fs12.writeFileSync(target, MANAGED_HEADER + js);
5187
5571
  return target;
5188
5572
  }
5189
5573
  var extensionSource, MANAGED_HEADER = `// @axterm-managed-omp-extension
@@ -5196,8 +5580,8 @@ var init_ext_install = __esm(() => {
5196
5580
 
5197
5581
  // packages/daemon/src/workspace-local.ts
5198
5582
  import { spawn as spawn2 } from "child_process";
5199
- import fs12 from "fs/promises";
5200
- import path12 from "path";
5583
+ import fs13 from "fs/promises";
5584
+ import path14 from "path";
5201
5585
  function isAbsent(error) {
5202
5586
  const code = error?.code;
5203
5587
  return code === "ENOENT" || code === "ENOTDIR";
@@ -5210,10 +5594,10 @@ class LocalWorkspace {
5210
5594
  this.stagingDir = stagingDir;
5211
5595
  }
5212
5596
  async list(dir) {
5213
- const dirents = await fs12.readdir(dir, { withFileTypes: true });
5597
+ const dirents = await fs13.readdir(dir, { withFileTypes: true });
5214
5598
  const stats = await Promise.all(dirents.map(async (dirent) => {
5215
5599
  try {
5216
- return await fs12.lstat(path12.join(dir, dirent.name));
5600
+ return await fs13.lstat(path14.join(dir, dirent.name));
5217
5601
  } catch (error) {
5218
5602
  if (isAbsent(error))
5219
5603
  return;
@@ -5237,7 +5621,7 @@ class LocalWorkspace {
5237
5621
  }
5238
5622
  async stat(target) {
5239
5623
  try {
5240
- const stat = await fs12.stat(target);
5624
+ const stat = await fs13.stat(target);
5241
5625
  const kind = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "other";
5242
5626
  return { kind, size: stat.size, mtimeMs: stat.mtimeMs };
5243
5627
  } catch (error) {
@@ -5248,8 +5632,8 @@ class LocalWorkspace {
5248
5632
  }
5249
5633
  async read(file, limit) {
5250
5634
  if (limit === undefined)
5251
- return await fs12.readFile(file);
5252
- const handle = await fs12.open(file, "r");
5635
+ return await fs13.readFile(file);
5636
+ const handle = await fs13.open(file, "r");
5253
5637
  try {
5254
5638
  const buffer = Buffer.allocUnsafe(Math.min(limit, (await handle.stat()).size));
5255
5639
  let filled = 0;
@@ -5268,10 +5652,10 @@ class LocalWorkspace {
5268
5652
  return Promise.resolve(file);
5269
5653
  }
5270
5654
  async mkdirp(dir, mode) {
5271
- await fs12.mkdir(dir, mode === undefined ? { recursive: true } : { recursive: true, mode });
5655
+ await fs13.mkdir(dir, mode === undefined ? { recursive: true } : { recursive: true, mode });
5272
5656
  }
5273
5657
  async write(file, bytes, mode) {
5274
- await fs12.writeFile(file, bytes, mode === undefined ? undefined : { mode });
5658
+ await fs13.writeFile(file, bytes, mode === undefined ? undefined : { mode });
5275
5659
  }
5276
5660
  exec(argv, cwd) {
5277
5661
  const [command, ...args] = argv;
@@ -5300,9 +5684,9 @@ var init_workspace_local = () => {};
5300
5684
 
5301
5685
  // packages/daemon/src/hook-host.ts
5302
5686
  import { randomBytes } from "crypto";
5303
- import fs13 from "fs";
5687
+ import fs14 from "fs";
5304
5688
  import http from "http";
5305
- import path13 from "path";
5689
+ import path15 from "path";
5306
5690
  function header(req, name) {
5307
5691
  const value = req.headers[name];
5308
5692
  return Array.isArray(value) ? value[0] : value;
@@ -5322,12 +5706,12 @@ class HookHost {
5322
5706
  askOpenHandler;
5323
5707
  resolveWorkspace;
5324
5708
  local;
5325
- codexApprovalHoldMs;
5326
- codexAskSeq = 0;
5709
+ approvalHoldMs;
5710
+ approvalSeq = 0;
5327
5711
  constructor(options) {
5328
5712
  this.endpointFile = options.endpointFile;
5329
- this.codexApprovalHoldMs = options.codexApprovalHoldMs ?? CODEX_APPROVAL_HOLD_MS;
5330
- this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(path13.join(RUNTIME_DIR, "materialised")));
5713
+ this.approvalHoldMs = options.approvalHoldMs ?? APPROVAL_HOLD_MS;
5714
+ this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(path15.join(RUNTIME_DIR, "materialised")));
5331
5715
  }
5332
5716
  start() {
5333
5717
  const server = http.createServer((req, res) => this.handle(req, res));
@@ -5343,12 +5727,12 @@ class HookHost {
5343
5727
  return promise;
5344
5728
  }
5345
5729
  writeEndpointFile(port) {
5346
- fs13.mkdirSync(path13.dirname(this.endpointFile), { recursive: true });
5730
+ fs14.mkdirSync(path15.dirname(this.endpointFile), { recursive: true });
5347
5731
  const tmp = `${this.endpointFile}.tmp`;
5348
- fs13.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
5732
+ fs14.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
5349
5733
  AXTERM_HOOK_TOKEN=${this.token}
5350
5734
  `);
5351
- fs13.renameSync(tmp, this.endpointFile);
5735
+ fs14.renameSync(tmp, this.endpointFile);
5352
5736
  }
5353
5737
  registerSession(sessionId, launchToken, onEvent) {
5354
5738
  this.sessions.set(sessionId, { launchToken, onEvent });
@@ -5417,11 +5801,11 @@ AXTERM_HOOK_TOKEN=${this.token}
5417
5801
  req.on("end", () => {
5418
5802
  const raw = Buffer.concat(chunks).toString("utf8");
5419
5803
  if (isClaude) {
5420
- this.handleAgentHook(req, raw, res, mapClaudeHook, "claude");
5804
+ this.handleDecidingAgentHook(req, raw, res, "claude");
5421
5805
  return;
5422
5806
  }
5423
5807
  if (isCodex) {
5424
- this.handleCodex(req, raw, res);
5808
+ this.handleDecidingAgentHook(req, raw, res, "codex");
5425
5809
  return;
5426
5810
  }
5427
5811
  if (isGemini) {
@@ -5483,7 +5867,7 @@ AXTERM_HOOK_TOKEN=${this.token}
5483
5867
  const events = withConversationId(map(input), { sessionId: named.session_id, subagentId: named.agent_id });
5484
5868
  this.dispatchHookPayloads(auth.session, events, agent);
5485
5869
  }
5486
- handleCodex(req, raw, res) {
5870
+ handleDecidingAgentHook(req, raw, res, agent) {
5487
5871
  const auth = this.agentHookSession(req);
5488
5872
  if (!auth) {
5489
5873
  res.writeHead(200).end();
@@ -5496,20 +5880,27 @@ AXTERM_HOOK_TOKEN=${this.token}
5496
5880
  res.writeHead(200).end();
5497
5881
  return;
5498
5882
  }
5883
+ const dispatch = () => {
5884
+ const events = withConversationId(mapClaudeHook(input), {
5885
+ sessionId: input.session_id,
5886
+ subagentId: input.agent_id
5887
+ });
5888
+ this.dispatchHookPayloads(auth.session, events, agent);
5889
+ };
5499
5890
  if (input.hook_event_name !== "PermissionRequest") {
5500
5891
  res.writeHead(200).end();
5501
- this.dispatchHookPayloads(auth.session, mapClaudeHook(input), "codex");
5892
+ dispatch();
5502
5893
  return;
5503
5894
  }
5504
5895
  const question = buildApprovalQuestion(input.tool_name);
5505
- this.dispatchHookPayloads(auth.session, mapClaudeHook(input), "codex");
5506
- const askId = `codex-${auth.sessionId}-${this.codexAskSeq++}`;
5896
+ dispatch();
5897
+ const askId = `${agent}-${auth.sessionId}-${this.approvalSeq++}`;
5507
5898
  let fence;
5508
5899
  const settle = (body) => {
5509
5900
  clearTimeout(fence);
5510
5901
  if (res.writableEnded)
5511
5902
  return;
5512
- const decision = body.status === "answered" ? codexPermissionDecision(question, body.answers, t("codex.approval.denied")) : undefined;
5903
+ const decision = body.status === "answered" ? hookPermissionDecision(question, body.answers, t("agent.approval.denied")) : undefined;
5513
5904
  if (decision === undefined) {
5514
5905
  res.writeHead(200).end();
5515
5906
  return;
@@ -5530,7 +5921,7 @@ AXTERM_HOOK_TOKEN=${this.token}
5530
5921
  return;
5531
5922
  this.pendingAsks.delete(askId);
5532
5923
  settle({ status: "cancelled" });
5533
- }, this.codexApprovalHoldMs);
5924
+ }, this.approvalHoldMs);
5534
5925
  fence.unref?.();
5535
5926
  }
5536
5927
  agentHookSession(req) {
@@ -5634,8 +6025,112 @@ var init_hook_host = __esm(() => {
5634
6025
  MAX_BODY_BYTES = 256 * 1024;
5635
6026
  });
5636
6027
 
6028
+ // packages/daemon/src/process-guards.ts
6029
+ class RejectionLogLimiter {
6030
+ detailLimit;
6031
+ windowMs;
6032
+ windowStartedAt;
6033
+ details = 0;
6034
+ noticeEmitted = false;
6035
+ constructor(detailLimit = DEFAULT_REJECTION_DETAILS, windowMs = DEFAULT_REJECTION_WINDOW_MS) {
6036
+ this.detailLimit = detailLimit;
6037
+ this.windowMs = windowMs;
6038
+ if (!Number.isInteger(detailLimit) || detailLimit < 1)
6039
+ throw new RangeError("detailLimit must be a positive integer");
6040
+ if (!Number.isFinite(windowMs) || windowMs <= 0)
6041
+ throw new RangeError("windowMs must be positive");
6042
+ }
6043
+ decide(now) {
6044
+ if (this.windowStartedAt === undefined || now - this.windowStartedAt >= this.windowMs || now < this.windowStartedAt) {
6045
+ this.windowStartedAt = now;
6046
+ this.details = 0;
6047
+ this.noticeEmitted = false;
6048
+ }
6049
+ if (this.details < this.detailLimit) {
6050
+ this.details++;
6051
+ return "detail";
6052
+ }
6053
+ if (!this.noticeEmitted) {
6054
+ this.noticeEmitted = true;
6055
+ return "notice";
6056
+ }
6057
+ return "drop";
6058
+ }
6059
+ }
6060
+ function thrownText(reason) {
6061
+ let text;
6062
+ if (reason instanceof Error) {
6063
+ text = reason.stack ?? reason.message;
6064
+ } else if (typeof reason === "string") {
6065
+ text = reason;
6066
+ } else {
6067
+ try {
6068
+ text = JSON.stringify(reason) ?? String(reason);
6069
+ } catch {
6070
+ text = String(reason);
6071
+ }
6072
+ }
6073
+ if (text.length <= MAX_BREADCRUMB_CHARS)
6074
+ return text;
6075
+ return `${text.slice(0, MAX_BREADCRUMB_CHARS)}\u2026 [truncated]`;
6076
+ }
6077
+
6078
+ class ProcessCrashGuards {
6079
+ options;
6080
+ limiter;
6081
+ now;
6082
+ fatal = false;
6083
+ uninstallCurrent;
6084
+ constructor(options) {
6085
+ this.options = options;
6086
+ this.now = options.now ?? Date.now;
6087
+ this.limiter = new RejectionLogLimiter(options.rejectionDetailsPerWindow, options.rejectionWindowMs);
6088
+ }
6089
+ install(events) {
6090
+ if (this.uninstallCurrent !== undefined)
6091
+ throw new Error("process crash guards are already installed");
6092
+ const uncaught = (reason) => this.fatalExit("uncaughtException", reason);
6093
+ const rejected = (reason) => this.unhandledRejection(reason);
6094
+ events.on("uncaughtException", uncaught);
6095
+ events.on("unhandledRejection", rejected);
6096
+ let installed = true;
6097
+ const uninstall = () => {
6098
+ if (!installed)
6099
+ return;
6100
+ installed = false;
6101
+ events.off("uncaughtException", uncaught);
6102
+ events.off("unhandledRejection", rejected);
6103
+ if (this.uninstallCurrent === uninstall)
6104
+ this.uninstallCurrent = undefined;
6105
+ };
6106
+ this.uninstallCurrent = uninstall;
6107
+ return uninstall;
6108
+ }
6109
+ startupFailed(reason) {
6110
+ this.fatalExit("startupFailure", reason);
6111
+ }
6112
+ unhandledRejection(reason) {
6113
+ if (this.fatal)
6114
+ return;
6115
+ const decision = this.limiter.decide(this.now());
6116
+ if (decision === "detail") {
6117
+ this.options.log(`unhandledRejection: ${thrownText(reason)}`);
6118
+ } else if (decision === "notice") {
6119
+ this.options.log("unhandledRejection: suppressing further unhandled rejections in this rate-limit window");
6120
+ }
6121
+ }
6122
+ fatalExit(origin, reason) {
6123
+ if (this.fatal)
6124
+ return;
6125
+ this.fatal = true;
6126
+ this.options.log(`${origin}: ${thrownText(reason)}`);
6127
+ this.options.exit(1);
6128
+ }
6129
+ }
6130
+ var DEFAULT_REJECTION_DETAILS = 20, DEFAULT_REJECTION_WINDOW_MS = 60000, MAX_BREADCRUMB_CHARS = 16384;
6131
+
5637
6132
  // packages/daemon/src/rpc-server.ts
5638
- import fs14 from "fs";
6133
+ import fs15 from "fs";
5639
6134
  import net2 from "net";
5640
6135
  function writeFrame(socket, frame) {
5641
6136
  if (!socket.destroyed)
@@ -5873,7 +6368,7 @@ function startRpcServer(options) {
5873
6368
  });
5874
6369
  if (process.platform !== "win32") {
5875
6370
  try {
5876
- fs14.unlinkSync(pipePath);
6371
+ fs15.unlinkSync(pipePath);
5877
6372
  } catch {}
5878
6373
  }
5879
6374
  server.listen(pipePath, () => {
@@ -5888,21 +6383,21 @@ var init_rpc_server = __esm(() => {
5888
6383
  });
5889
6384
 
5890
6385
  // packages/daemon/src/codex-home.ts
5891
- import fs15 from "fs";
5892
- import path14 from "path";
6386
+ import fs16 from "fs";
6387
+ import path16 from "path";
5893
6388
  function codexTrustKeyPath(home) {
5894
6389
  const resolved = (() => {
5895
6390
  try {
5896
- return fs15.realpathSync.native(home);
6391
+ return fs16.realpathSync.native(home);
5897
6392
  } catch {
5898
- return path14.resolve(home);
6393
+ return path16.resolve(home);
5899
6394
  }
5900
6395
  })();
5901
- return path14.join(resolved, "hooks.json");
6396
+ return path16.join(resolved, "hooks.json");
5902
6397
  }
5903
6398
  function referenceCredential(source, target, ops) {
5904
- const symlink = ops?.symlink ?? ((s, t2) => fs15.symlinkSync(s, t2, "file"));
5905
- const link = ops?.link ?? fs15.linkSync;
6399
+ const symlink = ops?.symlink ?? ((s, t2) => fs16.symlinkSync(s, t2, "file"));
6400
+ const link = ops?.link ?? fs16.linkSync;
5906
6401
  try {
5907
6402
  symlink(source, target);
5908
6403
  return "symlink";
@@ -5911,8 +6406,8 @@ function referenceCredential(source, target, ops) {
5911
6406
  link(source, target);
5912
6407
  return "hardlink";
5913
6408
  } catch {
5914
- fs15.copyFileSync(source, target);
5915
- fs15.chmodSync(target, 384);
6409
+ fs16.copyFileSync(source, target);
6410
+ fs16.chmodSync(target, 384);
5916
6411
  return "copy";
5917
6412
  }
5918
6413
  }
@@ -5999,8 +6494,8 @@ function slugifyBranch(branch) {
5999
6494
  var MAX_SLUG_LENGTH = 60;
6000
6495
 
6001
6496
  // packages/daemon/src/git.ts
6002
- import fs16 from "fs";
6003
- import path15 from "path";
6497
+ import fs17 from "fs";
6498
+ import path17 from "path";
6004
6499
  async function git(ws, cwd, args) {
6005
6500
  const run = await ws.exec(["git", ...args], cwd);
6006
6501
  return {
@@ -6017,15 +6512,15 @@ async function gitOrThrow(ws, cwd, args) {
6017
6512
  return result.stdout;
6018
6513
  }
6019
6514
  function canonicalPath(target) {
6020
- let resolved = path15.resolve(target);
6515
+ let resolved = path17.resolve(target);
6021
6516
  try {
6022
- resolved = fs16.realpathSync.native(resolved);
6517
+ resolved = fs17.realpathSync.native(resolved);
6023
6518
  } catch {}
6024
6519
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
6025
6520
  }
6026
6521
  async function resolveRepoRoot(ws, cwd) {
6027
6522
  const result = await git(ws, cwd, ["rev-parse", "--show-toplevel"]);
6028
- return result.ok ? path15.resolve(result.stdout) : undefined;
6523
+ return result.ok ? path17.resolve(result.stdout) : undefined;
6029
6524
  }
6030
6525
  async function currentBranch(ws, cwd) {
6031
6526
  const result = await git(ws, cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -6034,8 +6529,8 @@ async function currentBranch(ws, cwd) {
6034
6529
  async function listWorktrees(ws, repoRoot) {
6035
6530
  return parseWorktreeList(await gitOrThrow(ws, repoRoot, ["worktree", "list", "--porcelain"]));
6036
6531
  }
6037
- function worktreePathFor(repoRoot, branch, root = path15.join(STATE_DIR, "worktrees")) {
6038
- return path15.join(root, slugifyBranch(path15.basename(repoRoot)), slugifyBranch(branch));
6532
+ function worktreePathFor(repoRoot, branch, root = path17.join(STATE_DIR, "worktrees")) {
6533
+ return path17.join(root, slugifyBranch(path17.basename(repoRoot)), slugifyBranch(branch));
6039
6534
  }
6040
6535
  async function createWorktree(ws, options) {
6041
6536
  const { repoRoot, branch, base } = options;
@@ -6043,7 +6538,7 @@ async function createWorktree(ws, options) {
6043
6538
  const existing = (await listWorktrees(ws, repoRoot)).find((w) => canonicalPath(w.path) === canonicalPath(target));
6044
6539
  if (existing)
6045
6540
  return { path: target, branch, createdBranch: false };
6046
- fs16.mkdirSync(path15.dirname(target), { recursive: true });
6541
+ fs17.mkdirSync(path17.dirname(target), { recursive: true });
6047
6542
  const exists = (await git(ws, repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])).ok;
6048
6543
  const args = exists ? ["worktree", "add", target, branch] : ["worktree", "add", "-b", branch, target, ...base !== undefined ? [base] : []];
6049
6544
  await gitOrThrow(ws, repoRoot, args);
@@ -6104,7 +6599,7 @@ async function diffStat(ws, worktreePath, base) {
6104
6599
  const untracked = await untrackedFiles(ws, worktreePath);
6105
6600
  let newLines = 0;
6106
6601
  for (const file of untracked)
6107
- newLines += await countLines(ws, path15.join(worktreePath, file));
6602
+ newLines += await countLines(ws, path17.join(worktreePath, file));
6108
6603
  return {
6109
6604
  filesChanged: a.filesChanged + b.filesChanged + untracked.length,
6110
6605
  insertions: a.insertions + b.insertions + newLines,
@@ -6127,21 +6622,84 @@ async function changedFiles(ws, worktreePath, base, limit = 20) {
6127
6622
  names.add(file);
6128
6623
  return [...names].slice(0, limit);
6129
6624
  }
6130
- var UNTRACKED_LINE_SCAN_LIMIT;
6625
+ async function diffPatch(ws, worktreePath, base, maxChars = 64 * 1024) {
6626
+ const parts = [];
6627
+ let size = 0;
6628
+ const push = (text) => {
6629
+ if (size >= maxChars)
6630
+ return false;
6631
+ const room = maxChars - size;
6632
+ const piece = text.length > room ? text.slice(0, room) : text;
6633
+ parts.push(piece);
6634
+ size += piece.length;
6635
+ return piece.length === text.length;
6636
+ };
6637
+ for (const args of [
6638
+ ["diff", `${base}...HEAD`, "--", "."],
6639
+ ["diff", "HEAD"]
6640
+ ]) {
6641
+ const out = await git(ws, worktreePath, args);
6642
+ if (out.ok && out.stdout.trim().length > 0 && !push(`${out.stdout.replace(/\s+$/, "")}
6643
+ `)) {
6644
+ return parts.join("");
6645
+ }
6646
+ }
6647
+ for (const file of await untrackedFiles(ws, worktreePath)) {
6648
+ if (size >= maxChars)
6649
+ break;
6650
+ let text;
6651
+ try {
6652
+ const stat = await ws.stat(path17.join(worktreePath, file));
6653
+ if (stat?.kind !== "file" || stat.size > UNTRACKED_LINE_SCAN_LIMIT)
6654
+ continue;
6655
+ text = new TextDecoder().decode(await ws.read(path17.join(worktreePath, file)));
6656
+ } catch {
6657
+ continue;
6658
+ }
6659
+ if (text.slice(0, 8000).includes("\x00")) {
6660
+ if (!push(`diff --git a/${file} b/${file}
6661
+ Binary files /dev/null and b/${file} differ
6662
+ `))
6663
+ break;
6664
+ continue;
6665
+ }
6666
+ const lines = text.split(`
6667
+ `);
6668
+ if (lines.at(-1) === "")
6669
+ lines.pop();
6670
+ const shown = lines.slice(0, UNTRACKED_PATCH_LINES);
6671
+ const body = shown.map((line) => `+${line}`).join(`
6672
+ `);
6673
+ const elided = lines.length > shown.length ? `
6674
+ +\u2026 ${lines.length - shown.length} more lines
6675
+ ` : `
6676
+ `;
6677
+ const header2 = `diff --git a/${file} b/${file}
6678
+ new file mode 100644
6679
+ --- /dev/null
6680
+ +++ b/${file}
6681
+ @@ -0,0 +1,${lines.length} @@
6682
+ `;
6683
+ if (!push(`${header2}${body}${elided}`))
6684
+ break;
6685
+ }
6686
+ return parts.join("");
6687
+ }
6688
+ var UNTRACKED_LINE_SCAN_LIMIT, UNTRACKED_PATCH_LINES = 40;
6131
6689
  var init_git = __esm(() => {
6132
6690
  init_paths();
6133
6691
  UNTRACKED_LINE_SCAN_LIMIT = 1024 * 1024;
6134
6692
  });
6135
6693
 
6136
6694
  // packages/daemon/src/claude-store.ts
6137
- import fs17 from "fs";
6138
- import os4 from "os";
6139
- import path16 from "path";
6695
+ import fs18 from "fs";
6696
+ import os5 from "os";
6697
+ import path18 from "path";
6140
6698
  function claudeConfigDir(env = process.env) {
6141
6699
  const override = env["CLAUDE_CONFIG_DIR"];
6142
6700
  if (override !== undefined && override.length > 0)
6143
6701
  return override;
6144
- return path16.join(os4.homedir(), ".claude");
6702
+ return path18.join(os5.homedir(), ".claude");
6145
6703
  }
6146
6704
  function resumedConversationId(argv) {
6147
6705
  for (let i = 0;i < argv.length; i++) {
@@ -6157,41 +6715,41 @@ function resumedConversationId(argv) {
6157
6715
  return;
6158
6716
  }
6159
6717
  function claudeConversationExists(configDir, conversationId) {
6160
- const projects = path16.join(configDir, "projects");
6718
+ const projects = path18.join(configDir, "projects");
6161
6719
  let entries;
6162
6720
  try {
6163
- entries = fs17.readdirSync(projects);
6721
+ entries = fs18.readdirSync(projects);
6164
6722
  } catch {
6165
6723
  return;
6166
6724
  }
6167
6725
  const file = `${conversationId}.jsonl`;
6168
6726
  for (const entry of entries) {
6169
6727
  try {
6170
- if (fs17.existsSync(path16.join(projects, entry, file)))
6728
+ if (fs18.existsSync(path18.join(projects, entry, file)))
6171
6729
  return true;
6172
6730
  } catch {}
6173
6731
  }
6174
6732
  return false;
6175
6733
  }
6176
6734
  function claudeConversationCwd(configDir, conversationId) {
6177
- const projects = path16.join(configDir, "projects");
6735
+ const projects = path18.join(configDir, "projects");
6178
6736
  let entries;
6179
6737
  try {
6180
- entries = fs17.readdirSync(projects);
6738
+ entries = fs18.readdirSync(projects);
6181
6739
  } catch {
6182
6740
  return;
6183
6741
  }
6184
6742
  for (const entry of entries) {
6185
- const file = path16.join(projects, entry, `${conversationId}.jsonl`);
6743
+ const file = path18.join(projects, entry, `${conversationId}.jsonl`);
6186
6744
  let head;
6187
6745
  try {
6188
- const handle = fs17.openSync(file, "r");
6746
+ const handle = fs18.openSync(file, "r");
6189
6747
  try {
6190
6748
  const buffer = Buffer.alloc(64 * 1024);
6191
- const read = fs17.readSync(handle, buffer, 0, buffer.byteLength, 0);
6749
+ const read = fs18.readSync(handle, buffer, 0, buffer.byteLength, 0);
6192
6750
  head = buffer.subarray(0, read).toString("utf8");
6193
6751
  } finally {
6194
- fs17.closeSync(handle);
6752
+ fs18.closeSync(handle);
6195
6753
  }
6196
6754
  } catch {
6197
6755
  continue;
@@ -6215,7 +6773,7 @@ var init_claude_store = __esm(() => {
6215
6773
  });
6216
6774
 
6217
6775
  // packages/daemon/src/injector.ts
6218
- import { setTimeout as delay2 } from "timers/promises";
6776
+ import { setTimeout as delay3 } from "timers/promises";
6219
6777
 
6220
6778
  class Injector {
6221
6779
  queues = new Map;
@@ -6230,14 +6788,14 @@ class Injector {
6230
6788
  try {
6231
6789
  const deadline = Date.now() + 5000;
6232
6790
  while (!session.pasteReady && !session.isExited && Date.now() < deadline) {
6233
- await delay2(100);
6791
+ await delay3(100);
6234
6792
  }
6235
6793
  session.write(CTRL_U);
6236
- await delay2(CLEAR_SETTLE_MS);
6794
+ await delay3(CLEAR_SETTLE_MS);
6237
6795
  for (const chunk of iteratePasteChunks(text)) {
6238
6796
  session.write(chunk);
6239
6797
  }
6240
- await delay2(SUBMIT_DELAY_MS);
6798
+ await delay3(SUBMIT_DELAY_MS);
6241
6799
  session.write(SUBMIT);
6242
6800
  } catch (err) {
6243
6801
  session.write(BRACKETED_PASTE_END);
@@ -6249,7 +6807,7 @@ class Injector {
6249
6807
  interrupt(session) {
6250
6808
  return this.enqueue(session.info.id, async () => {
6251
6809
  session.write(ESC);
6252
- await delay2(500);
6810
+ await delay3(500);
6253
6811
  });
6254
6812
  }
6255
6813
  }
@@ -6459,18 +7017,18 @@ var init_pty_host_client = __esm(() => {
6459
7017
 
6460
7018
  // packages/daemon/src/pty-host-launch.ts
6461
7019
  import { spawn as spawn3 } from "child_process";
6462
- import fs18 from "fs";
6463
- import os5 from "os";
6464
- import path17 from "path";
7020
+ import fs19 from "fs";
7021
+ import os6 from "os";
7022
+ import path19 from "path";
6465
7023
  function hostFile(runtimeDir) {
6466
- return path17.join(runtimeDir, "pty-host.json");
7024
+ return path19.join(runtimeDir, "pty-host.json");
6467
7025
  }
6468
7026
  function readPtyHostIn(runtimeDir) {
6469
7027
  return readPtyHostFile(hostFile(runtimeDir));
6470
7028
  }
6471
7029
  function readPtyHostFile(file) {
6472
7030
  try {
6473
- const parsed = JSON.parse(fs18.readFileSync(file, "utf8"));
7031
+ const parsed = JSON.parse(fs19.readFileSync(file, "utf8"));
6474
7032
  if (typeof parsed !== "object" || parsed === null)
6475
7033
  return;
6476
7034
  const { pid, endpoint, token, version: version2 } = parsed;
@@ -6490,8 +7048,8 @@ function isRunning(pid) {
6490
7048
  }
6491
7049
  }
6492
7050
  function hostEndpoint(runtimeDir) {
6493
- const base = ipcEndpoint(process.platform, os5.userInfo().username, { runtimeDir });
6494
- return process.platform === "win32" ? `${base}-pty` : path17.join(runtimeDir, "axterm-pty.sock");
7051
+ const base = ipcEndpoint(process.platform, os6.userInfo().username, { runtimeDir });
7052
+ return process.platform === "win32" ? `${base}-pty` : path19.join(runtimeDir, "axterm-pty.sock");
6495
7053
  }
6496
7054
  async function ensurePtyHost(location) {
6497
7055
  const file = hostFile(location.runtimeDir);
@@ -6502,7 +7060,7 @@ async function ensurePtyHost(location) {
6502
7060
  log(`pty host: handoff file names a dead pid ${existing.pid}, starting a new host`);
6503
7061
  const endpoint = hostEndpoint(location.runtimeDir);
6504
7062
  if (process.platform !== "win32")
6505
- fs18.rmSync(endpoint, { force: true });
7063
+ fs19.rmSync(endpoint, { force: true });
6506
7064
  const token = shortHash(`${location.runtimeDir}:${Date.now()}:${Math.random()}`) + shortHash(String(process.pid));
6507
7065
  const launch = ptyHostLaunch({ execPath: process.execPath, mainScript: location.mainScript ?? Bun.main });
6508
7066
  const child = spawn3(launch.command, launch.args, {
@@ -6531,10 +7089,41 @@ async function ensurePtyHost(location) {
6531
7089
  }
6532
7090
  }
6533
7091
  function writePtyHostFile(file, info) {
6534
- fs18.mkdirSync(path17.dirname(file), { recursive: true, mode: 448 });
6535
- fs18.writeFileSync(file, `${JSON.stringify(info, null, 2)}
6536
- `, { mode: 384 });
6537
- restrictFile(file);
7092
+ const dir = path19.dirname(file);
7093
+ const tmp = `${file}.tmp`;
7094
+ fs19.mkdirSync(dir, { recursive: true, mode: 448 });
7095
+ let handle;
7096
+ try {
7097
+ handle = fs19.openSync(tmp, "w", 384);
7098
+ fs19.writeFileSync(handle, `${JSON.stringify(info, null, 2)}
7099
+ `, "utf8");
7100
+ fs19.fsyncSync(handle);
7101
+ fs19.closeSync(handle);
7102
+ handle = undefined;
7103
+ restrictFile(tmp);
7104
+ fs19.renameSync(tmp, file);
7105
+ restrictFile(file);
7106
+ if (process.platform !== "win32") {
7107
+ try {
7108
+ const directory = fs19.openSync(dir, "r");
7109
+ try {
7110
+ fs19.fsyncSync(directory);
7111
+ } finally {
7112
+ fs19.closeSync(directory);
7113
+ }
7114
+ } catch {}
7115
+ }
7116
+ } catch (err) {
7117
+ if (handle !== undefined) {
7118
+ try {
7119
+ fs19.closeSync(handle);
7120
+ } catch {}
7121
+ }
7122
+ try {
7123
+ fs19.rmSync(tmp, { force: true });
7124
+ } catch {}
7125
+ throw err;
7126
+ }
6538
7127
  }
6539
7128
  var init_pty_host_launch = __esm(() => {
6540
7129
  init_src();
@@ -6543,8 +7132,8 @@ var init_pty_host_launch = __esm(() => {
6543
7132
  });
6544
7133
 
6545
7134
  // packages/daemon/src/pty.ts
6546
- import { spawnSync as spawnSync2 } from "child_process";
6547
- import path18 from "path";
7135
+ import { spawnSync as spawnSync3 } from "child_process";
7136
+ import path20 from "path";
6548
7137
  import { StringDecoder as StringDecoder2 } from "string_decoder";
6549
7138
 
6550
7139
  class Fanout {
@@ -6565,13 +7154,13 @@ class Fanout {
6565
7154
  }
6566
7155
  function killTree(pid, exeName) {
6567
7156
  if (process.platform === "win32") {
6568
- const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
7157
+ const check = spawnSync3("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
6569
7158
  encoding: "utf8",
6570
7159
  windowsHide: true
6571
7160
  });
6572
7161
  const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
6573
7162
  if (image === exeName) {
6574
- spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
7163
+ spawnSync3("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
6575
7164
  }
6576
7165
  return;
6577
7166
  }
@@ -6644,7 +7233,7 @@ function spawnPty(options) {
6644
7233
  }
6645
7234
  }
6646
7235
  });
6647
- handle = new BunPtyHandle(proc, path18.basename(options.exe).toLowerCase());
7236
+ handle = new BunPtyHandle(proc, path20.basename(options.exe).toLowerCase());
6648
7237
  proc.exited.then((code) => handle?.finish(code ?? null));
6649
7238
  return handle;
6650
7239
  }
@@ -6652,7 +7241,7 @@ var init_pty = () => {};
6652
7241
 
6653
7242
  // packages/daemon/src/occupancy.ts
6654
7243
  import { spawn as spawn4 } from "child_process";
6655
- import fs19 from "fs/promises";
7244
+ import fs20 from "fs/promises";
6656
7245
  function shellOccupied(shellPid) {
6657
7246
  return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
6658
7247
  }
@@ -6697,11 +7286,11 @@ async function occupiedFromSnapshot(shellPid) {
6697
7286
  }
6698
7287
  async function childrenViaProc(pid) {
6699
7288
  try {
6700
- const children = await fs19.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
7289
+ const children = await fs20.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
6701
7290
  return children.trim().length > 0;
6702
7291
  } catch {
6703
7292
  try {
6704
- await fs19.access(`/proc/${pid}`);
7293
+ await fs20.access(`/proc/${pid}`);
6705
7294
  return;
6706
7295
  } catch {
6707
7296
  return false;
@@ -6765,20 +7354,20 @@ var init_occupancy = __esm(() => {
6765
7354
  });
6766
7355
 
6767
7356
  // packages/daemon/src/agent-store.ts
6768
- import fs20 from "fs";
6769
- import os6 from "os";
6770
- import path19 from "path";
7357
+ import fs21 from "fs";
7358
+ import os7 from "os";
7359
+ import path21 from "path";
6771
7360
  import { Database as Database3 } from "bun:sqlite";
6772
7361
  function readTailLines(file, maxBytes) {
6773
7362
  let fd;
6774
7363
  try {
6775
- fd = fs20.openSync(file, "r");
6776
- const size = fs20.fstatSync(fd).size;
7364
+ fd = fs21.openSync(file, "r");
7365
+ const size = fs21.fstatSync(fd).size;
6777
7366
  if (size === 0)
6778
7367
  return [];
6779
7368
  const length = Math.min(size, maxBytes);
6780
7369
  const buffer = Buffer.allocUnsafe(length);
6781
- fs20.readSync(fd, buffer, 0, length, size - length);
7370
+ fs21.readSync(fd, buffer, 0, length, size - length);
6782
7371
  const text = buffer.toString("utf8");
6783
7372
  const lines = text.split(`
6784
7373
  `);
@@ -6791,46 +7380,46 @@ function readTailLines(file, maxBytes) {
6791
7380
  return [];
6792
7381
  } finally {
6793
7382
  if (fd !== undefined)
6794
- fs20.closeSync(fd);
7383
+ fs21.closeSync(fd);
6795
7384
  }
6796
7385
  }
6797
7386
  function claudeConfigDir2() {
6798
- return process.env["CLAUDE_CONFIG_DIR"] ?? path19.join(os6.homedir(), ".claude");
7387
+ return process.env["CLAUDE_CONFIG_DIR"] ?? path21.join(os7.homedir(), ".claude");
6799
7388
  }
6800
7389
  function codexHome() {
6801
- return path19.join(STATE_DIR, "codex-home");
7390
+ return path21.join(STATE_DIR, "codex-home");
6802
7391
  }
6803
7392
  function ompAgentDb() {
6804
- return path19.join(os6.homedir(), ".omp", "agent", "agent.db");
7393
+ return path21.join(os7.homedir(), ".omp", "agent", "agent.db");
6805
7394
  }
6806
7395
  function findCodexRollout(sessionId) {
6807
- const root = path19.join(codexHome(), "sessions");
7396
+ const root = path21.join(codexHome(), "sessions");
6808
7397
  for (const shard2 of codexRolloutShards(sessionId)) {
6809
- const dir = path19.join(root, ...shard2.split("/"));
7398
+ const dir = path21.join(root, ...shard2.split("/"));
6810
7399
  let entries;
6811
7400
  try {
6812
- entries = fs20.readdirSync(dir);
7401
+ entries = fs21.readdirSync(dir);
6813
7402
  } catch {
6814
7403
  continue;
6815
7404
  }
6816
7405
  const match2 = entries.find((name) => name.endsWith(`-${sessionId}.jsonl`));
6817
7406
  if (match2 !== undefined)
6818
- return path19.join(dir, match2);
7407
+ return path21.join(dir, match2);
6819
7408
  }
6820
7409
  return;
6821
7410
  }
6822
7411
  function newestCodexRollout() {
6823
- const root = path19.join(codexHome(), "sessions");
7412
+ const root = path21.join(codexHome(), "sessions");
6824
7413
  let best;
6825
7414
  const walk = (dir, depth) => {
6826
7415
  let entries;
6827
7416
  try {
6828
- entries = fs20.readdirSync(dir, { withFileTypes: true });
7417
+ entries = fs21.readdirSync(dir, { withFileTypes: true });
6829
7418
  } catch {
6830
7419
  return;
6831
7420
  }
6832
7421
  for (const entry of entries) {
6833
- const full = path19.join(dir, entry.name);
7422
+ const full = path21.join(dir, entry.name);
6834
7423
  if (entry.isDirectory()) {
6835
7424
  if (depth < 3)
6836
7425
  walk(full, depth + 1);
@@ -6839,7 +7428,7 @@ function newestCodexRollout() {
6839
7428
  if (!entry.name.endsWith(".jsonl"))
6840
7429
  continue;
6841
7430
  try {
6842
- const { mtimeMs } = fs20.statSync(full);
7431
+ const { mtimeMs } = fs21.statSync(full);
6843
7432
  if (!best || mtimeMs > best.mtimeMs)
6844
7433
  best = { file: full, mtimeMs };
6845
7434
  } catch {}
@@ -6850,7 +7439,7 @@ function newestCodexRollout() {
6850
7439
  }
6851
7440
  function ompQuotaFacts() {
6852
7441
  const file = ompAgentDb();
6853
- if (!fs20.existsSync(file))
7442
+ if (!fs21.existsSync(file))
6854
7443
  return { agent: "omp", kind: "gauge" };
6855
7444
  let db;
6856
7445
  try {
@@ -6882,7 +7471,7 @@ function claudeQuotaFacts(sessions) {
6882
7471
  for (const session of sessions) {
6883
7472
  if (session.agentSessionId === undefined)
6884
7473
  continue;
6885
- const file = path19.join(dir, claudeTranscriptRelPath(session.cwd, session.agentSessionId));
7474
+ const file = path21.join(dir, claudeTranscriptRelPath(session.cwd, session.agentSessionId));
6886
7475
  const event = parseClaudeLimitEvent(readTailLines(file, QUOTA_TAIL_BYTES));
6887
7476
  if (event && (!newest || event.at > newest.at))
6888
7477
  newest = event;
@@ -6914,11 +7503,11 @@ function collectQuotaFacts(agents, sessions, onPath) {
6914
7503
  function storeQuietForMs(agent, cwd, agentSessionId, nowMs) {
6915
7504
  if (agentSessionId === undefined)
6916
7505
  return;
6917
- const file = agent === "claude" ? path19.join(claudeConfigDir2(), claudeTranscriptRelPath(cwd, agentSessionId)) : agent === "codex" ? findCodexRollout(agentSessionId) : undefined;
7506
+ const file = agent === "claude" ? path21.join(claudeConfigDir2(), claudeTranscriptRelPath(cwd, agentSessionId)) : agent === "codex" ? findCodexRollout(agentSessionId) : undefined;
6918
7507
  if (file === undefined)
6919
7508
  return;
6920
7509
  try {
6921
- return Math.max(0, nowMs - fs20.statSync(file).mtimeMs);
7510
+ return Math.max(0, nowMs - fs21.statSync(file).mtimeMs);
6922
7511
  } catch {
6923
7512
  return;
6924
7513
  }
@@ -6928,15 +7517,15 @@ function readFileRange(file, from, to) {
6928
7517
  return "";
6929
7518
  let fd;
6930
7519
  try {
6931
- fd = fs20.openSync(file, "r");
7520
+ fd = fs21.openSync(file, "r");
6932
7521
  const buffer = Buffer.allocUnsafe(to - from);
6933
- const read = fs20.readSync(fd, buffer, 0, to - from, from);
7522
+ const read = fs21.readSync(fd, buffer, 0, to - from, from);
6934
7523
  return buffer.subarray(0, read).toString("utf8");
6935
7524
  } catch {
6936
7525
  return "";
6937
7526
  } finally {
6938
7527
  if (fd !== undefined)
6939
- fs20.closeSync(fd);
7528
+ fs21.closeSync(fd);
6940
7529
  }
6941
7530
  }
6942
7531
  var QUOTA_TAIL_BYTES;
@@ -11429,8 +12018,8 @@ var init_terminal_mirror = __esm(() => {
11429
12018
  });
11430
12019
 
11431
12020
  // packages/daemon/src/pty-session.ts
11432
- import fs21 from "fs";
11433
- import path20 from "path";
12021
+ import fs22 from "fs";
12022
+ import path22 from "path";
11434
12023
 
11435
12024
  class PtySession {
11436
12025
  info;
@@ -11535,10 +12124,10 @@ class PtySession {
11535
12124
  }
11536
12125
  saveScrollback() {
11537
12126
  try {
11538
- const file = path20.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
12127
+ const file = path22.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
11539
12128
  const tmp = `${file}.tmp`;
11540
- fs21.writeFileSync(tmp, this.serializeBuffer(), "utf8");
11541
- fs21.renameSync(tmp, file);
12129
+ fs22.writeFileSync(tmp, this.serializeBuffer(), "utf8");
12130
+ fs22.renameSync(tmp, file);
11542
12131
  } catch (err) {
11543
12132
  log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
11544
12133
  }
@@ -11560,9 +12149,9 @@ var init_pty_session = __esm(() => {
11560
12149
  // packages/daemon/src/session-manager.ts
11561
12150
  import { randomBytes as randomBytes2, randomUUID } from "crypto";
11562
12151
  import { spawn as spawn5 } from "child_process";
11563
- import fs22 from "fs";
11564
- import os7 from "os";
11565
- import path21 from "path";
12152
+ import fs23 from "fs";
12153
+ import os8 from "os";
12154
+ import path23 from "path";
11566
12155
  function sessionScratchRoot(info) {
11567
12156
  return info.worktreePath ?? info.cwd;
11568
12157
  }
@@ -11612,11 +12201,15 @@ function pidIsRunning(pid) {
11612
12201
  return err.code === "EPERM";
11613
12202
  }
11614
12203
  }
12204
+ function ptyHostEnabled(env) {
12205
+ return env["AXTERM_PTY_HOST"] !== "0";
12206
+ }
11615
12207
 
11616
12208
  class SessionManager {
11617
12209
  store;
11618
12210
  hooks;
11619
12211
  configuredDefaults;
12212
+ ptyHostPolicy;
11620
12213
  live = new Map;
11621
12214
  agentRuntime = new Map;
11622
12215
  injector = new Injector;
@@ -11624,10 +12217,14 @@ class SessionManager {
11624
12217
  eventListeners = new Set;
11625
12218
  rawHookListeners = new Set;
11626
12219
  workspace = new LocalWorkspace(MATERIALISED_DIR);
11627
- constructor(store, hooks, configuredDefaults = () => ({})) {
12220
+ constructor(store, hooks, configuredDefaults = () => ({}), ptyHostPolicy = {
12221
+ enabled: ptyHostEnabled(process.env),
12222
+ ...process.env["AXTERM_PTY_HOST_MAIN"] ? { mainScript: process.env["AXTERM_PTY_HOST_MAIN"] } : {}
12223
+ }) {
11628
12224
  this.store = store;
11629
12225
  this.hooks = hooks;
11630
12226
  this.configuredDefaults = configuredDefaults;
12227
+ this.ptyHostPolicy = ptyHostPolicy;
11631
12228
  this.sessionWatch = setInterval(() => this.tickSessions(), SESSION_TICK_MS);
11632
12229
  this.sessionWatch.unref();
11633
12230
  }
@@ -11664,7 +12261,7 @@ class SessionManager {
11664
12261
  return { adopted: adoptedIds.length, sweptExited };
11665
12262
  }
11666
12263
  async hostSessions() {
11667
- if (process.env["AXTERM_PTY_HOST"] !== "1")
12264
+ if (!this.ptyHostPolicy.enabled)
11668
12265
  return new Map;
11669
12266
  const existing = readPtyHostIn(RUNTIME_DIR);
11670
12267
  if (existing === undefined)
@@ -11753,13 +12350,14 @@ class SessionManager {
11753
12350
  return {
11754
12351
  stat: await diffStat(ws, target, compare),
11755
12352
  files: await changedFiles(ws, target, compare),
12353
+ patch: await diffPatch(ws, target, compare),
11756
12354
  base: compare,
11757
12355
  ...info.branch !== undefined ? { branch: info.branch } : {}
11758
12356
  };
11759
12357
  }
11760
12358
  async create(params) {
11761
- const requestedCwd = params.cwd ? path21.resolve(params.cwd) : os7.homedir();
11762
- if (!fs22.existsSync(requestedCwd)) {
12359
+ const requestedCwd = params.cwd ? path23.resolve(params.cwd) : os8.homedir();
12360
+ if (!fs23.existsSync(requestedCwd)) {
11763
12361
  throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
11764
12362
  }
11765
12363
  let worktree;
@@ -11815,14 +12413,14 @@ class SessionManager {
11815
12413
  sessionCwd = resolveSessionCwd({
11816
12414
  requested: cwd,
11817
12415
  preferConversation: params.preferConversationCwd === true,
11818
- ...recorded !== undefined && fs22.existsSync(recorded) ? { conversationCwd: recorded } : {}
12416
+ ...recorded !== undefined && fs23.existsSync(recorded) ? { conversationCwd: recorded } : {}
11819
12417
  });
11820
12418
  }
11821
12419
  }
11822
12420
  const info = {
11823
12421
  id,
11824
12422
  shortId: id.slice(0, 6),
11825
- name: params.name ?? worktree?.branch ?? path21.basename(sessionCwd),
12423
+ name: params.name ?? worktree?.branch ?? path23.basename(sessionCwd),
11826
12424
  cwd: sessionCwd,
11827
12425
  shell: shell.kind,
11828
12426
  agentCommand,
@@ -11846,7 +12444,7 @@ class SessionManager {
11846
12444
  launchToken
11847
12445
  });
11848
12446
  const file = this.claudeSettingsPath(id);
11849
- fs22.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
12447
+ fs23.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
11850
12448
  }
11851
12449
  writeGeminiSettings(sessionId) {
11852
12450
  try {
@@ -11859,8 +12457,8 @@ class SessionManager {
11859
12457
  });
11860
12458
  const file = this.geminiSettingsPath();
11861
12459
  const tmp = `${file}.tmp`;
11862
- fs22.writeFileSync(tmp, JSON.stringify(settings, null, 2));
11863
- fs22.renameSync(tmp, file);
12460
+ fs23.writeFileSync(tmp, JSON.stringify(settings, null, 2));
12461
+ fs23.renameSync(tmp, file);
11864
12462
  } catch (err) {
11865
12463
  log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
11866
12464
  }
@@ -11889,7 +12487,7 @@ class SessionManager {
11889
12487
  return;
11890
12488
  }
11891
12489
  this.watchCodexAppServer(id, url, pid);
11892
- fs22.writeFileSync(this.codexAppServerFile(id), JSON.stringify({ pid, url }), { mode: 384 });
12490
+ fs23.writeFileSync(this.codexAppServerFile(id), JSON.stringify({ pid, url }), { mode: 384 });
11893
12491
  log(`codex app-server on ${url} (pid ${pid}) for session ${id.slice(0, 6)} in ${cwd}`);
11894
12492
  return url;
11895
12493
  } catch (err) {
@@ -11898,13 +12496,13 @@ class SessionManager {
11898
12496
  }
11899
12497
  }
11900
12498
  codexAppServerFile(id) {
11901
- return path21.join(RUNTIME_DIR, `codex-app-${id}.json`);
12499
+ return path23.join(RUNTIME_DIR, `codex-app-${id}.json`);
11902
12500
  }
11903
12501
  adoptCodexAppServer(id) {
11904
12502
  const file = this.codexAppServerFile(id);
11905
12503
  let record;
11906
12504
  try {
11907
- record = JSON.parse(fs22.readFileSync(file, "utf8"));
12505
+ record = JSON.parse(fs23.readFileSync(file, "utf8"));
11908
12506
  } catch {
11909
12507
  return;
11910
12508
  }
@@ -11913,7 +12511,7 @@ class SessionManager {
11913
12511
  if (typeof pid !== "number" || typeof url !== "string")
11914
12512
  return;
11915
12513
  if (!pidIsRunning(pid)) {
11916
- fs22.rmSync(file, { force: true });
12514
+ fs23.rmSync(file, { force: true });
11917
12515
  return;
11918
12516
  }
11919
12517
  this.watchCodexAppServer(id, url, pid);
@@ -11927,7 +12525,7 @@ class SessionManager {
11927
12525
  stop: () => {
11928
12526
  client.close();
11929
12527
  this.killCodexAppServer(pid);
11930
- fs22.rmSync(this.codexAppServerFile(id), { force: true });
12528
+ fs23.rmSync(this.codexAppServerFile(id), { force: true });
11931
12529
  }
11932
12530
  });
11933
12531
  }
@@ -11941,22 +12539,22 @@ class SessionManager {
11941
12539
  this.codexServers.delete(id);
11942
12540
  }
11943
12541
  ensureCodexHome() {
11944
- const home = path21.join(STATE_DIR, "codex-home");
11945
- fs22.mkdirSync(home, { recursive: true, mode: 448 });
12542
+ const home = path23.join(STATE_DIR, "codex-home");
12543
+ fs23.mkdirSync(home, { recursive: true, mode: 448 });
11946
12544
  this.linkCodexAuth(home);
11947
12545
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
11948
12546
  const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
11949
12547
  const hooksPath = codexTrustKeyPath(home);
11950
12548
  const tmp = `${hooksPath}.tmp`;
11951
- fs22.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
11952
- fs22.renameSync(tmp, hooksPath);
12549
+ fs23.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
12550
+ fs23.renameSync(tmp, hooksPath);
11953
12551
  const entries = codexHookTrust(hooksPath, hooksFile);
11954
- const configPath = path21.join(home, "config.toml");
11955
- const existing = fs22.existsSync(configPath) ? fs22.readFileSync(configPath, "utf8") : "";
12552
+ const configPath = path23.join(home, "config.toml");
12553
+ const existing = fs23.existsSync(configPath) ? fs23.readFileSync(configPath, "utf8") : "";
11956
12554
  const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
11957
12555
  if (missing.length > 0) {
11958
12556
  const block = codexTrustStateToml(missing);
11959
- fs22.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
12557
+ fs23.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
11960
12558
  `) ? `
11961
12559
  ${block}` : block);
11962
12560
  }
@@ -11969,12 +12567,12 @@ ${block}` : block);
11969
12567
  return home;
11970
12568
  }
11971
12569
  linkCodexAuth(home) {
11972
- const target = path21.join(home, "auth.json");
11973
- if (fs22.existsSync(target))
12570
+ const target = path23.join(home, "auth.json");
12571
+ if (fs23.existsSync(target))
11974
12572
  return;
11975
- const operatorHome = process.env["CODEX_HOME"] ?? path21.join(os7.homedir(), ".codex");
11976
- const source = path21.join(operatorHome, "auth.json");
11977
- if (!fs22.existsSync(source))
12573
+ const operatorHome = process.env["CODEX_HOME"] ?? path23.join(os8.homedir(), ".codex");
12574
+ const source = path23.join(operatorHome, "auth.json");
12575
+ if (!fs23.existsSync(source))
11978
12576
  throw new AxtermError("err.codex.noAuth", { authFile: source });
11979
12577
  let how;
11980
12578
  try {
@@ -11988,31 +12586,31 @@ ${block}` : block);
11988
12586
  }
11989
12587
  doctorReport() {
11990
12588
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
11991
- const home = path21.join(STATE_DIR, "codex-home");
11992
- const operatorHome = process.env["CODEX_HOME"] ?? path21.join(os7.homedir(), ".codex");
11993
- const authSource = path21.join(operatorHome, "auth.json");
12589
+ const home = path23.join(STATE_DIR, "codex-home");
12590
+ const operatorHome = process.env["CODEX_HOME"] ?? path23.join(os8.homedir(), ".codex");
12591
+ const authSource = path23.join(operatorHome, "auth.json");
11994
12592
  const geminiSettings = this.geminiSettingsPath();
11995
- const configPath = path21.join(home, "config.toml");
12593
+ const configPath = path23.join(home, "config.toml");
11996
12594
  const read = (file) => {
11997
12595
  try {
11998
- return fs22.existsSync(file) ? fs22.readFileSync(file, "utf8") : undefined;
12596
+ return fs23.existsSync(file) ? fs23.readFileSync(file, "utf8") : undefined;
11999
12597
  } catch {
12000
12598
  return;
12001
12599
  }
12002
12600
  };
12003
12601
  return [
12004
- ...evaluateBridgeDoctor({ command: bridge.command, exists: fs22.existsSync(bridge.command) }),
12602
+ ...evaluateBridgeDoctor({ command: bridge.command, exists: fs23.existsSync(bridge.command) }),
12005
12603
  ...evaluateOmpDoctor({
12006
12604
  binary: lookupOnPath("omp"),
12007
12605
  extensionPath: this.hooks.extensionPath,
12008
- extensionExists: fs22.existsSync(this.hooks.extensionPath)
12606
+ extensionExists: fs23.existsSync(this.hooks.extensionPath)
12009
12607
  }),
12010
12608
  ...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
12011
12609
  ...evaluateCodexDoctor({
12012
12610
  binary: lookupOnPath("codex"),
12013
12611
  authSourcePath: authSource,
12014
- authSourceExists: fs22.existsSync(authSource),
12015
- homeExists: fs22.existsSync(home),
12612
+ authSourceExists: fs23.existsSync(authSource),
12613
+ homeExists: fs23.existsSync(home),
12016
12614
  expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
12017
12615
  configToml: read(configPath)
12018
12616
  }),
@@ -12113,10 +12711,14 @@ ${block}` : block);
12113
12711
  }
12114
12712
  answerScreenChoice(idOrPrefix, optionIndex) {
12115
12713
  const session = this.resolveLive(idOrPrefix);
12116
- const dialog = detectOnboarding(agentIdFromCommand(session.info.agentCommand), session.screenText(30));
12714
+ const agent = agentIdFromCommand(session.info.agentCommand);
12715
+ const dialog = detectOnboarding(agent, session.screenText(30));
12117
12716
  const digit = dialog?.options[optionIndex]?.digit;
12118
12717
  if (digit === undefined) {
12119
- session.write(buildMenuSelectKeys(optionIndex));
12718
+ const keys2 = menuChoiceKeys(agentProfile(agent), optionIndex);
12719
+ if (keys2 === undefined)
12720
+ throw new AxtermError("err.choose.noSafeKey", { agent, index: optionIndex });
12721
+ session.write(keys2);
12120
12722
  return "menu";
12121
12723
  }
12122
12724
  this.pendingPrompt.delete(session.info.id);
@@ -12149,13 +12751,13 @@ ${block}` : block);
12149
12751
  }
12150
12752
  writeScreenFile(idOrPrefix, lines) {
12151
12753
  const session = this.resolveLive(idOrPrefix);
12152
- const file = path21.join(path21.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
12153
- fs22.writeFileSync(file, session.screenText(lines), { mode: 384 });
12754
+ const file = path23.join(path23.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
12755
+ fs23.writeFileSync(file, session.screenText(lines), { mode: 384 });
12154
12756
  return file;
12155
12757
  }
12156
12758
  removeScreenFile(id) {
12157
12759
  try {
12158
- fs22.rmSync(path21.join(path21.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
12760
+ fs23.rmSync(path23.join(path23.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
12159
12761
  } catch {}
12160
12762
  }
12161
12763
  async stageInboundFile(idOrPrefix, suggestedName, bytes) {
@@ -12164,20 +12766,20 @@ ${block}` : block);
12164
12766
  return this.stageInRuntimeDir(info, suggestedName, bytes);
12165
12767
  const ws = this.workspaceFor(info.id);
12166
12768
  const dir = sessionScratchRoot(info);
12167
- const file = path21.resolve(planMaterialisedPath(inboundStagingDir(dir), info.id, suggestedName));
12769
+ const file = path23.resolve(planMaterialisedPath(inboundStagingDir(dir), info.id, suggestedName));
12168
12770
  await this.prepareScratchDir(ws, dir, file);
12169
12771
  await ws.write(file, bytes, 384);
12170
- log(`session ${info.shortId} staged ${path21.basename(file)} (${bytes.byteLength} bytes)`);
12772
+ log(`session ${info.shortId} staged ${path23.basename(file)} (${bytes.byteLength} bytes)`);
12171
12773
  return file;
12172
12774
  }
12173
12775
  stagingMode() {
12174
12776
  return this.configuredDefaults().staging ?? "project";
12175
12777
  }
12176
12778
  async stageInRuntimeDir(info, suggestedName, bytes) {
12177
- const file = path21.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
12178
- await fs22.promises.mkdir(path21.dirname(file), { recursive: true, mode: 448 });
12179
- await fs22.promises.writeFile(file, bytes, { mode: 384 });
12180
- log(`session ${info.shortId} staged ${path21.basename(file)} (${bytes.byteLength} bytes, runtime dir)`);
12779
+ const file = path23.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
12780
+ await fs23.promises.mkdir(path23.dirname(file), { recursive: true, mode: 448 });
12781
+ await fs23.promises.writeFile(file, bytes, { mode: 384 });
12782
+ log(`session ${info.shortId} staged ${path23.basename(file)} (${bytes.byteLength} bytes, runtime dir)`);
12181
12783
  return file;
12182
12784
  }
12183
12785
  async prepareScratchDir(ws, sessionDir, file) {
@@ -12194,47 +12796,47 @@ ${block}` : block);
12194
12796
  throw new AxtermError("err.staging.notIgnored", { dir: scratch, file: SCRATCH_IGNORE_NAME });
12195
12797
  }
12196
12798
  try {
12197
- await ws.mkdirp(path21.dirname(file), 448);
12799
+ await ws.mkdirp(path23.dirname(file), 448);
12198
12800
  } catch (err) {
12199
12801
  throw new AxtermError("err.staging.dir", { dir: scratch, error: err instanceof Error ? err.message : String(err) });
12200
12802
  }
12201
12803
  }
12202
12804
  removeStagedFiles(id, info) {
12203
12805
  try {
12204
- fs22.rmSync(path21.join(MATERIALISED_DIR, id), { recursive: true, force: true });
12806
+ fs23.rmSync(path23.join(MATERIALISED_DIR, id), { recursive: true, force: true });
12205
12807
  } catch {}
12206
12808
  if (info === undefined)
12207
12809
  return;
12208
- const scratch = path21.resolve(scratchDir(sessionScratchRoot(info)));
12810
+ const scratch = path23.resolve(scratchDir(sessionScratchRoot(info)));
12209
12811
  try {
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);
12812
+ fs23.rmSync(path23.resolve(inboundStagingDir(sessionScratchRoot(info)), id), { recursive: true, force: true });
12813
+ fs23.rmdirSync(path23.join(scratch, "inbound"));
12814
+ fs23.rmSync(path23.join(scratch, SCRATCH_IGNORE_NAME), { force: true });
12815
+ fs23.rmdirSync(scratch);
12214
12816
  } catch {}
12215
12817
  }
12216
12818
  removeClaudeSettings(id) {
12217
12819
  try {
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 });
12820
+ fs23.rmSync(this.claudeSettingsPath(id), { force: true });
12821
+ fs23.rmSync(this.mcpConfigPath(id), { force: true });
12822
+ fs23.rmSync(this.mcpContextPath(id), { force: true });
12823
+ fs23.rmSync(this.geminiTelemetryPath(id), { force: true });
12222
12824
  this.stopCodexAppServer(id);
12223
12825
  } catch {}
12224
12826
  }
12225
12827
  claudeSettingsPath(id) {
12226
- return path21.join(path21.dirname(this.hooks.endpointFile), `claude-${id}.json`);
12828
+ return path23.join(path23.dirname(this.hooks.endpointFile), `claude-${id}.json`);
12227
12829
  }
12228
12830
  mcpConfigPath(id) {
12229
- return path21.join(path21.dirname(this.hooks.endpointFile), `mcp-${id}.json`);
12831
+ return path23.join(path23.dirname(this.hooks.endpointFile), `mcp-${id}.json`);
12230
12832
  }
12231
12833
  mcpContextPath(id) {
12232
- return path21.join(path21.dirname(this.hooks.endpointFile), `mcp-ctx-${id}.json`);
12834
+ return path23.join(path23.dirname(this.hooks.endpointFile), `mcp-ctx-${id}.json`);
12233
12835
  }
12234
12836
  writeMcpContext(id, launchToken) {
12235
12837
  const file = this.mcpContextPath(id);
12236
12838
  try {
12237
- fs22.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
12839
+ fs23.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
12238
12840
  return file;
12239
12841
  } catch (err) {
12240
12842
  log(`mcp context write failed; session starts without axterm's tools: ${String(err)}`);
@@ -12247,7 +12849,7 @@ ${block}` : block);
12247
12849
  writeMcpConfig(id, contextPath) {
12248
12850
  const file = this.mcpConfigPath(id);
12249
12851
  try {
12250
- fs22.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
12852
+ fs23.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
12251
12853
  return file;
12252
12854
  } catch (err) {
12253
12855
  log(`mcp config write failed; session starts without axterm's tools: ${String(err)}`);
@@ -12255,18 +12857,20 @@ ${block}` : block);
12255
12857
  }
12256
12858
  }
12257
12859
  geminiTelemetryPath(id) {
12258
- return path21.join(path21.dirname(this.hooks.endpointFile), `gemini-telemetry-${id}.log`);
12860
+ return path23.join(path23.dirname(this.hooks.endpointFile), `gemini-telemetry-${id}.log`);
12259
12861
  }
12260
12862
  geminiSettingsPath() {
12261
- return path21.join(path21.dirname(this.hooks.endpointFile), "gemini-defaults.json");
12863
+ return path23.join(path23.dirname(this.hooks.endpointFile), "gemini-defaults.json");
12262
12864
  }
12263
12865
  async ptySpawner() {
12264
- if (process.env["AXTERM_PTY_HOST"] !== "1" || this.ptyHostUnavailable)
12866
+ if (!this.ptyHostPolicy.enabled || this.ptyHostUnavailable)
12265
12867
  return { spawn: spawnPty, hostOwned: false };
12266
12868
  try {
12267
12869
  if (this.ptyHost === undefined) {
12268
- const override = process.env["AXTERM_PTY_HOST_MAIN"];
12269
- const info = await ensurePtyHost({ runtimeDir: RUNTIME_DIR, ...override ? { mainScript: override } : {} });
12870
+ const info = await ensurePtyHost({
12871
+ runtimeDir: RUNTIME_DIR,
12872
+ ...this.ptyHostPolicy.mainScript ? { mainScript: this.ptyHostPolicy.mainScript } : {}
12873
+ });
12270
12874
  this.ptyHost = await connectPtyHost({ endpoint: info.endpoint, token: info.token });
12271
12875
  log(`pty host: connected (pid ${info.pid})`);
12272
12876
  }
@@ -12431,7 +13035,7 @@ ${block}` : block);
12431
13035
  this.store.updateAgentState(id, result.entry.state, "hook", now);
12432
13036
  log(`session ${info.shortId} agent ${runtime.entry?.state ?? "-"} -> ${result.entry.state}`);
12433
13037
  this.emit({ t: "agent.status", sessionId: id, state: result.entry.state, toolName: result.entry.toolName });
12434
- if (result.entry.state === "blocked" && agentProfile(agentIdFromCommand(info.agentCommand)).structuredApprovals !== true) {
13038
+ if (offersKeystrokeApproval(agentProfile(agentIdFromCommand(info.agentCommand)), result.entry.state)) {
12435
13039
  runtime.choiceSeq += 1;
12436
13040
  this.emit({
12437
13041
  t: "choice.request",
@@ -12556,7 +13160,7 @@ ${block}` : block);
12556
13160
  if (agentCommand === undefined) {
12557
13161
  throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
12558
13162
  }
12559
- if (!fs22.existsSync(stored.cwd))
13163
+ if (!fs23.existsSync(stored.cwd))
12560
13164
  throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
12561
13165
  const info = { ...stored, agentCommand, state: "running" };
12562
13166
  delete info.exitCode;
@@ -12630,7 +13234,7 @@ ${block}` : block);
12630
13234
  removeScrollback(id) {
12631
13235
  for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
12632
13236
  try {
12633
- fs22.rmSync(path21.join(SCROLLBACK_DIR, file), { force: true });
13237
+ fs23.rmSync(path23.join(SCROLLBACK_DIR, file), { force: true });
12634
13238
  } catch {}
12635
13239
  }
12636
13240
  }
@@ -12643,7 +13247,7 @@ ${block}` : block);
12643
13247
  if (runtime === undefined)
12644
13248
  return;
12645
13249
  try {
12646
- runtime.telemetryOffset = fs22.statSync(this.geminiTelemetryPath(id)).size;
13250
+ runtime.telemetryOffset = fs23.statSync(this.geminiTelemetryPath(id)).size;
12647
13251
  } catch {}
12648
13252
  }
12649
13253
  drainGeminiTelemetry() {
@@ -12656,7 +13260,7 @@ ${block}` : block);
12656
13260
  const file = this.geminiTelemetryPath(id);
12657
13261
  let size = 0;
12658
13262
  try {
12659
- size = fs22.statSync(file).size;
13263
+ size = fs23.statSync(file).size;
12660
13264
  } catch {
12661
13265
  continue;
12662
13266
  }
@@ -12752,7 +13356,7 @@ var init_session_manager = __esm(() => {
12752
13356
  init_pty_session();
12753
13357
  init_shell();
12754
13358
  init_workspace_local();
12755
- MATERIALISED_DIR = path21.join(RUNTIME_DIR, "materialised");
13359
+ MATERIALISED_DIR = path23.join(RUNTIME_DIR, "materialised");
12756
13360
  PROMPT_OPTIONS = {
12757
13361
  hostKey: [{ label: "yes" }, { label: "no" }],
12758
13362
  password: [],
@@ -12904,11 +13508,11 @@ var init_acknowledge = __esm(() => {
12904
13508
  });
12905
13509
 
12906
13510
  // packages/channel-telegram/src/attach.ts
12907
- import fs23 from "fs";
12908
- import path22 from "path";
13511
+ import fs24 from "fs";
13512
+ import path24 from "path";
12909
13513
  function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
12910
- const name = path22.basename(filePath);
12911
- const bytes = fs23.statSync(filePath).size;
13514
+ const name = path24.basename(filePath);
13515
+ const bytes = fs24.statSync(filePath).size;
12912
13516
  const plan = planAttachment({ name, bytes }, limits);
12913
13517
  if (plan.kind === "rejected")
12914
13518
  throw new Error(plan.reason);
@@ -12918,7 +13522,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
12918
13522
  form.append("message_thread_id", String(topicId));
12919
13523
  if (caption !== undefined && caption.length > 0)
12920
13524
  form.append("caption", caption);
12921
- form.append(field, new Blob([fs23.readFileSync(filePath)]), name);
13525
+ form.append(field, new Blob([fs24.readFileSync(filePath)]), name);
12922
13526
  return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
12923
13527
  }
12924
13528
  function isPhotoRejection(error) {
@@ -12926,13 +13530,13 @@ function isPhotoRejection(error) {
12926
13530
  return /IMAGE_PROCESS_FAILED|PHOTO_INVALID_DIMENSIONS|PHOTO_EXT_INVALID|PHOTO_SAVE_FILE_INVALID/i.test(message);
12927
13531
  }
12928
13532
  function asDocumentUpload(filePath, chatId, topicId, caption) {
12929
- const name = path22.basename(filePath);
13533
+ const name = path24.basename(filePath);
12930
13534
  const form = new FormData;
12931
13535
  form.append("chat_id", String(chatId));
12932
13536
  form.append("message_thread_id", String(topicId));
12933
13537
  if (caption !== undefined && caption.length > 0)
12934
13538
  form.append("caption", caption);
12935
- form.append("document", new Blob([fs23.readFileSync(filePath)]), name);
13539
+ form.append("document", new Blob([fs24.readFileSync(filePath)]), name);
12936
13540
  return { method: "sendDocument", field: "document", form };
12937
13541
  }
12938
13542
  var TELEGRAM_LIMITS;
@@ -13002,6 +13606,31 @@ function planScreenDelivery(text, maxMessages) {
13002
13606
  const chunks = preChunks(text);
13003
13607
  return chunks.length <= maxMessages ? { kind: "messages", chunks } : { kind: "file", text };
13004
13608
  }
13609
+ function diffQuote(summary, limit = TELEGRAM_TEXT_LIMIT, reserved = 0) {
13610
+ const OPEN = "<blockquote expandable><pre>";
13611
+ const CLOSE = "</pre></blockquote>";
13612
+ const budget = limit - reserved - OPEN.length - CLOSE.length;
13613
+ const cut = (total) => escapeHtml(`\u2026 truncated, ${total} characters in the full patch`);
13614
+ const rendered = [];
13615
+ let used = 0;
13616
+ let stopped = false;
13617
+ for (const line of summary.lines) {
13618
+ const marker = line.kind === "add" ? "+" : line.kind === "del" ? "-" : "";
13619
+ const escaped = escapeHtml(`${marker}${line.text}`);
13620
+ if (used + escaped.length + 1 + cut(summary.totalChars).length > budget) {
13621
+ stopped = true;
13622
+ break;
13623
+ }
13624
+ rendered.push(escaped);
13625
+ used += escaped.length + 1;
13626
+ }
13627
+ if (rendered.length === 0)
13628
+ return "";
13629
+ if (stopped || summary.truncated !== undefined)
13630
+ rendered.push(cut(summary.totalChars));
13631
+ return `${OPEN}${rendered.join(`
13632
+ `)}${CLOSE}`;
13633
+ }
13005
13634
  function parseScreenLines(args) {
13006
13635
  const raw = args[0];
13007
13636
  if (raw === undefined || !/^-?\d+$/.test(raw))
@@ -13476,7 +14105,7 @@ var init_topic_title = __esm(() => {
13476
14105
  });
13477
14106
 
13478
14107
  // packages/channel-telegram/src/adapter.ts
13479
- import { setTimeout as delay3 } from "timers/promises";
14108
+ import { setTimeout as delay4 } from "timers/promises";
13480
14109
  function readMedia(msg) {
13481
14110
  const photo = msg.photo?.at(-1);
13482
14111
  if (photo) {
@@ -13512,6 +14141,7 @@ class TelegramChannel {
13512
14141
  titleFlushes = new Map;
13513
14142
  flows = new Map;
13514
14143
  outbound = Promise.resolve();
14144
+ username;
13515
14145
  polling = false;
13516
14146
  stopped = false;
13517
14147
  pollAbort;
@@ -13696,7 +14326,16 @@ class TelegramChannel {
13696
14326
  `${d.stat.filesChanged} files +${d.stat.insertions} -${d.stat.deletions}`,
13697
14327
  ...d.files.map((f) => `\u2022 ${f}`)
13698
14328
  ];
13699
- for (const message of htmlLines(lines)) {
14329
+ const messages = htmlLines(lines);
14330
+ const summary = d.patch === undefined ? undefined : summarizePatch(d.patch);
14331
+ const last = messages.at(-1);
14332
+ if (summary !== undefined && last !== undefined) {
14333
+ const quote = diffQuote(summary, TELEGRAM_TEXT_LIMIT, last.length + 1);
14334
+ if (quote !== "")
14335
+ messages[messages.length - 1] = `${last}
14336
+ ${quote}`;
14337
+ }
14338
+ for (const message of messages) {
13700
14339
  await this.sendToSession(sessionId, message);
13701
14340
  }
13702
14341
  }
@@ -14119,8 +14758,8 @@ ${code(prompt.line)}`,
14119
14758
  throw new Error("this Telegram client cannot download files");
14120
14759
  const files = [];
14121
14760
  for (const item of batch.items) {
14122
- const path23 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
14123
- files.push({ path: path23, kind: item.kind });
14761
+ const path25 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
14762
+ files.push({ path: path25, kind: item.kind });
14124
14763
  }
14125
14764
  await this.control.prompt(batch.sessionId, buildAttachmentPrompt(files, batch.caption));
14126
14765
  this.log(`staged ${files.length} inbound file(s) for ${batch.sessionId}`);
@@ -14135,7 +14774,7 @@ ${code(prompt.line)}`,
14135
14774
  async settleInboundForTest() {
14136
14775
  while (this.albums.size > 0 || this.inboundQueues.size > 0) {
14137
14776
  if (this.albums.size > 0)
14138
- await delay3(this.albumDebounceMs + 5);
14777
+ await delay4(this.albumDebounceMs + 5);
14139
14778
  await Promise.all([...this.inboundQueues.values()]);
14140
14779
  }
14141
14780
  }
@@ -14144,10 +14783,22 @@ ${code(prompt.line)}`,
14144
14783
  return;
14145
14784
  await this.api.call("setMessageReaction", { chat_id: chatId, message_id: messageId, reaction: reactionPayload(state) }).catch(() => {});
14146
14785
  }
14786
+ async selfUsername() {
14787
+ if (this.username !== undefined)
14788
+ return this.username;
14789
+ try {
14790
+ const me = await this.api.call("getMe");
14791
+ if (typeof me.username === "string" && me.username.length > 0)
14792
+ this.username = me.username;
14793
+ } catch {}
14794
+ return this.username;
14795
+ }
14147
14796
  async handleCommand(text, binding, msg) {
14148
14797
  const [rawCmd = "", ...args] = text.split(/\s+/);
14149
14798
  const rawTail = text.slice(rawCmd.length);
14150
- const cmd = rawCmd.replace(/@\w+$/, "");
14799
+ const { command: cmd, target } = parseCommandAddress(rawCmd);
14800
+ if (target !== undefined && !commandIsForBot(target, await this.selfUsername()))
14801
+ return;
14151
14802
  if (binding) {
14152
14803
  switch (cmd) {
14153
14804
  case "/esc":
@@ -14303,13 +14954,13 @@ ${code(prompt.line)}`,
14303
14954
  const project = await resolveProject(args[0]);
14304
14955
  if (!project)
14305
14956
  return;
14306
- const target = args[1];
14307
- if (target === undefined) {
14957
+ const target2 = args[1];
14958
+ if (target2 === undefined) {
14308
14959
  await reply(t("tg.usage.rm"));
14309
14960
  return;
14310
14961
  }
14311
- const result = await this.control.removeWorktree(project.alias, target, args[2] === "force");
14312
- await reply(result.removed ? t("tg.worktree.removed", { target }) : t("tg.worktree.refused", { reason: String(result.reason) }));
14962
+ const result = await this.control.removeWorktree(project.alias, target2, args[2] === "force");
14963
+ await reply(result.removed ? t("tg.worktree.removed", { target: target2 }) : t("tg.worktree.refused", { reason: String(result.reason) }));
14313
14964
  return;
14314
14965
  }
14315
14966
  case "/sessions": {
@@ -14407,8 +15058,12 @@ ${code(prompt.line)}`,
14407
15058
  return;
14408
15059
  }
14409
15060
  if (flow.askId === undefined) {
14410
- const keys2 = agentProfile(agentIdFromCommand(session.agentCommand)).approvalKeys;
14411
- this.control.write(session.id, keys2 !== undefined && decoded.optionIndex <= 1 ? decoded.optionIndex === 0 ? keys2.allow : keys2.deny : buildMenuSelectKeys(decoded.optionIndex));
15061
+ const keys2 = menuChoiceKeys(agentProfile(agentIdFromCommand(session.agentCommand)), decoded.optionIndex);
15062
+ if (keys2 === undefined) {
15063
+ await answer(t("tg.answer.noSafeKey"));
15064
+ return;
15065
+ }
15066
+ this.control.write(session.id, keys2);
14412
15067
  }
14413
15068
  flow.answers.push({ questionIndex: flow.cursor, selected: [label] });
14414
15069
  flow.cursor += 1;
@@ -14461,6 +15116,11 @@ ${code(prompt.line)}`,
14461
15116
  clearTimeout(album.timer);
14462
15117
  this.albums.clear();
14463
15118
  }
15119
+ pollHealth() {
15120
+ return this.pollError;
15121
+ }
15122
+ pollError;
15123
+ pollWarned = false;
14464
15124
  async pollLoop() {
14465
15125
  while (this.polling) {
14466
15126
  try {
@@ -14471,6 +15131,10 @@ ${code(prompt.line)}`,
14471
15131
  });
14472
15132
  if (this.stopped)
14473
15133
  return;
15134
+ if (this.pollError !== undefined)
15135
+ this.log("polling recovered");
15136
+ this.pollError = undefined;
15137
+ this.pollWarned = false;
14474
15138
  for (const update2 of updates) {
14475
15139
  this.updateOffset = Math.max(this.updateOffset, update2.update_id + 1);
14476
15140
  await this.handleUpdate(update2);
@@ -14480,11 +15144,29 @@ ${code(prompt.line)}`,
14480
15144
  } catch (err) {
14481
15145
  if (!this.polling)
14482
15146
  return;
14483
- this.log(`poll failed: ${String(err)}`);
14484
- await delay3(3000);
15147
+ const reason = String(err);
15148
+ this.pollError = reason;
15149
+ this.log(`poll failed: ${reason}`);
15150
+ if (!this.pollWarned) {
15151
+ this.pollWarned = true;
15152
+ this.warnPollFailure(reason);
15153
+ }
15154
+ await delay4(3000);
14485
15155
  }
14486
15156
  }
14487
15157
  }
15158
+ async warnPollFailure(reason) {
15159
+ const conflict = /\b409\b|terminated by other getUpdates/i.test(reason);
15160
+ const text = conflict ? tHtml("tg.poll.conflict", { machine: this.config.machine.name }) : tHtml("tg.poll.failed", { machine: this.config.machine.name, reason: oneLineReason(reason) });
15161
+ try {
15162
+ await this.api.call("sendMessage", { chat_id: this.config.groupId, text, parse_mode: "HTML" });
15163
+ } catch (err) {
15164
+ this.log(`could not report the poll failure: ${String(err)}`);
15165
+ }
15166
+ }
15167
+ }
15168
+ function oneLineReason(reason) {
15169
+ return reason.replace(/\s+/g, " ").trim().slice(0, 200);
14488
15170
  }
14489
15171
  var SCREEN_MESSAGE_BUDGET = 2, AGENT_ERROR_CHARS = 1200, RAW_TYPE_LIMIT = 500, RAW_SCREEN_LINES = 12, HELP_KEYS, helpText = () => HELP_KEYS.map((key) => escapeHtml(t(key))).join(`
14490
15172
  `), ALBUM_DEBOUNCE_MS = 2000, UNKNOWN_SIZE = 1, REFUSAL_KEYS, megabytes2 = (bytes) => `${Math.round(bytes / (1024 * 1024))} MB`;
@@ -14521,13 +15203,13 @@ var init_src2 = __esm(() => {
14521
15203
  });
14522
15204
 
14523
15205
  // packages/daemon/src/machine-identity.ts
14524
- import os8 from "os";
15206
+ import os9 from "os";
14525
15207
  function resolveMachineIdentity(override) {
14526
- const hostname = os8.hostname();
15208
+ const hostname = os9.hostname();
14527
15209
  return {
14528
15210
  name: slugifyMachineName(override && override.trim().length > 0 ? override : hostname),
14529
15211
  hostname,
14530
- addresses: collectAddresses(os8.networkInterfaces())
15212
+ addresses: collectAddresses(os9.networkInterfaces())
14531
15213
  };
14532
15214
  }
14533
15215
  var init_machine_identity = __esm(() => {
@@ -14719,6 +15401,9 @@ class TelegramService {
14719
15401
  problems.push(t("doctor.noUser"));
14720
15402
  if (problems.length > 0)
14721
15403
  return { ok: false, problems };
15404
+ const pollError = this.channel?.pollHealth();
15405
+ if (pollError !== undefined)
15406
+ problems.push(t("doctor.telegram.pollFailing", { reason: pollError.replace(/\s+/g, " ").slice(0, 160) }));
14722
15407
  const api = new FetchTelegramApi(s.botToken);
14723
15408
  const report = { ok: true, problems };
14724
15409
  try {
@@ -14759,7 +15444,7 @@ var init_telegram_service = __esm(() => {
14759
15444
  // packages/daemon/src/index.ts
14760
15445
  var exports_src = {};
14761
15446
  import { randomBytes as randomBytes3 } from "crypto";
14762
- import fs24 from "fs";
15447
+ import fs25 from "fs";
14763
15448
  import net5 from "net";
14764
15449
  function isPidAlive(pid) {
14765
15450
  try {
@@ -14771,9 +15456,9 @@ function isPidAlive(pid) {
14771
15456
  }
14772
15457
  async function main() {
14773
15458
  ensureDataDirs();
14774
- if (fs24.existsSync(RUNTIME_FILE2)) {
15459
+ if (fs25.existsSync(RUNTIME_FILE2)) {
14775
15460
  try {
14776
- const existing = JSON.parse(fs24.readFileSync(RUNTIME_FILE2, "utf8"));
15461
+ const existing = JSON.parse(fs25.readFileSync(RUNTIME_FILE2, "utf8"));
14777
15462
  if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
14778
15463
  log(`daemon already running (pid ${existing.pid}); exiting`);
14779
15464
  process.exit(0);
@@ -14796,7 +15481,7 @@ async function main() {
14796
15481
  version: productVersion(),
14797
15482
  startedAt: Date.now()
14798
15483
  };
14799
- fs24.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
15484
+ fs25.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
14800
15485
  restrictFile(RUNTIME_FILE2);
14801
15486
  const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
14802
15487
  if (config.loadError !== undefined)
@@ -14848,9 +15533,9 @@ async function main() {
14848
15533
  hookHost.stop();
14849
15534
  store.close();
14850
15535
  try {
14851
- const current = JSON.parse(fs24.readFileSync(RUNTIME_FILE2, "utf8"));
15536
+ const current = JSON.parse(fs25.readFileSync(RUNTIME_FILE2, "utf8"));
14852
15537
  if (current.pid === process.pid)
14853
- fs24.rmSync(RUNTIME_FILE2);
15538
+ fs25.rmSync(RUNTIME_FILE2);
14854
15539
  } catch {}
14855
15540
  process.exit(0);
14856
15541
  };
@@ -14869,12 +15554,9 @@ async function main() {
14869
15554
  });
14870
15555
  process.on("SIGINT", () => stop("SIGINT"));
14871
15556
  process.on("SIGTERM", () => stop("SIGTERM"));
14872
- process.on("uncaughtException", (err) => {
14873
- log(`uncaughtException: ${err.stack ?? String(err)}`);
14874
- stop("uncaughtException");
14875
- });
14876
15557
  log(`daemon started pid=${process.pid} config=${CONFIG_DIR} state=${STATE_DIR}`);
14877
15558
  }
15559
+ var crashGuards;
14878
15560
  var init_src3 = __esm(() => {
14879
15561
  init_src();
14880
15562
  init_config_migrate();
@@ -14887,7 +15569,12 @@ var init_src3 = __esm(() => {
14887
15569
  init_telegram_service();
14888
15570
  init_store();
14889
15571
  net5.setDefaultAutoSelectFamilyAttemptTimeout(3000);
14890
- main();
15572
+ crashGuards = new ProcessCrashGuards({
15573
+ log,
15574
+ exit: (code2) => process.exit(code2)
15575
+ });
15576
+ crashGuards.install(process);
15577
+ main().catch((reason) => crashGuards.startupFailed(reason));
14891
15578
  });
14892
15579
 
14893
15580
  // packages/daemon/src/pty-host.ts
@@ -15097,8 +15784,8 @@ var init_pty_host_main = __esm(() => {
15097
15784
 
15098
15785
  // packages/cli/src/index.ts
15099
15786
  init_src();
15100
- import fs25 from "fs";
15101
- import path23 from "path";
15787
+ import fs26 from "fs";
15788
+ import path25 from "path";
15102
15789
 
15103
15790
  // packages/cli/src/attach.ts
15104
15791
  import fs from "fs";
@@ -15517,11 +16204,11 @@ ${bold(t(state.promptKey))} ${dim("[yes]")} `);
15517
16204
  const finished = advance({ ...state, kind: "review", step: "review" }, "");
15518
16205
  if (!("done" in finished))
15519
16206
  return { saved: false };
15520
- for (const [path3, value] of finished.done.settings) {
15521
- await client.call("config.set", { path: path3, value });
16207
+ for (const [path4, value] of finished.done.settings) {
16208
+ await client.call("config.set", { path: path4, value });
15522
16209
  }
15523
- for (const [path3, value] of finished.done.secrets) {
15524
- await client.call("config.set", { path: path3, value });
16210
+ for (const [path4, value] of finished.done.secrets) {
16211
+ await client.call("config.set", { path: path4, value });
15525
16212
  }
15526
16213
  stdout.write(`
15527
16214
  ${green("\u2713")} ${t("wizard.saved")}
@@ -15559,10 +16246,202 @@ function describeHealth(health) {
15559
16246
  // packages/cli/src/client.ts
15560
16247
  init_src();
15561
16248
  import { spawn } from "child_process";
15562
- import fs2 from "fs";
16249
+ import fs3 from "fs";
15563
16250
  import net from "net";
15564
- import path3 from "path";
16251
+ import path5 from "path";
15565
16252
  import { setTimeout as delay } from "timers/promises";
16253
+
16254
+ // packages/cli/src/service-manager.ts
16255
+ init_src();
16256
+ import { spawnSync } from "child_process";
16257
+ import fs2 from "fs";
16258
+ import os3 from "os";
16259
+ import path4 from "path";
16260
+
16261
+ class ServiceCommandError extends Error {
16262
+ invocation;
16263
+ result;
16264
+ constructor(invocation, result) {
16265
+ super(result.stderr.trim() || result.stdout.trim() || `${invocation.command} exited ${result.status}`);
16266
+ this.invocation = invocation;
16267
+ this.result = result;
16268
+ this.name = "ServiceCommandError";
16269
+ }
16270
+ }
16271
+ var defaultRun = ({ command, args }) => {
16272
+ const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
16273
+ return {
16274
+ status: result.status ?? 1,
16275
+ stdout: result.stdout ?? "",
16276
+ stderr: result.stderr ?? result.error?.message ?? ""
16277
+ };
16278
+ };
16279
+ function writeAtomic(file, contents) {
16280
+ fs2.mkdirSync(path4.dirname(file), { recursive: true, mode: 448 });
16281
+ const tmp = `${file}.tmp`;
16282
+ try {
16283
+ fs2.writeFileSync(tmp, contents, { mode: 384 });
16284
+ fs2.renameSync(tmp, file);
16285
+ if (process.platform !== "win32")
16286
+ fs2.chmodSync(file, 384);
16287
+ } catch (err) {
16288
+ try {
16289
+ fs2.rmSync(tmp, { force: true });
16290
+ } catch {}
16291
+ throw err;
16292
+ }
16293
+ }
16294
+ function utf16Xml(contents) {
16295
+ return Buffer.concat([Buffer.from([255, 254]), Buffer.from(contents, "utf16le")]);
16296
+ }
16297
+ function utf8Script(contents) {
16298
+ return Buffer.concat([Buffer.from([239, 187, 191]), Buffer.from(contents, "utf8")]);
16299
+ }
16300
+
16301
+ class ServiceManager {
16302
+ options;
16303
+ definition;
16304
+ run;
16305
+ uid;
16306
+ constructor(options) {
16307
+ this.options = options;
16308
+ this.run = options.run ?? defaultRun;
16309
+ this.uid = options.uid ?? process.getuid?.();
16310
+ this.definition = serviceDefinition({
16311
+ platform: options.platform,
16312
+ launch: options.launch,
16313
+ homeDir: options.homeDir,
16314
+ stateDir: options.stateDir,
16315
+ ...options.userId ? { userId: options.userId } : {},
16316
+ ...options.environment ? { environment: options.environment } : {}
16317
+ });
16318
+ }
16319
+ install() {
16320
+ fs2.mkdirSync(path4.join(this.options.stateDir, "logs"), { recursive: true, mode: 448 });
16321
+ for (const companion of this.definition.companionFiles ?? []) {
16322
+ writeAtomic(companion.file, this.definition.kind === "task-scheduler" ? utf8Script(companion.contents) : companion.contents);
16323
+ }
16324
+ writeAtomic(this.definition.file, this.definition.kind === "task-scheduler" ? utf16Xml(this.definition.contents) : this.definition.contents);
16325
+ if (this.definition.kind === "systemd") {
16326
+ this.must({ command: "systemctl", args: ["--user", "daemon-reload"] });
16327
+ this.must({ command: "systemctl", args: ["--user", "enable", SYSTEMD_SERVICE] });
16328
+ } else if (this.definition.kind === "launchd") {
16329
+ this.must({ command: "launchctl", args: ["enable", this.launchdTarget()] });
16330
+ } else {
16331
+ this.must({ command: "schtasks.exe", args: ["/Create", "/TN", WINDOWS_TASK, "/XML", this.definition.file, "/F"] });
16332
+ }
16333
+ }
16334
+ start() {
16335
+ if (this.definition.kind === "systemd") {
16336
+ this.must({ command: "systemctl", args: ["--user", "start", SYSTEMD_SERVICE] });
16337
+ } else if (this.definition.kind === "launchd") {
16338
+ const target = this.launchdTarget();
16339
+ const loaded = this.run({ command: "launchctl", args: ["print", target] }).status === 0;
16340
+ this.must(loaded ? { command: "launchctl", args: ["kickstart", target] } : { command: "launchctl", args: ["bootstrap", `gui/${this.requireUid()}`, this.definition.file] });
16341
+ } else {
16342
+ this.must({ command: "schtasks.exe", args: ["/Run", "/TN", WINDOWS_TASK] });
16343
+ }
16344
+ }
16345
+ status() {
16346
+ if (this.definition.kind === "systemd") {
16347
+ return {
16348
+ kind: this.definition.kind,
16349
+ installed: fs2.existsSync(this.definition.file),
16350
+ active: this.run({ command: "systemctl", args: ["--user", "is-active", "--quiet", SYSTEMD_SERVICE] }).status === 0
16351
+ };
16352
+ }
16353
+ if (this.definition.kind === "launchd") {
16354
+ return {
16355
+ kind: this.definition.kind,
16356
+ installed: fs2.existsSync(this.definition.file),
16357
+ active: this.run({ command: "launchctl", args: ["print", this.launchdTarget()] }).status === 0
16358
+ };
16359
+ }
16360
+ const installed = this.run({ command: "schtasks.exe", args: ["/Query", "/TN", WINDOWS_TASK] }).status === 0;
16361
+ const active = installed && this.run({
16362
+ command: "powershell.exe",
16363
+ args: [
16364
+ "-NoProfile",
16365
+ "-NonInteractive",
16366
+ "-Command",
16367
+ `if ((Get-ScheduledTask -TaskName '${WINDOWS_TASK}').State -eq 'Running') { exit 0 } else { exit 1 }`
16368
+ ]
16369
+ }).status === 0;
16370
+ return { kind: this.definition.kind, installed, active };
16371
+ }
16372
+ uninstall() {
16373
+ if (this.definition.kind === "systemd") {
16374
+ this.run({ command: "systemctl", args: ["--user", "disable", SYSTEMD_SERVICE] });
16375
+ fs2.rmSync(this.definition.file, { force: true });
16376
+ this.must({ command: "systemctl", args: ["--user", "daemon-reload"] });
16377
+ } else if (this.definition.kind === "launchd") {
16378
+ this.run({ command: "launchctl", args: ["disable", this.launchdTarget()] });
16379
+ fs2.rmSync(this.definition.file, { force: true });
16380
+ } else {
16381
+ this.run({ command: "schtasks.exe", args: ["/Delete", "/TN", WINDOWS_TASK, "/F"] });
16382
+ fs2.rmSync(this.definition.file, { force: true });
16383
+ }
16384
+ for (const companion of this.definition.companionFiles ?? [])
16385
+ fs2.rmSync(companion.file, { force: true });
16386
+ }
16387
+ must(command) {
16388
+ const result = this.run(command);
16389
+ if (result.status !== 0)
16390
+ throw new ServiceCommandError(command, result);
16391
+ }
16392
+ requireUid() {
16393
+ if (this.uid === undefined)
16394
+ throw new Error("launchd requires the current uid");
16395
+ return this.uid;
16396
+ }
16397
+ launchdTarget() {
16398
+ return `gui/${this.requireUid()}/${LAUNCHD_LABEL}`;
16399
+ }
16400
+ }
16401
+ function windowsUserId(run = defaultRun) {
16402
+ const command = {
16403
+ command: "powershell.exe",
16404
+ args: [
16405
+ "-NoProfile",
16406
+ "-NonInteractive",
16407
+ "-Command",
16408
+ "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name"
16409
+ ]
16410
+ };
16411
+ const result = run(command);
16412
+ const value = result.stdout.trim();
16413
+ if (result.status !== 0 || value === "")
16414
+ throw new ServiceCommandError(command, result);
16415
+ return value;
16416
+ }
16417
+ function startInstalledService(manager) {
16418
+ if (!manager.status().installed)
16419
+ return false;
16420
+ manager.start();
16421
+ return true;
16422
+ }
16423
+ function currentServiceManager(run = defaultRun) {
16424
+ const paths = resolvePaths(currentPathEnv());
16425
+ const environment = {};
16426
+ if (process.env["AXTERM_CONFIG_DIR"] === paths.configDir)
16427
+ environment["AXTERM_CONFIG_DIR"] = paths.configDir;
16428
+ if (process.env["AXTERM_STATE_DIR"] === paths.stateDir)
16429
+ environment["AXTERM_STATE_DIR"] = paths.stateDir;
16430
+ if (process.env["AXTERM_RUNTIME_DIR"] === paths.runtimeDir)
16431
+ environment["AXTERM_RUNTIME_DIR"] = paths.runtimeDir;
16432
+ return new ServiceManager({
16433
+ platform: process.platform,
16434
+ homeDir: os3.homedir(),
16435
+ stateDir: paths.stateDir,
16436
+ launch: daemonLaunch({ execPath: process.execPath, mainScript: Bun.main }),
16437
+ ...process.getuid ? { uid: process.getuid() } : {},
16438
+ ...process.platform === "win32" ? { userId: windowsUserId(run) } : {},
16439
+ ...Object.keys(environment).length > 0 ? { environment } : {},
16440
+ run
16441
+ });
16442
+ }
16443
+
16444
+ // packages/cli/src/client.ts
15566
16445
  var RUNTIME_FILE = resolvePaths(currentPathEnv()).runtimeFile;
15567
16446
 
15568
16447
  class RpcClient {
@@ -15648,7 +16527,7 @@ class RpcClient {
15648
16527
  }
15649
16528
  function readRuntime() {
15650
16529
  try {
15651
- const runtime = JSON.parse(fs2.readFileSync(RUNTIME_FILE, "utf8"));
16530
+ const runtime = JSON.parse(fs3.readFileSync(RUNTIME_FILE, "utf8"));
15652
16531
  process.kill(runtime.pid, 0);
15653
16532
  return runtime;
15654
16533
  } catch {
@@ -15667,11 +16546,14 @@ async function tryConnect() {
15667
16546
  return;
15668
16547
  }
15669
16548
  }
16549
+ function connectDaemonIfRunning() {
16550
+ return tryConnect();
16551
+ }
15670
16552
  function daemonEnv(cwd, env) {
15671
16553
  const files = [];
15672
16554
  for (const name of dotenvCandidates(env["NODE_ENV"])) {
15673
16555
  try {
15674
- files.push(parseDotenv(fs2.readFileSync(path3.join(cwd, name), "utf8")));
16556
+ files.push(parseDotenv(fs3.readFileSync(path5.join(cwd, name), "utf8")));
15675
16557
  } catch {}
15676
16558
  }
15677
16559
  return scrubDotenvKeys(env, files);
@@ -15680,14 +16562,26 @@ async function ensureDaemon() {
15680
16562
  const existing = await tryConnect();
15681
16563
  if (existing)
15682
16564
  return existing;
15683
- const launch = daemonLaunch({ execPath: process.execPath, mainScript: Bun.main });
15684
- const child = spawn(launch.command, launch.args, {
15685
- detached: true,
15686
- stdio: "ignore",
15687
- windowsHide: true,
15688
- env: daemonEnv(process.cwd(), process.env)
15689
- });
15690
- child.unref();
16565
+ let supervised = false;
16566
+ try {
16567
+ supervised = startInstalledService(currentServiceManager());
16568
+ } catch (err) {
16569
+ if (err instanceof ServiceCommandError) {
16570
+ const command = [err.invocation.command, ...err.invocation.args].join(" ");
16571
+ throw new Error(t("cli.service.commandFailed", { command, error: err.message }));
16572
+ }
16573
+ throw err;
16574
+ }
16575
+ if (!supervised) {
16576
+ const launch = daemonLaunch({ execPath: process.execPath, mainScript: Bun.main });
16577
+ const child = spawn(launch.command, launch.args, {
16578
+ detached: true,
16579
+ stdio: "ignore",
16580
+ windowsHide: true,
16581
+ env: daemonEnv(process.cwd(), process.env)
16582
+ });
16583
+ child.unref();
16584
+ }
15691
16585
  const deadline = Date.now() + 15000;
15692
16586
  while (Date.now() < deadline) {
15693
16587
  await delay(300);
@@ -15695,12 +16589,12 @@ async function ensureDaemon() {
15695
16589
  if (client)
15696
16590
  return client;
15697
16591
  }
15698
- throw new Error(t("cli.daemon.notReady", { log: path3.join(resolvePaths(currentPathEnv()).logDir, "daemon.log") }));
16592
+ throw new Error(t("cli.daemon.notReady", { log: path5.join(resolvePaths(currentPathEnv()).logDir, "daemon.log") }));
15699
16593
  }
15700
16594
 
15701
16595
  // packages/cli/src/hook-bridge.ts
15702
16596
  init_src();
15703
- import fs3 from "fs";
16597
+ import fs4 from "fs";
15704
16598
  var POST_TIMEOUT_MS = 1500;
15705
16599
  function isCodexApproval(agent, body) {
15706
16600
  if (agent !== "codex")
@@ -15753,7 +16647,7 @@ function currentEndpoint() {
15753
16647
  if (file === undefined)
15754
16648
  return;
15755
16649
  try {
15756
- return parseHookEndpoint(fs3.readFileSync(file, "utf8"));
16650
+ return parseHookEndpoint(fs4.readFileSync(file, "utf8"));
15757
16651
  } catch {
15758
16652
  return;
15759
16653
  }
@@ -15761,8 +16655,8 @@ function currentEndpoint() {
15761
16655
 
15762
16656
  // packages/cli/src/mcp-server.ts
15763
16657
  init_src();
15764
- import fs4 from "fs";
15765
- import path4 from "path";
16658
+ import fs5 from "fs";
16659
+ import path6 from "path";
15766
16660
  var ASK_TIMEOUT_MS = 10 * 60000;
15767
16661
  var POST_TIMEOUT_MS2 = 5000;
15768
16662
  var TOOLS = [
@@ -15878,7 +16772,7 @@ class McpServer {
15878
16772
  const contextFile = this.io.env[MCP_CONTEXT_ENV];
15879
16773
  if (contextFile !== undefined) {
15880
16774
  try {
15881
- const context = JSON.parse(fs4.readFileSync(contextFile, "utf8"));
16775
+ const context = JSON.parse(fs5.readFileSync(contextFile, "utf8"));
15882
16776
  if (context.port && context.token && context.sessionId) {
15883
16777
  return {
15884
16778
  url: `http://127.0.0.1:${context.port}`,
@@ -15923,7 +16817,7 @@ class McpServer {
15923
16817
  const target = this.target();
15924
16818
  if (!target)
15925
16819
  return { text: "axterm is not reachable from this session", isError: true };
15926
- const resolved = path4.isAbsolute(file) ? file : path4.resolve(this.io.cwd, file);
16820
+ const resolved = path6.isAbsolute(file) ? file : path6.resolve(this.io.cwd, file);
15927
16821
  const caption = typeof args["caption"] === "string" ? args["caption"] : undefined;
15928
16822
  const res = await this.post("/hook/attach", { sessionId: target.sessionId, launchToken: target.launchToken, path: resolved, caption }, POST_TIMEOUT_MS2);
15929
16823
  if (!res)
@@ -16033,15 +16927,15 @@ function runMcpServer() {
16033
16927
 
16034
16928
  // packages/cli/src/update.ts
16035
16929
  init_src();
16036
- import { spawnSync } from "child_process";
16930
+ import { spawnSync as spawnSync2 } from "child_process";
16037
16931
  import { createHash as createHash2 } from "crypto";
16038
- import fs5 from "fs";
16039
- import path5 from "path";
16932
+ import fs6 from "fs";
16933
+ import path7 from "path";
16040
16934
  import { stdin as stdin2, stdout as stdout2 } from "process";
16041
16935
  // packages/cli/package.json
16042
16936
  var package_default = {
16043
16937
  name: "@layorixinc/axterm",
16044
- version: "0.6.0",
16938
+ version: "0.6.1",
16045
16939
  type: "module",
16046
16940
  bin: {
16047
16941
  axterm: "dist/cli.js"
@@ -16115,13 +17009,13 @@ function assetName(platform, arch, isMusl) {
16115
17009
  function usesMusl() {
16116
17010
  if (process.platform !== "linux")
16117
17011
  return false;
16118
- if (fs5.existsSync("/etc/alpine-release"))
17012
+ if (fs6.existsSync("/etc/alpine-release"))
16119
17013
  return true;
16120
- const ldd = spawnSync("ldd", ["--version"], { encoding: "utf8" });
17014
+ const ldd = spawnSync2("ldd", ["--version"], { encoding: "utf8" });
16121
17015
  return `${ldd.stdout ?? ""}${ldd.stderr ?? ""}`.toLowerCase().includes("musl");
16122
17016
  }
16123
17017
  function firstLine(command, args) {
16124
- const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
17018
+ const result = spawnSync2(command, args, { encoding: "utf8", windowsHide: true });
16125
17019
  if (result.status !== 0)
16126
17020
  return;
16127
17021
  return result.stdout?.trim().split(/\r?\n/)[0]?.trim() || undefined;
@@ -16129,7 +17023,7 @@ function firstLine(command, args) {
16129
17023
  function detectOrigin(execPath) {
16130
17024
  const resolved = (() => {
16131
17025
  try {
16132
- return fs5.realpathSync(execPath);
17026
+ return fs6.realpathSync(execPath);
16133
17027
  } catch {
16134
17028
  return execPath;
16135
17029
  }
@@ -16154,7 +17048,7 @@ function npmGlobalBin() {
16154
17048
  const prefix = firstLine("npm", ["prefix", "-g"]);
16155
17049
  if (prefix === undefined)
16156
17050
  return;
16157
- return process.platform === "win32" ? prefix : path5.join(prefix, "bin");
17051
+ return process.platform === "win32" ? prefix : path7.join(prefix, "bin");
16158
17052
  }
16159
17053
  async function download(url, into) {
16160
17054
  const response = await fetch(url, { signal: AbortSignal.timeout(600000) });
@@ -16171,7 +17065,7 @@ function checksumFor(sums, asset) {
16171
17065
  return;
16172
17066
  }
16173
17067
  function sha256(file) {
16174
- return createHash2("sha256").update(fs5.readFileSync(file)).digest("hex");
17068
+ return createHash2("sha256").update(fs6.readFileSync(file)).digest("hex");
16175
17069
  }
16176
17070
  function ask2(question) {
16177
17071
  if (stdin2.isTTY !== true)
@@ -16218,8 +17112,8 @@ async function runUpdate(client, current, options) {
16218
17112
  }
16219
17113
  const asset = assetName(process.platform, process.arch, usesMusl());
16220
17114
  const base = `${RELEASES}/v${latest.version}`;
16221
- const staging = fs5.mkdtempSync(`${process.execPath}.update-`);
16222
- const downloaded = path5.join(staging, asset);
17115
+ const staging = fs6.mkdtempSync(`${process.execPath}.update-`);
17116
+ const downloaded = path7.join(staging, asset);
16223
17117
  try {
16224
17118
  console.log(t("cli.update.downloading", { asset }));
16225
17119
  await download(`${base}/${asset}`, downloaded);
@@ -16232,8 +17126,8 @@ async function runUpdate(client, current, options) {
16232
17126
  return;
16233
17127
  }
16234
17128
  if (process.platform !== "win32")
16235
- fs5.chmodSync(downloaded, 493);
16236
- const reported = spawnSync(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
17129
+ fs6.chmodSync(downloaded, 493);
17130
+ const reported = spawnSync2(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
16237
17131
  if (reported !== latest.version) {
16238
17132
  console.error(t("cli.update.wrongVersion", { got: reported ?? "nothing", want: latest.version }));
16239
17133
  process.exitCode = 1;
@@ -16265,14 +17159,14 @@ async function runUpdate(client, current, options) {
16265
17159
  if (plan.stale > 0)
16266
17160
  console.log(t("cli.update.staleDaemon", { count: plan.stale }));
16267
17161
  for (const id of plan.resume) {
16268
- const result = spawnSync(process.execPath, ["resume", id, "--no-attach", "--no-setup"], { encoding: "utf8" });
17162
+ const result = spawnSync2(process.execPath, ["resume", id, "--no-attach", "--no-setup"], { encoding: "utf8" });
16269
17163
  if (result.status === 0)
16270
17164
  console.log(t("cli.update.restored", { id }));
16271
17165
  else
16272
17166
  console.log(t("cli.update.restoreFailed", { id, reason: (result.stderr ?? "").trim() || "unknown" }));
16273
17167
  }
16274
17168
  } finally {
16275
- fs5.rmSync(staging, { recursive: true, force: true });
17169
+ fs6.rmSync(staging, { recursive: true, force: true });
16276
17170
  }
16277
17171
  }
16278
17172
  function applySwap(target, downloaded) {
@@ -16281,20 +17175,88 @@ function applySwap(target, downloaded) {
16281
17175
  try {
16282
17176
  for (const step of steps) {
16283
17177
  if (step.op === "chmod")
16284
- fs5.chmodSync(step.from, 493);
17178
+ fs6.chmodSync(step.from, 493);
16285
17179
  else if (step.op === "backup") {
16286
- fs5.renameSync(step.from, step.to);
17180
+ fs6.renameSync(step.from, step.to);
16287
17181
  backup = step.to;
16288
17182
  } else
16289
- fs5.renameSync(step.from, step.to);
17183
+ fs6.renameSync(step.from, step.to);
16290
17184
  }
16291
17185
  } catch (err) {
16292
- if (backup !== undefined && !fs5.existsSync(target))
16293
- fs5.renameSync(backup, target);
17186
+ if (backup !== undefined && !fs6.existsSync(target))
17187
+ fs6.renameSync(backup, target);
16294
17188
  throw err;
16295
17189
  }
16296
17190
  }
16297
17191
 
17192
+ // packages/cli/src/service-command.ts
17193
+ init_src();
17194
+ import { setTimeout as delay2 } from "timers/promises";
17195
+ var SERVICE_WAIT_MS = 15000;
17196
+ var SERVICE_POLL_MS = 200;
17197
+ async function waitForDaemon(deps, running) {
17198
+ const sleep = deps.sleep ?? ((ms) => delay2(ms));
17199
+ const now = deps.now ?? Date.now;
17200
+ const deadline = now() + SERVICE_WAIT_MS;
17201
+ for (;; ) {
17202
+ const connected = await deps.connect();
17203
+ if (running && connected !== undefined)
17204
+ return connected;
17205
+ connected?.close();
17206
+ if (!running && connected === undefined)
17207
+ return;
17208
+ if (now() >= deadline)
17209
+ throw new Error(t("cli.service.notReady"));
17210
+ await sleep(SERVICE_POLL_MS);
17211
+ }
17212
+ }
17213
+ function localizedStatus(status) {
17214
+ return t("cli.service.status", {
17215
+ kind: status.kind,
17216
+ installed: t(status.installed ? "cli.service.yes" : "cli.service.no"),
17217
+ active: t(status.active ? "cli.service.yes" : "cli.service.no")
17218
+ });
17219
+ }
17220
+ function localizeCommandError(err) {
17221
+ if (err instanceof ServiceCommandError) {
17222
+ const command = [err.invocation.command, ...err.invocation.args].join(" ");
17223
+ throw new Error(t("cli.service.commandFailed", { command, error: err.message }));
17224
+ }
17225
+ throw err;
17226
+ }
17227
+ async function runServiceCommand(subcommand, deps) {
17228
+ const write = deps.write ?? console.log;
17229
+ try {
17230
+ if (subcommand === "status") {
17231
+ write(localizedStatus(deps.manager.status()));
17232
+ return;
17233
+ }
17234
+ if (subcommand === "uninstall") {
17235
+ deps.manager.uninstall();
17236
+ write(t("cli.service.uninstalled"));
17237
+ return;
17238
+ }
17239
+ if (subcommand !== "install")
17240
+ throw new Error(t("cli.usage.service"));
17241
+ deps.manager.install();
17242
+ const existing = await deps.connect();
17243
+ if (existing !== undefined) {
17244
+ try {
17245
+ await existing.call("daemon.stop");
17246
+ } finally {
17247
+ existing.close();
17248
+ }
17249
+ await waitForDaemon(deps, false);
17250
+ }
17251
+ deps.manager.start();
17252
+ const ready = await waitForDaemon(deps, true);
17253
+ ready?.close();
17254
+ write(t("cli.service.installed", { kind: deps.manager.definition.kind }));
17255
+ } catch (err) {
17256
+ localizeCommandError(err);
17257
+ }
17258
+ }
17259
+
16298
17260
  // packages/cli/src/index.ts
16299
17261
  var VERSION = package_default.version;
16300
17262
  var VALUE_FLAGS = new Set([
@@ -16363,7 +17325,7 @@ function printSessionTable(sessions) {
16363
17325
  console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
16364
17326
  }
16365
17327
  }
16366
- var realPath = (target) => fs25.realpathSync.native(target);
17328
+ var realPath = (target) => fs26.realpathSync.native(target);
16367
17329
  function scopedHere(sessions) {
16368
17330
  return sessionsInScope(sessions, process.cwd(), realPath);
16369
17331
  }
@@ -16376,7 +17338,7 @@ var help = () => t("cli.help");
16376
17338
  function readDotEnv(file = ".env") {
16377
17339
  const out = {};
16378
17340
  try {
16379
- for (const line of fs25.readFileSync(file, "utf8").split(/\r?\n/)) {
17341
+ for (const line of fs26.readFileSync(file, "utf8").split(/\r?\n/)) {
16380
17342
  const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
16381
17343
  if (m)
16382
17344
  out[m[1]] = m[2].trim();
@@ -16387,7 +17349,7 @@ function readDotEnv(file = ".env") {
16387
17349
  function configuredLocale() {
16388
17350
  try {
16389
17351
  const file = resolvePaths(currentPathEnv()).configFile;
16390
- const raw = JSON.parse(fs25.readFileSync(file, "utf8"));
17352
+ const raw = JSON.parse(fs26.readFileSync(file, "utf8"));
16391
17353
  return typeof raw.locale === "string" ? raw.locale : undefined;
16392
17354
  } catch {
16393
17355
  return;
@@ -16424,6 +17386,13 @@ async function main2() {
16424
17386
  console.log(help());
16425
17387
  return;
16426
17388
  }
17389
+ if (command === "service") {
17390
+ await runServiceCommand(positional[0] ?? "status", {
17391
+ manager: currentServiceManager(),
17392
+ connect: connectDaemonIfRunning
17393
+ });
17394
+ return;
17395
+ }
16427
17396
  const client = await ensureDaemon();
16428
17397
  const health = await client.call("config.health").catch(() => {
16429
17398
  return;
@@ -16538,12 +17507,10 @@ async function main2() {
16538
17507
  if (!id || indexArg === undefined)
16539
17508
  throw new Error(t("cli.usage.choose"));
16540
17509
  const index = Number(indexArg);
16541
- const answered = await client.call("session.choose", { id, index }).catch(async (err) => {
17510
+ const answered = await client.call("session.choose", { id, index }).catch((err) => {
16542
17511
  if (!/unknown method/i.test(String(err)))
16543
17512
  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" };
17513
+ throw new Error(t("cli.choose.daemonTooOld"));
16547
17514
  });
16548
17515
  console.log(t("cli.session.selected", { index: indexArg }));
16549
17516
  if (answered.answered === "onboarding")
@@ -16623,8 +17590,8 @@ async function main2() {
16623
17590
  case "project": {
16624
17591
  const sub = positional[0] ?? "list";
16625
17592
  if (sub === "add") {
16626
- const dir = positional[1] ? path23.resolve(positional[1]) : process.cwd();
16627
- const alias = positional[2] ?? path23.basename(dir);
17593
+ const dir = positional[1] ? path25.resolve(positional[1]) : process.cwd();
17594
+ const alias = positional[2] ?? path25.basename(dir);
16628
17595
  const projects = await client.call("project.add", {
16629
17596
  alias,
16630
17597
  path: dir