@agentdeck/bridge 1.0.20 → 1.0.21
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/dist/adapters/openclaw.d.ts +55 -1
- package/dist/adapters/openclaw.d.ts.map +1 -1
- package/dist/adapters/openclaw.js +278 -45
- package/dist/adapters/openclaw.js.map +1 -1
- package/dist/adb-reverse.d.ts +26 -3
- package/dist/adb-reverse.d.ts.map +1 -1
- package/dist/adb-reverse.js +26 -5
- package/dist/adb-reverse.js.map +1 -1
- package/dist/apme/adapters/openclaw-hook.d.ts +45 -1
- package/dist/apme/adapters/openclaw-hook.d.ts.map +1 -1
- package/dist/apme/adapters/openclaw-hook.js +84 -4
- package/dist/apme/adapters/openclaw-hook.js.map +1 -1
- package/dist/apme/claude-transcript-reader.d.ts +54 -0
- package/dist/apme/claude-transcript-reader.d.ts.map +1 -1
- package/dist/apme/claude-transcript-reader.js +77 -0
- package/dist/apme/claude-transcript-reader.js.map +1 -1
- package/dist/apme/collector.d.ts +63 -5
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +147 -10
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/store.d.ts +23 -1
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +81 -2
- package/dist/apme/store.js.map +1 -1
- package/dist/apme/types.d.ts +1 -1
- package/dist/apme/types.d.ts.map +1 -1
- package/dist/apme/types.js.map +1 -1
- package/dist/auth.d.ts +37 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +23 -0
- package/dist/auth.js.map +1 -1
- package/dist/claude-interrupt-marker.d.ts +31 -0
- package/dist/claude-interrupt-marker.d.ts.map +1 -0
- package/dist/claude-interrupt-marker.js +58 -0
- package/dist/claude-interrupt-marker.js.map +1 -0
- package/dist/claude-turn-watchdog.d.ts +23 -0
- package/dist/claude-turn-watchdog.d.ts.map +1 -1
- package/dist/claude-turn-watchdog.js +30 -4
- package/dist/claude-turn-watchdog.js.map +1 -1
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +398 -115
- package/dist/cli.js.map +1 -1
- package/dist/daemon-port.d.ts +90 -0
- package/dist/daemon-port.d.ts.map +1 -0
- package/dist/daemon-port.js +119 -0
- package/dist/daemon-port.js.map +1 -0
- package/dist/daemon-server.d.ts +14 -1
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +297 -69
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-settings.d.ts +46 -0
- package/dist/daemon-settings.d.ts.map +1 -0
- package/dist/daemon-settings.js +85 -0
- package/dist/daemon-settings.js.map +1 -0
- package/dist/daemon-takeover.d.ts +75 -0
- package/dist/daemon-takeover.d.ts.map +1 -0
- package/dist/daemon-takeover.js +137 -0
- package/dist/daemon-takeover.js.map +1 -0
- package/dist/daemon.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -11
- package/dist/index.js.map +1 -1
- package/dist/kiro-diagnostics.d.ts +90 -0
- package/dist/kiro-diagnostics.d.ts.map +1 -0
- package/dist/kiro-diagnostics.js +278 -0
- package/dist/kiro-diagnostics.js.map +1 -0
- package/dist/kiro-session.d.ts +113 -0
- package/dist/kiro-session.d.ts.map +1 -0
- package/dist/kiro-session.js +688 -0
- package/dist/kiro-session.js.map +1 -0
- package/dist/kiro-timeline-feed.d.ts +45 -0
- package/dist/kiro-timeline-feed.d.ts.map +1 -0
- package/dist/kiro-timeline-feed.js +116 -0
- package/dist/kiro-timeline-feed.js.map +1 -0
- package/dist/kiro-transcript-timeline.d.ts +57 -0
- package/dist/kiro-transcript-timeline.d.ts.map +1 -0
- package/dist/kiro-transcript-timeline.js +183 -0
- package/dist/kiro-transcript-timeline.js.map +1 -0
- package/dist/linux-service.d.ts +2 -1
- package/dist/linux-service.d.ts.map +1 -1
- package/dist/linux-service.js +8 -3
- package/dist/linux-service.js.map +1 -1
- package/dist/mdns-discover.d.ts +27 -0
- package/dist/mdns-discover.d.ts.map +1 -1
- package/dist/mdns-discover.js +60 -7
- package/dist/mdns-discover.js.map +1 -1
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +16 -3
- package/dist/mdns.js.map +1 -1
- package/dist/modules/adb-module.d.ts.map +1 -1
- package/dist/modules/adb-module.js +5 -2
- package/dist/modules/adb-module.js.map +1 -1
- package/dist/modules/index.d.ts +2 -1
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +1 -0
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/types.d.ts +34 -9
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/modules/types.js +34 -1
- package/dist/modules/types.js.map +1 -1
- package/dist/network-posture.d.ts +67 -0
- package/dist/network-posture.d.ts.map +1 -0
- package/dist/network-posture.js +100 -0
- package/dist/network-posture.js.map +1 -0
- package/dist/observed-steering.d.ts +14 -0
- package/dist/observed-steering.d.ts.map +1 -1
- package/dist/observed-steering.js +18 -0
- package/dist/observed-steering.js.map +1 -1
- package/dist/observed-turn-watchdogs.d.ts +84 -0
- package/dist/observed-turn-watchdogs.d.ts.map +1 -0
- package/dist/observed-turn-watchdogs.js +134 -0
- package/dist/observed-turn-watchdogs.js.map +1 -0
- package/dist/openclaw-session.d.ts +10 -0
- package/dist/openclaw-session.d.ts.map +1 -1
- package/dist/openclaw-session.js +12 -0
- package/dist/openclaw-session.js.map +1 -1
- package/dist/passive-observer.d.ts +19 -0
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +208 -24
- package/dist/passive-observer.js.map +1 -1
- package/dist/pixoo/micro-glyphs.d.ts +1 -1
- package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
- package/dist/pixoo/micro-glyphs.js +2 -0
- package/dist/pixoo/micro-glyphs.js.map +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.d.ts +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.d.ts.map +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.js +47 -0
- package/dist/pixoo/official-dot-glyphs.generated.js.map +1 -1
- package/dist/pixoo/pixoo-discover.d.ts +8 -1
- package/dist/pixoo/pixoo-discover.d.ts.map +1 -1
- package/dist/pixoo/pixoo-discover.js +15 -8
- package/dist/pixoo/pixoo-discover.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +4 -2
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +105 -36
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pixoo/pixoo-settings.d.ts +19 -2
- package/dist/pixoo/pixoo-settings.d.ts.map +1 -1
- package/dist/pixoo/pixoo-settings.js +22 -4
- package/dist/pixoo/pixoo-settings.js.map +1 -1
- package/dist/pixoo/pixoo-sprites.d.ts.map +1 -1
- package/dist/pixoo/pixoo-sprites.js +2 -0
- package/dist/pixoo/pixoo-sprites.js.map +1 -1
- package/dist/session-aggregator.d.ts +9 -1
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js +1 -1
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-registry.d.ts +71 -3
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +126 -19
- package/dist/session-registry.js.map +1 -1
- package/dist/subagent-timeline.d.ts +38 -7
- package/dist/subagent-timeline.d.ts.map +1 -1
- package/dist/subagent-timeline.js +154 -23
- package/dist/subagent-timeline.js.map +1 -1
- package/dist/timeline-store.d.ts.map +1 -1
- package/dist/timeline-store.js +26 -1
- package/dist/timeline-store.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +13 -1
- package/dist/tui/renderer.js.map +1 -1
- package/dist/tui/terrarium.d.ts.map +1 -1
- package/dist/tui/terrarium.js +10 -5
- package/dist/tui/terrarium.js.map +1 -1
- package/dist/utils/redact-secrets.d.ts +3 -0
- package/dist/utils/redact-secrets.d.ts.map +1 -0
- package/dist/utils/redact-secrets.js +29 -0
- package/dist/utils/redact-secrets.js.map +1 -0
- package/dist/windows-service.d.ts +2 -1
- package/dist/windows-service.d.ts.map +1 -1
- package/dist/windows-service.js +6 -3
- package/dist/windows-service.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `settings.json` access for the daemon — one reader, one writer.
|
|
3
|
+
*
|
|
4
|
+
* The reader was private to `daemon-server.ts`, which meant anything else that
|
|
5
|
+
* needed a setting either re-implemented the cross-directory discovery or, as
|
|
6
|
+
* the device-settings modules do, hardcoded `homedir()/.agentdeck` and lost the
|
|
7
|
+
* `AGENTDECK_DATA_DIR` override (roadmap item 14). Extracted here so a second
|
|
8
|
+
* consumer inherits the discovery rules rather than a copy of them.
|
|
9
|
+
*
|
|
10
|
+
* Read and write are deliberately asymmetric:
|
|
11
|
+
*
|
|
12
|
+
* - **Read** looks at every candidate data dir and takes the NEWEST file. A CLI
|
|
13
|
+
* world and an app world can both be installed, and the most recently written
|
|
14
|
+
* file is the one the user last edited.
|
|
15
|
+
* - **Write** touches only `getDataDir()` — this process's own directory. A
|
|
16
|
+
* cross-directory write would reach into another install's state (and, on
|
|
17
|
+
* macOS, into a sandbox container this process cannot legally touch).
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Newest `settings.json` across candidate data dirs — mirrors the
|
|
21
|
+
* daemon.json/sessions.json cross-dir discovery (and honors the
|
|
22
|
+
* AGENTDECK_DATA_DIR test override, which the hardcoded `~/.agentdeck` path in
|
|
23
|
+
* the device-settings modules ignores). The App Store sandbox container is
|
|
24
|
+
* intentionally NOT a candidate (TCC hang risk — see `getCandidateDataDirs`),
|
|
25
|
+
* so settings written by the sandboxed Swift app stay invisible here; that
|
|
26
|
+
* coexistence limit is documented in docs/appstore-feature-matrix.md.
|
|
27
|
+
*
|
|
28
|
+
* Never throws: an absent, unreadable or malformed file yields `{}`, so a
|
|
29
|
+
* setting's fallback is always the one its own reader defines.
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadDaemonSettings(): Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Read-modify-write one key of this process's own `settings.json`.
|
|
34
|
+
*
|
|
35
|
+
* `undefined` deletes the key rather than storing a null — a persisted setting
|
|
36
|
+
* that has been cleared must be indistinguishable from one that was never set,
|
|
37
|
+
* or every reader needs a third case.
|
|
38
|
+
*
|
|
39
|
+
* The write is tmp+rename like every other file this daemon owns: settings.json
|
|
40
|
+
* is read by the daemon, the CLI and the device modules, and a half-written
|
|
41
|
+
* file reads as `{}` — which is to say, as every default at once.
|
|
42
|
+
*/
|
|
43
|
+
export declare function updateDaemonSetting(key: string, value: unknown): void;
|
|
44
|
+
/** Absolute path of the `settings.json` this process writes to. */
|
|
45
|
+
export declare function ownSettingsPath(): string;
|
|
46
|
+
//# sourceMappingURL=daemon-settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-settings.d.ts","sourceRoot":"","sources":["../src/daemon-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAa5D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAgBrE;AAED,mEAAmE;AACnE,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `settings.json` access for the daemon — one reader, one writer.
|
|
3
|
+
*
|
|
4
|
+
* The reader was private to `daemon-server.ts`, which meant anything else that
|
|
5
|
+
* needed a setting either re-implemented the cross-directory discovery or, as
|
|
6
|
+
* the device-settings modules do, hardcoded `homedir()/.agentdeck` and lost the
|
|
7
|
+
* `AGENTDECK_DATA_DIR` override (roadmap item 14). Extracted here so a second
|
|
8
|
+
* consumer inherits the discovery rules rather than a copy of them.
|
|
9
|
+
*
|
|
10
|
+
* Read and write are deliberately asymmetric:
|
|
11
|
+
*
|
|
12
|
+
* - **Read** looks at every candidate data dir and takes the NEWEST file. A CLI
|
|
13
|
+
* world and an app world can both be installed, and the most recently written
|
|
14
|
+
* file is the one the user last edited.
|
|
15
|
+
* - **Write** touches only `getDataDir()` — this process's own directory. A
|
|
16
|
+
* cross-directory write would reach into another install's state (and, on
|
|
17
|
+
* macOS, into a sandbox container this process cannot legally touch).
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync, statSync } from 'fs';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import { randomUUID } from 'crypto';
|
|
22
|
+
import { getCandidateDataDirs, getDataDir } from './session-registry.js';
|
|
23
|
+
/**
|
|
24
|
+
* Newest `settings.json` across candidate data dirs — mirrors the
|
|
25
|
+
* daemon.json/sessions.json cross-dir discovery (and honors the
|
|
26
|
+
* AGENTDECK_DATA_DIR test override, which the hardcoded `~/.agentdeck` path in
|
|
27
|
+
* the device-settings modules ignores). The App Store sandbox container is
|
|
28
|
+
* intentionally NOT a candidate (TCC hang risk — see `getCandidateDataDirs`),
|
|
29
|
+
* so settings written by the sandboxed Swift app stay invisible here; that
|
|
30
|
+
* coexistence limit is documented in docs/appstore-feature-matrix.md.
|
|
31
|
+
*
|
|
32
|
+
* Never throws: an absent, unreadable or malformed file yields `{}`, so a
|
|
33
|
+
* setting's fallback is always the one its own reader defines.
|
|
34
|
+
*/
|
|
35
|
+
export function loadDaemonSettings() {
|
|
36
|
+
let best = null;
|
|
37
|
+
for (const dir of getCandidateDataDirs()) {
|
|
38
|
+
try {
|
|
39
|
+
const path = join(dir, 'settings.json');
|
|
40
|
+
const mtime = statSync(path).mtimeMs;
|
|
41
|
+
if (best && best.mtime >= mtime)
|
|
42
|
+
continue;
|
|
43
|
+
best = { mtime, parsed: JSON.parse(readFileSync(path, 'utf-8')) };
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Missing or unreadable — skip this candidate.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return best?.parsed ?? {};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read-modify-write one key of this process's own `settings.json`.
|
|
53
|
+
*
|
|
54
|
+
* `undefined` deletes the key rather than storing a null — a persisted setting
|
|
55
|
+
* that has been cleared must be indistinguishable from one that was never set,
|
|
56
|
+
* or every reader needs a third case.
|
|
57
|
+
*
|
|
58
|
+
* The write is tmp+rename like every other file this daemon owns: settings.json
|
|
59
|
+
* is read by the daemon, the CLI and the device modules, and a half-written
|
|
60
|
+
* file reads as `{}` — which is to say, as every default at once.
|
|
61
|
+
*/
|
|
62
|
+
export function updateDaemonSetting(key, value) {
|
|
63
|
+
const dir = getDataDir();
|
|
64
|
+
mkdirSync(dir, { recursive: true });
|
|
65
|
+
const path = join(dir, 'settings.json');
|
|
66
|
+
let settings = {};
|
|
67
|
+
try {
|
|
68
|
+
settings = JSON.parse(readFileSync(path, 'utf-8'));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Missing or malformed — start from empty rather than refusing to write.
|
|
72
|
+
}
|
|
73
|
+
if (value === undefined)
|
|
74
|
+
delete settings[key];
|
|
75
|
+
else
|
|
76
|
+
settings[key] = value;
|
|
77
|
+
const tmp = join(dir, `.settings.${randomUUID()}.tmp`);
|
|
78
|
+
writeFileSync(tmp, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
79
|
+
renameSync(tmp, path);
|
|
80
|
+
}
|
|
81
|
+
/** Absolute path of the `settings.json` this process writes to. */
|
|
82
|
+
export function ownSettingsPath() {
|
|
83
|
+
return join(getDataDir(), 'settings.json');
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=daemon-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-settings.js","sourceRoot":"","sources":["../src/daemon-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAClF,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEzE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,IAAI,GAA8D,IAAI,CAAC;IAC3E,KAAK,MAAM,GAAG,IAAI,oBAAoB,EAAE,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;YACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;YACrC,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,KAAK;gBAAE,SAAS;YAC1C,IAAI,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAA4B,EAAE,CAAC;QAC/F,CAAC;QAAC,MAAM,CAAC;YACP,+CAA+C;QACjD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,EAAE,MAAM,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,KAAc;IAC7D,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IACxC,IAAI,QAAQ,GAA4B,EAAE,CAAC;IAC3C,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAA4B,CAAC;IAChF,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;IACD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;;QACzC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAE3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,UAAU,EAAE,MAAM,CAAC,CAAC;IACvD,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IACtE,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,UAAU,EAAE,EAAE,eAAe,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `agentdeck daemon start` does about a daemon already answering on the
|
|
3
|
+
* port it wants.
|
|
4
|
+
*
|
|
5
|
+
* This used to live inline in the CLI action, which made it untestable and hid
|
|
6
|
+
* how much trust it extends: it ADOPTS the incumbent's pairing token and can
|
|
7
|
+
* ask it to stand down or shut down — all on the strength of `/health`
|
|
8
|
+
* answering on 127.0.0.1. That was right when the only two daemons on a
|
|
9
|
+
* machine were the Node CLI and the macOS app belonging to the same human. On
|
|
10
|
+
* a shared host it is a credential leak in one direction and a denial of
|
|
11
|
+
* service in the other: user B's `daemon start` copies user A's token into B's
|
|
12
|
+
* home, then demotes A's daemon (see docs/ENTERPRISE-ROADMAP.md §1.2, §1.3).
|
|
13
|
+
*
|
|
14
|
+
* So the decision now asks who owns the incumbent process before doing either.
|
|
15
|
+
* Everything with an effect is a dependency, which is what lets a test drive
|
|
16
|
+
* this against a foreign-owned incumbent and prove the adopt and the
|
|
17
|
+
* stand-down never happened — a truth table over the ownership predicate would
|
|
18
|
+
* stay green while this function forgot to call it.
|
|
19
|
+
*/
|
|
20
|
+
import { type LocalPeerOwnership } from './auth.js';
|
|
21
|
+
/** The subset of `/health` this decision reads. */
|
|
22
|
+
export interface IncumbentHealth {
|
|
23
|
+
mode?: string;
|
|
24
|
+
pid?: number;
|
|
25
|
+
isSwift?: boolean;
|
|
26
|
+
pairingToken?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Everything with an effect is required, not optional-with-a-default. A
|
|
30
|
+
* defaulted `standDown` that resolves false reads as "the app refused" and a
|
|
31
|
+
* defaulted `waitForBindable` as "the port never came back" — so a caller that
|
|
32
|
+
* forgot to wire one would take a fallback port and log a hardware excuse for
|
|
33
|
+
* it, silently and plausibly. Making them required turns that into a type
|
|
34
|
+
* error. Only the two dependencies whose real implementation is always correct
|
|
35
|
+
* (ownership, logging) carry defaults.
|
|
36
|
+
*/
|
|
37
|
+
export interface TakeoverDeps {
|
|
38
|
+
ownership?: (pid: number | null | undefined) => LocalPeerOwnership;
|
|
39
|
+
log?: (msg: string) => void;
|
|
40
|
+
/** Returns true when the served token actually changed. */
|
|
41
|
+
adoptToken: (token: unknown) => boolean;
|
|
42
|
+
standDown: (port: number) => Promise<boolean>;
|
|
43
|
+
shutdown: (port: number) => Promise<void>;
|
|
44
|
+
waitForExit: (port: number, timeoutMs: number) => Promise<boolean>;
|
|
45
|
+
waitForBindable: (port: number, timeoutMs: number) => Promise<boolean>;
|
|
46
|
+
}
|
|
47
|
+
export type TakeoverOutcome =
|
|
48
|
+
/** Nothing in the way (or it yielded) — go bind. */
|
|
49
|
+
'proceed'
|
|
50
|
+
/** Our own daemon is already serving this port; exit 0. */
|
|
51
|
+
| 'already-running'
|
|
52
|
+
/** The user asked for a port we must not take. Exit 1 with the reason. */
|
|
53
|
+
| 'refuse';
|
|
54
|
+
/**
|
|
55
|
+
* True when a daemon answering on this machine belongs to a DIFFERENT OS user,
|
|
56
|
+
* and must therefore be left alone entirely: not evicted, not stood down, not
|
|
57
|
+
* conceded to, not adopted from.
|
|
58
|
+
*
|
|
59
|
+
* The daemon's own startup sweeps the whole port window and shuts down every
|
|
60
|
+
* Swift daemon it finds there, which on a shared host means shutting down a
|
|
61
|
+
* coworker's macOS app the moment this user starts a CLI daemon. Only
|
|
62
|
+
* `other-user` — a definite refusal from the OS — blocks; `unknown` stays
|
|
63
|
+
* permissive for the same reason it does everywhere else (see
|
|
64
|
+
* `localPeerOwnership`), so a peer that reports no pid keeps working exactly
|
|
65
|
+
* as it did before.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isForeignDaemon(health: {
|
|
68
|
+
pid?: number;
|
|
69
|
+
} | null | undefined, ownership?: (pid: number | null | undefined) => LocalPeerOwnership): boolean;
|
|
70
|
+
export declare function negotiateIncumbentDaemon(args: {
|
|
71
|
+
port: number;
|
|
72
|
+
incumbent: IncumbentHealth | null;
|
|
73
|
+
portWasExplicit: boolean;
|
|
74
|
+
}, deps: TakeoverDeps): Promise<TakeoverOutcome>;
|
|
75
|
+
//# sourceMappingURL=daemon-takeover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-takeover.d.ts","sourceRoot":"","sources":["../src/daemon-takeover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGxE,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KAAK,kBAAkB,CAAC;IACnE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,2DAA2D;IAC3D,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IACxC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnE,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE;AAED,MAAM,MAAM,eAAe;AACzB,oDAAoD;AAClD,SAAS;AACX,2DAA2D;GACzD,iBAAiB;AACnB,0EAA0E;GACxE,QAAQ,CAAC;AAEb;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EAC3C,SAAS,GAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KAAK,kBAAuC,GACrF,OAAO,CAET;AASD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,EACnF,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,eAAe,CAAC,CAmG1B"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `agentdeck daemon start` does about a daemon already answering on the
|
|
3
|
+
* port it wants.
|
|
4
|
+
*
|
|
5
|
+
* This used to live inline in the CLI action, which made it untestable and hid
|
|
6
|
+
* how much trust it extends: it ADOPTS the incumbent's pairing token and can
|
|
7
|
+
* ask it to stand down or shut down — all on the strength of `/health`
|
|
8
|
+
* answering on 127.0.0.1. That was right when the only two daemons on a
|
|
9
|
+
* machine were the Node CLI and the macOS app belonging to the same human. On
|
|
10
|
+
* a shared host it is a credential leak in one direction and a denial of
|
|
11
|
+
* service in the other: user B's `daemon start` copies user A's token into B's
|
|
12
|
+
* home, then demotes A's daemon (see docs/ENTERPRISE-ROADMAP.md §1.2, §1.3).
|
|
13
|
+
*
|
|
14
|
+
* So the decision now asks who owns the incumbent process before doing either.
|
|
15
|
+
* Everything with an effect is a dependency, which is what lets a test drive
|
|
16
|
+
* this against a foreign-owned incumbent and prove the adopt and the
|
|
17
|
+
* stand-down never happened — a truth table over the ownership predicate would
|
|
18
|
+
* stay green while this function forgot to call it.
|
|
19
|
+
*/
|
|
20
|
+
import { localPeerOwnership } from './auth.js';
|
|
21
|
+
import { log as defaultLog } from './logger.js';
|
|
22
|
+
/**
|
|
23
|
+
* True when a daemon answering on this machine belongs to a DIFFERENT OS user,
|
|
24
|
+
* and must therefore be left alone entirely: not evicted, not stood down, not
|
|
25
|
+
* conceded to, not adopted from.
|
|
26
|
+
*
|
|
27
|
+
* The daemon's own startup sweeps the whole port window and shuts down every
|
|
28
|
+
* Swift daemon it finds there, which on a shared host means shutting down a
|
|
29
|
+
* coworker's macOS app the moment this user starts a CLI daemon. Only
|
|
30
|
+
* `other-user` — a definite refusal from the OS — blocks; `unknown` stays
|
|
31
|
+
* permissive for the same reason it does everywhere else (see
|
|
32
|
+
* `localPeerOwnership`), so a peer that reports no pid keeps working exactly
|
|
33
|
+
* as it did before.
|
|
34
|
+
*/
|
|
35
|
+
export function isForeignDaemon(health, ownership = localPeerOwnership) {
|
|
36
|
+
return ownership(health?.pid) === 'other-user';
|
|
37
|
+
}
|
|
38
|
+
/** 12s: `waitForDaemonExit` only proves the daemon stopped ANSWERING. */
|
|
39
|
+
const EXIT_WAIT_MS = 12_000;
|
|
40
|
+
/** 30s, and not padding: macOS keeps a NECP reservation on a cancelled
|
|
41
|
+
* listener's port for ~14s, during which `lsof` shows no sockets at all and
|
|
42
|
+
* bind() still returns EADDRINUSE (measured 2026-08-06 — bindable at ~17s). */
|
|
43
|
+
const BINDABLE_WAIT_MS = 30_000;
|
|
44
|
+
export async function negotiateIncumbentDaemon(args, deps) {
|
|
45
|
+
const { port, incumbent, portWasExplicit } = args;
|
|
46
|
+
if (incumbent?.mode !== 'daemon')
|
|
47
|
+
return 'proceed';
|
|
48
|
+
const log = deps.log ?? defaultLog;
|
|
49
|
+
const ownership = (deps.ownership ?? localPeerOwnership)(incumbent.pid);
|
|
50
|
+
if (ownership === 'other-user') {
|
|
51
|
+
// Neither half of the handover is available across users. Adopting would
|
|
52
|
+
// copy their credential into this user's home; standing them down would
|
|
53
|
+
// let any local account demote any other's daemon. Both are refused
|
|
54
|
+
// together — a daemon we may not talk to is also one we may not take the
|
|
55
|
+
// port from.
|
|
56
|
+
log(`Port ${port} is held by a daemon belonging to another user on this machine.`);
|
|
57
|
+
log(`Not adopting its pairing token and not asking it to stand down — a daemon is per-user.`);
|
|
58
|
+
if (portWasExplicit) {
|
|
59
|
+
log(`Choose another port with -p, or ask that user to stop their daemon.`);
|
|
60
|
+
return 'refuse';
|
|
61
|
+
}
|
|
62
|
+
log(`Starting on a free port instead; clients of yours resolve it from your own daemon.json.`);
|
|
63
|
+
return 'proceed';
|
|
64
|
+
}
|
|
65
|
+
if (ownership === 'unknown') {
|
|
66
|
+
// Permissive on purpose (see `localPeerOwnership`), but never silently:
|
|
67
|
+
// this is the branch where a token crosses a process boundary without
|
|
68
|
+
// proof of who is on the other side.
|
|
69
|
+
log(`Could not confirm which user owns the daemon on port ${port} (it reports no pid) — `
|
|
70
|
+
+ `proceeding as if it were yours.`);
|
|
71
|
+
}
|
|
72
|
+
// Take over the fleet's credential along with the port. The app's daemon
|
|
73
|
+
// keeps its token inside its sandbox container, unreadable from here, so
|
|
74
|
+
// this loopback answer is the only place we can learn what every paired
|
|
75
|
+
// board is currently holding. Adopting it BEFORE the stand-down means the
|
|
76
|
+
// handover costs no device its pairing.
|
|
77
|
+
if (deps.adoptToken(incumbent.pairingToken)) {
|
|
78
|
+
log(`Adopted the incumbent daemon's pairing token so paired devices survive the handover.`);
|
|
79
|
+
}
|
|
80
|
+
if (!incumbent.isSwift) {
|
|
81
|
+
log(`Daemon already running on port ${port}. Use 'agentdeck daemon stop' first.`);
|
|
82
|
+
return 'already-running';
|
|
83
|
+
}
|
|
84
|
+
// Reverse two-tier upgrade path: the macOS app may already own the canonical
|
|
85
|
+
// port with its in-process Swift daemon (Tier 1 — limited: no ADB devices,
|
|
86
|
+
// no subscription usage, …). A plain "already running → exit" would strand
|
|
87
|
+
// the user on Tier 1. Ask it to STAND DOWN (demote to client, app keeps
|
|
88
|
+
// running) so this CLI daemon can take over with the full feature set.
|
|
89
|
+
log(`AgentDeck app's in-process daemon holds port ${port} — requesting stand-down to take over with the full CLI feature set…`);
|
|
90
|
+
// Prefer /stand-down (clean demote: the app stays running as a client).
|
|
91
|
+
// Fall back to /shutdown for older app builds that predate the endpoint.
|
|
92
|
+
let acked = await deps.standDown(port);
|
|
93
|
+
if (!acked) {
|
|
94
|
+
await deps.shutdown(port);
|
|
95
|
+
acked = true; // shutdown is best-effort (no ack body); rely on the exit wait
|
|
96
|
+
}
|
|
97
|
+
// Two conditions, not one. `waitForDaemonExit` proves the app stopped
|
|
98
|
+
// answering; `waitForBindable` proves the socket is actually gone. The app
|
|
99
|
+
// releases the port with NWListener.cancel(), which returns before the
|
|
100
|
+
// teardown completes — binding on the first signal alone is what silently
|
|
101
|
+
// demoted this daemon to the 9121 fallback and left the canonical port
|
|
102
|
+
// ownerless. The app's takeover-yield window is longer (45s), so it stays
|
|
103
|
+
// off the port until well after this wait has claimed it.
|
|
104
|
+
const yielded = acked
|
|
105
|
+
&& await deps.waitForExit(port, EXIT_WAIT_MS)
|
|
106
|
+
&& await deps.waitForBindable(port, BINDABLE_WAIT_MS);
|
|
107
|
+
if (yielded) {
|
|
108
|
+
log(`App daemon yielded port ${port}. Starting CLI daemon…`);
|
|
109
|
+
return 'proceed';
|
|
110
|
+
}
|
|
111
|
+
// The app yielded but the PORT did not come back, and that is often not the
|
|
112
|
+
// app's doing: a WiFi device that has gone to sleep with bytes still queued
|
|
113
|
+
// leaves its socket in LAST_ACK on this port, and TCP holds that until it
|
|
114
|
+
// exhausts retransmits — minutes, sometimes. Network.framework exposes no
|
|
115
|
+
// SO_LINGER, so the app cannot RST out of it (measured 2026-08-06: two
|
|
116
|
+
// sleeping ESP32 boards, 11901 and 4 bytes queued, were the only things left
|
|
117
|
+
// on 9120).
|
|
118
|
+
//
|
|
119
|
+
// Carry on rather than exiting. The daemon binds a fallback port and writes
|
|
120
|
+
// it to daemon.json, which is what every client resolves through and what the
|
|
121
|
+
// app itself re-reads when its yield window ends — so the deck keeps working.
|
|
122
|
+
// What must NOT happen is this being silent, which is how the canonical port
|
|
123
|
+
// ended up owned by nobody with the CLI reporting success.
|
|
124
|
+
log(`The AgentDeck app yielded port ${port} but the port has not been released `
|
|
125
|
+
+ `(usually a sleeping WiFi device holding a half-closed socket on it).`);
|
|
126
|
+
if (portWasExplicit) {
|
|
127
|
+
// The port was ASKED for, not defaulted to. Quietly binding a different one
|
|
128
|
+
// would answer a question the user did not ask; an explicit choice fails
|
|
129
|
+
// loudly instead.
|
|
130
|
+
log(`Quit the AgentDeck app and retry, or choose another port.`);
|
|
131
|
+
return 'refuse';
|
|
132
|
+
}
|
|
133
|
+
log(`Starting on a fallback port instead — clients resolve it from daemon.json. `
|
|
134
|
+
+ `For the canonical port, quit the AgentDeck app and retry, or pass -p.`);
|
|
135
|
+
return 'proceed';
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=daemon-takeover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-takeover.js","sourceRoot":"","sources":["../src/daemon-takeover.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon.js
CHANGED
|
@@ -17,7 +17,7 @@ const program = new Command();
|
|
|
17
17
|
program
|
|
18
18
|
.name('agentdeck')
|
|
19
19
|
.description('AgentDeck daemon (legacy entry point — use `agentdeck daemon` instead)')
|
|
20
|
-
.version('1.0.
|
|
20
|
+
.version('1.0.21');
|
|
21
21
|
program
|
|
22
22
|
.command('start', { isDefault: true })
|
|
23
23
|
.description('Start monitoring daemon')
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Exports `startSession()` called by cli.ts.
|
|
5
5
|
* Uses BridgeCore for shared infrastructure.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type ModuleConfigs } from './modules/types.js';
|
|
8
8
|
import { type AgentType } from './types.js';
|
|
9
9
|
export interface SessionOptions {
|
|
10
10
|
agentType: AgentType;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA6DH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA6DH,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAUvE,OAAO,EAOL,KAAK,SAAS,EAQf,MAAM,YAAY,CAAC;AAwDpB,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,wEAAwE;IACxE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;kFAC8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;0EACsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;gEAC4D;IAC5D,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA0rCtE"}
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ import { DaemonWsClient } from './daemon-ws-client.js';
|
|
|
43
43
|
import { getLanIp } from '@agentdeck/shared';
|
|
44
44
|
import { migrateHooksIfNeeded } from '@agentdeck/hooks';
|
|
45
45
|
import { initModules, stopModules, createDefaultModules, } from './modules/index.js';
|
|
46
|
+
import { allModulesOff } from './modules/types.js';
|
|
46
47
|
import { onESP32Message, sendWifiProvisionToAll, } from './esp32-serial.js';
|
|
47
48
|
import { loadWifiConfig } from './wifi-config.js';
|
|
48
49
|
import { getAdbDeviceCount } from './adb-reverse.js';
|
|
@@ -348,12 +349,12 @@ export async function startSession(opts) {
|
|
|
348
349
|
});
|
|
349
350
|
// ===== Device modules =====
|
|
350
351
|
const moduleConfigs = {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}),
|
|
352
|
+
// "All off, then add back ADB" rather than a literal list of the modules
|
|
353
|
+
// that were known when this was written: initModules() reads an absent key
|
|
354
|
+
// as 'auto', so every module added since arrived here switched ON. That is
|
|
355
|
+
// how `idotmatrix` ended up spawning a Python BLE client per session
|
|
356
|
+
// bridge, duplicating the daemon's own.
|
|
357
|
+
...(opts.modules ?? { ...allModulesOff(), adb: 'auto' }),
|
|
357
358
|
// Session bridges are internal hook/PTY processes. Only the daemon hub is
|
|
358
359
|
// discoverable on the LAN; never advertise per-session project metadata.
|
|
359
360
|
mdns: false,
|
|
@@ -468,11 +469,18 @@ export async function startSession(opts) {
|
|
|
468
469
|
// this same event pipe — state machine, timeline, and APME all close via
|
|
469
470
|
// their existing Stop paths, and a late real Stop dedups downstream.
|
|
470
471
|
const turnWatchdog = new ClaudeTurnWatchdog({
|
|
471
|
-
onMissedStop: ({ transcript_path }) => {
|
|
472
|
+
onMissedStop: ({ transcript_path, reason }) => {
|
|
472
473
|
adapter.emit('event', {
|
|
473
474
|
source: 'hook',
|
|
474
475
|
event: 'Stop',
|
|
475
|
-
data: {
|
|
476
|
+
data: {
|
|
477
|
+
transcript_path,
|
|
478
|
+
synthetic_stop: true,
|
|
479
|
+
// A user cancel and a client abort are not lost hooks — same
|
|
480
|
+
// close, different buckets.
|
|
481
|
+
...(reason === 'interrupted' ? { interrupted: true } : {}),
|
|
482
|
+
...(reason === 'aborted' ? { aborted: true } : {}),
|
|
483
|
+
},
|
|
476
484
|
});
|
|
477
485
|
},
|
|
478
486
|
});
|
|
@@ -1521,10 +1529,21 @@ function wireClaudeCodeTimeline(adapter, core) {
|
|
|
1521
1529
|
debug('agentdeck', `transcript read skipped: ${String(err)}`);
|
|
1522
1530
|
}
|
|
1523
1531
|
}
|
|
1524
|
-
// APME: store Claude's response on the current turn
|
|
1532
|
+
// APME: store Claude's response on the current turn, then close the
|
|
1533
|
+
// turn at the Stop that produced it (response first — the trajectory
|
|
1534
|
+
// event and response_kind tag read the still-active turn). The close
|
|
1535
|
+
// records WHICH Stop closed it: a watchdog-injected synthetic Stop is
|
|
1536
|
+
// one that the real hook dropped, and counting those is the standing
|
|
1537
|
+
// measurement of hook loss (turns.end_source).
|
|
1525
1538
|
const apmeRef = core.getApme();
|
|
1526
|
-
if (apmeRef
|
|
1527
|
-
|
|
1539
|
+
if (apmeRef) {
|
|
1540
|
+
if (lastAssistantMsg)
|
|
1541
|
+
apmeRef.collector.setTurnResponse(core.sessionId, lastAssistantMsg);
|
|
1542
|
+
apmeRef.collector.noteTurnStop(core.sessionId, {
|
|
1543
|
+
synthetic: evt.data?.synthetic_stop === true,
|
|
1544
|
+
interrupted: evt.data?.interrupted === true,
|
|
1545
|
+
aborted: evt.data?.aborted === true,
|
|
1546
|
+
});
|
|
1528
1547
|
}
|
|
1529
1548
|
emitCompletion(lastAssistantMsg, duration, toolSummary);
|
|
1530
1549
|
break;
|