@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.
- package/README.md +14 -6
- package/assets/fonts/NOTICE-PocketSansWorld.md +10 -0
- package/assets/fonts/OFL-PocketSansWorld.txt +93 -0
- package/assets/fonts/PocketSansWorld_12.cpfont +0 -0
- package/assets/fonts/pocket-sans-world.manifest.json +49 -0
- package/assets/learning/LICENSE-CC-BY-SA-4.0.txt +10 -0
- package/assets/learning/NOTICE.md +33 -0
- package/assets/learning/jp-n3-ko.manifest.json +27 -0
- package/assets/learning/jp-n3-ko.pdl +0 -0
- package/dist/adapters/openclaw.d.ts +74 -4
- package/dist/adapters/openclaw.d.ts.map +1 -1
- package/dist/adapters/openclaw.js +235 -43
- package/dist/adapters/openclaw.js.map +1 -1
- package/dist/agent-cli-diagnostics.d.ts +20 -0
- package/dist/agent-cli-diagnostics.d.ts.map +1 -0
- package/dist/agent-cli-diagnostics.js +100 -0
- package/dist/agent-cli-diagnostics.js.map +1 -0
- package/dist/apme/adapters/openclaw-hook.d.ts +38 -10
- package/dist/apme/adapters/openclaw-hook.d.ts.map +1 -1
- package/dist/apme/adapters/openclaw-hook.js +181 -37
- package/dist/apme/adapters/openclaw-hook.js.map +1 -1
- package/dist/apme/collector.d.ts +55 -2
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +324 -37
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/dashboard-html.d.ts.map +1 -1
- package/dist/apme/dashboard-html.js +275 -35
- package/dist/apme/dashboard-html.js.map +1 -1
- package/dist/apme/http.d.ts.map +1 -1
- package/dist/apme/http.js +33 -5
- package/dist/apme/http.js.map +1 -1
- package/dist/apme/index.d.ts.map +1 -1
- package/dist/apme/index.js +6 -2
- package/dist/apme/index.js.map +1 -1
- package/dist/apme/pareto.d.ts +2 -1
- package/dist/apme/pareto.d.ts.map +1 -1
- package/dist/apme/pareto.js +15 -4
- package/dist/apme/pareto.js.map +1 -1
- package/dist/apme/recommend.d.ts +24 -1
- package/dist/apme/recommend.d.ts.map +1 -1
- package/dist/apme/recommend.js +43 -7
- package/dist/apme/recommend.js.map +1 -1
- package/dist/apme/store.d.ts +47 -1
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +397 -99
- package/dist/apme/store.js.map +1 -1
- package/dist/bridge-core.d.ts +25 -1
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +50 -9
- package/dist/bridge-core.js.map +1 -1
- package/dist/card-feed.d.ts +46 -1
- package/dist/card-feed.d.ts.map +1 -1
- package/dist/card-feed.js +166 -6
- package/dist/card-feed.js.map +1 -1
- package/dist/cli.d.ts +56 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +442 -34
- package/dist/cli.js.map +1 -1
- package/dist/codex-rate-limits-live.d.ts +180 -10
- package/dist/codex-rate-limits-live.d.ts.map +1 -1
- package/dist/codex-rate-limits-live.js +475 -22
- package/dist/codex-rate-limits-live.js.map +1 -1
- package/dist/daemon-build-identity.d.ts +124 -0
- package/dist/daemon-build-identity.d.ts.map +1 -0
- package/dist/daemon-build-identity.js +310 -0
- package/dist/daemon-build-identity.js.map +1 -0
- package/dist/daemon-server.d.ts +34 -6
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +876 -109
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-takeover.d.ts +11 -0
- package/dist/daemon-takeover.d.ts.map +1 -1
- package/dist/daemon-takeover.js +62 -17
- package/dist/daemon-takeover.js.map +1 -1
- package/dist/daemon-ws-client.d.ts +1 -0
- package/dist/daemon-ws-client.d.ts.map +1 -1
- package/dist/daemon-ws-client.js +25 -16
- package/dist/daemon-ws-client.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/daemon.js.map +1 -1
- package/dist/esp32-serial.d.ts +18 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +23 -1
- package/dist/esp32-serial.js.map +1 -1
- package/dist/font-pack.d.ts +15 -0
- package/dist/font-pack.d.ts.map +1 -0
- package/dist/font-pack.js +75 -0
- package/dist/font-pack.js.map +1 -0
- package/dist/glance-frame.d.ts.map +1 -1
- package/dist/glance-frame.js +5 -1
- package/dist/glance-frame.js.map +1 -1
- package/dist/hook-codex-sessions.d.ts +3 -1
- package/dist/hook-codex-sessions.d.ts.map +1 -1
- package/dist/hook-codex-sessions.js +27 -7
- package/dist/hook-codex-sessions.js.map +1 -1
- package/dist/http-auth-gate.d.ts.map +1 -1
- package/dist/http-auth-gate.js +9 -1
- package/dist/http-auth-gate.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/learning-pack.d.ts +24 -0
- package/dist/learning-pack.d.ts.map +1 -0
- package/dist/learning-pack.js +125 -0
- package/dist/learning-pack.js.map +1 -0
- package/dist/legacy-rearm-ledger.d.ts +56 -0
- package/dist/legacy-rearm-ledger.d.ts.map +1 -0
- package/dist/legacy-rearm-ledger.js +66 -0
- package/dist/legacy-rearm-ledger.js.map +1 -0
- package/dist/mdns.d.ts +33 -0
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +92 -3
- package/dist/mdns.js.map +1 -1
- package/dist/openclaw-session.d.ts.map +1 -1
- package/dist/openclaw-session.js +13 -0
- package/dist/openclaw-session.js.map +1 -1
- package/dist/openclaw-timeline-feed.d.ts +84 -0
- package/dist/openclaw-timeline-feed.d.ts.map +1 -0
- package/dist/openclaw-timeline-feed.js +162 -0
- package/dist/openclaw-timeline-feed.js.map +1 -0
- package/dist/openclaw-transcript-timeline.d.ts +103 -0
- package/dist/openclaw-transcript-timeline.d.ts.map +1 -0
- package/dist/openclaw-transcript-timeline.js +328 -0
- package/dist/openclaw-transcript-timeline.js.map +1 -0
- package/dist/pairing-knocks.d.ts +80 -0
- package/dist/pairing-knocks.d.ts.map +1 -0
- package/dist/pairing-knocks.js +312 -0
- package/dist/pairing-knocks.js.map +1 -0
- package/dist/passive-observer.d.ts +21 -1
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +37 -3
- package/dist/passive-observer.js.map +1 -1
- package/dist/pixoo/micro-glyphs.d.ts +4 -0
- package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
- package/dist/pixoo/micro-glyphs.js +29 -0
- package/dist/pixoo/micro-glyphs.js.map +1 -1
- package/dist/pixoo/pixoo-font.d.ts.map +1 -1
- package/dist/pixoo/pixoo-font.js +3 -1
- package/dist/pixoo/pixoo-font.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +17 -2
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pty-manager.d.ts +9 -0
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +63 -4
- package/dist/pty-manager.js.map +1 -1
- package/dist/relayed-usage.d.ts +98 -0
- package/dist/relayed-usage.d.ts.map +1 -0
- package/dist/relayed-usage.js +103 -0
- package/dist/relayed-usage.js.map +1 -0
- package/dist/session-aggregator.d.ts +3 -0
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-registry.d.ts +1 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/surface-protocol.d.ts +84 -0
- package/dist/surface-protocol.d.ts.map +1 -0
- package/dist/surface-protocol.js +237 -0
- package/dist/surface-protocol.js.map +1 -0
- package/dist/timeline-store.d.ts +1 -1
- package/dist/timeline-store.d.ts.map +1 -1
- package/dist/timeline-store.js +16 -1
- package/dist/timeline-store.js.map +1 -1
- package/dist/tui/dashboard.d.ts.map +1 -1
- package/dist/tui/dashboard.js +8 -1
- package/dist/tui/dashboard.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +35 -0
- package/dist/tui/renderer.js.map +1 -1
- package/dist/weather-config.d.ts +17 -0
- package/dist/weather-config.d.ts.map +1 -0
- package/dist/weather-config.js +54 -0
- package/dist/weather-config.js.map +1 -0
- package/dist/weather.d.ts +44 -4
- package/dist/weather.d.ts.map +1 -1
- package/dist/weather.js +223 -15
- package/dist/weather.js.map +1 -1
- package/dist/ws-server.d.ts +18 -0
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +95 -7
- package/dist/ws-server.js.map +1 -1
- package/fm-helper/AgentDeckFMHelper.swift +51 -5
- package/package.json +4 -3
- 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":"AAsBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAc/D,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"passive-observer.d.ts","sourceRoot":"","sources":["../src/passive-observer.ts"],"names":[],"mappings":"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"}
|
package/dist/passive-observer.js
CHANGED
|
@@ -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 &&
|
|
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,
|