@layorixinc/axterm 0.7.0 → 0.8.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 +489 -129
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -50,6 +50,11 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
50
50
  function descriptor(surfaceClass, lane, required, defaultEnabled, sizePolicy) {
51
51
  return { class: surfaceClass, lane, required, defaultEnabled, sizePolicy };
52
52
  }
53
+ function progressGlyph(status) {
54
+ if (status === undefined)
55
+ return "\uD83D\uDCCC";
56
+ return PROGRESS_GLYPHS[status] ?? "\uD83D\uDCCC";
57
+ }
53
58
 
54
59
  class SurfaceCommitter {
55
60
  options;
@@ -125,7 +130,7 @@ function remember(entries, key, limit) {
125
130
  if (oldest !== undefined)
126
131
  entries.delete(oldest);
127
132
  }
128
- var SURFACE_KINDS, SURFACE_DESCRIPTORS, OPTIONAL_SURFACE_KINDS;
133
+ var SURFACE_KINDS, SURFACE_DESCRIPTORS, OPTIONAL_SURFACE_KINDS, PROGRESS_GLYPHS, CONVERSATION_CHANGE_CAUSES;
129
134
  var init_agent_surface = __esm(() => {
130
135
  SURFACE_KINDS = [
131
136
  "state.agent",
@@ -200,10 +205,10 @@ var init_agent_surface = __esm(() => {
200
205
  "notice.auth": descriptor("notice", "immediate", true, true, "telemetry"),
201
206
  "notice.warning": descriptor("notice", "digest", false, true, "telemetry"),
202
207
  "notice.info": descriptor("notice", "digest", false, false, "telemetry"),
203
- "context.session": descriptor("context", "snapshot", false, true, "telemetry"),
208
+ "context.session": descriptor("context", "immediate", true, true, "telemetry"),
204
209
  "context.compaction": descriptor("context", "snapshot", false, true, "telemetry"),
205
210
  "context.handoff": descriptor("context", "snapshot", false, true, "telemetry"),
206
- "context.branch": descriptor("context", "digest", false, false, "telemetry"),
211
+ "context.branch": descriptor("context", "immediate", true, true, "telemetry"),
207
212
  "context.tree": descriptor("context", "digest", false, false, "telemetry"),
208
213
  "artifact.image": descriptor("artifact", "artifact", false, false, "channelArtifact"),
209
214
  "artifact.file": descriptor("artifact", "artifact", false, false, "channelArtifact"),
@@ -212,6 +217,15 @@ var init_agent_surface = __esm(() => {
212
217
  "artifact.link": descriptor("artifact", "digest", false, false, "telemetry")
213
218
  };
214
219
  OPTIONAL_SURFACE_KINDS = SURFACE_KINDS.filter((kind) => !SURFACE_DESCRIPTORS[kind].required);
220
+ PROGRESS_GLYPHS = {
221
+ pending: "\u23F3",
222
+ active: "\uD83D\uDD04",
223
+ blocked: "\u26D4",
224
+ done: "\u2705",
225
+ failed: "\u274C",
226
+ skipped: "\u23E9"
227
+ };
228
+ CONVERSATION_CHANGE_CAUSES = ["new", "resume", "fork", "handoff"];
215
229
  });
216
230
 
217
231
  // packages/core/src/menu-keys.ts
@@ -1816,6 +1830,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1816
1830
  "sl.command.unknownKey": 'Unknown key "{name}" \u2014 nothing was sent. Accepted: {keys}',
1817
1831
  "sl.command.keysSent": "\u2328\uFE0F sent: {keys}",
1818
1832
  "sl.command.typed": "\u2328\uFE0F typed without Enter:\n```text\n{screen}\n```",
1833
+ "sl.passthrough.sent": "\u2328\uFE0F typed `/{command}` for the agent. Nothing was submitted \u2014 use the `key` control command with `keys:enter` if it needs Enter:\n```text\n{screen}\n```",
1819
1834
  "sl.command.failed": "Failed: {error}",
1820
1835
  "sl.state.working": "working",
1821
1836
  "sl.state.blocked": "waiting for approval",
@@ -1863,6 +1878,8 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1863
1878
  "tg.agent.retry": "\u23F3 The agent is rate limited and will retry in {wait}: {error}",
1864
1879
  "tg.raw.keysSent": "\u2328\uFE0F sent: {keys}",
1865
1880
  "tg.raw.unknownKey": 'unknown key "{name}" \u2014 nothing was sent. Accepted: {keys}',
1881
+ "tg.passthrough.sent": `sent <code>/{command}</code> to the agent. Nothing was submitted \u2014 use the key buttons or <code>/key enter</code> if it needs Enter:
1882
+ {screen}`,
1866
1883
  "tg.raw.typed": `\u2328\uFE0F typed (not submitted \u2014 /key enter to send):
1867
1884
  <pre>{screen}</pre>`,
1868
1885
  "tg.raw.needText": "usage: /type <text> \u2014 written verbatim, without Enter. Then /key enter.",
@@ -1910,6 +1927,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1910
1927
  "dc.secret.submitted": "\uD83D\uDD10 The secret was submitted and the source message was removed if permissions allowed.",
1911
1928
  "dc.ask.tooMany": "This question has too many options for Discord; answer it in the attached terminal.",
1912
1929
  "dc.ask.resolvedElsewhere": "\u231B This question was answered elsewhere.",
1930
+ "dc.ask.conversationChanged": "\u231B The conversation changed, so this question no longer exists.",
1913
1931
  "dc.command.noProjects": "No projects configured.",
1914
1932
  "dc.command.noSessions": "No sessions.",
1915
1933
  "dc.command.unknownProject": "Unknown project: {project}",
@@ -1947,6 +1965,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
1947
1965
  "dc.command.unknownKey": 'Unknown key "{name}" \u2014 nothing was sent. Accepted: {keys}',
1948
1966
  "dc.command.keysSent": "\u2328\uFE0F sent: {keys}",
1949
1967
  "dc.command.typed": "\u2328\uFE0F typed without Enter:\n```text\n{screen}\n```",
1968
+ "dc.passthrough.sent": "\u2328\uFE0F typed `/{command}` for the agent. Nothing was submitted \u2014 use `/ax-key` with `enter` if it needs Enter:\n```text\n{screen}\n```",
1950
1969
  "dc.command.tooLong": "Raw input is limited to {limit} characters; send a normal prompt instead.",
1951
1970
  "dc.inbound.empty": "\u26A0\uFE0F The attachment is empty.",
1952
1971
  "err.discord.noThread": "Discord has no thread for session {session}",
@@ -2064,6 +2083,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
2064
2083
  "err.claude.conversationBusy": "session {session} is already in conversation {id} \u2014 two sessions writing one transcript would corrupt it; kill that one first",
2065
2084
  "err.claude.noConversation": "claude has no conversation {id} in its store on this machine \u2014 a conversation lives on the machine it ran on, so this one cannot be resumed here",
2066
2085
  "agent.approval.denied": "Denied from the axterm channel",
2086
+ "agent.conversation.switch.new": "\uD83D\uDD00 A new conversation was started under this topic \u2014 your next prompt goes to it, not the previous one.",
2087
+ "agent.conversation.switch.resume": "\uD83D\uDD00 This topic resumed a different conversation \u2014 your next prompt goes to the resumed one, not the previous one.",
2088
+ "agent.conversation.switch.fork": "\uD83D\uDD00 The conversation was forked \u2014 this topic now follows the fork, and your next prompt goes to it.",
2089
+ "agent.conversation.switch.handoff": "\uD83D\uDD00 The conversation was handed off \u2014 this topic now follows the new conversation, and your next prompt goes to it.",
2090
+ "agent.conversation.switch.unknown": "\uD83D\uDD00 The conversation under this topic changed \u2014 your next prompt goes to the new conversation, not the previous one.",
2091
+ "agent.conversation.branch": "\uD83C\uDF31 The conversation branched \u2014 this topic now follows the branch, and your next prompt goes to it.",
2067
2092
  "err.codex.noAuth": "codex is not logged in ({authFile} not found) \u2014 run `codex login` once, then start the session again",
2068
2093
  "err.codex.authLink": "could not link codex credentials into axterm's CODEX_HOME ({reason}) \u2014 refusing to start a logged-out agent",
2069
2094
  "health.config.invalid": "the configuration file could not be understood",
@@ -2104,6 +2129,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
2104
2129
  "wizard.review.header": "Review",
2105
2130
  "wizard.cancelled": "nothing was saved",
2106
2131
  "wizard.saved": "configuration saved",
2132
+ "wizard.telegramDiscovering": "looking up the group and user ids this bot can see\u2026",
2133
+ "wizard.telegramDiscoverFailed": "automatic Telegram discovery failed: {error} \u2014 enter the ids manually below or retry setup after stopping the active Telegram channel",
2107
2134
  "wizard.checking": "checking the Telegram setup\u2026",
2108
2135
  "wizard.telegramOk": "Telegram is ready",
2109
2136
  "wizard.needed": "axterm is not configured yet.",
@@ -2310,6 +2337,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
2310
2337
  "sl.command.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}',
2311
2338
  "sl.command.keysSent": "\u2328\uFE0F \uC804\uC1A1: {keys}",
2312
2339
  "sl.command.typed": "\u2328\uFE0F Enter \uC5C6\uC774 \uC785\uB825\uD588\uC2B5\uB2C8\uB2E4:\n```text\n{screen}\n```",
2340
+ "sl.passthrough.sent": "\u2328\uFE0F \uC5D0\uC774\uC804\uD2B8\uC5D0 `/{command}` \uB97C \uC785\uB825\uD588\uC2B5\uB2C8\uB2E4. \uC81C\uCD9C\uC740 \uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 Enter\uAC00 \uD544\uC694\uD558\uBA74 `key` \uC81C\uC5B4 \uBA85\uB839\uC5D0 `keys:enter` \uB97C \uC4F0\uC138\uC694:\n```text\n{screen}\n```",
2313
2341
  "sl.command.failed": "\uC2E4\uD328: {error}",
2314
2342
  "sl.state.working": "\uC791\uC5C5 \uC911",
2315
2343
  "sl.state.blocked": "\uC2B9\uC778 \uB300\uAE30 \uC911",
@@ -2371,6 +2399,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
2371
2399
  "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}",
2372
2400
  "tg.raw.keysSent": "\u2328\uFE0F \uC804\uC1A1: {keys}",
2373
2401
  "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}',
2402
+ "tg.passthrough.sent": `<code>/{command}</code> \uB97C \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC785\uB825\uD588\uC2B5\uB2C8\uB2E4. \uC81C\uCD9C\uC740 \uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 Enter\uAC00 \uD544\uC694\uD558\uBA74 \uD0A4 \uBC84\uD2BC\uC774\uB098 <code>/key enter</code> \uB97C \uC4F0\uC138\uC694:
2403
+ {screen}`,
2374
2404
  "tg.raw.typed": `\u2328\uFE0F \uC785\uB825\uD588\uC2B5\uB2C8\uB2E4 (\uC544\uC9C1 \uC81C\uCD9C \uC548 \uD568 \u2014 \uBCF4\uB0B4\uB824\uBA74 /key enter):
2375
2405
  <pre>{screen}</pre>`,
2376
2406
  "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.",
@@ -2418,6 +2448,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
2418
2448
  "dc.secret.submitted": "\uD83D\uDD10 \uBE44\uBC00\uC744 \uC804\uB2EC\uD588\uC73C\uBA70 \uAD8C\uD55C\uC774 \uD5C8\uC6A9\uD55C \uACBD\uC6B0 \uC6D0\uBCF8 \uBA54\uC2DC\uC9C0\uB97C \uC0AD\uC81C\uD588\uC2B5\uB2C8\uB2E4.",
2419
2449
  "dc.ask.tooMany": "Discord\uC5D0 \uD45C\uC2DC\uD558\uAE30\uC5D0\uB294 \uC120\uD0DD\uC9C0\uAC00 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4. \uC5F0\uACB0\uB41C \uD130\uBBF8\uB110\uC5D0\uC11C \uB2F5\uD558\uC138\uC694.",
2420
2450
  "dc.ask.resolvedElsewhere": "\u231B \uC774 \uC9C8\uBB38\uC740 \uB2E4\uB978 \uACF3\uC5D0\uC11C \uB2F5\uBCC0\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
2451
+ "dc.ask.conversationChanged": "\u231B \uB300\uD654\uAC00 \uBC14\uB00C\uC5B4 \uC774 \uC9C8\uBB38\uC740 \uB354 \uC774\uC0C1 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
2421
2452
  "dc.command.noProjects": "\uC124\uC815\uB41C \uD504\uB85C\uC81D\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
2422
2453
  "dc.command.noSessions": "\uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
2423
2454
  "dc.command.unknownProject": "\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uC81D\uD2B8: {project}",
@@ -2462,6 +2493,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
2462
2493
  "dc.command.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}',
2463
2494
  "dc.command.keysSent": "\u2328\uFE0F \uC804\uC1A1: {keys}",
2464
2495
  "dc.command.typed": "\u2328\uFE0F Enter \uC5C6\uC774 \uC785\uB825\uD588\uC2B5\uB2C8\uB2E4:\n```text\n{screen}\n```",
2496
+ "dc.passthrough.sent": "\u2328\uFE0F \uC5D0\uC774\uC804\uD2B8\uC5D0 `/{command}` \uB97C \uC785\uB825\uD588\uC2B5\uB2C8\uB2E4. \uC81C\uCD9C\uC740 \uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 Enter\uAC00 \uD544\uC694\uD558\uBA74 `/ax-key` \uC5D0 `enter` \uB97C \uC4F0\uC138\uC694:\n```text\n{screen}\n```",
2465
2497
  "dc.command.tooLong": "\uC6D0\uC2DC \uC785\uB825\uC740 \uCD5C\uB300 {limit}\uC790\uC785\uB2C8\uB2E4. \uC77C\uBC18 \uD504\uB86C\uD504\uD2B8\uB85C \uBCF4\uB0B4\uC138\uC694.",
2466
2498
  "dc.state.shell": "\uC178",
2467
2499
  "dc.state.exited": "\uC885\uB8CC\uB428",
@@ -2572,6 +2604,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
2572
2604
  "err.claude.conversationBusy": "\uC138\uC158 {session} \uC774(\uAC00) \uC774\uBBF8 \uB300\uD654 {id} \uC5D0 \uB4E4\uC5B4\uAC00 \uC788\uC2B5\uB2C8\uB2E4. \uD55C transcript\uC5D0 \uB450 \uC138\uC158\uC774 \uC4F0\uBA74 \uC190\uC0C1\uB418\uBBC0\uB85C \uBA3C\uC800 \uADF8 \uC138\uC158\uC744 \uC885\uB8CC\uD558\uC138\uC694",
2573
2605
  "err.claude.noConversation": "\uC774 \uBA38\uC2E0\uC758 claude \uC800\uC7A5\uC18C\uC5D0 \uB300\uD654 {id} \uC774(\uAC00) \uC5C6\uC2B5\uB2C8\uB2E4. \uB300\uD654\uB294 \uC2E4\uD589\uB41C \uBA38\uC2E0\uC5D0 \uB0A8\uC73C\uBBC0\uB85C \uC5EC\uAE30\uC11C\uB294 \uC774\uC5B4\uAC08 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
2574
2606
  "agent.approval.denied": "axterm \uCC44\uB110\uC5D0\uC11C \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
2607
+ "agent.conversation.switch.new": "\uD83D\uDD00 \uC774 \uD1A0\uD53D \uC544\uB798\uC5D0 \uC0C8 \uB300\uD654\uAC00 \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4 \u2014 \uB2E4\uC74C \uD504\uB86C\uD504\uD2B8\uB294 \uC774\uC804 \uB300\uD654\uAC00 \uC544\uB2C8\uB77C \uC0C8 \uB300\uD654\uB85C \uAC11\uB2C8\uB2E4.",
2608
+ "agent.conversation.switch.resume": "\uD83D\uDD00 \uC774 \uD1A0\uD53D\uC774 \uB2E4\uB978 \uB300\uD654\uB97C \uC774\uC5B4\uBC1B\uC558\uC2B5\uB2C8\uB2E4 \u2014 \uB2E4\uC74C \uD504\uB86C\uD504\uD2B8\uB294 \uC774\uC804 \uB300\uD654\uAC00 \uC544\uB2C8\uB77C \uC774\uC5B4\uC9C4 \uB300\uD654\uB85C \uAC11\uB2C8\uB2E4.",
2609
+ "agent.conversation.switch.fork": "\uD83D\uDD00 \uB300\uD654\uAC00 \uD3EC\uD06C\uB418\uC5C8\uC2B5\uB2C8\uB2E4 \u2014 \uC774 \uD1A0\uD53D\uC740 \uC774\uC81C \uD3EC\uD06C\uB41C \uB300\uD654\uB97C \uB530\uB974\uBA70, \uB2E4\uC74C \uD504\uB86C\uD504\uD2B8\uB294 \uADF8\uCABD\uC73C\uB85C \uAC11\uB2C8\uB2E4.",
2610
+ "agent.conversation.switch.handoff": "\uD83D\uDD00 \uB300\uD654\uAC00 \uC778\uACC4\uB418\uC5C8\uC2B5\uB2C8\uB2E4 \u2014 \uC774 \uD1A0\uD53D\uC740 \uC774\uC81C \uC0C8 \uB300\uD654\uB97C \uB530\uB974\uBA70, \uB2E4\uC74C \uD504\uB86C\uD504\uD2B8\uB294 \uADF8\uCABD\uC73C\uB85C \uAC11\uB2C8\uB2E4.",
2611
+ "agent.conversation.switch.unknown": "\uD83D\uDD00 \uC774 \uD1A0\uD53D\uC758 \uB300\uD654\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4 \u2014 \uB2E4\uC74C \uD504\uB86C\uD504\uD2B8\uB294 \uC774\uC804 \uB300\uD654\uAC00 \uC544\uB2C8\uB77C \uC0C8 \uB300\uD654\uB85C \uAC11\uB2C8\uB2E4.",
2612
+ "agent.conversation.branch": "\uD83C\uDF31 \uB300\uD654\uAC00 \uBD84\uAE30\uB418\uC5C8\uC2B5\uB2C8\uB2E4 \u2014 \uC774 \uD1A0\uD53D\uC740 \uC774\uC81C \uBD84\uAE30\uB41C \uB300\uD654\uB97C \uB530\uB974\uBA70, \uB2E4\uC74C \uD504\uB86C\uD504\uD2B8\uB294 \uADF8\uCABD\uC73C\uB85C \uAC11\uB2C8\uB2E4.",
2575
2613
  "err.codex.noAuth": "codex\uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 ({authFile} \uC5C6\uC74C). `codex login` \uC744 \uD55C \uBC88 \uC2E4\uD589\uD55C \uB4A4 \uC138\uC158\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC138\uC694",
2576
2614
  "err.codex.authLink": "codex \uC790\uACA9 \uC99D\uBA85\uC744 axterm\uC758 CODEX_HOME\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 ({reason}). \uB85C\uADF8\uC544\uC6C3 \uC0C1\uD0DC\uC758 \uC5D0\uC774\uC804\uD2B8\uB294 \uC2DC\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
2577
2615
  "health.config.invalid": "\uC124\uC815 \uD30C\uC77C\uC744 \uC774\uD574\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
@@ -2612,6 +2650,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
2612
2650
  "wizard.review.header": "\uD655\uC778",
2613
2651
  "wizard.cancelled": "\uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4",
2614
2652
  "wizard.saved": "\uC124\uC815\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4",
2653
+ "wizard.telegramDiscovering": "\uC774 \uBD07\uC774 \uBCFC \uC218 \uC788\uB294 \uADF8\uB8F9 id\uC640 \uC0AC\uC6A9\uC790 id\uB97C \uC870\uD68C\uD558\uB294 \uC911\u2026",
2654
+ "wizard.telegramDiscoverFailed": "Telegram \uC790\uB3D9 \uC870\uD68C \uC2E4\uD328: {error} \u2014 \uC544\uB798\uC5D0\uC11C id\uB97C \uC9C1\uC811 \uC785\uB825\uD558\uAC70\uB098, \uC2E4\uD589 \uC911\uC778 Telegram \uCC44\uB110\uC744 \uC911\uC9C0\uD55C \uB4A4 setup\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694",
2615
2655
  "wizard.checking": "Telegram \uC124\uC815\uC744 \uC810\uAC80\uD558\uB294 \uC911\u2026",
2616
2656
  "wizard.telegramOk": "Telegram \uC900\uBE44 \uC644\uB8CC",
2617
2657
  "wizard.needed": "axterm\uC774 \uC544\uC9C1 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
@@ -2780,6 +2820,25 @@ var init_setup_wizard = __esm(() => {
2780
2820
  });
2781
2821
 
2782
2822
  // packages/core/src/agent-resume.ts
2823
+ function repairPersistedResumeSource(profile, source) {
2824
+ if (profile.id !== "omp" || source.agentSessionFile === undefined)
2825
+ return source;
2826
+ const file = source.agentSessionFile;
2827
+ const lastSeparator = Math.max(file.lastIndexOf("/"), file.lastIndexOf("\\"));
2828
+ if (lastSeparator < 0 || !file.slice(lastSeparator + 1).toLowerCase().endsWith(".jsonl"))
2829
+ return source;
2830
+ const parent = file.slice(0, lastSeparator);
2831
+ const parentSeparator = Math.max(parent.lastIndexOf("/"), parent.lastIndexOf("\\"));
2832
+ const parentName = parent.slice(parentSeparator + 1);
2833
+ const match2 = OMP_SESSION_ID_SUFFIX.exec(parentName);
2834
+ if (match2 === null)
2835
+ return source;
2836
+ return {
2837
+ ...source,
2838
+ agentSessionFile: `${parent}.jsonl`,
2839
+ agentSessionId: match2[1]
2840
+ };
2841
+ }
2783
2842
  function resumeReference(profile, source) {
2784
2843
  const reported = profile.resume?.ref === "sessionId" ? source.agentSessionId : source.agentSessionFile;
2785
2844
  if (reported !== undefined && reported.length > 0)
@@ -2831,9 +2890,10 @@ function buildResumeCommand(command, profile, reference) {
2831
2890
  const fragment = template.map((token) => token.replaceAll(REF, reference));
2832
2891
  return resume.form === "subcommand" ? [base[0], ...fragment, ...base.slice(1)] : [...base, ...fragment];
2833
2892
  }
2834
- var REF = "{ref}", COMMAND_ALIASES;
2893
+ var REF = "{ref}", OMP_SESSION_ID_SUFFIX, COMMAND_ALIASES;
2835
2894
  var init_agent_resume = __esm(() => {
2836
2895
  init_agent_registry();
2896
+ OMP_SESSION_ID_SUFFIX = /_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
2837
2897
  COMMAND_ALIASES = new Map(REGISTERED_AGENTS.flatMap((id) => {
2838
2898
  const profile = agentProfile(id);
2839
2899
  return profile.command === undefined ? [] : [[profile.command, id]];
@@ -3698,15 +3758,45 @@ function next(prev, state, now) {
3698
3758
  stateStartedAt: prev?.state === state ? prev.stateStartedAt : now
3699
3759
  };
3700
3760
  }
3761
+ function delegationSurface(event, subagentId) {
3762
+ const done = event.event === "agent_end" || event.event === "agent_settled";
3763
+ const prompt = event.prompt?.split(`
3764
+ `, 1)[0]?.trim().slice(0, MAX_DELEGATION_TITLE);
3765
+ return {
3766
+ event: "surface",
3767
+ surface: {
3768
+ class: "progress",
3769
+ kind: "progress.delegation",
3770
+ key: subagentId,
3771
+ sourceEvent: event.event,
3772
+ payload: {
3773
+ scope: "delegation",
3774
+ title: event.tool_name ?? (prompt !== undefined && prompt !== "" ? prompt : subagentId),
3775
+ status: done ? "done" : "active"
3776
+ }
3777
+ }
3778
+ };
3779
+ }
3780
+ function withoutConversationIdentity(event) {
3781
+ if (event.session_id === undefined && event.session_file === undefined && event.session_name === undefined) {
3782
+ return event;
3783
+ }
3784
+ const { session_id: _id, session_file: _file, session_name: _name, ...rest } = event;
3785
+ return rest;
3786
+ }
3701
3787
  function withConversationId(events, identity) {
3702
3788
  const id = identity.sessionId;
3703
3789
  const subagent = identity.subagentId;
3704
- if (typeof id !== "string" || id === "" || typeof subagent === "string" && subagent !== "") {
3705
- return [...events];
3790
+ if (typeof subagent === "string" && subagent !== "") {
3791
+ return events.map((event) => LEAD_ONLY_EVENTS.has(event.event) ? delegationSurface(event, subagent) : withoutConversationIdentity(event));
3706
3792
  }
3793
+ if (typeof id !== "string" || id === "")
3794
+ return [...events];
3707
3795
  return events.map((event) => ({ session_id: id, ...event }));
3708
3796
  }
3709
- function mayReplaceConversationIdentity(agentId, current, reported, event) {
3797
+ function mayReplaceConversationIdentity(agentId, current, reported, event, fromSubagent = false) {
3798
+ if (fromSubagent)
3799
+ return false;
3710
3800
  if (reported === current)
3711
3801
  return false;
3712
3802
  if (current === undefined)
@@ -3802,8 +3892,15 @@ function isAgentSendable(probe, now) {
3802
3892
  return probe.occupied;
3803
3893
  return now - probe.spawnedAt < BOOT_GRACE_MS;
3804
3894
  }
3805
- var AGENT_ACTIVITY, WORKING_HOOK_STALE_MS = 30000, OUTPUT_QUIET_MS = 1e4, BOOT_GRACE_MS = 60000;
3895
+ var LEAD_ONLY_EVENTS, MAX_DELEGATION_TITLE = 80, AGENT_ACTIVITY, WORKING_HOOK_STALE_MS = 30000, OUTPUT_QUIET_MS = 1e4, BOOT_GRACE_MS = 60000;
3806
3896
  var init_agent_status = __esm(() => {
3897
+ LEAD_ONLY_EVENTS = new Set([
3898
+ "before_agent_start",
3899
+ "agent_start",
3900
+ "agent_settled",
3901
+ "agent_end",
3902
+ "message_end"
3903
+ ]);
3807
3904
  AGENT_ACTIVITY = new Set([
3808
3905
  "agent_start",
3809
3906
  "before_agent_start",
@@ -3824,6 +3921,42 @@ var init_agent_status = __esm(() => {
3824
3921
  ]);
3825
3922
  });
3826
3923
 
3924
+ // packages/core/src/agent-passthrough.ts
3925
+ function parseAgentPassthrough(text) {
3926
+ if (text.includes(`
3927
+ `) || text.includes("\r"))
3928
+ return;
3929
+ if (!text.startsWith("."))
3930
+ return;
3931
+ const rest = text.slice(1);
3932
+ const match2 = /^([A-Za-z][A-Za-z0-9_.-]*)(\s.*)?$/.exec(rest);
3933
+ if (!match2)
3934
+ return;
3935
+ const name = match2[1];
3936
+ if (name.length > MAX_NAME)
3937
+ return;
3938
+ if (match2[2] === undefined && DOTFILE_NAMES.has(name.toLowerCase()))
3939
+ return;
3940
+ return { keystrokes: `/${rest}`, name };
3941
+ }
3942
+ var MAX_NAME = 32, DOTFILE_NAMES;
3943
+ var init_agent_passthrough = __esm(() => {
3944
+ DOTFILE_NAMES = new Set([
3945
+ "env",
3946
+ "gitignore",
3947
+ "gitattributes",
3948
+ "npmrc",
3949
+ "nvmrc",
3950
+ "editorconfig",
3951
+ "dockerignore",
3952
+ "gitmodules",
3953
+ "bashrc",
3954
+ "zshrc",
3955
+ "profile",
3956
+ "tool-versions"
3957
+ ]);
3958
+ });
3959
+
3827
3960
  // packages/core/src/surface-policy.ts
3828
3961
  function resolveSurfacePolicy(agentId, kind, config) {
3829
3962
  const descriptor2 = SURFACE_DESCRIPTORS[kind];
@@ -4003,15 +4136,15 @@ function planMaterialisedPath(stagingDir, sessionId, remotePath) {
4003
4136
  let name = base.replace(/[\u0000-\u001f<>:"|?*]/g, "_").replace(/^\.+$/, "");
4004
4137
  if (name.length === 0)
4005
4138
  name = "file";
4006
- if (name.length > MAX_NAME) {
4139
+ if (name.length > MAX_NAME2) {
4007
4140
  const dot = name.lastIndexOf(".");
4008
4141
  const ext = dot > 0 && name.length - dot <= 16 ? name.slice(dot) : "";
4009
- name = name.slice(0, MAX_NAME - ext.length) + ext;
4142
+ name = name.slice(0, MAX_NAME2 - ext.length) + ext;
4010
4143
  }
4011
4144
  return `${stagingDir}/${sessionId}/${name}`;
4012
4145
  }
4013
4146
  var SCRATCH_DIR_NAME = ".axterm", SCRATCH_IGNORE_NAME = ".gitignore", SCRATCH_IGNORE_BODY = `*
4014
- `, MAX_NAME = 120;
4147
+ `, MAX_NAME2 = 120;
4015
4148
 
4016
4149
  // packages/core/src/claude-hooks.ts
4017
4150
  function resultText(response) {
@@ -6654,6 +6787,7 @@ var init_src = __esm(() => {
6654
6787
  init_choice();
6655
6788
  init_agent_status();
6656
6789
  init_activity();
6790
+ init_agent_passthrough();
6657
6791
  init_agent_surface();
6658
6792
  init_surface_policy();
6659
6793
  init_pty_protocol();
@@ -6747,14 +6881,20 @@ function renderSlackSurface(surface) {
6747
6881
  const progress = surface.payload.current !== undefined && surface.payload.total !== undefined ? ` (${surface.payload.current}/${surface.payload.total})` : "";
6748
6882
  const items = surface.payload.items?.map((item) => `${item.status === "done" ? "\u2611" : item.status === "active" ? "\u25B6" : "\u2022"} ${item.label}`).join(`
6749
6883
  `);
6750
- return `\uD83D\uDCCB **${surface.payload.title}**${progress}${items ? `
6884
+ const glyph = surface.payload.status === undefined ? "\uD83D\uDCCB" : progressGlyph(surface.payload.status);
6885
+ return `${glyph} **${surface.payload.title}**${progress}${items ? `
6751
6886
  ${items}` : ""}`;
6752
6887
  }
6753
6888
  case "activity":
6754
6889
  return `${surface.payload.isError ? "\u26A0\uFE0F" : "\uD83D\uDD27"} **${surface.payload.label}**${surface.payload.summary ? `
6755
6890
  ${surface.payload.summary}` : ""}`;
6756
- case "context":
6891
+ case "context": {
6892
+ if (surface.kind === "context.session" || surface.kind === "context.branch") {
6893
+ const key2 = surface.payload.action === "branch" ? "agent.conversation.branch" : `agent.conversation.switch.${surface.payload.cause ?? "unknown"}`;
6894
+ return `${t(key2)}${surface.payload.reason ? ` (${surface.payload.reason})` : ""}`;
6895
+ }
6757
6896
  return `\uD83E\uDDED ${surface.payload.action}${surface.payload.reason ? ` \xB7 ${surface.payload.reason}` : ""}`;
6897
+ }
6758
6898
  case "artifact":
6759
6899
  return `\uD83D\uDCCE ${surface.payload.name ?? surface.payload.path ?? surface.payload.url ?? "artifact"}${surface.payload.caption ? `
6760
6900
  ${surface.payload.caption}` : ""}${surface.payload.text ? `
@@ -7242,7 +7382,7 @@ ${screen}
7242
7382
  if (this.#secretPrompts.get(surface.sessionId) === surface.payload.seq) {
7243
7383
  this.#secretPrompts.delete(surface.sessionId);
7244
7384
  }
7245
- await this.#resolveFlow(surface.sessionId, surface.payload.seq);
7385
+ await this.#resolveFlow(surface.sessionId, surface.payload.seq, surface.source.event === "conversation_change");
7246
7386
  } else {
7247
7387
  await this.#openFlow(surface);
7248
7388
  }
@@ -7401,15 +7541,19 @@ ${screen}
7401
7541
  if (ts)
7402
7542
  await this.api.updateMessage(this.config.channelId, ts, { text, blocks: [] });
7403
7543
  }
7404
- async#resolveFlow(sessionId, seq) {
7544
+ async#resolveFlow(sessionId, seq, retracted) {
7405
7545
  const flow = this.#flows.get(sessionId);
7406
7546
  if (flow !== undefined && flow.seq !== seq)
7407
7547
  return;
7408
7548
  this.#flows.delete(sessionId);
7409
- if (flow?.messageTs)
7410
- await this.api.updateMessage(this.config.channelId, flow.messageTs, { text: t("dc.ask.resolvedElsewhere"), blocks: [] }).catch(() => {
7549
+ if (flow?.messageTs) {
7550
+ await this.api.updateMessage(this.config.channelId, flow.messageTs, {
7551
+ text: t(retracted ? "dc.ask.conversationChanged" : "dc.ask.resolvedElsewhere"),
7552
+ blocks: []
7553
+ }).catch(() => {
7411
7554
  return;
7412
7555
  });
7556
+ }
7413
7557
  }
7414
7558
  async#handleMessage(message) {
7415
7559
  if (message.channel !== this.config.channelId || message.bot_id || message.subtype || !message.user || !this.#allowedUsers.has(message.user) || !message.thread_ts)
@@ -7430,6 +7574,16 @@ ${screen}
7430
7574
  await this.#renderFlow(binding.sessionId);
7431
7575
  return;
7432
7576
  }
7577
+ const passthrough = parseAgentPassthrough(message.text ?? "");
7578
+ if (passthrough) {
7579
+ this.host.write(binding.sessionId, passthrough.keystrokes);
7580
+ this.#log(`session ${binding.sessionId} passthrough .${passthrough.name}`);
7581
+ await this.#sendToSession(binding.sessionId, t("sl.passthrough.sent", {
7582
+ command: passthrough.name,
7583
+ screen: this.host.screen(binding.sessionId, RAW_SCREEN_LINES)
7584
+ }));
7585
+ return;
7586
+ }
7433
7587
  const staged = [];
7434
7588
  if (this.config.inbound?.enabled) {
7435
7589
  for (const file of message.files ?? []) {
@@ -9012,14 +9166,20 @@ function renderDiscordSurface(surface) {
9012
9166
  const progress = surface.payload.current !== undefined && surface.payload.total !== undefined ? ` (${surface.payload.current}/${surface.payload.total})` : "";
9013
9167
  const items = surface.payload.items?.map((item) => `${item.status === "done" ? "\u2611" : item.status === "active" ? "\u25B6" : "\u2022"} ${item.label}`).join(`
9014
9168
  `);
9015
- return `\uD83D\uDCCB **${surface.payload.title}**${progress}${items ? `
9169
+ const glyph = surface.payload.status === undefined ? "\uD83D\uDCCB" : progressGlyph(surface.payload.status);
9170
+ return `${glyph} **${surface.payload.title}**${progress}${items ? `
9016
9171
  ${items}` : ""}`;
9017
9172
  }
9018
9173
  case "activity":
9019
9174
  return `${surface.payload.isError ? "\u26A0\uFE0F" : "\uD83D\uDD27"} **${surface.payload.label}**${surface.payload.summary ? `
9020
9175
  ${surface.payload.summary}` : ""}`;
9021
- case "context":
9176
+ case "context": {
9177
+ if (surface.kind === "context.session" || surface.kind === "context.branch") {
9178
+ const key2 = surface.payload.action === "branch" ? "agent.conversation.branch" : `agent.conversation.switch.${surface.payload.cause ?? "unknown"}`;
9179
+ return `${t(key2)}${surface.payload.reason ? ` (${surface.payload.reason})` : ""}`;
9180
+ }
9022
9181
  return `\uD83E\uDDED ${surface.payload.action}${surface.payload.reason ? ` \xB7 ${surface.payload.reason}` : ""}`;
9182
+ }
9023
9183
  case "artifact":
9024
9184
  return `\uD83D\uDCCE ${surface.payload.name ?? surface.payload.path ?? surface.payload.url ?? "artifact"}${surface.payload.caption ? `
9025
9185
  ${surface.payload.caption}` : ""}${surface.payload.text ? `
@@ -9243,9 +9403,9 @@ ${screen}
9243
9403
  }
9244
9404
  async onSurface(surface) {
9245
9405
  if (surface.class === "decision") {
9246
- if (surface.payload.state === "resolved")
9247
- await this.resolveFlow(surface.sessionId, surface.payload.seq);
9248
- else
9406
+ if (surface.payload.state === "resolved") {
9407
+ await this.resolveFlow(surface.sessionId, surface.payload.seq, surface.source.event === "conversation_change");
9408
+ } else
9249
9409
  await this.openFlow(surface);
9250
9410
  return;
9251
9411
  }
@@ -9413,7 +9573,7 @@ ${screen}
9413
9573
  });
9414
9574
  }
9415
9575
  }
9416
- async resolveFlow(sessionId, seq) {
9576
+ async resolveFlow(sessionId, seq, retracted) {
9417
9577
  const flow = this.flows.get(sessionId);
9418
9578
  const matchingFlow = flow?.seq === seq ? flow : undefined;
9419
9579
  if (matchingFlow !== undefined)
@@ -9423,7 +9583,7 @@ ${screen}
9423
9583
  const binding = this.host.findBindingBySession("discord", sessionId);
9424
9584
  if (matchingFlow?.messageId && binding) {
9425
9585
  await this.api.editMessage(binding.containerId, matchingFlow.messageId, {
9426
- content: t("dc.ask.resolvedElsewhere"),
9586
+ content: t(retracted ? "dc.ask.conversationChanged" : "dc.ask.resolvedElsewhere"),
9427
9587
  components: []
9428
9588
  }).catch(() => {
9429
9589
  return;
@@ -9683,6 +9843,16 @@ ${screen}
9683
9843
  await this.handleAttachments(binding.sessionId, attachments, text);
9684
9844
  return;
9685
9845
  }
9846
+ const passthrough = parseAgentPassthrough(message.content);
9847
+ if (passthrough) {
9848
+ this.host.write(binding.sessionId, passthrough.keystrokes);
9849
+ this.log(`session ${binding.sessionId} passthrough .${passthrough.name}`);
9850
+ await this.sendToSession(binding.sessionId, t("dc.passthrough.sent", {
9851
+ command: passthrough.name,
9852
+ screen: this.host.screen(binding.sessionId, RAW_SCREEN_LINES)
9853
+ }));
9854
+ return;
9855
+ }
9686
9856
  if (text)
9687
9857
  await this.host.prompt(binding.sessionId, text);
9688
9858
  }
@@ -10915,6 +11085,10 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
10915
11085
  // how omp instantiates the module, which is why the PID guard above uses it\r
10916
11086
  // too.\r
10917
11087
  const LEAD_KEY = "AXTERM_LEAD_SESSION";\r
11088
+ // Counts \`session_start\`s, not ids. The gate must distinguish "this build\r
11089
+ // exposes no session ids" from "this event happens to have no id" \u2014 the\r
11090
+ // count is what tells those apart (see isLeadSession).\r
11091
+ const STARTS_KEY = "AXTERM_LEAD_STARTS";\r
10918
11092
  \r
10919
11093
  function sessionIdOf(ctx: unknown): string | undefined {\r
10920
11094
  const manager = (ctx as { sessionManager?: { getSessionId?: () => string } } | undefined)?.sessionManager;\r
@@ -10953,24 +11127,39 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
10953
11127
  * cannot begin before its parent, so first is the lead \u2014 and recording it\r
10954
11128
  * here rather than lazily on the first judgement matters: a subagent's\r
10955
11129
  * completion would otherwise be the first caller and crown itself.\r
11130
+ *\r
11131
+ * Only the FIRST start may crown. "First id-bearing start wins" let a\r
11132
+ * subagent crown itself one step removed \u2014 the lead's start exposed no id,\r
11133
+ * the subagent's did, and its settled then passed the gate as the lead.\r
11134
+ * That is the operator's report (omp x telegram): the done message and the\r
11135
+ * topic rename arrived while the lead was still working.\r
10956
11136
  */\r
10957
11137
  function rememberLead(ctx: unknown): void {\r
11138
+ const starts = Number(process.env[STARTS_KEY] ?? "0") + 1;\r
11139
+ process.env[STARTS_KEY] = String(starts);\r
10958
11140
  const id = sessionIdOf(ctx);\r
10959
- if (id === undefined) return;\r
10960
- if (!process.env[LEAD_KEY]) process.env[LEAD_KEY] = id;\r
11141
+ if (starts === 1 && id !== undefined) process.env[LEAD_KEY] = id;\r
10961
11142
  }\r
10962
11143
  \r
10963
11144
  /**\r
10964
- * Fails OPEN twice over \u2014 no lead recorded, or no id on this event. A build\r
10965
- * that exposes neither must not lose its turn-end entirely, which would leave\r
10966
- * the session at \`working\` forever; the shutdown path makes the same choice\r
10967
- * (\xA74.42).\r
11145
+ * Whether this event may claim the SESSION's lifecycle (turn start/end,\r
11146
+ * prompt, reply). Two different unknowns meet here and must not be\r
11147
+ * conflated \u2014 conflating them was the reported bug:\r
11148
+ *\r
11149
+ * - Lead id known: exact match only. An event with no id is not "probably\r
11150
+ * the lead", it is unidentified \u2014 a false \`done\` sends the message and\r
11151
+ * renames the topic, and nothing repairs that; a LATE turn end is what\r
11152
+ * stall forwarding (\xA74.75) exists for.\r
11153
+ * - No lead id and ONE session started: this build exposes no ids and there\r
11154
+ * is nothing to confuse \u2014 fail open, or the session sits at \`working\`\r
11155
+ * forever; the shutdown path makes the same choice (\xA74.42).\r
11156
+ * - No lead id and SEVERAL sessions started: any of them could be the one\r
11157
+ * finishing, so nobody may claim the turn end.\r
10968
11158
  */\r
10969
11159
  function isLeadSession(ctx: unknown): boolean {\r
10970
11160
  const lead = process.env[LEAD_KEY];\r
10971
- if (!lead) return true;\r
10972
- const id = sessionIdOf(ctx);\r
10973
- return id === undefined || id === lead;\r
11161
+ if (lead) return sessionIdOf(ctx) === lead;\r
11162
+ return Number(process.env[STARTS_KEY] ?? "0") <= 1;\r
10974
11163
  }\r
10975
11164
  \r
10976
11165
  /**\r
@@ -11069,12 +11258,8 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
11069
11258
  const activityQueue: Array<{ event: string; extra: Record<string, unknown> }> = [];\r
11070
11259
  let activityDropped = 0;\r
11071
11260
  \r
11072
- function postActivity(extra: Record<string, unknown>): void {\r
11073
- if (activityDropped > 0) {\r
11074
- extra["dropped"] = activityDropped;\r
11075
- activityDropped = 0;\r
11076
- }\r
11077
- activityQueue.push({ event: "activity", extra });\r
11261
+ function enqueue(event: string, extra: Record<string, unknown>): void {\r
11262
+ activityQueue.push({ event, extra });\r
11078
11263
  if (activityQueue.length > MAX_ACTIVITY_QUEUE) {\r
11079
11264
  activityQueue.shift();\r
11080
11265
  // Counted so the next item can admit the gap rather than hide it.\r
@@ -11082,18 +11267,42 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
11082
11267
  }\r
11083
11268
  drain();\r
11084
11269
  }\r
11270
+ \r
11271
+ function postActivity(extra: Record<string, unknown>): void {\r
11272
+ if (activityDropped > 0) {\r
11273
+ extra["dropped"] = activityDropped;\r
11274
+ activityDropped = 0;\r
11275
+ }\r
11276
+ enqueue("activity", extra);\r
11277
+ }\r
11085
11278
  \r
11086
11279
  function postSurface(surface: Record<string, unknown>, immediate = false): void {\r
11087
11280
  if (immediate) {\r
11088
11281
  post("surface", { surface });\r
11089
11282
  return;\r
11090
11283
  }\r
11091
- activityQueue.push({ event: "surface", extra: { surface } });\r
11092
- if (activityQueue.length > MAX_ACTIVITY_QUEUE) {\r
11093
- activityQueue.shift();\r
11094
- activityDropped += 1;\r
11095
- }\r
11096
- drain();\r
11284
+ enqueue("surface", { surface });\r
11285
+ }\r
11286
+ \r
11287
+ /**\r
11288
+ * A subagent's lifecycle, labelled instead of dropped. Suppressing it was\r
11289
+ * the first fix, and it traded one failure for another: a silent subagent\r
11290
+ * looks like a hang (\xA74.51). So the event still goes out, but carrying the\r
11291
+ * subagent's id under \`agent_id\` \u2014 the field claude already marks its\r
11292
+ * subagents with and the daemon's delivery point keys on \u2014 so downstream it\r
11293
+ * renders as delegation telemetry, never as the session's turn.\r
11294
+ *\r
11295
+ * Queued, not \`post()\`: the status slot is latest-only, so a subagent's end\r
11296
+ * racing the lead's real turn-end would overwrite one or the other \u2014 either\r
11297
+ * the delegation vanishes or the session never leaves \`working\`.\r
11298
+ *\r
11299
+ * No id means unidentified, not "subagent": an unlabelled \`agent_end\` IS\r
11300
+ * the reported bug, so nothing is posted rather than something wrong.\r
11301
+ */\r
11302
+ function postDelegation(event: string, ctx: unknown, extra: Record<string, unknown> = {}): void {\r
11303
+ const id = sessionIdOf(ctx);\r
11304
+ if (id === undefined) return;\r
11305
+ enqueue(event, { ...extra, agent_id: id });\r
11097
11306
  }\r
11098
11307
  \r
11099
11308
  async function postOnce(event: string, extra: Record<string, unknown>): Promise<void> {\r
@@ -11310,8 +11519,19 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
11310
11519
  \r
11311
11520
  /* ------------------------------ handlers ------------------------------ */\r
11312
11521
  pi.on("session_start", (_event, ctx) => {\r
11313
- // First one wins, and it decides who may end a turn from here on.\r
11522
+ // \`rememberLead\` first: first-wins, and a subagent cannot begin before its\r
11523
+ // parent, so the lead is decided here for every later judgement.\r
11314
11524
  rememberLead(ctx);\r
11525
+ // Then the same rule as every other lead-only event (\xA74.114). This one was\r
11526
+ // missed, and it is the one that carries identity: every AgentSession\r
11527
+ // announced its own session_id and session_file, so a subagent's start\r
11528
+ // could become the session's conversation and \`axterm resume\` reopened it.\r
11529
+ // Labelled rather than dropped \u2014 the daemon strips a marked payload's\r
11530
+ // identity and still renders the subagent as delegation.\r
11531
+ if (!isLeadSession(ctx)) {\r
11532
+ postDelegation("session_start", ctx);\r
11533
+ return;\r
11534
+ }\r
11315
11535
  post("session_start", sessionIdentityOf(ctx));\r
11316
11536
  });\r
11317
11537
  \r
@@ -11331,10 +11551,15 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
11331
11551
  });\r
11332
11552
  \r
11333
11553
  // Lead only: this sets the run's starting prompt, which the channel shows in\r
11334
- // the topic title. A subagent's own prompt is not what the operator asked.\r
11554
+ // the topic title. A subagent's prompt is labelled instead \u2014 downstream it\r
11555
+ // becomes the delegation surface's title, not the topic's.\r
11335
11556
  pi.on("before_agent_start", (event, ctx) => {\r
11336
- if (!isLeadSession(ctx)) return;\r
11337
- post("before_agent_start", { prompt: (event as { prompt?: string } | undefined)?.prompt ?? "" });\r
11557
+ const prompt = (event as { prompt?: string } | undefined)?.prompt ?? "";\r
11558
+ if (!isLeadSession(ctx)) {\r
11559
+ postDelegation("before_agent_start", ctx, { prompt });\r
11560
+ return;\r
11561
+ }\r
11562
+ post("before_agent_start", { prompt });\r
11338
11563
  });\r
11339
11564
  \r
11340
11565
  /* -------- completion: agent_settled preferred, isIdle fallback -------- */\r
@@ -11380,8 +11605,12 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
11380
11605
  \r
11381
11606
  // Lead only: a subagent starting is not the session's turn restarting, and\r
11382
11607
  // resetting \`endReported\` here on its behalf would arm a second completion.\r
11608
+ // But it IS news (\xA74.51) \u2014 labelled, it marks the delegation active.\r
11383
11609
  pi.on("agent_start", (_event, ctx) => {\r
11384
- if (!isLeadSession(ctx)) return;\r
11610
+ if (!isLeadSession(ctx)) {\r
11611
+ postDelegation("agent_start", ctx);\r
11612
+ return;\r
11613
+ }\r
11385
11614
  clearPendingCheck();\r
11386
11615
  endReported = false;\r
11387
11616
  post("agent_start");\r
@@ -11625,9 +11854,16 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
11625
11854
  \r
11626
11855
  // Lead only, and this is the one the report was about: a finishing subagent\r
11627
11856
  // announced the turn as done and renamed the topic while the lead worked on.\r
11857
+ // Labelled with its id, a subagent's end is delegation telemetry instead.\r
11628
11858
  pi.on("agent_settled", (_event, ctx) => {\r
11629
- if (!isLeadSession(ctx)) return;\r
11859
+ // A build that emits agent_settled emits it for every AgentSession, so\r
11860
+ // this is a build property: record it before the gate, or a subagent's\r
11861
+ // settled would leave the isIdle fallback armed for a second end.\r
11630
11862
  settledSupported = true;\r
11863
+ if (!isLeadSession(ctx)) {\r
11864
+ postDelegation("agent_end", ctx);\r
11865
+ return;\r
11866
+ }\r
11631
11867
  clearPendingCheck();\r
11632
11868
  postEndOnce();\r
11633
11869
  });\r
@@ -11635,8 +11871,14 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
11635
11871
  // The other route to a turn end, for builds without \`agent_settled\`. Gated the\r
11636
11872
  // same way, or a subagent whose own work is idle ends the session's turn.\r
11637
11873
  pi.on("agent_end", (_event, ctx) => {\r
11638
- if (!isLeadSession(ctx)) return;\r
11639
11874
  if (settledSupported) return;\r
11875
+ if (!isLeadSession(ctx)) {\r
11876
+ // No isIdle wait for a delegation: the single pending-check slot\r
11877
+ // belongs to the lead's real turn, and a slightly early "done" on a\r
11878
+ // delegation surface is cosmetic where a stolen slot is not.\r
11879
+ postDelegation("agent_end", ctx);\r
11880
+ return;\r
11881
+ }\r
11640
11882
  const c = ctx as { isIdle?: () => boolean } | undefined;\r
11641
11883
  if (!c || typeof c.isIdle !== "function") {\r
11642
11884
  postEndOnce();\r
@@ -12308,10 +12550,12 @@ AXTERM_HOOK_TOKEN=${this.token}
12308
12550
  return;
12309
12551
  }
12310
12552
  res.writeHead(200).end();
12311
- try {
12312
- session.onEvent(payload);
12313
- } catch (err) {
12314
- log(`hook handler failed: ${String(err)}`);
12553
+ for (const delivered of withConversationId([payload], { subagentId: payload.agent_id })) {
12554
+ try {
12555
+ session.onEvent(delivered);
12556
+ } catch (err) {
12557
+ log(`hook handler failed: ${String(err)}`);
12558
+ }
12315
12559
  }
12316
12560
  }
12317
12561
  handleAsk(sessionId, askId, questions, _req, res) {
@@ -20532,6 +20776,60 @@ ${result.stderr}`);
20532
20776
  }
20533
20777
  });
20534
20778
  }
20779
+ applyConversationChange(id, runtime, info, change) {
20780
+ const previousId = runtime.mainAgentSessionId;
20781
+ if (change.nextId !== undefined) {
20782
+ runtime.mainAgentSessionId = change.nextId;
20783
+ info.agentSessionId = change.nextId;
20784
+ this.store.setAgentSessionId(id, change.nextId);
20785
+ }
20786
+ if (runtime.dialogOpen) {
20787
+ this.publishSurface({
20788
+ v: 1,
20789
+ sessionId: id,
20790
+ agentId: change.agentId,
20791
+ class: "decision",
20792
+ kind: `decision.${runtime.decisionKind ?? "ask"}`,
20793
+ source: { transport: "extension", event: "conversation_change", authoritative: true },
20794
+ payload: { state: "resolved", choiceKind: runtime.decisionKind ?? "ask", seq: runtime.choiceSeq }
20795
+ });
20796
+ }
20797
+ runtime.choiceSeq += 1;
20798
+ runtime.dialogOpen = false;
20799
+ runtime.decisionKind = undefined;
20800
+ this.pendingPrompt.delete(id);
20801
+ runtime.entry = undefined;
20802
+ runtime.stallReported = undefined;
20803
+ runtime.telemetryReported = undefined;
20804
+ runtime.antigravitySubagentActive = undefined;
20805
+ clearTimeout(runtime.exitTimer);
20806
+ runtime.exitTimer = undefined;
20807
+ runtime.agentExited = false;
20808
+ const action = change.event === "session_branch" ? "branch" : "switch";
20809
+ log(`session ${info.shortId} conversation ${action}: ${previousId ?? "(none)"} -> ${change.nextId ?? "(unknown)"}`);
20810
+ if (previousId === undefined)
20811
+ return;
20812
+ const cause = CONVERSATION_CHANGE_CAUSES.includes(change.reason ?? "") ? change.reason : undefined;
20813
+ const detail2 = cause === undefined ? change.reason ?? change.nextId ?? change.nextFile : undefined;
20814
+ this.publishSurface({
20815
+ v: 1,
20816
+ sessionId: id,
20817
+ agentId: change.agentId,
20818
+ class: "context",
20819
+ kind: change.event === "session_branch" ? "context.branch" : "context.session",
20820
+ key: "session",
20821
+ source: {
20822
+ transport: change.agentId === "omp" ? "extension" : "hook",
20823
+ event: change.event,
20824
+ authoritative: true
20825
+ },
20826
+ payload: {
20827
+ action,
20828
+ ...cause === undefined ? {} : { cause },
20829
+ ...detail2 === undefined ? {} : { reason: detail2 }
20830
+ }
20831
+ });
20832
+ }
20535
20833
  resolvePendingScreenDecision(id) {
20536
20834
  const session = this.live.get(id);
20537
20835
  const runtime = this.agentRuntime.get(id);
@@ -21425,37 +21723,20 @@ ${result.stderr}`);
21425
21723
  }
21426
21724
  const reportedId = fromAntigravitySubagent ? undefined : result.meta?.agentSessionId;
21427
21725
  const previousId = runtime.mainAgentSessionId;
21428
- const replacesId = reportedId !== undefined && mayReplaceConversationIdentity(agentId, previousId, reportedId, payload.event);
21726
+ const replacesId = reportedId !== undefined && mayReplaceConversationIdentity(agentId, previousId, reportedId, payload.event, fromAntigravitySubagent);
21429
21727
  const explicitOmpChange = agentId === "omp" && (payload.event === "session_switch" || payload.event === "session_branch");
21430
- const acceptsFile = previousId === undefined || reportedId === previousId || replacesId || explicitOmpChange;
21728
+ const acceptsFile = !fromAntigravitySubagent && (previousId === undefined || reportedId === previousId || replacesId || explicitOmpChange);
21431
21729
  if (result.meta?.agentSessionFile !== undefined && acceptsFile) {
21432
21730
  info.agentSessionFile = result.meta.agentSessionFile;
21433
21731
  this.store.setAgentSessionFile(id, result.meta.agentSessionFile);
21434
21732
  }
21435
- if (reportedId !== undefined && replacesId) {
21436
- runtime.mainAgentSessionId = reportedId;
21437
- info.agentSessionId = reportedId;
21438
- this.store.setAgentSessionId(id, reportedId);
21439
- }
21440
- if (previousId !== undefined && (reportedId !== undefined && replacesId || explicitOmpChange)) {
21441
- const action = payload.event === "session_branch" ? "branch" : "switch";
21442
- log(`session ${info.shortId} conversation ${action}: ${previousId} -> ${reportedId ?? "(unknown)"}`);
21443
- this.publishSurface({
21444
- v: 1,
21445
- sessionId: id,
21733
+ if (reportedId !== undefined && replacesId || explicitOmpChange) {
21734
+ this.applyConversationChange(id, runtime, info, {
21446
21735
  agentId,
21447
- class: "context",
21448
- kind: payload.event === "session_branch" ? "context.branch" : "context.session",
21449
- key: "session",
21450
- source: {
21451
- transport: agentId === "omp" ? "extension" : "hook",
21452
- event: payload.event,
21453
- authoritative: true
21454
- },
21455
- payload: {
21456
- action,
21457
- reason: payload.switch_reason ?? reportedId ?? result.meta?.agentSessionFile
21458
- }
21736
+ event: payload.event,
21737
+ ...reportedId !== undefined && replacesId ? { nextId: reportedId } : {},
21738
+ ...payload.switch_reason === undefined ? {} : { reason: payload.switch_reason },
21739
+ ...result.meta?.agentSessionFile === undefined ? {} : { nextFile: result.meta.agentSessionFile }
21459
21740
  });
21460
21741
  }
21461
21742
  if (agentId === "codex" && runtime.mainAgentSessionId !== undefined && payload.event !== "activity") {
@@ -21553,7 +21834,8 @@ ${result.stderr}`);
21553
21834
  if (this.live.has(stored.id))
21554
21835
  throw new AxtermError("err.session.running", { id: stored.shortId });
21555
21836
  const profile = agentProfile(agentIdFromCommand(stored.agentCommand));
21556
- const reference = resumeReference(profile, { ...stored, ownSessionId: stored.id });
21837
+ const resumeSource = repairPersistedResumeSource(profile, { ...stored, ownSessionId: stored.id });
21838
+ const reference = resumeReference(profile, resumeSource);
21557
21839
  if (reference === undefined) {
21558
21840
  throw new AxtermError("err.session.noConversation", {
21559
21841
  id: stored.shortId,
@@ -21566,7 +21848,20 @@ ${result.stderr}`);
21566
21848
  }
21567
21849
  if (!fs26.existsSync(stored.cwd))
21568
21850
  throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
21569
- const info = { ...stored, agentCommand, state: "running" };
21851
+ if (resumeSource.agentSessionFile !== undefined && resumeSource.agentSessionFile !== stored.agentSessionFile) {
21852
+ this.store.setAgentSessionFile(stored.id, resumeSource.agentSessionFile);
21853
+ log(`session ${stored.shortId} repaired OMP resume transcript to its parent conversation`);
21854
+ }
21855
+ if (resumeSource.agentSessionId !== undefined && resumeSource.agentSessionId !== stored.agentSessionId) {
21856
+ this.store.setAgentSessionId(stored.id, resumeSource.agentSessionId);
21857
+ }
21858
+ const info = {
21859
+ ...stored,
21860
+ ...resumeSource.agentSessionFile === undefined ? {} : { agentSessionFile: resumeSource.agentSessionFile },
21861
+ ...resumeSource.agentSessionId === undefined ? {} : { agentSessionId: resumeSource.agentSessionId },
21862
+ agentCommand,
21863
+ state: "running"
21864
+ };
21570
21865
  delete info.exitCode;
21571
21866
  delete info.exitedAt;
21572
21867
  delete info.agentState;
@@ -22740,7 +23035,7 @@ class TelegramChannel {
22740
23035
  case "immediate":
22741
23036
  if (surface.class === "decision") {
22742
23037
  if (surface.payload.state === "resolved") {
22743
- await this.onChoiceResolved(surface.sessionId, surface.payload.seq);
23038
+ await this.onChoiceResolved(surface.sessionId, surface.payload.seq, surface.source.event === "conversation_change");
22744
23039
  } else if (surface.payload.choiceKind === "shellPrompt") {
22745
23040
  await this.onShellPrompt(surface.sessionId, surface.payload.seq, surface.payload.shellPrompt);
22746
23041
  } else {
@@ -22748,6 +23043,8 @@ class TelegramChannel {
22748
23043
  }
22749
23044
  } else if (surface.class === "notice") {
22750
23045
  await this.onNotice(surface);
23046
+ } else if (surface.class === "context") {
23047
+ await this.onConversationChange(surface);
22751
23048
  }
22752
23049
  return;
22753
23050
  case "snapshot":
@@ -22777,6 +23074,11 @@ class TelegramChannel {
22777
23074
  const retryMs = surface.payload.retryAt === undefined ? surface.payload.durationMs : Math.max(0, surface.payload.retryAt - Date.now());
22778
23075
  await this.onAgentError(surface.sessionId, kind, surface.payload.detail, retryMs, surface.id);
22779
23076
  }
23077
+ async onConversationChange(surface) {
23078
+ const key2 = surface.payload.action === "branch" ? "agent.conversation.branch" : `agent.conversation.switch.${surface.payload.cause ?? "unknown"}`;
23079
+ const detail2 = surface.payload.reason;
23080
+ await this.sendToSession(surface.sessionId, tHtml(key2) + (detail2 === undefined ? "" : ` (${code(detail2)})`));
23081
+ }
22780
23082
  bufferDigest(surface) {
22781
23083
  const settings = this.deliverySettings;
22782
23084
  let digest = this.digests.get(surface.sessionId);
@@ -22842,7 +23144,8 @@ class TelegramChannel {
22842
23144
  return;
22843
23145
  const lines = [...snapshot2.items.values()].map((surface) => {
22844
23146
  const text = surface.class === "progress" ? surface.payload.title : surface.class === "state" ? `${surface.payload.name}: ${String(surface.payload.value)}` : surface.class === "context" ? `${surface.payload.action}${surface.payload.reason ? `: ${surface.payload.reason}` : ""}` : surface.kind;
22845
- return `\uD83D\uDCCC ${clipText(text, settings.maxChars)}`;
23147
+ const glyph = progressGlyph(surface.class === "progress" ? surface.payload.status : undefined);
23148
+ return `${glyph} ${clipText(text, settings.maxChars)}`;
22846
23149
  });
22847
23150
  this.snapshots.delete(sessionId);
22848
23151
  const body = clipText(lines.join(`
@@ -23028,7 +23331,7 @@ ${t("tg.status.inferred")}` : "");
23028
23331
  ${code(prompt.line)}`);
23029
23332
  return;
23030
23333
  }
23031
- await this.api.call("sendMessage", {
23334
+ const sent = await this.api.call("sendMessage", {
23032
23335
  chat_id: binding.chatId,
23033
23336
  message_thread_id: binding.topicId,
23034
23337
  parse_mode: "HTML",
@@ -23043,7 +23346,9 @@ ${code(prompt.line)}`,
23043
23346
  ]
23044
23347
  }
23045
23348
  });
23349
+ this.promptKeyboards.set(sessionId, { seq, messageId: sent.message_id });
23046
23350
  }
23351
+ promptKeyboards = new Map;
23047
23352
  awaitingSecret = new Map;
23048
23353
  async captureSecret(sessionId, msg, text) {
23049
23354
  const seq = this.awaitingSecret.get(sessionId);
@@ -23108,12 +23413,13 @@ ${code(prompt.line)}`,
23108
23413
  }
23109
23414
  if (extras.length > 0)
23110
23415
  rows2.push(extras);
23111
- await this.api.call("sendMessage", {
23416
+ const sent = await this.api.call("sendMessage", {
23112
23417
  chat_id: binding.chatId,
23113
23418
  message_thread_id: binding.topicId,
23114
23419
  text: t("tg.ask.question", { question: question.question, progress }) + (question.multi ? t("tg.ask.multiHint") : ""),
23115
23420
  reply_markup: { inline_keyboard: rows2 }
23116
23421
  });
23422
+ flow.messageId = sent.message_id;
23117
23423
  }
23118
23424
  async finishFlow(sessionId, flow, binding) {
23119
23425
  const summary = flow.questions.map((q, i) => {
@@ -23133,7 +23439,7 @@ ${code(prompt.line)}`,
23133
23439
  return;
23134
23440
  }
23135
23441
  if (flow.questions.length > 1) {
23136
- await this.api.call("sendMessage", {
23442
+ const sent = await this.api.call("sendMessage", {
23137
23443
  chat_id: binding.chatId,
23138
23444
  message_thread_id: binding.topicId,
23139
23445
  text: t("tg.ask.review", { summary }),
@@ -23141,11 +23447,29 @@ ${code(prompt.line)}`,
23141
23447
  inline_keyboard: [[{ text: t("tg.ask.submit"), callback_data: encodeSubmitCallback(flow.shortId, flow.seq) }]]
23142
23448
  }
23143
23449
  });
23450
+ flow.messageId = sent.message_id;
23144
23451
  }
23145
23452
  }
23146
- async onChoiceResolved(sessionId, seq) {
23147
- if (this.flows.get(sessionId)?.seq === seq)
23453
+ async onChoiceResolved(sessionId, seq, retracted) {
23454
+ const messageIds = [];
23455
+ const flow = this.flows.get(sessionId);
23456
+ if (flow?.seq === seq) {
23148
23457
  this.flows.delete(sessionId);
23458
+ if (flow.messageId !== undefined)
23459
+ messageIds.push(flow.messageId);
23460
+ }
23461
+ const prompt = this.promptKeyboards.get(sessionId);
23462
+ if (prompt?.seq === seq) {
23463
+ this.promptKeyboards.delete(sessionId);
23464
+ messageIds.push(prompt.messageId);
23465
+ }
23466
+ const binding = this.control.findBindingBySession(sessionId);
23467
+ if (!binding)
23468
+ return;
23469
+ for (const messageId of messageIds) {
23470
+ const edit = retracted ? this.api.call("editMessageText", { chat_id: binding.chatId, message_id: messageId, text: t("dc.ask.conversationChanged") }) : this.api.call("editMessageReplyMarkup", { chat_id: binding.chatId, message_id: messageId, reply_markup: { inline_keyboard: [] } });
23471
+ await edit.catch(() => {});
23472
+ }
23149
23473
  }
23150
23474
  async onAgentError(sessionId, kind, error, retryMs, surfaceId) {
23151
23475
  const reason = clipText(error, AGENT_ERROR_CHARS);
@@ -23299,6 +23623,16 @@ ${code(prompt.line)}`,
23299
23623
  return;
23300
23624
  if (await this.captureFreeText(binding.sessionId, text))
23301
23625
  return;
23626
+ const passthrough = parseAgentPassthrough(msg.text ?? "");
23627
+ if (passthrough) {
23628
+ this.control.write(binding.sessionId, passthrough.keystrokes);
23629
+ this.log(`session ${binding.sessionId} passthrough .${passthrough.name}`);
23630
+ await this.sendToSession(binding.sessionId, tHtml("tg.passthrough.sent", {
23631
+ command: passthrough.name,
23632
+ screen: this.control.screen(binding.sessionId, RAW_SCREEN_LINES)
23633
+ }));
23634
+ return;
23635
+ }
23302
23636
  const chatId = msg.chat.id;
23303
23637
  await this.react(chatId, msg.message_id, "received");
23304
23638
  try {
@@ -25622,19 +25956,19 @@ function readLine(secret) {
25622
25956
  stdin.on("data", onData);
25623
25957
  });
25624
25958
  }
25625
- function renderPrompt(state) {
25626
- stdout.write(`
25959
+ function renderPrompt(state, write) {
25960
+ write(`
25627
25961
  ${bold(t(state.promptKey))}
25628
25962
  `);
25629
25963
  if (state.choices) {
25630
25964
  for (const [index, choice2] of state.choices.entries()) {
25631
25965
  const marker = choice2 === state.default ? green("\u25CF") : "\u25CB";
25632
- stdout.write(` ${marker} ${index + 1}) ${choice2}
25966
+ write(` ${marker} ${index + 1}) ${choice2}
25633
25967
  `);
25634
25968
  }
25635
25969
  }
25636
25970
  const hint = state.default.length > 0 ? dim(` [${state.default}]`) : "";
25637
- stdout.write(`${dim("\u203A")}${hint} `);
25971
+ write(`${dim("\u203A")}${hint} `);
25638
25972
  }
25639
25973
  function interpret(state, input) {
25640
25974
  if (!state.choices)
@@ -25645,44 +25979,85 @@ function interpret(state, input) {
25645
25979
  }
25646
25980
  return input;
25647
25981
  }
25648
- function renderReview(state) {
25649
- stdout.write(`
25982
+ function renderReview(state, write) {
25983
+ write(`
25650
25984
  ${bold(t("wizard.review.header"))}
25651
25985
  `);
25652
25986
  for (const [key, value] of Object.entries(state.answers)) {
25653
25987
  const shown = key === "telegramToken" ? `${value.slice(0, 4)}\u2026` : value;
25654
- stdout.write(` ${key.padEnd(16)} ${shown}
25988
+ write(` ${key.padEnd(16)} ${shown}
25655
25989
  `);
25656
25990
  }
25657
25991
  }
25658
- async function runSetup(client, current = DEFAULT_CONFIG) {
25659
- stdout.write(`${bold("axterm setup")}
25992
+ function telegramDiscoveryLines(found) {
25993
+ const lines = [];
25994
+ if (found.chats.length > 0) {
25995
+ lines.push(t("cli.telegram.discoverChats"));
25996
+ for (const chat of found.chats) {
25997
+ const marks = [chat.type, chat.isForum ? undefined : t("cli.telegram.topicsOff")].filter((mark) => mark !== undefined).join(" \u2014 ");
25998
+ lines.push(` ${String(chat.id).padEnd(16)} ${(chat.title ?? "").padEnd(24)} ${marks}`);
25999
+ }
26000
+ } else {
26001
+ lines.push(t("cli.telegram.discoverNone"), t("cli.telegram.discoverHint"));
26002
+ }
26003
+ if (found.users.length > 0) {
26004
+ lines.push(t("cli.telegram.discoverUsers"));
26005
+ for (const user of found.users) {
26006
+ const label = [user.username !== undefined ? `@${user.username}` : undefined, user.name].filter((part) => part !== undefined).join(" ");
26007
+ lines.push(` ${String(user.id).padEnd(16)} ${label}`);
26008
+ }
26009
+ }
26010
+ return lines;
26011
+ }
26012
+ async function runSetup(client, current = DEFAULT_CONFIG, injectedIO) {
26013
+ const io = injectedIO ?? {
26014
+ hostname: os2.hostname().split(".")[0] ?? "",
26015
+ readLine,
26016
+ write: (text) => stdout.write(text)
26017
+ };
26018
+ io.write(`${bold("axterm setup")}
25660
26019
  ${dim(t("wizard.intro"))}
25661
26020
  `);
25662
- let state = startWizard(current, os2.hostname().split(".")[0] ?? "");
26021
+ let state = startWizard(current, io.hostname);
25663
26022
  for (;; ) {
25664
26023
  if (state.kind === "review") {
25665
- renderReview(state);
25666
- stdout.write(`
26024
+ renderReview(state, io.write);
26025
+ io.write(`
25667
26026
  ${bold(t(state.promptKey))} ${dim("[yes]")} `);
25668
- const confirm = (await readLine(false)).trim().toLowerCase();
26027
+ const confirm = (await io.readLine(false)).trim().toLowerCase();
25669
26028
  if (confirm.length > 0 && !["y", "yes"].includes(confirm)) {
25670
- stdout.write(`${dim(t("wizard.cancelled"))}
26029
+ io.write(`${dim(t("wizard.cancelled"))}
25671
26030
  `);
25672
26031
  return { saved: false };
25673
26032
  }
25674
26033
  break;
25675
26034
  }
25676
- renderPrompt(state);
25677
- const answer = interpret(state, await readLine(state.secret));
26035
+ renderPrompt(state, io.write);
26036
+ const answer = interpret(state, await io.readLine(state.secret));
25678
26037
  const result = advance(state, answer);
25679
26038
  if ("error" in result) {
25680
- stdout.write(` ${red("\u2717")} ${t(result.error)}
26039
+ io.write(` ${red("\u2717")} ${t(result.error)}
25681
26040
  `);
25682
26041
  continue;
25683
26042
  }
25684
26043
  if ("done" in result)
25685
26044
  break;
26045
+ if (state.step === "telegramToken" && result.state.step === "telegramGroup") {
26046
+ io.write(`${dim(t("wizard.telegramDiscovering"))}
26047
+ `);
26048
+ try {
26049
+ const found = await client.call("telegram.discover", {
26050
+ botToken: result.state.answers["telegramToken"]
26051
+ });
26052
+ for (const line of telegramDiscoveryLines(found))
26053
+ io.write(`${line}
26054
+ `);
26055
+ } catch (error) {
26056
+ const detail2 = error instanceof Error ? error.message : String(error);
26057
+ io.write(`${red("\u2717")} ${t("wizard.telegramDiscoverFailed", { error: detail2 })}
26058
+ `);
26059
+ }
26060
+ }
25686
26061
  state = result.state;
25687
26062
  if (state.answers["locale"] !== undefined)
25688
26063
  setLocale(state.answers["locale"]);
@@ -25696,19 +26071,19 @@ ${bold(t(state.promptKey))} ${dim("[yes]")} `);
25696
26071
  for (const [path4, value] of finished.done.secrets) {
25697
26072
  await client.call("config.set", { path: path4, value });
25698
26073
  }
25699
- stdout.write(`
26074
+ io.write(`
25700
26075
  ${green("\u2713")} ${t("wizard.saved")}
25701
26076
  `);
25702
26077
  if (finished.done.settings.some(([k, v]) => k === "channels.telegram.enabled" && v === "true")) {
25703
- stdout.write(`${dim(t("wizard.checking"))}
26078
+ io.write(`${dim(t("wizard.checking"))}
25704
26079
  `);
25705
26080
  const report = await client.call("telegram.doctor");
25706
26081
  if (report.ok)
25707
- stdout.write(`${green("\u2713")} ${t("wizard.telegramOk")}
26082
+ io.write(`${green("\u2713")} ${t("wizard.telegramOk")}
25708
26083
  `);
25709
26084
  else
25710
26085
  for (const problem of report.problems)
25711
- stdout.write(`${red("\u2717")} ${problem}
26086
+ io.write(`${red("\u2717")} ${problem}
25712
26087
  `);
25713
26088
  }
25714
26089
  return { saved: true };
@@ -26452,7 +26827,7 @@ import { stdin as stdin2, stdout as stdout2 } from "process";
26452
26827
  // packages/cli/package.json
26453
26828
  var package_default = {
26454
26829
  name: "@layorixinc/axterm",
26455
- version: "0.7.0",
26830
+ version: "0.8.0",
26456
26831
  type: "module",
26457
26832
  bin: {
26458
26833
  axterm: "dist/cli.js"
@@ -28978,23 +29353,8 @@ async function main2() {
28978
29353
  const found = await client.call("telegram.discover", {
28979
29354
  ...token !== undefined ? { botToken: token } : {}
28980
29355
  });
28981
- if (found.chats.length > 0) {
28982
- console.log(t("cli.telegram.discoverChats"));
28983
- for (const chat of found.chats) {
28984
- const marks = [chat.type, chat.isForum ? undefined : t("cli.telegram.topicsOff")].filter((m) => m !== undefined).join(" \u2014 ");
28985
- console.log(` ${String(chat.id).padEnd(16)} ${(chat.title ?? "").padEnd(24)} ${marks}`);
28986
- }
28987
- } else {
28988
- console.log(t("cli.telegram.discoverNone"));
28989
- console.log(t("cli.telegram.discoverHint"));
28990
- }
28991
- if (found.users.length > 0) {
28992
- console.log(t("cli.telegram.discoverUsers"));
28993
- for (const user of found.users) {
28994
- const label = [user.username !== undefined ? `@${user.username}` : undefined, user.name].filter((part) => part !== undefined).join(" ");
28995
- console.log(` ${String(user.id).padEnd(16)} ${label}`);
28996
- }
28997
- }
29356
+ for (const line of telegramDiscoveryLines(found))
29357
+ console.log(line);
28998
29358
  } else {
28999
29359
  throw new Error(t("cli.usage.telegram"));
29000
29360
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layorixinc/axterm",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "axterm": "dist/cli.js"