@layorixinc/axterm 0.1.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 +2442 -670
  2. package/package.json +50 -50
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)
@@ -684,9 +854,11 @@ usage:
684
854
  axterm fanout --count 3 [--prefix try] "prompt"
685
855
  axterm worktree list|rm <branch> [--force]
686
856
  axterm diff <id> [--base REF]
687
- axterm telegram link|doctor|status
857
+ axterm telegram link|doctor|status|discover
858
+ discover lists the group and user ids your bot can see
688
859
  axterm setup interactive configuration (runs automatically on first use)
689
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
690
862
  axterm config list|get <path>|set <path> <v>|unset <path>|path|import-env [file]
691
863
 
692
864
  locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
@@ -697,7 +869,9 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
697
869
  "cli.sessions.removed": "removed {count} session(s)",
698
870
  "cli.sessions.noneExited": "no exited sessions to remove",
699
871
  "cli.session.removeFailed": "could not remove {id}: {error}",
700
- "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",
701
875
  "cli.session.killed": "killed",
702
876
  "cli.session.sent": "sent",
703
877
  "cli.session.sentKey": "sent {key}",
@@ -725,7 +899,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
725
899
  "cli.update.notBinary": "this axterm runs from source, so there is nothing to replace \u2014 use git pull",
726
900
  "cli.flag.needsValue": "--{flag} requires a value",
727
901
  "cli.key.unknown": "unknown key: {name}",
728
- "cli.usage.attach": "usage: axterm attach <id>",
902
+ "cli.usage.attach": "usage: axterm attach <id> (or `axterm attach --last`)",
729
903
  "cli.usage.send": "usage: axterm send [--raw] <id> <text...>",
730
904
  "cli.usage.screen": "usage: axterm screen <id>",
731
905
  "cli.usage.esc": "usage: axterm esc <id>",
@@ -738,14 +912,14 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
738
912
  "cli.usage.diff": "usage: axterm diff <id> [--base REF]",
739
913
  "cli.usage.config": "usage: axterm config list|get|set|unset|path|import-env",
740
914
  "cli.usage.configGet": "usage: axterm config get <path> [--reveal]",
741
- "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 `--`)",
742
916
  "cli.usage.configUnset": "usage: axterm config unset <path>",
743
917
  "cli.usage.project": "usage: axterm project add|list|rm",
744
918
  "cli.usage.projectRm": "usage: axterm project rm <alias>",
745
919
  "cli.usage.worktree": "usage: axterm worktree list|rm <branch> [--force]",
746
920
  "cli.usage.worktreeRm": "usage: axterm worktree rm <branch> [--force]",
747
921
  "cli.usage.daemon": "usage: axterm daemon status|stop",
748
- "cli.usage.telegram": "usage: axterm telegram link|doctor|status",
922
+ "cli.usage.telegram": "usage: axterm telegram link|doctor|status|discover",
749
923
  "cli.usage.fanout": "usage: axterm fanout --count N <prompt>",
750
924
  "cli.config.set": "set {path}",
751
925
  "cli.config.unset": "unset {path}",
@@ -762,6 +936,13 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
762
936
  "cli.daemon.stopping": "daemon stopping",
763
937
  "cli.daemon.notReady": "daemon did not become ready within 15s (see {log})",
764
938
  "cli.telegram.missingSettings": "telegram link: no token/group/user found. Provide --token/--group/--user flags, TELEGRAM_* environment variables, or a .env file in the current directory.",
939
+ "cli.telegram.discoverHint": "Create a group with Topics enabled, add your bot to it, then run this again. Being added is enough \u2014 you do not have to send anything.",
940
+ "cli.telegram.discoverNone": "the bot cannot see any group yet.",
941
+ "cli.telegram.discoverChats": "groups this bot can see (use the id as --group):",
942
+ "cli.telegram.discoverUsers": "people it has seen (use the id as --user):",
943
+ "cli.telegram.topicsOff": "Topics are OFF \u2014 axterm needs them; enable Topics in the group settings",
944
+ "err.telegram.discoverWhileRunning": "the telegram channel is already running, and Telegram allows one reader per bot token \u2014 stop it with `axterm config set channels.telegram.enabled false` before discovering, or read the ids from `axterm telegram status`",
945
+ "err.telegram.noToken": "no bot token is configured \u2014 run `axterm setup` or pass --token",
765
946
  "tg.help.general.header": "axterm commands (General topic):",
766
947
  "tg.help.general.projects": "/projects \u2014 registered projects",
767
948
  "tg.help.general.new": "/new <project> [name] \u2014 new omp session",
@@ -770,7 +951,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
770
951
  "tg.help.general.worktrees": "/worktrees <project> \xB7 /rm <project> <branch> [force]",
771
952
  "tg.help.general.sessions": "/sessions \u2014 session list",
772
953
  "tg.help.topic.header": "Inside a session topic, a plain message is delivered as a prompt.",
773
- "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",
774
955
  "tg.session.started": `\u25B6 Session {id} started
775
956
  {command}
776
957
  \uD83D\uDCC1 {cwd}`,
@@ -794,6 +975,15 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
794
975
  "tg.shell.only": "(shell)",
795
976
  "tg.agent.failed": "\uD83D\uDED1 The agent could not finish this turn: {error}",
796
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>`,
797
987
  "tg.inbound.tooLarge": "\u26A0\uFE0F That file is larger than the {limit} limit \u2014 nothing was downloaded.",
798
988
  "tg.inbound.empty": "\u26A0\uFE0F That file is empty \u2014 nothing was downloaded.",
799
989
  "tg.inbound.noSession": "\u26A0\uFE0F Send the file inside a session topic \u2014 no session is bound to this one.",
@@ -869,6 +1059,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
869
1059
  "doctor.telegram.ok": "telegram: connected",
870
1060
  "doctor.telegram.off": "telegram: not running",
871
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",
872
1071
  "fanout.needCountOrBranches": "a branch count or a branch list is required",
873
1072
  "fanout.needPrompt": "a prompt is required",
874
1073
  "fanout.outOfRange": "must be between 1 and {max}",
@@ -892,11 +1091,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
892
1091
  "health.telegram.noUsers": "Telegram is enabled but no user is allowed to control it",
893
1092
  "wizard.prompt.locale": "Language",
894
1093
  "wizard.prompt.machine": "Name for this machine (used to address it from a channel)",
895
- "wizard.prompt.agent": "Default agent command",
1094
+ "wizard.prompt.agent": "Default agent (extra flags go in defaults.agentArgs.<agent>)",
896
1095
  "wizard.prompt.telegram": "Set up Telegram control now?",
897
1096
  "wizard.prompt.telegramToken": "Bot token from @BotFather",
898
- "wizard.prompt.telegramGroup": "Group id (negative, starts with -100)",
899
- "wizard.prompt.telegramUsers": "Telegram user ids allowed to control axterm (comma separated)",
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`.",
1098
+ "wizard.prompt.telegramGroup": "Group id (negative, starts with -100; `axterm telegram discover` lists them)",
1099
+ "wizard.prompt.telegramUsers": "Telegram user ids allowed to control axterm (comma separated; `axterm telegram discover` lists them)",
900
1100
  "wizard.prompt.review": "Save this configuration?",
901
1101
  "wizard.error.locale": "pick one of the offered languages",
902
1102
  "wizard.error.machine": "letters, digits, dot, dash and underscore only",
@@ -926,8 +1126,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
926
1126
  \uC0AC\uC6A9\uBC95:
927
1127
  axterm run [--cwd \uD3F4\uB354] [--name \uC774\uB984] [--shell pwsh|powershell|cmd|bash|zsh]
928
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
929
1131
  axterm list [--all] \uC774 \uD3F4\uB354\uC5D0\uC11C \uB9CC\uB4E0 \uC138\uC158. --all \uC740 \uC804\uCCB4
930
- 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+])
931
1135
  \uD638\uC2A4\uD2B8 \uD130\uBBF8\uB110 \uC81C\uBAA9\uC740 \uD56D\uC0C1 \uC5D0\uC774\uC804\uD2B8 \uC0C1\uD0DC\uB97C \uBC18\uC601\uD55C\uB2E4;
932
1136
  --host-status orca \uB294 \uD32C\uC5D0\uB3C4 \uC804\uCCB4 \uC0C1\uD0DC\uB97C \uBCF4\uACE0\uD55C\uB2E4
933
1137
  axterm esc <id> ESC \uC804\uC1A1 (\uC911\uB2E8)
@@ -946,9 +1150,11 @@ usage: /fanout <project> <count|branches> <prompt>`,
946
1150
  axterm fanout --count 3 [--prefix try] "\uD504\uB86C\uD504\uD2B8"
947
1151
  axterm worktree list|rm <\uBE0C\uB79C\uCE58> [--force]
948
1152
  axterm diff <id> [--base REF]
949
- axterm telegram link|doctor|status
1153
+ axterm telegram link|doctor|status|discover
1154
+ discover \u2014 \uBD07\uC774 \uBCFC \uC218 \uC788\uB294 \uADF8\uB8F9\xB7\uC0AC\uC6A9\uC790 id \uBAA9\uB85D
950
1155
  axterm setup \uB300\uD654\uD615 \uC124\uC815 (\uCD5C\uCD08 \uC2E4\uD589 \uC2DC \uC790\uB3D9)
951
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)
952
1158
  axterm config list|get <\uACBD\uB85C>|set <\uACBD\uB85C> <\uAC12>|unset <\uACBD\uB85C>|path|import-env [\uD30C\uC77C]
953
1159
 
954
1160
  \uC5B8\uC5B4: AXTERM_LANG=en|ko \uB610\uB294 \`axterm config set locale ko\`
@@ -959,7 +1165,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
959
1165
  "cli.sessions.removed": "\uC138\uC158 {count}\uAC1C \uC0AD\uC81C\uB428",
960
1166
  "cli.sessions.noneExited": "\uC0AD\uC81C\uD560 \uC885\uB8CC\uB41C \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
961
1167
  "cli.session.removeFailed": "{id} \uC0AD\uC81C \uC2E4\uD328: {error}",
962
- "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",
963
1171
  "cli.session.killed": "\uC885\uB8CC\uB428",
964
1172
  "cli.session.sent": "\uC804\uB2EC\uB428",
965
1173
  "cli.session.sentKey": "{key} \uC804\uB2EC\uB428",
@@ -987,7 +1195,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
987
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",
988
1196
  "cli.flag.needsValue": "--{flag} \uC5D0\uB294 \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
989
1197
  "cli.key.unknown": "\uC54C \uC218 \uC5C6\uB294 \uD0A4: {name}",
990
- "cli.usage.attach": "\uC0AC\uC6A9\uBC95: axterm attach <id>",
1198
+ "cli.usage.attach": "\uC0AC\uC6A9\uBC95: axterm attach <id> (\uB610\uB294 `axterm attach --last`)",
991
1199
  "cli.usage.send": "\uC0AC\uC6A9\uBC95: axterm send [--raw] <id> <\uD14D\uC2A4\uD2B8...>",
992
1200
  "cli.usage.screen": "\uC0AC\uC6A9\uBC95: axterm screen <id>",
993
1201
  "cli.usage.esc": "\uC0AC\uC6A9\uBC95: axterm esc <id>",
@@ -1000,14 +1208,14 @@ usage: /fanout <project> <count|branches> <prompt>`,
1000
1208
  "cli.usage.diff": "\uC0AC\uC6A9\uBC95: axterm diff <id> [--base REF]",
1001
1209
  "cli.usage.config": "\uC0AC\uC6A9\uBC95: axterm config list|get|set|unset|path|import-env",
1002
1210
  "cli.usage.configGet": "\uC0AC\uC6A9\uBC95: axterm config get <\uACBD\uB85C> [--reveal]",
1003
- "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)",
1004
1212
  "cli.usage.configUnset": "\uC0AC\uC6A9\uBC95: axterm config unset <\uACBD\uB85C>",
1005
1213
  "cli.usage.project": "\uC0AC\uC6A9\uBC95: axterm project add|list|rm",
1006
1214
  "cli.usage.projectRm": "\uC0AC\uC6A9\uBC95: axterm project rm <\uBCC4\uCE6D>",
1007
1215
  "cli.usage.worktree": "\uC0AC\uC6A9\uBC95: axterm worktree list|rm <\uBE0C\uB79C\uCE58> [--force]",
1008
1216
  "cli.usage.worktreeRm": "\uC0AC\uC6A9\uBC95: axterm worktree rm <\uBE0C\uB79C\uCE58> [--force]",
1009
1217
  "cli.usage.daemon": "\uC0AC\uC6A9\uBC95: axterm daemon status|stop",
1010
- "cli.usage.telegram": "\uC0AC\uC6A9\uBC95: axterm telegram link|doctor|status",
1218
+ "cli.usage.telegram": "\uC0AC\uC6A9\uBC95: axterm telegram link|doctor|status|discover",
1011
1219
  "cli.usage.fanout": "\uC0AC\uC6A9\uBC95: axterm fanout --count N <\uD504\uB86C\uD504\uD2B8>",
1012
1220
  "cli.config.set": "{path} \uC124\uC815\uB428",
1013
1221
  "cli.config.unset": "{path} \uD574\uC81C\uB428",
@@ -1030,9 +1238,16 @@ usage: /fanout <project> <count|branches> <prompt>`,
1030
1238
  "tg.help.general.wt": "/wt <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> \u2014 worktree \uC138\uC158",
1031
1239
  "tg.help.general.fanout": "/fanout <\uD504\uB85C\uC81D\uD2B8> <\uAC1C\uC218|\uBE0C\uB79C\uCE58\uB4E4> <\uD504\uB86C\uD504\uD2B8> \u2014 \uBCD1\uB82C \uC2E4\uD589",
1032
1240
  "tg.help.general.worktrees": "/worktrees <\uD504\uB85C\uC81D\uD2B8> \xB7 /rm <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> [force]",
1241
+ "cli.telegram.discoverHint": "\uD1A0\uD53D\uC744 \uCF20 \uADF8\uB8F9\uC744 \uB9CC\uB4E4\uACE0 \uBD07\uC744 \uCD08\uB300\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694. \uCD08\uB300\uB9CC \uD558\uBA74 \uCDA9\uBD84\uD569\uB2C8\uB2E4 \u2014 \uBA54\uC2DC\uC9C0\uB97C \uBCF4\uB0BC \uD544\uC694\uB294 \uC5C6\uC2B5\uB2C8\uB2E4.",
1242
+ "cli.telegram.discoverNone": "\uBD07\uC774 \uC544\uC9C1 \uC5B4\uB5A4 \uADF8\uB8F9\uB3C4 \uBCFC \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
1243
+ "cli.telegram.discoverChats": "\uBD07\uC774 \uBCFC \uC218 \uC788\uB294 \uADF8\uB8F9 (\uC774 id\uB97C --group \uC5D0 \uC0AC\uC6A9):",
1244
+ "cli.telegram.discoverUsers": "\uBD07\uC774 \uBCF8 \uC0AC\uC6A9\uC790 (\uC774 id\uB97C --user \uC5D0 \uC0AC\uC6A9):",
1245
+ "cli.telegram.topicsOff": "\uD1A0\uD53D\uC774 \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4 \u2014 axterm\uC5D0 \uD544\uC694\uD569\uB2C8\uB2E4. \uADF8\uB8F9 \uC124\uC815\uC5D0\uC11C \uD1A0\uD53D\uC744 \uCF1C\uC138\uC694",
1246
+ "err.telegram.discoverWhileRunning": "\uD154\uB808\uADF8\uB7A8 \uCC44\uB110\uC774 \uC774\uBBF8 \uC2E4\uD589 \uC911\uC774\uACE0, \uD154\uB808\uADF8\uB7A8\uC740 \uBD07 \uD1A0\uD070\uB2F9 \uB9AC\uB354\uB97C \uD558\uB098\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4 \u2014 `axterm config set channels.telegram.enabled false` \uB85C \uBA48\uCD98 \uB4A4 \uD0D0\uC0C9\uD558\uAC70\uB098, `axterm telegram status` \uC5D0\uC11C id\uB97C \uD655\uC778\uD558\uC138\uC694",
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",
1033
1248
  "tg.help.general.sessions": "/sessions \u2014 \uC138\uC158 \uBAA9\uB85D",
1034
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.",
1035
- "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",
1036
1251
  "tg.session.started": `\u25B6 \uC138\uC158 {id} \uC2DC\uC791
1037
1252
  {command}
1038
1253
  \uD83D\uDCC1 {cwd}`,
@@ -1056,6 +1271,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
1056
1271
  "tg.shell.only": "(\uC178)",
1057
1272
  "tg.agent.failed": "\uD83D\uDED1 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC774\uBC88 \uD134\uC744 \uB05D\uB0B4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: {error}",
1058
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>`,
1059
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.",
1060
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.",
1061
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.",
@@ -1131,6 +1355,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
1131
1355
  "doctor.telegram.ok": "\uD154\uB808\uADF8\uB7A8: \uC5F0\uACB0\uB428",
1132
1356
  "doctor.telegram.off": "\uD154\uB808\uADF8\uB7A8: \uBBF8\uC2E4\uD589",
1133
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",
1134
1367
  "fanout.needCountOrBranches": "\uBE0C\uB79C\uCE58 \uC218\uB098 \uBE0C\uB79C\uCE58 \uBAA9\uB85D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
