@songsid/agend 2.3.4-beta.3 → 2.3.4-beta.5

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 (73) hide show
  1. package/dist/backend/claude-code.js +1 -1
  2. package/dist/backend/claude-code.js.map +1 -1
  3. package/dist/backend/codex-session-lookup.d.ts +35 -0
  4. package/dist/backend/codex-session-lookup.js +93 -0
  5. package/dist/backend/codex-session-lookup.js.map +1 -1
  6. package/dist/backend/codex.d.ts +27 -0
  7. package/dist/backend/codex.js +116 -20
  8. package/dist/backend/codex.js.map +1 -1
  9. package/dist/backend/grok.d.ts +0 -2
  10. package/dist/backend/grok.js +0 -22
  11. package/dist/backend/grok.js.map +1 -1
  12. package/dist/backend/types.d.ts +0 -2
  13. package/dist/backend/types.js.map +1 -1
  14. package/dist/daemon.d.ts +10 -1
  15. package/dist/daemon.js +122 -80
  16. package/dist/daemon.js.map +1 -1
  17. package/dist/delivery-outbox.test.js +2 -2
  18. package/dist/delivery-outbox.test.js.map +1 -1
  19. package/dist/dialog-screen-text.d.ts +16 -0
  20. package/dist/dialog-screen-text.js +51 -0
  21. package/dist/dialog-screen-text.js.map +1 -0
  22. package/dist/fleet-manager.d.ts +4 -0
  23. package/dist/fleet-manager.js +46 -12
  24. package/dist/fleet-manager.js.map +1 -1
  25. package/dist/install-stream.d.ts +53 -0
  26. package/dist/install-stream.js +134 -0
  27. package/dist/install-stream.js.map +1 -0
  28. package/dist/instance-lifecycle.d.ts +12 -0
  29. package/dist/instance-lifecycle.js +31 -2
  30. package/dist/instance-lifecycle.js.map +1 -1
  31. package/dist/locale.js +26 -6
  32. package/dist/locale.js.map +1 -1
  33. package/dist/log-service.d.ts +2 -0
  34. package/dist/log-service.js +7 -3
  35. package/dist/log-service.js.map +1 -1
  36. package/dist/login-manager.d.ts +6 -0
  37. package/dist/login-manager.js +10 -1
  38. package/dist/login-manager.js.map +1 -1
  39. package/dist/outbound-handlers.js +56 -0
  40. package/dist/outbound-handlers.js.map +1 -1
  41. package/dist/outbound-schemas.d.ts +1 -0
  42. package/dist/outbound-schemas.js +3 -0
  43. package/dist/outbound-schemas.js.map +1 -1
  44. package/dist/selection-card.d.ts +2 -3
  45. package/dist/selection-card.js +99 -26
  46. package/dist/selection-card.js.map +1 -1
  47. package/dist/tmux-manager.d.ts +6 -0
  48. package/dist/tmux-manager.js +17 -0
  49. package/dist/tmux-manager.js.map +1 -1
  50. package/dist/ui/chat-store.js +20 -3
  51. package/dist/ui/fleet-canvas.js +4 -3
  52. package/dist/ui/fleet-office-model.js +55 -22
  53. package/dist/ui/fleet-office-overview.js +6 -38
  54. package/dist/ui/fleet-office-skin.js +12 -4
  55. package/dist/ui/fleet-office-store.js +5 -0
  56. package/dist/ui/fleet-office-zones.js +2 -2
  57. package/dist/ui/fleet-office.js +31 -1
  58. package/dist/ui/panel-log.js +7 -1
  59. package/dist/ui/shared/app-i18n.js +2 -2
  60. package/dist/ui/shared/app-shell.js +4 -2
  61. package/dist/ui/shared/app-stream.js +26 -0
  62. package/dist/ui/shared/app.css +6 -0
  63. package/dist/unattended-install.d.ts +6 -0
  64. package/dist/unattended-install.js +65 -0
  65. package/dist/unattended-install.js.map +1 -0
  66. package/dist/web-api.js +7 -2
  67. package/dist/web-api.js.map +1 -1
  68. package/dist/web-chat-history.d.ts +20 -0
  69. package/dist/web-chat-history.js +21 -0
  70. package/dist/web-chat-history.js.map +1 -1
  71. package/dist/web-guest-access.js +5 -1
  72. package/dist/web-guest-access.js.map +1 -1
  73. package/package.json +1 -1
