@adhdev/daemon-core 0.8.36 → 0.8.38
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/daemon/dev-server.d.ts +6 -3
- package/dist/index.js +146 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +146 -30
- package/dist/index.mjs.map +1 -1
- package/dist/shared-types.d.ts +4 -0
- package/dist/status/builders.d.ts +4 -7
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/daemon/dev-auto-implement.ts +36 -8
- package/src/daemon/dev-cdp-handlers.ts +22 -3
- package/src/daemon/dev-server.ts +20 -14
- package/src/installer.ts +13 -0
- package/src/shared-types.ts +4 -0
- package/src/status/builders.ts +17 -16
- package/src/status/snapshot.ts +73 -2
package/dist/shared-types.d.ts
CHANGED
|
@@ -244,6 +244,10 @@ export interface SessionEntry {
|
|
|
244
244
|
providerControls?: ProviderControlSchema[];
|
|
245
245
|
errorMessage?: string;
|
|
246
246
|
errorReason?: _ProviderErrorReason;
|
|
247
|
+
lastMessagePreview?: string;
|
|
248
|
+
lastMessageRole?: string;
|
|
249
|
+
lastMessageAt?: number;
|
|
250
|
+
lastMessageHash?: string;
|
|
247
251
|
lastUpdated?: number;
|
|
248
252
|
unread?: boolean;
|
|
249
253
|
lastSeenAt?: number;
|
|
@@ -15,15 +15,12 @@ export interface SessionEntryBuildOptions {
|
|
|
15
15
|
profile?: SessionEntryProfile;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Find a CDP manager by key
|
|
18
|
+
* Find a CDP manager by key. Supports single-window (`cursor`) and full multi-window keys (`cursor_<targetId>`).
|
|
19
19
|
*
|
|
20
20
|
* Lookup order:
|
|
21
|
-
* 1. Exact match
|
|
22
|
-
* 2.
|
|
23
|
-
* 3. null
|
|
24
|
-
*
|
|
25
|
-
* This replaces raw `cdpManagers.get(ideType)` calls that broke when
|
|
26
|
-
* multi-window keys like "cursor_remote_vs" were used.
|
|
21
|
+
* 1. Exact match when connected
|
|
22
|
+
* 2. If key has no multi-window suffix: at most **one** connected manager whose key starts with `key_`
|
|
23
|
+
* 3. If two or more windows share that prefix → **null** (ambiguous — pass full managerKey from `GET /api/cdp/targets`)
|
|
27
24
|
*/
|
|
28
25
|
export declare function findCdpManager(cdpManagers: Map<string, DaemonCdpManager>, key: string): DaemonCdpManager | null;
|
|
29
26
|
/**
|
package/package.json
CHANGED
|
@@ -71,6 +71,16 @@ export function getDefaultAutoImplReference(ctx: DevServerContext, category: str
|
|
|
71
71
|
if (category === 'cli') {
|
|
72
72
|
return type === 'codex-cli' ? 'claude-cli' : 'codex-cli';
|
|
73
73
|
}
|
|
74
|
+
if (category === 'extension') {
|
|
75
|
+
const preferred = ['claude-code-vscode', 'codex', 'cline', 'roo-code'];
|
|
76
|
+
for (const ref of preferred) {
|
|
77
|
+
if (ref === type) continue;
|
|
78
|
+
if (ctx.providerLoader.resolve(ref) || ctx.providerLoader.getMeta(ref)) return ref;
|
|
79
|
+
}
|
|
80
|
+
const all = ctx.providerLoader.getAll();
|
|
81
|
+
const fb = all.find((p: any) => p.category === 'extension' && p.type !== type);
|
|
82
|
+
if (fb?.type) return fb.type;
|
|
83
|
+
}
|
|
74
84
|
return 'antigravity';
|
|
75
85
|
}
|
|
76
86
|
|
|
@@ -725,6 +735,9 @@ export function buildAutoImplPrompt(ctx: DevServerContext,
|
|
|
725
735
|
|
|
726
736
|
const lines: string[] = [];
|
|
727
737
|
|
|
738
|
+
/** CDP connection key: extension scripts use host IDE (default Cursor), not the extension id. */
|
|
739
|
+
const cdpIdeType = provider.category === 'extension' ? 'cursor' : type;
|
|
740
|
+
|
|
728
741
|
// ── System instructions ──
|
|
729
742
|
lines.push('You are implementing browser automation scripts for an IDE provider.');
|
|
730
743
|
lines.push('Be concise. Do NOT explain your reasoning. Just edit files directly.');
|
|
@@ -734,6 +747,19 @@ export function buildAutoImplPrompt(ctx: DevServerContext,
|
|
|
734
747
|
lines.push(`# Target: ${provider.name || type} (${type})`);
|
|
735
748
|
lines.push(`Provider directory: \`${providerDir}\``);
|
|
736
749
|
lines.push('');
|
|
750
|
+
if (provider.category === 'extension') {
|
|
751
|
+
lines.push('## CDP host (extension providers)');
|
|
752
|
+
lines.push(
|
|
753
|
+
`Extension **${type}** runs inside a host IDE. For \`/api/scripts/run\` and \`/api/cdp/evaluate\`, keep \`"type": "${type}"\` (which provider scripts run) but set \`"ideType"\` to the DevServer CDP **managerKey** for that window.`,
|
|
754
|
+
);
|
|
755
|
+
lines.push(
|
|
756
|
+
`Examples use \`"ideType": "${cdpIdeType}"\` (Cursor). If **multiple** IDE windows are connected, run \`GET /api/cdp/targets\` and use the correct \`managerKey\` / \`pageTitle\` — short \`cursor\` or \`vscode\` only works when it uniquely identifies one window.`,
|
|
757
|
+
);
|
|
758
|
+
lines.push(
|
|
759
|
+
'For VS Code hosts, use `vscode` or full `vscode_<targetId>` managerKey in every curl below.',
|
|
760
|
+
);
|
|
761
|
+
lines.push('');
|
|
762
|
+
}
|
|
737
763
|
|
|
738
764
|
// ── funcToFile mapping (needed early for file classification) ──
|
|
739
765
|
const funcToFile: Record<string, string> = {
|
|
@@ -932,14 +958,14 @@ export function buildAutoImplPrompt(ctx: DevServerContext,
|
|
|
932
958
|
lines.push('```bash');
|
|
933
959
|
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cdp/evaluate \\`);
|
|
934
960
|
lines.push(` -H "Content-Type: application/json" \\`);
|
|
935
|
-
lines.push(` -d '{"expression": "document.body.innerHTML.substring(0, 1000)", "ideType": "${
|
|
961
|
+
lines.push(` -d '{"expression": "document.body.innerHTML.substring(0, 1000)", "ideType": "${cdpIdeType}"}'`);
|
|
936
962
|
lines.push('```');
|
|
937
963
|
lines.push('');
|
|
938
964
|
lines.push('### 2. Test your generated function');
|
|
939
965
|
lines.push('Once you save the file, test it by running:');
|
|
940
966
|
lines.push('```bash');
|
|
941
967
|
lines.push(`curl -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/providers/reload`);
|
|
942
|
-
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${
|
|
968
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${cdpIdeType}"}'`);
|
|
943
969
|
lines.push('```');
|
|
944
970
|
lines.push('');
|
|
945
971
|
lines.push('### Task Workflow');
|
|
@@ -949,10 +975,12 @@ export function buildAutoImplPrompt(ctx: DevServerContext,
|
|
|
949
975
|
lines.push('4. Reload providers and TEST your script via the API.');
|
|
950
976
|
lines.push('');
|
|
951
977
|
lines.push('### 🔥 Advanced UI Parsing (CRUCIAL for `readChat`)');
|
|
952
|
-
lines.push(
|
|
978
|
+
lines.push(
|
|
979
|
+
`Your \`readChat\` must flawlessly parse complex UI elements (tables, code blocks, tool calls, and AI thoughts). Match the depth of the **${referenceType || 'reference'}** scripts above (patterns and structure, not necessarily the same DOM).`,
|
|
980
|
+
);
|
|
953
981
|
lines.push('To achieve this, you MUST generate a live test scenario:');
|
|
954
982
|
lines.push(`1. Early in your process, send a rich prompt to the IDE using the API:`);
|
|
955
|
-
lines.push(` \`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "sendMessage", "type": "${type}", "ideType": "${
|
|
983
|
+
lines.push(` \`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "sendMessage", "type": "${type}", "ideType": "${cdpIdeType}", "args": {"message": "Write a python script, draw a markdown table, use a tool, and show your reasoning/thought process"}}'\``);
|
|
956
984
|
lines.push('2. Wait a few seconds for the IDE AI to generate these elements in the UI.');
|
|
957
985
|
lines.push('3. Use CDP evaluate to deeply inspect the DOM structure of the newly generated tables, code blocks, thought blocks, and tool calls.');
|
|
958
986
|
lines.push('4. Ensure `readChat` extracts `content` with precise markdown formatting (especially for tables/code) and assigns correct `kind` tags (`thought`, `tool`, `terminal`).');
|
|
@@ -965,27 +993,27 @@ export function buildAutoImplPrompt(ctx: DevServerContext,
|
|
|
965
993
|
lines.push('### Step 1: Baseline — confirm idle');
|
|
966
994
|
lines.push('```bash');
|
|
967
995
|
lines.push(`curl -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/providers/reload`);
|
|
968
|
-
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${
|
|
996
|
+
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${cdpIdeType}"}')`);
|
|
969
997
|
lines.push(`echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',d); r=json.loads(r) if isinstance(r,str) else r; assert r.get('status')=='idle', f'Expected idle, got {r.get(chr(34)+chr(115)+chr(116)+chr(97)+chr(116)+chr(117)+chr(115)+chr(34))}'; print('Step 1 PASS: status=idle')"`);
|
|
970
998
|
lines.push('```');
|
|
971
999
|
lines.push('');
|
|
972
1000
|
lines.push('### Step 2: Send a LONG message that triggers extended generation (10+ seconds)');
|
|
973
1001
|
lines.push('```bash');
|
|
974
|
-
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "sendMessage", "type": "${type}", "ideType": "${
|
|
1002
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "sendMessage", "type": "${type}", "ideType": "${cdpIdeType}", "args": {"message": "Write an extremely detailed 5000-word essay about the history of artificial intelligence from Alan Turing to 2025. Be very thorough and verbose."}}'`);
|
|
975
1003
|
lines.push('sleep 3');
|
|
976
1004
|
lines.push('```');
|
|
977
1005
|
lines.push('');
|
|
978
1006
|
lines.push('### Step 3: Check generating OR completed');
|
|
979
1007
|
lines.push('The AI may still be generating OR may have finished already. Either generating or idle is acceptable:');
|
|
980
1008
|
lines.push('```bash');
|
|
981
|
-
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${
|
|
1009
|
+
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${cdpIdeType}"}')`);
|
|
982
1010
|
lines.push(`echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',d); r=json.loads(r) if isinstance(r,str) else r; s=r.get('status'); assert s in ('generating','idle','waiting_approval'), f'Unexpected: {s}'; print(f'Step 3 PASS: status={s}')"`);
|
|
983
1011
|
lines.push('```');
|
|
984
1012
|
lines.push('');
|
|
985
1013
|
lines.push('### Step 4: Wait for completion and verify new message');
|
|
986
1014
|
lines.push('```bash');
|
|
987
1015
|
lines.push('sleep 10');
|
|
988
|
-
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${
|
|
1016
|
+
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${cdpIdeType}"}')`);
|
|
989
1017
|
lines.push(`echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',d); r=json.loads(r) if isinstance(r,str) else r; s=r.get('status'); msgs=r.get('messages',[]); assert s=='idle', f'Expected idle, got {s}'; assert len(msgs)>0, 'No messages'; print(f'Step 4 PASS: status={s}, messages={len(msgs)}')"`);
|
|
990
1018
|
lines.push('```');
|
|
991
1019
|
lines.push('');
|
|
@@ -295,9 +295,28 @@ export async function handleScriptHints(ctx: DevServerContext, type: string, _re
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
export async function handleCdpTargets(ctx: DevServerContext, _req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
298
|
-
const targets: {
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
const targets: Array<{
|
|
299
|
+
managerKey: string;
|
|
300
|
+
/** @deprecated same as managerKey — kept for older clients */
|
|
301
|
+
ide: string;
|
|
302
|
+
ideBase: string;
|
|
303
|
+
pageTitle: string;
|
|
304
|
+
targetId: string | null;
|
|
305
|
+
connected: boolean;
|
|
306
|
+
port: number;
|
|
307
|
+
}> = [];
|
|
308
|
+
for (const [managerKey, cdp] of ctx.cdpManagers.entries()) {
|
|
309
|
+
const underscore = managerKey.indexOf('_');
|
|
310
|
+
const ideBase = underscore === -1 ? managerKey : managerKey.slice(0, underscore);
|
|
311
|
+
targets.push({
|
|
312
|
+
managerKey,
|
|
313
|
+
ide: managerKey,
|
|
314
|
+
ideBase,
|
|
315
|
+
pageTitle: cdp.pageTitle,
|
|
316
|
+
targetId: cdp.targetId,
|
|
317
|
+
connected: cdp.isConnected,
|
|
318
|
+
port: cdp.getPort(),
|
|
319
|
+
});
|
|
301
320
|
}
|
|
302
321
|
ctx.json(res, 200, { targets });
|
|
303
322
|
}
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type { DaemonCliManager } from '../commands/cli-manager.js';
|
|
|
28
28
|
import { generateTemplate as genScaffoldTemplate, generateFiles as genScaffoldFiles } from './scaffold-template.js';
|
|
29
29
|
import { VersionArchive, detectAllVersions } from '../providers/version-archive.js';
|
|
30
30
|
import { LOG } from '../logging/logger.js';
|
|
31
|
+
import { findCdpManager } from '../status/builders.js';
|
|
31
32
|
import { handleCdpEvaluate, handleCdpClick, handleCdpDomQuery, handleScreenshot, handleScriptsRun, handleTypeAndSend, handleTypeAndSendAt, handleScriptHints, handleCdpTargets, handleDomInspect, handleDomChildren, handleDomAnalyze, handleFindCommon, handleFindByText, handleDomContext } from './dev-cdp-handlers.js';
|
|
32
33
|
import { handleCliStatus, handleCliLaunch, handleCliSend, handleCliStop, handleCliDebug, handleCliTrace, handleCliExercise, handleCliFixtureCapture, handleCliFixtureList, handleCliFixtureReplay, handleCliResolve, handleCliRaw, handleCliSSE } from './dev-cli-debug.js';
|
|
33
34
|
import { handleAutoImplement, handleAutoImplCancel, handleAutoImplSSE } from './dev-auto-implement.js';
|
|
@@ -1590,24 +1591,29 @@ export class DevServer implements DevServerContext {
|
|
|
1590
1591
|
}
|
|
1591
1592
|
}
|
|
1592
1593
|
|
|
1593
|
-
/**
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1594
|
+
/**
|
|
1595
|
+
* Resolve a CDP manager for DevServer APIs.
|
|
1596
|
+
* - Pass full **managerKey** from `GET /api/cdp/targets` when multiple Cursor/VS Code windows are open
|
|
1597
|
+
* (e.g. `cursor_0006DE34…`); short `cursor` only works when it maps to exactly one connected manager.
|
|
1598
|
+
* - With `ideType` omitted: only succeeds when exactly one connected manager exists.
|
|
1599
|
+
*/
|
|
1596
1600
|
public getCdp(ideType?: string): DaemonCdpManager | null {
|
|
1597
1601
|
if (ideType) {
|
|
1598
|
-
const cdp = this.cdpManagers
|
|
1599
|
-
if (cdp
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
LOG.warn('DevServer', `getCdp: no manager found for ideType '${ideType}', available: [${[...this.cdpManagers.keys()].join(', ')}]`);
|
|
1602
|
+
const cdp = findCdpManager(this.cdpManagers, ideType);
|
|
1603
|
+
if (cdp) return cdp;
|
|
1604
|
+
LOG.warn(
|
|
1605
|
+
'DevServer',
|
|
1606
|
+
`getCdp: no unique match for '${ideType}', available: [${[...this.cdpManagers.keys()].join(', ')}] — use managerKey from GET /api/cdp/targets`,
|
|
1607
|
+
);
|
|
1605
1608
|
return null;
|
|
1606
1609
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1610
|
+
const connected = [...this.cdpManagers.entries()].filter(([, m]) => m.isConnected);
|
|
1611
|
+
if (connected.length === 1) return connected[0][1];
|
|
1612
|
+
if (connected.length === 0) return null;
|
|
1613
|
+
LOG.warn(
|
|
1614
|
+
'DevServer',
|
|
1615
|
+
`getCdp: ideType omitted but ${connected.length} CDP windows — pass managerKey from GET /api/cdp/targets`,
|
|
1616
|
+
);
|
|
1611
1617
|
return null;
|
|
1612
1618
|
}
|
|
1613
1619
|
|
package/src/installer.ts
CHANGED
|
@@ -74,6 +74,19 @@ export const EXTENSION_CATALOG: ExtensionInfo[] = [
|
|
|
74
74
|
requiresApiKey: true,
|
|
75
75
|
apiKeyName: 'Anthropic/OpenAI API key',
|
|
76
76
|
},
|
|
77
|
+
{
|
|
78
|
+
id: 'claude-code-vscode',
|
|
79
|
+
name: 'Claude Code',
|
|
80
|
+
displayName: 'Claude Code (Anthropic)',
|
|
81
|
+
marketplaceId: 'anthropic.claude-code',
|
|
82
|
+
description: 'Anthropic Claude Code agent in VS Code–compatible editors',
|
|
83
|
+
category: 'ai-agent',
|
|
84
|
+
icon: '🟠',
|
|
85
|
+
recommended: true,
|
|
86
|
+
requiresApiKey: true,
|
|
87
|
+
apiKeyName: 'Anthropic account',
|
|
88
|
+
website: 'https://www.anthropic.com/claude-code',
|
|
89
|
+
},
|
|
77
90
|
{
|
|
78
91
|
id: 'continue',
|
|
79
92
|
name: 'Continue',
|
package/src/shared-types.ts
CHANGED
|
@@ -308,6 +308,10 @@ export interface SessionEntry {
|
|
|
308
308
|
providerControls?: ProviderControlSchema[];
|
|
309
309
|
errorMessage?: string;
|
|
310
310
|
errorReason?: _ProviderErrorReason;
|
|
311
|
+
lastMessagePreview?: string;
|
|
312
|
+
lastMessageRole?: string;
|
|
313
|
+
lastMessageAt?: number;
|
|
314
|
+
lastMessageHash?: string;
|
|
311
315
|
lastUpdated?: number;
|
|
312
316
|
unread?: boolean;
|
|
313
317
|
lastSeenAt?: number;
|
package/src/status/builders.ts
CHANGED
|
@@ -50,30 +50,26 @@ function shouldIncludeRuntimeMetadata(profile: SessionEntryProfile): boolean {
|
|
|
50
50
|
// ─── CDP Manager lookup helpers ──────────────────────
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* Find a CDP manager by key
|
|
53
|
+
* Find a CDP manager by key. Supports single-window (`cursor`) and full multi-window keys (`cursor_<targetId>`).
|
|
54
54
|
*
|
|
55
55
|
* Lookup order:
|
|
56
|
-
* 1. Exact match
|
|
57
|
-
* 2.
|
|
58
|
-
* 3. null
|
|
59
|
-
*
|
|
60
|
-
* This replaces raw `cdpManagers.get(ideType)` calls that broke when
|
|
61
|
-
* multi-window keys like "cursor_remote_vs" were used.
|
|
56
|
+
* 1. Exact match when connected
|
|
57
|
+
* 2. If key has no multi-window suffix: at most **one** connected manager whose key starts with `key_`
|
|
58
|
+
* 3. If two or more windows share that prefix → **null** (ambiguous — pass full managerKey from `GET /api/cdp/targets`)
|
|
62
59
|
*/
|
|
63
60
|
export function findCdpManager(
|
|
64
61
|
cdpManagers: Map<string, DaemonCdpManager>,
|
|
65
62
|
key: string,
|
|
66
63
|
): DaemonCdpManager | null {
|
|
67
|
-
// 1. Exact match (single-window: "cursor", or full managerKey: "
|
|
64
|
+
// 1. Exact match (single-window: "cursor", or full managerKey: "cursor_<targetId>")
|
|
68
65
|
const exact = cdpManagers.get(key);
|
|
69
|
-
if (exact) return exact;
|
|
66
|
+
if (exact) return exact.isConnected ? exact : null;
|
|
70
67
|
|
|
71
|
-
// 2. Prefix match
|
|
68
|
+
// 2. Prefix match only when it resolves to exactly one connected manager
|
|
72
69
|
const prefix = key + '_';
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
const matches = [...cdpManagers.entries()].filter(([k, m]) => m.isConnected && k.startsWith(prefix));
|
|
71
|
+
if (matches.length === 1) return matches[0][1];
|
|
72
|
+
// 0 matches → null; 2+ → ambiguous — caller must pass full managerKey (e.g. from /api/cdp/targets)
|
|
77
73
|
return null;
|
|
78
74
|
}
|
|
79
75
|
|
|
@@ -99,8 +95,13 @@ export function isCdpConnected(
|
|
|
99
95
|
cdpManagers: Map<string, DaemonCdpManager>,
|
|
100
96
|
key: string,
|
|
101
97
|
): boolean {
|
|
102
|
-
const
|
|
103
|
-
|
|
98
|
+
const exact = cdpManagers.get(key);
|
|
99
|
+
if (exact?.isConnected) return true;
|
|
100
|
+
const prefix = key + '_';
|
|
101
|
+
for (const [k, m] of cdpManagers.entries()) {
|
|
102
|
+
if (m.isConnected && k.startsWith(prefix)) return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/**
|
package/src/status/snapshot.ts
CHANGED
|
@@ -146,6 +146,71 @@ function parseMessageTime(value: unknown): number {
|
|
|
146
146
|
return 0;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
function stringifyPreviewContent(content: unknown): string {
|
|
150
|
+
if (typeof content === 'string') return content;
|
|
151
|
+
if (Array.isArray(content)) {
|
|
152
|
+
return content.map((block) => {
|
|
153
|
+
if (typeof block === 'string') return block;
|
|
154
|
+
if (block && typeof block === 'object' && 'text' in block) {
|
|
155
|
+
return String((block as { text?: unknown }).text || '');
|
|
156
|
+
}
|
|
157
|
+
return '';
|
|
158
|
+
}).join(' ');
|
|
159
|
+
}
|
|
160
|
+
if (content && typeof content === 'object' && 'text' in content) {
|
|
161
|
+
return String((content as { text?: unknown }).text || '');
|
|
162
|
+
}
|
|
163
|
+
return String(content || '');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizePreviewText(content: unknown): string {
|
|
167
|
+
return stringifyPreviewContent(content)
|
|
168
|
+
.replace(/\s+/g, ' ')
|
|
169
|
+
.trim();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function clampPreviewText(value: string, maxChars = 120): string {
|
|
173
|
+
if (value.length <= maxChars) return value;
|
|
174
|
+
if (maxChars <= 1) return value.slice(0, maxChars);
|
|
175
|
+
return `${value.slice(0, maxChars - 1)}…`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function simplePreviewHash(value: string): string {
|
|
179
|
+
let h = 0x811c9dc5;
|
|
180
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
181
|
+
h ^= value.charCodeAt(i);
|
|
182
|
+
h = (h * 0x01000193) >>> 0;
|
|
183
|
+
}
|
|
184
|
+
return h.toString(16);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getLastDisplayMessage(session: {
|
|
188
|
+
activeChat?: {
|
|
189
|
+
messages?: Array<{
|
|
190
|
+
role?: string;
|
|
191
|
+
content?: unknown;
|
|
192
|
+
receivedAt?: number | string;
|
|
193
|
+
}> | null
|
|
194
|
+
} | null
|
|
195
|
+
}) {
|
|
196
|
+
const messages = session.activeChat?.messages;
|
|
197
|
+
if (!Array.isArray(messages) || messages.length === 0) return null;
|
|
198
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
199
|
+
const candidate = messages[i];
|
|
200
|
+
const role = typeof candidate?.role === 'string' ? candidate.role : '';
|
|
201
|
+
if (role === 'system') continue;
|
|
202
|
+
const preview = clampPreviewText(normalizePreviewText(candidate?.content));
|
|
203
|
+
if (!preview) continue;
|
|
204
|
+
return {
|
|
205
|
+
role,
|
|
206
|
+
preview,
|
|
207
|
+
receivedAt: parseMessageTime(candidate?.receivedAt),
|
|
208
|
+
hash: simplePreviewHash(`${role}:${preview}`),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
149
214
|
function getSessionMessageUpdatedAt(session: {
|
|
150
215
|
activeChat?: {
|
|
151
216
|
messages?: Array<{ receivedAt?: number | string }> | null
|
|
@@ -196,8 +261,7 @@ function getSessionKind(session: SessionEntry): RecentLaunchEntry['kind'] {
|
|
|
196
261
|
}
|
|
197
262
|
|
|
198
263
|
function getLastMessageRole(session: { activeChat?: { messages?: Array<{ role?: string }> | null } | null }): string {
|
|
199
|
-
|
|
200
|
-
return typeof role === 'string' ? role : '';
|
|
264
|
+
return getLastDisplayMessage(session)?.role || '';
|
|
201
265
|
}
|
|
202
266
|
|
|
203
267
|
function getUnreadState(
|
|
@@ -286,6 +350,13 @@ export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnaps
|
|
|
286
350
|
`snapshot session id=${session.id} provider=${session.providerType} status=${String(session.status || '')} bucket=${inboxBucket} unread=${String(unread)} lastSeenAt=${lastSeenAt} completionMarker=${completionMarker || '-'} seenMarker=${seenCompletionMarker || '-'} lastUpdated=${String(session.lastUpdated || 0)} lastUsedAt=${lastUsedAt} lastRole=${getLastMessageRole(sourceSession)} msgUpdatedAt=${getSessionMessageUpdatedAt(sourceSession)}`,
|
|
287
351
|
);
|
|
288
352
|
}
|
|
353
|
+
const lastDisplayMessage = getLastDisplayMessage(sourceSession);
|
|
354
|
+
if (lastDisplayMessage) {
|
|
355
|
+
session.lastMessagePreview = lastDisplayMessage.preview;
|
|
356
|
+
session.lastMessageRole = lastDisplayMessage.role;
|
|
357
|
+
if (lastDisplayMessage.receivedAt > 0) session.lastMessageAt = lastDisplayMessage.receivedAt;
|
|
358
|
+
session.lastMessageHash = lastDisplayMessage.hash;
|
|
359
|
+
}
|
|
289
360
|
}
|
|
290
361
|
const includeMachineMetadata = profile !== 'live';
|
|
291
362
|
const terminalBackend = includeMachineMetadata
|