1135
1368
  "fanout.needPrompt": "\uD504\uB86C\uD504\uD2B8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4",
1136
1369
  "fanout.outOfRange": "1..{max} \uBC94\uC704\uC5EC\uC57C \uD569\uB2C8\uB2E4",
@@ -1154,11 +1387,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
1154
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",
1155
1388
  "wizard.prompt.locale": "\uC5B8\uC5B4",
1156
1389
  "wizard.prompt.machine": "\uC774 \uBA38\uC2E0\uC758 \uC774\uB984 (\uCC44\uB110\uC5D0\uC11C \uC9C0\uC815\uD560 \uB54C \uC500)",
1157
- "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)",
1158
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.",
1159
1393
  "wizard.prompt.telegramToken": "@BotFather \uC5D0\uC11C \uBC1B\uC740 \uBD07 \uD1A0\uD070",
1160
- "wizard.prompt.telegramGroup": "\uADF8\uB8F9 id (\uC74C\uC218, \uBCF4\uD1B5 -100 \uC73C\uB85C \uC2DC\uC791)",
1161
- "wizard.prompt.telegramUsers": "axterm \uC870\uC791\uC744 \uD5C8\uC6A9\uD560 Telegram user id (\uC27C\uD45C\uB85C \uAD6C\uBD84)",
1394
+ "wizard.prompt.telegramGroup": "\uADF8\uB8F9 id (\uC74C\uC218, \uBCF4\uD1B5 -100 \uC73C\uB85C \uC2DC\uC791. `axterm telegram discover` \uB85C \uD655\uC778 \uAC00\uB2A5)",
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)",
1162
1396
  "wizard.prompt.review": "\uC774 \uC124\uC815\uC744 \uC800\uC7A5\uD560\uAE4C\uC694?",
1163
1397
  "wizard.error.locale": "\uC81C\uC2DC\uB41C \uC5B8\uC5B4 \uC911\uC5D0\uC11C \uACE0\uB974\uC138\uC694",
1164
1398
  "wizard.error.machine": "\uC601\uBB38\xB7\uC22B\uC790\uC640 . - _ \uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4",