@@ -0,0 +1,53 @@
1
+ export declare const INSTALL_STREAM_LINES = 15;
2
+ export declare const INSTALL_STREAM_BYTES = 1500;
3
+ export declare const INSTALL_STREAM_EDIT_MIN_MS = 5000;
4
+ export interface InstallStreamSink {
5
+ /** Post the message (its plain status line); its id is what the edits change. */
6
+ send(text: string): Promise<{
7
+ messageId: string;
8
+ } | undefined>;
9
+ /** Replace the message's text: a code block under the status line, in the stream's style (markdown or html). */
10
+ edit(messageId: string, text: string): Promise<void>;
11
+ warn?(message: string, err: unknown): void;
12
+ }
13
+ export interface InstallStreamClock {
14
+ now(): number;
15
+ setTimeout(fn: () => void, ms: number): unknown;
16
+ clearTimeout(handle: unknown): void;
17
+ }
18
+ /**
19
+ * The window's own text, without what tmux adds to it: a dead pane's "Pane is dead (status N, <date>)" line (drawn on
20
+ * the bottom row, below a screenful of blank rows), and runs of blank rows collapsed to one — else the last lines of a
21
+ * finished install would be blanks and that line.
22
+ */
23
+ export declare function installerOutput(pane: string): string;
24
+ /**
25
+ * How a frame is written: "markdown" (a fenced code block — Discord) or "html" (Telegram's own `<pre>`: its markdown
26
+ * conversion keeps a fence as literal backticks).
27
+ */
28
+ export type InstallFrameStyle = "markdown" | "html";
29
+ /** The window's last lines as a code block under a status line; just the status line when there are none. */
30
+ export declare function renderInstallFrame(header: string, pane: string, style?: InstallFrameStyle): string;
31
+ export declare class InstallStream {
32
+ private readonly sink;
33
+ private readonly header;
34
+ private readonly clock;
35
+ private readonly editMinMs;
36
+ private readonly style;
37
+ private messageId;
38
+ private shown;
39
+ private wanted;
40
+ private pane;
41
+ private lastAt;
42
+ private timer;
43
+ private inFlight;
44
+ private closed;
45
+ constructor(sink: InstallStreamSink, header: string, clock?: InstallStreamClock, editMinMs?: number, style?: InstallFrameStyle);
46
+ /** A new capture of the window. Never awaits; nothing is posted until the window shows something. */
47
+ update(pane: string): void;
48
+ /** Stop editing and write the final frame (the last capture, unless a later one is given) over the message. */
49
+ finish(header: string, pane?: string): Promise<void>;
50
+ private schedule;
51
+ /** `header`: the frame's status line as plain text — what a new message is first posted as. */
52
+ private write;
53
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * M-2281 (#2980): the installer's output, live, in the chat that asked `/login` to install a CLI. No page shows the
3
+ * `agend-login-<backend>` window (it is not an instance), so the chat is where its progress is seen.
4
+ *
5
+ * One message, edited in place: the last INSTALL_STREAM_LINES lines of the window, ANSI stripped and secrets masked
6
+ * (dialogScreenText — the whole capture is masked before its bottom is kept), under INSTALL_STREAM_BYTES so it fits
7
+ * a Discord (2000) or Telegram (4096) message. At most one edit per INSTALL_STREAM_EDIT_MIN_MS, one in flight, none
8
+ * for an unchanged frame. The frames come from the session's own poll capture — nothing here reads the pane.
9
+ *
10
+ * Fire-and-forget from the poll: update() only records the frame and arms a timer. finish() stops the edits and
11
+ * writes the final frame (success / failed / timed out / cancelled) over the same message.
12
+ */
13
+ import { dialogScreenText } from "./dialog-screen-text.js";
14
+ export const INSTALL_STREAM_LINES = 15;
15
+ export const INSTALL_STREAM_BYTES = 1_500;
16
+ export const INSTALL_STREAM_EDIT_MIN_MS = 5_000;
17
+ const realClock = {
18
+ now: () => performance.now(),
19
+ setTimeout: (fn, ms) => { const t = setTimeout(fn, ms); t.unref?.(); return t; },
20
+ clearTimeout: handle => clearTimeout(handle),
21
+ };
22
+ /**
23
+ * The window's own text, without what tmux adds to it: a dead pane's "Pane is dead (status N, <date>)" line (drawn on
24
+ * the bottom row, below a screenful of blank rows), and runs of blank rows collapsed to one — else the last lines of a
25
+ * finished install would be blanks and that line.
26
+ */
27
+ export function installerOutput(pane) {
28
+ return pane.replace(/^Pane is dead\b.*$/gm, "").replace(/\n(?:[ \t]*\n){2,}/g, "\n\n");
29
+ }
30
+ const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
31
+ /** The window's last lines as a code block under a status line; just the status line when there are none. */
32
+ export function renderInstallFrame(header, pane, style = "markdown") {
33
+ const body = dialogScreenText(installerOutput(pane), { lines: INSTALL_STREAM_LINES, bytes: INSTALL_STREAM_BYTES });
34
+ if (!body)
35
+ return style === "html" ? escapeHtml(header) : header;
36
+ // HTML: everything escaped, the only tag is ours. Markdown: a fence inside the output would close the block early,
37
+ // so its backticks become look-alikes.
38
+ return style === "html"
39
+ ? `${escapeHtml(header)}\n<pre>${escapeHtml(body)}</pre>`
40
+ : `${header}\n\`\`\`\n${body.replace(/`/g, "ˋ")}\n\`\`\``;
41
+ }
42
+ export class InstallStream {
43
+ sink;
44
+ header;
45
+ clock;
46
+ editMinMs;
47
+ style;
48
+ messageId;
49
+ shown = "";
50
+ wanted = "";
51
+ pane = "";
52
+ lastAt = Number.NEGATIVE_INFINITY;
53
+ timer = null;
54
+ inFlight = null;
55
+ closed = false;
56
+ constructor(sink, header, clock = realClock, editMinMs = INSTALL_STREAM_EDIT_MIN_MS, style = "markdown") {
57
+ this.sink = sink;
58
+ this.header = header;
59
+ this.clock = clock;
60
+ this.editMinMs = editMinMs;
61
+ this.style = style;
62
+ }
63
+ /** A new capture of the window. Never awaits; nothing is posted until the window shows something. */
64
+ update(pane) {
65
+ if (this.closed)
66
+ return;
67
+ this.pane = pane;
68
+ const frame = renderInstallFrame(this.header, pane, this.style);
69
+ if (frame === renderInstallFrame(this.header, "", this.style) && this.messageId === undefined)
70
+ return;
71
+ this.wanted = frame;
72
+ this.schedule();
73
+ }
74
+ /** Stop editing and write the final frame (the last capture, unless a later one is given) over the message. */
75
+ async finish(header, pane) {
76
+ this.closed = true;
77
+ if (this.timer !== null) {
78
+ this.clock.clearTimeout(this.timer);
79
+ this.timer = null;
80
+ }
81
+ await this.inFlight;
82
+ const frame = renderInstallFrame(header, pane ?? this.pane, this.style);
83
+ // Nothing was ever shown: the result message says it all.
84
+ if (this.messageId === undefined && frame === renderInstallFrame(header, "", this.style))
85
+ return;
86
+ // The final edit keeps the rate too: it waits out what is left of the interval rather than skip it.
87
+ const wait = this.lastAt + this.editMinMs - this.clock.now();
88
+ if (wait > 0)
89
+ await new Promise(resolve => this.clock.setTimeout(resolve, wait));
90
+ await this.write(frame, header);
91
+ }
92
+ schedule() {
93
+ if (this.timer !== null || this.inFlight || this.wanted === this.shown)
94
+ return;
95
+ const delay = Math.max(0, this.lastAt + this.editMinMs - this.clock.now());
96
+ this.timer = this.clock.setTimeout(() => {
97
+ this.timer = null;
98
+ if (this.closed)
99
+ return;
100
+ const frame = this.wanted;
101
+ this.inFlight = this.write(frame, this.header).finally(() => {
102
+ this.inFlight = null;
103
+ if (!this.closed)
104
+ this.schedule();
105
+ });
106
+ }, delay);
107
+ }
108
+ /** `header`: the frame's status line as plain text — what a new message is first posted as. */
109
+ async write(frame, header) {
110
+ if (frame === this.shown)
111
+ return;
112
+ this.lastAt = this.clock.now();
113
+ try {
114
+ if (this.messageId === undefined) {
115
+ // Posted as its plain status line, and every frame goes in by an edit: the message is then one kind from start
116
+ // to end (Telegram posts a text with a code block as a rich message, but edits text with HTML).
117
+ const sent = await this.sink.send(header);
118
+ if (!sent?.messageId)
119
+ return;
120
+ this.messageId = sent.messageId;
121
+ await this.sink.edit(this.messageId, frame);
122
+ }
123
+ else {
124
+ await this.sink.edit(this.messageId, frame);
125
+ }
126
+ this.shown = frame;
127
+ }
128
+ catch (err) {
129
+ // A refused edit (rate limit, a reconnect) is tried again with the next frame, after the interval.
130
+ this.sink.warn?.("install output edit failed", err);
131
+ }
132
+ }
133
+ }
134
+ //# sourceMappingURL=install-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-stream.js","sourceRoot":"","sources":["../src/install-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AACvC,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAC1C,MAAM,CAAC,MAAM,0BAA0B,GAAG,KAAK,CAAC;AAgBhD,MAAM,SAAS,GAAuB;IACpC,GAAG,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;IAC5B,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC,GAAG,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAChF,YAAY,EAAE,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,MAAwB,CAAC;CAC/D,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACzF,CAAC;AAQD,MAAM,UAAU,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAE/G,6GAA6G;AAC7G,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,IAAY,EAAE,KAAK,GAAsB,UAAU;IACpG,MAAM,IAAI,GAAG,gBAAgB,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;IACnH,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACjE,mHAAmH;IACnH,uCAAuC;IACvC,OAAO,KAAK,KAAK,MAAM;QACrB,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,UAAU,UAAU,CAAC,IAAI,CAAC,QAAQ;QACzD,CAAC,CAAC,GAAG,MAAM,aAAa,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,UAAU,CAAC;AAC9D,CAAC;AAED,MAAM,OAAO,aAAa;IAWL,IAAI;IACJ,MAAM;IACN,KAAK;IACL,SAAS;IACT,KAAK;IAdhB,SAAS,CAAqB;IAC9B,KAAK,GAAG,EAAE,CAAC;IACX,MAAM,GAAG,EAAE,CAAC;IACZ,IAAI,GAAG,EAAE,CAAC;IACV,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAClC,KAAK,GAAY,IAAI,CAAC;IACtB,QAAQ,GAAyB,IAAI,CAAC;IACtC,MAAM,GAAG,KAAK,CAAC;IAEvB,YACmB,IAAuB,EACvB,MAAc,EACd,KAAK,GAAuB,SAAS,EACrC,SAAS,GAAG,0BAA0B,EACtC,KAAK,GAAsB,UAAU;oBAJrC,IAAI;sBACJ,MAAM;qBACN,KAAK;yBACL,SAAS;qBACT,KAAK;IACrB,CAAC;IAEJ,qGAAqG;IACrG,MAAM,CAAC,IAAY;QACjB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAChE,IAAI,KAAK,KAAK,kBAAkB,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO;QACtG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAED,+GAA+G;IAC/G,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,IAAa;QACxC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAAC,CAAC;QACpF,MAAM,IAAI,CAAC,QAAQ,CAAC;QACpB,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACxE,0DAA0D;QAC1D,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,KAAK,kBAAkB,CAAC,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO;QACjG,oGAAoG;QACpG,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAC7D,IAAI,IAAI,GAAG,CAAC;YAAE,MAAM,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QACvF,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC;IAEO,QAAQ;QACd,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK;YAAE,OAAO;QAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE;YACtC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO;YACxB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;YAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC1D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,IAAI,CAAC,MAAM;oBAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC;IAED,+FAA+F;IACvF,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,MAAc;QAC/C,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK;YAAE,OAAO;QACjC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBACjC,+GAA+G;gBAC/G,gGAAgG;gBAChG,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC1C,IAAI,CAAC,IAAI,EAAE,SAAS;oBAAE,OAAO;gBAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;gBAChC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YAC9C,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YAC9C,CAAC;YACD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,mGAAmG;YACnG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;CACF"}
@@ -41,6 +41,11 @@ export interface BackendInstallationInfo {
41
41
  export declare const BACKEND_INSTALLATION_INFO: Readonly<Record<string, BackendInstallationInfo>>;
42
42
  /** Check one executable using the same PATH visible to the fleet process. */
43
43
  export declare function checkBinaryInstalled(binary: string): boolean;
44
+ /**
45
+ * M-2281 (#2980): the same check without holding the fleet loop — for paths a chat click runs (`/login`'s install
46
+ * routing), where a slow PATH (WSL's /mnt/c entries) would otherwise stall every adapter for up to the timeout.
47
+ */
48
+ export declare function checkBinaryInstalledAsync(binary: string): Promise<boolean>;
44
49
  /**
45
50
  * Return why an instance daemon PID must not receive SIGTERM, or null when the
46
51
  * stale-process cleanup may proceed. daemon.pid historically stores the shared
@@ -366,6 +371,13 @@ export declare class InstanceLifecycle {
366
371
  /** Max retries before giving up and pausing. */
367
372
  private static readonly CAPACITY_BACKOFF_MAX;
368
373
  constructor(ctx: LifecycleContext);
374
+ /** M-2269: the last dialog text logged per instance — the same screen seen again on later scans is not logged again. */
375
+ private readonly lastLoggedScreen;
376
+ /**
377
+ * M-2269: `{ screen }` for a dialog/prompt event's Log row (the daemon already stripped, masked and capped it), or
378
+ * nothing when there is none or it is the same text this instance's Log already has. Bookkeeping only.
379
+ */
380
+ private screenForLog;
369
381
  /** Seed only the Codex daemon replacing a scheduled capacity retry. */
370
382
  private applyCapacityBackoffBaseline;
371
383
  /**
@@ -5,6 +5,7 @@ import { existsSync, readFileSync, mkdirSync, realpathSync, writeFileSync, statS
5
5
  import { ensureInstanceDir } from "./private-dir.js";
6
6
  import { assertNoSessionOomStop } from "./session-oom-stop.js";
7
7
  import { execFileSync } from "node:child_process";
8
+ import { execFile } from "./child-process.js";
8
9
  import { join, basename, dirname, resolve, sep as pathSep } from "node:path";
9
10
  import { access } from "node:fs/promises";
10
11
  import { getAgendHome, ensureWorkspaceGit } from "./paths.js";
@@ -65,6 +66,20 @@ export const BACKEND_INSTALLATION_INFO = {
65
66
  export function checkBinaryInstalled(binary) {
66
67
  return measureSyncWork("lifecycle.checkBinaryInstalled", () => checkBinaryInstalledSync(binary));
67
68
  }
69
+ /**
70
+ * M-2281 (#2980): the same check without holding the fleet loop — for paths a chat click runs (`/login`'s install
71
+ * routing), where a slow PATH (WSL's /mnt/c entries) would otherwise stall every adapter for up to the timeout.
72
+ */
73
+ export function checkBinaryInstalledAsync(binary) {
74
+ return new Promise(resolve => {
75
+ try {
76
+ execFile("which", [binary], { timeout: 2000 }, err => resolve(!err));
77
+ }
78
+ catch {
79
+ resolve(false);
80
+ }
81
+ });
82
+ }
68
83
  function checkBinaryInstalledSync(binary) {
69
84
  try {
70
85
  execFileSync("which", [binary], { stdio: "pipe", timeout: 2000 });
@@ -322,6 +337,20 @@ export class InstanceLifecycle {
322
337
  constructor(ctx) {
323
338
  this.ctx = ctx;
324
339
  }
340
+ /** M-2269: the last dialog text logged per instance — the same screen seen again on later scans is not logged again. */
341
+ lastLoggedScreen = new Map();
342
+ /**
343
+ * M-2269: `{ screen }` for a dialog/prompt event's Log row (the daemon already stripped, masked and capped it), or
344
+ * nothing when there is none or it is the same text this instance's Log already has. Bookkeeping only.
345
+ */
346
+ screenForLog(name, screen) {
347
+ if (typeof screen !== "string" || !screen)
348
+ return {};
349
+ if (this.lastLoggedScreen.get(name) === screen)
350
+ return {};
351
+ this.lastLoggedScreen.set(name, screen);
352
+ return { screen };
353
+ }
325
354
  /** Seed only the Codex daemon replacing a scheduled capacity retry. */
326
355
  applyCapacityBackoffBaseline(name, backendName, daemon) {
327
356
  if (backendName !== "codex")
@@ -937,7 +966,7 @@ export class InstanceLifecycle {
937
966
  // position, so the human must LOOK and pick the option explicitly — no
938
967
  // fixed key sequence; a known dialog whose auto-dismiss did not take gets
939
968
  // the keys we would have pressed.
940
- this.ctx.eventLog?.insert(name, "dialog_parked", { description: data.description, holdOnly: data.holdOnly });
969
+ this.ctx.eventLog?.insert(name, "dialog_parked", { description: data.description, holdOnly: data.holdOnly, ...this.screenForLog(name, data.screen) });
941
970
  this.ctx.logger.warn({ name, description: data.description, holdOnly: data.holdOnly }, "Instance is parked on a CLI dialog — not auto-answering");
942
971
  if (this.ctx.isPlannedRestart())
943
972
  return;
@@ -983,7 +1012,7 @@ export class InstanceLifecycle {
983
1012
  // #2822: a usage-limit hold began or ended — Needs you lists it while it lasts.
984
1013
  daemon.on("usage_hold", () => this.ctx.onAttentionChanged?.(name));
985
1014
  daemon.on("interactive_prompt", safeHandler(async (data) => {
986
- this.ctx.eventLog?.insert(name, "interactive_prompt", { kind: data.kind });
1015
+ this.ctx.eventLog?.insert(name, "interactive_prompt", { kind: data.kind, ...this.screenForLog(name, data.screen) });
987
1016
  this.ctx.logger.warn({ name, kind: data.kind, prompt: data.prompt }, "Instance is waiting for interactive terminal input");
988
1017
  if (this.ctx.isPlannedRestart()) {
989
1018
  this.ctx.logger.info({ name }, "Interactive prompt notification suppressed — planned restart in progress");