@agentdeck/bridge 1.0.24 → 1.2.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 (184) hide show
  1. package/README.md +14 -6
  2. package/assets/fonts/NOTICE-PocketSansWorld.md +10 -0
  3. package/assets/fonts/OFL-PocketSansWorld.txt +93 -0
  4. package/assets/fonts/PocketSansWorld_12.cpfont +0 -0
  5. package/assets/fonts/pocket-sans-world.manifest.json +49 -0
  6. package/assets/learning/LICENSE-CC-BY-SA-4.0.txt +10 -0
  7. package/assets/learning/NOTICE.md +33 -0
  8. package/assets/learning/jp-n3-ko.manifest.json +27 -0
  9. package/assets/learning/jp-n3-ko.pdl +0 -0
  10. package/dist/adapters/openclaw.d.ts +74 -4
  11. package/dist/adapters/openclaw.d.ts.map +1 -1
  12. package/dist/adapters/openclaw.js +235 -43
  13. package/dist/adapters/openclaw.js.map +1 -1
  14. package/dist/agent-cli-diagnostics.d.ts +20 -0
  15. package/dist/agent-cli-diagnostics.d.ts.map +1 -0
  16. package/dist/agent-cli-diagnostics.js +100 -0
  17. package/dist/agent-cli-diagnostics.js.map +1 -0
  18. package/dist/apme/adapters/openclaw-hook.d.ts +38 -10
  19. package/dist/apme/adapters/openclaw-hook.d.ts.map +1 -1
  20. package/dist/apme/adapters/openclaw-hook.js +181 -37
  21. package/dist/apme/adapters/openclaw-hook.js.map +1 -1
  22. package/dist/apme/collector.d.ts +55 -2
  23. package/dist/apme/collector.d.ts.map +1 -1
  24. package/dist/apme/collector.js +324 -37
  25. package/dist/apme/collector.js.map +1 -1
  26. package/dist/apme/dashboard-html.d.ts.map +1 -1
  27. package/dist/apme/dashboard-html.js +275 -35
  28. package/dist/apme/dashboard-html.js.map +1 -1
  29. package/dist/apme/http.d.ts.map +1 -1
  30. package/dist/apme/http.js +33 -5
  31. package/dist/apme/http.js.map +1 -1
  32. package/dist/apme/index.d.ts.map +1 -1
  33. package/dist/apme/index.js +6 -2
  34. package/dist/apme/index.js.map +1 -1
  35. package/dist/apme/pareto.d.ts +2 -1
  36. package/dist/apme/pareto.d.ts.map +1 -1
  37. package/dist/apme/pareto.js +15 -4
  38. package/dist/apme/pareto.js.map +1 -1
  39. package/dist/apme/recommend.d.ts +24 -1
  40. package/dist/apme/recommend.d.ts.map +1 -1
  41. package/dist/apme/recommend.js +43 -7
  42. package/dist/apme/recommend.js.map +1 -1
  43. package/dist/apme/store.d.ts +47 -1
  44. package/dist/apme/store.d.ts.map +1 -1
  45. package/dist/apme/store.js +397 -99
  46. package/dist/apme/store.js.map +1 -1
  47. package/dist/bridge-core.d.ts +25 -1
  48. package/dist/bridge-core.d.ts.map +1 -1
  49. package/dist/bridge-core.js +50 -9
  50. package/dist/bridge-core.js.map +1 -1
  51. package/dist/card-feed.d.ts +46 -1
  52. package/dist/card-feed.d.ts.map +1 -1
  53. package/dist/card-feed.js +166 -6
  54. package/dist/card-feed.js.map +1 -1
  55. package/dist/cli.d.ts +56 -0
  56. package/dist/cli.d.ts.map +1 -1
  57. package/dist/cli.js +442 -34
  58. package/dist/cli.js.map +1 -1
  59. package/dist/codex-rate-limits-live.d.ts +180 -10
  60. package/dist/codex-rate-limits-live.d.ts.map +1 -1
  61. package/dist/codex-rate-limits-live.js +475 -22
  62. package/dist/codex-rate-limits-live.js.map +1 -1
  63. package/dist/daemon-build-identity.d.ts +124 -0
  64. package/dist/daemon-build-identity.d.ts.map +1 -0
  65. package/dist/daemon-build-identity.js +310 -0
  66. package/dist/daemon-build-identity.js.map +1 -0
  67. package/dist/daemon-server.d.ts +34 -6
  68. package/dist/daemon-server.d.ts.map +1 -1
  69. package/dist/daemon-server.js +876 -109
  70. package/dist/daemon-server.js.map +1 -1
  71. package/dist/daemon-takeover.d.ts +11 -0
  72. package/dist/daemon-takeover.d.ts.map +1 -1
  73. package/dist/daemon-takeover.js +62 -17
  74. package/dist/daemon-takeover.js.map +1 -1
  75. package/dist/daemon-ws-client.d.ts +1 -0
  76. package/dist/daemon-ws-client.d.ts.map +1 -1
  77. package/dist/daemon-ws-client.js +25 -16
  78. package/dist/daemon-ws-client.js.map +1 -1
  79. package/dist/daemon.js +1 -1
  80. package/dist/daemon.js.map +1 -1
  81. package/dist/esp32-serial.d.ts +18 -0
  82. package/dist/esp32-serial.d.ts.map +1 -1
  83. package/dist/esp32-serial.js +23 -1
  84. package/dist/esp32-serial.js.map +1 -1
  85. package/dist/font-pack.d.ts +15 -0
  86. package/dist/font-pack.d.ts.map +1 -0
  87. package/dist/font-pack.js +75 -0
  88. package/dist/font-pack.js.map +1 -0
  89. package/dist/glance-frame.d.ts.map +1 -1
  90. package/dist/glance-frame.js +5 -1
  91. package/dist/glance-frame.js.map +1 -1
  92. package/dist/hook-codex-sessions.d.ts +3 -1
  93. package/dist/hook-codex-sessions.d.ts.map +1 -1
  94. package/dist/hook-codex-sessions.js +27 -7
  95. package/dist/hook-codex-sessions.js.map +1 -1
  96. package/dist/http-auth-gate.d.ts.map +1 -1
  97. package/dist/http-auth-gate.js +9 -1
  98. package/dist/http-auth-gate.js.map +1 -1
  99. package/dist/index.js +2 -2
  100. package/dist/index.js.map +1 -1
  101. package/dist/learning-pack.d.ts +24 -0
  102. package/dist/learning-pack.d.ts.map +1 -0
  103. package/dist/learning-pack.js +125 -0
  104. package/dist/learning-pack.js.map +1 -0
  105. package/dist/legacy-rearm-ledger.d.ts +56 -0
  106. package/dist/legacy-rearm-ledger.d.ts.map +1 -0
  107. package/dist/legacy-rearm-ledger.js +66 -0
  108. package/dist/legacy-rearm-ledger.js.map +1 -0
  109. package/dist/mdns.d.ts +33 -0
  110. package/dist/mdns.d.ts.map +1 -1
  111. package/dist/mdns.js +92 -3
  112. package/dist/mdns.js.map +1 -1
  113. package/dist/openclaw-session.d.ts.map +1 -1
  114. package/dist/openclaw-session.js +13 -0
  115. package/dist/openclaw-session.js.map +1 -1
  116. package/dist/openclaw-timeline-feed.d.ts +84 -0
  117. package/dist/openclaw-timeline-feed.d.ts.map +1 -0
  118. package/dist/openclaw-timeline-feed.js +162 -0
  119. package/dist/openclaw-timeline-feed.js.map +1 -0
  120. package/dist/openclaw-transcript-timeline.d.ts +103 -0
  121. package/dist/openclaw-transcript-timeline.d.ts.map +1 -0
  122. package/dist/openclaw-transcript-timeline.js +328 -0
  123. package/dist/openclaw-transcript-timeline.js.map +1 -0
  124. package/dist/pairing-knocks.d.ts +80 -0
  125. package/dist/pairing-knocks.d.ts.map +1 -0
  126. package/dist/pairing-knocks.js +312 -0
  127. package/dist/pairing-knocks.js.map +1 -0
  128. package/dist/passive-observer.d.ts +21 -1
  129. package/dist/passive-observer.d.ts.map +1 -1
  130. package/dist/passive-observer.js +37 -3
  131. package/dist/passive-observer.js.map +1 -1
  132. package/dist/pixoo/micro-glyphs.d.ts +4 -0
  133. package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
  134. package/dist/pixoo/micro-glyphs.js +29 -0
  135. package/dist/pixoo/micro-glyphs.js.map +1 -1
  136. package/dist/pixoo/pixoo-font.d.ts.map +1 -1
  137. package/dist/pixoo/pixoo-font.js +3 -1
  138. package/dist/pixoo/pixoo-font.js.map +1 -1
  139. package/dist/pixoo/pixoo-renderer.d.ts +1 -1
  140. package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
  141. package/dist/pixoo/pixoo-renderer.js +17 -2
  142. package/dist/pixoo/pixoo-renderer.js.map +1 -1
  143. package/dist/pty-manager.d.ts +9 -0
  144. package/dist/pty-manager.d.ts.map +1 -1
  145. package/dist/pty-manager.js +63 -4
  146. package/dist/pty-manager.js.map +1 -1
  147. package/dist/relayed-usage.d.ts +98 -0
  148. package/dist/relayed-usage.d.ts.map +1 -0
  149. package/dist/relayed-usage.js +103 -0
  150. package/dist/relayed-usage.js.map +1 -0
  151. package/dist/session-aggregator.d.ts +3 -0
  152. package/dist/session-aggregator.d.ts.map +1 -1
  153. package/dist/session-aggregator.js.map +1 -1
  154. package/dist/session-registry.d.ts +1 -0
  155. package/dist/session-registry.d.ts.map +1 -1
  156. package/dist/surface-protocol.d.ts +84 -0
  157. package/dist/surface-protocol.d.ts.map +1 -0
  158. package/dist/surface-protocol.js +237 -0
  159. package/dist/surface-protocol.js.map +1 -0
  160. package/dist/timeline-store.d.ts +1 -1
  161. package/dist/timeline-store.d.ts.map +1 -1
  162. package/dist/timeline-store.js +16 -1
  163. package/dist/timeline-store.js.map +1 -1
  164. package/dist/tui/dashboard.d.ts.map +1 -1
  165. package/dist/tui/dashboard.js +8 -1
  166. package/dist/tui/dashboard.js.map +1 -1
  167. package/dist/tui/renderer.d.ts.map +1 -1
  168. package/dist/tui/renderer.js +35 -0
  169. package/dist/tui/renderer.js.map +1 -1
  170. package/dist/weather-config.d.ts +17 -0
  171. package/dist/weather-config.d.ts.map +1 -0
  172. package/dist/weather-config.js +54 -0
  173. package/dist/weather-config.js.map +1 -0
  174. package/dist/weather.d.ts +44 -4
  175. package/dist/weather.d.ts.map +1 -1
  176. package/dist/weather.js +223 -15
  177. package/dist/weather.js.map +1 -1
  178. package/dist/ws-server.d.ts +18 -0
  179. package/dist/ws-server.d.ts.map +1 -1
  180. package/dist/ws-server.js +95 -7
  181. package/dist/ws-server.js.map +1 -1
  182. package/fm-helper/AgentDeckFMHelper.swift +51 -5
  183. package/package.json +4 -3
  184. package/src/timebox/sync_ble.py +35 -16
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Who tried to connect, and who the operator let in — the Node daemon's half
3
+ * of the operator-approval credential path (`PairingKnockStore.swift`).
4
+ *
5
+ * The daemon already knew everything this needs: it refuses an unauthenticated
6
+ * LAN peer at the WebSocket handshake and logs the address. What it did not do
7
+ * was tell anyone. So a device with no credential retried forever into a log
8
+ * nobody reads, while every way to give it a credential asked the DEVICE to do
9
+ * something — scan a QR, type six digits, type a 32-character token. An e-ink
10
+ * reader can do none of them comfortably, and one with no camera cannot do the
11
+ * first at all.
12
+ *
13
+ * Turning the refusal into a prompt inverts that: the device does nothing, and
14
+ * the operator — who is holding the machine — approves a peer they can see.
15
+ * That is also the stronger of the two trust models. A pairing code trusts
16
+ * whoever knows a secret, so anyone in range may guess at it while the window
17
+ * is open; an approval trusts a specific peer the operator pointed at, and an
18
+ * attacker cannot approve themselves.
19
+ *
20
+ * This file is the Node parity port. It is deliberately a near-transliteration
21
+ * of the Swift store rather than a re-derivation: both daemons take turns
22
+ * owning port 9120 and they read each other's state, so a rule that is restated
23
+ * in this file's own words is a rule that can drift. The properties below are
24
+ * the ones that must hold in both.
25
+ *
26
+ * **Identity is the device id when the client sends one, and the address only
27
+ * when it does not.** The refusal happens at the HTTP upgrade, before any frame
28
+ * — `client_register`, where a device says its name, arrives only after a
29
+ * socket exists — so the daemon must learn who this is from the handshake
30
+ * itself. A client that sends `x-agentdeck-device` is approved as that device:
31
+ * two devices behind one NAT are told apart, a DHCP lease change does not
32
+ * retire the grant, and one device can be revoked without touching the token
33
+ * the whole fleet shares.
34
+ *
35
+ * The address remains the key for clients that send no id, and that path must
36
+ * keep working: every device already in the field predates the header, and
37
+ * refusing them would turn an upgrade into a fleet-wide outage. An IP-keyed
38
+ * approval carries the old caveats — it retires when the lease changes, and
39
+ * behind NAT it is not an identity at all — so a row says which kind it is
40
+ * instead of letting the two read alike.
41
+ *
42
+ * Neither kind is proof. The link is plaintext `ws://`, so a device id in the
43
+ * handshake is replayable by a passive observer on the same segment — exactly
44
+ * as true of the `?token=` every paired device already carries. This is no
45
+ * weaker than what ships today; it buys granularity and revocation, not
46
+ * secrecy.
47
+ *
48
+ * **A claimed name is never identity.** Nothing here reads one. If a name is
49
+ * ever surfaced it must sit beside the address, never instead of it.
50
+ *
51
+ * **Bounded, and expired on read.** A hostile peer must not be able to grow
52
+ * this list without limit, so it is capped and the oldest knock is dropped —
53
+ * which does mean a flood can push a real device out of view, and the cap is
54
+ * chosen high enough that a human notices the flood first. Expiry is evaluated
55
+ * when the list is read, never by a timer: a timer that fires late on a
56
+ * sleeping laptop would keep a stale knock alive past its promise, exactly as
57
+ * in `pairing-window.ts`.
58
+ */
59
+ import { readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from 'fs';
60
+ import { join } from 'path';
61
+ import { homedir } from 'os';
62
+ import { normalizeDeviceId } from '@agentdeck/shared';
63
+ import { debug } from './logger.js';
64
+ /**
65
+ * A knock is worth showing for this long after its last attempt. Long enough
66
+ * that the operator can walk to the machine; short enough that a device carried
67
+ * out of the building stops being offered.
68
+ */
69
+ export const KNOCK_TTL_MS = 15 * 60_000;
70
+ /** Cap on simultaneously-tracked peers. */
71
+ export const MAX_KNOCKS = 12;
72
+ /**
73
+ * The one place a key is spelled, so the gate, the store and the UI cannot
74
+ * disagree about what "this peer" means.
75
+ */
76
+ export function peerKey(ip, deviceId) {
77
+ const normalized = normalizeDeviceId(deviceId);
78
+ if (normalized)
79
+ return `device:${normalized}`;
80
+ return `ip:${ip}`;
81
+ }
82
+ function deviceIdOfKey(key) {
83
+ return key.startsWith('device:') ? key.slice('device:'.length) : null;
84
+ }
85
+ /**
86
+ * Every other module resolves its state through
87
+ * `AGENTDECK_DATA_DIR || ~/.agentdeck`; mirror that here rather than hardcoding
88
+ * the home path, or a daemon started with a custom data dir would keep its
89
+ * approvals somewhere else than the rest of its state.
90
+ */
91
+ function dataDir() {
92
+ return process.env.AGENTDECK_DATA_DIR || join(homedir(), '.agentdeck');
93
+ }
94
+ function approvalsFile() {
95
+ return join(dataDir(), 'pairing-approved.json');
96
+ }
97
+ /** Cached per resolved directory, so pointing AGENTDECK_DATA_DIR elsewhere re-reads. */
98
+ let cache = null;
99
+ const pending = new Map();
100
+ /**
101
+ * Read a timestamp written by either daemon.
102
+ *
103
+ * The two encode `approvedAt` differently — this file writes epoch ms, while
104
+ * Swift's `JSONEncoder` default writes a Foundation reference-date Double
105
+ * (seconds since 2001-01-01). The files do not collide today, because the
106
+ * sandboxed app keeps its copy inside its container and this one lives in
107
+ * `~/.agentdeck`. But the two daemons take turns owning port 9120, and the day
108
+ * those paths converge a silent misread would date every approval to 1970 or to
109
+ * the far future — so accept both now, by magnitude, rather than discovering it
110
+ * as a bug later. A reference-date value for any plausible date is < 1e11;
111
+ * epoch ms is > 1e12.
112
+ */
113
+ const FOUNDATION_REFERENCE_EPOCH_MS = 978_307_200_000; // 2001-01-01T00:00:00Z
114
+ function toEpoch(value) {
115
+ if (typeof value === 'number' && Number.isFinite(value)) {
116
+ if (Math.abs(value) < 1e11)
117
+ return Math.round(value * 1000) + FOUNDATION_REFERENCE_EPOCH_MS;
118
+ return value;
119
+ }
120
+ if (typeof value === 'string') {
121
+ const parsed = Date.parse(value);
122
+ if (!Number.isNaN(parsed))
123
+ return parsed;
124
+ }
125
+ return 0;
126
+ }
127
+ function loadApprovals() {
128
+ const dir = dataDir();
129
+ if (cache && cache.dir === dir)
130
+ return cache.approved;
131
+ const table = new Map();
132
+ let raw = null;
133
+ try {
134
+ raw = readFileSync(approvalsFile(), 'utf8');
135
+ }
136
+ catch {
137
+ // No file yet is the common case: nobody has been approved.
138
+ cache = { dir, approved: table };
139
+ return table;
140
+ }
141
+ let parsed;
142
+ try {
143
+ parsed = JSON.parse(raw);
144
+ }
145
+ catch {
146
+ // Unparseable is NOT "nobody is approved" — see the migration note below.
147
+ // We cannot recover rows from it, but we must not overwrite the file
148
+ // either, so leave it on disk untouched for the operator to inspect.
149
+ debug('Pair', 'approvals file is unparseable; treating as empty without overwriting it');
150
+ cache = { dir, approved: table };
151
+ return table;
152
+ }
153
+ if (Array.isArray(parsed)) {
154
+ for (const row of parsed) {
155
+ if (!row || typeof row !== 'object')
156
+ continue;
157
+ const r = row;
158
+ if (typeof r.key === 'string' && r.key.length > 0) {
159
+ table.set(r.key, {
160
+ key: r.key,
161
+ lastIP: typeof r.lastIP === 'string' ? r.lastIP : '',
162
+ approvedAt: toEpoch(r.approvedAt),
163
+ deviceId: deviceIdOfKey(r.key),
164
+ });
165
+ continue;
166
+ }
167
+ // Migrate rather than discard. A decode that merely fails reads as
168
+ // "nobody is approved", which silently un-pairs every device the operator
169
+ // already let in — the same failure the tmp+rename below exists to
170
+ // prevent, arriving through the schema instead of the disk.
171
+ const legacy = row;
172
+ if (typeof legacy.ip === 'string' && legacy.ip.length > 0) {
173
+ const key = `ip:${legacy.ip}`;
174
+ table.set(key, {
175
+ key,
176
+ lastIP: legacy.ip,
177
+ approvedAt: toEpoch(legacy.approvedAt),
178
+ deviceId: null,
179
+ });
180
+ }
181
+ }
182
+ }
183
+ cache = { dir, approved: table };
184
+ return table;
185
+ }
186
+ function persist(approved) {
187
+ const rows = [...approved.values()].sort((a, b) => a.approvedAt - b.approvedAt);
188
+ const path = approvalsFile();
189
+ const tmp = `${path}.tmp`;
190
+ try {
191
+ mkdirSync(dataDir(), { recursive: true });
192
+ // tmp+rename, as everywhere else that writes the data dir: a truncated
193
+ // approvals file reads as "nobody is approved" and silently locks the whole
194
+ // fleet out.
195
+ writeFileSync(tmp, JSON.stringify(rows, null, 2), { mode: 0o600 });
196
+ renameSync(tmp, path);
197
+ }
198
+ catch (err) {
199
+ try {
200
+ unlinkSync(tmp);
201
+ }
202
+ catch { /* nothing to clean up */ }
203
+ debug('Pair', `failed to persist approvals: ${String(err)}`);
204
+ }
205
+ }
206
+ function evictIfNeeded() {
207
+ if (pending.size <= MAX_KNOCKS)
208
+ return;
209
+ const ordered = [...pending.values()].sort((a, b) => a.lastSeen - b.lastSeen);
210
+ for (const knock of ordered.slice(0, pending.size - MAX_KNOCKS)) {
211
+ pending.delete(knock.key);
212
+ }
213
+ }
214
+ /**
215
+ * Called from the refusal path. Cheap on purpose — it runs for every rejected
216
+ * handshake, and a looping device produces one every few seconds.
217
+ */
218
+ export function recordKnock(ip, deviceId, staleToken, now = Date.now()) {
219
+ if (!ip)
220
+ return;
221
+ const normalized = normalizeDeviceId(deviceId);
222
+ const key = peerKey(ip, normalized);
223
+ if (loadApprovals().has(key))
224
+ return; // already let in; not a knock
225
+ const existing = pending.get(key);
226
+ if (existing) {
227
+ existing.attempts += 1;
228
+ existing.lastSeen = now;
229
+ existing.staleToken = staleToken;
230
+ return;
231
+ }
232
+ pending.set(key, {
233
+ key,
234
+ ip,
235
+ deviceId: normalized,
236
+ attempts: 1,
237
+ firstSeen: now,
238
+ lastSeen: now,
239
+ staleToken,
240
+ deviceScoped: normalized !== null,
241
+ });
242
+ evictIfNeeded();
243
+ }
244
+ /** Live knocks, newest first. Expiry is applied here — see the header. */
245
+ export function pairingKnocks(now = Date.now()) {
246
+ for (const [key, knock] of pending) {
247
+ if (now - knock.lastSeen >= KNOCK_TTL_MS)
248
+ pending.delete(key);
249
+ }
250
+ return [...pending.values()].sort((a, b) => b.lastSeen - a.lastSeen);
251
+ }
252
+ /**
253
+ * Drop a knock without approving it. Not a block: the peer may knock again,
254
+ * which is deliberate — a permanent denylist keyed on an address a stranger can
255
+ * change is security theatre, and it would silently strand a real device that
256
+ * later inherits that address.
257
+ */
258
+ export function dismissKnock(key) {
259
+ return pending.delete(key);
260
+ }
261
+ /**
262
+ * Approving is what a pairing code would have done, minus the typing: from here
263
+ * on this peer authenticates by its key. Persisted immediately.
264
+ */
265
+ export function approvePeer(key, now = Date.now()) {
266
+ if (!key)
267
+ return false;
268
+ const approved = loadApprovals();
269
+ const lastIP = pending.get(key)?.ip ?? approved.get(key)?.lastIP ?? '';
270
+ approved.set(key, { key, lastIP, approvedAt: now, deviceId: deviceIdOfKey(key) });
271
+ pending.delete(key);
272
+ persist(approved);
273
+ return true;
274
+ }
275
+ export function revokePeer(key) {
276
+ const approved = loadApprovals();
277
+ if (!approved.delete(key))
278
+ return false;
279
+ persist(approved);
280
+ return true;
281
+ }
282
+ /**
283
+ * The question the WebSocket gate asks.
284
+ *
285
+ * A device id wins when the client sent one; the address is consulted only as
286
+ * the legacy path, so a client that adopts the header stops depending on its
287
+ * address the moment it does. Both are checked rather than one, because a
288
+ * device approved by address before it learned to send an id must not be locked
289
+ * out by the upgrade that taught it.
290
+ */
291
+ export function isApprovedPeer(ip, deviceId) {
292
+ const approved = loadApprovals();
293
+ const normalized = normalizeDeviceId(deviceId);
294
+ if (normalized && approved.has(`device:${normalized}`))
295
+ return true;
296
+ if (!ip)
297
+ return false;
298
+ return approved.has(`ip:${ip}`);
299
+ }
300
+ export function pairingApprovals() {
301
+ return [...loadApprovals().values()].sort((a, b) => b.approvedAt - a.approvedAt);
302
+ }
303
+ /** Read the device id a client offered on the handshake, if any. */
304
+ export function deviceIdFromHeaders(headers, headerName) {
305
+ const raw = headers?.[headerName];
306
+ return normalizeDeviceId(Array.isArray(raw) ? raw[0] : raw);
307
+ }
308
+ export function resetPairingKnocksForTests() {
309
+ pending.clear();
310
+ cache = null;
311
+ }
312
+ //# sourceMappingURL=pairing-knocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-knocks.js","sourceRoot":"","sources":["../src/pairing-knocks.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,8 @@ export interface CodexRolloutSummary extends TranscriptSummary {
26
26
  hasPendingCalls?: boolean;
27
27
  /** Internal companion rollouts never become top-level dashboard sessions. */
28
28
  isSubagent: boolean;
29
+ /** `codex-tui` for CLI, `Codex Desktop` for the desktop client. */
30
+ originator?: string;
29
31
  }