@@ -1195,6 +1429,8 @@ function defaultFor(step, config, hostname) {
1195
1429
  return config.machine?.name ?? hostname;
1196
1430
  case "agent":
1197
1431
  return config.defaults?.agent ?? "omp";
1432
+ case "agentApproval":
1433
+ return "no";
1198
1434
  case "telegram":
1199
1435
  return telegram?.enabled === true ? "yes" : "no";
1200
1436
  case "telegramGroup":
@@ -1228,6 +1464,7 @@ function collect(answers) {
1228
1464
  ["machine.name", answers["machine"] ?? ""],
1229
1465
  ["defaults.agent", answers["agent"] ?? "omp"]
1230
1466
  ];
1467
+ settings.push(["defaults.autoApprove", YES.has(answers["agentApproval"] ?? "") ? "true" : "false"]);
1231
1468
  const secrets = [];
1232
1469
  const wantsTelegram = YES.has(answers["telegram"] ?? "");
1233
1470
  settings.push(["channels.telegram.enabled", wantsTelegram ? "true" : "false"]);
@@ -1265,6 +1502,7 @@ var YES, NO, numericList = (value) => {
1265
1502
  return parts.length > 0 && parts.every((p) => /^-?\d+$/.test(p)) ? undefined : "wizard.error.numberList";
1266
1503
  }, STEPS, wizardConfig;
1267
1504
  var init_setup_wizard = __esm(() => {
1505
+ init_agent_launch();
1268
1506
  init_i18n();
1269
1507
  YES = new Set(["y", "yes", "true", "1"]);
1270
1508
  NO = new Set(["n", "no", "false", "0"]);
@@ -1283,9 +1521,17 @@ var init_setup_wizard = __esm(() => {
1283
1521
  next: () => "agent"
1284
1522
  },
1285
1523
  agent: {
1286
- kind: "text",
1524
+ kind: "select",
1287
1525
  promptKey: "wizard.prompt.agent",
1288
- 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",
1289
1535
  next: () => "telegram"
1290
1536
  },
1291
1537
  telegram: {
@@ -1355,7 +1601,7 @@ function oscTitle(title) {
1355
1601
  const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
1356
1602
  return `\x1B]0;${safe}\x07`;
1357
1603
  }
1358
- 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;
1359
1605
  var init_agent_registry = __esm(() => {
1360
1606
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1361
1607
  PROFILES = {
@@ -1364,6 +1610,8 @@ var init_agent_registry = __esm(() => {
1364
1610
  displayName: "OMP",
1365
1611
  title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
1366
1612
  hostHookSource: "omp",
1613
+ approvalBypassArgs: ["--approval-mode", "yolo"],
1614
+ quota: "gauge",
1367
1615
  resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
1368
1616
  },
1369
1617
  pi: {
@@ -1383,6 +1631,8 @@ var init_agent_registry = __esm(() => {
1383
1631
  synthesizeWorking: false
1384
1632
  },
1385
1633
  hostHookSource: "codex",
1634
+ quota: "gauge",
1635
+ approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
1386
1636
  structuredApprovals: true,
1387
1637
  resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
1388
1638
  },
@@ -1427,6 +1677,8 @@ var init_agent_registry = __esm(() => {
1427
1677
  synthesizeTerminal: false
1428
1678
  },
1429
1679
  hostHookSource: "claude",
1680
+ quota: "event",
1681
+ approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
1430
1682
  resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
1431
1683
  },
1432
1684
  gemini: {
@@ -1440,11 +1692,13 @@ var init_agent_registry = __esm(() => {
1440
1692
  synthesizeTerminal: false
1441
1693
  },
1442
1694
  hostHookSource: "gemini",
1695
+ approvalBypassArgs: ["--approval-mode", "yolo"],
1443
1696
  hookAck: "{}",
1444
1697
  approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
1445
1698
  resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
1446
1699
  }
1447
1700
  };
1701
+ REGISTERED_AGENTS = Object.keys(PROFILES);
1448
1702
  });
1449
1703
 
1450
1704
  // packages/core/src/agent-resume.ts
@@ -1502,6 +1756,13 @@ function hookLaunch(input) {
1502
1756
  function isHookInvocation(args) {
1503
1757
  return args[0] === HOOK_ARGV_FLAG && typeof args[1] === "string" && args[1] !== "";
1504
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
+ }
1505
1766
  function hookPostTarget(env, agent) {
1506
1767
  const port = Number(env["AXTERM_HOOK_PORT"]);
1507
1768
  const token = env["AXTERM_HOOK_TOKEN"];
@@ -1524,7 +1785,7 @@ function hookPostTarget(env, agent) {
1524
1785
  function isDaemonInvocation(args) {
1525
1786
  return args[0] === DAEMON_ARGV_FLAG;
1526
1787
  }
1527
- 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;
1528
1789
  var init_self_spawn = __esm(() => {
1529
1790
  RUNTIME_NAMES = new Set(["bun", "bun.exe", "bun-debug", "bun-debug.exe", "node", "node.exe"]);
1530
1791
  });
@@ -1603,6 +1864,15 @@ function updateNeeded(current, latest) {
1603
1864
  return false;
1604
1865
  return Bun.semver.order(latest, current) > 0;
1605
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
+ }
1606
1876
  function planBinarySwap(input) {
1607
1877
  const steps = [];
1608
1878
  if (input.platform !== "win32")
@@ -1612,16 +1882,16 @@ function planBinarySwap(input) {
1612
1882
  return steps;
1613
1883
  }
1614
1884
  function planSessionRestore(sessions) {
1615
- const running = sessions.filter((s) => s.state === "running");
1885
+ const live = sessions.filter((s) => s.state !== "exited");
1616
1886
  const resumable = (s) => agentProfile(agentIdFromCommand(s.agentCommand)).resume !== undefined;
1617
1887
  return {
1618
- resume: running.filter(resumable).map((s) => s.id),
1619
- cannotResume: running.filter((s) => !resumable(s)),
1620
- 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
1621
1891
  };
1622
1892
  }
1623
1893
  function planUpdateRestart(input) {
1624
- const live = input.sessions.filter((s) => s.state === "running").length;
1894
+ const live = input.sessions.filter((s) => s.state !== "exited").length;
1625
1895
  if (!input.restart && live > 0) {
1626
1896
  return { stopDaemon: false, resume: [], lost: [], stale: live, needsConfirmation: false };
1627
1897
  }
@@ -1769,6 +2039,44 @@ class EchoGuard {
1769
2039
  this.pending.delete(sessionId);
1770
2040
  }
1771
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
+
1772
2080
  // packages/core/src/choice.ts
1773
2081
  function buildApprovalQuestion(toolName) {
1774
2082
  return {
@@ -1999,8 +2307,7 @@ function mapClaudeHook(input) {
1999
2307
  ...input.tool_input !== undefined ? { tool_input: input.tool_input } : {}
2000
2308
  }
2001
2309
  ];
2002
- case "Stop":
2003
- case "StopFailure": {
2310
+ case "Stop": {
2004
2311
  const out = [];
2005
2312
  const text = input.last_assistant_message;
2006
2313
  if (typeof text === "string" && text.trim() !== "") {
@@ -2009,6 +2316,19 @@ function mapClaudeHook(input) {
2009
2316
  out.push({ event: "agent_end" });
2010
2317
  return out;
2011
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
+ }
2012
2332
  case "SessionEnd":
2013
2333
  return [
2014
2334
  { event: "session_shutdown", ...input.session_id !== undefined ? { session_id: input.session_id } : {} }
@@ -2041,16 +2361,26 @@ function buildClaudeSettings(input) {
2041
2361
  var MAX_RESULT_TEXT = 2000, HOOK_TIMEOUT_SECONDS = 5, TOOL_EVENTS, PLAIN_EVENTS;
2042
2362
  var init_claude_hooks = __esm(() => {
2043
2363
  TOOL_EVENTS = ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest"];
2044
- PLAIN_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure", "SessionEnd"];
2364
+ PLAIN_EVENTS = [
2365
+ "SessionStart",
2366
+ "UserPromptSubmit",
2367
+ "Stop",
2368
+ "StopFailure",
2369
+ "MessageDisplay",
2370
+ "SessionEnd"
2371
+ ];
2045
2372
  });
2046
2373
 
2047
- // packages/core/src/gemini-onboarding.ts
2048
- 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;
2049
2379
  const lines = screen.split(`
2050
2380
  `).map((line) => line.replace(/^\s*\u2502/, "").replace(/\u2502\s*$/, "").trim());
2051
2381
  for (let i = lines.length - 1;i >= 0; i--) {
2052
2382
  const line = lines[i];
2053
- const dialog = DIALOGS.find((d) => line === d.question);
2383
+ const dialog = dialogs.find((d) => line.startsWith(d.question));
2054
2384
  if (!dialog)
2055
2385
  continue;
2056
2386
  const options = [];
@@ -2066,12 +2396,19 @@ function detectGeminiOnboarding(screen) {
2066
2396
  return;
2067
2397
  }
2068
2398
  var DIALOGS, OPTION_ROW, OPTION_WINDOW_LINES = 8;
2069
- var init_gemini_onboarding = __esm(() => {
2070
- DIALOGS = [
2071
- { kind: "folderTrust", question: "Do you trust the files in this folder?" },
2072
- { kind: "authMethod", question: "How would you like to authenticate for this project?" }
2073
- ];
2074
- 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+(.+)$/;
2075
2412
  });
2076
2413
 
2077
2414
  // packages/core/src/doctor.ts
@@ -2206,87 +2543,587 @@ var init_doctor = __esm(() => {
2206
2543
  GEMINI_FORBIDDEN_EVENTS = ["AfterModel", "BeforeModel"];
2207
2544
  });
2208
2545
 
2209
- // packages/core/src/version.ts
2210
- function setProductVersion(version) {
2211
- product = version;
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;
2212
2557
  }
2213
- function productVersion() {
2214
- return product;
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();
2215
2572
  }
2216
- var product = "";
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
+ });
2217
2578
 
2218
- // packages/core/src/codex-hooks.ts
2219
- import { createHash } from "crypto";
2220
- function quoteToken(token) {
2221
- if (token !== "" && !/[ \t"]/.test(token))
2222
- return token;
2223
- return `"${token.replaceAll('"', "\\\"")}"`;
2224
- }
2225
- function buildCodexHooks(input) {
2226
- const command = [input.command, ...input.args].map(quoteToken).join(" ");
2227
- const group = (timeout) => [
2228
- { hooks: [{ type: "command", command, timeout, async: false }] }
2229
- ];
2230
- const hooks = {};
2231
- for (const event of OBSERVATION_EVENTS)
2232
- hooks[event] = group(input.timeoutSeconds ?? OBSERVATION_TIMEOUT_SECONDS);
2233
- hooks["PermissionRequest"] = group(CODEX_PERMISSION_TIMEOUT_SECONDS);
2234
- hooks["SessionEnd"] = group(SESSION_END_TIMEOUT_SECONDS);
2235
- return { hooks };
2236
- }
2237
- function normalizedTimeout(event, timeout) {
2238
- if (event === "SessionEnd")
2239
- return Math.min(3, Math.max(1, timeout));
2240
- return Math.max(1, timeout);
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
+ }
2241
2624
  }
2242
- function canonicalJson(value) {
2243
- if (Array.isArray(value))
2244
- return `[${value.map(canonicalJson).join(",")}]`;
2245
- if (typeof value === "object" && value !== null) {
2246
- const entries = Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);
2247
- return `{${entries.join(",")}}`;
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`;
2248
2679
  }
2249
- return JSON.stringify(value);
2680
+ const days = Math.floor(hours / 24);
2681
+ const restHours = hours % 24;
2682
+ return restHours === 0 ? `${days}d` : `${days}d ${restHours}h`;
2250
2683
  }
2251
- function codexHookTrust(hooksJsonPath, file) {
2252
- const entries = [];
2253
- for (const [event, groups] of Object.entries(file.hooks)) {
2254
- const label = EVENT_KEY_LABELS[event];
2255
- if (label === undefined)
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 } });
2256
2691
  continue;
2257
- groups.forEach((group, groupIndex) => {
2258
- group.hooks.forEach((hook, hookIndex) => {
2259
- const identity = {
2260
- event_name: label,
2261
- ...group.matcher !== undefined ? { matcher: group.matcher } : {},
2262
- hooks: [
2263
- {
2264
- type: "command",
2265
- command: hook.command,
2266
- timeout: normalizedTimeout(event, hook.timeout),
2267
- async: hook.async
2268
- }
2269
- ]
2270
- };
2271
- const hash = `sha256:${createHash("sha256").update(canonicalJson(identity)).digest("hex")}`;
2272
- entries.push({ key: `${hooksJsonPath}:${label}:${groupIndex}:${hookIndex}`, hash });
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
+ }
2273
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 });
2274
2735
  });
2275
2736
  }
2276
- return entries;
2277
- }
2278
- function codexTrustStateToml(entries) {
2279
- return entries.map((e) => {
2280
- const key = e.key.replaceAll("\\", "\\\\").replaceAll('"', "\\\"");
2281
- return `[hooks.state."${key}"]
2282
- trusted_hash = "${e.hash}"
2283
- `;
2284
- }).join(`
2285
- `);
2737
+ return out;
2286
2738
  }
2287
- function codexPermissionDecision(question, answers, denyMessage) {
2288
- const picked = answers?.find((a) => a.questionIndex === 0)?.selected[0];
2289
- if (picked === undefined)
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
+
2967
+ // packages/core/src/version.ts
2968
+ function setProductVersion(version) {
2969
+ product = version;
2970
+ }
2971
+ function productVersion() {
2972
+ return product;
2973
+ }
2974
+ var product = "";
2975
+
2976
+ // packages/core/src/telegram-discovery.ts
2977
+ function readChat(value) {
2978
+ if (!isRecord(value))
2979
+ return;
2980
+ const id = value["id"];
2981
+ const type = value["type"];
2982
+ if (typeof id !== "number" || typeof type !== "string")
2983
+ return;
2984
+ const title = value["title"];
2985
+ const isForum = value["is_forum"];
2986
+ return {
2987
+ id,
2988
+ type,
2989
+ ...typeof title === "string" ? { title } : {},
2990
+ ...isForum === true ? { isForum: true } : {}
2991
+ };
2992
+ }
2993
+ function readUser(value) {
2994
+ if (!isRecord(value))
2995
+ return;
2996
+ const id = value["id"];
2997
+ if (typeof id !== "number")
2998
+ return;
2999
+ if (value["is_bot"] === true)
3000
+ return;
3001
+ const username = value["username"];
3002
+ const name = value["first_name"];
3003
+ return {
3004
+ id,
3005
+ ...typeof username === "string" ? { username } : {},
3006
+ ...typeof name === "string" ? { name } : {}
3007
+ };
3008
+ }
3009
+ function detail(candidate) {
3010
+ return (candidate.title !== undefined ? 1 : 0) + (candidate.isForum !== undefined ? 1 : 0);
3011
+ }
3012
+ function discoverTelegramTargets(updates) {
3013
+ const chats = new Map;
3014
+ const users = new Map;
3015
+ if (!Array.isArray(updates))
3016
+ return { chats: [], users: [] };
3017
+ const noteChat = (raw) => {
3018
+ const chat = readChat(raw);
3019
+ if (chat === undefined || !chat.type.includes("group"))
3020
+ return;
3021
+ const seen = chats.get(chat.id);
3022
+ if (seen === undefined || detail(chat) > detail(seen))
3023
+ chats.set(chat.id, chat);
3024
+ };
3025
+ const noteUser = (raw) => {
3026
+ const user = readUser(raw);
3027
+ if (user !== undefined && !users.has(user.id))
3028
+ users.set(user.id, user);
3029
+ };
3030
+ for (const update of updates) {
3031
+ if (!isRecord(update))
3032
+ continue;
3033
+ for (const key of CHAT_CARRIERS) {
3034
+ const carrier = update[key];
3035
+ if (!isRecord(carrier))
3036
+ continue;
3037
+ noteChat(carrier["chat"]);
3038
+ noteUser(carrier["from"]);
3039
+ }
3040
+ const callback = update["callback_query"];
3041
+ if (isRecord(callback)) {
3042
+ noteUser(callback["from"]);
3043
+ const message = callback["message"];
3044
+ if (isRecord(message))
3045
+ noteChat(message["chat"]);
3046
+ }
3047
+ }
3048
+ return { chats: [...chats.values()], users: [...users.values()] };
3049
+ }
3050
+ var CHAT_CARRIERS;
3051
+ var init_telegram_discovery = __esm(() => {
3052
+ CHAT_CARRIERS = ["message", "edited_message", "channel_post", "edited_channel_post", "my_chat_member"];
3053
+ });
3054
+
3055
+ // packages/core/src/codex-hooks.ts
3056
+ import { createHash } from "crypto";
3057
+ function quoteToken(token) {
3058
+ if (token !== "" && !/[ \t"]/.test(token))
3059
+ return token;
3060
+ return `"${token.replaceAll('"', "\\\"")}"`;
3061
+ }
3062
+ function buildCodexHooks(input) {
3063
+ const command = [input.command, ...input.args].map(quoteToken).join(" ");
3064
+ const group = (timeout) => [
3065
+ { hooks: [{ type: "command", command, timeout, async: false }] }
3066
+ ];
3067
+ const hooks = {};
3068
+ for (const event of OBSERVATION_EVENTS)
3069
+ hooks[event] = group(input.timeoutSeconds ?? OBSERVATION_TIMEOUT_SECONDS);
3070
+ hooks["PermissionRequest"] = group(CODEX_PERMISSION_TIMEOUT_SECONDS);
3071
+ hooks["SessionEnd"] = group(SESSION_END_TIMEOUT_SECONDS);
3072
+ return { hooks };
3073
+ }
3074
+ function normalizedTimeout(event, timeout) {
3075
+ if (event === "SessionEnd")
3076
+ return Math.min(3, Math.max(1, timeout));
3077
+ return Math.max(1, timeout);
3078
+ }
3079
+ function canonicalJson(value) {
3080
+ if (Array.isArray(value))
3081
+ return `[${value.map(canonicalJson).join(",")}]`;
3082
+ if (typeof value === "object" && value !== null) {
3083
+ const entries = Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);
3084
+ return `{${entries.join(",")}}`;
3085
+ }
3086
+ return JSON.stringify(value);
3087
+ }
3088
+ function codexHookTrust(hooksJsonPath, file) {
3089
+ const entries = [];
3090
+ for (const [event, groups] of Object.entries(file.hooks)) {
3091
+ const label = EVENT_KEY_LABELS[event];
3092
+ if (label === undefined)
3093
+ continue;
3094
+ groups.forEach((group, groupIndex) => {
3095
+ group.hooks.forEach((hook, hookIndex) => {
3096
+ const identity = {
3097
+ event_name: label,
3098
+ ...group.matcher !== undefined ? { matcher: group.matcher } : {},
3099
+ hooks: [
3100
+ {
3101
+ type: "command",
3102
+ command: hook.command,
3103
+ timeout: normalizedTimeout(event, hook.timeout),
3104
+ async: hook.async
3105
+ }
3106
+ ]
3107
+ };
3108
+ const hash = `sha256:${createHash("sha256").update(canonicalJson(identity)).digest("hex")}`;
3109
+ entries.push({ key: `${hooksJsonPath}:${label}:${groupIndex}:${hookIndex}`, hash });
3110
+ });
3111
+ });
3112
+ }
3113
+ return entries;
3114
+ }
3115
+ function codexTrustStateToml(entries) {
3116
+ return entries.map((e) => {
3117
+ const key = e.key.replaceAll("\\", "\\\\").replaceAll('"', "\\\"");
3118
+ return `[hooks.state."${key}"]
3119
+ trusted_hash = "${e.hash}"
3120
+ `;
3121
+ }).join(`
3122
+ `);
3123
+ }
3124
+ function codexPermissionDecision(question, answers, denyMessage) {
3125
+ const picked = answers?.find((a) => a.questionIndex === 0)?.selected[0];
3126
+ if (picked === undefined)
2290
3127
  return;
2291
3128
  if (picked === question.options[0]?.label) {
2292
3129
  return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "allow" } } };
@@ -2346,6 +3183,19 @@ function errorText(value) {
2346
3183
  return message.slice(0, MAX_RESULT_TEXT2);
2347
3184
  return llmContentText(value);
2348
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
+ }
2349
3199
  function mapGeminiHook(input) {
2350
3200
  switch (input.hook_event_name) {
2351
3201
  case "SessionStart":
@@ -2378,15 +3228,17 @@ function mapGeminiHook(input) {
2378
3228
  }
2379
3229
  return out;
2380
3230
  }
2381
- case "Notification":
3231
+ case "Notification": {
2382
3232
  if (input.notification_type !== "ToolPermission")
2383
3233
  return [];
3234
+ const toolName = permissionToolName(input.details);
2384
3235
  return [
2385
3236
  {
2386
3237
  event: "tool_approval_requested",
2387
- ...input.tool_name !== undefined ? { tool_name: input.tool_name } : {}
3238
+ ...toolName !== undefined ? { tool_name: toolName } : {}
2388
3239
  }
2389
3240
  ];
3241
+ }
2390
3242
  case "AfterAgent": {
2391
3243
  const out = [];
2392
3244
  const text = input.prompt_response;
@@ -2418,7 +3270,10 @@ function buildGeminiSettings(input) {
2418
3270
  const hooks = {};
2419
3271
  for (const event of GEMINI_EVENTS)
2420
3272
  hooks[event] = [{ hooks: [handler] }];
2421
- return { hooks };
3273
+ return {
3274
+ hooks,
3275
+ ...input.mcp === undefined ? {} : { mcpServers: { [MCP_SERVER_NAME]: mcpServerEntry(input.mcp, input.mcpContextPath) } }
3276
+ };
2422
3277
  }
2423
3278
  var MAX_RESULT_TEXT2 = 2000, HOOK_TIMEOUT_MS = 2000, GEMINI_EVENTS;
2424
3279
  var init_gemini_hooks = __esm(() => {
@@ -2433,65 +3288,6 @@ var init_gemini_hooks = __esm(() => {
2433
3288
  ];
2434
3289
  });
2435
3290
 
2436
- // packages/core/src/agent-launch.ts
2437
- function uuidFromSessionId(sessionId) {
2438
- const hex = sessionId.replace(/-/g, "");
2439
- if (hex.length !== 32)
2440
- throw new Error(`session id is not 32 hex characters: ${sessionId}`);
2441
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
2442
- }
2443
- function buildAgentCommand(input) {
2444
- const extra = [...input.extraArgs ?? []];
2445
- if (input.agent === "claude") {
2446
- return [
2447
- "claude",
2448
- "--settings",
2449
- input.claudeSettingsPath,
2450
- "--session-id",
2451
- uuidFromSessionId(input.sessionId),
2452
- ...extra
2453
- ];
2454
- }
2455
- if (input.agent === "gemini") {
2456
- return [
2457
- "gemini",
2458
- "--session-id",
2459
- input.sessionId,
2460
- ...extra
2461
- ];
2462
- }
2463
- if (input.agent === "codex") {
2464
- return ["codex", ...extra];
2465
- }
2466
- return ["omp", "--extension", input.extensionPath, ...extra];
2467
- }
2468
- function buildAgentResumeCommand(input) {
2469
- if (input.agent === "claude") {
2470
- return [
2471
- "claude",
2472
- "--settings",
2473
- input.claudeSettingsPath,
2474
- "--resume",
2475
- uuidFromSessionId(input.sessionId),
2476
- ...input.extraArgs ?? []
2477
- ];
2478
- }
2479
- if (input.agent === "gemini") {
2480
- return ["gemini", "--resume", input.sessionId, ...input.extraArgs ?? []];
2481
- }
2482
- return buildAgentCommand(input);
2483
- }
2484
- function isAgentId(value) {
2485
- return AGENT_IDS.includes(value);
2486
- }
2487
- function supportedAgents() {
2488
- return AGENT_IDS;
2489
- }
2490
- var AGENT_IDS;
2491
- var init_agent_launch = __esm(() => {
2492
- AGENT_IDS = ["omp", "claude", "codex", "gemini"];
2493
- });
2494
-
2495
3291
  // packages/core/src/dotenv-scrub.ts
2496
3292
  function dotenvCandidates(nodeEnv) {
2497
3293
  const mode = nodeEnv === undefined || nodeEnv === "" ? "development" : nodeEnv;
@@ -2543,21 +3339,6 @@ var init_dotenv_scrub = __esm(() => {
2543
3339
  LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
2544
3340
  });
2545
3341
 
2546
- // packages/core/src/duration.ts
2547
- function humanDuration(ms) {
2548
- if (!Number.isFinite(ms) || ms <= 0)
2549
- return "0s";
2550
- const seconds = Math.max(1, Math.round(ms / 1000));
2551
- if (seconds < 60)
2552
- return `${seconds}s`;
2553
- const minutes = Math.round(seconds / 60);
2554
- if (minutes < 60)
2555
- return `${minutes}m`;
2556
- const hours = Math.floor(minutes / 60);
2557
- const rest = minutes % 60;
2558
- return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
2559
- }
2560
-
2561
3342
  // packages/core/src/idle-screen.ts
2562
3343
  function screenHash(screen) {
2563
3344
  const normalised = screen.split(`
@@ -2633,6 +3414,24 @@ function sessionInScope(session, scope, resolve = asGiven) {
2633
3414
  function sessionsInScope(sessions, scope, resolve = asGiven) {
2634
3415
  return sessions.filter((session) => sessionInScope(session, scope, resolve));
2635
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);
3434
+ }
2636
3435
  var asGiven = (target) => target;
2637
3436
  var init_session_scope = () => {};
2638
3437
 
@@ -2665,8 +3464,16 @@ var init_src = __esm(() => {
2665
3464
  init_agent_status();
2666
3465
  init_activity();
2667
3466
  init_claude_hooks();
2668
- init_gemini_onboarding();
3467
+ init_onboarding();
2669
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();
3476
+ init_telegram_discovery();
2670
3477
  init_codex_hooks();
2671
3478
  init_gemini_hooks();
2672
3479
  init_agent_launch();
@@ -2678,14 +3485,14 @@ var init_src = __esm(() => {
2678
3485
  });
2679
3486
 
2680
3487
  // packages/daemon/src/config-store.ts
2681
- import fs4 from "fs";
2682
- import path5 from "path";
3488
+ import fs5 from "fs";
3489
+ import path6 from "path";
2683
3490
  function writeAtomic(target, contents, mode) {
2684
3491
  const tmp = `${target}.tmp`;
2685
- fs4.writeFileSync(tmp, contents, { mode });
2686
- fs4.renameSync(tmp, target);
3492
+ fs5.writeFileSync(tmp, contents, { mode });
3493
+ fs5.renameSync(tmp, target);
2687
3494
  if (process.platform !== "win32")
2688
- fs4.chmodSync(target, mode);
3495
+ fs5.chmodSync(target, mode);
2689
3496
  }
2690
3497
 
2691
3498
  class ConfigStore {
@@ -2701,16 +3508,16 @@ class ConfigStore {
2701
3508
  }
2702
3509
  load() {
2703
3510
  this.loadError = undefined;
2704
- fs4.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
3511
+ fs5.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
2705
3512
  if (process.platform !== "win32") {
2706
3513
  try {
2707
- fs4.chmodSync(this.paths.configDir, DIR_MODE);
3514
+ fs5.chmodSync(this.paths.configDir, DIR_MODE);
2708
3515
  } catch {}
2709
3516
  }
2710
3517
  this.config = structuredClone(DEFAULT_CONFIG);
2711
- if (fs4.existsSync(this.paths.configFile)) {
3518
+ if (fs5.existsSync(this.paths.configFile)) {
2712
3519
  try {
2713
- const parsed = parseConfig(JSON.parse(fs4.readFileSync(this.paths.configFile, "utf8")));
3520
+ const parsed = parseConfig(JSON.parse(fs5.readFileSync(this.paths.configFile, "utf8")));
2714
3521
  if ("errors" in parsed)
2715
3522
  this.loadError = parsed.errors.join("; ");
2716
3523
  else
@@ -2720,10 +3527,10 @@ class ConfigStore {
2720
3527
  }
2721
3528
  }
2722
3529
  this.secrets = {};
2723
- if (fs4.existsSync(this.paths.secretsFile)) {
3530
+ if (fs5.existsSync(this.paths.secretsFile)) {
2724
3531
  this.tightenPermissions(this.paths.secretsFile);
2725
3532
  try {
2726
- const raw = JSON.parse(fs4.readFileSync(this.paths.secretsFile, "utf8"));
3533
+ const raw = JSON.parse(fs5.readFileSync(this.paths.secretsFile, "utf8"));
2727
3534
  if (raw !== null && typeof raw === "object") {
2728
3535
  for (const [key, value] of Object.entries(raw)) {
2729
3536
  if (typeof value === "string")
@@ -2739,8 +3546,8 @@ class ConfigStore {
2739
3546
  if (process.platform === "win32")
2740
3547
  return;
2741
3548
  try {
2742
- if ((fs4.statSync(file).mode & 63) !== 0)
2743
- fs4.chmodSync(file, FILE_MODE);
3549
+ if ((fs5.statSync(file).mode & 63) !== 0)
3550
+ fs5.chmodSync(file, FILE_MODE);
2744
3551
  } catch {}
2745
3552
  }
2746
3553
  get() {
@@ -2748,7 +3555,7 @@ class ConfigStore {
2748
3555
  }
2749
3556
  health() {
2750
3557
  return configHealth({
2751
- fileExists: fs4.existsSync(this.paths.configFile),
3558
+ fileExists: fs5.existsSync(this.paths.configFile),
2752
3559
  parseError: this.loadError,
2753
3560
  config: this.resolved()
2754
3561
  });
@@ -2793,7 +3600,7 @@ class ConfigStore {
2793
3600
  delete this.secrets[dottedPath];
2794
3601
  else
2795
3602
  this.secrets[dottedPath] = value;
2796
- if (!fs4.existsSync(this.paths.configFile)) {
3603
+ if (!fs5.existsSync(this.paths.configFile)) {
2797
3604
  writeAtomic(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
2798
3605
  `, FILE_MODE);
2799
3606
  }
@@ -2823,7 +3630,7 @@ class ConfigStore {
2823
3630
  fingerprint() {
2824
3631
  const stamp = (file) => {
2825
3632
  try {
2826
- return String(fs4.statSync(file).mtimeMs);
3633
+ return String(fs5.statSync(file).mtimeMs);
2827
3634
  } catch {
2828
3635
  return "-";
2829
3636
  }
@@ -2845,8 +3652,8 @@ function applyPath(config, dottedPath, value) {
2845
3652
  function defaultConfigStorePaths(configDir) {
2846
3653
  return {
2847
3654
  configDir,
2848
- configFile: path5.join(configDir, "config.json"),
2849
- secretsFile: path5.join(configDir, "secrets.json")
3655
+ configFile: path6.join(configDir, "config.json"),
3656
+ secretsFile: path6.join(configDir, "secrets.json")
2850
3657
  };
2851
3658
  }
2852
3659
  var ENV_ALIASES, DIR_MODE = 448, FILE_MODE = 384;
@@ -2865,13 +3672,13 @@ var init_config_store = __esm(() => {
2865
3672
  });
2866
3673
 
2867
3674
  // packages/daemon/src/shell.ts
2868
- import fs5 from "fs";
2869
- import path6 from "path";
3675
+ import fs6 from "fs";
3676
+ import path7 from "path";
2870
3677
  function findExecutable(name, env) {
2871
3678
  const isWindows = env.platform === "win32";
2872
- const join = isWindows ? path6.win32.join : path6.posix.join;
3679
+ const join = isWindows ? path7.win32.join : path7.posix.join;
2873
3680
  const separator = isWindows ? ";" : ":";
2874
- 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) : [""];
2875
3682
  for (const rawDir of env.pathEnv.split(separator)) {
2876
3683
  const dir = rawDir.trim().replace(/^"|"$/g, "");
2877
3684
  if (dir.length === 0)
@@ -2889,16 +3696,16 @@ function lookupOnPath(name) {
2889
3696
  platform: process.platform,
2890
3697
  pathEnv: process.env["PATH"] ?? "",
2891
3698
  pathExt: process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD",
2892
- exists: (candidate) => fs5.existsSync(candidate)
3699
+ exists: (candidate) => fs6.existsSync(candidate)
2893
3700
  });
2894
3701
  }
2895
3702
  function resolveShell(kind) {
2896
3703
  if (process.platform !== "win32")
2897
3704
  return resolvePosixShell(kind);
2898
3705
  const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
2899
- const windowsPowershell = path6.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
3706
+ const windowsPowershell = path7.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
2900
3707
  if (kind === "cmd") {
2901
- return { kind: "cmd", exe: path6.join(systemRoot, "System32", "cmd.exe") };
3708
+ return { kind: "cmd", exe: path7.join(systemRoot, "System32", "cmd.exe") };
2902
3709
  }
2903
3710
  if (kind === "powershell") {
2904
3711
  return { kind: "powershell", exe: windowsPowershell };
@@ -2926,7 +3733,7 @@ function resolvePosixShell(kind) {
2926
3733
  candidates.push({ kind: "bash", exe: login });
2927
3734
  candidates.push({ kind: "bash", exe: "/bin/bash" }, { kind: "bash", exe: "/usr/bin/bash" });
2928
3735
  }
2929
- const found = candidates.find((c) => fs5.existsSync(c.exe));
3736
+ const found = candidates.find((c) => fs6.existsSync(c.exe));
2930
3737
  if (found)
2931
3738
  return found;
2932
3739
  return { kind: "sh", exe: "/bin/sh" };
@@ -2971,24 +3778,24 @@ function ipcEndpoint(platform, user, env) {
2971
3778
  const suffix = `-${shortHash(env.runtimeDir)}`;
2972
3779
  return `\\\\.\\pipe\\axterm-${safeUser}${suffix}`;
2973
3780
  }
2974
- return path6.posix.join(env.runtimeDir, "axterm.sock");
3781
+ return path7.posix.join(env.runtimeDir, "axterm.sock");
2975
3782
  }
2976
3783
  var cachedPwsh;
2977
3784
  var init_shell = () => {};
2978
-
2979
- // packages/daemon/src/paths.ts
2980
- import fs6 from "fs";
2981
- import os4 from "os";
2982
- import path7 from "path";
3785
+
3786
+ // packages/daemon/src/paths.ts
3787
+ import fs7 from "fs";
3788
+ import os3 from "os";
3789
+ import path8 from "path";
2983
3790
  function ensureDataDirs() {
2984
3791
  for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir, SCROLLBACK_DIR, LOG_DIR, EXT_DIR]) {
2985
- fs6.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
3792
+ fs7.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
2986
3793
  }
2987
3794
  if (process.platform === "win32")
2988
3795
  return;
2989
3796
  for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir]) {
2990
3797
  try {
2991
- fs6.chmodSync(dir, DIR_MODE2);
3798
+ fs7.chmodSync(dir, DIR_MODE2);
2992
3799
  } catch {}
2993
3800
  }
2994
3801
  }
@@ -2996,14 +3803,14 @@ function restrictFile(file) {
2996
3803
  if (process.platform === "win32")
2997
3804
  return;
2998
3805
  try {
2999
- fs6.chmodSync(file, 384);
3806
+ fs7.chmodSync(file, 384);
3000
3807
  } catch {}
3001
3808
  }
3002
3809
  function log(message) {
3003
3810
  const line = `${new Date().toISOString()} ${message}
3004
3811
  `;
3005
3812
  try {
3006
- fs6.appendFileSync(LOG_FILE, line);
3813
+ fs7.appendFileSync(LOG_FILE, line);
3007
3814
  } catch {}
3008
3815
  }
3009
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;
@@ -3020,15 +3827,15 @@ var init_paths = __esm(() => {
3020
3827
  DB_PATH = PATHS.dbFile;
3021
3828
  RUNTIME_FILE2 = PATHS.runtimeFile;
3022
3829
  HOOK_ENDPOINT_FILE = PATHS.hookEndpointFile;
3023
- PIPE_PATH = ipcEndpoint(process.platform, os4.userInfo().username, {
3830
+ PIPE_PATH = ipcEndpoint(process.platform, os3.userInfo().username, {
3024
3831
  runtimeDir: PATHS.runtimeDir
3025
3832
  });
3026
- LOG_FILE = path7.join(LOG_DIR, "daemon.log");
3833
+ LOG_FILE = path8.join(LOG_DIR, "daemon.log");
3027
3834
  });
3028
3835
 
3029
3836
  // packages/daemon/src/store.ts
3030
- import fs7 from "fs";
3031
- import path8 from "path";
3837
+ import fs8 from "fs";
3838
+ import path9 from "path";
3032
3839
  import { Database } from "bun:sqlite";
3033
3840
  function rowToInfo(row) {
3034
3841
  return {
@@ -3059,7 +3866,7 @@ function rowToInfo(row) {
3059
3866
  class Store {
3060
3867
  db;
3061
3868
  constructor(dbPath = DB_PATH) {
3062
- fs7.mkdirSync(path8.dirname(dbPath), { recursive: true });
3869
+ fs8.mkdirSync(path9.dirname(dbPath), { recursive: true });
3063
3870
  this.db = new Database(dbPath, { strict: true });
3064
3871
  this.db.run("PRAGMA journal_mode = WAL");
3065
3872
  this.db.run(`
@@ -3220,11 +4027,11 @@ var init_store = __esm(() => {
3220
4027
  });
3221
4028
 
3222
4029
  // packages/daemon/src/config-migrate.ts
3223
- import fs8 from "fs";
3224
- import path9 from "path";
4030
+ import fs9 from "fs";
4031
+ import path10 from "path";
3225
4032
  import { Database as Database2 } from "bun:sqlite";
3226
4033
  function checkpointWal(dbFile) {
3227
- if (!fs8.existsSync(dbFile))
4034
+ if (!fs9.existsSync(dbFile))
3228
4035
  return;
3229
4036
  try {
3230
4037
  const db = new Database2(dbFile);
@@ -3234,32 +4041,32 @@ function checkpointWal(dbFile) {
3234
4041
  }
3235
4042
  function relocate(source, target) {
3236
4043
  try {
3237
- fs8.renameSync(source, target);
4044
+ fs9.renameSync(source, target);
3238
4045
  } catch {
3239
- fs8.cpSync(source, target, { recursive: true });
4046
+ fs9.cpSync(source, target, { recursive: true });
3240
4047
  }
3241
4048
  }
3242
4049
  function moveInto(from, to, notes) {
3243
- checkpointWal(path9.join(from, "axterm.db"));
3244
- fs8.mkdirSync(to, { recursive: true });
4050
+ checkpointWal(path10.join(from, "axterm.db"));
4051
+ fs9.mkdirSync(to, { recursive: true });
3245
4052
  for (const entry of STATE_ENTRIES) {
3246
- const source = path9.join(from, entry);
3247
- const target = path9.join(to, entry);
3248
- if (!fs8.existsSync(source))
4053
+ const source = path10.join(from, entry);
4054
+ const target = path10.join(to, entry);
4055
+ if (!fs9.existsSync(source))
3249
4056
  continue;
3250
- if (!fs8.existsSync(target)) {
4057
+ if (!fs9.existsSync(target)) {
3251
4058
  relocate(source, target);
3252
4059
  notes.push(`moved ${entry}`);
3253
4060
  continue;
3254
4061
  }
3255
- if (!fs8.statSync(source).isDirectory() || !fs8.statSync(target).isDirectory())
4062
+ if (!fs9.statSync(source).isDirectory() || !fs9.statSync(target).isDirectory())
3256
4063
  continue;
3257
4064
  let moved = 0;
3258
- for (const child of fs8.readdirSync(source)) {
3259
- const childTarget = path9.join(target, child);
3260
- if (fs8.existsSync(childTarget))
4065
+ for (const child of fs9.readdirSync(source)) {
4066
+ const childTarget = path10.join(target, child);
4067
+ if (fs9.existsSync(childTarget))
3261
4068
  continue;
3262
- relocate(path9.join(source, child), childTarget);
4069
+ relocate(path10.join(source, child), childTarget);
3263
4070
  moved += 1;
3264
4071
  }
3265
4072
  if (moved > 0)
@@ -3269,29 +4076,29 @@ function moveInto(from, to, notes) {
3269
4076
  function migrateLegacyConfig(input) {
3270
4077
  const notes = [];
3271
4078
  const storePaths = defaultConfigStorePaths(input.configDir);
3272
- if (fs8.existsSync(storePaths.configFile))
4079
+ if (fs9.existsSync(storePaths.configFile))
3273
4080
  return { migrated: false, notes };
3274
- 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")));
3275
4082
  if (configSource !== undefined) {
3276
- fs8.mkdirSync(input.configDir, { recursive: true });
3277
- fs8.copyFileSync(path9.join(configSource, "config.json"), storePaths.configFile);
3278
- const secrets = path9.join(configSource, "secrets.json");
3279
- if (fs8.existsSync(secrets)) {
3280
- 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);
3281
4088
  try {
3282
- fs8.chmodSync(storePaths.secretsFile, 384);
4089
+ fs9.chmodSync(storePaths.secretsFile, 384);
3283
4090
  } catch {}
3284
4091
  }
3285
- 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:
3286
4093
  ${input.configDir}
3287
4094
  ` + `These files are no longer used. secrets.json still holds your bot token \u2014 delete this directory once you are satisfied.
3288
4095
  `);
3289
4096
  notes.push(`adopted configuration from ${configSource}`);
3290
4097
  }
3291
- 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")));
3292
4099
  if (legacy !== undefined) {
3293
4100
  moveInto(legacy, input.stateDir, notes);
3294
- 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:
3295
4102
  ${input.stateDir}
3296
4103
  and configuration in:
3297
4104
  ${input.configDir}
@@ -3300,8 +4107,8 @@ and configuration in:
3300
4107
  }
3301
4108
  if (configSource !== undefined)
3302
4109
  return { migrated: true, ...legacy ? { from: legacy } : {}, notes };
3303
- const dbFile = path9.join(input.stateDir, "axterm.db");
3304
- if (!fs8.existsSync(dbFile))
4110
+ const dbFile = path10.join(input.stateDir, "axterm.db");
4111
+ if (!fs9.existsSync(dbFile))
3305
4112
  return { migrated: notes.length > 0, ...legacy ? { from: legacy } : {}, notes };
3306
4113
  const store = new Store(dbFile);
3307
4114
  const machine2 = store.getConfig("axterm_machine");
@@ -3346,6 +4153,7 @@ var axterm_agent_status_default = `// @axterm-managed-omp-extension\r
3346
4153
  // completion) follow Orca's production orca-agent-status.ts, reviewed in\r
3347
4154
  // docs/design/terminal-wrapper-omp-telegram.md \xA75.\r
3348
4155
  import fs from "node:fs";\r
4156
+ import path from "node:path";\r
3349
4157
  \r
3350
4158
  const HOOK_POST_TIMEOUT_MS = 1000;\r
3351
4159
  const MAX_ASSISTANT_TEXT = 4000;\r
@@ -3541,11 +4349,75 @@ function formatAnswers(params: unknown, answers: AskAnswerWire[]): Record<string
3541
4349
  }\r
3542
4350
  \r
3543
4351
  export default function axtermAgentStatus(pi: PiLike): void {\r
3544
- // 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
3545
4357
  const owner = process.env["AXTERM_STATUS_OWNED"];\r
3546
4358
  const selfPid = String(process.pid);\r
3547
4359
  if (owner && owner !== selfPid) return;\r
3548
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
3549
4421
  \r
3550
4422
  /* ---------------- endpoint re-sourcing (env-freeze fix) ---------------- */\r
3551
4423
  let warnedBadEndpoint = false;\r
@@ -3774,6 +4646,12 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3774
4646
  const say = (text: string, isError = false) => ({ content: [{ type: "text" as const, text }], isError });\r
3775
4647
  const { path: filePath, caption } = (params ?? {}) as { path?: string; caption?: string };\r
3776
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
3777
4655
  const coords = resolveCoords();\r
3778
4656
  const sessionId = process.env["AXTERM_SESSION_ID"];\r
3779
4657
  if (!coords.port || !coords.token || !sessionId) {\r
@@ -3786,7 +4664,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3786
4664
  body: JSON.stringify({\r
3787
4665
  sessionId,\r
3788
4666
  launchToken: process.env["AXTERM_LAUNCH_TOKEN"] ?? "",\r
3789
- path: filePath,\r
4667
+ path: resolved,\r
3790
4668
  caption,\r
3791
4669
  }),\r
3792
4670
  });\r
@@ -3810,7 +4688,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3810
4688
  return { status: "no-channel" };\r
3811
4689
  }\r
3812
4690
  try {\r
3813
- 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
3814
4692
  method: "POST",\r
3815
4693
  headers: { "content-type": "application/json", "x-axterm-hook-token": coords.token },\r
3816
4694
  body: JSON.stringify({\r
@@ -3833,7 +4711,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3833
4711
  const sessionId = process.env["AXTERM_SESSION_ID"];\r
3834
4712
  if (!coords.port || !coords.token || !sessionId) return;\r
3835
4713
  try {\r
3836
- 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
3837
4715
  method: "POST",\r
3838
4716
  headers: { "content-type": "application/json", "x-axterm-hook-token": coords.token },\r
3839
4717
  body: JSON.stringify({ sessionId, launchToken: process.env["AXTERM_LAUNCH_TOKEN"] ?? "", askId }),\r
@@ -3845,6 +4723,8 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3845
4723
  \r
3846
4724
  /* ------------------------------ handlers ------------------------------ */\r
3847
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
3848
4728
  const manager = (ctx as { sessionManager?: Record<string, unknown> } | undefined)?.sessionManager;\r
3849
4729
  const probe = (names: string[]): unknown => {\r
3850
4730
  for (const name of names) {\r
@@ -3867,7 +4747,16 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3867
4747
  });\r
3868
4748
  });\r
3869
4749
  \r
3870
- 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
3871
4760
  post("before_agent_start", { prompt: (event as { prompt?: string } | undefined)?.prompt ?? "" });\r
3872
4761
  });\r
3873
4762
  \r
@@ -3912,7 +4801,10 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3912
4801
  recheckMs = Math.min(recheckMs * 2, IDLE_RECHECK_MAX_MS);\r
3913
4802
  }\r
3914
4803
  \r
3915
- 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
3916
4808
  clearPendingCheck();\r
3917
4809
  endReported = false;\r
3918
4810
  post("agent_start");\r
@@ -3997,20 +4889,29 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
3997
4889
  });\r
3998
4890
  });\r
3999
4891
  \r
4000
- 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
4001
4896
  const message = (event as { message?: unknown } | undefined)?.message;\r
4002
4897
  const text = extractAssistantText(message);\r
4003
4898
  if (!text) return;\r
4004
4899
  post("message_end", { role: "assistant", text });\r
4005
4900
  });\r
4006
4901
  \r
4007
- 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
4008
4906
  settledSupported = true;\r
4009
4907
  clearPendingCheck();\r
4010
4908
  postEndOnce();\r
4011
4909
  });\r
4012
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
4013
4913
  pi.on("agent_end", (_event, ctx) => {\r
4914
+ if (!isLeadSession(ctx)) return;\r
4014
4915
  if (settledSupported) return;\r
4015
4916
  const c = ctx as { isIdle?: () => boolean } | undefined;\r
4016
4917
  if (!c || typeof c.isIdle !== "function") {\r
@@ -4045,13 +4946,13 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
4045
4946
  var init_axterm_agent_status = () => {};
4046
4947
 
4047
4948
  // packages/daemon/src/ext-install.ts
4048
- import fs9 from "fs";
4049
- import path10 from "path";
4949
+ import fs10 from "fs";
4950
+ import path11 from "path";
4050
4951
  function installExtensionAsset(targetDir) {
4051
- fs9.mkdirSync(targetDir, { recursive: true });
4052
- 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");
4053
4954
  const js = new Bun.Transpiler({ loader: "ts" }).transformSync(extensionSource);
4054
- fs9.writeFileSync(target, MANAGED_HEADER + js);
4955
+ fs10.writeFileSync(target, MANAGED_HEADER + js);
4055
4956
  return target;
4056
4957
  }
4057
4958
  var extensionSource, MANAGED_HEADER = `// @axterm-managed-omp-extension
@@ -4064,8 +4965,8 @@ var init_ext_install = __esm(() => {
4064
4965
 
4065
4966
  // packages/daemon/src/workspace-local.ts
4066
4967
  import { spawn as spawn2 } from "child_process";
4067
- import fs10 from "fs/promises";
4068
- import path11 from "path";
4968
+ import fs11 from "fs/promises";
4969
+ import path12 from "path";
4069
4970
  function isAbsent(error) {
4070
4971
  const code = error?.code;
4071
4972
  return code === "ENOENT" || code === "ENOTDIR";
@@ -4078,10 +4979,10 @@ class LocalWorkspace {
4078
4979
  this.stagingDir = stagingDir;
4079
4980
  }
4080
4981
  async list(dir) {
4081
- const dirents = await fs10.readdir(dir, { withFileTypes: true });
4982
+ const dirents = await fs11.readdir(dir, { withFileTypes: true });
4082
4983
  const stats = await Promise.all(dirents.map(async (dirent) => {
4083
4984
  try {
4084
- return await fs10.lstat(path11.join(dir, dirent.name));
4985
+ return await fs11.lstat(path12.join(dir, dirent.name));
4085
4986
  } catch (error) {
4086
4987
  if (isAbsent(error))
4087
4988
  return;
@@ -4105,7 +5006,7 @@ class LocalWorkspace {
4105
5006
  }
4106
5007
  async stat(target) {
4107
5008
  try {
4108
- const stat = await fs10.stat(target);
5009
+ const stat = await fs11.stat(target);
4109
5010
  const kind = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "other";
4110
5011
  return { kind, size: stat.size, mtimeMs: stat.mtimeMs };
4111
5012
  } catch (error) {
@@ -4116,8 +5017,8 @@ class LocalWorkspace {
4116
5017
  }
4117
5018
  async read(file, limit) {
4118
5019
  if (limit === undefined)
4119
- return await fs10.readFile(file);
4120
- const handle = await fs10.open(file, "r");
5020
+ return await fs11.readFile(file);
5021
+ const handle = await fs11.open(file, "r");
4121
5022
  try {
4122
5023
  const buffer = Buffer.allocUnsafe(Math.min(limit, (await handle.stat()).size));
4123
5024
  let filled = 0;
@@ -4162,9 +5063,9 @@ var init_workspace_local = () => {};
4162
5063
 
4163
5064
  // packages/daemon/src/hook-host.ts
4164
5065
  import { randomBytes } from "crypto";
4165
- import fs11 from "fs";
5066
+ import fs12 from "fs";
4166
5067
  import http from "http";
4167
- import path12 from "path";
5068
+ import path13 from "path";
4168
5069
  function header(req, name) {
4169
5070
  const value = req.headers[name];
4170
5071
  return Array.isArray(value) ? value[0] : value;
@@ -4188,7 +5089,7 @@ class HookHost {
4188
5089
  constructor(options) {
4189
5090
  this.endpointFile = options.endpointFile;
4190
5091
  this.codexApprovalHoldMs = options.codexApprovalHoldMs ?? CODEX_APPROVAL_HOLD_MS;
4191
- 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")));
4192
5093
  }
4193
5094
  start() {
4194
5095
  const server = http.createServer((req, res) => this.handle(req, res));
@@ -4204,12 +5105,12 @@ class HookHost {
4204
5105
  return promise;
4205
5106
  }
4206
5107
  writeEndpointFile(port) {
4207
- fs11.mkdirSync(path12.dirname(this.endpointFile), { recursive: true });
5108
+ fs12.mkdirSync(path13.dirname(this.endpointFile), { recursive: true });
4208
5109
  const tmp = `${this.endpointFile}.tmp`;
4209
- fs11.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
5110
+ fs12.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
4210
5111
  AXTERM_HOOK_TOKEN=${this.token}
4211
5112
  `);
4212
- fs11.renameSync(tmp, this.endpointFile);
5113
+ fs12.renameSync(tmp, this.endpointFile);
4213
5114
  }
4214
5115
  registerSession(sessionId, launchToken, onEvent) {
4215
5116
  this.sessions.set(sessionId, { launchToken, onEvent });
@@ -4247,8 +5148,8 @@ AXTERM_HOOK_TOKEN=${this.token}
4247
5148
  }
4248
5149
  handle(req, res) {
4249
5150
  const isEvent = req.url === "/hook/omp";
4250
- const isAsk = req.url === "/hook/omp/ask";
4251
- const isAskCancel = req.url === "/hook/omp/ask-cancel";
5151
+ const isAsk = req.url === "/hook/ask";
5152
+ const isAskCancel = req.url === "/hook/ask-cancel";
4252
5153
  const isAttach = req.url === "/hook/attach";
4253
5154
  const isClaude = req.url === "/hook/claude";
4254
5155
  const isCodex = req.url === "/hook/codex";
@@ -4484,7 +5385,7 @@ var init_hook_host = __esm(() => {
4484
5385
  });
4485
5386
 
4486
5387
  // packages/daemon/src/rpc-server.ts
4487
- import fs12 from "fs";
5388
+ import fs13 from "fs";
4488
5389
  import net2 from "net";
4489
5390
  function writeFrame(socket, frame) {
4490
5391
  if (!socket.destroyed)
@@ -4669,10 +5570,12 @@ function startRpcServer(options) {
4669
5570
  }
4670
5571
  case "config.set": {
4671
5572
  const dottedPath = p["path"];
4672
- if (isSecretPath(dottedPath))
4673
- config.setSecret(dottedPath, p["value"]);
4674
- else {
4675
- 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);
4676
5579
  if ("error" in next2)
4677
5580
  throw new Error(next2.error);
4678
5581
  config.update(() => next2.config);
@@ -4700,10 +5603,14 @@ function startRpcServer(options) {
4700
5603
  }
4701
5604
  case "telegram.doctor":
4702
5605
  return telegram.doctor();
5606
+ case "telegram.discover":
5607
+ return telegram.discover(p["botToken"]);
4703
5608
  case "telegram.status":
4704
5609
  return telegram.status();
4705
5610
  case "doctor.report":
4706
5611
  return { checks: manager.doctorReport() };
5612
+ case "quota.report":
5613
+ return { checks: manager.quotaReport() };
4707
5614
  case "daemon.stop":
4708
5615
  setTimeout(onStopRequested, 50);
4709
5616
  return {};
@@ -4714,7 +5621,7 @@ function startRpcServer(options) {
4714
5621
  });
4715
5622
  if (process.platform !== "win32") {
4716
5623
  try {
4717
- fs12.unlinkSync(pipePath);
5624
+ fs13.unlinkSync(pipePath);
4718
5625
  } catch {}
4719
5626
  }
4720
5627
  server.listen(pipePath, () => {
@@ -4729,17 +5636,17 @@ var init_rpc_server = __esm(() => {
4729
5636
  });
4730
5637
 
4731
5638
  // packages/daemon/src/codex-home.ts
4732
- import fs13 from "fs";
4733
- import path13 from "path";
5639
+ import fs14 from "fs";
5640
+ import path14 from "path";
4734
5641
  function codexTrustKeyPath(home) {
4735
5642
  const resolved = (() => {
4736
5643
  try {
4737
- return fs13.realpathSync.native(home);
5644
+ return fs14.realpathSync.native(home);
4738
5645
  } catch {
4739
- return path13.resolve(home);
5646
+ return path14.resolve(home);
4740
5647
  }
4741
5648
  })();
4742
- return path13.join(resolved, "hooks.json");
5649
+ return path14.join(resolved, "hooks.json");
4743
5650
  }
4744
5651
  var init_codex_home = () => {};
4745
5652
 
@@ -4802,8 +5709,8 @@ function slugifyBranch(branch) {
4802
5709
  var MAX_SLUG_LENGTH = 60;
4803
5710
 
4804
5711
  // packages/daemon/src/git.ts
4805
- import fs14 from "fs";
4806
- import path14 from "path";
5712
+ import fs15 from "fs";
5713
+ import path15 from "path";
4807
5714
  async function git(ws, cwd, args) {
4808
5715
  const run = await ws.exec(["git", ...args], cwd);
4809
5716
  return {
@@ -4820,15 +5727,15 @@ async function gitOrThrow(ws, cwd, args) {
4820
5727
  return result.stdout;
4821
5728
  }
4822
5729
  function canonicalPath(target) {
4823
- let resolved = path14.resolve(target);
5730
+ let resolved = path15.resolve(target);
4824
5731
  try {
4825
- resolved = fs14.realpathSync.native(resolved);
5732
+ resolved = fs15.realpathSync.native(resolved);
4826
5733
  } catch {}
4827
5734
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
4828
5735
  }
4829
5736
  async function resolveRepoRoot(ws, cwd) {
4830
5737
  const result = await git(ws, cwd, ["rev-parse", "--show-toplevel"]);
4831
- return result.ok ? path14.resolve(result.stdout) : undefined;
5738
+ return result.ok ? path15.resolve(result.stdout) : undefined;
4832
5739
  }
4833
5740
  async function currentBranch(ws, cwd) {
4834
5741
  const result = await git(ws, cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -4837,8 +5744,8 @@ async function currentBranch(ws, cwd) {
4837
5744
  async function listWorktrees(ws, repoRoot) {
4838
5745
  return parseWorktreeList(await gitOrThrow(ws, repoRoot, ["worktree", "list", "--porcelain"]));
4839
5746
  }
4840
- function worktreePathFor(repoRoot, branch, root = path14.join(STATE_DIR, "worktrees")) {
4841
- 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));
4842
5749
  }
4843
5750
  async function createWorktree(ws, options) {
4844
5751
  const { repoRoot, branch, base } = options;
@@ -4846,7 +5753,7 @@ async function createWorktree(ws, options) {
4846
5753
  const existing = (await listWorktrees(ws, repoRoot)).find((w) => canonicalPath(w.path) === canonicalPath(target));
4847
5754
  if (existing)
4848
5755
  return { path: target, branch, createdBranch: false };
4849
- fs14.mkdirSync(path14.dirname(target), { recursive: true });
5756
+ fs15.mkdirSync(path15.dirname(target), { recursive: true });
4850
5757
  const exists = (await git(ws, repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])).ok;
4851
5758
  const args = exists ? ["worktree", "add", target, branch] : ["worktree", "add", "-b", branch, target, ...base !== undefined ? [base] : []];
4852
5759
  await gitOrThrow(ws, repoRoot, args);
@@ -4899,7 +5806,7 @@ async function diffStat(ws, worktreePath, base) {
4899
5806
  const untracked = await untrackedFiles(ws, worktreePath);
4900
5807
  let newLines = 0;
4901
5808
  for (const file of untracked)
4902
- newLines += await countLines(ws, path14.join(worktreePath, file));
5809
+ newLines += await countLines(ws, path15.join(worktreePath, file));
4903
5810
  return {
4904
5811
  filesChanged: a.filesChanged + b.filesChanged + untracked.length,
4905
5812
  insertions: a.insertions + b.insertions + newLines,
@@ -4975,7 +5882,7 @@ var init_injector = __esm(() => {
4975
5882
 
4976
5883
  // packages/daemon/src/occupancy.ts
4977
5884
  import { spawn as spawn3 } from "child_process";
4978
- import fs15 from "fs/promises";
5885
+ import fs16 from "fs/promises";
4979
5886
  function shellOccupied(shellPid) {
4980
5887
  return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
4981
5888
  }
@@ -5020,72 +5927,474 @@ async function occupiedFromSnapshot(shellPid) {
5020
5927
  }
5021
5928
  async function childrenViaProc(pid) {
5022
5929
  try {
5023
- const children = await fs15.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
5930
+ const children = await fs16.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
5024
5931
  return children.trim().length > 0;
5025
5932
  } catch {
5026
5933
  try {
5027
- await fs15.access(`/proc/${pid}`);
5934
+ await fs16.access(`/proc/${pid}`);
5028
5935
  return;
5029
5936
  } catch {
5030
5937
  return false;
5031
5938
  }
5032
- }
5033
- }
5034
- async function childrenViaPgrep(pid) {
5035
- const result = await capture("pgrep", ["-P", String(pid)]);
5036
- if (result === undefined)
5037
- return;
5038
- if (result.code === 1)
5039
- return false;
5040
- if (result.code !== 0)
5939
+ }
5940
+ }
5941
+ async function childrenViaPgrep(pid) {
5942
+ const result = await capture("pgrep", ["-P", String(pid)]);
5943
+ if (result === undefined)
5944
+ return;
5945
+ if (result.code === 1)
5946
+ return false;
5947
+ if (result.code !== 0)
5948
+ return;
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
+ }
5041
6322
  return;
5042
- return result.stdout.trim().length > 0;
6323
+ }
6324
+ try {
6325
+ process.kill(-pid, "SIGKILL");
6326
+ } catch {}
5043
6327
  }
5044
- async function processTable() {
5045
- const result = await capture(resolveShell("powershell").exe, [
5046
- "-NoLogo",
5047
- "-NoProfile",
5048
- "-NonInteractive",
5049
- "-Command",
5050
- "(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 ' '`
5051
- ]);
5052
- if (result === undefined || result.code !== 0)
5053
- return;
5054
- const startedAt = new Map;
5055
- const newestChild = new Map;
5056
- for (const row of result.stdout.split(/\s+/)) {
5057
- const [rawPid, rawParent, rawStarted] = row.split(",");
5058
- const pid = Number(rawPid);
5059
- const parent = Number(rawParent);
5060
- const started = Number(rawStarted);
5061
- if (!Number.isInteger(pid) || !Number.isInteger(parent) || !Number.isInteger(started))
5062
- continue;
5063
- if (pid === result.pid)
5064
- continue;
5065
- startedAt.set(pid, started);
5066
- const newest = newestChild.get(parent);
5067
- if (newest === undefined || started > newest)
5068
- 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 {}
5069
6377
  }
5070
- return startedAt.size > 0 ? { startedAt, newestChild } : undefined;
5071
6378
  }
5072
- function capture(exe, argv) {
5073
- const { promise, resolve } = Promise.withResolvers();
5074
- const child = spawn3(exe, argv, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
5075
- let stdout3 = "";
5076
- child.stdout.setEncoding("utf8");
5077
- child.stdout.on("data", (chunk) => {
5078
- 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
+ }
5079
6392
  });
5080
- child.once("error", () => resolve(undefined));
5081
- child.once("close", (code) => resolve({ code, stdout: stdout3, pid: child.pid }));
5082
- return promise;
6393
+ handle = new BunPtyHandle(proc, path17.basename(options.exe).toLowerCase());
6394
+ proc.exited.then((code) => handle?.finish(code ?? null));
6395
+ return handle;
5083
6396
  }
5084
- var OCCUPANCY_CACHE_MS = 1500, perPid, snapshot;
5085
- var init_occupancy = __esm(() => {
5086
- init_shell();
5087
- perPid = new Map;
5088
- });
6397
+ var init_pty = () => {};
5089
6398
 
5090
6399
  // node_modules/.bun/@xterm+headless@5.5.0/node_modules/@xterm/headless/lib-headless/xterm-headless.js
5091
6400
  var require_xterm_headless = __commonJS((exports) => {
@@ -9379,154 +10688,46 @@ var require_addon_serialize = __commonJS((exports, module) => {
9379
10688
  this._htmlContent += "<html><body><!--StartFragment--><pre>";
9380
10689
  let r2 = "#000000", o2 = "#ffffff";
9381
10690
  this._options.includeGlobalBackground && (r2 = this._terminal.options.theme?.foreground ?? "#ffffff", o2 = this._terminal.options.theme?.background ?? "#000000");
9382
- const i2 = [];
9383
- 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(" ") + "'>";
9384
- }
9385
- _afterSerialize() {
9386
- this._htmlContent += "</div>", this._htmlContent += "</pre><!--EndFragment--></body></html>";
9387
- }
9388
- _rowEnd(e3, t4) {
9389
- this._htmlContent += "<div><span>" + this._currentRow + "</span></div>", this._currentRow = "";
9390
- }
9391
- _getHexColor(e3, t4) {
9392
- const s2 = t4 ? e3.getFgColor() : e3.getBgColor();
9393
- 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;
9394
- }
9395
- _diffStyle(e3, t4) {
9396
- const s2 = [], r2 = !n(e3, t4), o2 = !l(e3, t4), i2 = !a(e3, t4);
9397
- if (r2 || o2 || i2) {
9398
- const t5 = this._getHexColor(e3, true);
9399
- t5 && s2.push("color: " + t5 + ";");
9400
- const r3 = this._getHexColor(e3, false);
9401
- 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;
9402
- }
9403
- }
9404
- _nextCell(e3, t4, s2, r2) {
9405
- if (e3.getWidth() === 0)
9406
- return;
9407
- const o2 = e3.getChars() === "", i2 = this._diffStyle(e3, t4);
9408
- i2 && (this._currentRow += i2.length === 0 ? "</span><span>" : "</span><span style='" + i2.join(" ") + "'>"), this._currentRow += o2 ? " " : e3.getChars();
9409
- }
9410
- _serializeString() {
9411
- return this._htmlContent;
9412
- }
9413
- }
9414
- e2.HTMLSerializeHandler = h;
9415
- })(), r;
9416
- })());
9417
- });
9418
-
9419
- // packages/daemon/src/pty.ts
9420
- import { spawnSync as spawnSync2 } from "child_process";
9421
- import path15 from "path";
9422
- import { StringDecoder } from "string_decoder";
9423
-
9424
- class Fanout {
9425
- listeners = new Set;
9426
- backlog = [];
9427
- add(listener) {
9428
- this.listeners.add(listener);
9429
- for (const value of this.backlog)
9430
- listener(value);
9431
- }
9432
- emit(value) {
9433
- if (this.listeners.size === 0)
9434
- this.backlog.push(value);
9435
- else
9436
- for (const listener of this.listeners)
9437
- listener(value);
9438
- }
9439
- }
9440
- function killTree(pid, exeName) {
9441
- if (process.platform === "win32") {
9442
- const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
9443
- encoding: "utf8",
9444
- windowsHide: true
9445
- });
9446
- const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
9447
- if (image === exeName) {
9448
- spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
9449
- }
9450
- return;
9451
- }
9452
- try {
9453
- process.kill(-pid, "SIGKILL");
9454
- } catch {}
9455
- }
9456
-
9457
- class BunPtyHandle {
9458
- proc;
9459
- exeName;
9460
- data = new Fanout;
9461
- exit = new Fanout;
9462
- decoder = new StringDecoder("utf8");
9463
- exited = false;
9464
- constructor(proc, exeName) {
9465
- this.proc = proc;
9466
- this.exeName = exeName;
9467
- }
9468
- get pid() {
9469
- return this.proc.pid;
9470
- }
9471
- push(bytes) {
9472
- const text = this.decoder.write(Buffer.from(bytes));
9473
- if (text.length > 0)
9474
- this.data.emit(text);
9475
- }
9476
- finish(code) {
9477
- this.exited = true;
9478
- const tail = this.decoder.end();
9479
- if (tail.length > 0)
9480
- this.data.emit(tail);
9481
- try {
9482
- this.proc.terminal.close();
9483
- } catch {}
9484
- this.exit.emit(code);
9485
- }
9486
- onData(listener) {
9487
- this.data.add(listener);
9488
- }
9489
- onExit(listener) {
9490
- this.exit.add(listener);
9491
- }
9492
- write(data) {
9493
- this.proc.terminal.write(data);
9494
- }
9495
- resize(cols, rows) {
9496
- this.proc.terminal.resize(cols, rows);
9497
- }
9498
- kill() {
9499
- if (this.exited)
9500
- return;
9501
- killTree(this.proc.pid, this.exeName);
9502
- try {
9503
- this.proc.kill();
9504
- } catch {}
9505
- }
9506
- }
9507
- function spawnPty(options) {
9508
- let handle;
9509
- const proc = Bun.spawn([options.exe, ...options.argv], {
9510
- cwd: options.cwd,
9511
- env: { ...options.env, TERM: options.term },
9512
- terminal: {
9513
- cols: options.cols,
9514
- rows: options.rows,
9515
- name: options.term,
9516
- data(_terminal, bytes) {
9517
- handle?.push(bytes);
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
+ }
9518
10722
  }
9519
- }
9520
- });
9521
- handle = new BunPtyHandle(proc, path15.basename(options.exe).toLowerCase());
9522
- proc.exited.then((code) => handle?.finish(code ?? null));
9523
- return handle;
9524
- }
9525
- var init_pty = () => {};
10723
+ e2.HTMLSerializeHandler = h;
10724
+ })(), r;
10725
+ })());
10726
+ });
9526
10727
 
9527
10728
  // packages/daemon/src/pty-session.ts
9528
- import fs16 from "fs";
9529
- import path16 from "path";
10729
+ import fs18 from "fs";
10730
+ import path18 from "path";
9530
10731
 
9531
10732
  class PtySession {
9532
10733
  info;
@@ -9642,10 +10843,10 @@ class PtySession {
9642
10843
  }
9643
10844
  saveScrollback() {
9644
10845
  try {
9645
- const file = path16.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
10846
+ const file = path18.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
9646
10847
  const tmp = `${file}.tmp`;
9647
- fs16.writeFileSync(tmp, this.serializeBuffer(), "utf8");
9648
- fs16.renameSync(tmp, file);
10848
+ fs18.writeFileSync(tmp, this.serializeBuffer(), "utf8");
10849
+ fs18.renameSync(tmp, file);
9649
10850
  } catch (err) {
9650
10851
  log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
9651
10852
  }
@@ -9667,9 +10868,9 @@ var init_pty_session = __esm(() => {
9667
10868
 
9668
10869
  // packages/daemon/src/session-manager.ts
9669
10870
  import { randomBytes as randomBytes2, randomUUID } from "crypto";
9670
- import fs17 from "fs";
10871
+ import fs19 from "fs";
9671
10872
  import os5 from "os";
9672
- import path17 from "path";
10873
+ import path19 from "path";
9673
10874
  function unresolvableAgent(commandOverride, agentCommand, lookup) {
9674
10875
  if (commandOverride !== undefined)
9675
10876
  return;
@@ -9712,6 +10913,7 @@ async function sendableNow(runtime, info, probe, now) {
9712
10913
  class SessionManager {
9713
10914
  store;
9714
10915
  hooks;
10916
+ configuredDefaults;
9715
10917
  live = new Map;
9716
10918
  agentRuntime = new Map;
9717
10919
  injector = new Injector;
@@ -9719,13 +10921,18 @@ class SessionManager {
9719
10921
  eventListeners = new Set;
9720
10922
  rawHookListeners = new Set;
9721
10923
  workspace = new LocalWorkspace(MATERIALISED_DIR);
9722
- constructor(store, hooks) {
10924
+ constructor(store, hooks, configuredDefaults = () => ({})) {
9723
10925
  this.store = store;
9724
10926
  this.hooks = hooks;
10927
+ this.configuredDefaults = configuredDefaults;
9725
10928
  const stale = store.markStaleRunningAsExited();
9726
10929
  if (stale > 0)
9727
10930
  log(`boot: marked ${stale} stale session(s) as exited`);
10931
+ this.sessionWatch = setInterval(() => this.tickSessions(), SESSION_TICK_MS);
10932
+ this.sessionWatch.unref();
9728
10933
  }
10934
+ sessionWatch;
10935
+ codexServers = new Map;
9729
10936
  workspaceFor(_sessionId) {
9730
10937
  return this.workspace;
9731
10938
  }
@@ -9802,8 +11009,8 @@ class SessionManager {
9802
11009
  };
9803
11010
  }
9804
11011
  async create(params) {
9805
- const requestedCwd = params.cwd ? path17.resolve(params.cwd) : os5.homedir();
9806
- if (!fs17.existsSync(requestedCwd)) {
11012
+ const requestedCwd = params.cwd ? path19.resolve(params.cwd) : os5.homedir();
11013
+ if (!fs19.existsSync(requestedCwd)) {
9807
11014
  throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
9808
11015
  }
9809
11016
  let worktree;
@@ -9820,17 +11027,29 @@ class SessionManager {
9820
11027
  log(`worktree ${created.createdBranch ? "created" : "reused"} ${created.branch} at ${created.path}`);
9821
11028
  }
9822
11029
  const cwd = worktree?.path ?? requestedCwd;
9823
- const shell = resolveShell(params.shell);
9824
- 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";
9825
11033
  if (!isAgentId(agent))
9826
11034
  throw new AxtermError("err.agent.unknown", { agent, supported: supportedAgents().join(", ") });
9827
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;
9828
11045
  const agentCommand = params.commandOverride ?? buildAgentCommand({
9829
11046
  agent,
9830
11047
  sessionId: id,
9831
11048
  extensionPath: this.hooks.extensionPath,
9832
11049
  claudeSettingsPath: this.claudeSettingsPath(id),
9833
- ...params.agentArgs !== undefined ? { extraArgs: params.agentArgs } : {}
11050
+ ...mcpConfigPath === undefined ? {} : { mcpConfigPath },
11051
+ ...agent === "codex" ? { mcpLaunch: this.mcpServerLaunch(), mcpContextPath: mcpContext } : {},
11052
+ ...extraArgs.length > 0 ? { extraArgs } : {}
9834
11053
  });
9835
11054
  const missing = unresolvableAgent(params.commandOverride, agentCommand, lookupOnPath);
9836
11055
  if (missing !== undefined)
@@ -9838,7 +11057,7 @@ class SessionManager {
9838
11057
  const info = {
9839
11058
  id,
9840
11059
  shortId: id.slice(0, 6),
9841
- name: params.name ?? worktree?.branch ?? path17.basename(cwd),
11060
+ name: params.name ?? worktree?.branch ?? path19.basename(cwd),
9842
11061
  cwd,
9843
11062
  shell: shell.kind,
9844
11063
  agentCommand,
@@ -9848,7 +11067,8 @@ class SessionManager {
9848
11067
  createdAt: Date.now(),
9849
11068
  ...worktree !== undefined ? { worktreePath: worktree.path, branch: worktree.branch, repoRoot: worktree.repoRoot } : {}
9850
11069
  };
9851
- 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);
9852
11072
  this.store.insertSession(info);
9853
11073
  log(`session ${info.shortId} created shell=${shell.kind} cmd=${agentCommand.join(" ") || "(shell only)"}`);
9854
11074
  this.emit({ t: "session.created", info });
@@ -9862,37 +11082,82 @@ class SessionManager {
9862
11082
  launchToken
9863
11083
  });
9864
11084
  const file = this.claudeSettingsPath(id);
9865
- fs17.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
11085
+ fs19.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
9866
11086
  }
9867
- writeGeminiSettings() {
11087
+ writeGeminiSettings(sessionId) {
9868
11088
  try {
9869
11089
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "gemini" });
9870
- 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
+ });
9871
11096
  const file = this.geminiSettingsPath();
9872
11097
  const tmp = `${file}.tmp`;
9873
- fs17.writeFileSync(tmp, JSON.stringify(settings, null, 2));
9874
- fs17.renameSync(tmp, file);
11098
+ fs19.writeFileSync(tmp, JSON.stringify(settings, null, 2));
11099
+ fs19.renameSync(tmp, file);
9875
11100
  } catch (err) {
9876
11101
  log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
9877
11102
  }
9878
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
+ }
9879
11144
  ensureCodexHome() {
9880
- const home = path17.join(STATE_DIR, "codex-home");
9881
- fs17.mkdirSync(home, { recursive: true, mode: 448 });
11145
+ const home = path19.join(STATE_DIR, "codex-home");
11146
+ fs19.mkdirSync(home, { recursive: true, mode: 448 });
9882
11147
  this.linkCodexAuth(home);
9883
11148
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
9884
11149
  const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
9885
11150
  const hooksPath = codexTrustKeyPath(home);
9886
11151
  const tmp = `${hooksPath}.tmp`;
9887
- fs17.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
9888
- fs17.renameSync(tmp, hooksPath);
11152
+ fs19.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
11153
+ fs19.renameSync(tmp, hooksPath);
9889
11154
  const entries = codexHookTrust(hooksPath, hooksFile);
9890
- const configPath = path17.join(home, "config.toml");
9891
- 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") : "";
9892
11157
  const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
9893
11158
  if (missing.length > 0) {
9894
11159
  const block = codexTrustStateToml(missing);
9895
- fs17.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
11160
+ fs19.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
9896
11161
  `) ? `
9897
11162
  ${block}` : block);
9898
11163
  }
@@ -9905,18 +11170,18 @@ ${block}` : block);
9905
11170
  return home;
9906
11171
  }
9907
11172
  linkCodexAuth(home) {
9908
- const target = path17.join(home, "auth.json");
9909
- if (fs17.existsSync(target))
11173
+ const target = path19.join(home, "auth.json");
11174
+ if (fs19.existsSync(target))
9910
11175
  return;
9911
- const operatorHome = process.env["CODEX_HOME"] ?? path17.join(os5.homedir(), ".codex");
9912
- const source = path17.join(operatorHome, "auth.json");
9913
- 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))
9914
11179
  throw new AxtermError("err.codex.noAuth", { authFile: source });
9915
11180
  try {
9916
- fs17.symlinkSync(source, target, "file");
11181
+ fs19.symlinkSync(source, target, "file");
9917
11182
  } catch {
9918
11183
  try {
9919
- fs17.linkSync(source, target);
11184
+ fs19.linkSync(source, target);
9920
11185
  } catch (err) {
9921
11186
  throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
9922
11187
  }
@@ -9924,37 +11189,45 @@ ${block}` : block);
9924
11189
  }
9925
11190
  doctorReport() {
9926
11191
  const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
9927
- const home = path17.join(STATE_DIR, "codex-home");
9928
- const operatorHome = process.env["CODEX_HOME"] ?? path17.join(os5.homedir(), ".codex");
9929
- 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");
9930
11195
  const geminiSettings = this.geminiSettingsPath();
9931
- const configPath = path17.join(home, "config.toml");
11196
+ const configPath = path19.join(home, "config.toml");
9932
11197
  const read = (file) => {
9933
11198
  try {
9934
- return fs17.existsSync(file) ? fs17.readFileSync(file, "utf8") : undefined;
11199
+ return fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : undefined;
9935
11200
  } catch {
9936
11201
  return;
9937
11202
  }
9938
11203
  };
9939
11204
  return [
9940
- ...evaluateBridgeDoctor({ command: bridge.command, exists: fs17.existsSync(bridge.command) }),
11205
+ ...evaluateBridgeDoctor({ command: bridge.command, exists: fs19.existsSync(bridge.command) }),
9941
11206
  ...evaluateOmpDoctor({
9942
11207
  binary: lookupOnPath("omp"),
9943
11208
  extensionPath: this.hooks.extensionPath,
9944
- extensionExists: fs17.existsSync(this.hooks.extensionPath)
11209
+ extensionExists: fs19.existsSync(this.hooks.extensionPath)
9945
11210
  }),
9946
11211
  ...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
9947
11212
  ...evaluateCodexDoctor({
9948
11213
  binary: lookupOnPath("codex"),
9949
11214
  authSourcePath: authSource,
9950
- authSourceExists: fs17.existsSync(authSource),
9951
- homeExists: fs17.existsSync(home),
11215
+ authSourceExists: fs19.existsSync(authSource),
11216
+ homeExists: fs19.existsSync(home),
9952
11217
  expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
9953
11218
  configToml: read(configPath)
9954
11219
  }),
9955
11220
  ...evaluateGeminiDoctor({ binary: lookupOnPath("gemini"), settingsJson: read(geminiSettings) })
9956
11221
  ];
9957
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
+ }
9958
11231
  sawOutput = new Set;
9959
11232
  idleTimers = new Map;
9960
11233
  lastCaptured = new Map;
@@ -9981,7 +11254,7 @@ ${block}` : block);
9981
11254
  if (!session || !runtime)
9982
11255
  return;
9983
11256
  const found = detectPrompt(session.screenText(4));
9984
- 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;
9985
11258
  if (!found && !dialog) {
9986
11259
  if (this.pendingPrompt.delete(id))
9987
11260
  this.emit({ t: "choice.resolved", sessionId: id, seq: runtime.choiceSeq });
@@ -10064,51 +11337,89 @@ ${block}` : block);
10064
11337
  }
10065
11338
  writeScreenFile(idOrPrefix, lines) {
10066
11339
  const session = this.resolveLive(idOrPrefix);
10067
- const file = path17.join(path17.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
10068
- 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 });
10069
11342
  return file;
10070
11343
  }
10071
11344
  removeScreenFile(id) {
10072
11345
  try {
10073
- 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 });
10074
11347
  } catch {}
10075
11348
  }
10076
11349
  async stageInboundFile(idOrPrefix, suggestedName, bytes) {
10077
11350
  const info = this.resolveLive(idOrPrefix).info;
10078
- const file = path17.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
10079
- await fs17.promises.mkdir(path17.dirname(file), { recursive: true, mode: 448 });
10080
- await fs17.promises.writeFile(file, bytes, { mode: 384 });
10081
- 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)`);
10082
11355
  return file;
10083
11356
  }
10084
11357
  removeStagedFiles(id) {
10085
11358
  try {
10086
- fs17.rmSync(path17.join(MATERIALISED_DIR, id), { recursive: true, force: true });
11359
+ fs19.rmSync(path19.join(MATERIALISED_DIR, id), { recursive: true, force: true });
10087
11360
  } catch {}
10088
11361
  }
10089
11362
  removeClaudeSettings(id) {
10090
11363
  try {
10091
- 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);
10092
11369
  } catch {}
10093
11370
  }
10094
11371
  claudeSettingsPath(id) {
10095
- 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`);
10096
11405
  }
10097
11406
  geminiSettingsPath() {
10098
- return path17.join(path17.dirname(this.hooks.endpointFile), "gemini-defaults.json");
11407
+ return path19.join(path19.dirname(this.hooks.endpointFile), "gemini-defaults.json");
10099
11408
  }
10100
- launch(info, shell, agentCommand) {
11409
+ launch(info, shell, agentCommand, appServerUrl) {
10101
11410
  const id = info.id;
10102
11411
  const launchToken = randomBytes2(16).toString("hex");
10103
11412
  const agentId = agentIdFromCommand(info.agentCommand);
10104
11413
  if (agentId === "claude")
10105
11414
  this.writeClaudeSettings(id, launchToken);
11415
+ if (agentId !== "omp")
11416
+ this.writeMcpContext(id, launchToken);
10106
11417
  if (agentId === "gemini")
10107
- this.writeGeminiSettings();
10108
- const codexHome = agentId === "codex" ? this.ensureCodexHome() : undefined;
11418
+ this.writeGeminiSettings(id);
11419
+ const codexHome2 = agentId === "codex" ? this.ensureCodexHome() : undefined;
10109
11420
  const session = new PtySession(info, {
10110
11421
  exe: shell.exe,
10111
- argv: buildShellArgv(shell, agentCommand),
11422
+ argv: buildShellArgv(shell, withAppServer(agentCommand, appServerUrl)),
10112
11423
  cwd: info.cwd,
10113
11424
  cols: info.cols,
10114
11425
  rows: info.rows,
@@ -10119,8 +11430,15 @@ ${block}` : block);
10119
11430
  AXTERM_HOOK_PORT: String(this.hooks.port),
10120
11431
  AXTERM_HOOK_TOKEN: this.hooks.host.token,
10121
11432
  AXTERM_HOOK_ENDPOINT: this.hooks.endpointFile,
10122
- ...agentId === "gemini" ? { GEMINI_CLI_SYSTEM_DEFAULTS_PATH: this.geminiSettingsPath() } : {},
10123
- ...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 } : {}
10124
11442
  }
10125
11443
  });
10126
11444
  this.agentRuntime.set(id, { agentExited: false, choiceSeq: 0, dialogOpen: false });
@@ -10242,6 +11560,9 @@ ${block}` : block);
10242
11560
  this.emit({ t: "chat.message", sessionId: id, role: "assistant", text: result.assistantText });
10243
11561
  }
10244
11562
  if (payload.event === "before_agent_start") {
11563
+ const runtime2 = this.agentRuntime.get(id);
11564
+ if (runtime2 !== undefined)
11565
+ runtime2.telemetryReported = false;
10245
11566
  const prompt = (payload.prompt ?? "").trim();
10246
11567
  if (prompt.length > 0 && !this.echoGuard.consume(id, prompt, now)) {
10247
11568
  this.emit({ t: "chat.message", sessionId: id, role: "user", text: prompt });
@@ -10318,14 +11639,10 @@ ${block}` : block);
10318
11639
  return this.allSessions();
10319
11640
  }
10320
11641
  allSessions() {
10321
- const liveIds = new Set(this.live.keys());
10322
- return [
10323
- ...[...this.live.values()].map((s) => s.info),
10324
- ...this.store.listSessions().filter((s) => !liveIds.has(s.id))
10325
- ];
11642
+ return sessionsByRecency([...this.live.values()].map((s) => s.info), this.store.listSessions());
10326
11643
  }
10327
11644
  resumeNamed(stored, agent) {
10328
- if (!fs17.existsSync(stored.cwd))
11645
+ if (!fs19.existsSync(stored.cwd))
10329
11646
  throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
10330
11647
  const agentCommand = buildAgentResumeCommand({
10331
11648
  agent,
@@ -10344,7 +11661,7 @@ ${block}` : block);
10344
11661
  this.emit({ t: "session.created", info });
10345
11662
  return info;
10346
11663
  }
10347
- resume(idOrPrefix) {
11664
+ async resume(idOrPrefix) {
10348
11665
  const stored = this.store.findByPrefix(idOrPrefix);
10349
11666
  if (!stored)
10350
11667
  throw new AxtermError("err.session.unknown", { id: idOrPrefix });
@@ -10365,7 +11682,7 @@ ${block}` : block);
10365
11682
  if (agentCommand === undefined) {
10366
11683
  throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
10367
11684
  }
10368
- if (!fs17.existsSync(stored.cwd))
11685
+ if (!fs19.existsSync(stored.cwd))
10369
11686
  throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
10370
11687
  const info = { ...stored, agentCommand, state: "running" };
10371
11688
  delete info.exitCode;
@@ -10373,7 +11690,8 @@ ${block}` : block);
10373
11690
  delete info.agentState;
10374
11691
  delete info.agentStateSource;
10375
11692
  const shell = resolveShell(info.shell);
10376
- 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);
10377
11695
  this.store.markResumed(info.id, agentCommand, info.ptyPid);
10378
11696
  log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
10379
11697
  this.emit({ t: "session.created", info });
@@ -10438,11 +11756,86 @@ ${block}` : block);
10438
11756
  removeScrollback(id) {
10439
11757
  for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
10440
11758
  try {
10441
- fs17.rmSync(path17.join(SCROLLBACK_DIR, file), { force: true });
11759
+ fs19.rmSync(path19.join(SCROLLBACK_DIR, file), { force: true });
10442
11760
  } catch {}
10443
11761
  }
10444
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
+ }
10445
11835
  shutdownAll() {
11836
+ clearInterval(this.sessionWatch);
11837
+ for (const id of [...this.codexServers.keys()])
11838
+ this.stopCodexAppServer(id);
10446
11839
  for (const session of this.live.values()) {
10447
11840
  session.saveScrollback();
10448
11841
  session.kill();
@@ -10451,7 +11844,7 @@ ${block}` : block);
10451
11844
  this.agentRuntime.clear();
10452
11845
  }
10453
11846
  }
10454
- 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;
10455
11848
  var init_session_manager = __esm(() => {
10456
11849
  init_src();
10457
11850
  init_codex_home();
@@ -10459,11 +11852,14 @@ var init_session_manager = __esm(() => {
10459
11852
  init_git();
10460
11853
  init_injector();
10461
11854
  init_occupancy();
11855
+ init_agent_store();
11856
+ init_codex_app_server();
11857
+ init_pty();
10462
11858
  init_paths();
10463
11859
  init_pty_session();
10464
11860
  init_shell();
10465
11861
  init_workspace_local();
10466
- MATERIALISED_DIR = path17.join(RUNTIME_DIR, "materialised");
11862
+ MATERIALISED_DIR = path19.join(RUNTIME_DIR, "materialised");
10467
11863
  PROMPT_OPTIONS = {
10468
11864
  hostKey: [{ label: "yes" }, { label: "no" }],
10469
11865
  password: [],
@@ -10612,11 +12008,11 @@ var init_acknowledge = __esm(() => {
10612
12008
  });
10613
12009
 
10614
12010
  // packages/channel-telegram/src/attach.ts
10615
- import fs18 from "fs";
10616
- import path18 from "path";
12011
+ import fs20 from "fs";
12012
+ import path20 from "path";
10617
12013
  function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
10618
- const name = path18.basename(filePath);
10619
- const bytes = fs18.statSync(filePath).size;
12014
+ const name = path20.basename(filePath);
12015
+ const bytes = fs20.statSync(filePath).size;
10620
12016
  const plan = planAttachment({ name, bytes }, limits);
10621
12017
  if (plan.kind === "rejected")
10622
12018
  throw new Error(plan.reason);
@@ -10626,7 +12022,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
10626
12022
  form.append("message_thread_id", String(topicId));
10627
12023
  if (caption !== undefined && caption.length > 0)
10628
12024
  form.append("caption", caption);
10629
- form.append(field, new Blob([fs18.readFileSync(filePath)]), name);
12025
+ form.append(field, new Blob([fs20.readFileSync(filePath)]), name);
10630
12026
  return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
10631
12027
  }
10632
12028
  function isPhotoRejection(error) {
@@ -10634,13 +12030,13 @@ function isPhotoRejection(error) {
10634
12030
  return /IMAGE_PROCESS_FAILED|PHOTO_INVALID_DIMENSIONS|PHOTO_EXT_INVALID|PHOTO_SAVE_FILE_INVALID/i.test(message);
10635
12031
  }
10636
12032
  function asDocumentUpload(filePath, chatId, topicId, caption) {
10637
- const name = path18.basename(filePath);
12033
+ const name = path20.basename(filePath);
10638
12034
  const form = new FormData;
10639
12035
  form.append("chat_id", String(chatId));
10640
12036
  form.append("message_thread_id", String(topicId));
10641
12037
  if (caption !== undefined && caption.length > 0)
10642
12038
  form.append("caption", caption);
10643
- form.append("document", new Blob([fs18.readFileSync(filePath)]), name);
12039
+ form.append("document", new Blob([fs20.readFileSync(filePath)]), name);
10644
12040
  return { method: "sendDocument", field: "document", form };
10645
12041
  }
10646
12042
  var TELEGRAM_LIMITS;
@@ -11407,6 +12803,14 @@ class TelegramChannel {
11407
12803
  await this.sendToSession(sessionId, message);
11408
12804
  }
11409
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
+ }
11410
12814
  async onAgentStatus(sessionId, state, toolName) {
11411
12815
  const binding = this.control.findBindingBySession(sessionId);
11412
12816
  if (!binding)
@@ -11527,7 +12931,7 @@ ${code(prompt.line)}`,
11527
12931
  });
11528
12932
  }
11529
12933
  awaitingSecret = new Map;
11530
- async onChoiceRequest(sessionId, seq, questions, askId, onboarding = false) {
12934
+ async onChoiceRequest(sessionId, seq, questions, askId, onboarding2 = false) {
11531
12935
  const binding = this.control.findBindingBySession(sessionId);
11532
12936
  if (!binding || questions.length === 0)
11533
12937
  return;
@@ -11544,7 +12948,7 @@ ${code(prompt.line)}`,
11544
12948
  answers: [],
11545
12949
  picked: [],
11546
12950
  ...askId !== undefined ? { askId } : {},
11547
- ...onboarding ? { onboarding: true } : {}
12951
+ ...onboarding2 ? { onboarding: true } : {}
11548
12952
  });
11549
12953
  await this.renderChoiceStep(sessionId);
11550
12954
  }
@@ -11609,7 +13013,7 @@ ${code(prompt.line)}`,
11609
13013
  }
11610
13014
  async onAgentError(sessionId, kind, text, retryMs) {
11611
13015
  const reason = clipText(text, AGENT_ERROR_CHARS);
11612
- 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 });
11613
13017
  await this.sendToSession(sessionId, message);
11614
13018
  }
11615
13019
  async onChatMessage(sessionId, role, text) {
@@ -11773,8 +13177,8 @@ ${code(prompt.line)}`,
11773
13177
  throw new Error("this Telegram client cannot download files");
11774
13178
  const files = [];
11775
13179
  for (const item of batch.items) {
11776
- const path19 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
11777
- 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 });
11778
13182
  }
11779
13183
  await this.control.prompt(batch.sessionId, buildAttachmentPrompt(files, batch.caption));
11780
13184
  this.log(`staged ${files.length} inbound file(s) for ${batch.sessionId}`);
@@ -11800,6 +13204,7 @@ ${code(prompt.line)}`,
11800
13204
  }
11801
13205
  async handleCommand(text, binding, msg) {
11802
13206
  const [rawCmd = "", ...args] = text.split(/\s+/);
13207
+ const rawTail = text.slice(rawCmd.length);
11803
13208
  const cmd = rawCmd.replace(/@\w+$/, "");
11804
13209
  if (binding) {
11805
13210
  switch (cmd) {
@@ -11809,9 +13214,34 @@ ${code(prompt.line)}`,
11809
13214
  case "/kill":
11810
13215
  this.control.kill(binding.sessionId);
11811
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
+ }
11812
13242
  case "/resume": {
11813
13243
  try {
11814
- const info = this.control.resume(binding.sessionId);
13244
+ const info = await this.control.resume(binding.sessionId);
11815
13245
  await this.sendToSession(binding.sessionId, tHtml("tg.resume.done", { command: info.agentCommand.join(" ") }));
11816
13246
  } catch (err) {
11817
13247
  await this.sendToSession(binding.sessionId, tHtml("tg.resume.failed", { error: localizeError(err) }));
@@ -11826,6 +13256,10 @@ ${code(prompt.line)}`,
11826
13256
  case "/diff":
11827
13257
  await this.postDiffSummary(binding.sessionId);
11828
13258
  return;
13259
+ case "/quota": {
13260
+ await this.sendToSession(binding.sessionId, this.quotaText());
13261
+ return;
13262
+ }
11829
13263
  default:
11830
13264
  await this.sendToSession(binding.sessionId, helpText());
11831
13265
  return;
@@ -12024,8 +13458,8 @@ ${code(prompt.line)}`,
12024
13458
  return;
12025
13459
  }
12026
13460
  if (flow.askId === undefined) {
12027
- const keys = agentProfile(agentIdFromCommand(session.agentCommand)).approvalKeys;
12028
- 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));
12029
13463
  }
12030
13464
  flow.answers.push({ questionIndex: flow.cursor, selected: [label] });
12031
13465
  flow.cursor += 1;
@@ -12103,7 +13537,7 @@ ${code(prompt.line)}`,
12103
13537
  }
12104
13538
  }
12105
13539
  }
12106
- 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(`
12107
13541
  `), ALBUM_DEBOUNCE_MS = 2000, UNKNOWN_SIZE = 1, REFUSAL_KEYS, megabytes2 = (bytes) => `${Math.round(bytes / (1024 * 1024))} MB`;
12108
13542
  var init_adapter = __esm(() => {
12109
13543
  init_src();
@@ -12170,6 +13604,7 @@ class TelegramService {
12170
13604
  return {
12171
13605
  ...telegram?.botToken !== undefined ? { botToken: telegram.botToken } : {},
12172
13606
  ...telegram?.groupId !== undefined ? { groupId: telegram.groupId } : {},
13607
+ ...telegram?.enabled !== undefined ? { enabled: telegram.enabled } : {},
12173
13608
  ...telegram?.allowUserIds?.[0] !== undefined ? { allowUserId: telegram.allowUserIds[0] } : {},
12174
13609
  ...machine2 !== undefined ? { machine: machine2 } : {}
12175
13610
  };
@@ -12204,13 +13639,27 @@ class TelegramService {
12204
13639
  hasToken: s.botToken !== undefined
12205
13640
  };
12206
13641
  }
13642
+ async discover(botToken) {
13643
+ if (this.channel !== undefined)
13644
+ throw new AxtermError("err.telegram.discoverWhileRunning");
13645
+ const token = botToken ?? this.readSettings().botToken;
13646
+ if (!token)
13647
+ throw new AxtermError("err.telegram.noToken");
13648
+ const api = new FetchTelegramApi(token);
13649
+ const updates = await api.call("getUpdates", { timeout: 0 });
13650
+ return discoverTelegramTargets(updates);
13651
+ }
12207
13652
  start() {
12208
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
+ }
12209
13659
  if (!s.botToken || s.groupId === undefined || s.allowUserId === undefined) {
12210
13660
  log("telegram: not configured, channel not started");
12211
13661
  return false;
12212
13662
  }
12213
- this.stop();
12214
13663
  const api = new FetchTelegramApi(s.botToken);
12215
13664
  const config = {
12216
13665
  groupId: s.groupId,
@@ -12252,6 +13701,7 @@ class TelegramService {
12252
13701
  answerShellPrompt: (id, seq, text) => this.manager.answerShellPrompt(id, seq, text),
12253
13702
  answerOnboarding: (id, seq, optionIndex) => this.manager.answerOnboardingChoice(id, seq, optionIndex),
12254
13703
  diff: (id) => this.manager.sessionDiff(id),
13704
+ quota: () => this.manager.quotaReport(),
12255
13705
  saveBinding: (b) => this.store.saveBinding(b),
12256
13706
  findBindingBySession: (id) => this.store.findBindingBySession(id),
12257
13707
  findBindingByTopic: (chatId, topicId) => this.store.findBindingByTopic(chatId, topicId),
@@ -12358,8 +13808,8 @@ var init_telegram_service = __esm(() => {
12358
13808
  // packages/daemon/src/index.ts
12359
13809
  var exports_src = {};
12360
13810
  import { randomBytes as randomBytes3 } from "crypto";
12361
- import fs19 from "fs";
12362
- import net3 from "net";
13811
+ import fs21 from "fs";
13812
+ import net4 from "net";
12363
13813
  function isPidAlive(pid) {
12364
13814
  try {
12365
13815
  process.kill(pid, 0);
@@ -12370,9 +13820,9 @@ function isPidAlive(pid) {
12370
13820
  }
12371
13821
  async function main() {
12372
13822
  ensureDataDirs();
12373
- if (fs19.existsSync(RUNTIME_FILE2)) {
13823
+ if (fs21.existsSync(RUNTIME_FILE2)) {
12374
13824
  try {
12375
- const existing = JSON.parse(fs19.readFileSync(RUNTIME_FILE2, "utf8"));
13825
+ const existing = JSON.parse(fs21.readFileSync(RUNTIME_FILE2, "utf8"));
12376
13826
  if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
12377
13827
  log(`daemon already running (pid ${existing.pid}); exiting`);
12378
13828
  process.exit(0);
@@ -12395,7 +13845,7 @@ async function main() {
12395
13845
  version: productVersion(),
12396
13846
  startedAt: Date.now()
12397
13847
  };
12398
- fs19.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
13848
+ fs21.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
12399
13849
  restrictFile(RUNTIME_FILE2);
12400
13850
  const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
12401
13851
  if (config.loadError !== undefined)
@@ -12413,7 +13863,7 @@ async function main() {
12413
13863
  port: hookPort,
12414
13864
  endpointFile: HOOK_ENDPOINT_FILE,
12415
13865
  extensionPath
12416
- });
13866
+ }, () => config.resolved().defaults ?? {});
12417
13867
  const telegram = new TelegramService(config, store, manager);
12418
13868
  hookHost.onAttachment(async (sessionId, filePath, caption) => {
12419
13869
  await telegram.sendAttachment(sessionId, filePath, caption);
@@ -12447,9 +13897,9 @@ async function main() {
12447
13897
  hookHost.stop();
12448
13898
  store.close();
12449
13899
  try {
12450
- const current = JSON.parse(fs19.readFileSync(RUNTIME_FILE2, "utf8"));
13900
+ const current = JSON.parse(fs21.readFileSync(RUNTIME_FILE2, "utf8"));
12451
13901
  if (current.pid === process.pid)
12452
- fs19.rmSync(RUNTIME_FILE2);
13902
+ fs21.rmSync(RUNTIME_FILE2);
12453
13903
  } catch {}
12454
13904
  process.exit(0);
12455
13905
  };
@@ -12482,14 +13932,14 @@ var init_src3 = __esm(() => {
12482
13932
  init_session_manager();
12483
13933
  init_telegram_service();
12484
13934
  init_store();
12485
- net3.setDefaultAutoSelectFamilyAttemptTimeout(3000);
13935
+ net4.setDefaultAutoSelectFamilyAttemptTimeout(3000);
12486
13936
  main();
12487
13937
  });
12488
13938
 
12489
13939
  // packages/cli/src/index.ts
12490
13940
  init_src();
12491
- import fs20 from "fs";
12492
- import path19 from "path";
13941
+ import fs22 from "fs";
13942
+ import path21 from "path";
12493
13943
 
12494
13944
  // packages/cli/src/attach.ts
12495
13945
  import fs from "fs";
@@ -13129,18 +14579,289 @@ async function relayHookEvent(agent) {
13129
14579
  } catch {}
13130
14580
  }
13131
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
+
13132
14854
  // packages/cli/src/update.ts
13133
14855
  init_src();
13134
14856
  import { spawnSync } from "child_process";
13135
14857
  import { createHash as createHash2 } from "crypto";
13136
- import fs3 from "fs";
13137
- import os3 from "os";
13138
- import path4 from "path";
14858
+ import fs4 from "fs";
14859
+ import path5 from "path";
13139
14860
  import { stdin as stdin2, stdout as stdout2 } from "process";
13140
14861
  // packages/cli/package.json
13141
14862
  var package_default = {
13142
14863
  name: "@layorixinc/axterm",
13143
- version: "0.1.0",
14864
+ version: "0.4.0",
13144
14865
  type: "module",
13145
14866
  bin: {
13146
14867
  axterm: "dist/cli.js"
@@ -13214,7 +14935,7 @@ function assetName(platform, arch, isMusl) {
13214
14935
  function usesMusl() {
13215
14936
  if (process.platform !== "linux")
13216
14937
  return false;
13217
- if (fs3.existsSync("/etc/alpine-release"))
14938
+ if (fs4.existsSync("/etc/alpine-release"))
13218
14939
  return true;
13219
14940
  const ldd = spawnSync("ldd", ["--version"], { encoding: "utf8" });
13220
14941
  return `${ldd.stdout ?? ""}${ldd.stderr ?? ""}`.toLowerCase().includes("musl");
@@ -13228,7 +14949,7 @@ function firstLine(command, args) {
13228
14949
  function detectOrigin(execPath) {
13229
14950
  const resolved = (() => {
13230
14951
  try {
13231
- return fs3.realpathSync(execPath);
14952
+ return fs4.realpathSync(execPath);
13232
14953
  } catch {
13233
14954
  return execPath;
13234
14955
  }
@@ -13240,7 +14961,7 @@ function detectOrigin(execPath) {
13240
14961
  brewPrefix: firstLine("brew", ["--prefix"]),
13241
14962
  misePaths: misePaths === undefined ? [] : [misePaths],
13242
14963
  bunBin: firstLine("bun", ["pm", "bin", "-g"]),
13243
- npmBin: firstLine("npm", ["bin", "-g"])
14964
+ npmBin: npmGlobalBin()
13244
14965
  });
13245
14966
  }
13246
14967
  var MANAGER_COMMAND = {
@@ -13249,8 +14970,11 @@ var MANAGER_COMMAND = {
13249
14970
  bun: (v) => `bun install -g ${PACKAGE}@${v}`,
13250
14971
  npm: (v) => `npm install -g ${PACKAGE}@${v}`
13251
14972
  };
13252
- function runningFromSource() {
13253
- 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");
13254
14978
  }
13255
14979
  async function download(url, into) {
13256
14980
  const response = await fetch(url, { signal: AbortSignal.timeout(600000) });
@@ -13267,7 +14991,7 @@ function checksumFor(sums, asset) {
13267
14991
  return;
13268
14992
  }
13269
14993
  function sha256(file) {
13270
- return createHash2("sha256").update(fs3.readFileSync(file)).digest("hex");
14994
+ return createHash2("sha256").update(fs4.readFileSync(file)).digest("hex");
13271
14995
  }
13272
14996
  function ask2(question) {
13273
14997
  if (stdin2.isTTY !== true)
@@ -13296,21 +15020,26 @@ async function runUpdate(client, current, options) {
13296
15020
  console.log(t("cli.update.available", { latest: latest.version, current }));
13297
15021
  if (options.checkOnly)
13298
15022
  return;
13299
- if (runningFromSource()) {
15023
+ const lane = classifyRuntimeLane(Bun.main);
15024
+ if (lane === "checkout") {
13300
15025
  console.log(t("cli.update.notBinary"));
13301
15026
  return;
13302
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
+ }
13303
15033
  const origin = detectOrigin(process.execPath);
13304
15034
  if (origin !== "binary") {
13305
- const command = MANAGER_COMMAND[origin](latest.version);
13306
15035
  console.log(t("cli.update.viaManager", { manager: origin }));
13307
- console.log(t("cli.update.managerHint", { command }));
15036
+ console.log(t("cli.update.managerHint", { command: MANAGER_COMMAND[origin](latest.version) }));
13308
15037
  return;
13309
15038
  }
13310
15039
  const asset = assetName(process.platform, process.arch, usesMusl());
13311
15040
  const base = `${RELEASES}/v${latest.version}`;
13312
- const staging = fs3.mkdtempSync(path4.join(os3.tmpdir(), "axterm-update-"));
13313
- const downloaded = path4.join(staging, asset);
15041
+ const staging = fs4.mkdtempSync(`${process.execPath}.update-`);
15042
+ const downloaded = path5.join(staging, asset);
13314
15043
  try {
13315
15044
  console.log(t("cli.update.downloading", { asset }));
13316
15045
  await download(`${base}/${asset}`, downloaded);
@@ -13323,7 +15052,7 @@ async function runUpdate(client, current, options) {
13323
15052
  return;
13324
15053
  }
13325
15054
  if (process.platform !== "win32")
13326
- fs3.chmodSync(downloaded, 493);
15055
+ fs4.chmodSync(downloaded, 493);
13327
15056
  const reported = spawnSync(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
13328
15057
  if (reported !== latest.version) {
13329
15058
  console.error(t("cli.update.wrongVersion", { got: reported ?? "nothing", want: latest.version }));
@@ -13342,6 +15071,9 @@ async function runUpdate(client, current, options) {
13342
15071
  return;
13343
15072
  }
13344
15073
  }
15074
+ console.log(t("cli.update.swapping"));
15075
+ applySwap(process.execPath, downloaded);
15076
+ console.log(t("cli.update.done", { version: latest.version }));
13345
15077
  if (plan.stopDaemon) {
13346
15078
  console.log(t("cli.update.stopping"));
13347
15079
  await client.call("daemon.stop").catch(() => {
@@ -13350,9 +15082,6 @@ async function runUpdate(client, current, options) {
13350
15082
  client.close();
13351
15083
  await Bun.sleep(1500);
13352
15084
  }
13353
- console.log(t("cli.update.swapping"));
13354
- applySwap(process.execPath, downloaded);
13355
- console.log(t("cli.update.done", { version: latest.version }));
13356
15085
  if (plan.stale > 0)
13357
15086
  console.log(t("cli.update.staleDaemon", { count: plan.stale }));
13358
15087
  for (const id of plan.resume) {
@@ -13363,7 +15092,7 @@ async function runUpdate(client, current, options) {
13363
15092
  console.log(t("cli.update.restoreFailed", { id, reason: (result.stderr ?? "").trim() || "unknown" }));
13364
15093
  }
13365
15094
  } finally {
13366
- fs3.rmSync(staging, { recursive: true, force: true });
15095
+ fs4.rmSync(staging, { recursive: true, force: true });
13367
15096
  }
13368
15097
  }
13369
15098
  function applySwap(target, downloaded) {
@@ -13372,16 +15101,16 @@ function applySwap(target, downloaded) {
13372
15101
  try {
13373
15102
  for (const step of steps) {
13374
15103
  if (step.op === "chmod")
13375
- fs3.chmodSync(step.from, 493);
15104
+ fs4.chmodSync(step.from, 493);
13376
15105
  else if (step.op === "backup") {
13377
- fs3.renameSync(step.from, step.to);
15106
+ fs4.renameSync(step.from, step.to);
13378
15107
  backup = step.to;
13379
15108
  } else
13380
- fs3.renameSync(step.from, step.to);
15109
+ fs4.renameSync(step.from, step.to);
13381
15110
  }
13382
15111
  } catch (err) {
13383
- if (backup !== undefined && !fs3.existsSync(target))
13384
- fs3.renameSync(backup, target);
15112
+ if (backup !== undefined && !fs4.existsSync(target))
15113
+ fs4.renameSync(backup, target);
13385
15114
  throw err;
13386
15115
  }
13387
15116
  }
@@ -13454,7 +15183,7 @@ function printSessionTable(sessions) {
13454
15183
  console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
13455
15184
  }
13456
15185
  }
13457
- var realPath = (target) => fs20.realpathSync.native(target);
15186
+ var realPath = (target) => fs22.realpathSync.native(target);
13458
15187
  function scopedHere(sessions) {
13459
15188
  return sessionsInScope(sessions, process.cwd(), realPath);
13460
15189
  }
@@ -13464,22 +15193,10 @@ async function exitedIds(client, all) {
13464
15193
  return scoped.filter((s) => s.state === "exited").map((s) => s.id);
13465
15194
  }
13466
15195
  var help = () => t("cli.help");
13467
- var KEY_SEQUENCES = {
13468
- approve: "\r",
13469
- deny: "\x1B[B\r",
13470
- enter: "\r",
13471
- esc: "\x1B",
13472
- up: "\x1B[A",
13473
- down: "\x1B[B",
13474
- left: "\x1B[D",
13475
- right: "\x1B[C",
13476
- tab: "\t",
13477
- space: " "
13478
- };
13479
15196
  function readDotEnv(file = ".env") {
13480
15197
  const out = {};
13481
15198
  try {
13482
- for (const line of fs20.readFileSync(file, "utf8").split(/\r?\n/)) {
15199
+ for (const line of fs22.readFileSync(file, "utf8").split(/\r?\n/)) {
13483
15200
  const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
13484
15201
  if (m)
13485
15202
  out[m[1]] = m[2].trim();
@@ -13490,7 +15207,7 @@ function readDotEnv(file = ".env") {
13490
15207
  function configuredLocale() {
13491
15208
  try {
13492
15209
  const file = resolvePaths(currentPathEnv()).configFile;
13493
- const raw = JSON.parse(fs20.readFileSync(file, "utf8"));
15210
+ const raw = JSON.parse(fs22.readFileSync(file, "utf8"));
13494
15211
  return typeof raw.locale === "string" ? raw.locale : undefined;
13495
15212
  } catch {
13496
15213
  return;
@@ -13507,6 +15224,10 @@ async function main2() {
13507
15224
  await relayHookEvent(argv[1]);
13508
15225
  return;
13509
15226
  }
15227
+ if (isMcpInvocation(argv)) {
15228
+ runMcpServer();
15229
+ return;
15230
+ }
13510
15231
  const [command, ...rest] = argv;
13511
15232
  setLocale(resolveLocale({ configured: configuredLocale(), env: process.env }));
13512
15233
  const { flags, positional, passthrough } = parseArgs(rest);
@@ -13525,9 +15246,9 @@ async function main2() {
13525
15246
  if (health !== undefined && shouldOfferSetup(health, command, flags)) {
13526
15247
  const header2 = health.status === "invalid" ? "wizard.invalid" : health.status === "incomplete" ? "wizard.incomplete" : "wizard.needed";
13527
15248
  console.log(t(header2));
13528
- const detail = describeHealth(health);
13529
- if (detail.length > 0)
13530
- console.log(detail);
15249
+ const detail2 = describeHealth(health);
15250
+ if (detail2.length > 0)
15251
+ console.log(detail2);
13531
15252
  console.log(t("wizard.hint"));
13532
15253
  await runSetup(client, await client.call("config.raw"));
13533
15254
  }
@@ -13540,6 +15261,7 @@ async function main2() {
13540
15261
  name: flags.get("name"),
13541
15262
  shell: flags.get("shell"),
13542
15263
  agent: flags.get("agent"),
15264
+ autoApprove: typeof flags.get("auto-approve") === "boolean" ? flags.get("auto-approve") : undefined,
13543
15265
  agentArgs: passthrough,
13544
15266
  worktreeBranch: flags.get("worktree"),
13545
15267
  worktreeBase: flags.get("base"),
@@ -13568,7 +15290,16 @@ async function main2() {
13568
15290
  break;
13569
15291
  }
13570
15292
  case "attach": {
13571
- 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
+ }
13572
15303
  if (!id)
13573
15304
  throw new Error(t("cli.usage.attach"));
13574
15305
  await attachSession(client, id, flags.get("host-status") === "orca");
@@ -13608,8 +15339,8 @@ async function main2() {
13608
15339
  case "key": {
13609
15340
  const [id, name] = positional;
13610
15341
  if (!id || !name)
13611
- throw new Error(t("cli.usage.key", { keys: Object.keys(KEY_SEQUENCES).join("|") }));
13612
- const seq = KEY_SEQUENCES[name];
15342
+ throw new Error(t("cli.usage.key", { keys: KEY_NAMES.join("|") }));
15343
+ const seq = keySequence(name);
13613
15344
  if (seq === undefined)
13614
15345
  throw new Error(t("cli.key.unknown", { name }));
13615
15346
  await client.call("terminal.write", { id, dataB64: Buffer.from(seq, "utf8").toString("base64") });
@@ -13620,8 +15351,8 @@ async function main2() {
13620
15351
  const [id, indexArg] = positional;
13621
15352
  if (!id || indexArg === undefined)
13622
15353
  throw new Error(t("cli.usage.choose"));
13623
- const keys = buildMenuSelectKeys(Number(indexArg));
13624
- 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") });
13625
15356
  console.log(t("cli.session.selected", { index: indexArg }));
13626
15357
  break;
13627
15358
  }
@@ -13656,9 +15387,10 @@ async function main2() {
13656
15387
  console.log(value === undefined ? "" : String(value));
13657
15388
  } else if (sub === "set") {
13658
15389
  const [, dottedPath, ...rest2] = positional;
13659
- if (!dottedPath || rest2.length === 0)
15390
+ const value = passthrough.length > 0 ? passthrough : rest2.join(" ");
15391
+ if (!dottedPath || value.length === 0)
13660
15392
  throw new Error(t("cli.usage.configSet"));
13661
- await client.call("config.set", { path: dottedPath, value: rest2.join(" ") });
15393
+ await client.call("config.set", { path: dottedPath, value });
13662
15394
  console.log(t("cli.config.set", { path: dottedPath }));
13663
15395
  } else if (sub === "unset") {
13664
15396
  const dottedPath = positional[1];
@@ -13697,8 +15429,8 @@ async function main2() {
13697
15429
  case "project": {
13698
15430
  const sub = positional[0] ?? "list";
13699
15431
  if (sub === "add") {
13700
- const dir = positional[1] ? path19.resolve(positional[1]) : process.cwd();
13701
- 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);
13702
15434
  const projects = await client.call("project.add", {
13703
15435
  alias,
13704
15436
  path: dir
@@ -13757,6 +15489,24 @@ async function main2() {
13757
15489
  }
13758
15490
  break;
13759
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
+ }
13760
15510
  case "worktree": {
13761
15511
  const sub = positional[0] ?? "list";
13762
15512
  if (sub === "list") {
@@ -13913,6 +15663,28 @@ async function main2() {
13913
15663
  console.log(JSON.stringify(await client.call("telegram.doctor"), null, 2));
13914
15664
  } else if (sub === "status") {
13915
15665
  console.log(JSON.stringify(await client.call("telegram.status"), null, 2));
15666
+ } else if (sub === "discover") {
15667
+ const token = flags.get("token");
15668
+ const found = await client.call("telegram.discover", {
15669
+ ...token !== undefined ? { botToken: token } : {}
15670
+ });
15671
+ if (found.chats.length > 0) {
15672
+ console.log(t("cli.telegram.discoverChats"));
15673
+ for (const chat of found.chats) {
15674
+ const marks = [chat.type, chat.isForum ? undefined : t("cli.telegram.topicsOff")].filter((m) => m !== undefined).join(" \u2014 ");
15675
+ console.log(` ${String(chat.id).padEnd(16)} ${(chat.title ?? "").padEnd(24)} ${marks}`);
15676
+ }
15677
+ } else {
15678
+ console.log(t("cli.telegram.discoverNone"));
15679
+ console.log(t("cli.telegram.discoverHint"));
15680
+ }
15681
+ if (found.users.length > 0) {
15682
+ console.log(t("cli.telegram.discoverUsers"));
15683
+ for (const user of found.users) {
15684
+ const label = [user.username !== undefined ? `@${user.username}` : undefined, user.name].filter((part) => part !== undefined).join(" ");
15685
+ console.log(` ${String(user.id).padEnd(16)} ${label}`);
15686
+ }
15687
+ }
13916
15688
  } else {
13917
15689
  throw new Error(t("cli.usage.telegram"));
13918
15690
  }