@agentdeck/bridge 1.0.3 → 1.0.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.
- package/assets/fm-helper/agentdeck-fm-helper +0 -0
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +3 -0
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adb-reverse.d.ts.map +1 -1
- package/dist/adb-reverse.js +4 -2
- package/dist/adb-reverse.js.map +1 -1
- package/dist/apme/collector.js +2 -2
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/outcome.js +2 -2
- package/dist/apme/outcome.js.map +1 -1
- package/dist/apme/runner.js +3 -3
- package/dist/apme/runner.js.map +1 -1
- package/dist/ble-sync-spawn.js +1 -1
- package/dist/ble-sync-spawn.js.map +1 -1
- package/dist/calendar.d.ts +82 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +324 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card-feed.d.ts +63 -5
- package/dist/card-feed.d.ts.map +1 -1
- package/dist/card-feed.js +218 -14
- package/dist/card-feed.js.map +1 -1
- package/dist/card-modules.d.ts +4 -0
- package/dist/card-modules.d.ts.map +1 -1
- package/dist/card-modules.js +5 -3
- package/dist/card-modules.js.map +1 -1
- package/dist/check-deps.js +4 -4
- package/dist/check-deps.js.map +1 -1
- package/dist/cli.d.ts +35 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +406 -76
- package/dist/cli.js.map +1 -1
- package/dist/codex-otel.d.ts +89 -0
- package/dist/codex-otel.d.ts.map +1 -0
- package/dist/codex-otel.js +487 -0
- package/dist/codex-otel.js.map +1 -0
- package/dist/codex-rate-limits.d.ts.map +1 -1
- package/dist/codex-rate-limits.js +15 -3
- package/dist/codex-rate-limits.js.map +1 -1
- package/dist/daemon-server.d.ts +4 -0
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +797 -54
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-ws-client.d.ts +108 -24
- package/dist/daemon-ws-client.d.ts.map +1 -1
- package/dist/daemon-ws-client.js +175 -39
- package/dist/daemon-ws-client.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/device-voice-reply.d.ts.map +1 -1
- package/dist/device-voice-reply.js +15 -3
- package/dist/device-voice-reply.js.map +1 -1
- package/dist/device-voice.d.ts +37 -0
- package/dist/device-voice.d.ts.map +1 -1
- package/dist/device-voice.js +130 -1
- package/dist/device-voice.js.map +1 -1
- package/dist/diag-analyzer.d.ts.map +1 -1
- package/dist/diag-analyzer.js +1 -0
- package/dist/diag-analyzer.js.map +1 -1
- package/dist/display-monitor.d.ts.map +1 -1
- package/dist/display-monitor.js +1 -0
- package/dist/display-monitor.js.map +1 -1
- package/dist/esp32-serial.d.ts +4 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +6 -3
- package/dist/esp32-serial.js.map +1 -1
- package/dist/foundation-models-helper.d.ts +23 -0
- package/dist/foundation-models-helper.d.ts.map +1 -1
- package/dist/foundation-models-helper.js +32 -0
- package/dist/foundation-models-helper.js.map +1 -1
- package/dist/gateway-probe.js +1 -1
- package/dist/gateway-probe.js.map +1 -1
- package/dist/glance-frame.d.ts +64 -0
- package/dist/glance-frame.d.ts.map +1 -0
- package/dist/glance-frame.js +364 -0
- package/dist/glance-frame.js.map +1 -0
- package/dist/hook-codex-sessions.d.ts +40 -0
- package/dist/hook-codex-sessions.d.ts.map +1 -0
- package/dist/hook-codex-sessions.js +174 -0
- package/dist/hook-codex-sessions.js.map +1 -0
- package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.js +5 -18
- package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
- package/dist/idotmatrix/idotmatrix-discover.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-discover.js +12 -17
- package/dist/idotmatrix/idotmatrix-discover.js.map +1 -1
- package/dist/idotmatrix/idotmatrix-settings.d.ts +15 -0
- package/dist/idotmatrix/idotmatrix-settings.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-settings.js +21 -0
- package/dist/idotmatrix/idotmatrix-settings.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -7
- package/dist/index.js.map +1 -1
- package/dist/linux-service.d.ts +52 -0
- package/dist/linux-service.d.ts.map +1 -0
- package/dist/linux-service.js +206 -0
- package/dist/linux-service.js.map +1 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -1
- package/dist/mdns-discover.d.ts +19 -0
- package/dist/mdns-discover.d.ts.map +1 -0
- package/dist/mdns-discover.js +109 -0
- package/dist/mdns-discover.js.map +1 -0
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +1 -0
- package/dist/model-catalog.js.map +1 -1
- package/dist/modules/adb-module.js +1 -1
- package/dist/modules/adb-module.js.map +1 -1
- package/dist/output-parser.d.ts +9 -0
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +67 -16
- package/dist/output-parser.js.map +1 -1
- package/dist/passive-observer.d.ts +57 -8
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +210 -74
- package/dist/passive-observer.js.map +1 -1
- package/dist/pixoo/pixoo-bridge.d.ts.map +1 -1
- package/dist/pixoo/pixoo-bridge.js +47 -6
- package/dist/pixoo/pixoo-bridge.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +2 -1
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +137 -8
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pocket-autonomy.d.ts +78 -0
- package/dist/pocket-autonomy.d.ts.map +1 -0
- package/dist/pocket-autonomy.js +529 -0
- package/dist/pocket-autonomy.js.map +1 -0
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +3 -2
- package/dist/pty-manager.js.map +1 -1
- package/dist/python-ble-runtime.d.ts +64 -0
- package/dist/python-ble-runtime.d.ts.map +1 -0
- package/dist/python-ble-runtime.js +216 -0
- package/dist/python-ble-runtime.js.map +1 -0
- package/dist/remote-sessions.d.ts +64 -0
- package/dist/remote-sessions.d.ts.map +1 -0
- package/dist/remote-sessions.js +81 -0
- package/dist/remote-sessions.js.map +1 -0
- package/dist/review-runner.js +2 -2
- package/dist/review-runner.js.map +1 -1
- package/dist/session-aggregator.d.ts +2 -0
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js +6 -1
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-focus-relay.d.ts +47 -1
- package/dist/session-focus-relay.d.ts.map +1 -1
- package/dist/session-focus-relay.js +130 -15
- package/dist/session-focus-relay.js.map +1 -1
- package/dist/session-push-channel.d.ts +31 -0
- package/dist/session-push-channel.d.ts.map +1 -0
- package/dist/session-push-channel.js +94 -0
- package/dist/session-push-channel.js.map +1 -0
- package/dist/session-registry.d.ts +126 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +180 -3
- package/dist/session-registry.js.map +1 -1
- package/dist/subagent-timeline.d.ts +34 -0
- package/dist/subagent-timeline.d.ts.map +1 -0
- package/dist/subagent-timeline.js +156 -0
- package/dist/subagent-timeline.js.map +1 -0
- package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
- package/dist/timebox/timebox-daemon-sync.js +5 -20
- package/dist/timebox/timebox-daemon-sync.js.map +1 -1
- package/dist/timebox/timebox-discover.d.ts.map +1 -1
- package/dist/timebox/timebox-discover.js +7 -16
- package/dist/timebox/timebox-discover.js.map +1 -1
- package/dist/tui/gauge.d.ts +4 -2
- package/dist/tui/gauge.d.ts.map +1 -1
- package/dist/tui/gauge.js +8 -4
- package/dist/tui/gauge.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +28 -0
- package/dist/tui/renderer.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/usage-api.d.ts +43 -0
- package/dist/usage-api.d.ts.map +1 -1
- package/dist/usage-api.js +114 -9
- package/dist/usage-api.js.map +1 -1
- package/dist/usage-event.d.ts +19 -0
- package/dist/usage-event.d.ts.map +1 -1
- package/dist/usage-event.js +35 -0
- package/dist/usage-event.js.map +1 -1
- package/dist/utils/project-name.d.ts.map +1 -1
- package/dist/utils/project-name.js +1 -0
- package/dist/utils/project-name.js.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +3 -2
- package/dist/version-check.js.map +1 -1
- package/dist/voice-assistant.d.ts.map +1 -1
- package/dist/voice-assistant.js +2 -1
- package/dist/voice-assistant.js.map +1 -1
- package/dist/voice.d.ts.map +1 -1
- package/dist/voice.js +1 -0
- package/dist/voice.js.map +1 -1
- package/dist/weather.d.ts +72 -0
- package/dist/weather.d.ts.map +1 -0
- package/dist/weather.js +206 -0
- package/dist/weather.js.map +1 -0
- package/dist/windows-service.js +5 -5
- package/dist/windows-service.js.map +1 -1
- package/fm-helper/AgentDeckFMHelper.swift +200 -0
- package/package.json +20 -14
- package/python/requirements-ble.txt +5 -0
- package/src/idotmatrix/brightness.py +40 -0
- package/src/idotmatrix/identity_generated.py +50 -0
- package/src/idotmatrix/scan.py +55 -0
- package/src/idotmatrix/sync.py +400 -0
- package/src/pysync/matrix_sync_common.py +89 -0
- package/src/timebox/scan_ble.py +52 -0
- package/src/timebox/sync_ble.py +394 -0
|
@@ -17,6 +17,15 @@ export interface TranscriptSummary {
|
|
|
17
17
|
totalTokens?: number;
|
|
18
18
|
contextPercent?: number;
|
|
19
19
|
}
|
|
20
|
+
export interface CodexRolloutSummary extends TranscriptSummary {
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
startedAt?: number;
|
|
24
|
+
effort?: string;
|
|
25
|
+
hasPendingCalls?: boolean;
|
|
26
|
+
/** Internal companion rollouts never become top-level dashboard sessions. */
|
|
27
|
+
isSubagent: boolean;
|
|
28
|
+
}
|
|
20
29
|
/**
|
|
21
30
|
* Condense a first user prompt into a one-line session goal: strip injected
|
|
22
31
|
* tags/markup + slash-command noise, collapse whitespace, cap length. Returns ''
|
|
@@ -43,10 +52,35 @@ export interface ObservedSession extends EnrichedSession {
|
|
|
43
52
|
* prompt is over. Absent when no transcript could be located. */
|
|
44
53
|
lastActivityAt?: number;
|
|
45
54
|
}
|
|
55
|
+
interface CodexRolloutFileInfo {
|
|
56
|
+
mtimeMs: number;
|
|
57
|
+
size: number;
|
|
58
|
+
}
|
|
59
|
+
interface CodexRolloutCacheEntry extends CodexRolloutFileInfo {
|
|
60
|
+
summary: CodexRolloutSummary;
|
|
61
|
+
}
|
|
62
|
+
export interface CodexRolloutCacheDependencies {
|
|
63
|
+
stat(path: string): Promise<CodexRolloutFileInfo>;
|
|
64
|
+
read(path: string, headBytes: number, tailBytes: number): Promise<string>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Bounded per-rollout summary cache. A scan only stats open rollout paths;
|
|
68
|
+
* unchanged `(path, mtime, size)` entries reuse their parsed summary. Changed
|
|
69
|
+
* files are read asynchronously, and paths no longer held open are invalidated.
|
|
70
|
+
*/
|
|
71
|
+
export declare class CodexRolloutCache {
|
|
72
|
+
private deps;
|
|
73
|
+
private entries;
|
|
74
|
+
constructor(deps?: CodexRolloutCacheDependencies);
|
|
75
|
+
get(path: string): Promise<CodexRolloutCacheEntry | undefined>;
|
|
76
|
+
retain(paths: ReadonlySet<string>): void;
|
|
77
|
+
get size(): number;
|
|
78
|
+
}
|
|
46
79
|
export declare class PassiveSessionObserver {
|
|
47
80
|
private lastScanAt;
|
|
48
81
|
private cached;
|
|
49
82
|
private scanInFlight;
|
|
83
|
+
private codexRolloutCache;
|
|
50
84
|
/** Fired after a background scan changed the cached list. Wire to a
|
|
51
85
|
* debounced sessions broadcast so fresh observations reach clients
|
|
52
86
|
* without the enricher ever blocking on the scan. */
|
|
@@ -65,13 +99,7 @@ export declare class PassiveSessionObserver {
|
|
|
65
99
|
}
|
|
66
100
|
export declare function parseProcessTable(output: string): ProcInfo[];
|
|
67
101
|
export declare function parseClaudeTranscript(raw: string): TranscriptSummary;
|
|
68
|
-
export declare function parseCodexRollout(raw: string):
|
|
69
|
-
sessionId?: string;
|
|
70
|
-
cwd?: string;
|
|
71
|
-
startedAt?: number;
|
|
72
|
-
effort?: string;
|
|
73
|
-
hasPendingCalls?: boolean;
|
|
74
|
-
};
|
|
102
|
+
export declare function parseCodexRollout(raw: string): CodexRolloutSummary;
|
|
75
103
|
/** Extract the enclosing macOS app name from an executable path, e.g.
|
|
76
104
|
* "/Applications/ChatGPT.app/Contents/Resources/codex …" → "ChatGPT". */
|
|
77
105
|
export declare function appNameFromCommand(command: string): string | undefined;
|
|
@@ -79,6 +107,27 @@ export declare function appNameFromCommand(command: string): string | undefined;
|
|
|
79
107
|
* this process. GUI-hosted agent sessions (Claude.app, ChatGPT.app) have no
|
|
80
108
|
* tty, so the owning app is how the injector finds their UI. */
|
|
81
109
|
export declare function resolveHostApp(pid: number, byPid: Map<number, ProcInfo>, maxDepth?: number): string | undefined;
|
|
110
|
+
/** Testable async projection from open rollout paths to top-level sessions. */
|
|
111
|
+
export declare function collectCodexSessionsFromRollouts(processes: ProcInfo[], rolloutsByPid: Map<number, string[]>, rolloutCache: CodexRolloutCache): Promise<ObservedSession[]>;
|
|
112
|
+
/**
|
|
113
|
+
* A Codex process that can own a user session.
|
|
114
|
+
*
|
|
115
|
+
* `codex app-server` was excluded here originally because it was only ever an
|
|
116
|
+
* IDE extension's headless backend. The ChatGPT.app Codex desktop app now runs
|
|
117
|
+
* exactly that command for a real, user-facing session
|
|
118
|
+
* (`/Applications/ChatGPT.app/Contents/Resources/codex … app-server`), so the
|
|
119
|
+
* process shape no longer separates the two and the exclusion made every
|
|
120
|
+
* desktop-app session invisible — no session row, no creature — even though its
|
|
121
|
+
* lifecycle hooks were landing in the timeline the whole time.
|
|
122
|
+
*
|
|
123
|
+
* Admission is settled downstream instead, by whether the pid holds an open
|
|
124
|
+
* rollout file: an app-server with no conversation holds none and is dropped in
|
|
125
|
+
* `collectCodexSessions`. The Electron helpers ship a capital-`Codex` basename
|
|
126
|
+
* (`Codex (Renderer)`), so `cmdHasBinary` already excludes them.
|
|
127
|
+
*/
|
|
128
|
+
export declare function isCodexSessionProcessCommand(command: string): boolean;
|
|
82
129
|
export declare function isAntigravityProcessCommand(command: string): boolean;
|
|
83
|
-
export declare function
|
|
130
|
+
export declare function dedupeObservedSessions(observed: ObservedSession[], managedSessions: EnrichedSession[], processes: ProcInfo[]): ObservedSession[];
|
|
131
|
+
export declare function parseLsofRollouts(output: string): Map<number, string[]>;
|
|
132
|
+
export {};
|
|
84
133
|
//# sourceMappingURL=passive-observer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passive-observer.d.ts","sourceRoot":"","sources":["../src/passive-observer.ts"],"names":[],"mappings":"
|
|
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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
+
import { open as openAsync, readdir as readdirAsync, readlink as readlinkAsync, stat as statAsync, } from 'node:fs/promises';
|
|
3
4
|
const execFileAsync = promisify(execFile);
|
|
4
|
-
import { closeSync, existsSync, lstatSync, openSync, readFileSync,
|
|
5
|
+
import { closeSync, existsSync, lstatSync, openSync, readFileSync, readSync, readdirSync, statSync, } from 'node:fs';
|
|
5
6
|
import { basename, join } from 'node:path';
|
|
6
7
|
import { homedir } from 'node:os';
|
|
7
8
|
import { resolveProjectNameFromCwdCached } from './utils/project-name.js';
|
|
@@ -44,10 +45,58 @@ const MAX_TAIL_BYTES = 512 * 1024;
|
|
|
44
45
|
const MAX_SAMPLE_BYTES = 1024 * 1024;
|
|
45
46
|
/** Rollout silence (no writes) after which an end-event-less turn is presumed dead. */
|
|
46
47
|
const STALE_TURN_MS = 10 * 60 * 1000;
|
|
48
|
+
const defaultCodexRolloutCacheDependencies = {
|
|
49
|
+
stat: async (path) => {
|
|
50
|
+
const info = await statAsync(path);
|
|
51
|
+
return { mtimeMs: info.mtimeMs, size: info.size };
|
|
52
|
+
},
|
|
53
|
+
read: readFileHeadAndTailAsync,
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Bounded per-rollout summary cache. A scan only stats open rollout paths;
|
|
57
|
+
* unchanged `(path, mtime, size)` entries reuse their parsed summary. Changed
|
|
58
|
+
* files are read asynchronously, and paths no longer held open are invalidated.
|
|
59
|
+
*/
|
|
60
|
+
export class CodexRolloutCache {
|
|
61
|
+
deps;
|
|
62
|
+
entries = new Map();
|
|
63
|
+
constructor(deps = defaultCodexRolloutCacheDependencies) {
|
|
64
|
+
this.deps = deps;
|
|
65
|
+
}
|
|
66
|
+
async get(path) {
|
|
67
|
+
let info;
|
|
68
|
+
try {
|
|
69
|
+
info = await this.deps.stat(path);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
this.entries.delete(path);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const cached = this.entries.get(path);
|
|
76
|
+
if (cached && cached.mtimeMs === info.mtimeMs && cached.size === info.size)
|
|
77
|
+
return cached;
|
|
78
|
+
const raw = await this.deps.read(path, 256 * 1024, MAX_SAMPLE_BYTES).catch(() => '');
|
|
79
|
+
if (!raw) {
|
|
80
|
+
this.entries.delete(path);
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const entry = { ...info, summary: parseCodexRollout(raw) };
|
|
84
|
+
this.entries.set(path, entry);
|
|
85
|
+
return entry;
|
|
86
|
+
}
|
|
87
|
+
retain(paths) {
|
|
88
|
+
for (const path of this.entries.keys()) {
|
|
89
|
+
if (!paths.has(path))
|
|
90
|
+
this.entries.delete(path);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
get size() { return this.entries.size; }
|
|
94
|
+
}
|
|
47
95
|
export class PassiveSessionObserver {
|
|
48
96
|
lastScanAt = 0;
|
|
49
97
|
cached = [];
|
|
50
98
|
scanInFlight = false;
|
|
99
|
+
codexRolloutCache = new CodexRolloutCache();
|
|
51
100
|
/** Fired after a background scan changed the cached list. Wire to a
|
|
52
101
|
* debounced sessions broadcast so fresh observations reach clients
|
|
53
102
|
* without the enricher ever blocking on the scan. */
|
|
@@ -76,7 +125,7 @@ export class PassiveSessionObserver {
|
|
|
76
125
|
const processes = await collectProcessInfo();
|
|
77
126
|
const observed = [
|
|
78
127
|
...collectClaudeSessions(processes),
|
|
79
|
-
...(await collectCodexSessions(processes)),
|
|
128
|
+
...(await collectCodexSessions(processes, this.codexRolloutCache)),
|
|
80
129
|
...(await collectOpenCodeSessions(processes)),
|
|
81
130
|
...(await collectAntigravitySessions(processes)),
|
|
82
131
|
];
|
|
@@ -193,6 +242,33 @@ export function parseClaudeTranscript(raw) {
|
|
|
193
242
|
: undefined,
|
|
194
243
|
};
|
|
195
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Codex reports `input_tokens` INCLUSIVE of `cached_input_tokens` — the live
|
|
247
|
+
* rollouts satisfy `total_tokens === input_tokens + output_tokens`. Adding the
|
|
248
|
+
* cached count on top double-counts it, which pushed one observed session to
|
|
249
|
+
* 105% context and roughly doubled its token total. Older producers that report
|
|
250
|
+
* cached separately have no `total_tokens`, so the additive form stays the
|
|
251
|
+
* fallback and their accounting is unchanged.
|
|
252
|
+
*/
|
|
253
|
+
function codexCachedIsIncluded(usage) {
|
|
254
|
+
const reported = numberAt(usage, 'total_tokens');
|
|
255
|
+
if (reported <= 0)
|
|
256
|
+
return false;
|
|
257
|
+
return numberAt(usage, 'input_tokens') + numberAt(usage, 'output_tokens') === reported;
|
|
258
|
+
}
|
|
259
|
+
function codexCachedInput(usage) {
|
|
260
|
+
return numberAt(usage, 'cached_input_tokens') || numberAt(usage, 'cache_read_input_tokens');
|
|
261
|
+
}
|
|
262
|
+
/** Whole-session token spend (prompt + completion, cached counted once). */
|
|
263
|
+
function codexUsageTotal(usage) {
|
|
264
|
+
const base = numberAt(usage, 'input_tokens') + numberAt(usage, 'output_tokens');
|
|
265
|
+
return codexCachedIsIncluded(usage) ? base : base + codexCachedInput(usage);
|
|
266
|
+
}
|
|
267
|
+
/** Context-window occupancy: the prompt side of the most recent request only. */
|
|
268
|
+
function codexUsageContext(usage) {
|
|
269
|
+
const input = numberAt(usage, 'input_tokens');
|
|
270
|
+
return codexCachedIsIncluded(usage) ? input : input + codexCachedInput(usage);
|
|
271
|
+
}
|
|
196
272
|
export function parseCodexRollout(raw) {
|
|
197
273
|
let sessionId;
|
|
198
274
|
let cwd;
|
|
@@ -204,6 +280,7 @@ export function parseCodexRollout(raw) {
|
|
|
204
280
|
let totalTokens = 0;
|
|
205
281
|
let lastContextTokens = 0;
|
|
206
282
|
let contextWindow = 0;
|
|
283
|
+
let isSubagent = false;
|
|
207
284
|
// A turn is in flight from task_started/user_message until task_complete/
|
|
208
285
|
// turn_aborted. Mid-turn events (agent_message, function_call) must NOT end
|
|
209
286
|
// it: most of a working turn is the thinking gap between a tool result and
|
|
@@ -223,6 +300,8 @@ export function parseCodexRollout(raw) {
|
|
|
223
300
|
sessionId = stringAt(payload, 'id') ?? sessionId;
|
|
224
301
|
cwd = stringAt(payload, 'cwd') ?? cwd;
|
|
225
302
|
startedAt = timestampMs(stringAt(payload, 'timestamp')) ?? startedAt;
|
|
303
|
+
const source = payload.source;
|
|
304
|
+
isSubagent = isSubagent || (isRecord(source) && 'subagent' in source);
|
|
226
305
|
}
|
|
227
306
|
else if (type === 'event_msg') {
|
|
228
307
|
const payload = objectAt(value, 'payload');
|
|
@@ -262,18 +341,11 @@ export function parseCodexRollout(raw) {
|
|
|
262
341
|
if (!info)
|
|
263
342
|
break;
|
|
264
343
|
const total = objectAt(info, 'total_token_usage');
|
|
265
|
-
if (total)
|
|
266
|
-
totalTokens =
|
|
267
|
-
numberAt(total, 'input_tokens') +
|
|
268
|
-
numberAt(total, 'output_tokens') +
|
|
269
|
-
(numberAt(total, 'cached_input_tokens') || numberAt(total, 'cache_read_input_tokens'));
|
|
270
|
-
}
|
|
344
|
+
if (total)
|
|
345
|
+
totalTokens = codexUsageTotal(total);
|
|
271
346
|
const last = objectAt(info, 'last_token_usage');
|
|
272
|
-
if (last)
|
|
273
|
-
lastContextTokens =
|
|
274
|
-
numberAt(last, 'input_tokens') +
|
|
275
|
-
(numberAt(last, 'cached_input_tokens') || numberAt(last, 'cache_read_input_tokens'));
|
|
276
|
-
}
|
|
347
|
+
if (last)
|
|
348
|
+
lastContextTokens = codexUsageContext(last);
|
|
277
349
|
contextWindow = numberAt(info, 'model_context_window') || contextWindow;
|
|
278
350
|
break;
|
|
279
351
|
}
|
|
@@ -317,6 +389,7 @@ export function parseCodexRollout(raw) {
|
|
|
317
389
|
const state = turnActive || pendingCalls.size > 0 ? 'processing' : 'idle';
|
|
318
390
|
return {
|
|
319
391
|
hasPendingCalls: pendingCalls.size > 0,
|
|
392
|
+
isSubagent,
|
|
320
393
|
sessionId,
|
|
321
394
|
cwd,
|
|
322
395
|
startedAt,
|
|
@@ -337,6 +410,7 @@ async function collectProcessInfo() {
|
|
|
337
410
|
encoding: 'utf8',
|
|
338
411
|
timeout: 2_000,
|
|
339
412
|
maxBuffer: 2 * 1024 * 1024,
|
|
413
|
+
windowsHide: true, // git-bash ps.exe pops a console window every 5s scan on Windows without this
|
|
340
414
|
});
|
|
341
415
|
return parseProcessTable(stdout);
|
|
342
416
|
}
|
|
@@ -404,56 +478,88 @@ function collectClaudeSessions(processes) {
|
|
|
404
478
|
}
|
|
405
479
|
return sessions;
|
|
406
480
|
}
|
|
407
|
-
async function collectCodexSessions(processes) {
|
|
481
|
+
async function collectCodexSessions(processes, rolloutCache) {
|
|
482
|
+
const codex = processes.filter((p) => isCodexSessionProcessCommand(p.command));
|
|
483
|
+
const rolloutsByPid = await mapCodexPidsToRollouts(codex.map((p) => p.pid));
|
|
484
|
+
return collectCodexSessionsFromRollouts(processes, rolloutsByPid, rolloutCache);
|
|
485
|
+
}
|
|
486
|
+
/** Testable async projection from open rollout paths to top-level sessions. */
|
|
487
|
+
export async function collectCodexSessionsFromRollouts(processes, rolloutsByPid, rolloutCache) {
|
|
408
488
|
const byPid = new Map(processes.map((p) => [p.pid, p]));
|
|
409
|
-
const codex = processes.filter((p) =>
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
489
|
+
const codex = processes.filter((p) => isCodexSessionProcessCommand(p.command));
|
|
490
|
+
const activeRollouts = new Set();
|
|
491
|
+
for (const paths of rolloutsByPid.values()) {
|
|
492
|
+
for (const path of paths)
|
|
493
|
+
activeRollouts.add(path);
|
|
494
|
+
}
|
|
495
|
+
rolloutCache.retain(activeRollouts);
|
|
413
496
|
const sessions = [];
|
|
497
|
+
const seen = new Set();
|
|
414
498
|
for (const proc of codex) {
|
|
415
|
-
const rollout
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
499
|
+
for (const rollout of rolloutsByPid.get(proc.pid) ?? []) {
|
|
500
|
+
// Sequential awaits deliberately bound memory/read pressure to one rollout
|
|
501
|
+
// sample at a time while still yielding the daemon event loop.
|
|
502
|
+
const snapshot = await rolloutCache.get(rollout);
|
|
503
|
+
if (!snapshot || snapshot.summary.isSubagent)
|
|
504
|
+
continue;
|
|
505
|
+
const parsed = snapshot.summary;
|
|
506
|
+
let state = parsed.state;
|
|
507
|
+
if (state === 'processing' && !parsed.hasPendingCalls && Date.now() - snapshot.mtimeMs > STALE_TURN_MS) {
|
|
508
|
+
state = 'idle';
|
|
509
|
+
}
|
|
510
|
+
const sessionId = parsed.sessionId ?? codexRolloutId(rollout);
|
|
511
|
+
if (seen.has(sessionId))
|
|
512
|
+
continue;
|
|
513
|
+
seen.add(sessionId);
|
|
514
|
+
const cwd = parsed.cwd;
|
|
515
|
+
const desktop = proc.command.includes('app-server');
|
|
516
|
+
sessions.push({
|
|
517
|
+
id: desktop ? `observed:codex-app:${sessionId}` : `observed:codex:${sessionId}`,
|
|
518
|
+
port: 0,
|
|
519
|
+
pid: proc.pid,
|
|
520
|
+
projectName: cwd ? projectNameFromCwd(cwd) : 'Codex',
|
|
521
|
+
agentType: desktop ? 'codex-app' : 'codex-cli',
|
|
522
|
+
tty: proc.tty,
|
|
523
|
+
appName: proc.tty ? undefined : resolveHostApp(proc.pid, byPid),
|
|
524
|
+
alive: true,
|
|
525
|
+
state,
|
|
526
|
+
modelName: parsed.modelName,
|
|
527
|
+
startedAt: parsed.startedAt ? new Date(parsed.startedAt).toISOString() : new Date().toISOString(),
|
|
528
|
+
controlMode: 'observed',
|
|
529
|
+
cwd,
|
|
530
|
+
currentTask: parsed.currentTask,
|
|
531
|
+
goal: parsed.goal,
|
|
532
|
+
contextPercent: parsed.contextPercent,
|
|
533
|
+
totalTokens: parsed.totalTokens,
|
|
534
|
+
lastActivityAt: snapshot.mtimeMs,
|
|
535
|
+
});
|
|
430
536
|
}
|
|
431
|
-
const sessionId = parsed.sessionId ?? String(proc.pid);
|
|
432
|
-
const cwd = parsed.cwd;
|
|
433
|
-
sessions.push({
|
|
434
|
-
id: `observed:codex:${sessionId}`,
|
|
435
|
-
port: 0,
|
|
436
|
-
pid: proc.pid,
|
|
437
|
-
projectName: cwd ? projectNameFromCwd(cwd) : 'Codex',
|
|
438
|
-
agentType: 'codex-cli',
|
|
439
|
-
// Same injection targets as observed Claude: answering or dictating into
|
|
440
|
-
// a Codex session means typing into the terminal that owns it.
|
|
441
|
-
tty: proc.tty,
|
|
442
|
-
appName: proc.tty ? undefined : resolveHostApp(proc.pid, byPid),
|
|
443
|
-
alive: true,
|
|
444
|
-
state,
|
|
445
|
-
modelName: parsed.modelName,
|
|
446
|
-
startedAt: parsed.startedAt ? new Date(parsed.startedAt).toISOString() : new Date().toISOString(),
|
|
447
|
-
controlMode: 'observed',
|
|
448
|
-
cwd,
|
|
449
|
-
currentTask: parsed.currentTask,
|
|
450
|
-
goal: parsed.goal,
|
|
451
|
-
contextPercent: parsed.contextPercent,
|
|
452
|
-
totalTokens: parsed.totalTokens,
|
|
453
|
-
});
|
|
454
537
|
}
|
|
455
538
|
return sessions;
|
|
456
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* A Codex process that can own a user session.
|
|
542
|
+
*
|
|
543
|
+
* `codex app-server` was excluded here originally because it was only ever an
|
|
544
|
+
* IDE extension's headless backend. The ChatGPT.app Codex desktop app now runs
|
|
545
|
+
* exactly that command for a real, user-facing session
|
|
546
|
+
* (`/Applications/ChatGPT.app/Contents/Resources/codex … app-server`), so the
|
|
547
|
+
* process shape no longer separates the two and the exclusion made every
|
|
548
|
+
* desktop-app session invisible — no session row, no creature — even though its
|
|
549
|
+
* lifecycle hooks were landing in the timeline the whole time.
|
|
550
|
+
*
|
|
551
|
+
* Admission is settled downstream instead, by whether the pid holds an open
|
|
552
|
+
* rollout file: an app-server with no conversation holds none and is dropped in
|
|
553
|
+
* `collectCodexSessions`. The Electron helpers ship a capital-`Codex` basename
|
|
554
|
+
* (`Codex (Renderer)`), so `cmdHasBinary` already excludes them.
|
|
555
|
+
*/
|
|
556
|
+
export function isCodexSessionProcessCommand(command) {
|
|
557
|
+
return cmdHasBinary(command, 'codex') && !command.includes('grep');
|
|
558
|
+
}
|
|
559
|
+
/** Fallback session id for a rollout with no parsable `session_meta`. */
|
|
560
|
+
function codexRolloutId(rolloutPath) {
|
|
561
|
+
return basename(rolloutPath).replace(/^rollout-/, '').replace(/\.jsonl$/, '');
|
|
562
|
+
}
|
|
457
563
|
/**
|
|
458
564
|
* Surface standalone `opencode` processes (not launched via `agentdeck opencode`).
|
|
459
565
|
* Unlike Claude Code / Codex, OpenCode has no lifecycle hooks, so without this a
|
|
@@ -565,9 +671,10 @@ async function cwdForPids(pids) {
|
|
|
565
671
|
}
|
|
566
672
|
return map;
|
|
567
673
|
}
|
|
568
|
-
function dedupeObservedSessions(observed, managedSessions, processes) {
|
|
674
|
+
export function dedupeObservedSessions(observed, managedSessions, processes) {
|
|
569
675
|
const byPid = new Map(processes.map((p) => [p.pid, p]));
|
|
570
676
|
const managedIds = new Set(managedSessions.map((s) => s.id));
|
|
677
|
+
const managedRawIds = new Set(managedSessions.map((s) => rawSessionId(s.id)));
|
|
571
678
|
const managedPids = managedSessions
|
|
572
679
|
.map((s) => s.pid)
|
|
573
680
|
.filter((pid) => typeof pid === 'number' && pid > 0);
|
|
@@ -575,8 +682,13 @@ function dedupeObservedSessions(observed, managedSessions, processes) {
|
|
|
575
682
|
if (managedIds.has(session.id))
|
|
576
683
|
return false;
|
|
577
684
|
const rawId = rawSessionId(session.id);
|
|
578
|
-
if (managedIds.has(rawId))
|
|
685
|
+
if (managedIds.has(rawId) || managedRawIds.has(rawId))
|
|
579
686
|
return false;
|
|
687
|
+
// One Desktop app-server owns many independent rollouts. A hook-observed
|
|
688
|
+
// conversation sharing that process must suppress only its matching id,
|
|
689
|
+
// not every sibling conversation under the same pid.
|
|
690
|
+
if (session.agentType === 'codex-app')
|
|
691
|
+
return true;
|
|
580
692
|
return !managedPids.some((pid) => pid === session.pid || isDescendantOf(session.pid, pid, byPid));
|
|
581
693
|
});
|
|
582
694
|
}
|
|
@@ -633,6 +745,11 @@ function findClaudeTranscript(configDirs, cwd, sessionId) {
|
|
|
633
745
|
}
|
|
634
746
|
return null;
|
|
635
747
|
}
|
|
748
|
+
/**
|
|
749
|
+
* Open rollout files per pid. A terminal `codex` owns exactly one, but a desktop
|
|
750
|
+
* app-server pid owns one per open conversation — so this is 1:N and every entry
|
|
751
|
+
* becomes its own session.
|
|
752
|
+
*/
|
|
636
753
|
async function mapCodexPidsToRollouts(pids) {
|
|
637
754
|
if (pids.length === 0)
|
|
638
755
|
return new Map();
|
|
@@ -640,13 +757,14 @@ async function mapCodexPidsToRollouts(pids) {
|
|
|
640
757
|
const map = new Map();
|
|
641
758
|
for (const pid of pids) {
|
|
642
759
|
try {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
760
|
+
const paths = new Set();
|
|
761
|
+
for (const fd of await readdirAsync(`/proc/${pid}/fd`)) {
|
|
762
|
+
const path = await readlinkAsync(`/proc/${pid}/fd/${fd}`);
|
|
763
|
+
if (isCodexRolloutPath(path))
|
|
764
|
+
paths.add(path);
|
|
649
765
|
}
|
|
766
|
+
if (paths.size > 0)
|
|
767
|
+
map.set(pid, [...paths]);
|
|
650
768
|
}
|
|
651
769
|
catch {
|
|
652
770
|
// /proc permission/race failures are normal.
|
|
@@ -676,20 +794,17 @@ export function parseLsofRollouts(output) {
|
|
|
676
794
|
}
|
|
677
795
|
else if (line.startsWith('n') && currentPid != null) {
|
|
678
796
|
const path = line.slice(1);
|
|
679
|
-
if (isCodexRolloutPath(path))
|
|
680
|
-
|
|
797
|
+
if (!isCodexRolloutPath(path))
|
|
798
|
+
continue;
|
|
799
|
+
const paths = map.get(currentPid) ?? [];
|
|
800
|
+
// The same rollout can appear twice (dup'd fd); one session per file.
|
|
801
|
+
if (!paths.includes(path))
|
|
802
|
+
paths.push(path);
|
|
803
|
+
map.set(currentPid, paths);
|
|
681
804
|
}
|
|
682
805
|
}
|
|
683
806
|
return map;
|
|
684
807
|
}
|
|
685
|
-
function rolloutAgeMs(path) {
|
|
686
|
-
try {
|
|
687
|
-
return Date.now() - statSync(path).mtimeMs;
|
|
688
|
-
}
|
|
689
|
-
catch {
|
|
690
|
-
return 0;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
808
|
/** Epoch-ms of a file's last write, or undefined if it can't be stat'd. */
|
|
694
809
|
function fileMtimeMs(path) {
|
|
695
810
|
try {
|
|
@@ -725,6 +840,27 @@ function readFileHeadAndTail(path, headBytes, tailBytes) {
|
|
|
725
840
|
closeSync(fd);
|
|
726
841
|
}
|
|
727
842
|
}
|
|
843
|
+
async function readFileHeadAndTailAsync(path, headBytes, tailBytes) {
|
|
844
|
+
const file = await openAsync(path, 'r');
|
|
845
|
+
try {
|
|
846
|
+
const { size } = await file.stat();
|
|
847
|
+
if (size <= headBytes + tailBytes) {
|
|
848
|
+
const buffer = Buffer.alloc(size);
|
|
849
|
+
await file.read(buffer, 0, size, 0);
|
|
850
|
+
return buffer.toString('utf8');
|
|
851
|
+
}
|
|
852
|
+
const head = Buffer.alloc(headBytes);
|
|
853
|
+
await file.read(head, 0, headBytes, 0);
|
|
854
|
+
const tail = Buffer.alloc(tailBytes);
|
|
855
|
+
await file.read(tail, 0, tailBytes, size - tailBytes);
|
|
856
|
+
const tailText = tail.toString('utf8');
|
|
857
|
+
const firstNewline = tailText.indexOf('\n');
|
|
858
|
+
return `${head.toString('utf8')}\n${firstNewline >= 0 ? tailText.slice(firstNewline + 1) : ''}`;
|
|
859
|
+
}
|
|
860
|
+
finally {
|
|
861
|
+
await file.close();
|
|
862
|
+
}
|
|
863
|
+
}
|
|
728
864
|
function parseJsonl(raw) {
|
|
729
865
|
const values = [];
|
|
730
866
|
for (const line of raw.split('\n')) {
|