@agentdeck/bridge 1.0.9 → 1.0.11

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.
@@ -1,3 +1,3 @@
1
1
  import type { AgentType } from './types.js';
2
- export declare function advertiseUdpBroadcast(port: number, projectName: string, agentType: AgentType, token?: string): () => void;
2
+ export declare function advertiseUdpBroadcast(port: number, projectName: string, agentType: AgentType): () => void;
3
3
  //# sourceMappingURL=broadcast.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsC5C,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,SAAS,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,MAAM,IAAI,CAoFZ"}
1
+ {"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAwC5C,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,SAAS,GACnB,MAAM,IAAI,CAsFZ"}
package/dist/broadcast.js CHANGED
@@ -14,10 +14,12 @@ import { getLanIp } from '@agentdeck/shared';
14
14
  * (and 255.255.255.255 as a fallback) on UDP port 9121 every 2 seconds.
15
15
  * Subnet broadcast is unicast-friendly IGMP-wise and is forwarded by every
16
16
  * WiFi AP we have seen, so the device's UDP listener picks it up even when
17
- * mDNS is blocked. The beacon reuses the same fields the device needs from
18
- * the mDNS TXT records (ip / port / project / agent / token) so the existing
19
- * selection logic in mdns_discovery.cpp and udp_discovery.cpp share the same
20
- * BridgeInfo contract.
17
+ * mDNS is blocked. The beacon contains discovery metadata only (ip / port /
18
+ * project / agent) plus `authRequired`, matching the unauthenticated /health
19
+ * semantics: discovery says "a daemon is here and pairing is required" and
20
+ * nothing more. It must never contain the pairing token: UDP broadcast is
21
+ * visible to every peer on the segment, just like multicast mDNS. A client
22
+ * must already hold a token obtained through explicit pairing/provisioning.
21
23
  */
22
24
  const UDP_DISCOVERY_PORT = 9121;
23
25
  const UDP_BROADCAST_INTERVAL_MS = 2_000;
@@ -34,7 +36,7 @@ function computeSubnetBroadcast(ip) {
34
36
  }
35
37
  return `${parts[0]}.${parts[1]}.${parts[2]}.255`;
36
38
  }
