@layorixinc/axterm 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +2229 -587
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -33,6 +33,136 @@ var __toESM = (mod, isNodeMode, target) => {
33
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
34
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
35
35
 
36
+ // packages/core/src/mcp-config.ts
37
+ function mcpServerEntry(launch, contextPath) {
38
+ return {
39
+ command: launch.command,
40
+ args: [...launch.args],
41
+ ...contextPath === undefined ? {} : { env: { [MCP_CONTEXT_ENV]: contextPath } }
42
+ };
43
+ }
44
+ function claudeMcpConfig(launch, contextPath) {
45
+ return JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: mcpServerEntry(launch, contextPath) } }, null, 2);
46
+ }
47
+ function tomlLiteral(value) {
48
+ if (value.includes("'")) {
49
+ throw new Error(`cannot pass a path containing a single quote to codex: ${value}`);
50
+ }
51
+ return `'${value}'`;
52
+ }
53
+ function codexMcpArgs(launch, contextPath) {
54
+ const args = launch.args.map(tomlLiteral).join(",");
55
+ return [
56
+ "-c",
57
+ `mcp_servers.${MCP_SERVER_NAME}.command=${tomlLiteral(launch.command)}`,
58
+ "-c",
59
+ `mcp_servers.${MCP_SERVER_NAME}.args=[${args}]`,
60
+ ...contextPath === undefined ? [] : ["-c", `mcp_servers.${MCP_SERVER_NAME}.env={${MCP_CONTEXT_ENV}=${tomlLiteral(contextPath)}}`]
61
+ ];
62
+ }
63
+ var MCP_SERVER_NAME = "axterm", MCP_CONTEXT_ENV = "AXTERM_MCP_CONTEXT";
64
+
65
+ // packages/core/src/agent-launch.ts
66
+ function uuidFromSessionId(sessionId) {
67
+ const hex = sessionId.replace(/-/g, "");
68
+ if (hex.length !== 32)
69
+ throw new Error(`session id is not 32 hex characters: ${sessionId}`);
70
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
71
+ }
72
+ function withAppServer(argv, url) {
73
+ const out = [];
74
+ for (let i = 0;i < argv.length; i++) {
75
+ if (argv[i] === "--remote") {
76
+ i++;
77
+ continue;
78
+ }
79
+ out.push(argv[i]);
80
+ }
81
+ if (url === undefined || out.length === 0)
82
+ return out;
83
+ return [out[0], "--remote", url, ...out.slice(1)];
84
+ }
85
+ function buildAgentCommand(input) {
86
+ const extra = [...input.extraArgs ?? []];
87
+ if (input.agent === "claude") {
88
+ return [
89
+ "claude",
90
+ "--settings",
91
+ input.claudeSettingsPath,
92
+ ...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
93
+ "--session-id",
94
+ uuidFromSessionId(input.sessionId),
95
+ ...extra
96
+ ];
97
+ }
98
+ if (input.agent === "gemini") {
99
+ return [
100
+ "gemini",
101
+ "--session-id",
102
+ input.sessionId,
103
+ ...extra
104
+ ];
105
+ }
106
+ if (input.agent === "codex") {
107
+ return [
108
+ "codex",
109
+ ...input.appServerUrl === undefined ? [] : ["--remote", input.appServerUrl],
110
+ ...input.mcpLaunch === undefined ? [] : codexMcpArgs(input.mcpLaunch, input.mcpContextPath),
111
+ ...extra
112
+ ];
113
+ }
114
+ return ["omp", "--extension", input.extensionPath, ...extra];
115
+ }
116
+ function resolveAgentArgs(input) {
117
+ const approve = input.autoApprove ?? input.autoApproveConfigured;
118
+ const layered = [
119
+ ...approve === true ? input.bypass ?? [] : [],
120
+ ...input.configured ?? [],
121
+ ...input.requested ?? []
122
+ ];
123
+ const groups = [];
124
+ for (const token of layered) {
125
+ if (token.startsWith("-"))
126
+ groups.push({ flag: token, tokens: [token] });
127
+ else if (groups.length > 0)
128
+ groups[groups.length - 1].tokens.push(token);
129
+ else
130
+ groups.push({ flag: undefined, tokens: [token] });
131
+ }
132
+ const lastIndex = new Map;
133
+ groups.forEach((group, index) => {
134
+ if (group.flag !== undefined)
135
+ lastIndex.set(group.flag, index);
136
+ });
137
+ return groups.filter((group, index) => group.flag === undefined || lastIndex.get(group.flag) === index).flatMap((group) => group.tokens);
138
+ }
139
+ function buildAgentResumeCommand(input) {
140
+ if (input.agent === "claude") {
141
+ return [
142
+ "claude",
143
+ "--settings",
144
+ input.claudeSettingsPath,
145
+ "--resume",
146
+ uuidFromSessionId(input.sessionId),
147
+ ...input.extraArgs ?? []
148
+ ];
149
+ }
150
+ if (input.agent === "gemini") {
151
+ return ["gemini", "--resume", input.sessionId, ...input.extraArgs ?? []];
152
+ }
153
+ return buildAgentCommand(input);
154
+ }
155
+ function isAgentId(value) {
156
+ return AGENT_IDS.includes(value);
157
+ }
158
+ function supportedAgents() {
159
+ return AGENT_IDS;
160
+ }
161
+ var AGENT_IDS;
162
+ var init_agent_launch = __esm(() => {
163
+ AGENT_IDS = ["omp", "claude", "codex", "gemini"];
164
+ });
165
+
36
166
  // packages/core/src/activity.ts