30
32
  /**
31
33
  * Condense a first user prompt into a one-line session goal: strip injected
@@ -53,6 +55,15 @@ export interface ObservedSession extends EnrichedSession {
53
55
  * prompt is over. Absent when no transcript could be located. */
54
56
  lastActivityAt?: number;
55
57
  }
58
+ /**
59
+ * A desktop app-server keeps rollout descriptors open for chats retained by
60
+ * the Codex App, including conversations that have not done work for days.
61
+ * An idle descriptor is therefore only a recent observation, not process
62
+ * liveness. Mirror the Swift daemon's `codexIdleObservationStaleTTL`: after
63
+ * this window the chat remains resumable in Codex, but leaves AgentDeck's
64
+ * active-session roster until its rollout changes again.
65
+ */
66
+ export declare const CODEX_APP_IDLE_ROLLOUT_TTL_MS = 90000;
56
67
  export declare function observedStateAfterSilence(state: ObservedState, lastActivityAt: number | undefined, now?: number): ObservedState;
57
68
  interface CodexRolloutFileInfo {
58
69
  mtimeMs: number;
@@ -103,6 +114,15 @@ export declare class PassiveSessionObserver {
103
114
  export declare function parseProcessTable(output: string): ProcInfo[];
104
115
  export declare function parseClaudeTranscript(raw: string): TranscriptSummary;
105
116
  export declare function parseCodexRollout(raw: string): CodexRolloutSummary;
117
+ export interface LocatedCodexRolloutSummary {
118
+ path: string;
119
+ mtimeMs: number;
120
+ summary: CodexRolloutSummary;
121
+ }
122
+ /** Locate and summarize a rollout by the stable id carried by lifecycle hooks.
123
+ * This path is independent of pid/fd enumeration, so it works on Windows and
124
+ * can distinguish desktop from TUI via session_meta.originator. */
125
+ export declare function codexRolloutSummaryForSession(sessionId: string, sessionsRoot?: string): LocatedCodexRolloutSummary | null;
106
126
  export declare function collectProcessInfo(): Promise<ProcInfo[]>;
107
127
  /**
108
128
  * Parse `Win32_Process | ConvertTo-Json` output into the shared ProcInfo shape.
@@ -121,7 +141,7 @@ export declare function appNameFromCommand(command: string): string | undefined;
121
141
  * tty, so the owning app is how the injector finds their UI. */
122
142
  export declare function resolveHostApp(pid: number, byPid: Map<number, ProcInfo>, maxDepth?: number): string | undefined;
123
143
  /** Testable async projection from open rollout paths to top-level sessions. */
124
- export declare function collectCodexSessionsFromRollouts(processes: ProcInfo[], rolloutsByPid: Map<number, string[]>, rolloutCache: CodexRolloutCache): Promise<ObservedSession[]>;
144
+ export declare function collectCodexSessionsFromRollouts(processes: ProcInfo[], rolloutsByPid: Map<number, string[]>, rolloutCache: CodexRolloutCache, now?: number): Promise<ObservedSession[]>;
125
145
  /**
126
146
  * A Codex process that can own a user session.
127
147
  *
@@ -1 +1 @@
1
- {"version":3,"file":"passive-observer.d.ts","sourceRoot":"","sources":["../src/passive-observer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"passive-observer.d.ts","sourceRoot":"","sources":["../src/passive-observer.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ import { stripUnsafeText, rawSessionId } from '@agentdeck/shared';
13
13
  import { isClaudeInterruptMessage } from './claude-interrupt-marker.js';
14
14
  import { parseAntigravityTranscript, antigravityDefaultModel, antigravityConversationId, antigravityTranscriptPath, } from './apme/antigravity-transcript.js';
15
15
  import { KiroSessionCache, readKiroNativeSessionSnapshots, } from './kiro-session.js';
16
+ import { locateCodexRollout } from './codex-rollout-response.js';
16
17
  /** Pull display text out of a Claude user message (string or text blocks). */
17
18
  function claudeUserText(message) {
18
19
  if (!message)
@@ -50,6 +51,15 @@ const MAX_TAIL_BYTES = 512 * 1024;
50
51
  const MAX_SAMPLE_BYTES = 1024 * 1024;
51
52
  /** Transcript/rollout silence after which an end-event-less turn is presumed dead. */
52
53
  const STALE_TURN_MS = 10 * 60 * 1000;
54
+ /**
55
+ * A desktop app-server keeps rollout descriptors open for chats retained by
56
+ * the Codex App, including conversations that have not done work for days.
57
+ * An idle descriptor is therefore only a recent observation, not process
58
+ * liveness. Mirror the Swift daemon's `codexIdleObservationStaleTTL`: after
59
+ * this window the chat remains resumable in Codex, but leaves AgentDeck's
60
+ * active-session roster until its rollout changes again.
61
+ */
62
+ export const CODEX_APP_IDLE_ROLLOUT_TTL_MS = 90_000;
53
63
  export function observedStateAfterSilence(state, lastActivityAt, now = Date.now()) {
54
64
  return state === 'processing'
55
65
  && lastActivityAt !== undefined
@@ -295,6 +305,7 @@ export function parseCodexRollout(raw) {
295
305
  let lastContextTokens = 0;
296
306
  let contextWindow = 0;
297
307
  let isSubagent = false;
308
+ let originator;
298
309
  // A turn is in flight from task_started/user_message until task_complete/
299
310
  // turn_aborted. Mid-turn events (agent_message, function_call) must NOT end
300
311
  // it: most of a working turn is the thinking gap between a tool result and
@@ -313,6 +324,7 @@ export function parseCodexRollout(raw) {
313
324
  continue;
314
325
  sessionId = stringAt(payload, 'id') ?? sessionId;
315
326
  cwd = stringAt(payload, 'cwd') ?? cwd;
327
+ originator = stringAt(payload, 'originator') ?? originator;
316
328
  startedAt = timestampMs(stringAt(payload, 'timestamp')) ?? startedAt;
317
329
  const source = payload.source;
318
330
  isSubagent = isSubagent || (isRecord(source) && 'subagent' in source);
@@ -404,6 +416,7 @@ export function parseCodexRollout(raw) {
404
416
  return {
405
417
  hasPendingCalls: pendingCalls.size > 0,
406
418
  isSubagent,
419
+ originator,
407
420
  sessionId,
408
421
  cwd,
409
422
  startedAt,
@@ -418,6 +431,24 @@ export function parseCodexRollout(raw) {
418
431
  : undefined,
419
432
  };
420
433
  }
434
+ /** Locate and summarize a rollout by the stable id carried by lifecycle hooks.
435
+ * This path is independent of pid/fd enumeration, so it works on Windows and
436
+ * can distinguish desktop from TUI via session_meta.originator. */
437
+ export function codexRolloutSummaryForSession(sessionId, sessionsRoot) {
438
+ const path = locateCodexRollout(sessionId, sessionsRoot);
439
+ if (!path)
440
+ return null;
441
+ try {
442
+ const stat = statSync(path);
443
+ const raw = readFileHeadAndTail(path, 256 * 1024, MAX_SAMPLE_BYTES);
444
+ if (!raw)
445
+ return null;
446
+ return { path, mtimeMs: stat.mtimeMs, summary: parseCodexRollout(raw) };
447
+ }
448
+ catch {
449
+ return null;
450
+ }
451
+ }
421
452
  export async function collectProcessInfo() {
422
453
  if (process.platform === 'win32')
423
454
  return collectProcessInfoWin32();
@@ -578,7 +609,7 @@ async function collectCodexSessions(processes, rolloutCache) {
578
609
  return collectCodexSessionsFromRollouts(processes, rolloutsByPid, rolloutCache);
579
610
  }
580
611
  /** Testable async projection from open rollout paths to top-level sessions. */
581
- export async function collectCodexSessionsFromRollouts(processes, rolloutsByPid, rolloutCache) {
612
+ export async function collectCodexSessionsFromRollouts(processes, rolloutsByPid, rolloutCache, now = Date.now()) {
582
613
  const byPid = new Map(processes.map((p) => [p.pid, p]));
583
614
  const codex = processes.filter((p) => isCodexSessionProcessCommand(p.command));
584
615
  const activeRollouts = new Set();
@@ -598,15 +629,18 @@ export async function collectCodexSessionsFromRollouts(processes, rolloutsByPid,
598
629
  continue;
599
630
  const parsed = snapshot.summary;
600
631
  let state = parsed.state;
601
- if (state === 'processing' && !parsed.hasPendingCalls && Date.now() - snapshot.mtimeMs > STALE_TURN_MS) {
632
+ if (state === 'processing' && !parsed.hasPendingCalls && now - snapshot.mtimeMs > STALE_TURN_MS) {
602
633
  state = 'idle';
603
634
  }
635
+ const desktop = proc.command.includes('app-server');
636
+ if (desktop && state === 'idle' && now - snapshot.mtimeMs > CODEX_APP_IDLE_ROLLOUT_TTL_MS) {
637
+ continue;
638
+ }
604
639
  const sessionId = parsed.sessionId ?? codexRolloutId(rollout);
605
640
  if (seen.has(sessionId))
606
641
  continue;
607
642
  seen.add(sessionId);
608
643
  const cwd = parsed.cwd;
609
- const desktop = proc.command.includes('app-server');
610
644
  sessions.push({
611
645
  id: desktop ? `observed:codex-app:${sessionId}` : `observed:codex:${sessionId}`,
612
646
  port: 0,