37
- export function advertiseUdpBroadcast(port, projectName, agentType, token) {
39
+ export function advertiseUdpBroadcast(port, projectName, agentType) {
38
40
  let stopped = false;
39
41
  let socket = null;
40
42
  let timer = null;
@@ -74,9 +76,10 @@ export function advertiseUdpBroadcast(port, projectName, agentType, token) {
74
76
  port,
75
77
  project: projectName,
76
78
  agent: agentType,
79
+ // Same signal the unauthenticated /health carries: tells a client to
80
+ // open its pairing flow instead of expecting a self-serve credential.
81
+ authRequired: true,
77
82
  };
78
- if (token)
79
- payload.token = token;
80
83
  const buf = Buffer.from(JSON.stringify(payload));
81
84
  // Limited broadcast — accepted by every AP, never routed across subnets.
82
85
  // This is the primary path when IGMP Snooping drops multicast.
@@ -1 +1 @@
1
- {"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAE/B,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAChC,MAAM,yBAAyB,GAAG,KAAK,CAAC;AACxC,MAAM,oBAAoB,GAAG,GAAG,CAAC,CAAC,kDAAkD;AAEpF,oFAAoF;AACpF,SAAS,sBAAsB,CAAC,EAAU;IACxC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG;YAAE,OAAO,IAAI,CAAC;IAC5D,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,IAAY,EACZ,WAAmB,EACnB,SAAoB,EACpB,KAAc;IAEd,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,MAAM,GAAwB,IAAI,CAAC;IACvC,IAAI,KAAK,GAA0C,IAAI,CAAC;IACxD,IAAI,YAAY,GAAyC,IAAI,CAAC;IAE9D,IAAI,CAAC;QACH,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YAChC,KAAK,CAAC,WAAW,EAAE,2BAA2B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACxB,2EAA2E;YAC3E,6EAA6E;QAC/E,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACf,IAAI,CAAC;gBACH,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,KAAK,CAAC,WAAW,EAAE,wBAAwB,GAAG,EAAE,CAAC,CAAC;YACpD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,CAAC,WAAW,EAAE,8BAA8B,GAAG,EAAE,CAAC,CAAC;QACxD,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,QAAQ,GAAG,GAAS,EAAE;QAC1B,IAAI,OAAO,IAAI,CAAC,MAAM;YAAE,OAAO;QAC/B,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;YACpC,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAoC;YAC/C,CAAC,EAAE,CAAC;YACJ,EAAE,EAAE,KAAK;YACT,IAAI;YACJ,OAAO,EAAE,WAAW;YACpB,KAAK,EAAE,SAAS;SACjB,CAAC;QACF,IAAI,KAAK;YAAE,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;QACjC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QAEjD,yEAAyE;QACzE,+DAA+D;QAC/D,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,CAAC,GAAG,EAAE,EAAE;YAC9D,IAAI,GAAG;gBAAE,KAAK,CAAC,WAAW,EAAE,kCAAkC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;QAEH,wEAAwE;QACxE,wEAAwE;QACxE,mEAAmE;QACnE,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;gBACnD,IAAI,GAAG;oBAAE,KAAK,CAAC,WAAW,EAAE,iCAAiC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9E,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;IAEF,GAAG,CAAC,4CAA4C,kBAAkB,cAAc,WAAW,GAAG,CAAC,CAAC;IAChG,YAAY,GAAG,UAAU,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;IAC1D,KAAK,GAAG,WAAW,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;IAEzD,OAAO,GAAG,EAAE;QACV,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,YAAY,EAAE,CAAC;YACjB,YAAY,CAAC,YAAY,CAAC,CAAC;YAC3B,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;QACD,IAAI,KAAK,EAAE,CAAC;YACV,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;QACD,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC;gBACH,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC;YAAC,MAAM,CAAC;gBACP,yCAAyC;YAC3C,CAAC;YACD,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"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"}
@@ -22,10 +22,37 @@
22
22
  /** Locate `rollout-*-<sessionId>.jsonl` under the newest day dirs. */
23
23
  export declare function locateCodexRollout(sessionId: string, sessionsRoot?: string): string | null;
24
24
  /**
25
- * Parse the rollout tail for the turn's final response, newest record
26
- * first. `task_complete.last_agent_message` wins (authoritative turn
27
- * close); otherwise the last `agent_message` body Codex emits
28
- * mid-turn `commentary` messages too, and the final one is the reply.
25
+ * What the rollout says happened on a turn.
26
+ *
27
+ * A turn ends one of two ways and the tail records both, but only one used to
28
+ * be read. `text` is the assistant's reply; `error` is the failure Codex wrote
29
+ * instead — quota exhausted, a model the account may not use, a stream that
30
+ * died. Codex does not run its `Stop` hook on a failed turn, so this file is
31
+ * the ONLY place the failure is observable, and dropping it is what left a
32
+ * turn spinning with its cause sitting unread on disk (issue: "ChatGPT free
33
+ * request fails, timeline stays in progress").
34
+ */
35
+ export interface CodexTurnOutcome {
36
+ /** Assistant reply, empty when the turn produced none. */
37
+ text: string;
38
+ /** Human-readable failure text, when the turn ended in one. */
39
+ error?: string;
40
+ /** Codex's own classification, e.g. `usage_limit_exceeded`. */
41
+ errorKind?: string;
42
+ }
43
+ /**
44
+ * Parse the rollout tail for how the turn ended, newest record first.
45
+ *
46
+ * The scan stops at the turn's own opening record (`task_started` /
47
+ * `user_message`). Without that stop a failed turn — which contributes no
48
+ * `agent_message` of its own — kept walking into the PREVIOUS turn and
49
+ * returned its reply as this one's, quietly attributing old text to a request
50
+ * that never produced any.
51
+ */
52
+ export declare function codexTurnOutcomeFromRollout(sessionId: string, sessionsRoot?: string): CodexTurnOutcome;
53
+ /**
54
+ * Back-compat shim: the turn's reply text only.
55
+ * Prefer `codexTurnOutcomeFromRollout`, which also reports failures.
29
56
  */
30
57
  export declare function lastAgentMessageFromCodexRollout(sessionId: string, sessionsRoot?: string): string;
31
58
  //# sourceMappingURL=codex-rollout-response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex-rollout-response.d.ts","sourceRoot":"","sources":["../src/codex-rollout-response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA0BH,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmB1F;AAuBD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAiCjG"}
1
+ {"version":3,"file":"codex-rollout-response.d.ts","sourceRoot":"","sources":["../src/codex-rollout-response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA0BH,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmB1F;AAuBD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAqDtG;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjG"}
@@ -86,18 +86,44 @@ function readTail(path, maxBytes) {
86
86
  }
87
87
  }
88
88
  }
89
+ /** Longest error text worth carrying onto a timeline row. */
90
+ const MAX_ERROR_CHARS = 400;
91
+ function cleanErrorText(raw) {
92
+ if (typeof raw !== 'string')
93
+ return undefined;
94
+ let text = raw.trim();
95
+ if (!text)
96
+ return undefined;
97
+ // Codex nests the upstream JSON body inside `message` for HTTP failures.
98
+ // The useful sentence is the inner `message`; the envelope is noise.
99
+ if (text.startsWith('{')) {
100
+ try {
101
+ const inner = JSON.parse(text);
102
+ const nested = (inner.message ?? inner.error?.message);
103
+ if (typeof nested === 'string' && nested.trim())
104
+ text = nested.trim();
105
+ }
106
+ catch { /* not JSON after all — keep the raw text */ }
107
+ }
108
+ return text.length > MAX_ERROR_CHARS ? `${text.slice(0, MAX_ERROR_CHARS - 1)}…` : text;
109
+ }
89
110
  /**
90
- * Parse the rollout tail for the turn's final response, newest record
91
- * first. `task_complete.last_agent_message` wins (authoritative turn
92
- * close); otherwise the last `agent_message` body Codex emits
93
- * mid-turn `commentary` messages too, and the final one is the reply.
111
+ * Parse the rollout tail for how the turn ended, newest record first.
112
+ *
113
+ * The scan stops at the turn's own opening record (`task_started` /
114
+ * `user_message`). Without that stop a failed turn which contributes no
115
+ * `agent_message` of its own — kept walking into the PREVIOUS turn and
116
+ * returned its reply as this one's, quietly attributing old text to a request
117
+ * that never produced any.
94
118
  */
95
- export function lastAgentMessageFromCodexRollout(sessionId, sessionsRoot) {
119
+ export function codexTurnOutcomeFromRollout(sessionId, sessionsRoot) {
96
120
  const path = locateCodexRollout(sessionId, sessionsRoot);
97
121
  if (!path)
98
- return '';
122
+ return { text: '' };
99
123
  const lines = readTail(path, TAIL_BYTES).split('\n');
100
- let lastAgentMessage = '';
124
+ let text = '';
125
+ let error;
126
+ let errorKind;
101
127
  for (let i = lines.length - 1; i >= 0; i--) {
102
128
  const line = lines[i].trim();
103
129
  if (!line)
@@ -114,22 +140,44 @@ export function lastAgentMessageFromCodexRollout(sessionId, sessionsRoot) {
114
140
  const payload = record.payload;
115
141
  if (!payload || typeof payload !== 'object')
116
142
  continue;
117
- if (payload.type === 'task_complete' && typeof payload.last_agent_message === 'string'
118
- && payload.last_agent_message.trim()) {
119
- return payload.last_agent_message.trim();
143
+ // Turn boundary everything above belongs to an earlier turn.
144
+ if (payload.type === 'task_started' || payload.type === 'user_message')
145
+ break;
146
+ if (payload.type === 'error') {
147
+ error ??= cleanErrorText(payload.message);
148
+ if (typeof payload.codex_error_info === 'string')
149
+ errorKind ??= payload.codex_error_info;
150
+ continue;
120
151
  }
121
- if (!lastAgentMessage && payload.type === 'agent_message'
152
+ if (payload.type === 'task_complete') {
153
+ const failure = payload.error;
154
+ if (failure && typeof failure === 'object') {
155
+ error ??= cleanErrorText(failure.message);
156
+ if (typeof failure.codex_error_info === 'string')
157
+ errorKind ??= failure.codex_error_info;
158
+ }
159
+ if (typeof payload.last_agent_message === 'string' && payload.last_agent_message.trim()) {
160
+ return { text: payload.last_agent_message.trim(), error, errorKind };
161
+ }
162
+ continue;
163
+ }
164
+ if (!text && payload.type === 'agent_message'
122
165
  && typeof payload.message === 'string' && payload.message.trim()) {
123
- lastAgentMessage = payload.message.trim();
124
- // Keep scanning upward only for a task_complete that might supersede;
125
- // but task_complete always FOLLOWS agent_message in the log, so if we
126
- // reached an agent_message first (scanning backwards) there is no
127
- // newer task_complete — return immediately.
166
+ text = payload.message.trim();
167
+ // task_complete always FOLLOWS agent_message, so scanning backwards past
168
+ // this point can only reach older records.
128
169
  break;
129
170
  }
130
171
  }
131
- if (!lastAgentMessage)
132
- debug('codex-rollout', `no agent_message in tail of ${path}`);
133
- return lastAgentMessage;
172
+ if (!text && !error)
173
+ debug('codex-rollout', `no agent_message or error in tail of ${path}`);
174
+ return { text, error, errorKind };
175
+ }
176
+ /**
177
+ * Back-compat shim: the turn's reply text only.
178
+ * Prefer `codexTurnOutcomeFromRollout`, which also reports failures.
179
+ */
180
+ export function lastAgentMessageFromCodexRollout(sessionId, sessionsRoot) {
181
+ return codexTurnOutcomeFromRollout(sessionId, sessionsRoot).text;
134
182
  }
135
183
  //# sourceMappingURL=codex-rollout-response.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex-rollout-response.js","sourceRoot":"","sources":["../src/codex-rollout-response.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"codex-rollout-response.js","sourceRoot":"","sources":["../src/codex-rollout-response.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-server.d.ts","sourceRoot":"","sources":["../src/daemon-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAgH1E,OAAO,EAA8D,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA8P/G;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC5C,YAAY,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAClD,OAAO,CAST;AAkBD,iBAAS,iBAAiB,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CA+C1E;AAYD,iBAAS,yBAAyB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAStD;AAED,iBAAS,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAsBlE;AA6ED,gBAAgB;AAChB,wBAAgB,uBAAuB,CAAC,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKzH;AACD,gBAAgB;AAChB,wBAAgB,wBAAwB,IAAI,IAAI,CAS/C;AACD,gBAAgB;AAChB,eAAO,MAAM,gBAAgB;;;;;CAK5B,CAAC;AAEF,iBAAe,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA8F3H;AAoHD,iEAAiE;AACjE,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAsBtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EACnH,KAAK,EAAE,CAAC,GACP,CAAC,CAWH;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,aAAa,CAAA;CAAE,CAa3F;AAID,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA+HD,wBAAsB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA4gIpE"}
1
+ {"version":3,"file":"daemon-server.d.ts","sourceRoot":"","sources":["../src/daemon-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAgH1E,OAAO,EAA8D,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA8P/G;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC5C,YAAY,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAClD,OAAO,CAST;AAkBD,iBAAS,iBAAiB,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CA+C1E;AAYD,iBAAS,yBAAyB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAStD;AAED,iBAAS,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAsBlE;AA6ED,gBAAgB;AAChB,wBAAgB,uBAAuB,CAAC,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKzH;AACD,gBAAgB;AAChB,wBAAgB,wBAAwB,IAAI,IAAI,CAS/C;AACD,gBAAgB;AAChB,eAAO,MAAM,gBAAgB;;;;;CAK5B,CAAC;AAEF,iBAAe,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA8F3H;AAoHD,iEAAiE;AACjE,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAsBtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EACnH,KAAK,EAAE,CAAC,GACP,CAAC,CAWH;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,aAAa,CAAA;CAAE,CAa3F;AAID,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA+HD,wBAAsB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgkIpE"}
@@ -84,7 +84,7 @@ import { readModelFromTranscript } from './apme/claude-transcript-reader.js';
84
84
  import { transcriptTimelineForSession, lastAssistantTextFromTranscript, lastAssistantTextForSession, } from './session-transcript-timeline.js';
85
85
  import { DeviceVoiceReplyRouter, speakableReply, spokenDigest, pcmFromWav, } from './device-voice-reply.js';
86
86
  import { rawSessionId } from '@agentdeck/shared';
87
- import { lastAgentMessageFromCodexRollout } from './codex-rollout-response.js';
87
+ import { codexTurnOutcomeFromRollout, lastAgentMessageFromCodexRollout } from './codex-rollout-response.js';
88
88
  import { callFoundationModelsHelper } from './foundation-models-helper.js';
89
89
  import { initModules, stopModules, createDefaultModules, } from './modules/index.js';
90
90
  import { esp32ConnectionCount, getESP32DeviceInfo, onESP32Message, sendWifiProvisionToAll, handleESP32Wake, getESP32Ports, getSerialConnectionStatus, getSerialLastError, getSerialReachableBoards } from './esp32-serial.js';
@@ -998,6 +998,17 @@ export async function startDaemon(opts) {
998
998
  // that is provably alive (agentic turns regularly outlive any fixed age
999
999
  // threshold). Declared before the HTTP handlers that populate it.
1000
1000
  const hookSessionsSeen = new Set();
1001
+ /**
1002
+ * When each session last posted a hook.
1003
+ *
1004
+ * `hookSessionsSeen` answers "has this session EVER been heard from", which
1005
+ * is the wrong question for the turn watchdog: the very prompt hook that
1006
+ * opens a chat_start also adds the session to that set, so the row it
1007
+ * created was permanently immune to the reaper meant to close it. A
1008
+ * timestamp lets the watchdog ask the answerable question instead — has
1009
+ * anything arrived SINCE this turn opened.
1010
+ */
1011
+ const hookSessionLastSeenAt = new Map();
1001
1012
  // Codex OTel span state. Declared before the HTTP server because the
1002
1013
  // `/otel/v1/traces` handler closes over it — Codex's exporter can POST before
1003
1014
  // the rest of daemon startup finishes.
@@ -1917,6 +1928,7 @@ export async function startDaemon(opts) {
1917
1928
  // posts ANY hook after daemon startup is alive, and its open turn
1918
1929
  // (even one persisted before the restart) must not be force-closed.
1919
1930
  hookSessionsSeen.add(hookSid);
1931
+ hookSessionLastSeenAt.set(hookSid, Date.now());
1920
1932
  // Claude hooks carry `cwd` (the worktree dir), not project_name/path —
1921
1933
  // capture it so APME runs are attributable to a specific worktree.
1922
1934
  const hookCwd = (typeof json.cwd === 'string' ? json.cwd
@@ -2085,9 +2097,10 @@ export async function startDaemon(opts) {
2085
2097
  // authoritative source — the Codex counterpart of Claude's
2086
2098
  // transcript tail. Read it whenever a completion row might be
2087
2099
  // emitted (open turn, or a response-only close below).
2088
- const rolloutResponse = !tp && !inlineResponse && hookAgentType === 'codex-cli'
2089
- ? lastAgentMessageFromCodexRollout(hookSid)
2090
- : '';
2100
+ const rolloutOutcome = hookAgentType === 'codex-cli' && !tp && !inlineResponse
2101
+ ? codexTurnOutcomeFromRollout(hookSid)
2102
+ : { text: '', error: undefined, errorKind: undefined };
2103
+ const rolloutResponse = rolloutOutcome.text;
2091
2104
  const responseText = tp
2092
2105
  ? stripUnsafeText(lastAssistantTextFromTranscript(tp))
2093
2106
  : stripUnsafeText(inlineResponse || rolloutResponse);
@@ -2137,12 +2150,25 @@ export async function startDaemon(opts) {
2137
2150
  });
2138
2151
  }
2139
2152
  else {
2140
- // No readable response (interrupted / tool-only turn) — still
2141
- // close the row so the dashboards stop the spinner.
2153
+ // No readable response (interrupted / tool-only turn / failure) —
2154
+ // still close the row so the dashboards stop the spinner.
2142
2155
  const durS = Math.max(0, Math.round((now - (lastStart.startedAt ?? lastStart.ts)) / 1000));
2156
+ // A turn that failed says so, in the words Codex used. The text
2157
+ // is on disk in the rollout and used to be dropped, so a quota
2158
+ // refusal read as an ordinary quiet turn.
2159
+ if (rolloutOutcome.error) {
2160
+ core.bridgeTimeline.addEntry({
2161
+ ts: now, type: 'error',
2162
+ raw: cleanRawText(rolloutOutcome.error.slice(0, 197)),
2163
+ detail: prepareMarkdownDetail(rolloutOutcome.error) || undefined,
2164
+ ...base,
2165
+ });
2166
+ }
2143
2167
  core.bridgeTimeline.addEntry({
2144
2168
  ts: now, type: 'chat_end',
2145
- raw: `Completed · ${formatDurationSec(durS)}`,
2169
+ raw: rolloutOutcome.error
2170
+ ? `Failed · ${formatDurationSec(durS)}`
2171
+ : `Completed · ${formatDurationSec(durS)}`,
2146
2172
  summaryKind: 'none',
2147
2173
  ...base,
2148
2174
  });
@@ -2672,6 +2698,31 @@ export async function startDaemon(opts) {
2672
2698
  log(`[agentdeck] Closed ${reapedTurns} orphaned chat_start rows as interrupted`);
2673
2699
  }, 5 * 60_000);
2674
2700
  chatReaperTimer.unref?.();
2701
+ // Standing watchdog for turns that end without a closing signal.
2702
+ //
2703
+ // The startup pass above only ever runs once, so a turn that wedges an
2704
+ // hour later is never revisited. Codex makes that the common case rather
2705
+ // than the exception: it does NOT run its `Stop` hook when a request
2706
+ // fails, so a rejected prompt (quota exhausted, a model the plan may not
2707
+ // use) leaves its chat_start open with nothing on the way to close it, and
2708
+ // every surface spins on it indefinitely. This sweeps for such rows and
2709
+ // closes them, but only where the session has gone quiet SINCE the turn
2710
+ // opened — a long-running turn that is still posting tool hooks is alive
2711
+ // and must be left alone.
2712
+ const TURN_WATCHDOG_INTERVAL_MS = 60_000;
2713
+ const TURN_WATCHDOG_SILENCE_MS = 3 * 60_000;
2714
+ const turnWatchdog = setInterval(() => {
2715
+ const now = Date.now();
2716
+ const active = new Set();
2717
+ for (const [sid, at] of hookSessionLastSeenAt) {
2718
+ if (now - at <= TURN_WATCHDOG_SILENCE_MS)
2719
+ active.add(sid);
2720
+ }
2721
+ const closed = core.bridgeTimeline.reapOrphanChatStarts(TURN_WATCHDOG_SILENCE_MS, now, active);
2722
+ if (closed > 0)
2723
+ log(`[agentdeck] Turn watchdog closed ${closed} stalled chat_start row(s)`);
2724
+ }, TURN_WATCHDOG_INTERVAL_MS);
2725
+ turnWatchdog.unref?.();
2675
2726
  }
2676
2727
  // Enabled AFTER rehydration + reaping so the first write carries the restored
2677
2728
  // history instead of truncating the file to whatever this run has seen.
@@ -3009,7 +3060,7 @@ export async function startDaemon(opts) {
3009
3060
  }
3010
3061
  }
3011
3062
  log(`[agentdeck] WebSocket server ready on port ${port}`);
3012
- log(`[agentdeck] Pairing URL: ${core.wsUrl}`);
3063
+ log('[agentdeck] Pairing credential ready; run "agentdeck qr" to pair a device.');
3013
3064
  // Initialize APME store + collector so the daemon can serve /apme/* HTTP
3014
3065
  // routes. `setApme` on core is gated against the `daemon` meta-session so
3015
3066
  // register/deregister won't open a bogus run. Session bridges opening their