37
167
  function shouldStream(kind, settings) {
38
168
  if (!settings.enabled)
@@ -41,6 +171,8 @@ function shouldStream(kind, settings) {
41
171
  return settings.thinking;
42
172
  if (kind === "tool")
43
173
  return settings.tools;
174
+ if (kind === "reply")
175
+ return settings.replies;
44
176
  return settings.toolResults;
45
177
  }
46
178
  function clipText(text, maxChars) {
@@ -147,7 +279,8 @@ var init_activity = __esm(() => {
147
279
  MARKERS = {
148
280
  thinking: "\uD83E\uDDE0",
149
281
  tool: "\uD83D\uDD27",
150
- toolResult: "\u21B3"
282
+ toolResult: "\u21B3",
283
+ reply: "\uD83D\uDCAC"
151
284
  };
152
285
  });
153
286
 
@@ -279,6 +412,7 @@ function resolveStreamConfig(file) {
279
412
  thinking: file?.thinking ?? DEFAULT_STREAM_CONFIG.thinking,
280
413
  tools: file?.tools ?? DEFAULT_STREAM_CONFIG.tools,
281
414
  toolResults: file?.toolResults ?? DEFAULT_STREAM_CONFIG.toolResults,
415
+ replies: file?.replies ?? DEFAULT_STREAM_CONFIG.replies,
282
416
  intervalMs: file?.intervalMs ?? DEFAULT_STREAM_CONFIG.intervalMs,
283
417
  maxChars: file?.maxChars ?? DEFAULT_STREAM_CONFIG.maxChars
284
418
  };
@@ -365,12 +499,30 @@ function parseConfig(raw) {
365
499
  }
366
500
  const defaults = raw["defaults"];
367
501
  if (isRecord(defaults)) {
368
- checkKeys(defaults, ["agent", "shell"], "defaults.", errors);
502
+ checkKeys(defaults, ["agent", "shell", "autoApprove", "agentArgs"], "defaults.", errors);
369
503
  const agent = defaults["agent"];
370
504
  const shell = defaults["shell"];
505
+ const rawArgs = defaults["agentArgs"];
506
+ const agentArgs = {};
507
+ if (isRecord(rawArgs)) {
508
+ for (const [id, value] of Object.entries(rawArgs)) {
509
+ if (Array.isArray(value) && value.every((part) => typeof part === "string"))
510
+ agentArgs[id] = value;
511
+ else
512
+ errors.push(`defaults.agentArgs.${id} must be a list of strings`);
513
+ }
514
+ } else if (rawArgs !== undefined) {
515
+ errors.push("defaults.agentArgs must be an object keyed by agent");
516
+ }
517
+ const autoApprove = defaults["autoApprove"];
518
+ if (autoApprove !== undefined && typeof autoApprove !== "boolean") {
519
+ errors.push("defaults.autoApprove must be true or false");
520
+ }
371
521
  config.defaults = {
372
522
  ...typeof agent === "string" ? { agent } : {},
373
- ...typeof shell === "string" ? { shell } : {}
523
+ ...typeof shell === "string" ? { shell } : {},
524
+ ...typeof autoApprove === "boolean" ? { autoApprove } : {},
525
+ ...Object.keys(agentArgs).length > 0 ? { agentArgs } : {}
374
526
  };
375
527
  }
376
528
  const projects = raw["projects"];
@@ -404,9 +556,9 @@ function parseConfig(raw) {
404
556
  }
405
557
  const stream = telegram["stream"];
406
558
  if (isRecord(stream)) {
407
- checkKeys(stream, ["enabled", "thinking", "tools", "toolResults", "intervalMs", "maxChars"], "channels.telegram.stream.", errors);
559
+ checkKeys(stream, ["enabled", "thinking", "tools", "toolResults", "replies", "intervalMs", "maxChars"], "channels.telegram.stream.", errors);
408
560
  const streamEntry = {};
409
- for (const key of ["enabled", "thinking", "tools", "toolResults", "intervalMs", "maxChars"]) {
561
+ for (const key of ["enabled", "thinking", "tools", "toolResults", "replies", "intervalMs", "maxChars"]) {
410
562
  const value = stream[key];
411
563
  if (value === undefined)
412
564
  continue;
@@ -445,21 +597,28 @@ function getConfigValue(config, dottedPath) {
445
597
  return cursor;
446
598
  }
447
599
  function coerce(input, type) {
600
+ if (Array.isArray(input))
601
+ return type === "string[]" && input.length > 0 ? [...input] : undefined;
602
+ const text = input;
448
603
  if (type === "string")
449
- return input;
604
+ return text;
450
605
  if (type === "number") {
451
- const value = Number(input);
606
+ const value = Number(text);
452
607
  return Number.isFinite(value) ? value : undefined;
453
608
  }
454
609
  if (type === "boolean") {
455
- if (/^(true|yes|on|1)$/i.test(input))
610
+ if (/^(true|yes|on|1)$/i.test(text))
456
611
  return true;
457
- if (/^(false|no|off|0)$/i.test(input))
612
+ if (/^(false|no|off|0)$/i.test(text))
458
613
  return false;
459
614
  return;
460
615
  }
461
- const parts = input.split(",").map((p) => Number(p.trim()));
462
- return parts.every((p) => Number.isFinite(p)) ? parts : undefined;
616
+ if (type === "string[]") {
617
+ const parts = text.trim().split(/\s+/).filter((part) => part !== "");
618
+ return parts.length > 0 ? parts : undefined;
619
+ }
620
+ const numbers = text.split(",").map((part) => Number(part.trim()));
621
+ return numbers.every((part) => Number.isFinite(part)) ? numbers : undefined;
463
622
  }
464
623
  function setConfigValue(config, dottedPath, rawValue) {
465
624
  const type = FIELDS[dottedPath];
@@ -468,8 +627,10 @@ function setConfigValue(config, dottedPath, rawValue) {
468
627
  return { error: `unknown setting "${dottedPath}"${suggestion ? ` \u2014 did you mean "${suggestion}"?` : ""}` };
469
628
  }
470
629
  const value = coerce(rawValue, type);
471
- if (value === undefined)
472
- return { error: `"${rawValue}" is not a valid ${type} for ${dottedPath}` };
630
+ if (value === undefined) {
631
+ const shown = Array.isArray(rawValue) ? rawValue.join(" ") : rawValue;
632
+ return { error: `"${shown}" is not a valid ${type} for ${dottedPath}` };
633
+ }
473
634
  const next = structuredClone(config);
474
635
  const segments = dottedPath.split(".");
475
636
  let cursor = next;
@@ -515,6 +676,7 @@ function resolveEnvRefs(config, env) {
515
676
  }
516
677
  var CONFIG_VERSION = 1, DEFAULT_CONFIG, DEFAULT_STREAM_CONFIG, DEFAULT_INBOUND_CONFIG, FIELDS, SECRET_SUFFIXES, ENV_REF;
517
678
  var init_config_schema = __esm(() => {
679
+ init_agent_launch();
518
680
  init_channel_spi();
519
681
  DEFAULT_CONFIG = {
520
682
  version: CONFIG_VERSION,
@@ -526,6 +688,7 @@ var init_config_schema = __esm(() => {
526
688
  thinking: false,
527
689
  tools: true,
528
690
  toolResults: false,
691
+ replies: false,
529
692
  intervalMs: 1e4,
530
693
  maxChars: 300
531
694
  };
@@ -534,10 +697,12 @@ var init_config_schema = __esm(() => {
534
697
  maxBytes: TELEGRAM_CAPABILITIES.attachments.maxBytes
535
698
  };
536
699
  FIELDS = {
700
+ ...Object.fromEntries(supportedAgents().map((agent) => [`defaults.agentArgs.${agent}`, "string[]"])),
537
701
  locale: "string",
538
702
  "machine.name": "string",
539
703
  "defaults.agent": "string",
540
704
  "defaults.shell": "string",
705
+ "defaults.autoApprove": "boolean",
541
706
  "channels.telegram.enabled": "boolean",
542
707
  "channels.telegram.groupId": "number",
543
708
  "channels.telegram.allowUserIds": "number[]",
@@ -548,6 +713,7 @@ var init_config_schema = __esm(() => {
548
713
  "channels.telegram.stream.thinking": "boolean",
549
714
  "channels.telegram.stream.tools": "boolean",
550
715
  "channels.telegram.stream.toolResults": "boolean",
716
+ "channels.telegram.stream.replies": "boolean",
551
717
  "channels.telegram.stream.intervalMs": "number",
552
718
  "channels.telegram.stream.maxChars": "number",
553
719
  "channels.telegram.inbound.enabled": "boolean",
@@ -664,8 +830,12 @@ var init_i18n = __esm(() => {
664
830
  usage:
665
831
  axterm run [--cwd DIR] [--name NAME] [--shell pwsh|powershell|cmd|bash|zsh]
666
832
  [--no-attach] [--cmd "raw command"] [-- <extra agent args>]
833
+ [--auto-approve | --no-auto-approve] let the agent act without asking,
834
+ overriding defaults.autoApprove for this session only
667
835
  axterm list [--all] sessions started in this folder; --all for every one
668
- axterm attach <id> [--host-status orca] detach: Ctrl+X Ctrl+X (or F12 / Ctrl+])
836
+ axterm attach <id>|--last [--all] [--host-status orca]
837
+ --last: the newest live session here, for when the id is gone
838
+ detach: Ctrl+X Ctrl+X (or F12 / Ctrl+])
669
839
  the host terminal title always reflects agent status;
670
840
  --host-status orca also reports full status to the pane
671
841
  axterm esc <id> send ESC (interrupt)
@@ -688,6 +858,7 @@ usage:
688
858
  discover lists the group and user ids your bot can see
689
859
  axterm setup interactive configuration (runs automatically on first use)
690
860
  axterm doctor diagnose the silent failures: agent binaries, hook configs, codex trust, the bridge
861
+ axterm quota how much of each agent's rate limit is spent, from the agent's own records
691
862
  axterm config list|get <path>|set <path> <v>|unset <path>|path|import-env [file]
692
863
 
693
864
  locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
@@ -698,7 +869,9 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
698
869
  "cli.sessions.removed": "removed {count} session(s)",
699
870
  "cli.sessions.noneExited": "no exited sessions to remove",
700
871
  "cli.session.removeFailed": "could not remove {id}: {error}",
701
- "cli.session.created": "created session {id} ({shell}, pid {pid})",
872
+ "cli.session.created": "created session {id} ({shell}, pid {pid}) \u2014 reattach with `axterm attach {id}`, or `axterm attach --last`",
873
+ "cli.attach.picked": "attaching to {id} ({name}), the most recent live session here",
874
+ "cli.attach.noRecent": "no live session here to reattach \u2014 `axterm list --all` shows every one",
702
875
  "cli.session.killed": "killed",
703
876
  "cli.session.sent": "sent",
704
877
  "cli.session.sentKey": "sent {key}",
@@ -726,7 +899,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
726
899
  "cli.update.notBinary": "this axterm runs from source, so there is nothing to replace \u2014 use git pull",
727
900
  "cli.flag.needsValue": "--{flag} requires a value",
728
901
  "cli.key.unknown": "unknown key: {name}",
729
- "cli.usage.attach": "usage: axterm attach <id>",
902
+ "cli.usage.attach": "usage: axterm attach <id> (or `axterm attach --last`)",
730
903
  "cli.usage.send": "usage: axterm send [--raw] <id> <text...>",
731
904
  "cli.usage.screen": "usage: axterm screen <id>",
732
905
  "cli.usage.esc": "usage: axterm esc <id>",
@@ -739,7 +912,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
739
912
  "cli.usage.diff": "usage: axterm diff <id> [--base REF]",
740
913
  "cli.usage.config": "usage: axterm config list|get|set|unset|path|import-env",
741
914
  "cli.usage.configGet": "usage: axterm config get <path> [--reveal]",
742
- "cli.usage.configSet": "usage: axterm config set <path> <value>",
915
+ "cli.usage.configSet": "usage: axterm config set <path> <value> (a value starting with - goes after `--`)",
743
916
  "cli.usage.configUnset": "usage: axterm config unset <path>",
744
917
  "cli.usage.project": "usage: axterm project add|list|rm",
745
918
  "cli.usage.projectRm": "usage: axterm project rm <alias>",
@@ -778,7 +951,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
778
951
  "tg.help.general.worktrees": "/worktrees <project> \xB7 /rm <project> <branch> [force]",
779
952
  "tg.help.general.sessions": "/sessions \u2014 session list",
780
953
  "tg.help.topic.header": "Inside a session topic, a plain message is delivered as a prompt.",
781
- "tg.help.topic.commands": "/esc interrupt \xB7 /screen snapshot \xB7 /diff change summary \xB7 /resume continue the conversation \xB7 /kill stop",
954
+ "tg.help.topic.commands": "/esc interrupt \xB7 /screen snapshot \xB7 /diff change summary \xB7 /quota rate limits \xB7 /key & /type raw input \xB7 /resume continue the conversation \xB7 /kill stop",
782
955
  "tg.session.started": `\u25B6 Session {id} started
783
956
  {command}
784
957
  \uD83D\uDCC1 {cwd}`,
@@ -802,6 +975,15 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
802
975
  "tg.shell.only": "(shell)",
803
976
  "tg.agent.failed": "\uD83D\uDED1 The agent could not finish this turn: {error}",
804
977
  "tg.agent.retry": "\u23F3 The agent is rate limited and will retry in {wait}: {error}",
978
+ "tg.raw.keysSent": "\u2328\uFE0F sent: {keys}",
979
+ "tg.raw.unknownKey": 'unknown key "{name}" \u2014 nothing was sent. Accepted: {keys}',
980
+ "tg.raw.typed": `\u2328\uFE0F typed (not submitted \u2014 /key enter to send):
981
+ <pre>{screen}</pre>`,
982
+ "tg.raw.needText": "usage: /type <text> \u2014 written verbatim, without Enter. Then /key enter.",
983
+ "tg.raw.tooLong": "too long for raw input ({limit} characters max) \u2014 send it as an ordinary message instead",
984
+ "tg.agent.attempt": "\u26A0\uFE0F An attempt failed and the agent is retrying: {error}",
985
+ "tg.agent.stalled": `\u23F8\uFE0F No progress for {wait}. The agent has not said why \u2014 this is the last thing it printed:
986
+ <pre>{screen}</pre>`,
805
987
  "tg.inbound.tooLarge": "\u26A0\uFE0F That file is larger than the {limit} limit \u2014 nothing was downloaded.",
806
988
  "tg.inbound.empty": "\u26A0\uFE0F That file is empty \u2014 nothing was downloaded.",
807
989
  "tg.inbound.noSession": "\u26A0\uFE0F Send the file inside a session topic \u2014 no session is bound to this one.",
@@ -877,6 +1059,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
877
1059
  "doctor.telegram.ok": "telegram: connected",
878
1060
  "doctor.telegram.off": "telegram: not running",
879
1061
  "doctor.telegram.problems": "telegram: {problems}",
1062
+ "quota.gauge": "{agent}: {percent}% of {window} used, resets in {resets}",
1063
+ "quota.gaugeStale": "{agent}: {percent}% of {window} used, resets in {resets} (recorded {age} ago)",
1064
+ "quota.gaugeElapsed": "{agent}: {percent}% of {window} used, and that window has already reset",
1065
+ "quota.reached": "{agent}: the limit was reached \u2014 {percent}% of {window}, resets in {resets}",
1066
+ "quota.event": "{agent}: hit its limit {age} ago \u2014 {text}",
1067
+ "quota.eventNone": "{agent}: no limit reported (it records no percentage)",
1068
+ "quota.noData": "{agent}: nothing recorded yet \u2014 run a turn first",
1069
+ "quota.unsupported": "{agent}: reports no quota at all",
1070
+ "cli.quota.none": "no agent on this machine reports a quota",
880
1071
  "fanout.needCountOrBranches": "a branch count or a branch list is required",
881
1072
  "fanout.needPrompt": "a prompt is required",
882
1073
  "fanout.outOfRange": "must be between 1 and {max}",
@@ -900,9 +1091,10 @@ usage: /fanout <project> <count|branches> <prompt>`,
900
1091
  "health.telegram.noUsers": "Telegram is enabled but no user is allowed to control it",
901
1092
  "wizard.prompt.locale": "Language",
902
1093
  "wizard.prompt.machine": "Name for this machine (used to address it from a channel)",
903
- "wizard.prompt.agent": "Default agent command",
1094
+ "wizard.prompt.agent": "Default agent (extra flags go in defaults.agentArgs.<agent>)",
904
1095
  "wizard.prompt.telegram": "Set up Telegram control now?",
905
1096
  "wizard.prompt.telegramToken": "Bot token from @BotFather",
1097
+ "wizard.prompt.agentApproval": "Let this agent act without asking for approval? It skips every permission prompt \u2014 convenient from a phone, and it means the agent may edit or run anything in the session's folder. Reversible with `axterm config set defaults.autoApprove false`, or for one session with `axterm run --no-auto-approve`.",
906
1098
  "wizard.prompt.telegramGroup": "Group id (negative, starts with -100; `axterm telegram discover` lists them)",
907
1099
  "wizard.prompt.telegramUsers": "Telegram user ids allowed to control axterm (comma separated; `axterm telegram discover` lists them)",
908
1100
  "wizard.prompt.review": "Save this configuration?",
@@ -934,8 +1126,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
934
1126
  \uC0AC\uC6A9\uBC95:
935
1127
  axterm run [--cwd \uD3F4\uB354] [--name \uC774\uB984] [--shell pwsh|powershell|cmd|bash|zsh]
936
1128
  [--no-attach] [--cmd "\uC6D0\uC2DC \uBA85\uB839"] [-- <\uC5D0\uC774\uC804\uD2B8 \uCD94\uAC00 \uC778\uC790>]
1129
+ [--auto-approve | --no-auto-approve] \uC774 \uC138\uC158\uB9CC \uC2B9\uC778 \uC5C6\uC774 \uB3D9\uC791\uD558\uAC8C,
1130
+ defaults.autoApprove \uB97C \uB36E\uC5B4\uC501\uB2C8\uB2E4
937
1131
  axterm list [--all] \uC774 \uD3F4\uB354\uC5D0\uC11C \uB9CC\uB4E0 \uC138\uC158. --all \uC740 \uC804\uCCB4
938
- axterm attach <id> [--host-status orca] \uBD84\uB9AC: Ctrl+X Ctrl+X (\uB610\uB294 F12 / Ctrl+])
1132
+ axterm attach <id>|--last [--all] [--host-status orca]
1133
+ --last: \uC774 \uD3F4\uB354\uC758 \uAC00\uC7A5 \uCD5C\uADFC \uC0B4\uC544\uC788\uB294 \uC138\uC158 (id\uB97C \uC783\uC5C8\uC744 \uB54C)
1134
+ \uBD84\uB9AC: Ctrl+X Ctrl+X (\uB610\uB294 F12 / Ctrl+])
939
1135
  \uD638\uC2A4\uD2B8 \uD130\uBBF8\uB110 \uC81C\uBAA9\uC740 \uD56D\uC0C1 \uC5D0\uC774\uC804\uD2B8 \uC0C1\uD0DC\uB97C \uBC18\uC601\uD55C\uB2E4;
940
1136
  --host-status orca \uB294 \uD32C\uC5D0\uB3C4 \uC804\uCCB4 \uC0C1\uD0DC\uB97C \uBCF4\uACE0\uD55C\uB2E4
941
1137
  axterm esc <id> ESC \uC804\uC1A1 (\uC911\uB2E8)
@@ -958,6 +1154,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
958
1154
  discover \u2014 \uBD07\uC774 \uBCFC \uC218 \uC788\uB294 \uADF8\uB8F9\xB7\uC0AC\uC6A9\uC790 id \uBAA9\uB85D
959
1155
  axterm setup \uB300\uD654\uD615 \uC124\uC815 (\uCD5C\uCD08 \uC2E4\uD589 \uC2DC \uC790\uB3D9)
960
1156
  axterm doctor \uCE68\uBB35\uD558\uB294 \uC2E4\uD328\uB97C \uC9C4\uB2E8: \uC5D0\uC774\uC804\uD2B8 \uBC14\uC774\uB108\uB9AC, \uD6C5 \uC124\uC815, codex \uC2E0\uB8B0, \uBE0C\uB9AC\uC9C0
1157
+ axterm quota \uAC01 \uC5D0\uC774\uC804\uD2B8\uC758 \uC0AC\uC6A9\uB7C9 \uD55C\uB3C4 \uC18C\uC9C4 \uC815\uB3C4 (\uC5D0\uC774\uC804\uD2B8 \uC790\uC2E0\uC758 \uAE30\uB85D\uC5D0\uC11C)
961
1158
  axterm config list|get <\uACBD\uB85C>|set <\uACBD\uB85C> <\uAC12>|unset <\uACBD\uB85C>|path|import-env [\uD30C\uC77C]
962
1159
 
963
1160
  \uC5B8\uC5B4: AXTERM_LANG=en|ko \uB610\uB294 \`axterm config set locale ko\`
@@ -968,7 +1165,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
968
1165
  "cli.sessions.removed": "\uC138\uC158 {count}\uAC1C \uC0AD\uC81C\uB428",
969
1166
  "cli.sessions.noneExited": "\uC0AD\uC81C\uD560 \uC885\uB8CC\uB41C \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
970
1167
  "cli.session.removeFailed": "{id} \uC0AD\uC81C \uC2E4\uD328: {error}",
971
- "cli.session.created": "\uC138\uC158 {id} \uC0DD\uC131\uB428 ({shell}, pid {pid})",
1168
+ "cli.session.created": "\uC138\uC158 {id} \uC0DD\uC131\uB428 ({shell}, pid {pid}) \u2014 \uC7AC\uC811\uC18D: `axterm attach {id}` \uB610\uB294 `axterm attach --last`",
1169
+ "cli.attach.picked": "{id} ({name}) \uC5D0 \uC811\uC18D\uD569\uB2C8\uB2E4 \u2014 \uC774 \uD3F4\uB354\uC5D0\uC11C \uAC00\uC7A5 \uCD5C\uADFC\uC5D0 \uB9CC\uB4E0 \uC0B4\uC544\uC788\uB294 \uC138\uC158\uC785\uB2C8\uB2E4",
1170
+ "cli.attach.noRecent": "\uC774 \uD3F4\uB354\uC5D0 \uC7AC\uC811\uC18D\uD560 \uC0B4\uC544\uC788\uB294 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 `axterm list --all` \uB85C \uC804\uCCB4\uB97C \uBCF4\uC138\uC694",
972
1171
  "cli.session.killed": "\uC885\uB8CC\uB428",
973
1172
  "cli.session.sent": "\uC804\uB2EC\uB428",
974
1173
  "cli.session.sentKey": "{key} \uC804\uB2EC\uB428",
@@ -996,7 +1195,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
996
1195
  "cli.update.notBinary": "\uC774 axterm \uC740 \uC18C\uC2A4\uC5D0\uC11C \uC2E4\uD589 \uC911\uC774\uB77C \uAD50\uCCB4\uD560 \uC2E4\uD589 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. git pull \uC744 \uC0AC\uC6A9\uD558\uC138\uC694",
997
1196
  "cli.flag.needsValue": "--{flag} \uC5D0\uB294 \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
998
1197
  "cli.key.unknown": "\uC54C \uC218 \uC5C6\uB294 \uD0A4: {name}",
999
- "cli.usage.attach": "\uC0AC\uC6A9\uBC95: axterm attach <id>",
1198
+ "cli.usage.attach": "\uC0AC\uC6A9\uBC95: axterm attach <id> (\uB610\uB294 `axterm attach --last`)",
1000
1199
  "cli.usage.send": "\uC0AC\uC6A9\uBC95: axterm send [--raw] <id> <\uD14D\uC2A4\uD2B8...>",
1001
1200
  "cli.usage.screen": "\uC0AC\uC6A9\uBC95: axterm screen <id>",
1002
1201
  "cli.usage.esc": "\uC0AC\uC6A9\uBC95: axterm esc <id>",
@@ -1009,7 +1208,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1009
1208
  "cli.usage.diff": "\uC0AC\uC6A9\uBC95: axterm diff <id> [--base REF]",
1010
1209
  "cli.usage.config": "\uC0AC\uC6A9\uBC95: axterm config list|get|set|unset|path|import-env",
1011
1210
  "cli.usage.configGet": "\uC0AC\uC6A9\uBC95: axterm config get <\uACBD\uB85C> [--reveal]",
1012
- "cli.usage.configSet": "\uC0AC\uC6A9\uBC95: axterm config set <\uACBD\uB85C> <\uAC12>",
1211
+ "cli.usage.configSet": "\uC0AC\uC6A9\uBC95: axterm config set <\uACBD\uB85C> <\uAC12> (- \uB85C \uC2DC\uC791\uD558\uB294 \uAC12\uC740 `--` \uB4A4\uC5D0)",
1013
1212
  "cli.usage.configUnset": "\uC0AC\uC6A9\uBC95: axterm config unset <\uACBD\uB85C>",
1014
1213
  "cli.usage.project": "\uC0AC\uC6A9\uBC95: axterm project add|list|rm",
1015
1214
  "cli.usage.projectRm": "\uC0AC\uC6A9\uBC95: axterm project rm <\uBCC4\uCE6D>",
@@ -1048,7 +1247,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
1048
1247
  "err.telegram.noToken": "\uBD07 \uD1A0\uD070\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 `axterm setup` \uC744 \uC2E4\uD589\uD558\uAC70\uB098 --token \uC744 \uB118\uAE30\uC138\uC694",
1049
1248
  "tg.help.general.sessions": "/sessions \u2014 \uC138\uC158 \uBAA9\uB85D",
1050
1249
  "tg.help.topic.header": "\uC138\uC158 \uD1A0\uD53D \uC548\uC5D0\uC11C\uB294 \uC77C\uBC18 \uBA54\uC2DC\uC9C0\uAC00 \uD504\uB86C\uD504\uD2B8\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4.",
1051
- "tg.help.topic.commands": "/esc \uC911\uB2E8 \xB7 /screen \uD654\uBA74 \xB7 /diff \uBCC0\uACBD\uC694\uC57D \xB7 /resume \uB300\uD654 \uC774\uC5B4\uC11C \uC7AC\uC2DC\uC791 \xB7 /kill \uC885\uB8CC",
1250
+ "tg.help.topic.commands": "/esc \uC911\uB2E8 \xB7 /screen \uD654\uBA74 \xB7 /diff \uBCC0\uACBD\uC694\uC57D \xB7 /quota \uC0AC\uC6A9\uB7C9 \xB7 /key\xB7/type \uC6D0\uC2DC \uC785\uB825 \xB7 /resume \uB300\uD654 \uC774\uC5B4\uC11C \uC7AC\uC2DC\uC791 \xB7 /kill \uC885\uB8CC",
1052
1251
  "tg.session.started": `\u25B6 \uC138\uC158 {id} \uC2DC\uC791
1053
1252
  {command}
1054
1253
  \uD83D\uDCC1 {cwd}`,
@@ -1072,6 +1271,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
1072
1271
  "tg.shell.only": "(\uC178)",
1073
1272
  "tg.agent.failed": "\uD83D\uDED1 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC774\uBC88 \uD134\uC744 \uB05D\uB0B4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: {error}",
1074
1273
  "tg.agent.retry": "\u23F3 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC694\uCCAD \uD55C\uB3C4\uC5D0 \uAC78\uB824 {wait} \uD6C4 \uC7AC\uC2DC\uB3C4\uD569\uB2C8\uB2E4: {error}",
1274
+ "tg.raw.keysSent": "\u2328\uFE0F \uC804\uC1A1: {keys}",
1275
+ "tg.raw.unknownKey": '\uC54C \uC218 \uC5C6\uB294 \uD0A4 "{name}" \u2014 \uC544\uBB34\uAC83\uB3C4 \uBCF4\uB0B4\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uAC00\uB2A5\uD55C \uD0A4: {keys}',
1276
+ "tg.raw.typed": `\u2328\uFE0F \uC785\uB825\uD588\uC2B5\uB2C8\uB2E4 (\uC544\uC9C1 \uC81C\uCD9C \uC548 \uD568 \u2014 \uBCF4\uB0B4\uB824\uBA74 /key enter):
1277
+ <pre>{screen}</pre>`,
1278
+ "tg.raw.needText": "\uC0AC\uC6A9\uBC95: /type <\uD14D\uC2A4\uD2B8> \u2014 Enter \uC5C6\uC774 \uADF8\uB300\uB85C \uC785\uB825\uB429\uB2C8\uB2E4. \uC774\uD6C4 /key enter.",
1279
+ "tg.raw.tooLong": "\uC6D0\uC2DC \uC785\uB825\uC73C\uB85C\uB294 \uB108\uBB34 \uAE41\uB2C8\uB2E4 (\uCD5C\uB300 {limit}\uC790) \u2014 \uC77C\uBC18 \uBA54\uC2DC\uC9C0\uB85C \uBCF4\uB0B4\uC138\uC694",
1280
+ "tg.agent.attempt": "\u26A0\uFE0F \uC2DC\uB3C4\uAC00 \uD55C \uBC88 \uC2E4\uD328\uD574 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC7AC\uC2DC\uB3C4\uD569\uB2C8\uB2E4: {error}",
1281
+ "tg.agent.stalled": `\u23F8\uFE0F {wait} \uB3D9\uC548 \uC9C4\uC804\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC5D0\uC774\uC804\uD2B8\uAC00 \uC774\uC720\uB97C \uB9D0\uD558\uC9C0 \uC54A\uC558\uC73C\uBBC0\uB85C, \uB9C8\uC9C0\uB9C9\uC73C\uB85C \uCD9C\uB825\uD55C \uB0B4\uC6A9\uC785\uB2C8\uB2E4:
1282
+ <pre>{screen}</pre>`,
1075
1283
  "tg.inbound.tooLarge": "\u26A0\uFE0F {limit} \uC81C\uD55C\uC744 \uB118\uB294 \uD30C\uC77C\uC785\uB2C8\uB2E4 \u2014 \uB0B4\uB824\uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
1076
1284
  "tg.inbound.empty": "\u26A0\uFE0F \uBE48 \uD30C\uC77C\uC774\uB77C \uC138\uC158\uC5D0 \uC804\uB2EC\uD560 \uB0B4\uC6A9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB0B4\uB824\uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
1077
1285
  "tg.inbound.noSession": "\u26A0\uFE0F \uD30C\uC77C\uC740 \uC138\uC158 \uD1A0\uD53D \uC548\uC5D0\uC11C \uBCF4\uB0B4\uC8FC\uC138\uC694 \u2014 \uC774 \uD1A0\uD53D\uC5D0 \uC5F0\uACB0\uB41C \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -1147,6 +1355,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
1147
1355
  "doctor.telegram.ok": "\uD154\uB808\uADF8\uB7A8: \uC5F0\uACB0\uB428",
1148
1356
  "doctor.telegram.off": "\uD154\uB808\uADF8\uB7A8: \uBBF8\uC2E4\uD589",
1149
1357
  "doctor.telegram.problems": "\uD154\uB808\uADF8\uB7A8: {problems}",
1358
+ "quota.gauge": "{agent}: {window} \uD55C\uB3C4\uC758 {percent}% \uC0AC\uC6A9, {resets} \uD6C4 \uCD08\uAE30\uD654",
1359
+ "quota.gaugeStale": "{agent}: {window} \uD55C\uB3C4\uC758 {percent}% \uC0AC\uC6A9, {resets} \uD6C4 \uCD08\uAE30\uD654 ({age} \uC804 \uAE30\uB85D)",
1360
+ "quota.gaugeElapsed": "{agent}: {window} \uD55C\uB3C4\uC758 {percent}% \uC0AC\uC6A9, \uD574\uB2F9 \uCC3D\uC740 \uC774\uBBF8 \uCD08\uAE30\uD654\uB428",
1361
+ "quota.reached": "{agent}: \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD568 \u2014 {window} \uD55C\uB3C4\uC758 {percent}%, {resets} \uD6C4 \uCD08\uAE30\uD654",
1362
+ "quota.event": "{agent}: {age} \uC804 \uD55C\uB3C4\uC5D0 \uAC78\uB838\uC2B5\uB2C8\uB2E4 \u2014 {text}",
1363
+ "quota.eventNone": "{agent}: \uD55C\uB3C4 \uAE30\uB85D \uC5C6\uC74C (\uC0AC\uC6A9\uB960\uC744 \uB0A8\uAE30\uC9C0 \uC54A\uB294 \uC5D0\uC774\uC804\uD2B8)",
1364
+ "quota.noData": "{agent}: \uC544\uC9C1 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uBA3C\uC800 \uD55C \uD134 \uC2E4\uD589\uD558\uC138\uC694",
1365
+ "quota.unsupported": "{agent}: \uC0AC\uC6A9\uB7C9 \uD55C\uB3C4\uB97C \uC804\uD600 \uBCF4\uACE0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
1366
+ "cli.quota.none": "\uC774 \uCEF4\uD4E8\uD130\uC758 \uC5B4\uB5A4 \uC5D0\uC774\uC804\uD2B8\uB3C4 \uC0AC\uC6A9\uB7C9 \uD55C\uB3C4\uB97C \uBCF4\uACE0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
1150
1367
  "fanout.needCountOrBranches": "\uBE0C\uB79C\uCE58 \uC218\uB098 \uBE0C\uB79C\uCE58 \uBAA9\uB85D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
1151
1368
  "fanout.needPrompt": "\uD504\uB86C\uD504\uD2B8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4",
1152
1369
  "fanout.outOfRange": "1..{max} \uBC94\uC704\uC5EC\uC57C \uD569\uB2C8\uB2E4",
@@ -1170,8 +1387,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
1170
1387
  "health.telegram.noUsers": "Telegram\uC774 \uCF1C\uC838 \uC788\uC9C0\uB9CC \uC870\uC791\uC744 \uD5C8\uC6A9\uD55C \uC0AC\uC6A9\uC790\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4",
1171
1388
  "wizard.prompt.locale": "\uC5B8\uC5B4",
1172
1389
  "wizard.prompt.machine": "\uC774 \uBA38\uC2E0\uC758 \uC774\uB984 (\uCC44\uB110\uC5D0\uC11C \uC9C0\uC815\uD560 \uB54C \uC500)",
1173
- "wizard.prompt.agent": "\uAE30\uBCF8 \uC5D0\uC774\uC804\uD2B8 \uBA85\uB839",
1390
+ "wizard.prompt.agent": "\uAE30\uBCF8 \uC5D0\uC774\uC804\uD2B8 (\uCD94\uAC00 \uD50C\uB798\uADF8\uB294 defaults.agentArgs.<\uC5D0\uC774\uC804\uD2B8> \uC5D0)",
1174
1391
  "wizard.prompt.telegram": "\uC9C0\uAE08 Telegram \uC81C\uC5B4\uB97C \uC124\uC815\uD560\uAE4C\uC694?",
1392
+ "wizard.prompt.agentApproval": "\uC774 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC2B9\uC778 \uC5C6\uC774 \uC791\uC5C5\uD558\uB3C4\uB85D \uD560\uAE4C\uC694? \uBAA8\uB4E0 \uAD8C\uD55C \uD655\uC778\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4 \u2014 \uD3F0\uC5D0\uC11C \uD3B8\uD558\uC9C0\uB9CC, \uC138\uC158 \uD3F4\uB354 \uC548\uC5D0\uC11C \uBB34\uC5C7\uC774\uB4E0 \uC218\uC815\xB7\uC2E4\uD589\uD560 \uC218 \uC788\uAC8C \uB429\uB2C8\uB2E4. `axterm config set defaults.autoApprove false` \uB85C \uB418\uB3CC\uB9AC\uAC70\uB098, \uD55C \uC138\uC158\uB9CC\uC774\uB77C\uBA74 `axterm run --no-auto-approve` \uB97C \uC4F0\uC138\uC694.",
1175
1393
  "wizard.prompt.telegramToken": "@BotFather \uC5D0\uC11C \uBC1B\uC740 \uBD07 \uD1A0\uD070",
1176
1394
  "wizard.prompt.telegramGroup": "\uADF8\uB8F9 id (\uC74C\uC218, \uBCF4\uD1B5 -100 \uC73C\uB85C \uC2DC\uC791. `axterm telegram discover` \uB85C \uD655\uC778 \uAC00\uB2A5)",
1177
1395
  "wizard.prompt.telegramUsers": "axterm \uC870\uC791\uC744 \uD5C8\uC6A9\uD560 Telegram user id (\uC27C\uD45C\uB85C \uAD6C\uBD84. `axterm telegram discover` \uB85C \uD655\uC778 \uAC00\uB2A5)",
@@ -1211,6 +1429,8 @@ function defaultFor(step, config, hostname) {
1211
1429
  return config.machine?.name ?? hostname;
1212
1430
  case "agent":
1213
1431
  return config.defaults?.agent ?? "omp";
1432
+ case "agentApproval":
1433
+ return "no";
1214
1434
  case "telegram":
1215
1435
  return telegram?.enabled === true ? "yes" : "no";
1216
1436
  case "telegramGroup":
@@ -1244,6 +1464,7 @@ function collect(answers) {
1244
1464
  ["machine.name", answers["machine"] ?? ""],
1245
1465
  ["defaults.agent", answers["agent"] ?? "omp"]
1246
1466
  ];
1467
+ settings.push(["defaults.autoApprove", YES.has(answers["agentApproval"] ?? "") ? "true" : "false"]);
1247
1468
  const secrets = [];
1248
1469
  const wantsTelegram = YES.has(answers["telegram"] ?? "");
1249
1470
  settings.push(["channels.telegram.enabled", wantsTelegram ? "true" : "false"]);
@@ -1281,6 +1502,7 @@ var YES, NO, numericList = (value) => {
1281
1502
  return parts.length > 0 && parts.every((p) => /^-?\d+$/.test(p)) ? undefined : "wizard.error.numberList";
1282
1503
  }, STEPS, wizardConfig;
1283
1504
  var init_setup_wizard = __esm(() => {
1505
+ init_agent_launch();
1284
1506
  init_i18n();
1285
1507
  YES = new Set(["y", "yes", "true", "1"]);
1286
1508
  NO = new Set(["n", "no", "false", "0"]);
@@ -1299,9 +1521,17 @@ var init_setup_wizard = __esm(() => {
1299
1521
  next: () => "agent"
1300
1522
  },
1301
1523
  agent: {
1302
- kind: "text",
1524
+ kind: "select",
1303
1525
  promptKey: "wizard.prompt.agent",
1304
- validate: (v) => v.trim().length > 0 ? undefined : "wizard.error.agent",
1526
+ choices: [...supportedAgents()],
1527
+ validate: (v) => supportedAgents().includes(v.trim()) ? undefined : "wizard.error.agent",
1528
+ next: () => "agentApproval"
1529
+ },
1530
+ agentApproval: {
1531
+ kind: "select",
1532
+ promptKey: "wizard.prompt.agentApproval",
1533
+ choices: ["yes", "no"],
1534
+ validate: (v) => YES.has(v) || NO.has(v) ? undefined : "wizard.error.yesNo",
1305
1535
  next: () => "telegram"
1306
1536
  },
1307
1537
  telegram: {
@@ -1371,7 +1601,7 @@ function oscTitle(title) {
1371
1601
  const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
1372
1602
  return `\x1B]0;${safe}\x07`;
1373
1603
  }
1374
- var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, MAX_TITLE_LENGTH = 200;
1604
+ var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, REGISTERED_AGENTS, MAX_TITLE_LENGTH = 200;
1375
1605
  var init_agent_registry = __esm(() => {
1376
1606
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1377
1607
  PROFILES = {
@@ -1380,6 +1610,8 @@ var init_agent_registry = __esm(() => {
1380
1610
  displayName: "OMP",
1381
1611
  title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
1382
1612
  hostHookSource: "omp",
1613
+ approvalBypassArgs: ["--approval-mode", "yolo"],
1614
+ quota: "gauge",
1383
1615
  resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
1384
1616
  },
1385
1617
  pi: {
@@ -1399,6 +1631,8 @@ var init_agent_registry = __esm(() => {
1399
1631
  synthesizeWorking: false
1400
1632
  },
1401
1633
  hostHookSource: "codex",
1634
+ quota: "gauge",
1635
+ approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
1402
1636
  structuredApprovals: true,
1403
1637
  resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
1404
1638
  },
@@ -1443,6 +1677,8 @@ var init_agent_registry = __esm(() => {
1443
1677
  synthesizeTerminal: false
1444
1678
  },
1445
1679
  hostHookSource: "claude",
1680
+ quota: "event",
1681
+ approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
1446
1682
  resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
1447
1683
  },
1448
1684
  gemini: {
@@ -1456,11 +1692,13 @@ var init_agent_registry = __esm(() => {
1456
1692
  synthesizeTerminal: false
1457
1693
  },
1458
1694
  hostHookSource: "gemini",
1695
+ approvalBypassArgs: ["--approval-mode", "yolo"],
1459
1696
  hookAck: "{}",
1460
1697
  approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
1461
1698
  resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
1462
1699
  }
1463
1700
  };
1701
+ REGISTERED_AGENTS = Object.keys(PROFILES);
1464
1702
  });
1465
1703
 
1466
1704
  // packages/core/src/agent-resume.ts
@@ -1518,6 +1756,13 @@ function hookLaunch(input) {
1518
1756
  function isHookInvocation(args) {
1519
1757
  return args[0] === HOOK_ARGV_FLAG && typeof args[1] === "string" && args[1] !== "";
1520
1758
  }
1759
+ function mcpLaunch(input) {
1760
+ const base = daemonLaunch(input);
1761
+ return { command: base.command, args: [...base.args.slice(0, -1), MCP_ARGV_FLAG] };
1762
+ }
1763
+ function isMcpInvocation(args) {
1764
+ return args[0] === MCP_ARGV_FLAG;
1765
+ }
1521
1766
  function hookPostTarget(env, agent) {
1522
1767
  const port = Number(env["AXTERM_HOOK_PORT"]);
1523
1768
  const token = env["AXTERM_HOOK_TOKEN"];
@@ -1540,7 +1785,7 @@ function hookPostTarget(env, agent) {
1540
1785
  function isDaemonInvocation(args) {
1541
1786
  return args[0] === DAEMON_ARGV_FLAG;
1542
1787
  }
1543
- var DAEMON_ARGV_FLAG = "__daemon", HOOK_ARGV_FLAG = "__hook", RUNTIME_NAMES;
1788
+ var DAEMON_ARGV_FLAG = "__daemon", HOOK_ARGV_FLAG = "__hook", MCP_ARGV_FLAG = "__mcp", RUNTIME_NAMES;
1544
1789
  var init_self_spawn = __esm(() => {
1545
1790
  RUNTIME_NAMES = new Set(["bun", "bun.exe", "bun-debug", "bun-debug.exe", "node", "node.exe"]);
1546
1791
  });
@@ -1619,6 +1864,15 @@ function updateNeeded(current, latest) {
1619
1864
  return false;
1620
1865
  return Bun.semver.order(latest, current) > 0;
1621
1866
  }
1867
+ function classifyRuntimeLane(main) {
1868
+ const normalised = main.replace(/\\/g, "/");
1869
+ if (normalised.split("/").includes("node_modules")) {
1870
+ return normalised.includes("/.bun/") ? "bun" : "npm";
1871
+ }
1872
+ if (normalised.endsWith(".ts") || normalised.endsWith(".js"))
1873
+ return "checkout";
1874
+ return "binary";
1875
+ }
1622
1876
  function planBinarySwap(input) {
1623
1877
  const steps = [];
1624
1878
  if (input.platform !== "win32")
@@ -1628,16 +1882,16 @@ function planBinarySwap(input) {
1628
1882
  return steps;
1629
1883
  }
1630
1884
  function planSessionRestore(sessions) {
1631
- const running = sessions.filter((s) => s.state === "running");
1885
+ const live = sessions.filter((s) => s.state !== "exited");
1632
1886
  const resumable = (s) => agentProfile(agentIdFromCommand(s.agentCommand)).resume !== undefined;
1633
1887
  return {
1634
- resume: running.filter(resumable).map((s) => s.id),
1635
- cannotResume: running.filter((s) => !resumable(s)),
1636
- needsConfirmation: running.length > 0
1888
+ resume: live.filter(resumable).map((s) => s.id),
1889
+ cannotResume: live.filter((s) => !resumable(s)),
1890
+ needsConfirmation: live.length > 0
1637
1891
  };
1638
1892
  }
1639
1893
  function planUpdateRestart(input) {
1640
- const live = input.sessions.filter((s) => s.state === "running").length;
1894
+ const live = input.sessions.filter((s) => s.state !== "exited").length;
1641
1895
  if (!input.restart && live > 0) {
1642
1896
  return { stopDaemon: false, resume: [], lost: [], stale: live, needsConfirmation: false };
1643
1897
  }
@@ -1785,6 +2039,44 @@ class EchoGuard {
1785
2039
  this.pending.delete(sessionId);
1786
2040
  }
1787
2041
  }
2042
+
2043
+ // packages/core/src/ask.ts
2044
+ function answerText(answer) {
2045
+ if (!answer)
2046
+ return "(no answer)";
2047
+ const parts = [...answer.selected];
2048
+ if (answer.customInput !== undefined && answer.customInput.length > 0)
2049
+ parts.push(answer.customInput);
2050
+ return parts.join(", ") || "(no answer)";
2051
+ }
2052
+ function detailsFor(question, answer) {
2053
+ return {
2054
+ ...question.id !== undefined ? { id: question.id } : {},
2055
+ question: question.question,
2056
+ options: question.options.map((o) => o.label),
2057
+ multi: question.multi,
2058
+ selectedOptions: answer?.selected ?? [],
2059
+ ...answer?.customInput !== undefined ? { customInput: answer.customInput } : {}
2060
+ };
2061
+ }
2062
+ function formatAskResult(questions, answers) {
2063
+ const byIndex = new Map(answers.map((a) => [a.questionIndex, a]));
2064
+ if (questions.length === 1) {
2065
+ const question = questions[0];
2066
+ return {
2067
+ text: answerText(byIndex.get(0)),
2068
+ details: detailsFor(question, byIndex.get(0))
2069
+ };
2070
+ }
2071
+ const lines = questions.map((q, i) => `${q.id ?? String(i)}: ${answerText(byIndex.get(i))}`);
2072
+ return {
2073
+ text: `User answers:
2074
+ ${lines.join(`
2075
+ `)}`,
2076
+ details: { results: questions.map((q, i) => detailsFor(q, byIndex.get(i))) }
2077
+ };
2078
+ }
2079
+
1788
2080
  // packages/core/src/choice.ts
1789
2081
  function buildApprovalQuestion(toolName) {
1790
2082
  return {
@@ -2015,8 +2307,7 @@ function mapClaudeHook(input) {
2015
2307
  ...input.tool_input !== undefined ? { tool_input: input.tool_input } : {}
2016
2308
  }
2017
2309
  ];
2018
- case "Stop":
2019
- case "StopFailure": {
2310
+ case "Stop": {
2020
2311
  const out = [];
2021
2312
  const text = input.last_assistant_message;
2022
2313
  if (typeof text === "string" && text.trim() !== "") {
@@ -2025,6 +2316,19 @@ function mapClaudeHook(input) {
2025
2316
  out.push({ event: "agent_end" });
2026
2317
  return out;
2027
2318
  }
2319
+ case "StopFailure": {
2320
+ const parts = [input.error, input.error_details, input.last_assistant_message].filter((part) => typeof part === "string" && part.trim() !== "").map((part) => part.trim());
2321
+ return [
2322
+ { event: "agent_error", error_kind: "failed", text: parts.join(" \u2014 ") },
2323
+ { event: "agent_end" }
2324
+ ];
2325
+ }
2326
+ case "MessageDisplay": {
2327
+ const delta = input.delta;
2328
+ if (typeof delta !== "string" || delta === "")
2329
+ return [];
2330
+ return [{ event: "activity", activity: "reply", text: delta }];
2331
+ }
2028
2332
  case "SessionEnd":
2029
2333
  return [
2030
2334
  { event: "session_shutdown", ...input.session_id !== undefined ? { session_id: input.session_id } : {} }
@@ -2057,16 +2361,26 @@ function buildClaudeSettings(input) {
2057
2361
  var MAX_RESULT_TEXT = 2000, HOOK_TIMEOUT_SECONDS = 5, TOOL_EVENTS, PLAIN_EVENTS;
2058
2362
  var init_claude_hooks = __esm(() => {
2059
2363
  TOOL_EVENTS = ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest"];
2060
- PLAIN_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure", "SessionEnd"];
2364
+ PLAIN_EVENTS = [
2365
+ "SessionStart",
2366
+ "UserPromptSubmit",
2367
+ "Stop",
2368
+ "StopFailure",
2369
+ "MessageDisplay",
2370
+ "SessionEnd"
2371
+ ];
2061
2372
  });
2062
2373
 
2063
- // packages/core/src/gemini-onboarding.ts
2064
- function detectGeminiOnboarding(screen) {
2374
+ // packages/core/src/onboarding.ts
2375
+ function detectOnboarding(agent, screen) {
2376
+ const dialogs = DIALOGS[agent];
2377
+ if (dialogs === undefined)
2378
+ return;
2065
2379
  const lines = screen.split(`
2066
2380
  `).map((line) => line.replace(/^\s*\u2502/, "").replace(/\u2502\s*$/, "").trim());
2067
2381
  for (let i = lines.length - 1;i >= 0; i--) {
2068
2382
  const line = lines[i];
2069
- const dialog = DIALOGS.find((d) => line === d.question);
2383
+ const dialog = dialogs.find((d) => line.startsWith(d.question));
2070
2384
  if (!dialog)
2071
2385
  continue;
2072
2386
  const options = [];
@@ -2082,12 +2396,19 @@ function detectGeminiOnboarding(screen) {
2082
2396
  return;
2083
2397
  }
2084
2398
  var DIALOGS, OPTION_ROW, OPTION_WINDOW_LINES = 8;
2085
- var init_gemini_onboarding = __esm(() => {
2086
- DIALOGS = [
2087
- { kind: "folderTrust", question: "Do you trust the files in this folder?" },
2088
- { kind: "authMethod", question: "How would you like to authenticate for this project?" }
2089
- ];
2090
- OPTION_ROW = /^[\u25CF\u25CB]?\s*(\d)\.\s+(.+)$/;
2399
+ var init_onboarding = __esm(() => {
2400
+ DIALOGS = {
2401
+ gemini: [
2402
+ { kind: "folderTrust", question: "Do you trust the files in this folder?" },
2403
+ { kind: "authMethod", question: "How would you like to authenticate for this project?" }
2404
+ ],
2405
+ claude: [
2406
+ { kind: "apiKey", question: "Do you want to use this API key?" },
2407
+ { kind: "loginMethod", question: "Select login method:" }
2408
+ ],
2409
+ codex: [{ kind: "folderTrust", question: "Do you trust the contents of this directory?" }]
2410
+ };
2411
+ OPTION_ROW = /^[\u25CF\u25CB\u276F>\u203A]?\s*(\d)\.\s+(.+)$/;
2091
2412
  });
2092
2413
 
2093
2414
  // packages/core/src/doctor.ts
@@ -2222,6 +2543,427 @@ var init_doctor = __esm(() => {
2222
2543
  GEMINI_FORBIDDEN_EVENTS = ["AfterModel", "BeforeModel"];
2223
2544
  });
2224
2545
 
2546
+ // packages/core/src/stall.ts
2547
+ function shouldReportStall(input) {
2548
+ if (input.reported)
2549
+ return false;
2550
+ if (input.agentState !== "working")
2551
+ return false;
2552
+ if (input.quietForMs <= input.thresholdMs)
2553
+ return false;
2554
+ if (input.storeQuietForMs !== undefined && input.storeQuietForMs <= input.thresholdMs)
2555
+ return false;
2556
+ return true;
2557
+ }
2558
+ function quoteScreenTail(screen, maxLines) {
2559
+ const kept = [];
2560
+ const lines = screen.split(`
2561
+ `);
2562
+ for (let i = lines.length - 1;i >= 0 && kept.length < maxLines; i--) {
2563
+ const line = lines[i];
2564
+ if (CHROME_ONLY.test(line))
2565
+ continue;
2566
+ const text = line.replace(EDGE_CHROME, "");
2567
+ if (text === "")
2568
+ continue;
2569
+ kept.push(text);
2570
+ }
2571
+ return kept.reverse();
2572
+ }
2573
+ var CHROME_ONLY, EDGE_CHROME;
2574
+ var init_stall = __esm(() => {
2575
+ CHROME_ONLY = /^[\s\u2502\u2503|\u256D\u256E\u2570\u256F\u251C\u2524\u252C\u2534\u253C\u2500\u2501\u2550\u254C\u254D\u2504\u2505.\u00B7*]*$/u;
2576
+ EDGE_CHROME = /^[\s\u2502\u2503|]+|[\s\u2502\u2503|]+$/gu;
2577
+ });
2578
+
2579
+ // packages/core/src/codex-stream.ts
2580
+ class CodexStreamMapper {
2581
+ finalAnswerItems = new Set;
2582
+ handle(method, params) {
2583
+ switch (method) {
2584
+ case "item/started": {
2585
+ const item = params["item"];
2586
+ if (!isRecord(item) || item["type"] !== "agentMessage")
2587
+ return [];
2588
+ if (item["phase"] === "final_answer" && typeof item["id"] === "string") {
2589
+ this.finalAnswerItems.add(item["id"]);
2590
+ }
2591
+ return [];
2592
+ }
2593
+ case "item/reasoning/summaryTextDelta": {
2594
+ const delta = params["delta"];
2595
+ if (typeof delta !== "string" || delta === "")
2596
+ return [];
2597
+ return [{ event: "activity", activity: "thinking", text: delta }];
2598
+ }
2599
+ case "item/agentMessage/delta": {
2600
+ const delta = params["delta"];
2601
+ const itemId = params["itemId"];
2602
+ if (typeof delta !== "string" || delta === "" || typeof itemId !== "string")
2603
+ return [];
2604
+ if (!this.finalAnswerItems.has(itemId))
2605
+ return [];
2606
+ return [{ event: "activity", activity: "reply", text: delta }];
2607
+ }
2608
+ case "error": {
2609
+ const willRetry = params["willRetry"] === true;
2610
+ const raw = params["error"];
2611
+ const text = typeof raw === "string" ? raw : isRecord(raw) && typeof raw["message"] === "string" ? raw["message"] : JSON.stringify(raw ?? {});
2612
+ return [{ event: "agent_error", error_kind: willRetry ? "retry" : "failed", text }];
2613
+ }
2614
+ case "thread/status/changed": {
2615
+ const status = params["status"];
2616
+ if (isRecord(status) && status["type"] === "idle")
2617
+ this.finalAnswerItems.clear();
2618
+ return [];
2619
+ }
2620
+ default:
2621
+ return [];
2622
+ }
2623
+ }
2624
+ }
2625
+ var init_codex_stream = () => {};
2626
+
2627
+ // packages/core/src/keys.ts
2628
+ function keySequence(name) {
2629
+ return SEQUENCES[name];
2630
+ }
2631
+ function parseKeyNames(input) {
2632
+ const names = input.trim().split(/\s+/).filter((name) => name !== "");
2633
+ if (names.length === 0)
2634
+ return { unknown: "" };
2635
+ const keys = [];
2636
+ for (const name of names) {
2637
+ const sequence = keySequence(name);
2638
+ if (sequence === undefined)
2639
+ return { unknown: name };
2640
+ keys.push(sequence);
2641
+ }
2642
+ return { keys };
2643
+ }
2644
+ var SEQUENCES, KEY_NAMES;
2645
+ var init_keys = __esm(() => {
2646
+ SEQUENCES = {
2647
+ approve: "\r",
2648
+ deny: "\x1B[B\r",
2649
+ enter: "\r",
2650
+ esc: "\x1B",
2651
+ up: "\x1B[A",
2652
+ down: "\x1B[B",
2653
+ left: "\x1B[D",
2654
+ right: "\x1B[C",
2655
+ tab: "\t",
2656
+ space: " ",
2657
+ "ctrl+c": "\x03",
2658
+ "ctrl+d": "\x04",
2659
+ backspace: "\x7F",
2660
+ "shift+tab": "\x1B[Z"
2661
+ };
2662
+ KEY_NAMES = Object.keys(SEQUENCES).sort();
2663
+ });
2664
+
2665
+ // packages/core/src/duration.ts
2666
+ function humanDuration(ms) {
2667
+ if (!Number.isFinite(ms) || ms <= 0)
2668
+ return "0s";
2669
+ const seconds = Math.max(1, Math.round(ms / 1000));
2670
+ if (seconds < 60)
2671
+ return `${seconds}s`;
2672
+ const minutes = Math.round(seconds / 60);
2673
+ if (minutes < 60)
2674
+ return `${minutes}m`;
2675
+ const hours = Math.floor(minutes / 60);
2676
+ if (hours < 24) {
2677
+ const rest = minutes % 60;
2678
+ return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
2679
+ }
2680
+ const days = Math.floor(hours / 24);
2681
+ const restHours = hours % 24;
2682
+ return restHours === 0 ? `${days}d` : `${days}d ${restHours}h`;
2683
+ }
2684
+
2685
+ // packages/core/src/quota.ts
2686
+ function quotaReport(facts, nowMs) {
2687
+ const out = [];
2688
+ for (const fact of facts) {
2689
+ if (fact.kind === "none") {
2690
+ out.push({ id: `quota.${fact.agent}`, status: "info", key: "quota.unsupported", params: { agent: fact.agent } });
2691
+ continue;
2692
+ }
2693
+ if (fact.kind === "event") {
2694
+ if (!fact.event) {
2695
+ out.push({ id: `quota.${fact.agent}`, status: "ok", key: "quota.eventNone", params: { agent: fact.agent } });
2696
+ continue;
2697
+ }
2698
+ out.push({
2699
+ id: `quota.${fact.agent}`,
2700
+ status: "warn",
2701
+ key: "quota.event",
2702
+ params: {
2703
+ agent: fact.agent,
2704
+ age: humanDuration(Math.max(0, nowMs - fact.event.at)),
2705
+ text: fact.event.text
2706
+ }
2707
+ });
2708
+ continue;
2709
+ }
2710
+ const reading = fact.reading;
2711
+ if (!reading || reading.windows.length === 0) {
2712
+ out.push({ id: `quota.${fact.agent}`, status: "info", key: "quota.noData", params: { agent: fact.agent } });
2713
+ continue;
2714
+ }
2715
+ const age = nowMs - reading.recordedAt;
2716
+ reading.windows.forEach((window2, index) => {
2717
+ const params = {
2718
+ agent: fact.agent,
2719
+ percent: window2.usedPercent,
2720
+ window: window2.label ?? (window2.windowMinutes === undefined ? "?" : humanDuration(window2.windowMinutes * 60000)),
2721
+ resets: humanDuration(Math.max(0, window2.resetsAt - nowMs)),
2722
+ plan: reading.planType ?? "",
2723
+ age: humanDuration(Math.max(0, age))
2724
+ };
2725
+ const id = reading.windows.length === 1 ? `quota.${fact.agent}` : `quota.${fact.agent}.${index}`;
2726
+ if (reading.limitReached) {
2727
+ out.push({ id, status: "warn", key: "quota.reached", params });
2728
+ return;
2729
+ }
2730
+ if (window2.resetsAt <= nowMs) {
2731
+ out.push({ id, status: "info", key: "quota.gaugeElapsed", params });
2732
+ return;
2733
+ }
2734
+ out.push({ id, status: "ok", key: age > STALE_AFTER_MS ? "quota.gaugeStale" : "quota.gauge", params });
2735
+ });
2736
+ }
2737
+ return out;
2738
+ }
2739
+ var STALE_AFTER_MS = 3600000;
2740
+ var init_quota = () => {};
2741
+
2742
+ // packages/core/src/gemini-telemetry.ts
2743
+ function splitTelemetryRecords(buffer) {
2744
+ if (buffer === "")
2745
+ return { complete: [], remainder: "" };
2746
+ const chunks = buffer.split(/\n(?=\{)/);
2747
+ const last = chunks.pop() ?? "";
2748
+ const complete = chunks.filter((chunk) => chunk.trim() !== "");
2749
+ if (/\n\s*$/.test(last) || tryParse(last) !== undefined) {
2750
+ if (last.trim() !== "")
2751
+ complete.push(last);
2752
+ return { complete, remainder: "" };
2753
+ }
2754
+ return { complete, remainder: last };
2755
+ }
2756
+ function tryParse(text) {
2757
+ if (text.trim() === "")
2758
+ return;
2759
+ try {
2760
+ const value = JSON.parse(text);
2761
+ return isRecord(value) ? value : undefined;
2762
+ } catch {
2763
+ return;
2764
+ }
2765
+ }
2766
+ function attributesOf(record) {
2767
+ const attributes = record["attributes"];
2768
+ if (Array.isArray(attributes)) {
2769
+ const out = {};
2770
+ for (const entry of attributes) {
2771
+ if (Array.isArray(entry) && typeof entry[0] === "string")
2772
+ out[entry[0]] = entry[1];
2773
+ }
2774
+ return out;
2775
+ }
2776
+ return isRecord(attributes) ? attributes : {};
2777
+ }
2778
+ function parseGeminiTelemetry(records) {
2779
+ const failures = [];
2780
+ for (const raw of records) {
2781
+ const record = tryParse(raw);
2782
+ if (record === undefined)
2783
+ continue;
2784
+ const attributes = attributesOf(record);
2785
+ if (attributes["event.name"] !== "gemini_cli.api_error")
2786
+ continue;
2787
+ const type = typeof attributes["error.type"] === "string" ? attributes["error.type"] : "";
2788
+ const message = typeof attributes["error.message"] === "string" ? attributes["error.message"] : "";
2789
+ const timestamp = attributes["event.timestamp"];
2790
+ const at = typeof timestamp === "string" ? Date.parse(timestamp) : Number.NaN;
2791
+ const status = attributes["status_code"];
2792
+ failures.push({
2793
+ ...Number.isFinite(at) ? { at } : {},
2794
+ ...typeof status === "number" ? { status } : {},
2795
+ text: [type, message].filter((part) => part !== "").join(": ")
2796
+ });
2797
+ }
2798
+ return failures;
2799
+ }
2800
+ var init_gemini_telemetry = () => {};
2801
+
2802
+ // packages/core/src/mcp-ask.ts
2803
+ function fieldName(index) {
2804
+ return `q${index}`;
2805
+ }
2806
+ function elicitationSchema(questions) {
2807
+ const properties = {};
2808
+ const required = [];
2809
+ questions.forEach((question, index) => {
2810
+ const labels = question.options.map((option) => option.label).filter((label) => label !== "");
2811
+ properties[fieldName(index)] = {
2812
+ type: "string",
2813
+ title: question.question,
2814
+ ...labels.length > 0 ? { enum: labels } : {}
2815
+ };
2816
+ required.push(fieldName(index));
2817
+ });
2818
+ return { type: "object", properties, required };
2819
+ }
2820
+ function answersFromElicitation(questions, result) {
2821
+ if (!result || result.action !== "accept" || !isRecord(result.content))
2822
+ return;
2823
+ const answers = [];
2824
+ questions.forEach((question, index) => {
2825
+ const value = result.content?.[fieldName(index)];
2826
+ if (typeof value !== "string" || value === "")
2827
+ return;
2828
+ const matched = question.options.some((option) => option.label === value);
2829
+ answers.push(matched ? { questionIndex: index, selected: [value] } : { questionIndex: index, selected: [], customInput: value });
2830
+ });
2831
+ return answers.length > 0 ? answers : undefined;
2832
+ }
2833
+ var init_mcp_ask = () => {};
2834
+
2835
+ // packages/core/src/store-locate.ts
2836
+ function claudeHash(text) {
2837
+ let hash = 0;
2838
+ for (let i = 0;i < text.length; i++)
2839
+ hash = (hash << 5) - hash + text.charCodeAt(i) | 0;
2840
+ return hash;
2841
+ }
2842
+ function claudeProjectKey(cwd) {
2843
+ const key = cwd.replace(/[^a-zA-Z0-9]/g, "-");
2844
+ if (key.length <= CLAUDE_KEY_LIMIT)
2845
+ return key;
2846
+ return `${key.slice(0, CLAUDE_KEY_LIMIT)}-${Math.abs(claudeHash(cwd)).toString(36)}`;
2847
+ }
2848
+ function claudeTranscriptRelPath(cwd, sessionId) {
2849
+ return `projects/${claudeProjectKey(cwd)}/${sessionId}.jsonl`;
2850
+ }
2851
+ function uuidV7TimeMs(id) {
2852
+ const hex = id.replace(/-/g, "");
2853
+ if (!/^[0-9a-f]{32}$/i.test(hex))
2854
+ return;
2855
+ const ms = Number.parseInt(hex.slice(0, 12), 16);
2856
+ return Number.isFinite(ms) && ms > 0 ? ms : undefined;
2857
+ }
2858
+ function shard(date) {
2859
+ const month = String(date.getMonth() + 1).padStart(2, "0");
2860
+ const day = String(date.getDate()).padStart(2, "0");
2861
+ return `${date.getFullYear()}/${month}/${day}`;
2862
+ }
2863
+ function codexRolloutShards(sessionId) {
2864
+ const ms = uuidV7TimeMs(sessionId);
2865
+ if (ms === undefined)
2866
+ return [];
2867
+ return [shard(new Date(ms)), shard(new Date(ms - 86400000)), shard(new Date(ms + 86400000))];
2868
+ }
2869
+ var CLAUDE_KEY_LIMIT = 200;
2870
+
2871
+ // packages/core/src/store-quota.ts
2872
+ function parseLine(line) {
2873
+ if (line.trim() === "")
2874
+ return;
2875
+ try {
2876
+ const value = JSON.parse(line);
2877
+ return isRecord(value) ? value : undefined;
2878
+ } catch {
2879
+ return;
2880
+ }
2881
+ }
2882
+ function numberOf(value) {
2883
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
2884
+ }
2885
+ function codexWindow(value) {
2886
+ if (!isRecord(value))
2887
+ return;
2888
+ const usedPercent = numberOf(value["used_percent"]);
2889
+ const resetsAtSeconds = numberOf(value["resets_at"]);
2890
+ if (usedPercent === undefined || resetsAtSeconds === undefined)
2891
+ return;
2892
+ const windowMinutes = numberOf(value["window_minutes"]);
2893
+ return {
2894
+ usedPercent,
2895
+ resetsAt: resetsAtSeconds * 1000,
2896
+ ...windowMinutes === undefined ? {} : { windowMinutes }
2897
+ };
2898
+ }
2899
+ function parseCodexQuota(lines) {
2900
+ for (let i = lines.length - 1;i >= 0; i--) {
2901
+ const record = parseLine(lines[i]);
2902
+ const payload = record?.["payload"];
2903
+ if (!isRecord(payload) || payload["type"] !== "token_count")
2904
+ continue;
2905
+ const limits = payload["rate_limits"];
2906
+ if (!isRecord(limits))
2907
+ continue;
2908
+ const windows = [];
2909
+ for (const key of ["primary", "secondary"]) {
2910
+ const window2 = codexWindow(limits[key]);
2911
+ if (window2)
2912
+ windows.push(window2);
2913
+ }
2914
+ if (windows.length === 0)
2915
+ continue;
2916
+ const recordedAt = Date.parse(String(record?.["timestamp"] ?? ""));
2917
+ const planType = limits["plan_type"];
2918
+ return {
2919
+ recordedAt: Number.isFinite(recordedAt) ? recordedAt : 0,
2920
+ windows,
2921
+ limitReached: limits["rate_limit_reached_type"] != null || limits["spend_control_reached"] === true,
2922
+ ...typeof planType === "string" ? { planType } : {}
2923
+ };
2924
+ }
2925
+ return;
2926
+ }
2927
+ function parseOmpQuota(rows) {
2928
+ const windows = [];
2929
+ let recordedAt = 0;
2930
+ let limitReached = false;
2931
+ for (const row of rows) {
2932
+ if (row.used_fraction === null || row.resets_at === null)
2933
+ continue;
2934
+ windows.push({
2935
+ usedPercent: Math.round(row.used_fraction * 100),
2936
+ label: row.label,
2937
+ resetsAt: row.resets_at
2938
+ });
2939
+ recordedAt = Math.max(recordedAt, row.recorded_at);
2940
+ if (row.status !== null && row.status !== "ok")
2941
+ limitReached = true;
2942
+ }
2943
+ return windows.length === 0 ? undefined : { recordedAt, windows, limitReached };
2944
+ }
2945
+ function parseClaudeLimitEvent(lines) {
2946
+ for (let i = lines.length - 1;i >= 0; i--) {
2947
+ const record = parseLine(lines[i]);
2948
+ if (!record || record["error"] !== "rate_limit")
2949
+ continue;
2950
+ const at = Date.parse(String(record["timestamp"] ?? ""));
2951
+ const message = record["message"];
2952
+ let text = "";
2953
+ if (isRecord(message) && Array.isArray(message["content"])) {
2954
+ for (const block of message["content"]) {
2955
+ if (isRecord(block) && typeof block["text"] === "string")
2956
+ text += block["text"];
2957
+ }
2958
+ }
2959
+ if (text === "")
2960
+ continue;
2961
+ return { at: Number.isFinite(at) ? at : 0, text };
2962
+ }
2963
+ return;
2964
+ }
2965
+ var init_store_quota = () => {};
2966
+
2225
2967
  // packages/core/src/version.ts
2226
2968
  function setProductVersion(version) {
2227
2969
  product = version;
@@ -2441,6 +3183,19 @@ function errorText(value) {
2441
3183
  return message.slice(0, MAX_RESULT_TEXT2);
2442
3184
  return llmContentText(value);
2443
3185
  }
3186
+ function permissionToolName(details) {
3187
+ if (!isRecord(details))
3188
+ return;
3189
+ const pick = (key) => {
3190
+ const value = details[key];
3191
+ return typeof value === "string" && value !== "" ? value : undefined;
3192
+ };
3193
+ if (details["type"] === "mcp")
3194
+ return pick("toolName") ?? pick("toolDisplayName") ?? pick("title");
3195
+ if (details["type"] === "exec")
3196
+ return pick("rootCommand") ?? pick("command") ?? pick("title");
3197
+ return pick("title");
3198
+ }
2444
3199
  function mapGeminiHook(input) {
2445
3200
  switch (input.hook_event_name) {
2446
3201
  case "SessionStart":
@@ -2473,15 +3228,17 @@ function mapGeminiHook(input) {
2473
3228
  }
2474
3229
  return out;
2475
3230
  }
2476
- case "Notification":
3231
+ case "Notification": {
2477
3232
  if (input.notification_type !== "ToolPermission")
2478
3233
  return [];
3234
+ const toolName = permissionToolName(input.details);
2479
3235
  return [
2480
3236
  {
2481
3237
  event: "tool_approval_requested",
2482
- ...input.tool_name !== undefined ? { tool_name: input.tool_name } : {}
3238
+ ...toolName !== undefined ? { tool_name: toolName } : {}
2483
3239
  }
2484
3240
  ];
3241
+ }
2485
3242
  case "AfterAgent": {
2486
3243
  const out = [];
2487
3244
  const text = input.prompt_response;
@@ -2513,7 +3270,10 @@ function buildGeminiSettings(input) {
2513
3270
  const hooks = {};
2514
3271
  for (const event of GEMINI_EVENTS)
2515
3272
  hooks[event] = [{ hooks: [handler] }];
2516
- return { hooks };
3273
+ return {
3274
+ hooks,
3275
+ ...input.mcp === undefined ? {} : { mcpServers: { [MCP_SERVER_NAME]: mcpServerEntry(input.mcp, input.mcpContextPath) } }
3276
+ };
2517
3277
  }
2518
3278
  var MAX_RESULT_TEXT2 = 2000, HOOK_TIMEOUT_MS = 2000, GEMINI_EVENTS;
2519
3279
  var init_gemini_hooks = __esm(() => {
@@ -2528,65 +3288,6 @@ var init_gemini_hooks = __esm(() => {
2528
3288
  ];
2529
3289
  });
2530
3290
 
2531
- // packages/core/src/agent-launch.ts
2532
- function uuidFromSessionId(sessionId) {
2533
- const hex = sessionId.replace(/-/g, "");
2534
- if (hex.length !== 32)
2535
- throw new Error(`session id is not 32 hex characters: ${sessionId}`);
2536
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
2537
- }
2538
- function buildAgentCommand(input) {
2539
- const extra = [...input.extraArgs ?? []];
2540
- if (input.agent === "claude") {
2541
- return [
2542
- "claude",
2543
- "--settings",
2544
- input.claudeSettingsPath,
2545
- "--session-id",
2546
- uuidFromSessionId(input.sessionId),
2547
- ...extra
2548
- ];
2549
- }
2550
- if (input.agent === "gemini") {
2551
- return [
2552
- "gemini",
2553
- "--session-id",
2554
- input.sessionId,
2555
- ...extra
2556
- ];
2557
- }
2558
- if (input.agent === "codex") {
2559
- return ["codex", ...extra];
2560
- }
2561
- return ["omp", "--extension", input.extensionPath, ...extra];
2562
- }
2563
- function buildAgentResumeCommand(input) {
2564
- if (input.agent === "claude") {
2565
- return [
2566
- "claude",
2567
- "--settings",
2568
- input.claudeSettingsPath,
2569
- "--resume",
2570
- uuidFromSessionId(input.sessionId),
2571
- ...input.extraArgs ?? []
2572
- ];
2573
- }
2574
- if (input.agent === "gemini") {
2575
- return ["gemini", "--resume", input.sessionId, ...input.extraArgs ?? []];
2576
- }
2577
- return buildAgentCommand(input);
2578
- }
2579
- function isAgentId(value) {
2580
- return AGENT_IDS.includes(value);
2581
- }
2582
- function supportedAgents() {
2583
- return AGENT_IDS;
2584
- }
2585
- var AGENT_IDS;
2586
- var init_agent_launch = __esm(() => {
2587
- AGENT_IDS = ["omp", "claude", "codex", "gemini"];
2588
- });
2589
-
2590
3291
  // packages/core/src/dotenv-scrub.ts
2591
3292
  function dotenvCandidates(nodeEnv) {
2592
3293
  const mode = nodeEnv === undefined || nodeEnv === "" ? "development" : nodeEnv;
@@ -2638,21 +3339,6 @@ var init_dotenv_scrub = __esm(() => {
2638
3339
  LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
2639
3340
  });
2640
3341
 
2641
- // packages/core/src/duration.ts
2642
- function humanDuration(ms) {
2643
- if (!Number.isFinite(ms) || ms <= 0)
2644
- return "0s";
2645
- const seconds = Math.max(1, Math.round(ms / 1000));
2646
- if (seconds < 60)
2647
- return `${seconds}s`;
2648
- const minutes = Math.round(seconds / 60);
2649
- if (minutes < 60)
2650
- return `${minutes}m`;
2651
- const hours = Math.floor(minutes / 60);
2652
- const rest = minutes % 60;
2653
- return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
2654
- }
2655
-
2656
3342
  // packages/core/src/idle-screen.ts
2657
3343
  function screenHash(screen) {
2658
3344
  const normalised = screen.split(`
@@ -2725,8 +3411,26 @@ function sessionInScope(session, scope, resolve = asGiven) {
2725
3411
  return true;
2726
3412
  return session.repoRoot !== undefined && pathWithin(scope, session.repoRoot, resolve);
2727
3413
  }
2728
- function sessionsInScope(sessions, scope, resolve = asGiven) {
2729
- return sessions.filter((session) => sessionInScope(session, scope, resolve));
3414
+ function sessionsInScope(sessions, scope, resolve = asGiven) {
3415
+ return sessions.filter((session) => sessionInScope(session, scope, resolve));
3416
+ }
3417
+ function mostRecentSession(sessions) {
3418
+ let newest;
3419
+ for (const session of sessions) {
3420
+ if (session.state === "exited")
3421
+ continue;
3422
+ if (newest === undefined || session.createdAt > newest.createdAt)
3423
+ newest = session;
3424
+ }
3425
+ return newest;
3426
+ }
3427
+ function sessionsByRecency(live, stored) {
3428
+ const byId = new Map;
3429
+ for (const session of stored)
3430
+ byId.set(session.id, session);
3431
+ for (const session of live)
3432
+ byId.set(session.id, session);
3433
+ return [...byId.values()].sort((a, b) => b.createdAt - a.createdAt);
2730
3434
  }
2731
3435
  var asGiven = (target) => target;
2732
3436
  var init_session_scope = () => {};
@@ -2760,8 +3464,15 @@ var init_src = __esm(() => {
2760
3464
  init_agent_status();
2761
3465
  init_activity();
2762
3466
  init_claude_hooks();
2763
- init_gemini_onboarding();
3467
+ init_onboarding();
2764
3468
  init_doctor();
3469
+ init_stall();
3470
+ init_codex_stream();
3471
+ init_keys();
3472
+ init_quota();
3473
+ init_gemini_telemetry();
3474
+ init_mcp_ask();
3475
+ init_store_quota();
2765
3476
  init_telegram_discovery();
2766
3477
  init_codex_hooks();
2767
3478
  init_gemini_hooks();
@@ -2774,14 +3485,14 @@ var init_src = __esm(() => {
2774
3485
  });
2775
3486
 
2776
3487
  // packages/daemon/src/config-store.ts
2777
- import fs4 from "fs";
2778
- import path5 from "path";
3488
+ import fs5 from "fs";
3489
+ import path6 from "path";
2779
3490
  function writeAtomic(target, contents, mode) {
2780
3491
  const tmp = `${target}.tmp`;
2781
- fs4.writeFileSync(tmp, contents, { mode });
2782
- fs4.renameSync(tmp, target);
3492
+ fs5.writeFileSync(tmp, contents, { mode });
3493
+ fs5.renameSync(tmp, target);
2783
3494
  if (process.platform !== "win32")
2784
- fs4.chmodSync(target, mode);
3495
+ fs5.chmodSync(target, mode);
2785
3496
  }
2786
3497
 
2787
3498
  class ConfigStore {
@@ -2797,16 +3508,16 @@ class ConfigStore {
2797
3508
  }
2798
3509
  load() {
2799
3510
  this.loadError = undefined;
2800
- fs4.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
3511
+ fs5.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
2801
3512
  if (process.platform !== "win32") {
2802
3513
  try {
2803
- fs4.chmodSync(this.paths.configDir, DIR_MODE);
3514
+ fs5.chmodSync(this.paths.configDir, DIR_MODE);
2804
3515
  } catch {}
2805
3516
  }
2806
3517
  this.config = structuredClone(DEFAULT_CONFIG);
2807
- if (fs4.existsSync(this.paths.configFile)) {
3518
+ if (fs5.existsSync(this.paths.configFile)) {
2808
3519
  try {
2809
- const parsed = parseConfig(JSON.parse(fs4.readFileSync(this.paths.configFile, "utf8")));
3520
+ const parsed = parseConfig(JSON.parse(fs5.readFileSync(this.paths.configFile, "utf8")));
2810
3521
  if ("errors" in parsed)
2811
3522
  this.loadError = parsed.errors.join("; ");
2812
3523
  else
@@ -2816,10 +3527,10 @@ class ConfigStore {
2816
3527
  }
2817
3528
  }
2818
3529
  this.secrets = {};
2819
- if (fs4.existsSync(this.paths.secretsFile)) {
3530
+ if (fs5.existsSync(this.paths.secretsFile)) {
2820
3531
  this.tightenPermissions(this.paths.secretsFile);
2821
3532
  try {
2822
- const raw = JSON.parse(fs4.readFileSync(this.paths.secretsFile, "utf8"));
3533
+ const raw = JSON.parse(fs5.readFileSync(this.paths.secretsFile, "utf8"));
2823
3534
  if (raw !== null && typeof raw === "object") {
2824
3535
  for (const [key, value] of Object.entries(raw)) {
2825
3536
  if (typeof value === "string")
@@ -2835,8 +3546,8 @@ class ConfigStore {
2835
3546
  if (process.platform === "win32")
2836
3547
  return;
2837
3548
  try {
2838
- if ((fs4.statSync(file).mode & 63) !== 0)
2839
- fs4.chmodSync(file, FILE_MODE);
3549
+ if ((fs5.statSync(file).mode & 63) !== 0)
3550
+ fs5.chmodSync(file, FILE_MODE);
2840
3551
  } catch {}
2841
3552
  }
2842
3553
  get() {
@@ -2844,7 +3555,7 @@ class ConfigStore {
2844
3555
  }
2845
3556
  health() {
2846
3557
  return configHealth({
2847
- fileExists: fs4.existsSync(this.paths.configFile),
3558
+ fileExists: fs5.existsSync(this.paths.configFile),
2848
3559
  parseError: this.loadError,
2849
3560
  config: this.resolved()
2850
3561
  });
@@ -2889,7 +3600,7 @@ class ConfigStore {
2889
3600
  delete this.secrets[dottedPath];
2890
3601
  else
2891
3602
  this.secrets[dottedPath] = value;
2892
- if (!fs4.existsSync(this.paths.configFile)) {
3603
+ if (!fs5.existsSync(this.paths.configFile)) {
2893
3604
  writeAtomic(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
2894
3605
  `, FILE_MODE);
2895
3606
  }
@@ -2919,7 +3630,7 @@ class ConfigStore {
2919
3630
  fingerprint() {
2920
3631
  const stamp = (file) => {
2921
3632
  try {
2922
- return String(fs4.statSync(file).mtimeMs);
3633
+ return String(fs5.statSync(file).mtimeMs);
2923
3634
  } catch {
2924
3635
  return "-";
2925
3636
  }
@@ -2941,8 +3652,8 @@ function applyPath(config, dottedPath, value) {
2941
3652
  function defaultConfigStorePaths(configDir) {
2942
3653
  return {
2943
3654
  configDir,
2944
- configFile: path5.join(configDir, "config.json"),
2945
- secretsFile: path5.join(configDir, "secrets.json")
3655
+ configFile: path6.join(configDir, "config.json"),
3656
+ secretsFile: path6.join(configDir, "secrets.json")
2946
3657
  };
2947
3658
  }
2948
3659
  var ENV_ALIASES, DIR_MODE = 448, FILE_MODE = 384;
@@ -2961,13 +3672,13 @@ var init_config_store = __esm(() => {
2961
3672
  });
2962
3673
 
2963
3674
  // packages/daemon/src/shell.ts
2964
- import fs5 from "fs";
2965
- import path6 from "path";
3675
+ import fs6 from "fs";
3676
+ import path7 from "path";
2966
3677
  function findExecutable(name, env) {
2967
3678
  const isWindows = env.platform === "win32";
2968
- const join = isWindows ? path6.win32.join : path6.posix.join;
3679
+ const join = isWindows ? path7.win32.join : path7.posix.join;
2969
3680
  const separator = isWindows ? ";" : ":";
2970
- const extensions = isWindows ? path6.win32.extname(name).length > 0 ? [""] : env.pathExt.split(";").filter((e) => e.length > 0) : [""];
3681
+ const extensions = isWindows ? path7.win32.extname(name).length > 0 ? [""] : env.pathExt.split(";").filter((e) => e.length > 0) : [""];
2971
3682
  for (const rawDir of env.pathEnv.split(separator)) {
2972
3683
  const dir = rawDir.trim().replace(/^"|"$/g, "");
2973
3684
  if (dir.length === 0)
@@ -2985,16 +3696,16 @@ function lookupOnPath(name) {
2985
3696
  platform: process.platform,
2986
3697
  pathEnv: process.env["PATH"] ?? "",
2987
3698
  pathExt: process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD",
2988
- exists: (candidate) => fs5.existsSync(candidate)
3699
+ exists: (candidate) => fs6.existsSync(candidate)
2989
3700
  });
2990
3701
  }
2991
3702
  function resolveShell(kind) {
2992
3703
  if (process.platform !== "win32")
2993
3704
  return resolvePosixShell(kind);
2994
3705
  const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
2995
- const windowsPowershell = path6.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
3706
+ const windowsPowershell = path7.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
2996
3707
  if (kind === "cmd") {
2997
- return { kind: "cmd", exe: path6.join(systemRoot, "System32", "cmd.exe") };
3708
+ return { kind: "cmd", exe: path7.join(systemRoot, "System32", "cmd.exe") };
2998
3709
  }
2999
3710
  if (kind === "powershell") {
3000
3711
  return { kind: "powershell", exe: windowsPowershell };
@@ -3022,7 +3733,7 @@ function resolvePosixShell(kind) {
3022
3733
  candidates.push({ kind: "bash", exe: login });
3023
3734
  candidates.push({ kind: "bash", exe: "/bin/bash" }, { kind: "bash", exe: "/usr/bin/bash" });
3024
3735
  }
3025
- const found = candidates.find((c) => fs5.existsSync(c.exe));
3736
+ const found = candidates.find((c) => fs6.existsSync(c.exe));
3026
3737
  if (found)
3027
3738
  return found;
3028
3739
  return { kind: "sh", exe: "/bin/sh" };
@@ -3067,24 +3778,24 @@ function ipcEndpoint(platform, user, env) {
3067
3778
  const suffix = `-${shortHash(env.runtimeDir)}`;
3068
3779
  return `\\\\.\\pipe\\axterm-${safeUser}${suffix}`;
3069
3780
  }
3070
- return path6.posix.join(env.runtimeDir, "axterm.sock");
3781
+ return path7.posix.join(env.runtimeDir, "axterm.sock");
3071
3782
  }
3072
3783
  var cachedPwsh;
3073
3784
  var init_shell = () => {};
3074
3785
 
3075
3786
  // packages/daemon/src/paths.ts
3076
- import fs6 from "fs";
3077
- import os4 from "os";
3078
- import path7 from "path";
3787
+ import fs7 from "fs";
3788
+ import os3 from "os";
3789
+ import path8 from "path";
3079
3790
  function ensureDataDirs() {
3080
3791
  for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir, SCROLLBACK_DIR, LOG_DIR, EXT_DIR]) {
3081
- fs6.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
3792
+ fs7.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
3082
3793
  }
3083
3794
  if (process.platform === "win32")
3084
3795
  return;
3085
3796
  for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir]) {
3086
3797
  try {
3087
- fs6.chmodSync(dir, DIR_MODE2);
3798
+ fs7.chmodSync(dir, DIR_MODE2);
3088
3799
  } catch {}
3089
3800
  }
3090
3801
  }
@@ -3092,14 +3803,14 @@ function restrictFile(file) {
3092
3803
  if (process.platform === "win32")
3093
3804
  return;
3094
3805
  try {
3095
- fs6.chmodSync(file, 384);
3806
+ fs7.chmodSync(file, 384);
3096
3807
  } catch {}
3097
3808
  }
3098
3809
  function log(message) {
3099
3810
  const line = `${new Date().toISOString()} ${message}
3100
3811
  `;
3101
3812
  try {
3102
- fs6.appendFileSync(LOG_FILE, line);
3813
+ fs7.appendFileSync(LOG_FILE, line);
3103
3814
  } catch {}
3104
3815
  }
3105
3816
  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;
@@ -3116,15 +3827,15 @@ var init_paths = __esm(() => {
3116
3827
  DB_PATH = PATHS.dbFile;
3117
3828
  RUNTIME_FILE2 = PATHS.runtimeFile;
3118
3829
  HOOK_ENDPOINT_FILE = PATHS.hookEndpointFile;
3119
- PIPE_PATH = ipcEndpoint(process.platform, os4.userInfo().username, {
3830
+ PIPE_PATH = ipcEndpoint(process.platform, os3.userInfo().username, {
3120
3831
  runtimeDir: PATHS.runtimeDir
3121
3832
  });
3122
- LOG_FILE = path7.join(LOG_DIR, "daemon.log");
3833
+ LOG_FILE = path8.join(LOG_DIR, "daemon.log");
3123
3834
  });
3124
3835
 
3125
3836
  // packages/daemon/src/store.ts
3126
- import fs7 from "fs";
3127
- import path8 from "path";
3837
+ import fs8 from "fs";
3838
+ import path9 from "path";
3128
3839
  import { Database } from "bun:sqlite";
3129
3840
  function rowToInfo(row) {
3130
3841
  return {
@@ -3155,7 +3866,7 @@ function rowToInfo(row) {
3155
3866
  class Store {
3156
3867
  db;
3157
3868
  constructor(dbPath = DB_PATH) {
3158
- fs7.mkdirSync(path8.dirname(dbPath), { recursive: true });
3869
+ fs8.mkdirSync(path9.dirname(dbPath), { recursive: true });
3159
3870
  this.db = new Database(dbPath, { strict: true });
3160
3871
  this.db.run("PRAGMA journal_mode = WAL");
3161
3872
  this.db.run(`
@@ -3316,11 +4027,11 @@ var init_store = __esm(() => {
3316
4027
  });
3317
4028
 
3318
4029
  // packages/daemon/src/config-migrate.ts
3319
- import fs8 from "fs";
3320
- import path9 from "path";
4030
+ import fs9 from "fs";
4031
+ import path10 from "path";
3321
4032
  import { Database as Database2 } from "bun:sqlite";
3322
4033
  function checkpointWal(dbFile) {
3323
- if (!fs8.existsSync(dbFile))
4034
+ if (!fs9.existsSync(dbFile))
3324
4035
  return;
3325
4036
  try {
3326
4037
  const db = new Database2(dbFile);
@@ -3330,32 +4041,32 @@ function checkpointWal(dbFile) {
3330
4041
  }
3331
4042
  function relocate(source, target) {
3332
4043
  try {
3333
- fs8.renameSync(source, target);
4044
+ fs9.renameSync(source, target);
3334
4045
  } catch {
3335
- fs8.cpSync(source, target, { recursive: true });
4046
+ fs9.cpSync(source, target, { recursive: true });
3336
4047
  }
3337
4048
  }
3338
4049
  function moveInto(from, to, notes) {
3339
- checkpointWal(path9.join(from, "axterm.db"));
3340
- fs8.mkdirSync(to, { recursive: true });
4050
+ checkpointWal(path10.join(from, "axterm.db"));
4051
+ fs9.mkdirSync(to, { recursive: true });
3341
4052
  for (const entry of STATE_ENTRIES) {
3342
- const source = path9.join(from, entry);
3343
- const target = path9.join(to, entry);
3344
- if (!fs8.existsSync(source))
4053
+ const source = path10.join(from, entry);
4054
+ const target = path10.join(to, entry);
4055
+ if (!fs9.existsSync(source))
3345
4056
  continue;
3346
- if (!fs8.existsSync(target)) {
4057
+ if (!fs9.existsSync(target)) {
3347
4058
  relocate(source, target);
3348
4059
  notes.push(`moved ${entry}`);
3349
4060
  continue;
3350
4061
  }
3351
- if (!fs8.statSync(source).isDirectory() || !fs8.statSync(target).isDirectory())
4062
+ if (!fs9.statSync(source).isDirectory() || !fs9.statSync(target).isDirectory())
3352
4063
  continue;
3353
4064
  let moved = 0;
3354
- for (const child of fs8.readdirSync(source)) {
3355
- const childTarget = path9.join(target, child);
3356
- if (fs8.existsSync(childTarget))
4065
+ for (const child of fs9.readdirSync(source)) {
4066
+ const childTarget = path10.join(target, child);
4067
+ if (fs9.existsSync(childTarget))
3357
4068
  continue;
3358
- relocate(path9.join(source, child), childTarget);
4069
+ relocate(path10.join(source, child), childTarget);
3359
4070
  moved += 1;
3360
4071
  }
3361
4072
  if (moved > 0)
@@ -3365,29 +4076,29 @@ function moveInto(from, to, notes) {
3365
4076
  function migrateLegacyConfig(input) {
3366
4077
  const notes = [];
3367
4078
  const storePaths = defaultConfigStorePaths(input.configDir);
3368
- if (fs8.existsSync(storePaths.configFile))
4079
+ if (fs9.existsSync(storePaths.configFile))
3369
4080
  return { migrated: false, notes };
3370
- const configSource = input.legacyRoots.find((dir) => fs8.existsSync(path9.join(dir, "config.json")));
4081
+ const configSource = input.legacyRoots.find((dir) => fs9.existsSync(path10.join(dir, "config.json")));
3371
4082
  if (configSource !== undefined) {
3372
- fs8.mkdirSync(input.configDir, { recursive: true });
3373
- fs8.copyFileSync(path9.join(configSource, "config.json"), storePaths.configFile);
3374
- const secrets = path9.join(configSource, "secrets.json");
3375
- if (fs8.existsSync(secrets)) {
3376
- fs8.copyFileSync(secrets, storePaths.secretsFile);
4083
+ fs9.mkdirSync(input.configDir, { recursive: true });
4084
+ fs9.copyFileSync(path10.join(configSource, "config.json"), storePaths.configFile);
4085
+ const secrets = path10.join(configSource, "secrets.json");
4086
+ if (fs9.existsSync(secrets)) {
4087
+ fs9.copyFileSync(secrets, storePaths.secretsFile);
3377
4088
  try {
3378
- fs8.chmodSync(storePaths.secretsFile, 384);
4089
+ fs9.chmodSync(storePaths.secretsFile, 384);
3379
4090
  } catch {}
3380
4091
  }
3381
- fs8.writeFileSync(path9.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
4092
+ fs9.writeFileSync(path10.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
3382
4093
  ${input.configDir}
3383
4094
  ` + `These files are no longer used. secrets.json still holds your bot token \u2014 delete this directory once you are satisfied.
3384
4095
  `);
3385
4096
  notes.push(`adopted configuration from ${configSource}`);
3386
4097
  }
3387
- const legacy = input.legacyRoots.find((dir) => fs8.existsSync(path9.join(dir, "axterm.db")));
4098
+ const legacy = input.legacyRoots.find((dir) => fs9.existsSync(path10.join(dir, "axterm.db")));
3388
4099
  if (legacy !== undefined) {
3389
4100
  moveInto(legacy, input.stateDir, notes);
3390
- fs8.writeFileSync(path9.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
4101
+ fs9.writeFileSync(path10.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
3391
4102
  ${input.stateDir}
3392
4103
  and configuration in:
3393
4104
  ${input.configDir}
@@ -3396,8 +4107,8 @@ and configuration in:
3396
4107
  }
3397
4108
  if (configSource !== undefined)
3398
4109
  return { migrated: true, ...legacy ? { from: legacy } : {}, notes };
3399
- const dbFile = path9.join(input.stateDir, "axterm.db");
3400
- if (!fs8.existsSync(dbFile))
4110
+ const dbFile = path10.join(input.stateDir, "axterm.db");
4111
+ if (!fs9.existsSync(dbFile))
3401
4112
  return { migrated: notes.length > 0, ...legacy ? { from: legacy } : {}, notes };
3402
4113
  const store = new Store(dbFile);
3403
4114
  const machine2 = store.getConfig("axterm_machine");
@@ -3442,6 +4153,7 @@ var axterm_agent_status_default = `// @axterm-managed-omp-extension\r
3442
4153
  // completion) follow Orca's production orca-agent-status.ts, reviewed in\r
3443
4154
  // docs/design/terminal-wrapper-omp-telegram.md \xA75.\r
3444
4155
  import fs from "node:fs";\r
4156
+ import path from "node:path";\r
3445
4157
  \r
3446
4158
  const HOOK_POST_TIMEOUT_MS = 1000;\r
3447
4159
  const MAX_ASSISTANT_TEXT = 4000;\r
@@ -3637,11 +4349,75 @@ function formatAnswers(params: unknown, answers: AskAnswerWire[]): Record<string
3637
4349
  }\r
3638
4350
  \r
3639
4351
  export default function axtermAgentStatus(pi: PiLike): void {\r
3640
- // Subagents inherit the lead's env; only one process may report.\r
4352
+ // Only one PROCESS may report. This catches an agent that spawns another\r
4353
+ // agent as a child process; it does NOT catch omp's own subagents, which are\r
4354
+ // \`AgentSession\`s inside THIS process \u2014 \`process.pid\` is identical for them,\r
4355
+ // so they fall straight through. Telling those apart needs the session id,\r
4356
+ // which \`isLeadSession\` below does per event.\r
3641
4357
  const owner = process.env["AXTERM_STATUS_OWNED"];\r
3642
4358
  const selfPid = String(process.pid);\r
3643
4359
  if (owner && owner !== selfPid) return;\r
3644
4360
  process.env["AXTERM_STATUS_OWNED"] = selfPid;\r
4361
+ \r
4362
+ /* ----------------------- lead versus subagent ------------------------ */\r
4363
+ // omp loads this extension into every AgentSession, and every handler gets\r
4364
+ // the same ExtensionContext \u2014 \`sessionManager.getSessionId()\` is available on\r
4365
+ // all of them (verified in the installed omp's types). Only the shutdown\r
4366
+ // handler used it, so a finishing SUBAGENT posted \`agent_end\`: the session\r
4367
+ // flipped to \`done\`, Telegram announced it and renamed the topic, while the\r
4368
+ // lead was still working.\r
4369
+ //\r
4370
+ // The first session to start in the process is the lead \u2014 a subagent cannot\r
4371
+ // begin before its parent. Recorded in \`process.env\` rather than a module\r
4372
+ // variable because that is the one channel guaranteed to be shared no matter\r
4373
+ // how omp instantiates the module, which is why the PID guard above uses it\r
4374
+ // too.\r
4375
+ const LEAD_KEY = "AXTERM_LEAD_SESSION";\r
4376
+ \r
4377
+ function sessionIdOf(ctx: unknown): string | undefined {\r
4378
+ const manager = (ctx as { sessionManager?: { getSessionId?: () => string } } | undefined)?.sessionManager;\r
4379
+ try {\r
4380
+ return manager?.getSessionId?.();\r
4381
+ } catch {\r
4382
+ return undefined;\r
4383
+ }\r
4384
+ }\r
4385
+ \r
4386
+ /**\r
4387
+ * Claim lead on the first \`session_start\` seen in this process. A subagent\r
4388
+ * cannot begin before its parent, so first is the lead \u2014 and recording it\r
4389
+ * here rather than lazily on the first judgement matters: a subagent's\r
4390
+ * completion would otherwise be the first caller and crown itself.\r
4391
+ */\r
4392
+ function rememberLead(ctx: unknown): void {\r
4393
+ const id = sessionIdOf(ctx);\r
4394
+ if (id === undefined) return;\r
4395
+ if (!process.env[LEAD_KEY]) process.env[LEAD_KEY] = id;\r
4396
+ }\r
4397
+ \r
4398
+ /**\r
4399
+ * Fails OPEN twice over \u2014 no lead recorded, or no id on this event. A build\r
4400
+ * that exposes neither must not lose its turn-end entirely, which would leave\r
4401
+ * the session at \`working\` forever; the shutdown path makes the same choice\r
4402
+ * (\xA74.42).\r
4403
+ */\r
4404
+ function isLeadSession(ctx: unknown): boolean {\r
4405
+ const lead = process.env[LEAD_KEY];\r
4406
+ if (!lead) return true;\r
4407
+ const id = sessionIdOf(ctx);\r
4408
+ return id === undefined || id === lead;\r
4409
+ }\r
4410
+ \r
4411
+ /**\r
4412
+ * A switch or a branch makes another session the one the operator is driving,\r
4413
+ * so it becomes the lead \u2014 force, not first-wins. Without this the gate would\r
4414
+ * treat the new foreground session as a subagent and the session would never\r
4415
+ * report a completed turn again, which is worse than the leak it prevents.\r
4416
+ */\r
4417
+ function claimLead(ctx: unknown): void {\r
4418
+ const id = sessionIdOf(ctx);\r
4419
+ if (id !== undefined) process.env[LEAD_KEY] = id;\r
4420
+ }\r
3645
4421
  \r
3646
4422
  /* ---------------- endpoint re-sourcing (env-freeze fix) ---------------- */\r
3647
4423
  let warnedBadEndpoint = false;\r
@@ -3870,6 +4646,12 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3870
4646
  const say = (text: string, isError = false) => ({ content: [{ type: "text" as const, text }], isError });\r
3871
4647
  const { path: filePath, caption } = (params ?? {}) as { path?: string; caption?: string };\r
3872
4648
  if (!filePath) return say("no path given", true);\r
4649
+ // Resolved HERE, because this process is the only one standing in the\r
4650
+ // right directory. The daemon interprets what it receives against its\r
4651
+ // own cwd \u2014 wherever it was spawned, never the session's folder \u2014 so a\r
4652
+ // bare filename came back "file not found" while the file was plainly\r
4653
+ // there. An absolute path is passed through untouched.\r
4654
+ const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);\r
3873
4655
  const coords = resolveCoords();\r
3874
4656
  const sessionId = process.env["AXTERM_SESSION_ID"];\r
3875
4657
  if (!coords.port || !coords.token || !sessionId) {\r
@@ -3882,7 +4664,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3882
4664
  body: JSON.stringify({\r
3883
4665
  sessionId,\r
3884
4666
  launchToken: process.env["AXTERM_LAUNCH_TOKEN"] ?? "",\r
3885
- path: filePath,\r
4667
+ path: resolved,\r
3886
4668
  caption,\r
3887
4669
  }),\r
3888
4670
  });\r
@@ -3906,7 +4688,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3906
4688
  return { status: "no-channel" };\r
3907
4689
  }\r
3908
4690
  try {\r
3909
- const res = await fetch(\`http://127.0.0.1:\${coords.port}/hook/omp/ask\`, {\r
4691
+ const res = await fetch(\`http://127.0.0.1:\${coords.port}/hook/ask\`, {\r
3910
4692
  method: "POST",\r
3911
4693
  headers: { "content-type": "application/json", "x-axterm-hook-token": coords.token },\r
3912
4694
  body: JSON.stringify({\r
@@ -3929,7 +4711,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3929
4711
  const sessionId = process.env["AXTERM_SESSION_ID"];\r
3930
4712
  if (!coords.port || !coords.token || !sessionId) return;\r
3931
4713
  try {\r
3932
- await fetch(\`http://127.0.0.1:\${coords.port}/hook/omp/ask-cancel\`, {\r
4714
+ await fetch(\`http://127.0.0.1:\${coords.port}/hook/ask-cancel\`, {\r
3933
4715
  method: "POST",\r
3934
4716
  headers: { "content-type": "application/json", "x-axterm-hook-token": coords.token },\r
3935
4717
  body: JSON.stringify({ sessionId, launchToken: process.env["AXTERM_LAUNCH_TOKEN"] ?? "", askId }),\r
@@ -3941,6 +4723,8 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3941
4723
  \r
3942
4724
  /* ------------------------------ handlers ------------------------------ */\r
3943
4725
  pi.on("session_start", (_event, ctx) => {\r
4726
+ // First one wins, and it decides who may end a turn from here on.\r
4727
+ rememberLead(ctx);\r
3944
4728
  const manager = (ctx as { sessionManager?: Record<string, unknown> } | undefined)?.sessionManager;\r
3945
4729
  const probe = (names: string[]): unknown => {\r
3946
4730
  for (const name of names) {\r
@@ -3963,7 +4747,16 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3963
4747
  });\r
3964
4748
  });\r
3965
4749
  \r
3966
- pi.on("before_agent_start", (event) => {\r
4750
+ // Switching or branching inside omp changes which session the operator is\r
4751
+ // driving. Not reported to the daemon \u2014 the session row is unchanged \u2014 but the\r
4752
+ // lead has to follow, or turn ends stop arriving from here on.\r
4753
+ pi.on("session_switch", (_event, ctx) => claimLead(ctx));\r
4754
+ pi.on("session_branch", (_event, ctx) => claimLead(ctx));\r
4755
+ \r
4756
+ // Lead only: this sets the run's starting prompt, which the channel shows in\r
4757
+ // the topic title. A subagent's own prompt is not what the operator asked.\r
4758
+ pi.on("before_agent_start", (event, ctx) => {\r
4759
+ if (!isLeadSession(ctx)) return;\r
3967
4760
  post("before_agent_start", { prompt: (event as { prompt?: string } | undefined)?.prompt ?? "" });\r
3968
4761
  });\r
3969
4762
  \r
@@ -4008,7 +4801,10 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
4008
4801
  recheckMs = Math.min(recheckMs * 2, IDLE_RECHECK_MAX_MS);\r
4009
4802
  }\r
4010
4803
  \r
4011
- pi.on("agent_start", () => {\r
4804
+ // Lead only: a subagent starting is not the session's turn restarting, and\r
4805
+ // resetting \`endReported\` here on its behalf would arm a second completion.\r
4806
+ pi.on("agent_start", (_event, ctx) => {\r
4807
+ if (!isLeadSession(ctx)) return;\r
4012
4808
  clearPendingCheck();\r
4013
4809
  endReported = false;\r
4014
4810
  post("agent_start");\r
@@ -4093,20 +4889,29 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
4093
4889
  });\r
4094
4890
  });\r
4095
4891
  \r
4096
- pi.on("message_end", (event) => {\r
4892
+ // Lead only: this becomes the session's \`lastReply\` \u2014 the answer the channel\r
4893
+ // shows. A subagent's internal message is not the session's answer.\r
4894
+ pi.on("message_end", (event, ctx) => {\r
4895
+ if (!isLeadSession(ctx)) return;\r
4097
4896
  const message = (event as { message?: unknown } | undefined)?.message;\r
4098
4897
  const text = extractAssistantText(message);\r
4099
4898
  if (!text) return;\r
4100
4899
  post("message_end", { role: "assistant", text });\r
4101
4900
  });\r
4102
4901
  \r
4103
- pi.on("agent_settled", () => {\r
4902
+ // Lead only, and this is the one the report was about: a finishing subagent\r
4903
+ // announced the turn as done and renamed the topic while the lead worked on.\r
4904
+ pi.on("agent_settled", (_event, ctx) => {\r
4905
+ if (!isLeadSession(ctx)) return;\r
4104
4906
  settledSupported = true;\r
4105
4907
  clearPendingCheck();\r
4106
4908
  postEndOnce();\r
4107
4909
  });\r
4108
4910
  \r
4911
+ // The other route to a turn end, for builds without \`agent_settled\`. Gated the\r
4912
+ // same way, or a subagent whose own work is idle ends the session's turn.\r
4109
4913
  pi.on("agent_end", (_event, ctx) => {\r
4914
+ if (!isLeadSession(ctx)) return;\r
4110
4915
  if (settledSupported) return;\r
4111
4916
  const c = ctx as { isIdle?: () => boolean } | undefined;\r
4112
4917
  if (!c || typeof c.isIdle !== "function") {\r
@@ -4141,13 +4946,13 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
4141
4946
  var init_axterm_agent_status = () => {};
4142
4947
 
4143
4948
  // packages/daemon/src/ext-install.ts
4144
- import fs9 from "fs";
4145
- import path10 from "path";
4949
+ import fs10 from "fs";
4950
+ import path11 from "path";
4146
4951
  function installExtensionAsset(targetDir) {
4147
- fs9.mkdirSync(targetDir, { recursive: true });
4148
- const target = path10.join(targetDir, "axterm-agent-status.mjs");
4952
+ fs10.mkdirSync(targetDir, { recursive: true });
4953
+ const target = path11.join(targetDir, "axterm-agent-status.mjs");
4149
4954
  const js = new Bun.Transpiler({ loader: "ts" }).transformSync(extensionSource);
4150
- fs9.writeFileSync(target, MANAGED_HEADER + js);
4955
+ fs10.writeFileSync(target, MANAGED_HEADER + js);
4151
4956
  return target;
4152
4957
  }
4153
4958
  var extensionSource, MANAGED_HEADER = `// @axterm-managed-omp-extension
@@ -4160,8 +4965,8 @@ var init_ext_install = __esm(() => {
4160
4965
 
4161
4966
  // packages/daemon/src/workspace-local.ts
4162
4967
  import { spawn as spawn2 } from "child_process";
4163
- import fs10 from "fs/promises";
4164
- import path11 from "path";
4968
+ import fs11 from "fs/promises";
4969
+ import path12 from "path";
4165
4970
  function isAbsent(error) {
4166
4971
  const code = error?.code;
4167
4972
  return code === "ENOENT" || code === "ENOTDIR";
@@ -4174,10 +4979,10 @@ class LocalWorkspace {
4174
4979
  this.stagingDir = stagingDir;
4175
4980
  }
4176
4981
  async list(dir) {
4177
- const dirents = await fs10.readdir(dir, { withFileTypes: true });
4982
+ const dirents = await fs11.readdir(dir, { withFileTypes: true });
4178
4983
  const stats = await Promise.all(dirents.map(async (dirent) => {
4179
4984
  try {
4180
- return await fs10.lstat(path11.join(dir, dirent.name));
4985
+ return await fs11.lstat(path12.join(dir, dirent.name));
4181
4986
  } catch (error) {
4182
4987
  if (isAbsent(error))
4183
4988
  return;
@@ -4201,7 +5006,7 @@ class LocalWorkspace {
4201
5006
  }
4202
5007
  async stat(target) {
4203
5008
  try {
4204
- const stat = await fs10.stat(target);
5009
+ const stat = await fs11.stat(target);
4205
5010
  const kind = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "other";
4206
5011
  return { kind, size: stat.size, mtimeMs: stat.mtimeMs };
4207
5012
  } catch (error) {
@@ -4212,8 +5017,8 @@ class LocalWorkspace {
4212
5017
  }
4213
5018
  async read(file, limit) {
4214
5019
  if (limit === undefined)
4215
- return await fs10.readFile(file);
4216
- const handle = await fs10.open(file, "r");
5020
+ return await fs11.readFile(file);
5021
+ const handle = await fs11.open(file, "r");
4217
5022
  try {
4218
5023
  const buffer = Buffer.allocUnsafe(Math.min(limit, (await handle.stat()).size));
4219
5024
  let filled = 0;
@@ -4258,9 +5063,9 @@ var init_workspace_local = () => {};
4258
5063
 
4259
5064
  // packages/daemon/src/hook-host.ts
4260
5065
  import { randomBytes } from "crypto";
4261
- import fs11 from "fs";
5066
+ import fs12 from "fs";
4262
5067
  import http from "http";
4263
- import path12 from "path";
5068
+ import path13 from "path";
4264
5069
  function header(req, name) {
4265
5070
  const value = req.headers[name];
4266
5071
  return Array.isArray(value) ? value[0] : value;
@@ -4284,7 +5089,7 @@ class HookHost {
4284
5089
  constructor(options) {
4285
5090
  this.endpointFile = options.endpointFile;
4286
5091
  this.codexApprovalHoldMs = options.codexApprovalHoldMs ?? CODEX_APPROVAL_HOLD_MS;
4287
- this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(path12.join(RUNTIME_DIR, "materialised")));
5092
+ this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(path13.join(RUNTIME_DIR, "materialised")));
4288
5093
  }
4289
5094
  start() {
4290
5095
  const server = http.createServer((req, res) => this.handle(req, res));
@@ -4300,12 +5105,12 @@ class HookHost {
4300
5105
  return promise;
4301
5106
  }
4302
5107
  writeEndpointFile(port) {
4303
- fs11.mkdirSync(path12.dirname(this.endpointFile), { recursive: true });
5108
+ fs12.mkdirSync(path13.dirname(this.endpointFile), { recursive: true });
4304
5109
  const tmp = `${this.endpointFile}.tmp`;
4305
- fs11.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
5110
+ fs12.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
4306
5111
  AXTERM_HOOK_TOKEN=${this.token}
4307
5112
  `);
4308
- fs11.renameSync(tmp, this.endpointFile);
5113
+ fs12.renameSync(tmp, this.endpointFile);
4309
5114
  }
4310
5115
  registerSession(sessionId, launchToken, onEvent) {
4311
5116
  this.sessions.set(sessionId, { launchToken, onEvent });
@@ -4343,8 +5148,8 @@ AXTERM_HOOK_TOKEN=${this.token}
4343
5148
  }
4344
5149
  handle(req, res) {
4345
5150
  const isEvent = req.url === "/hook/omp";
4346
- const isAsk = req.url === "/hook/omp/ask";
4347
- const isAskCancel = req.url === "/hook/omp/ask-cancel";
5151
+ const isAsk = req.url === "/hook/ask";
5152
+ const isAskCancel = req.url === "/hook/ask-cancel";
4348
5153
  const isAttach = req.url === "/hook/attach";
4349
5154
  const isClaude = req.url === "/hook/claude";
4350
5155
  const isCodex = req.url === "/hook/codex";
@@ -4580,7 +5385,7 @@ var init_hook_host = __esm(() => {
4580
5385
  });
4581
5386
 
4582
5387
  // packages/daemon/src/rpc-server.ts
4583
- import fs12 from "fs";
5388
+ import fs13 from "fs";
4584
5389
  import net2 from "net";
4585
5390
  function writeFrame(socket, frame) {
4586
5391
  if (!socket.destroyed)
@@ -4765,10 +5570,12 @@ function startRpcServer(options) {
4765
5570
  }
4766
5571
  case "config.set": {
4767
5572
  const dottedPath = p["path"];
4768
- if (isSecretPath(dottedPath))
4769
- config.setSecret(dottedPath, p["value"]);
4770
- else {
4771
- const next2 = setConfigValue(config.get(), dottedPath, p["value"]);
5573
+ const raw = p["value"];
5574
+ const value = Array.isArray(raw) ? raw : raw;
5575
+ if (isSecretPath(dottedPath)) {
5576
+ config.setSecret(dottedPath, Array.isArray(value) ? value.join(" ") : value);
5577
+ } else {
5578
+ const next2 = setConfigValue(config.get(), dottedPath, value);
4772
5579
  if ("error" in next2)
4773
5580
  throw new Error(next2.error);
4774
5581
  config.update(() => next2.config);
@@ -4802,6 +5609,8 @@ function startRpcServer(options) {
4802
5609
  return telegram.status();
4803
5610
  case "doctor.report":
4804
5611
  return { checks: manager.doctorReport() };
5612
+ case "quota.report":
5613
+ return { checks: manager.quotaReport() };
4805
5614
  case "daemon.stop":
4806
5615
  setTimeout(onStopRequested, 50);
4807
5616
  return {};
@@ -4812,7 +5621,7 @@ function startRpcServer(options) {
4812
5621
  });
4813
5622
  if (process.platform !== "win32") {
4814
5623
  try {
4815
- fs12.unlinkSync(pipePath);
5624
+ fs13.unlinkSync(pipePath);
4816
5625
  } catch {}
4817
5626
  }
4818
5627
  server.listen(pipePath, () => {
@@ -4827,17 +5636,17 @@ var init_rpc_server = __esm(() => {
4827
5636
  });
4828
5637
 
4829
5638
  // packages/daemon/src/codex-home.ts
4830
- import fs13 from "fs";
4831
- import path13 from "path";
5639
+ import fs14 from "fs";
5640
+ import path14 from "path";
4832
5641
  function codexTrustKeyPath(home) {
4833
5642
  const resolved = (() => {
4834
5643
  try {
4835
- return fs13.realpathSync.native(home);
5644
+ return fs14.realpathSync.native(home);
4836
5645
  } catch {
4837
- return path13.resolve(home);
5646
+ return path14.resolve(home);
4838
5647
  }
4839
5648
  })();
4840
- return path13.join(resolved, "hooks.json");
5649
+ return path14.join(resolved, "hooks.json");
4841
5650
  }
4842
5651
  var init_codex_home = () => {};
4843
5652
 
@@ -4900,8 +5709,8 @@ function slugifyBranch(branch) {
4900
5709
  var MAX_SLUG_LENGTH = 60;
4901
5710
 
4902
5711
  // packages/daemon/src/git.ts
4903
- import fs14 from "fs";
4904
- import path14 from "path";
5712
+ import fs15 from "fs";
5713
+ import path15 from "path";
4905
5714
  async function git(ws, cwd, args) {
4906
5715
  const run = await ws.exec(["git", ...args], cwd);
4907
5716
  return {
@@ -4918,15 +5727,15 @@ async function gitOrThrow(ws, cwd, args) {
4918
5727
  return result.stdout;
4919
5728
  }
4920
5729
  function canonicalPath(target) {
4921
- let resolved = path14.resolve(target);
5730
+ let resolved = path15.resolve(target);
4922
5731
  try {
4923
- resolved = fs14.realpathSync.native(resolved);
5732
+ resolved = fs15.realpathSync.native(resolved);
4924
5733
  } catch {}
4925
5734
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
4926
5735
  }
4927
5736
  async function resolveRepoRoot(ws, cwd) {
4928
5737
  const result = await git(ws, cwd, ["rev-parse", "--show-toplevel"]);
4929
- return result.ok ? path14.resolve(result.stdout) : undefined;
5738
+ return result.ok ? path15.resolve(result.stdout) : undefined;
4930
5739
  }
4931
5740
  async function currentBranch(ws, cwd) {
4932
5741
  const result = await git(ws, cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -4935,8 +5744,8 @@ async function currentBranch(ws, cwd) {
4935
5744
  async function listWorktrees(ws, repoRoot) {
4936
5745
  return parseWorktreeList(await gitOrThrow(ws, repoRoot, ["worktree", "list", "--porcelain"]));
4937
5746
  }
4938
- function worktreePathFor(repoRoot, branch, root = path14.join(STATE_DIR, "worktrees")) {
4939
- return path14.join(root, slugifyBranch(path14.basename(repoRoot)), slugifyBranch(branch));
5747
+ function worktreePathFor(repoRoot, branch, root = path15.join(STATE_DIR, "worktrees")) {
5748
+ return path15.join(root, slugifyBranch(path15.basename(repoRoot)), slugifyBranch(branch));
4940
5749
  }
4941
5750
  async function createWorktree(ws, options) {
4942
5751
  const { repoRoot, branch, base } = options;
@@ -4944,7 +5753,7 @@ async function createWorktree(ws, options) {
4944
5753
  const existing = (await listWorktrees(ws, repoRoot)).find((w) => canonicalPath(w.path) === canonicalPath(target));
4945
5754
  if (existing)
4946
5755
  return { path: target, branch, createdBranch: false };
4947
- fs14.mkdirSync(path14.dirname(target), { recursive: true });
5756
+ fs15.mkdirSync(path15.dirname(target), { recursive: true });
4948
5757
  const exists = (await git(ws, repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])).ok;
4949
5758
  const args = exists ? ["worktree", "add", target, branch] : ["worktree", "add", "-b", branch, target, ...base !== undefined ? [base] : []];
4950
5759
  await gitOrThrow(ws, repoRoot, args);
@@ -4997,7 +5806,7 @@ async function diffStat(ws, worktreePath, base) {
4997
5806
  const untracked = await untrackedFiles(ws, worktreePath);
4998
5807
  let newLines = 0;
4999
5808
  for (const file of untracked)
5000
- newLines += await countLines(ws, path14.join(worktreePath, file));
5809
+ newLines += await countLines(ws, path15.join(worktreePath, file));
5001
5810
  return {
5002
5811
  filesChanged: a.filesChanged + b.filesChanged + untracked.length,
5003
5812
  insertions: a.insertions + b.insertions + newLines,
@@ -5073,7 +5882,7 @@ var init_injector = __esm(() => {
5073
5882
 
5074
5883
  // packages/daemon/src/occupancy.ts
5075
5884
  import { spawn as spawn3 } from "child_process";
5076
- import fs15 from "fs/promises";
5885
+ import fs16 from "fs/promises";
5077
5886
  function shellOccupied(shellPid) {
5078
5887
  return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
5079
5888
  }
@@ -5118,11 +5927,11 @@ async function occupiedFromSnapshot(shellPid) {
5118
5927
  }
5119
5928
  async function childrenViaProc(pid) {
5120
5929
  try {
5121
- const children = await fs15.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
5930
+ const children = await fs16.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
5122
5931
  return children.trim().length > 0;
5123
5932
  } catch {
5124
5933
  try {
5125
- await fs15.access(`/proc/${pid}`);
5934
+ await fs16.access(`/proc/${pid}`);
5126
5935
  return;
5127
5936
  } catch {
5128
5937
  return false;
@@ -5137,53 +5946,455 @@ async function childrenViaPgrep(pid) {
5137
5946
  return false;
5138
5947
  if (result.code !== 0)
5139
5948
  return;
5140
- return result.stdout.trim().length > 0;
5949
+ return result.stdout.trim().length > 0;
5950
+ }
5951
+ async function processTable() {
5952
+ const result = await capture(resolveShell("powershell").exe, [
5953
+ "-NoLogo",
5954
+ "-NoProfile",
5955
+ "-NonInteractive",
5956
+ "-Command",
5957
+ "(Get-CimInstance -ClassName Win32_Process -Property ProcessId,ParentProcessId,Name,CreationDate" + " | Where-Object { $_.Name -ne 'conhost.exe' }" + ` | ForEach-Object { "$($_.ProcessId),$($_.ParentProcessId),$([long]($_.CreationDate.Ticks / 10000))" }) -join ' '`
5958
+ ]);
5959
+ if (result === undefined || result.code !== 0)
5960
+ return;
5961
+ const startedAt = new Map;
5962
+ const newestChild = new Map;
5963
+ for (const row of result.stdout.split(/\s+/)) {
5964
+ const [rawPid, rawParent, rawStarted] = row.split(",");
5965
+ const pid = Number(rawPid);
5966
+ const parent = Number(rawParent);
5967
+ const started = Number(rawStarted);
5968
+ if (!Number.isInteger(pid) || !Number.isInteger(parent) || !Number.isInteger(started))
5969
+ continue;
5970
+ if (pid === result.pid)
5971
+ continue;
5972
+ startedAt.set(pid, started);
5973
+ const newest = newestChild.get(parent);
5974
+ if (newest === undefined || started > newest)
5975
+ newestChild.set(parent, started);
5976
+ }
5977
+ return startedAt.size > 0 ? { startedAt, newestChild } : undefined;
5978
+ }
5979
+ function capture(exe, argv) {
5980
+ const { promise, resolve } = Promise.withResolvers();
5981
+ const child = spawn3(exe, argv, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
5982
+ let stdout3 = "";
5983
+ child.stdout.setEncoding("utf8");
5984
+ child.stdout.on("data", (chunk) => {
5985
+ stdout3 += chunk;
5986
+ });
5987
+ child.once("error", () => resolve(undefined));
5988
+ child.once("close", (code) => resolve({ code, stdout: stdout3, pid: child.pid }));
5989
+ return promise;
5990
+ }
5991
+ var OCCUPANCY_CACHE_MS = 1500, perPid, snapshot;
5992
+ var init_occupancy = __esm(() => {
5993
+ init_shell();
5994
+ perPid = new Map;
5995
+ });
5996
+
5997
+ // packages/daemon/src/agent-store.ts
5998
+ import fs17 from "fs";
5999
+ import os4 from "os";
6000
+ import path16 from "path";
6001
+ import { Database as Database3 } from "bun:sqlite";
6002
+ function readTailLines(file, maxBytes) {
6003
+ let fd;
6004
+ try {
6005
+ fd = fs17.openSync(file, "r");
6006
+ const size = fs17.fstatSync(fd).size;
6007
+ if (size === 0)
6008
+ return [];
6009
+ const length = Math.min(size, maxBytes);
6010
+ const buffer = Buffer.allocUnsafe(length);
6011
+ fs17.readSync(fd, buffer, 0, length, size - length);
6012
+ const text = buffer.toString("utf8");
6013
+ const lines = text.split(`
6014
+ `);
6015
+ if (length < size)
6016
+ lines.shift();
6017
+ if (lines.at(-1) === "")
6018
+ lines.pop();
6019
+ return lines;
6020
+ } catch {
6021
+ return [];
6022
+ } finally {
6023
+ if (fd !== undefined)
6024
+ fs17.closeSync(fd);
6025
+ }
6026
+ }
6027
+ function claudeConfigDir() {
6028
+ return process.env["CLAUDE_CONFIG_DIR"] ?? path16.join(os4.homedir(), ".claude");
6029
+ }
6030
+ function codexHome() {
6031
+ return path16.join(STATE_DIR, "codex-home");
6032
+ }
6033
+ function ompAgentDb() {
6034
+ return path16.join(os4.homedir(), ".omp", "agent", "agent.db");
6035
+ }
6036
+ function findCodexRollout(sessionId) {
6037
+ const root = path16.join(codexHome(), "sessions");
6038
+ for (const shard2 of codexRolloutShards(sessionId)) {
6039
+ const dir = path16.join(root, ...shard2.split("/"));
6040
+ let entries;
6041
+ try {
6042
+ entries = fs17.readdirSync(dir);
6043
+ } catch {
6044
+ continue;
6045
+ }
6046
+ const match2 = entries.find((name) => name.endsWith(`-${sessionId}.jsonl`));
6047
+ if (match2 !== undefined)
6048
+ return path16.join(dir, match2);
6049
+ }
6050
+ return;
6051
+ }
6052
+ function newestCodexRollout() {
6053
+ const root = path16.join(codexHome(), "sessions");
6054
+ let best;
6055
+ const walk = (dir, depth) => {
6056
+ let entries;
6057
+ try {
6058
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
6059
+ } catch {
6060
+ return;
6061
+ }
6062
+ for (const entry of entries) {
6063
+ const full = path16.join(dir, entry.name);
6064
+ if (entry.isDirectory()) {
6065
+ if (depth < 3)
6066
+ walk(full, depth + 1);
6067
+ continue;
6068
+ }
6069
+ if (!entry.name.endsWith(".jsonl"))
6070
+ continue;
6071
+ try {
6072
+ const { mtimeMs } = fs17.statSync(full);
6073
+ if (!best || mtimeMs > best.mtimeMs)
6074
+ best = { file: full, mtimeMs };
6075
+ } catch {}
6076
+ }
6077
+ };
6078
+ walk(root, 0);
6079
+ return best?.file;
6080
+ }
6081
+ function ompQuotaFacts() {
6082
+ const file = ompAgentDb();
6083
+ if (!fs17.existsSync(file))
6084
+ return { agent: "omp", kind: "gauge" };
6085
+ let db;
6086
+ try {
6087
+ db = new Database3(file, { readonly: true, strict: true });
6088
+ const rows = db.query(`SELECT recorded_at, limit_id, label, used_fraction, status, resets_at
6089
+ FROM usage_history
6090
+ WHERE recorded_at = (SELECT MAX(recorded_at) FROM usage_history h WHERE h.limit_id = usage_history.limit_id)
6091
+ AND recorded_at > ?
6092
+ ORDER BY limit_id`).all(Date.now() - 7 * 86400000);
6093
+ const reading = parseOmpQuota(rows);
6094
+ return reading ? { agent: "omp", kind: "gauge", reading } : { agent: "omp", kind: "gauge" };
6095
+ } catch (err) {
6096
+ log(`omp quota unreadable: ${String(err)}`);
6097
+ return { agent: "omp", kind: "gauge" };
6098
+ } finally {
6099
+ db?.close();
6100
+ }
6101
+ }
6102
+ function codexQuotaFacts() {
6103
+ const file = newestCodexRollout();
6104
+ if (file === undefined)
6105
+ return { agent: "codex", kind: "gauge" };
6106
+ const reading = parseCodexQuota(readTailLines(file, QUOTA_TAIL_BYTES));
6107
+ return reading ? { agent: "codex", kind: "gauge", reading } : { agent: "codex", kind: "gauge" };
6108
+ }
6109
+ function claudeQuotaFacts(sessions) {
6110
+ const dir = claudeConfigDir();
6111
+ let newest;
6112
+ for (const session of sessions) {
6113
+ if (session.agentSessionId === undefined)
6114
+ continue;
6115
+ const file = path16.join(dir, claudeTranscriptRelPath(session.cwd, session.agentSessionId));
6116
+ const event = parseClaudeLimitEvent(readTailLines(file, QUOTA_TAIL_BYTES));
6117
+ if (event && (!newest || event.at > newest.at))
6118
+ newest = event;
6119
+ }
6120
+ return newest ? { agent: "claude", kind: "event", event: newest } : { agent: "claude", kind: "event" };
6121
+ }
6122
+ function collectQuotaFacts(agents, sessions, onPath) {
6123
+ const running = new Set(sessions.map((session) => session.agent).filter((agent) => agent !== undefined));
6124
+ const facts = [];
6125
+ for (const agent of agents) {
6126
+ if (!running.has(agent) && onPath(agent) === undefined)
6127
+ continue;
6128
+ const kind = agentProfile(agent).quota;
6129
+ if (kind === undefined) {
6130
+ facts.push({ agent, kind: "none" });
6131
+ continue;
6132
+ }
6133
+ if (agent === "codex")
6134
+ facts.push(codexQuotaFacts());
6135
+ else if (agent === "omp")
6136
+ facts.push(ompQuotaFacts());
6137
+ else if (agent === "claude")
6138
+ facts.push(claudeQuotaFacts(sessions));
6139
+ else
6140
+ facts.push({ agent, kind: kind === "gauge" ? "gauge" : "event" });
6141
+ }
6142
+ return facts;
6143
+ }
6144
+ function storeQuietForMs(agent, cwd, agentSessionId, nowMs) {
6145
+ if (agentSessionId === undefined)
6146
+ return;
6147
+ const file = agent === "claude" ? path16.join(claudeConfigDir(), claudeTranscriptRelPath(cwd, agentSessionId)) : agent === "codex" ? findCodexRollout(agentSessionId) : undefined;
6148
+ if (file === undefined)
6149
+ return;
6150
+ try {
6151
+ return Math.max(0, nowMs - fs17.statSync(file).mtimeMs);
6152
+ } catch {
6153
+ return;
6154
+ }
6155
+ }
6156
+ function readFileRange(file, from, to) {
6157
+ if (to <= from)
6158
+ return "";
6159
+ let fd;
6160
+ try {
6161
+ fd = fs17.openSync(file, "r");
6162
+ const buffer = Buffer.allocUnsafe(to - from);
6163
+ const read = fs17.readSync(fd, buffer, 0, to - from, from);
6164
+ return buffer.subarray(0, read).toString("utf8");
6165
+ } catch {
6166
+ return "";
6167
+ } finally {
6168
+ if (fd !== undefined)
6169
+ fs17.closeSync(fd);
6170
+ }
6171
+ }
6172
+ var QUOTA_TAIL_BYTES;
6173
+ var init_agent_store = __esm(() => {
6174
+ init_src();
6175
+ init_paths();
6176
+ QUOTA_TAIL_BYTES = 64 * 1024;
6177
+ });
6178
+
6179
+ // packages/daemon/src/codex-app-server.ts
6180
+ import net3 from "net";
6181
+ async function freeLoopbackPort() {
6182
+ const { promise, resolve, reject } = Promise.withResolvers();
6183
+ const probe = net3.createServer();
6184
+ probe.once("error", reject);
6185
+ probe.listen({ host: "127.0.0.1", port: 0 }, () => {
6186
+ const address = probe.address();
6187
+ const port = typeof address === "object" && address !== null ? address.port : 0;
6188
+ probe.close(() => port > 0 ? resolve(port) : reject(new Error("no port")));
6189
+ });
6190
+ return promise;
6191
+ }
6192
+ async function waitForPort(port, timeoutMs = READY_TIMEOUT_MS) {
6193
+ const deadline = Date.now() + timeoutMs;
6194
+ for (;; ) {
6195
+ const open = await new Promise((resolve) => {
6196
+ const socket = net3.connect({ host: "127.0.0.1", port });
6197
+ const done = (value) => {
6198
+ socket.destroy();
6199
+ resolve(value);
6200
+ };
6201
+ socket.once("connect", () => done(true));
6202
+ socket.once("error", () => done(false));
6203
+ });
6204
+ if (open)
6205
+ return true;
6206
+ if (Date.now() >= deadline)
6207
+ return false;
6208
+ await new Promise((resolve) => setTimeout(resolve, READY_POLL_MS));
6209
+ }
6210
+ }
6211
+
6212
+ class CodexAppServerClient {
6213
+ url;
6214
+ sink;
6215
+ socket;
6216
+ mapper = new CodexStreamMapper;
6217
+ joined = new Set;
6218
+ nextId = 1;
6219
+ closed = false;
6220
+ constructor(url, sink) {
6221
+ this.url = url;
6222
+ this.sink = sink;
6223
+ }
6224
+ get connected() {
6225
+ return this.socket?.readyState === WebSocket.OPEN;
6226
+ }
6227
+ connect() {
6228
+ if (this.closed)
6229
+ return;
6230
+ let socket;
6231
+ try {
6232
+ socket = new WebSocket(this.url);
6233
+ } catch (err) {
6234
+ log(`codex app-server: cannot connect (${String(err)}); the session keeps its hooks`);
6235
+ return;
6236
+ }
6237
+ this.socket = socket;
6238
+ socket.addEventListener("open", () => {
6239
+ this.send("initialize", { clientInfo: { name: "axterm", title: "axterm", version: "1" } });
6240
+ });
6241
+ socket.addEventListener("message", (event) => {
6242
+ this.onFrame(typeof event.data === "string" ? event.data : "");
6243
+ });
6244
+ socket.addEventListener("error", () => {});
6245
+ socket.addEventListener("close", () => {
6246
+ if (!this.closed)
6247
+ log("codex app-server: connection closed; the session keeps its hooks");
6248
+ });
6249
+ }
6250
+ close() {
6251
+ this.closed = true;
6252
+ try {
6253
+ this.socket?.close();
6254
+ } catch {}
6255
+ this.socket = undefined;
6256
+ }
6257
+ send(method, params) {
6258
+ if (this.socket?.readyState !== WebSocket.OPEN)
6259
+ return;
6260
+ this.socket.send(JSON.stringify({ jsonrpc: "2.0", id: this.nextId++, method, params }));
6261
+ }
6262
+ onFrame(raw) {
6263
+ if (raw === "")
6264
+ return;
6265
+ let message;
6266
+ try {
6267
+ message = JSON.parse(raw);
6268
+ } catch {
6269
+ return;
6270
+ }
6271
+ const method = message["method"];
6272
+ if (typeof method !== "string")
6273
+ return;
6274
+ const params = message["params"] ?? {};
6275
+ const threadId = params["threadId"];
6276
+ if (typeof threadId === "string" && !this.joined.has(threadId)) {
6277
+ this.joined.add(threadId);
6278
+ this.send("thread/resume", { threadId });
6279
+ log(`codex app-server: joined thread ${threadId.slice(0, 8)}`);
6280
+ }
6281
+ for (const payload of this.mapper.handle(method, params))
6282
+ this.sink(payload);
6283
+ }
6284
+ }
6285
+ var READY_TIMEOUT_MS = 1e4, READY_POLL_MS = 100;
6286
+ var init_codex_app_server = __esm(() => {
6287
+ init_src();
6288
+ init_paths();
6289
+ });
6290
+
6291
+ // packages/daemon/src/pty.ts
6292
+ import { spawnSync as spawnSync2 } from "child_process";
6293
+ import path17 from "path";
6294
+ import { StringDecoder } from "string_decoder";
6295
+
6296
+ class Fanout {
6297
+ listeners = new Set;
6298
+ backlog = [];
6299
+ add(listener) {
6300
+ this.listeners.add(listener);
6301
+ for (const value of this.backlog)
6302
+ listener(value);
6303
+ }
6304
+ emit(value) {
6305
+ if (this.listeners.size === 0)
6306
+ this.backlog.push(value);
6307
+ else
6308
+ for (const listener of this.listeners)
6309
+ listener(value);
6310
+ }
6311
+ }
6312
+ function killTree(pid, exeName) {
6313
+ if (process.platform === "win32") {
6314
+ const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
6315
+ encoding: "utf8",
6316
+ windowsHide: true
6317
+ });
6318
+ const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
6319
+ if (image === exeName) {
6320
+ spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
6321
+ }
6322
+ return;
6323
+ }
6324
+ try {
6325
+ process.kill(-pid, "SIGKILL");
6326
+ } catch {}
5141
6327
  }
5142
- async function processTable() {
5143
- const result = await capture(resolveShell("powershell").exe, [
5144
- "-NoLogo",
5145
- "-NoProfile",
5146
- "-NonInteractive",
5147
- "-Command",
5148
- "(Get-CimInstance -ClassName Win32_Process -Property ProcessId,ParentProcessId,Name,CreationDate" + " | Where-Object { $_.Name -ne 'conhost.exe' }" + ` | ForEach-Object { "$($_.ProcessId),$($_.ParentProcessId),$([long]($_.CreationDate.Ticks / 10000))" }) -join ' '`
5149
- ]);
5150
- if (result === undefined || result.code !== 0)
5151
- return;
5152
- const startedAt = new Map;
5153
- const newestChild = new Map;
5154
- for (const row of result.stdout.split(/\s+/)) {
5155
- const [rawPid, rawParent, rawStarted] = row.split(",");
5156
- const pid = Number(rawPid);
5157
- const parent = Number(rawParent);
5158
- const started = Number(rawStarted);
5159
- if (!Number.isInteger(pid) || !Number.isInteger(parent) || !Number.isInteger(started))
5160
- continue;
5161
- if (pid === result.pid)
5162
- continue;
5163
- startedAt.set(pid, started);
5164
- const newest = newestChild.get(parent);
5165
- if (newest === undefined || started > newest)
5166
- newestChild.set(parent, started);
6328
+
6329
+ class BunPtyHandle {
6330
+ proc;
6331
+ exeName;
6332
+ data = new Fanout;
6333
+ exit = new Fanout;
6334
+ decoder = new StringDecoder("utf8");
6335
+ exited = false;
6336
+ constructor(proc, exeName) {
6337
+ this.proc = proc;
6338
+ this.exeName = exeName;
6339
+ }
6340
+ get pid() {
6341
+ return this.proc.pid;
6342
+ }
6343
+ push(bytes) {
6344
+ const text = this.decoder.write(Buffer.from(bytes));
6345
+ if (text.length > 0)
6346
+ this.data.emit(text);
6347
+ }
6348
+ finish(code) {
6349
+ this.exited = true;
6350
+ const tail = this.decoder.end();
6351
+ if (tail.length > 0)
6352
+ this.data.emit(tail);
6353
+ try {
6354
+ this.proc.terminal.close();
6355
+ } catch {}
6356
+ this.exit.emit(code);
6357
+ }
6358
+ onData(listener) {
6359
+ this.data.add(listener);
6360
+ }
6361
+ onExit(listener) {
6362
+ this.exit.add(listener);
6363
+ }
6364
+ write(data) {
6365
+ this.proc.terminal.write(data);
6366
+ }
6367
+ resize(cols, rows) {
6368
+ this.proc.terminal.resize(cols, rows);
6369
+ }
6370
+ kill() {
6371
+ if (this.exited)
6372
+ return;
6373
+ killTree(this.proc.pid, this.exeName);
6374
+ try {
6375
+ this.proc.kill();
6376
+ } catch {}
5167
6377
  }
5168
- return startedAt.size > 0 ? { startedAt, newestChild } : undefined;
5169
6378
  }
5170
- function capture(exe, argv) {
5171
- const { promise, resolve } = Promise.withResolvers();
5172
- const child = spawn3(exe, argv, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
5173
- let stdout3 = "";
5174
- child.stdout.setEncoding("utf8");
5175
- child.stdout.on("data", (chunk) => {
5176
- stdout3 += chunk;
6379
+ function spawnPty(options) {
6380
+ let handle;
6381
+ const proc = Bun.spawn([options.exe, ...options.argv], {
6382
+ cwd: options.cwd,
6383
+ env: { ...options.env, TERM: options.term },
6384
+ terminal: {
6385
+ cols: options.cols,
6386
+ rows: options.rows,
6387
+ name: options.term,
6388
+ data(_terminal, bytes) {
6389
+ handle?.push(bytes);
6390
+ }
6391
+ }
5177
6392
  });
5178
- child.once("error", () => resolve(undefined));
5179
- child.once("close", (code) => resolve({ code, stdout: stdout3, pid: child.pid }));
5180
- return promise;
6393
+ handle = new BunPtyHandle(proc, path17.basename(options.exe).toLowerCase());
6394
+ proc.exited.then((code) => handle?.finish(code ?? null));
6395
+ return handle;
5181
6396
  }
5182
- var OCCUPANCY_CACHE_MS = 1500, perPid, snapshot;
5183
- var init_occupancy = __esm(() => {
5184
- init_shell();
5185
- perPid = new Map;
5186
- });
6397
+ var init_pty = () => {};
5187
6398
 
5188
6399
  // node_modules/.bun/@xterm+headless@5.5.0/node_modules/@xterm/headless/lib-headless/xterm-headless.js
5189
6400
  var require_xterm_headless = __commonJS((exports) => {
@@ -9470,161 +10681,53 @@ var require_addon_serialize = __commonJS((exports, module) => {
9470
10681
  constructor(e3, s2, r2) {
9471
10682
  super(e3), this._terminal = s2, this._options = r2, this._currentRow = "", this._htmlContent = "", s2._core._themeService ? this._ansiColors = s2._core._themeService.colors.ansi : this._ansiColors = t3.DEFAULT_ANSI_COLORS;
9472
10683
  }
9473
- _padStart(e3, t4, s2) {
9474
- return t4 >>= 0, s2 = s2 ?? " ", e3.length > t4 ? e3 : ((t4 -= e3.length) > s2.length && (s2 += s2.repeat(t4 / s2.length)), s2.slice(0, t4) + e3);
9475
- }
9476
- _beforeSerialize(e3, t4, s2) {
9477
- this._htmlContent += "<html><body><!--StartFragment--><pre>";
9478
- let r2 = "#000000", o2 = "#ffffff";
9479
- this._options.includeGlobalBackground && (r2 = this._terminal.options.theme?.foreground ?? "#ffffff", o2 = this._terminal.options.theme?.background ?? "#000000");
9480
- const i2 = [];
9481
- i2.push("color: " + r2 + ";"), i2.push("background-color: " + o2 + ";"), i2.push("font-family: " + this._terminal.options.fontFamily + ";"), i2.push("font-size: " + this._terminal.options.fontSize + "px;"), this._htmlContent += "<div style='" + i2.join(" ") + "'>";
9482
- }
9483
- _afterSerialize() {
9484
- this._htmlContent += "</div>", this._htmlContent += "</pre><!--EndFragment--></body></html>";
9485
- }
9486
- _rowEnd(e3, t4) {
9487
- this._htmlContent += "<div><span>" + this._currentRow + "</span></div>", this._currentRow = "";
9488
- }
9489
- _getHexColor(e3, t4) {
9490
- const s2 = t4 ? e3.getFgColor() : e3.getBgColor();
9491
- return (t4 ? e3.isFgRGB() : e3.isBgRGB()) ? "#" + [s2 >> 16 & 255, s2 >> 8 & 255, 255 & s2].map((e4) => this._padStart(e4.toString(16), 2, "0")).join("") : (t4 ? e3.isFgPalette() : e3.isBgPalette()) ? this._ansiColors[s2].css : undefined;
9492
- }
9493
- _diffStyle(e3, t4) {
9494
- const s2 = [], r2 = !n(e3, t4), o2 = !l(e3, t4), i2 = !a(e3, t4);
9495
- if (r2 || o2 || i2) {
9496
- const t5 = this._getHexColor(e3, true);
9497
- t5 && s2.push("color: " + t5 + ";");
9498
- const r3 = this._getHexColor(e3, false);
9499
- return r3 && s2.push("background-color: " + r3 + ";"), e3.isInverse() && s2.push("color: #000000; background-color: #BFBFBF;"), e3.isBold() && s2.push("font-weight: bold;"), e3.isUnderline() && e3.isOverline() ? s2.push("text-decoration: overline underline;") : e3.isUnderline() ? s2.push("text-decoration: underline;") : e3.isOverline() && s2.push("text-decoration: overline;"), e3.isBlink() && s2.push("text-decoration: blink;"), e3.isInvisible() && s2.push("visibility: hidden;"), e3.isItalic() && s2.push("font-style: italic;"), e3.isDim() && s2.push("opacity: 0.5;"), e3.isStrikethrough() && s2.push("text-decoration: line-through;"), s2;
9500
- }
9501
- }
9502
- _nextCell(e3, t4, s2, r2) {
9503
- if (e3.getWidth() === 0)
9504
- return;
9505
- const o2 = e3.getChars() === "", i2 = this._diffStyle(e3, t4);
9506
- i2 && (this._currentRow += i2.length === 0 ? "</span><span>" : "</span><span style='" + i2.join(" ") + "'>"), this._currentRow += o2 ? " " : e3.getChars();
9507
- }
9508
- _serializeString() {
9509
- return this._htmlContent;
9510
- }
9511
- }
9512
- e2.HTMLSerializeHandler = h;
9513
- })(), r;
9514
- })());
9515
- });
9516
-
9517
- // packages/daemon/src/pty.ts
9518
- import { spawnSync as spawnSync2 } from "child_process";
9519
- import path15 from "path";
9520
- import { StringDecoder } from "string_decoder";
9521
-
9522
- class Fanout {
9523
- listeners = new Set;
9524
- backlog = [];
9525
- add(listener) {
9526
- this.listeners.add(listener);
9527
- for (const value of this.backlog)
9528
- listener(value);
9529
- }
9530
- emit(value) {
9531
- if (this.listeners.size === 0)
9532
- this.backlog.push(value);
9533
- else
9534
- for (const listener of this.listeners)
9535
- listener(value);
9536
- }
9537
- }
9538
- function killTree(pid, exeName) {
9539
- if (process.platform === "win32") {
9540
- const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
9541
- encoding: "utf8",
9542
- windowsHide: true
9543
- });
9544
- const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
9545
- if (image === exeName) {
9546
- spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
9547
- }
9548
- return;
9549
- }
9550
- try {
9551
- process.kill(-pid, "SIGKILL");
9552
- } catch {}
9553
- }
9554
-
9555
- class BunPtyHandle {
9556
- proc;
9557
- exeName;
9558
- data = new Fanout;
9559
- exit = new Fanout;
9560
- decoder = new StringDecoder("utf8");
9561
- exited = false;
9562
- constructor(proc, exeName) {
9563
- this.proc = proc;
9564
- this.exeName = exeName;
9565
- }
9566
- get pid() {
9567
- return this.proc.pid;
9568
- }
9569
- push(bytes) {
9570
- const text = this.decoder.write(Buffer.from(bytes));
9571
- if (text.length > 0)
9572
- this.data.emit(text);
9573
- }
9574
- finish(code) {
9575
- this.exited = true;
9576
- const tail = this.decoder.end();
9577
- if (tail.length > 0)
9578
- this.data.emit(tail);
9579
- try {
9580
- this.proc.terminal.close();
9581
- } catch {}
9582
- this.exit.emit(code);
9583
- }
9584
- onData(listener) {
9585
- this.data.add(listener);
9586
- }
9587
- onExit(listener) {
9588
- this.exit.add(listener);
9589
- }
9590
- write(data) {
9591
- this.proc.terminal.write(data);
9592
- }
9593
- resize(cols, rows) {
9594
- this.proc.terminal.resize(cols, rows);
9595
- }
9596
- kill() {
9597
- if (this.exited)
9598
- return;
9599
- killTree(this.proc.pid, this.exeName);
9600
- try {
9601
- this.proc.kill();
9602
- } catch {}
9603
- }
9604
- }
9605
- function spawnPty(options) {
9606
- let handle;
9607
- const proc = Bun.spawn([options.exe, ...options.argv], {
9608
- cwd: options.cwd,
9609
- env: { ...options.env, TERM: options.term },
9610
- terminal: {
9611
- cols: options.cols,
9612
- rows: options.rows,
9613
- name: options.term,
9614
- data(_terminal, bytes) {
9615
- handle?.push(bytes);
10684
+ _padStart(e3, t4, s2) {
10685
+ return t4 >>= 0, s2 = s2 ?? " ", e3.length > t4 ? e3 : ((t4 -= e3.length) > s2.length && (s2 += s2.repeat(t4 / s2.length)), s2.slice(0, t4) + e3);
10686
+ }
10687
+ _beforeSerialize(e3, t4, s2) {
10688
+ this._htmlContent += "<html><body><!--StartFragment--><pre>";
10689
+ let r2 = "#000000", o2 = "#ffffff";
10690
+ this._options.includeGlobalBackground && (r2 = this._terminal.options.theme?.foreground ?? "#ffffff", o2 = this._terminal.options.theme?.background ?? "#000000");
10691
+ const i2 = [];
10692
+ i2.push("color: " + r2 + ";"), i2.push("background-color: " + o2 + ";"), i2.push("font-family: " + this._terminal.options.fontFamily + ";"), i2.push("font-size: " + this._terminal.options.fontSize + "px;"), this._htmlContent += "<div style='" + i2.join(" ") + "'>";
10693
+ }
10694
+ _afterSerialize() {
10695
+ this._htmlContent += "</div>", this._htmlContent += "</pre><!--EndFragment--></body></html>";
10696
+ }
10697
+ _rowEnd(e3, t4) {
10698
+ this._htmlContent += "<div><span>" + this._currentRow + "</span></div>", this._currentRow = "";
10699
+ }
10700
+ _getHexColor(e3, t4) {
10701
+ const s2 = t4 ? e3.getFgColor() : e3.getBgColor();
10702
+ return (t4 ? e3.isFgRGB() : e3.isBgRGB()) ? "#" + [s2 >> 16 & 255, s2 >> 8 & 255, 255 & s2].map((e4) => this._padStart(e4.toString(16), 2, "0")).join("") : (t4 ? e3.isFgPalette() : e3.isBgPalette()) ? this._ansiColors[s2].css : undefined;
10703
+ }
10704
+ _diffStyle(e3, t4) {
10705
+ const s2 = [], r2 = !n(e3, t4), o2 = !l(e3, t4), i2 = !a(e3, t4);
10706
+ if (r2 || o2 || i2) {
10707
+ const t5 = this._getHexColor(e3, true);
10708
+ t5 && s2.push("color: " + t5 + ";");
10709
+ const r3 = this._getHexColor(e3, false);
10710
+ return r3 && s2.push("background-color: " + r3 + ";"), e3.isInverse() && s2.push("color: #000000; background-color: #BFBFBF;"), e3.isBold() && s2.push("font-weight: bold;"), e3.isUnderline() && e3.isOverline() ? s2.push("text-decoration: overline underline;") : e3.isUnderline() ? s2.push("text-decoration: underline;") : e3.isOverline() && s2.push("text-decoration: overline;"), e3.isBlink() && s2.push("text-decoration: blink;"), e3.isInvisible() && s2.push("visibility: hidden;"), e3.isItalic() && s2.push("font-style: italic;"), e3.isDim() && s2.push("opacity: 0.5;"), e3.isStrikethrough() && s2.push("text-decoration: line-through;"), s2;
10711
+ }
10712
+ }
10713
+ _nextCell(e3, t4, s2, r2) {
10714
+ if (e3.getWidth() === 0)
10715
+ return;
10716
+ const o2 = e3.getChars() === "", i2 = this._diffStyle(e3, t4);
10717
+ i2 && (this._currentRow += i2.length === 0 ? "</span><span>" : "</span><span style='" + i2.join(" ") + "'>"), this._currentRow += o2 ? " " : e3.getChars();
10718
+ }
10719
+ _serializeString() {
10720
+ return this._htmlContent;
10721
+ }
9616
10722
  }
9617
- }
9618
- });
9619
- handle = new BunPtyHandle(proc, path15.basename(options.exe).toLowerCase());
9620
- proc.exited.then((code) => handle?.finish(code ?? null));
9621
- return handle;
9622
- }
9623
- var init_pty = () => {};
10723
+ e2.HTMLSerializeHandler = h;
10724
+ })(), r;
10725
+ })());
10726
+ });
9624
10727
 
9625
10728
  // packages/daemon/src/pty-session.ts
9626
- import fs16 from "fs";
9627
- import path16 from "path";
10729
+ import fs18 from "fs";
10730
+ import path18 from "path";
9628
10731
 
9629
10732
  class PtySession {
9630
10733
  info;
@@ -9740,10 +10843,10 @@ class PtySession {
9740
10843
  }
9741
10844
  saveScrollback() {
9742
10845
  try {
9743
- const file = path16.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
10846
+ const file = path18.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
9744
10847
  const tmp = `${file}.tmp`;
9745
- fs16.writeFileSync(tmp, this.serializeBuffer(), "utf8");
9746
- fs16.renameSync(tmp, file);
10848
+ fs18.writeFileSync(tmp, this.serializeBuffer(), "utf8");
10849
+ fs18.renameSync(tmp, file);
9747
10850
  } catch (err) {
9748
10851
  log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
9749
10852
  }
@@ -9765,9 +10868,9 @@ var init_pty_session = __esm(() => {
9765
10868
 
9766
10869
  // packages/daemon/src/session-manager.ts
9767
10870
  import { randomBytes as randomBytes2, randomUUID } from "crypto";
9768
- import fs17 from "fs";
10871
+ import fs19 from "fs";
9769
10872
  import os5 from "os";
9770
- import path17 from "path";
10873
+ import path19 from "path";
9771
10874
  function unresolvableAgent(commandOverride, agentCommand, lookup) {
9772
10875
  if (commandOverride !== undefined)
9773
10876
  return;
@@ -9810,6 +10913,7 @@ async function sendableNow(runtime, info, probe, now) {
9810
10913
  class SessionManager {
9811
10914
  store;
9812
10915
  hooks;
10916
+ configuredDefaults;
9813
10917
  live = new Map;
9814
10918
  agentRuntime = new Map;
9815
10919
  injector = new Injector;
@@ -9817,13 +10921,18 @@ class SessionManager {
9817
10921
  eventListeners = new Set;
9818
10922
  rawHookListeners = new Set;
9819
10923
  workspace = new LocalWorkspace(MATERIALISED_DIR);
9820
- constructor(store, hooks) {
10924
+ constructor(store, hooks, configuredDefaults = () => ({})) {
9821
10925
  this.store = store;
9822
10926
  this.hooks = hooks;
10927
+ this.configuredDefaults = configuredDefaults;
9823
10928
  const stale = store.markStaleRunningAsExited();
9824
10929
  if (stale > 0)
9825
10930
  log(`boot: marked ${stale} stale session(s) as exited`);
10931
+ this.sessionWatch = setInterval(() => this.tickSessions(), SESSION_TICK_MS);
10932
+ this.sessionWatch.unref();
9826
10933
  }
10934
+ sessionWatch;
10935
+ codexServers = new Map;
9827
10936
  workspaceFor(_sessionId) {
9828
10937
  return this.workspace;
9829
10938
  }
@@ -9900,8 +11009,8 @@ class SessionManager {
9900
11009
  };
9901
11010
  }
9902
11011
  async create(params) {
9903
- const requestedCwd = params.cwd ? path17.resolve(params.cwd) : os5.homedir();
9904
- if (!fs17.existsSync(requestedCwd)) {
11012
+ const requestedCwd = params.cwd ? path19.resolve(params.cwd) : os5.homedir();
11013
+ if (!fs19.existsSync(requestedCwd)) {
9905
11014
  throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
9906
11015
  }
9907
11016
  let worktree;
@@ -9918,17 +11027,29 @@ class SessionManager {
9918
11027
  log(`worktree ${created.createdBranch ? "created" : "reused"} ${created.branch} at ${created.path}`);
9919
11028
  }
9920
11029
  const cwd = worktree?.path ?? requestedCwd;
9921
- const shell = resolveShell(params.shell);
9922
- const agent = params.agent ?? "omp";
11030
+ const configured = this.configuredDefaults();
11031
+ const shell = resolveShell(params.shell ?? configured.shell);
11032
+ const agent = params.agent ?? configured.agent ?? "omp";
9923
11033
  if (!isAgentId(agent))
9924
11034
  throw new AxtermError("err.agent.unknown", { agent, supported: supportedAgents().join(", ") });
9925
11035
  const id = randomUUID().replace(/-/g, "");
11036
+ const extraArgs = resolveAgentArgs({
11037
+ bypass: agentProfile(agent).approvalBypassArgs,
11038
+ autoApprove: params.autoApprove,
11039
+ autoApproveConfigured: configured.autoApprove,
11040
+ configured: configured.agentArgs?.[agent],
11041
+ requested: params.agentArgs
11042
+ });
11043
+ const mcpContext = agent === "omp" ? undefined : this.mcpContextPath(id);
11044
+ const mcpConfigPath = agent === "claude" ? this.writeMcpConfig(id, mcpContext) : undefined;
9926
11045
  const agentCommand = params.commandOverride ?? buildAgentCommand({
9927
11046
  agent,
9928
11047
  sessionId: id,
9929
11048
  extensionPath: this.hooks.extensionPath,
9930
11049
  claudeSettingsPath: this.claudeSettingsPath(id),
9931
- ...params.agentArgs !== undefined ? { extraArgs: params.agentArgs } : {}
11050
+ ...mcpConfigPath === undefined ? {} : { mcpConfigPath },
11051
+ ...agent === "codex" ? { mcpLaunch: this.mcpServerLaunch(), mcpContextPath: mcpContext } : {},
11052
+ ...extraArgs.length > 0 ? { extraArgs } : {}
9932
11053
  });
9933
11054
  const missing = unresolvableAgent(params.commandOverride, agentCommand, lookupOnPath);
9934
11055
  if (missing !== undefined)
@@ -9936,7 +11057,7 @@ class SessionManager {
9936
11057
  const info = {
9937
11058
  id,
9938
11059
  shortId: id.slice(0, 6),
9939
- name: params.name ?? worktree?.branch ?? path17.basename(cwd),
11060
+ name: params.name ?? worktree?.branch ?? path19.basename(cwd),
9940
11061
  cwd,
9941
11062
  shell: shell.kind,
9942
11063
  agentCommand,
@@ -9946,7 +11067,8 @@ class SessionManager {
9946
11067
  createdAt: Date.now(),
9947
11068
  ...worktree !== undefined ? { worktreePath: worktree.path, branch: worktree.branch, repoRoot: worktree.repoRoot } : {}
9948
11069
  };
9949
- this.launch(info, shell, agentCommand);
11070
+ const appServerUrl = agent === "codex" ? await this.startCodexAppServer(id, this.ensureCodexHome(), cwd) : undefined;
11071
+ this.launch(info, shell, agentCommand, appServerUrl);
9950
11072
  this.store.insertSession(info);
9951
11073
  log(`session ${info.shortId} created shell=${shell.kind} cmd=${agentCommand.join(" ") || "(shell only)"}`);
9952
11074
  this.emit({ t: "session.created", info });
@@ -9960,37 +11082,82 @@ class SessionManager {
9960
11082
  launchToken
9961
11083
  });
9962
11084
  const file = this.claudeSettingsPath(id);
9963
- fs17.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
11085
+ fs19.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
9964
11086
  }
9965
- writeGeminiSettings() {
11087
+ writeGeminiSettings(sessionId) {
9966
11088
  try {
9967
11089
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "gemini" });
9968
- const settings = buildGeminiSettings({ command: bridge.command, args: bridge.args });
11090
+ const settings = buildGeminiSettings({
11091
+ command: bridge.command,
11092
+ args: bridge.args,
11093
+ mcp: this.mcpServerLaunch(),
11094
+ mcpContextPath: this.mcpContextPath(sessionId)
11095
+ });
9969
11096
  const file = this.geminiSettingsPath();
9970
11097
  const tmp = `${file}.tmp`;
9971
- fs17.writeFileSync(tmp, JSON.stringify(settings, null, 2));
9972
- fs17.renameSync(tmp, file);
11098
+ fs19.writeFileSync(tmp, JSON.stringify(settings, null, 2));
11099
+ fs19.renameSync(tmp, file);
9973
11100
  } catch (err) {
9974
11101
  log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
9975
11102
  }
9976
11103
  }
11104
+ async startCodexAppServer(id, codexHome2, cwd) {
11105
+ try {
11106
+ const port = await freeLoopbackPort();
11107
+ const url = `ws://127.0.0.1:${port}`;
11108
+ const proc = Bun.spawn(["codex", "app-server", "--listen", url], {
11109
+ cwd,
11110
+ env: { ...stripHostAttribution(process.env), CODEX_HOME: codexHome2 },
11111
+ stdout: "ignore",
11112
+ stderr: "ignore",
11113
+ stdin: "ignore"
11114
+ });
11115
+ if (!await waitForPort(port)) {
11116
+ proc.kill();
11117
+ log(`codex app-server did not open ${port}; session ${id.slice(0, 6)} keeps its hooks`);
11118
+ return;
11119
+ }
11120
+ const client = new CodexAppServerClient(url, (payload) => this.ingestHookEvent(id, payload));
11121
+ client.connect();
11122
+ this.codexServers.set(id, {
11123
+ stop: () => {
11124
+ client.close();
11125
+ try {
11126
+ killTree(proc.pid, "codex.exe");
11127
+ } catch {}
11128
+ try {
11129
+ proc.kill();
11130
+ } catch {}
11131
+ }
11132
+ });
11133
+ log(`codex app-server on ${url} for session ${id.slice(0, 6)} in ${cwd}`);
11134
+ return url;
11135
+ } catch (err) {
11136
+ log(`codex app-server unavailable (${String(err)}); session ${id.slice(0, 6)} keeps its hooks`);
11137
+ return;
11138
+ }
11139
+ }
11140
+ stopCodexAppServer(id) {
11141
+ this.codexServers.get(id)?.stop();
11142
+ this.codexServers.delete(id);
11143
+ }
9977
11144
  ensureCodexHome() {
9978
- const home = path17.join(STATE_DIR, "codex-home");
9979
- fs17.mkdirSync(home, { recursive: true, mode: 448 });
11145
+ const home = path19.join(STATE_DIR, "codex-home");
11146
+ fs19.mkdirSync(home, { recursive: true, mode: 448 });
9980
11147
  this.linkCodexAuth(home);
9981
11148
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
9982
11149
  const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
9983
11150
  const hooksPath = codexTrustKeyPath(home);
9984
11151
  const tmp = `${hooksPath}.tmp`;
9985
- fs17.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
9986
- fs17.renameSync(tmp, hooksPath);
11152
+ fs19.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
11153
+ fs19.renameSync(tmp, hooksPath);
9987
11154
  const entries = codexHookTrust(hooksPath, hooksFile);
9988
- const configPath = path17.join(home, "config.toml");
9989
- const existing = fs17.existsSync(configPath) ? fs17.readFileSync(configPath, "utf8") : "";
11155
+ const configPath = path19.join(home, "config.toml");
11156
+ const existing = fs19.existsSync(configPath) ? fs19.readFileSync(configPath, "utf8") : "";
9990
11157
  const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
9991
11158
  if (missing.length > 0) {
9992
11159
  const block = codexTrustStateToml(missing);
9993
- fs17.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
11160
+ fs19.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
9994
11161
  `) ? `
9995
11162
  ${block}` : block);
9996
11163
  }
@@ -10003,18 +11170,18 @@ ${block}` : block);
10003
11170
  return home;
10004
11171
  }
10005
11172
  linkCodexAuth(home) {
10006
- const target = path17.join(home, "auth.json");
10007
- if (fs17.existsSync(target))
11173
+ const target = path19.join(home, "auth.json");
11174
+ if (fs19.existsSync(target))
10008
11175
  return;
10009
- const operatorHome = process.env["CODEX_HOME"] ?? path17.join(os5.homedir(), ".codex");
10010
- const source = path17.join(operatorHome, "auth.json");
10011
- if (!fs17.existsSync(source))
11176
+ const operatorHome = process.env["CODEX_HOME"] ?? path19.join(os5.homedir(), ".codex");
11177
+ const source = path19.join(operatorHome, "auth.json");
11178
+ if (!fs19.existsSync(source))
10012
11179
  throw new AxtermError("err.codex.noAuth", { authFile: source });
10013
11180
  try {
10014
- fs17.symlinkSync(source, target, "file");
11181
+ fs19.symlinkSync(source, target, "file");
10015
11182
  } catch {
10016
11183
  try {
10017
- fs17.linkSync(source, target);
11184
+ fs19.linkSync(source, target);
10018
11185
  } catch (err) {
10019
11186
  throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
10020
11187
  }
@@ -10022,37 +11189,45 @@ ${block}` : block);
10022
11189
  }
10023
11190
  doctorReport() {
10024
11191
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
10025
- const home = path17.join(STATE_DIR, "codex-home");
10026
- const operatorHome = process.env["CODEX_HOME"] ?? path17.join(os5.homedir(), ".codex");
10027
- const authSource = path17.join(operatorHome, "auth.json");
11192
+ const home = path19.join(STATE_DIR, "codex-home");
11193
+ const operatorHome = process.env["CODEX_HOME"] ?? path19.join(os5.homedir(), ".codex");
11194
+ const authSource = path19.join(operatorHome, "auth.json");
10028
11195
  const geminiSettings = this.geminiSettingsPath();
10029
- const configPath = path17.join(home, "config.toml");
11196
+ const configPath = path19.join(home, "config.toml");
10030
11197
  const read = (file) => {
10031
11198
  try {
10032
- return fs17.existsSync(file) ? fs17.readFileSync(file, "utf8") : undefined;
11199
+ return fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : undefined;
10033
11200
  } catch {
10034
11201
  return;
10035
11202
  }
10036
11203
  };
10037
11204
  return [
10038
- ...evaluateBridgeDoctor({ command: bridge.command, exists: fs17.existsSync(bridge.command) }),
11205
+ ...evaluateBridgeDoctor({ command: bridge.command, exists: fs19.existsSync(bridge.command) }),
10039
11206
  ...evaluateOmpDoctor({
10040
11207
  binary: lookupOnPath("omp"),
10041
11208
  extensionPath: this.hooks.extensionPath,
10042
- extensionExists: fs17.existsSync(this.hooks.extensionPath)
11209
+ extensionExists: fs19.existsSync(this.hooks.extensionPath)
10043
11210
  }),
10044
11211
  ...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
10045
11212
  ...evaluateCodexDoctor({
10046
11213
  binary: lookupOnPath("codex"),
10047
11214
  authSourcePath: authSource,
10048
- authSourceExists: fs17.existsSync(authSource),
10049
- homeExists: fs17.existsSync(home),
11215
+ authSourceExists: fs19.existsSync(authSource),
11216
+ homeExists: fs19.existsSync(home),
10050
11217
  expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
10051
11218
  configToml: read(configPath)
10052
11219
  }),
10053
11220
  ...evaluateGeminiDoctor({ binary: lookupOnPath("gemini"), settingsJson: read(geminiSettings) })
10054
11221
  ];
10055
11222
  }
11223
+ quotaReport() {
11224
+ const sessions = this.store.listSessions().map((session) => ({
11225
+ cwd: session.cwd,
11226
+ agent: agentIdFromCommand(session.agentCommand),
11227
+ ...session.agentSessionId === undefined ? {} : { agentSessionId: session.agentSessionId }
11228
+ }));
11229
+ return quotaReport(collectQuotaFacts(REGISTERED_AGENTS, sessions, lookupOnPath), Date.now());
11230
+ }
10056
11231
  sawOutput = new Set;
10057
11232
  idleTimers = new Map;
10058
11233
  lastCaptured = new Map;
@@ -10079,7 +11254,7 @@ ${block}` : block);
10079
11254
  if (!session || !runtime)
10080
11255
  return;
10081
11256
  const found = detectPrompt(session.screenText(4));
10082
- const dialog = found === undefined && agentIdFromCommand(session.info.agentCommand) === "gemini" ? detectGeminiOnboarding(session.screenText(30)) : undefined;
11257
+ const dialog = found === undefined ? detectOnboarding(agentIdFromCommand(session.info.agentCommand), session.screenText(30)) : undefined;
10083
11258
  if (!found && !dialog) {
10084
11259
  if (this.pendingPrompt.delete(id))
10085
11260
  this.emit({ t: "choice.resolved", sessionId: id, seq: runtime.choiceSeq });
@@ -10162,51 +11337,89 @@ ${block}` : block);
10162
11337
  }
10163
11338
  writeScreenFile(idOrPrefix, lines) {
10164
11339
  const session = this.resolveLive(idOrPrefix);
10165
- const file = path17.join(path17.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
10166
- fs17.writeFileSync(file, session.screenText(lines), { mode: 384 });
11340
+ const file = path19.join(path19.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
11341
+ fs19.writeFileSync(file, session.screenText(lines), { mode: 384 });
10167
11342
  return file;
10168
11343
  }
10169
11344
  removeScreenFile(id) {
10170
11345
  try {
10171
- fs17.rmSync(path17.join(path17.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
11346
+ fs19.rmSync(path19.join(path19.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
10172
11347
  } catch {}
10173
11348
  }
10174
11349
  async stageInboundFile(idOrPrefix, suggestedName, bytes) {
10175
11350
  const info = this.resolveLive(idOrPrefix).info;
10176
- const file = path17.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
10177
- await fs17.promises.mkdir(path17.dirname(file), { recursive: true, mode: 448 });
10178
- await fs17.promises.writeFile(file, bytes, { mode: 384 });
10179
- log(`session ${info.shortId} staged ${path17.basename(file)} (${bytes.byteLength} bytes)`);
11351
+ const file = path19.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
11352
+ await fs19.promises.mkdir(path19.dirname(file), { recursive: true, mode: 448 });
11353
+ await fs19.promises.writeFile(file, bytes, { mode: 384 });
11354
+ log(`session ${info.shortId} staged ${path19.basename(file)} (${bytes.byteLength} bytes)`);
10180
11355
  return file;
10181
11356
  }
10182
11357
  removeStagedFiles(id) {
10183
11358
  try {
10184
- fs17.rmSync(path17.join(MATERIALISED_DIR, id), { recursive: true, force: true });
11359
+ fs19.rmSync(path19.join(MATERIALISED_DIR, id), { recursive: true, force: true });
10185
11360
  } catch {}
10186
11361
  }
10187
11362
  removeClaudeSettings(id) {
10188
11363
  try {
10189
- fs17.rmSync(this.claudeSettingsPath(id), { force: true });
11364
+ fs19.rmSync(this.claudeSettingsPath(id), { force: true });
11365
+ fs19.rmSync(this.mcpConfigPath(id), { force: true });
11366
+ fs19.rmSync(this.mcpContextPath(id), { force: true });
11367
+ fs19.rmSync(this.geminiTelemetryPath(id), { force: true });
11368
+ this.stopCodexAppServer(id);
10190
11369
  } catch {}
10191
11370
  }
10192
11371
  claudeSettingsPath(id) {
10193
- return path17.join(path17.dirname(this.hooks.endpointFile), `claude-${id}.json`);
11372
+ return path19.join(path19.dirname(this.hooks.endpointFile), `claude-${id}.json`);
11373
+ }
11374
+ mcpConfigPath(id) {
11375
+ return path19.join(path19.dirname(this.hooks.endpointFile), `mcp-${id}.json`);
11376
+ }
11377
+ mcpContextPath(id) {
11378
+ return path19.join(path19.dirname(this.hooks.endpointFile), `mcp-ctx-${id}.json`);
11379
+ }
11380
+ writeMcpContext(id, launchToken) {
11381
+ const file = this.mcpContextPath(id);
11382
+ try {
11383
+ fs19.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
11384
+ return file;
11385
+ } catch (err) {
11386
+ log(`mcp context write failed; session starts without axterm's tools: ${String(err)}`);
11387
+ return;
11388
+ }
11389
+ }
11390
+ mcpServerLaunch() {
11391
+ return mcpLaunch({ execPath: process.execPath, mainScript: Bun.main });
11392
+ }
11393
+ writeMcpConfig(id, contextPath) {
11394
+ const file = this.mcpConfigPath(id);
11395
+ try {
11396
+ fs19.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
11397
+ return file;
11398
+ } catch (err) {
11399
+ log(`mcp config write failed; session starts without axterm's tools: ${String(err)}`);
11400
+ return;
11401
+ }
11402
+ }
11403
+ geminiTelemetryPath(id) {
11404
+ return path19.join(path19.dirname(this.hooks.endpointFile), `gemini-telemetry-${id}.log`);
10194
11405
  }
10195
11406
  geminiSettingsPath() {
10196
- return path17.join(path17.dirname(this.hooks.endpointFile), "gemini-defaults.json");
11407
+ return path19.join(path19.dirname(this.hooks.endpointFile), "gemini-defaults.json");
10197
11408
  }
10198
- launch(info, shell, agentCommand) {
11409
+ launch(info, shell, agentCommand, appServerUrl) {
10199
11410
  const id = info.id;
10200
11411
  const launchToken = randomBytes2(16).toString("hex");
10201
11412
  const agentId = agentIdFromCommand(info.agentCommand);
10202
11413
  if (agentId === "claude")
10203
11414
  this.writeClaudeSettings(id, launchToken);
11415
+ if (agentId !== "omp")
11416
+ this.writeMcpContext(id, launchToken);
10204
11417
  if (agentId === "gemini")
10205
- this.writeGeminiSettings();
10206
- const codexHome = agentId === "codex" ? this.ensureCodexHome() : undefined;
11418
+ this.writeGeminiSettings(id);
11419
+ const codexHome2 = agentId === "codex" ? this.ensureCodexHome() : undefined;
10207
11420
  const session = new PtySession(info, {
10208
11421
  exe: shell.exe,
10209
- argv: buildShellArgv(shell, agentCommand),
11422
+ argv: buildShellArgv(shell, withAppServer(agentCommand, appServerUrl)),
10210
11423
  cwd: info.cwd,
10211
11424
  cols: info.cols,
10212
11425
  rows: info.rows,
@@ -10217,8 +11430,15 @@ ${block}` : block);
10217
11430
  AXTERM_HOOK_PORT: String(this.hooks.port),
10218
11431
  AXTERM_HOOK_TOKEN: this.hooks.host.token,
10219
11432
  AXTERM_HOOK_ENDPOINT: this.hooks.endpointFile,
10220
- ...agentId === "gemini" ? { GEMINI_CLI_SYSTEM_DEFAULTS_PATH: this.geminiSettingsPath() } : {},
10221
- ...codexHome !== undefined ? { CODEX_HOME: codexHome } : {}
11433
+ ...agentId === "gemini" ? {
11434
+ GEMINI_CLI_SYSTEM_DEFAULTS_PATH: this.geminiSettingsPath(),
11435
+ GEMINI_TELEMETRY_ENABLED: "true",
11436
+ GEMINI_TELEMETRY_TARGET: "local",
11437
+ GEMINI_TELEMETRY_OUTFILE: this.geminiTelemetryPath(id),
11438
+ GEMINI_TELEMETRY_LOG_PROMPTS: "false",
11439
+ GEMINI_TELEMETRY_TRACES_ENABLED: "false"
11440
+ } : {},
11441
+ ...codexHome2 !== undefined ? { CODEX_HOME: codexHome2 } : {}
10222
11442
  }
10223
11443
  });
10224
11444
  this.agentRuntime.set(id, { agentExited: false, choiceSeq: 0, dialogOpen: false });
@@ -10340,6 +11560,9 @@ ${block}` : block);
10340
11560
  this.emit({ t: "chat.message", sessionId: id, role: "assistant", text: result.assistantText });
10341
11561
  }
10342
11562
  if (payload.event === "before_agent_start") {
11563
+ const runtime2 = this.agentRuntime.get(id);
11564
+ if (runtime2 !== undefined)
11565
+ runtime2.telemetryReported = false;
10343
11566
  const prompt = (payload.prompt ?? "").trim();
10344
11567
  if (prompt.length > 0 && !this.echoGuard.consume(id, prompt, now)) {
10345
11568
  this.emit({ t: "chat.message", sessionId: id, role: "user", text: prompt });
@@ -10416,14 +11639,10 @@ ${block}` : block);
10416
11639
  return this.allSessions();
10417
11640
  }
10418
11641
  allSessions() {
10419
- const liveIds = new Set(this.live.keys());
10420
- return [
10421
- ...[...this.live.values()].map((s) => s.info),
10422
- ...this.store.listSessions().filter((s) => !liveIds.has(s.id))
10423
- ];
11642
+ return sessionsByRecency([...this.live.values()].map((s) => s.info), this.store.listSessions());
10424
11643
  }
10425
11644
  resumeNamed(stored, agent) {
10426
- if (!fs17.existsSync(stored.cwd))
11645
+ if (!fs19.existsSync(stored.cwd))
10427
11646
  throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
10428
11647
  const agentCommand = buildAgentResumeCommand({
10429
11648
  agent,
@@ -10442,7 +11661,7 @@ ${block}` : block);
10442
11661
  this.emit({ t: "session.created", info });
10443
11662
  return info;
10444
11663
  }
10445
- resume(idOrPrefix) {
11664
+ async resume(idOrPrefix) {
10446
11665
  const stored = this.store.findByPrefix(idOrPrefix);
10447
11666
  if (!stored)
10448
11667
  throw new AxtermError("err.session.unknown", { id: idOrPrefix });
@@ -10463,7 +11682,7 @@ ${block}` : block);
10463
11682
  if (agentCommand === undefined) {
10464
11683
  throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
10465
11684
  }
10466
- if (!fs17.existsSync(stored.cwd))
11685
+ if (!fs19.existsSync(stored.cwd))
10467
11686
  throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
10468
11687
  const info = { ...stored, agentCommand, state: "running" };
10469
11688
  delete info.exitCode;
@@ -10471,7 +11690,8 @@ ${block}` : block);
10471
11690
  delete info.agentState;
10472
11691
  delete info.agentStateSource;
10473
11692
  const shell = resolveShell(info.shell);
10474
- this.launch(info, shell, agentCommand);
11693
+ const resumedAppServer = agentIdFromCommand(agentCommand) === "codex" ? await this.startCodexAppServer(info.id, this.ensureCodexHome(), info.cwd) : undefined;
11694
+ this.launch(info, shell, agentCommand, resumedAppServer);
10475
11695
  this.store.markResumed(info.id, agentCommand, info.ptyPid);
10476
11696
  log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
10477
11697
  this.emit({ t: "session.created", info });
@@ -10536,11 +11756,86 @@ ${block}` : block);
10536
11756
  removeScrollback(id) {
10537
11757
  for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
10538
11758
  try {
10539
- fs17.rmSync(path17.join(SCROLLBACK_DIR, file), { force: true });
11759
+ fs19.rmSync(path19.join(SCROLLBACK_DIR, file), { force: true });
10540
11760
  } catch {}
10541
11761
  }
10542
11762
  }
11763
+ tickSessions() {
11764
+ this.drainGeminiTelemetry();
11765
+ this.watchForStalls();
11766
+ }
11767
+ drainGeminiTelemetry() {
11768
+ for (const [id, session] of this.live) {
11769
+ if (agentIdFromCommand(session.info.agentCommand) !== "gemini")
11770
+ continue;
11771
+ const runtime = this.agentRuntime.get(id);
11772
+ if (runtime === undefined)
11773
+ continue;
11774
+ const file = this.geminiTelemetryPath(id);
11775
+ let size = 0;
11776
+ try {
11777
+ size = fs19.statSync(file).size;
11778
+ } catch {
11779
+ continue;
11780
+ }
11781
+ const offset = runtime.telemetryOffset ?? 0;
11782
+ const from = size < offset ? 0 : offset;
11783
+ if (size < offset)
11784
+ runtime.telemetryRemainder = "";
11785
+ if (size <= from)
11786
+ continue;
11787
+ const chunk = readFileRange(file, from, size);
11788
+ runtime.telemetryOffset = size;
11789
+ const split = splitTelemetryRecords(`${runtime.telemetryRemainder ?? ""}${chunk}`);
11790
+ runtime.telemetryRemainder = split.remainder;
11791
+ const failures = parseGeminiTelemetry(split.complete);
11792
+ if (failures.length === 0)
11793
+ continue;
11794
+ const first = failures[0];
11795
+ log(`session ${session.info.shortId} gemini api failure${first.status === undefined ? "" : ` (${first.status})`}: ${first.text.slice(0, 200)}`);
11796
+ if (runtime.telemetryReported === true)
11797
+ continue;
11798
+ runtime.telemetryReported = true;
11799
+ this.emit({
11800
+ t: "agent.error",
11801
+ sessionId: id,
11802
+ kind: "attempt",
11803
+ text: first.status === undefined ? first.text : `${first.status} ${first.text}`
11804
+ });
11805
+ }
11806
+ }
11807
+ watchForStalls() {
11808
+ const now = Date.now();
11809
+ for (const [id, session] of this.live) {
11810
+ const runtime = this.agentRuntime.get(id);
11811
+ if (runtime === undefined)
11812
+ continue;
11813
+ const quietForMs = now - session.lastOutputAt;
11814
+ if (quietForMs <= STALL_AFTER_MS) {
11815
+ runtime.stallReported = false;
11816
+ continue;
11817
+ }
11818
+ const storeQuiet = storeQuietForMs(agentIdFromCommand(session.info.agentCommand), session.info.cwd, session.info.agentSessionId, now);
11819
+ if (!shouldReportStall({
11820
+ agentState: runtime.entry?.state,
11821
+ quietForMs,
11822
+ thresholdMs: STALL_AFTER_MS,
11823
+ reported: runtime.stallReported === true,
11824
+ ...storeQuiet === undefined ? {} : { storeQuietForMs: storeQuiet }
11825
+ })) {
11826
+ continue;
11827
+ }
11828
+ runtime.stallReported = true;
11829
+ const screen = quoteScreenTail(session.screenText(STALL_SCREEN_LINES), STALL_QUOTE_LINES).join(`
11830
+ `);
11831
+ log(`session ${session.info.shortId} has made no progress for ${Math.round(quietForMs / 1000)}s`);
11832
+ this.emit({ t: "agent.error", sessionId: id, kind: "stalled", text: screen, retryMs: quietForMs });
11833
+ }
11834
+ }
10543
11835
  shutdownAll() {
11836
+ clearInterval(this.sessionWatch);
11837
+ for (const id of [...this.codexServers.keys()])
11838
+ this.stopCodexAppServer(id);
10544
11839
  for (const session of this.live.values()) {
10545
11840
  session.saveScrollback();
10546
11841
  session.kill();
@@ -10549,7 +11844,7 @@ ${block}` : block);
10549
11844
  this.agentRuntime.clear();
10550
11845
  }
10551
11846
  }
10552
- var DEFAULT_COLS = 120, DEFAULT_ROWS = 30, MATERIALISED_DIR, ACTIVITY_ARG_CHARS = 160, PROMPT_SETTLE_MS = 400, PROMPT_OPTIONS, AGENT_EXIT_CONFIRM_MS = 5000;
11847
+ var DEFAULT_COLS = 120, DEFAULT_ROWS = 30, MATERIALISED_DIR, ACTIVITY_ARG_CHARS = 160, PROMPT_SETTLE_MS = 400, STALL_AFTER_MS = 180000, STALL_SCREEN_LINES = 24, STALL_QUOTE_LINES = 6, SESSION_TICK_MS = 5000, PROMPT_OPTIONS, AGENT_EXIT_CONFIRM_MS = 5000;
10553
11848
  var init_session_manager = __esm(() => {
10554
11849
  init_src();
10555
11850
  init_codex_home();
@@ -10557,11 +11852,14 @@ var init_session_manager = __esm(() => {
10557
11852
  init_git();
10558
11853
  init_injector();
10559
11854
  init_occupancy();
11855
+ init_agent_store();
11856
+ init_codex_app_server();
11857
+ init_pty();
10560
11858
  init_paths();
10561
11859
  init_pty_session();
10562
11860
  init_shell();
10563
11861
  init_workspace_local();
10564
- MATERIALISED_DIR = path17.join(RUNTIME_DIR, "materialised");
11862
+ MATERIALISED_DIR = path19.join(RUNTIME_DIR, "materialised");
10565
11863
  PROMPT_OPTIONS = {
10566
11864
  hostKey: [{ label: "yes" }, { label: "no" }],
10567
11865
  password: [],
@@ -10710,11 +12008,11 @@ var init_acknowledge = __esm(() => {
10710
12008
  });
10711
12009
 
10712
12010
  // packages/channel-telegram/src/attach.ts
10713
- import fs18 from "fs";
10714
- import path18 from "path";
12011
+ import fs20 from "fs";
12012
+ import path20 from "path";
10715
12013
  function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
10716
- const name = path18.basename(filePath);
10717
- const bytes = fs18.statSync(filePath).size;
12014
+ const name = path20.basename(filePath);
12015
+ const bytes = fs20.statSync(filePath).size;
10718
12016
  const plan = planAttachment({ name, bytes }, limits);
10719
12017
  if (plan.kind === "rejected")
10720
12018
  throw new Error(plan.reason);
@@ -10724,7 +12022,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
10724
12022
  form.append("message_thread_id", String(topicId));
10725
12023
  if (caption !== undefined && caption.length > 0)
10726
12024
  form.append("caption", caption);
10727
- form.append(field, new Blob([fs18.readFileSync(filePath)]), name);
12025
+ form.append(field, new Blob([fs20.readFileSync(filePath)]), name);
10728
12026
  return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
10729
12027
  }
10730
12028
  function isPhotoRejection(error) {
@@ -10732,13 +12030,13 @@ function isPhotoRejection(error) {
10732
12030
  return /IMAGE_PROCESS_FAILED|PHOTO_INVALID_DIMENSIONS|PHOTO_EXT_INVALID|PHOTO_SAVE_FILE_INVALID/i.test(message);
10733
12031
  }
10734
12032
  function asDocumentUpload(filePath, chatId, topicId, caption) {
10735
- const name = path18.basename(filePath);
12033
+ const name = path20.basename(filePath);
10736
12034
  const form = new FormData;
10737
12035
  form.append("chat_id", String(chatId));
10738
12036
  form.append("message_thread_id", String(topicId));
10739
12037
  if (caption !== undefined && caption.length > 0)
10740
12038
  form.append("caption", caption);
10741
- form.append("document", new Blob([fs18.readFileSync(filePath)]), name);
12039
+ form.append("document", new Blob([fs20.readFileSync(filePath)]), name);
10742
12040
  return { method: "sendDocument", field: "document", form };
10743
12041
  }
10744
12042
  var TELEGRAM_LIMITS;
@@ -11505,6 +12803,14 @@ class TelegramChannel {
11505
12803
  await this.sendToSession(sessionId, message);
11506
12804
  }
11507
12805
  }
12806
+ quotaText() {
12807
+ const checks = this.control.quota();
12808
+ if (checks.length === 0)
12809
+ return escapeHtml(t("cli.quota.none"));
12810
+ const GLYPHS = { ok: "\uD83D\uDFE2", info: "\u26AA", warn: "\uD83D\uDD34", fail: "\uD83D\uDD34" };
12811
+ return checks.map((check) => `${GLYPHS[check.status]} ${escapeHtml(t(check.key, check.params ?? {}))}`).join(`
12812
+ `);
12813
+ }
11508
12814
  async onAgentStatus(sessionId, state, toolName) {
11509
12815
  const binding = this.control.findBindingBySession(sessionId);
11510
12816
  if (!binding)
@@ -11625,7 +12931,7 @@ ${code(prompt.line)}`,
11625
12931
  });
11626
12932
  }
11627
12933
  awaitingSecret = new Map;
11628
- async onChoiceRequest(sessionId, seq, questions, askId, onboarding = false) {
12934
+ async onChoiceRequest(sessionId, seq, questions, askId, onboarding2 = false) {
11629
12935
  const binding = this.control.findBindingBySession(sessionId);
11630
12936
  if (!binding || questions.length === 0)
11631
12937
  return;
@@ -11642,7 +12948,7 @@ ${code(prompt.line)}`,
11642
12948
  answers: [],
11643
12949
  picked: [],
11644
12950
  ...askId !== undefined ? { askId } : {},
11645
- ...onboarding ? { onboarding: true } : {}
12951
+ ...onboarding2 ? { onboarding: true } : {}
11646
12952
  });
11647
12953
  await this.renderChoiceStep(sessionId);
11648
12954
  }
@@ -11707,7 +13013,7 @@ ${code(prompt.line)}`,
11707
13013
  }
11708
13014
  async onAgentError(sessionId, kind, text, retryMs) {
11709
13015
  const reason = clipText(text, AGENT_ERROR_CHARS);
11710
- const message = kind === "retry" && retryMs !== undefined && retryMs > 0 ? tHtml("tg.agent.retry", { wait: humanDuration(retryMs), error: reason }) : tHtml("tg.agent.failed", { error: reason });
13016
+ const message = kind === "stalled" ? tHtml("tg.agent.stalled", { wait: humanDuration(retryMs ?? 0), screen: reason }) : kind === "attempt" ? tHtml("tg.agent.attempt", { error: reason }) : kind === "retry" && retryMs !== undefined && retryMs > 0 ? tHtml("tg.agent.retry", { wait: humanDuration(retryMs), error: reason }) : tHtml("tg.agent.failed", { error: reason });
11711
13017
  await this.sendToSession(sessionId, message);
11712
13018
  }
11713
13019
  async onChatMessage(sessionId, role, text) {
@@ -11871,8 +13177,8 @@ ${code(prompt.line)}`,
11871
13177
  throw new Error("this Telegram client cannot download files");
11872
13178
  const files = [];
11873
13179
  for (const item of batch.items) {
11874
- const path19 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
11875
- files.push({ path: path19, kind: item.kind });
13180
+ const path21 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
13181
+ files.push({ path: path21, kind: item.kind });
11876
13182
  }
11877
13183
  await this.control.prompt(batch.sessionId, buildAttachmentPrompt(files, batch.caption));
11878
13184
  this.log(`staged ${files.length} inbound file(s) for ${batch.sessionId}`);
@@ -11898,6 +13204,7 @@ ${code(prompt.line)}`,
11898
13204
  }
11899
13205
  async handleCommand(text, binding, msg) {
11900
13206
  const [rawCmd = "", ...args] = text.split(/\s+/);
13207
+ const rawTail = text.slice(rawCmd.length);
11901
13208
  const cmd = rawCmd.replace(/@\w+$/, "");
11902
13209
  if (binding) {
11903
13210
  switch (cmd) {
@@ -11907,9 +13214,34 @@ ${code(prompt.line)}`,
11907
13214
  case "/kill":
11908
13215
  this.control.kill(binding.sessionId);
11909
13216
  return;
13217
+ case "/key": {
13218
+ const parsed = parseKeyNames(args.join(" "));
13219
+ if ("unknown" in parsed) {
13220
+ await this.sendToSession(binding.sessionId, tHtml("tg.raw.unknownKey", { name: parsed.unknown, keys: KEY_NAMES.join(" ") }));
13221
+ return;
13222
+ }
13223
+ for (const sequence of parsed.keys)
13224
+ this.control.write(binding.sessionId, sequence);
13225
+ await this.sendToSession(binding.sessionId, tHtml("tg.raw.keysSent", { keys: args.join(" ") }));
13226
+ return;
13227
+ }
13228
+ case "/type": {
13229
+ const typed = rawTail.replace(/^ /, "");
13230
+ if (typed === "") {
13231
+ await this.sendToSession(binding.sessionId, tHtml("tg.raw.needText"));
13232
+ return;
13233
+ }
13234
+ if (typed.length > RAW_TYPE_LIMIT) {
13235
+ await this.sendToSession(binding.sessionId, tHtml("tg.raw.tooLong", { limit: RAW_TYPE_LIMIT }));
13236
+ return;
13237
+ }
13238
+ this.control.write(binding.sessionId, typed);
13239
+ await this.sendToSession(binding.sessionId, tHtml("tg.raw.typed", { screen: this.control.screen(binding.sessionId, RAW_SCREEN_LINES) }));
13240
+ return;
13241
+ }
11910
13242
  case "/resume": {
11911
13243
  try {
11912
- const info = this.control.resume(binding.sessionId);
13244
+ const info = await this.control.resume(binding.sessionId);
11913
13245
  await this.sendToSession(binding.sessionId, tHtml("tg.resume.done", { command: info.agentCommand.join(" ") }));
11914
13246
  } catch (err) {
11915
13247
  await this.sendToSession(binding.sessionId, tHtml("tg.resume.failed", { error: localizeError(err) }));
@@ -11924,6 +13256,10 @@ ${code(prompt.line)}`,
11924
13256
  case "/diff":
11925
13257
  await this.postDiffSummary(binding.sessionId);
11926
13258
  return;
13259
+ case "/quota": {
13260
+ await this.sendToSession(binding.sessionId, this.quotaText());
13261
+ return;
13262
+ }
11927
13263
  default:
11928
13264
  await this.sendToSession(binding.sessionId, helpText());
11929
13265
  return;
@@ -12122,8 +13458,8 @@ ${code(prompt.line)}`,
12122
13458
  return;
12123
13459
  }
12124
13460
  if (flow.askId === undefined) {
12125
- const keys = agentProfile(agentIdFromCommand(session.agentCommand)).approvalKeys;
12126
- this.control.write(session.id, keys !== undefined && decoded.optionIndex <= 1 ? decoded.optionIndex === 0 ? keys.allow : keys.deny : buildMenuSelectKeys(decoded.optionIndex));
13461
+ const keys2 = agentProfile(agentIdFromCommand(session.agentCommand)).approvalKeys;
13462
+ this.control.write(session.id, keys2 !== undefined && decoded.optionIndex <= 1 ? decoded.optionIndex === 0 ? keys2.allow : keys2.deny : buildMenuSelectKeys(decoded.optionIndex));
12127
13463
  }
12128
13464
  flow.answers.push({ questionIndex: flow.cursor, selected: [label] });
12129
13465
  flow.cursor += 1;
@@ -12201,7 +13537,7 @@ ${code(prompt.line)}`,
12201
13537
  }
12202
13538
  }
12203
13539
  }
12204
- var SCREEN_MESSAGE_BUDGET = 2, AGENT_ERROR_CHARS = 1200, HELP_KEYS, helpText = () => HELP_KEYS.map((key) => escapeHtml(t(key))).join(`
13540
+ 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(`
12205
13541
  `), ALBUM_DEBOUNCE_MS = 2000, UNKNOWN_SIZE = 1, REFUSAL_KEYS, megabytes2 = (bytes) => `${Math.round(bytes / (1024 * 1024))} MB`;
12206
13542
  var init_adapter = __esm(() => {
12207
13543
  init_src();
@@ -12268,6 +13604,7 @@ class TelegramService {
12268
13604
  return {
12269
13605
  ...telegram?.botToken !== undefined ? { botToken: telegram.botToken } : {},
12270
13606
  ...telegram?.groupId !== undefined ? { groupId: telegram.groupId } : {},
13607
+ ...telegram?.enabled !== undefined ? { enabled: telegram.enabled } : {},
12271
13608
  ...telegram?.allowUserIds?.[0] !== undefined ? { allowUserId: telegram.allowUserIds[0] } : {},
12272
13609
  ...machine2 !== undefined ? { machine: machine2 } : {}
12273
13610
  };
@@ -12314,11 +13651,15 @@ class TelegramService {
12314
13651
  }
12315
13652
  start() {
12316
13653
  const s = this.readSettings();
13654
+ this.stop();
13655
+ if (s.enabled === false) {
13656
+ log("telegram: disabled by configuration, channel not started");
13657
+ return false;
13658
+ }
12317
13659
  if (!s.botToken || s.groupId === undefined || s.allowUserId === undefined) {
12318
13660
  log("telegram: not configured, channel not started");
12319
13661
  return false;
12320
13662
  }
12321
- this.stop();
12322
13663
  const api = new FetchTelegramApi(s.botToken);
12323
13664
  const config = {
12324
13665
  groupId: s.groupId,
@@ -12360,6 +13701,7 @@ class TelegramService {
12360
13701
  answerShellPrompt: (id, seq, text) => this.manager.answerShellPrompt(id, seq, text),
12361
13702
  answerOnboarding: (id, seq, optionIndex) => this.manager.answerOnboardingChoice(id, seq, optionIndex),
12362
13703
  diff: (id) => this.manager.sessionDiff(id),
13704
+ quota: () => this.manager.quotaReport(),
12363
13705
  saveBinding: (b) => this.store.saveBinding(b),
12364
13706
  findBindingBySession: (id) => this.store.findBindingBySession(id),
12365
13707
  findBindingByTopic: (chatId, topicId) => this.store.findBindingByTopic(chatId, topicId),
@@ -12466,8 +13808,8 @@ var init_telegram_service = __esm(() => {
12466
13808
  // packages/daemon/src/index.ts
12467
13809
  var exports_src = {};
12468
13810
  import { randomBytes as randomBytes3 } from "crypto";
12469
- import fs19 from "fs";
12470
- import net3 from "net";
13811
+ import fs21 from "fs";
13812
+ import net4 from "net";
12471
13813
  function isPidAlive(pid) {
12472
13814
  try {
12473
13815
  process.kill(pid, 0);
@@ -12478,9 +13820,9 @@ function isPidAlive(pid) {
12478
13820
  }
12479
13821
  async function main() {
12480
13822
  ensureDataDirs();
12481
- if (fs19.existsSync(RUNTIME_FILE2)) {
13823
+ if (fs21.existsSync(RUNTIME_FILE2)) {
12482
13824
  try {
12483
- const existing = JSON.parse(fs19.readFileSync(RUNTIME_FILE2, "utf8"));
13825
+ const existing = JSON.parse(fs21.readFileSync(RUNTIME_FILE2, "utf8"));
12484
13826
  if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
12485
13827
  log(`daemon already running (pid ${existing.pid}); exiting`);
12486
13828
  process.exit(0);
@@ -12503,7 +13845,7 @@ async function main() {
12503
13845
  version: productVersion(),
12504
13846
  startedAt: Date.now()
12505
13847
  };
12506
- fs19.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
13848
+ fs21.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
12507
13849
  restrictFile(RUNTIME_FILE2);
12508
13850
  const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
12509
13851
  if (config.loadError !== undefined)
@@ -12521,7 +13863,7 @@ async function main() {
12521
13863
  port: hookPort,
12522
13864
  endpointFile: HOOK_ENDPOINT_FILE,
12523
13865
  extensionPath
12524
- });
13866
+ }, () => config.resolved().defaults ?? {});
12525
13867
  const telegram = new TelegramService(config, store, manager);
12526
13868
  hookHost.onAttachment(async (sessionId, filePath, caption) => {
12527
13869
  await telegram.sendAttachment(sessionId, filePath, caption);
@@ -12555,9 +13897,9 @@ async function main() {
12555
13897
  hookHost.stop();
12556
13898
  store.close();
12557
13899
  try {
12558
- const current = JSON.parse(fs19.readFileSync(RUNTIME_FILE2, "utf8"));
13900
+ const current = JSON.parse(fs21.readFileSync(RUNTIME_FILE2, "utf8"));
12559
13901
  if (current.pid === process.pid)
12560
- fs19.rmSync(RUNTIME_FILE2);
13902
+ fs21.rmSync(RUNTIME_FILE2);
12561
13903
  } catch {}
12562
13904
  process.exit(0);
12563
13905
  };
@@ -12590,14 +13932,14 @@ var init_src3 = __esm(() => {
12590
13932
  init_session_manager();
12591
13933
  init_telegram_service();
12592
13934
  init_store();
12593
- net3.setDefaultAutoSelectFamilyAttemptTimeout(3000);
13935
+ net4.setDefaultAutoSelectFamilyAttemptTimeout(3000);
12594
13936
  main();
12595
13937
  });
12596
13938
 
12597
13939
  // packages/cli/src/index.ts
12598
13940
  init_src();
12599
- import fs20 from "fs";
12600
- import path19 from "path";
13941
+ import fs22 from "fs";
13942
+ import path21 from "path";
12601
13943
 
12602
13944
  // packages/cli/src/attach.ts
12603
13945
  import fs from "fs";
@@ -13237,18 +14579,289 @@ async function relayHookEvent(agent) {
13237
14579
  } catch {}
13238
14580
  }
13239
14581
 
14582
+ // packages/cli/src/mcp-server.ts
14583
+ init_src();
14584
+ import fs3 from "fs";
14585
+ import path4 from "path";
14586
+ var ASK_TIMEOUT_MS = 10 * 60000;
14587
+ var POST_TIMEOUT_MS2 = 5000;
14588
+ var TOOLS = [
14589
+ {
14590
+ name: "axterm_send_file",
14591
+ description: "Send a file from this machine to the operator's messaging channel (the chat they are watching " + "this session from). Use when asked to send, share, deliver, or show a file, image, screenshot, " + "log, or document. Images arrive inline; anything else as a download.",
14592
+ inputSchema: {
14593
+ type: "object",
14594
+ properties: {
14595
+ path: { type: "string", description: "Path to the file; relative paths resolve against the session's folder" },
14596
+ caption: { type: "string", description: "Optional one-line caption" }
14597
+ },
14598
+ required: ["path"]
14599
+ }
14600
+ },
14601
+ {
14602
+ name: "axterm_ask",
14603
+ description: "Ask the operator a question with a fixed set of choices. The question appears both in this " + "terminal and in the operator's messaging channel, and whichever they answer first is used. " + "Prefer this over guessing when a decision is genuinely the operator's to make.",
14604
+ inputSchema: {
14605
+ type: "object",
14606
+ properties: {
14607
+ questions: {
14608
+ type: "array",
14609
+ description: "One or more questions to put to the operator",
14610
+ items: {
14611
+ type: "object",
14612
+ properties: {
14613
+ question: { type: "string" },
14614
+ options: { type: "array", items: { type: "string" }, description: "Omit for a free-text answer" },
14615
+ multi: { type: "boolean", description: "Allow more than one choice" }
14616
+ },
14617
+ required: ["question"]
14618
+ }
14619
+ }
14620
+ },
14621
+ required: ["questions"]
14622
+ }
14623
+ }
14624
+ ];
14625
+
14626
+ class McpServer {
14627
+ io;
14628
+ clientElicits = false;
14629
+ nextRequestId = 1;
14630
+ pending = new Map;
14631
+ constructor(io) {
14632
+ this.io = io;
14633
+ }
14634
+ handle(raw) {
14635
+ let message;
14636
+ try {
14637
+ message = JSON.parse(raw);
14638
+ } catch {
14639
+ return;
14640
+ }
14641
+ if (message.method === undefined && message.id !== undefined) {
14642
+ const resolve = this.pending.get(Number(message.id));
14643
+ this.pending.delete(Number(message.id));
14644
+ resolve?.(message.result ?? message.error);
14645
+ return;
14646
+ }
14647
+ this.dispatch(message);
14648
+ }
14649
+ send(message) {
14650
+ this.io.write(`${JSON.stringify(message)}
14651
+ `);
14652
+ }
14653
+ async dispatch(message) {
14654
+ const { id, method } = message;
14655
+ switch (method) {
14656
+ case "initialize": {
14657
+ const params = message.params ?? {};
14658
+ const capabilities = params["capabilities"];
14659
+ this.clientElicits = typeof capabilities === "object" && capabilities !== null && "elicitation" in capabilities;
14660
+ this.send({
14661
+ jsonrpc: "2.0",
14662
+ id,
14663
+ result: {
14664
+ protocolVersion: typeof params["protocolVersion"] === "string" ? params["protocolVersion"] : "2025-06-18",
14665
+ capabilities: { tools: {} },
14666
+ serverInfo: { name: "axterm", version: "1" }
14667
+ }
14668
+ });
14669
+ return;
14670
+ }
14671
+ case "tools/list":
14672
+ this.send({ jsonrpc: "2.0", id, result: { tools: TOOLS } });
14673
+ return;
14674
+ case "resources/list":
14675
+ this.send({ jsonrpc: "2.0", id, result: { resources: [] } });
14676
+ return;
14677
+ case "prompts/list":
14678
+ this.send({ jsonrpc: "2.0", id, result: { prompts: [] } });
14679
+ return;
14680
+ case "tools/call": {
14681
+ const params = message.params ?? {};
14682
+ const name = params["name"];
14683
+ const args = params["arguments"] ?? {};
14684
+ const outcome = name === "axterm_send_file" ? await this.sendFile(args) : name === "axterm_ask" ? await this.ask(args) : { text: `unknown tool: ${String(name)}`, isError: true };
14685
+ this.send({
14686
+ jsonrpc: "2.0",
14687
+ id,
14688
+ result: { content: [{ type: "text", text: outcome.text }], isError: outcome.isError }
14689
+ });
14690
+ return;
14691
+ }
14692
+ default:
14693
+ if (id !== undefined)
14694
+ this.send({ jsonrpc: "2.0", id, error: { code: -32601, message: `unknown: ${String(method)}` } });
14695
+ }
14696
+ }
14697
+ target() {
14698
+ const contextFile = this.io.env[MCP_CONTEXT_ENV];
14699
+ if (contextFile !== undefined) {
14700
+ try {
14701
+ const context = JSON.parse(fs3.readFileSync(contextFile, "utf8"));
14702
+ if (context.port && context.token && context.sessionId) {
14703
+ return {
14704
+ url: `http://127.0.0.1:${context.port}`,
14705
+ headers: { "x-axterm-hook-token": context.token },
14706
+ sessionId: context.sessionId,
14707
+ launchToken: context.launchToken ?? ""
14708
+ };
14709
+ }
14710
+ } catch {}
14711
+ return;
14712
+ }
14713
+ const base = hookPostTarget(this.io.env, "omp");
14714
+ const sessionId = this.io.env["AXTERM_SESSION_ID"];
14715
+ if (!base || !sessionId)
14716
+ return;
14717
+ return {
14718
+ url: base.url.replace(/\/hook\/[^/]+$/, ""),
14719
+ headers: base.headers,
14720
+ sessionId,
14721
+ launchToken: this.io.env["AXTERM_LAUNCH_TOKEN"] ?? ""
14722
+ };
14723
+ }
14724
+ async post(route, body, timeoutMs) {
14725
+ const target = this.target();
14726
+ if (!target)
14727
+ return;
14728
+ try {
14729
+ return await this.io.fetch(`${target.url}${route}`, {
14730
+ method: "POST",
14731
+ headers: { "content-type": "application/json", ...target.headers },
14732
+ body: JSON.stringify(body),
14733
+ signal: AbortSignal.timeout(timeoutMs)
14734
+ });
14735
+ } catch {
14736
+ return;
14737
+ }
14738
+ }
14739
+ async sendFile(args) {
14740
+ const file = typeof args["path"] === "string" ? args["path"] : "";
14741
+ if (file === "")
14742
+ return { text: "no path given", isError: true };
14743
+ const target = this.target();
14744
+ if (!target)
14745
+ return { text: "axterm is not reachable from this session", isError: true };
14746
+ const resolved = path4.isAbsolute(file) ? file : path4.resolve(this.io.cwd, file);
14747
+ const caption = typeof args["caption"] === "string" ? args["caption"] : undefined;
14748
+ const res = await this.post("/hook/attach", { sessionId: target.sessionId, launchToken: target.launchToken, path: resolved, caption }, POST_TIMEOUT_MS2);
14749
+ if (!res)
14750
+ return { text: "axterm did not answer", isError: true };
14751
+ if (!res.ok)
14752
+ return { text: `axterm could not deliver the file (${res.status})`, isError: true };
14753
+ return { text: `sent ${resolved} to the operator's channel`, isError: false };
14754
+ }
14755
+ async ask(args) {
14756
+ const questions = parseQuestions(args["questions"]);
14757
+ if (questions.length === 0)
14758
+ return { text: "no questions given", isError: true };
14759
+ const target = this.target();
14760
+ if (!target)
14761
+ return { text: "axterm is not reachable from this session", isError: true };
14762
+ const askId = `mcp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
14763
+ const channel = this.post("/hook/ask", { sessionId: target.sessionId, launchToken: target.launchToken, askId, questions }, ASK_TIMEOUT_MS).then(async (res) => {
14764
+ if (!res?.ok)
14765
+ return;
14766
+ const body = await res.json().catch(() => {
14767
+ return;
14768
+ });
14769
+ return body?.status === "answered" ? body.answers : undefined;
14770
+ });
14771
+ const terminal = this.clientElicits ? this.elicit(questions) : undefined;
14772
+ const winner = await Promise.race([
14773
+ channel.then((answers) => ({ from: "channel", answers })),
14774
+ ...terminal ? [terminal.promise.then((answers) => ({ from: "terminal", answers }))] : []
14775
+ ]);
14776
+ if (winner.from === "channel") {
14777
+ if (winner.answers) {
14778
+ terminal?.cancel();
14779
+ return { text: formatAskResult(questions, winner.answers).text, isError: false };
14780
+ }
14781
+ const settled = terminal ? await terminal.promise : undefined;
14782
+ if (settled)
14783
+ return { text: formatAskResult(questions, settled).text, isError: false };
14784
+ return { text: "nobody answered the question", isError: true };
14785
+ }
14786
+ this.post("/hook/ask-cancel", { sessionId: target.sessionId, launchToken: target.launchToken, askId }, POST_TIMEOUT_MS2);
14787
+ if (winner.answers)
14788
+ return { text: formatAskResult(questions, winner.answers).text, isError: false };
14789
+ return { text: "the operator dismissed the question in the terminal", isError: true };
14790
+ }
14791
+ elicit(questions) {
14792
+ const id = this.nextRequestId++;
14793
+ const { promise, resolve } = Promise.withResolvers();
14794
+ this.pending.set(id, resolve);
14795
+ this.send({
14796
+ jsonrpc: "2.0",
14797
+ id,
14798
+ method: "elicitation/create",
14799
+ params: {
14800
+ message: questions.length === 1 ? questions[0].question : "axterm needs your input",
14801
+ requestedSchema: elicitationSchema(questions)
14802
+ }
14803
+ });
14804
+ return {
14805
+ promise: promise.then((result) => answersFromElicitation(questions, result)),
14806
+ cancel: () => {
14807
+ if (!this.pending.has(id))
14808
+ return;
14809
+ this.pending.delete(id);
14810
+ this.send({ jsonrpc: "2.0", method: "notifications/cancelled", params: { requestId: id, reason: "answered on the channel" } });
14811
+ }
14812
+ };
14813
+ }
14814
+ }
14815
+ function parseQuestions(value) {
14816
+ if (!Array.isArray(value))
14817
+ return [];
14818
+ const questions = [];
14819
+ for (const raw of value) {
14820
+ if (typeof raw !== "object" || raw === null)
14821
+ continue;
14822
+ const entry = raw;
14823
+ if (typeof entry.question !== "string" || entry.question === "")
14824
+ continue;
14825
+ const options = Array.isArray(entry.options) ? entry.options.filter((option) => typeof option === "string").map((label) => ({ label })) : [];
14826
+ questions.push({ question: entry.question, options, multi: entry.multi === true });
14827
+ }
14828
+ return questions;
14829
+ }
14830
+ function runMcpServer() {
14831
+ const server = new McpServer({
14832
+ write: (line) => process.stdout.write(line),
14833
+ fetch,
14834
+ env: process.env,
14835
+ cwd: process.cwd()
14836
+ });
14837
+ let buffer = "";
14838
+ process.stdin.on("data", (chunk) => {
14839
+ buffer += chunk.toString("utf8");
14840
+ for (;; ) {
14841
+ const newline = buffer.indexOf(`
14842
+ `);
14843
+ if (newline < 0)
14844
+ break;
14845
+ const line = buffer.slice(0, newline).trim();
14846
+ buffer = buffer.slice(newline + 1);
14847
+ if (line !== "")
14848
+ server.handle(line);
14849
+ }
14850
+ });
14851
+ process.stdin.resume();
14852
+ }
14853
+
13240
14854
  // packages/cli/src/update.ts
13241
14855
  init_src();
13242
14856
  import { spawnSync } from "child_process";
13243
14857
  import { createHash as createHash2 } from "crypto";
13244
- import fs3 from "fs";
13245
- import os3 from "os";
13246
- import path4 from "path";
14858
+ import fs4 from "fs";
14859
+ import path5 from "path";
13247
14860
  import { stdin as stdin2, stdout as stdout2 } from "process";
13248
14861
  // packages/cli/package.json
13249
14862
  var package_default = {
13250
14863
  name: "@layorixinc/axterm",
13251
- version: "0.3.0",
14864
+ version: "0.4.0",
13252
14865
  type: "module",
13253
14866
  bin: {
13254
14867
  axterm: "dist/cli.js"
@@ -13322,7 +14935,7 @@ function assetName(platform, arch, isMusl) {
13322
14935
  function usesMusl() {
13323
14936
  if (process.platform !== "linux")
13324
14937
  return false;
13325
- if (fs3.existsSync("/etc/alpine-release"))
14938
+ if (fs4.existsSync("/etc/alpine-release"))
13326
14939
  return true;
13327
14940
  const ldd = spawnSync("ldd", ["--version"], { encoding: "utf8" });
13328
14941
  return `${ldd.stdout ?? ""}${ldd.stderr ?? ""}`.toLowerCase().includes("musl");
@@ -13336,7 +14949,7 @@ function firstLine(command, args) {
13336
14949
  function detectOrigin(execPath) {
13337
14950
  const resolved = (() => {
13338
14951
  try {
13339
- return fs3.realpathSync(execPath);
14952
+ return fs4.realpathSync(execPath);
13340
14953
  } catch {
13341
14954
  return execPath;
13342
14955
  }
@@ -13348,7 +14961,7 @@ function detectOrigin(execPath) {
13348
14961
  brewPrefix: firstLine("brew", ["--prefix"]),
13349
14962
  misePaths: misePaths === undefined ? [] : [misePaths],
13350
14963
  bunBin: firstLine("bun", ["pm", "bin", "-g"]),
13351
- npmBin: firstLine("npm", ["bin", "-g"])
14964
+ npmBin: npmGlobalBin()
13352
14965
  });
13353
14966
  }
13354
14967
  var MANAGER_COMMAND = {
@@ -13357,8 +14970,11 @@ var MANAGER_COMMAND = {
13357
14970
  bun: (v) => `bun install -g ${PACKAGE}@${v}`,
13358
14971
  npm: (v) => `npm install -g ${PACKAGE}@${v}`
13359
14972
  };
13360
- function runningFromSource() {
13361
- return Bun.main.endsWith(".ts") || Bun.main.endsWith(".js");
14973
+ function npmGlobalBin() {
14974
+ const prefix = firstLine("npm", ["prefix", "-g"]);
14975
+ if (prefix === undefined)
14976
+ return;
14977
+ return process.platform === "win32" ? prefix : path5.join(prefix, "bin");
13362
14978
  }
13363
14979
  async function download(url, into) {
13364
14980
  const response = await fetch(url, { signal: AbortSignal.timeout(600000) });
@@ -13375,7 +14991,7 @@ function checksumFor(sums, asset) {
13375
14991
  return;
13376
14992
  }
13377
14993
  function sha256(file) {
13378
- return createHash2("sha256").update(fs3.readFileSync(file)).digest("hex");
14994
+ return createHash2("sha256").update(fs4.readFileSync(file)).digest("hex");
13379
14995
  }
13380
14996
  function ask2(question) {
13381
14997
  if (stdin2.isTTY !== true)
@@ -13404,21 +15020,26 @@ async function runUpdate(client, current, options) {
13404
15020
  console.log(t("cli.update.available", { latest: latest.version, current }));
13405
15021
  if (options.checkOnly)
13406
15022
  return;
13407
- if (runningFromSource()) {
15023
+ const lane = classifyRuntimeLane(Bun.main);
15024
+ if (lane === "checkout") {
13408
15025
  console.log(t("cli.update.notBinary"));
13409
15026
  return;
13410
15027
  }
15028
+ if (lane === "bun" || lane === "npm") {
15029
+ console.log(t("cli.update.viaManager", { manager: lane }));
15030
+ console.log(t("cli.update.managerHint", { command: MANAGER_COMMAND[lane](latest.version) }));
15031
+ return;
15032
+ }
13411
15033
  const origin = detectOrigin(process.execPath);
13412
15034
  if (origin !== "binary") {
13413
- const command = MANAGER_COMMAND[origin](latest.version);
13414
15035
  console.log(t("cli.update.viaManager", { manager: origin }));
13415
- console.log(t("cli.update.managerHint", { command }));
15036
+ console.log(t("cli.update.managerHint", { command: MANAGER_COMMAND[origin](latest.version) }));
13416
15037
  return;
13417
15038
  }
13418
15039
  const asset = assetName(process.platform, process.arch, usesMusl());
13419
15040
  const base = `${RELEASES}/v${latest.version}`;
13420
- const staging = fs3.mkdtempSync(path4.join(os3.tmpdir(), "axterm-update-"));
13421
- const downloaded = path4.join(staging, asset);
15041
+ const staging = fs4.mkdtempSync(`${process.execPath}.update-`);
15042
+ const downloaded = path5.join(staging, asset);
13422
15043
  try {
13423
15044
  console.log(t("cli.update.downloading", { asset }));
13424
15045
  await download(`${base}/${asset}`, downloaded);
@@ -13431,7 +15052,7 @@ async function runUpdate(client, current, options) {
13431
15052
  return;
13432
15053
  }
13433
15054
  if (process.platform !== "win32")
13434
- fs3.chmodSync(downloaded, 493);
15055
+ fs4.chmodSync(downloaded, 493);
13435
15056
  const reported = spawnSync(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
13436
15057
  if (reported !== latest.version) {
13437
15058
  console.error(t("cli.update.wrongVersion", { got: reported ?? "nothing", want: latest.version }));
@@ -13450,6 +15071,9 @@ async function runUpdate(client, current, options) {
13450
15071
  return;
13451
15072
  }
13452
15073
  }
15074
+ console.log(t("cli.update.swapping"));
15075
+ applySwap(process.execPath, downloaded);
15076
+ console.log(t("cli.update.done", { version: latest.version }));
13453
15077
  if (plan.stopDaemon) {
13454
15078
  console.log(t("cli.update.stopping"));
13455
15079
  await client.call("daemon.stop").catch(() => {
@@ -13458,9 +15082,6 @@ async function runUpdate(client, current, options) {
13458
15082
  client.close();
13459
15083
  await Bun.sleep(1500);
13460
15084
  }
13461
- console.log(t("cli.update.swapping"));
13462
- applySwap(process.execPath, downloaded);
13463
- console.log(t("cli.update.done", { version: latest.version }));
13464
15085
  if (plan.stale > 0)
13465
15086
  console.log(t("cli.update.staleDaemon", { count: plan.stale }));
13466
15087
  for (const id of plan.resume) {
@@ -13471,7 +15092,7 @@ async function runUpdate(client, current, options) {
13471
15092
  console.log(t("cli.update.restoreFailed", { id, reason: (result.stderr ?? "").trim() || "unknown" }));
13472
15093
  }
13473
15094
  } finally {
13474
- fs3.rmSync(staging, { recursive: true, force: true });
15095
+ fs4.rmSync(staging, { recursive: true, force: true });
13475
15096
  }
13476
15097
  }
13477
15098
  function applySwap(target, downloaded) {
@@ -13480,16 +15101,16 @@ function applySwap(target, downloaded) {
13480
15101
  try {
13481
15102
  for (const step of steps) {
13482
15103
  if (step.op === "chmod")
13483
- fs3.chmodSync(step.from, 493);
15104
+ fs4.chmodSync(step.from, 493);
13484
15105
  else if (step.op === "backup") {
13485
- fs3.renameSync(step.from, step.to);
15106
+ fs4.renameSync(step.from, step.to);
13486
15107
  backup = step.to;
13487
15108
  } else
13488
- fs3.renameSync(step.from, step.to);
15109
+ fs4.renameSync(step.from, step.to);
13489
15110
  }
13490
15111
  } catch (err) {
13491
- if (backup !== undefined && !fs3.existsSync(target))
13492
- fs3.renameSync(backup, target);
15112
+ if (backup !== undefined && !fs4.existsSync(target))
15113
+ fs4.renameSync(backup, target);
13493
15114
  throw err;
13494
15115
  }
13495
15116
  }
@@ -13562,7 +15183,7 @@ function printSessionTable(sessions) {
13562
15183
  console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
13563
15184
  }
13564
15185
  }
13565
- var realPath = (target) => fs20.realpathSync.native(target);
15186
+ var realPath = (target) => fs22.realpathSync.native(target);
13566
15187
  function scopedHere(sessions) {
13567
15188
  return sessionsInScope(sessions, process.cwd(), realPath);
13568
15189
  }
@@ -13572,22 +15193,10 @@ async function exitedIds(client, all) {
13572
15193
  return scoped.filter((s) => s.state === "exited").map((s) => s.id);
13573
15194
  }
13574
15195
  var help = () => t("cli.help");
13575
- var KEY_SEQUENCES = {
13576
- approve: "\r",
13577
- deny: "\x1B[B\r",
13578
- enter: "\r",
13579
- esc: "\x1B",
13580
- up: "\x1B[A",
13581
- down: "\x1B[B",
13582
- left: "\x1B[D",
13583
- right: "\x1B[C",
13584
- tab: "\t",
13585
- space: " "
13586
- };
13587
15196
  function readDotEnv(file = ".env") {
13588
15197
  const out = {};
13589
15198
  try {
13590
- for (const line of fs20.readFileSync(file, "utf8").split(/\r?\n/)) {
15199
+ for (const line of fs22.readFileSync(file, "utf8").split(/\r?\n/)) {
13591
15200
  const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
13592
15201
  if (m)
13593
15202
  out[m[1]] = m[2].trim();
@@ -13598,7 +15207,7 @@ function readDotEnv(file = ".env") {
13598
15207
  function configuredLocale() {
13599
15208
  try {
13600
15209
  const file = resolvePaths(currentPathEnv()).configFile;
13601
- const raw = JSON.parse(fs20.readFileSync(file, "utf8"));
15210
+ const raw = JSON.parse(fs22.readFileSync(file, "utf8"));
13602
15211
  return typeof raw.locale === "string" ? raw.locale : undefined;
13603
15212
  } catch {
13604
15213
  return;
@@ -13615,6 +15224,10 @@ async function main2() {
13615
15224
  await relayHookEvent(argv[1]);
13616
15225
  return;
13617
15226
  }
15227
+ if (isMcpInvocation(argv)) {
15228
+ runMcpServer();
15229
+ return;
15230
+ }
13618
15231
  const [command, ...rest] = argv;
13619
15232
  setLocale(resolveLocale({ configured: configuredLocale(), env: process.env }));
13620
15233
  const { flags, positional, passthrough } = parseArgs(rest);
@@ -13648,6 +15261,7 @@ async function main2() {
13648
15261
  name: flags.get("name"),
13649
15262
  shell: flags.get("shell"),
13650
15263
  agent: flags.get("agent"),
15264
+ autoApprove: typeof flags.get("auto-approve") === "boolean" ? flags.get("auto-approve") : undefined,
13651
15265
  agentArgs: passthrough,
13652
15266
  worktreeBranch: flags.get("worktree"),
13653
15267
  worktreeBase: flags.get("base"),
@@ -13676,7 +15290,16 @@ async function main2() {
13676
15290
  break;
13677
15291
  }
13678
15292
  case "attach": {
13679
- const id = positional[0];
15293
+ let id = positional[0];
15294
+ if (flags.get("last") === true) {
15295
+ const all = await client.call("session.list");
15296
+ const pool = flags.get("all") === true ? all : scopedHere(all);
15297
+ const last = mostRecentSession(pool);
15298
+ if (!last)
15299
+ throw new Error(t("cli.attach.noRecent"));
15300
+ console.log(t("cli.attach.picked", { id: last.shortId, name: last.name }));
15301
+ id = last.id;
15302
+ }
13680
15303
  if (!id)
13681
15304
  throw new Error(t("cli.usage.attach"));
13682
15305
  await attachSession(client, id, flags.get("host-status") === "orca");
@@ -13716,8 +15339,8 @@ async function main2() {
13716
15339
  case "key": {
13717
15340
  const [id, name] = positional;
13718
15341
  if (!id || !name)
13719
- throw new Error(t("cli.usage.key", { keys: Object.keys(KEY_SEQUENCES).join("|") }));
13720
- const seq = KEY_SEQUENCES[name];
15342
+ throw new Error(t("cli.usage.key", { keys: KEY_NAMES.join("|") }));
15343
+ const seq = keySequence(name);
13721
15344
  if (seq === undefined)
13722
15345
  throw new Error(t("cli.key.unknown", { name }));
13723
15346
  await client.call("terminal.write", { id, dataB64: Buffer.from(seq, "utf8").toString("base64") });
@@ -13728,8 +15351,8 @@ async function main2() {
13728
15351
  const [id, indexArg] = positional;
13729
15352
  if (!id || indexArg === undefined)
13730
15353
  throw new Error(t("cli.usage.choose"));
13731
- const keys = buildMenuSelectKeys(Number(indexArg));
13732
- await client.call("terminal.write", { id, dataB64: Buffer.from(keys, "utf8").toString("base64") });
15354
+ const keys2 = buildMenuSelectKeys(Number(indexArg));
15355
+ await client.call("terminal.write", { id, dataB64: Buffer.from(keys2, "utf8").toString("base64") });
13733
15356
  console.log(t("cli.session.selected", { index: indexArg }));
13734
15357
  break;
13735
15358
  }
@@ -13764,9 +15387,10 @@ async function main2() {
13764
15387
  console.log(value === undefined ? "" : String(value));
13765
15388
  } else if (sub === "set") {
13766
15389
  const [, dottedPath, ...rest2] = positional;
13767
- if (!dottedPath || rest2.length === 0)
15390
+ const value = passthrough.length > 0 ? passthrough : rest2.join(" ");
15391
+ if (!dottedPath || value.length === 0)
13768
15392
  throw new Error(t("cli.usage.configSet"));
13769
- await client.call("config.set", { path: dottedPath, value: rest2.join(" ") });
15393
+ await client.call("config.set", { path: dottedPath, value });
13770
15394
  console.log(t("cli.config.set", { path: dottedPath }));
13771
15395
  } else if (sub === "unset") {
13772
15396
  const dottedPath = positional[1];
@@ -13805,8 +15429,8 @@ async function main2() {
13805
15429
  case "project": {
13806
15430
  const sub = positional[0] ?? "list";
13807
15431
  if (sub === "add") {
13808
- const dir = positional[1] ? path19.resolve(positional[1]) : process.cwd();
13809
- const alias = positional[2] ?? path19.basename(dir);
15432
+ const dir = positional[1] ? path21.resolve(positional[1]) : process.cwd();
15433
+ const alias = positional[2] ?? path21.basename(dir);
13810
15434
  const projects = await client.call("project.add", {
13811
15435
  alias,
13812
15436
  path: dir
@@ -13865,6 +15489,24 @@ async function main2() {
13865
15489
  }
13866
15490
  break;
13867
15491
  }
15492
+ case "quota": {
15493
+ const report = await client.call("quota.report").catch(() => {
15494
+ return;
15495
+ });
15496
+ if (report === undefined) {
15497
+ console.log(t("doctor.daemon.old"));
15498
+ break;
15499
+ }
15500
+ if (report.checks.length === 0) {
15501
+ console.log(t("cli.quota.none"));
15502
+ break;
15503
+ }
15504
+ const GLYPHS = { ok: "\u2713", info: "\u2023", warn: "!", fail: "\u2717" };
15505
+ for (const check of report.checks) {
15506
+ console.log(` ${GLYPHS[check.status]} ${t(check.key, check.params ?? {})}`);
15507
+ }
15508
+ break;
15509
+ }
13868
15510
  case "worktree": {
13869
15511
  const sub = positional[0] ?? "list";
13870
15512
  if (sub === "list") {