@adhdev/daemon-core 0.8.75 → 0.8.77
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/agent-stream/manager.d.ts +1 -1
- package/dist/cdp/manager.d.ts +9 -2
- package/dist/chat/async-batch.d.ts +4 -0
- package/dist/cli-adapters/pty-transport.d.ts +2 -0
- package/dist/config/chat-history.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +713 -147
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +712 -147
- package/dist/index.mjs.map +1 -1
- package/dist/providers/provider-instance.d.ts +4 -0
- package/dist/shared-types.d.ts +4 -0
- package/dist/status/chat-tail-hot-sessions.d.ts +4 -0
- package/node_modules/@adhdev/session-host-core/package.json +2 -2
- package/package.json +2 -2
- package/src/agent-stream/manager.ts +47 -8
- package/src/agent-stream/poller.ts +1 -0
- package/src/cdp/manager.ts +65 -13
- package/src/chat/async-batch.ts +26 -0
- package/src/cli-adapters/pty-transport.ts +2 -0
- package/src/cli-adapters/session-host-transport.ts +2 -0
- package/src/commands/stream-commands.ts +8 -5
- package/src/config/chat-history.ts +596 -63
- package/src/index.ts +2 -0
- package/src/providers/cli-provider-instance.ts +4 -0
- package/src/providers/provider-instance.ts +4 -0
- package/src/shared-types.ts +4 -0
- package/src/status/builders.ts +33 -1
- package/src/status/chat-tail-hot-sessions.ts +35 -1
|
@@ -25,8 +25,12 @@ export interface ProviderRuntimeInfo {
|
|
|
25
25
|
runtimeKey?: string;
|
|
26
26
|
displayName?: string;
|
|
27
27
|
workspaceLabel?: string;
|
|
28
|
+
lifecycle?: string | null;
|
|
29
|
+
surfaceKind?: 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
|
|
28
30
|
writeOwner?: ProviderRuntimeWriteOwner | null;
|
|
29
31
|
attachedClients?: ProviderRuntimeClient[];
|
|
32
|
+
restoredFromStorage?: boolean;
|
|
33
|
+
recoveryState?: string | null;
|
|
30
34
|
}
|
|
31
35
|
export interface ActiveChatData {
|
|
32
36
|
id: string;
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -242,10 +242,14 @@ export interface SessionEntry {
|
|
|
242
242
|
runtimeKey?: string;
|
|
243
243
|
runtimeDisplayName?: string;
|
|
244
244
|
runtimeWorkspaceLabel?: string;
|
|
245
|
+
runtimeLifecycle?: string | null;
|
|
246
|
+
runtimeSurfaceKind?: 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
|
|
245
247
|
/** CLI only: active presentation mode */
|
|
246
248
|
mode?: 'terminal' | 'chat';
|
|
247
249
|
runtimeWriteOwner?: RuntimeWriteOwner | null;
|
|
248
250
|
runtimeAttachedClients?: RuntimeAttachedClient[];
|
|
251
|
+
runtimeRestoredFromStorage?: boolean;
|
|
252
|
+
runtimeRecoveryState?: string | null;
|
|
249
253
|
resume?: ProviderResumeCapability;
|
|
250
254
|
activeChat: _ActiveChatData | null;
|
|
251
255
|
capabilities?: SessionCapability[];
|
|
@@ -4,6 +4,10 @@ export interface HotChatSessionLike {
|
|
|
4
4
|
id?: string | null;
|
|
5
5
|
status?: unknown;
|
|
6
6
|
lastMessageAt?: unknown;
|
|
7
|
+
runtimeLifecycle?: unknown;
|
|
8
|
+
runtimeSurfaceKind?: unknown;
|
|
9
|
+
runtimeRestoredFromStorage?: unknown;
|
|
10
|
+
runtimeRecoveryState?: unknown;
|
|
7
11
|
}
|
|
8
12
|
export declare function classifyHotChatSessionsForSubscriptionFlush(sessions: HotChatSessionLike[], previousHotSessionIds: ReadonlySet<string>, options?: {
|
|
9
13
|
now?: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/session-host-core",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "ADHDev local session host core
|
|
3
|
+
"version": "0.8.77",
|
|
4
|
+
"description": "ADHDev local session host core — session registry, protocol, buffers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "ADHDev daemon core
|
|
3
|
+
"version": "0.8.77",
|
|
4
|
+
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -181,10 +181,17 @@ export class DaemonAgentStreamManager {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/** Collect active extension session state */
|
|
184
|
-
async collectActiveSession(
|
|
184
|
+
async collectActiveSession(
|
|
185
|
+
cdp: DaemonCdpManager,
|
|
186
|
+
parentSessionId: string,
|
|
187
|
+
attemptedSessionIds: Set<string> = new Set(),
|
|
188
|
+
originSessionId?: string,
|
|
189
|
+
): Promise<AgentStreamState | null> {
|
|
185
190
|
if (!this.enabled) return null;
|
|
186
191
|
const activeSessionId = this.getActiveSessionId(parentSessionId);
|
|
187
192
|
if (!activeSessionId) return null;
|
|
193
|
+
const resolvedOriginSessionId = originSessionId || activeSessionId;
|
|
194
|
+
attemptedSessionIds.add(activeSessionId);
|
|
188
195
|
let agent = this.managedBySessionId.get(activeSessionId);
|
|
189
196
|
if (!agent) {
|
|
190
197
|
agent = await this.connectManagedSession(cdp, parentSessionId, activeSessionId) || undefined;
|
|
@@ -200,18 +207,50 @@ export class DaemonAgentStreamManager {
|
|
|
200
207
|
const evaluate: AgentEvaluateFn = (expr, timeout) =>
|
|
201
208
|
cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
|
|
202
209
|
const state = await agent.adapter.readChat(evaluate);
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
const resolvedProviderSessionId = typeof state.providerSessionId === 'string' && state.providerSessionId.trim()
|
|
211
|
+
? state.providerSessionId.trim()
|
|
212
|
+
: (typeof state.sessionId === 'string' && state.sessionId.trim() && state.sessionId !== agent.runtimeSessionId
|
|
213
|
+
? state.sessionId.trim()
|
|
214
|
+
: undefined);
|
|
215
|
+
const normalizedState: AgentStreamState = {
|
|
216
|
+
...state,
|
|
217
|
+
sessionId: agent.runtimeSessionId,
|
|
218
|
+
...(resolvedProviderSessionId ? { providerSessionId: resolvedProviderSessionId } : {}),
|
|
219
|
+
};
|
|
220
|
+
const stateError = this.getStateError(normalizedState);
|
|
221
|
+
const selectedModelValue = typeof normalizedState.controlValues?.model === 'string' ? normalizedState.controlValues.model : '';
|
|
222
|
+
LOG.debug('AgentStream', `[AgentStream] readChat(${type}) result: status=${normalizedState.status} msgs=${normalizedState.messages?.length || 0} model=${selectedModelValue}${normalizedState.status === 'error' ? ' error=' + JSON.stringify(stateError) : ''}`);
|
|
223
|
+
if (normalizedState.status === 'error' && this.isRecoverableSessionError(stateError)) {
|
|
207
224
|
throw new Error(stateError);
|
|
208
225
|
}
|
|
209
|
-
agent.lastState =
|
|
226
|
+
agent.lastState = normalizedState;
|
|
210
227
|
agent.lastError = null;
|
|
211
|
-
if (
|
|
228
|
+
if (normalizedState.status === 'panel_hidden') {
|
|
229
|
+
const discovered = await cdp.discoverAgentWebviews().catch(() => [] as AgentWebviewTarget[]);
|
|
230
|
+
const fallbackTarget = discovered.find((entry) => {
|
|
231
|
+
if (entry.agentType === type) return false;
|
|
232
|
+
const fallbackSessionId = this.resolveSessionIdForTarget(parentSessionId, entry.agentType);
|
|
233
|
+
return !!fallbackSessionId
|
|
234
|
+
&& fallbackSessionId !== activeSessionId
|
|
235
|
+
&& !attemptedSessionIds.has(fallbackSessionId);
|
|
236
|
+
});
|
|
237
|
+
if (fallbackTarget) {
|
|
238
|
+
const fallbackSessionId = this.resolveSessionIdForTarget(parentSessionId, fallbackTarget.agentType);
|
|
239
|
+
if (fallbackSessionId && fallbackSessionId !== activeSessionId && !attemptedSessionIds.has(fallbackSessionId)) {
|
|
240
|
+
this.logFn(`[AgentStream] Active session ${type} is hidden; switching to visible agent ${fallbackTarget.agentType} (${parentSessionId})`);
|
|
241
|
+
await this.setActiveSession(cdp, parentSessionId, fallbackSessionId);
|
|
242
|
+
await this.syncActiveSession(cdp, parentSessionId);
|
|
243
|
+
const fallbackState = await this.collectActiveSession(cdp, parentSessionId, attemptedSessionIds, resolvedOriginSessionId);
|
|
244
|
+
if (fallbackState?.status === 'panel_hidden' && resolvedOriginSessionId !== fallbackSessionId) {
|
|
245
|
+
await this.setActiveSession(cdp, parentSessionId, resolvedOriginSessionId);
|
|
246
|
+
await this.syncActiveSession(cdp, parentSessionId);
|
|
247
|
+
}
|
|
248
|
+
return fallbackState;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
212
251
|
agent.lastHiddenCheckTime = Date.now();
|
|
213
252
|
}
|
|
214
|
-
return
|
|
253
|
+
return normalizedState;
|
|
215
254
|
} catch (e) {
|
|
216
255
|
const errorMsg = (e as Error)?.message || String(e);
|
|
217
256
|
this.logFn(`[AgentStream] readChat(${type}) error: ${errorMsg.slice(0, 200)}`);
|
|
@@ -206,6 +206,7 @@ export class AgentStreamPoller {
|
|
|
206
206
|
try {
|
|
207
207
|
await agentStreamManager.syncActiveSession(cdp, parentSessionId);
|
|
208
208
|
let stream = await agentStreamManager.collectActiveSession(cdp, parentSessionId);
|
|
209
|
+
resolvedActiveSessionId = stream?.sessionId || agentStreamManager.getActiveSessionId(parentSessionId) || resolvedActiveSessionId;
|
|
209
210
|
if (stream?.status === 'waiting_approval') {
|
|
210
211
|
const autoApprove = providerLoader.getSettings(stream.agentType).autoApprove !== false;
|
|
211
212
|
if (autoApprove && resolvedActiveSessionId) {
|
package/src/cdp/manager.ts
CHANGED
|
@@ -16,7 +16,7 @@ import * as fs from 'fs';
|
|
|
16
16
|
import { LOG } from '../logging/logger.js';
|
|
17
17
|
import type { CdpTargetFilter } from '../providers/contracts.js';
|
|
18
18
|
|
|
19
|
-
interface CdpTarget {
|
|
19
|
+
export interface CdpTarget {
|
|
20
20
|
id: string;
|
|
21
21
|
type: string;
|
|
22
22
|
title: string;
|
|
@@ -24,6 +24,44 @@ interface CdpTarget {
|
|
|
24
24
|
webSocketDebuggerUrl: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function normalizeTitle(value: string | null | undefined): string {
|
|
28
|
+
return String(value || '').trim().replace(/\s+/g, ' ').toLowerCase();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function titlesMatch(lhs: string | null | undefined, rhs: string | null | undefined): boolean {
|
|
32
|
+
const a = normalizeTitle(lhs);
|
|
33
|
+
const b = normalizeTitle(rhs);
|
|
34
|
+
if (!a || !b) return false;
|
|
35
|
+
return a === b || a.includes(b) || b.includes(a);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveCdpPageTarget(params: {
|
|
39
|
+
pages: CdpTarget[];
|
|
40
|
+
pinnedTargetId?: string | null;
|
|
41
|
+
previousPageTitle?: string | null;
|
|
42
|
+
}): { target: CdpTarget | null; retargeted: boolean } {
|
|
43
|
+
const { pages, pinnedTargetId, previousPageTitle } = params;
|
|
44
|
+
if (pages.length === 0) return { target: null, retargeted: false };
|
|
45
|
+
|
|
46
|
+
if (!pinnedTargetId) {
|
|
47
|
+
return { target: pages[0] || null, retargeted: false };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const exact = pages.find((page) => page.id === pinnedTargetId);
|
|
51
|
+
if (exact) return { target: exact, retargeted: false };
|
|
52
|
+
|
|
53
|
+
const titleMatchesList = pages.filter((page) => titlesMatch(page.title, previousPageTitle));
|
|
54
|
+
if (titleMatchesList.length === 1) {
|
|
55
|
+
return { target: titleMatchesList[0], retargeted: true };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (pages.length === 1) {
|
|
59
|
+
return { target: pages[0], retargeted: true };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { target: null, retargeted: false };
|
|
63
|
+
}
|
|
64
|
+
|
|
27
65
|
export interface AgentWebviewTarget {
|
|
28
66
|
targetId: string;
|
|
29
67
|
extensionId: string;
|
|
@@ -204,22 +242,36 @@ export class DaemonCdpManager {
|
|
|
204
242
|
return;
|
|
205
243
|
}
|
|
206
244
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
// Keep reconnect target selection aligned with initial scan rules:
|
|
246
|
+
// prefer visible workbench pages that satisfy provider URL filters,
|
|
247
|
+
// then fall back to title-filtered pages only when nothing else matches.
|
|
248
|
+
const titleFilteredPages = pages.filter(t => !this.isNonMainTitle(t.title || ''));
|
|
249
|
+
const mainPages = titleFilteredPages.filter(t => this.isMainPageUrl(t.url));
|
|
250
|
+
const list = mainPages.length > 0
|
|
251
|
+
? mainPages
|
|
252
|
+
: (titleFilteredPages.length > 0 ? titleFilteredPages : pages);
|
|
210
253
|
|
|
211
254
|
this.log(`[CDP] pages(${list.length}): ${list.map(t => `"${t.title}"`).join(', ')}`);
|
|
212
255
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
256
|
+
const previousTargetId = this._targetId;
|
|
257
|
+
const selected = resolveCdpPageTarget({
|
|
258
|
+
pages: list,
|
|
259
|
+
pinnedTargetId: previousTargetId,
|
|
260
|
+
previousPageTitle: this._pageTitle,
|
|
261
|
+
});
|
|
262
|
+
if (selected.target) {
|
|
263
|
+
if (selected.retargeted && previousTargetId && previousTargetId !== selected.target.id) {
|
|
264
|
+
this.log(`[CDP] Target ${previousTargetId} rekeyed to ${selected.target.id}`);
|
|
265
|
+
this._targetId = selected.target.id;
|
|
222
266
|
}
|
|
267
|
+
this._pageTitle = selected.target.title || '';
|
|
268
|
+
resolve(selected.target);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (previousTargetId) {
|
|
273
|
+
this.log(`[CDP] Target ${previousTargetId} not found in page list`);
|
|
274
|
+
resolve(null);
|
|
223
275
|
return;
|
|
224
276
|
}
|
|
225
277
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface AsyncBatchOptions {
|
|
2
|
+
concurrency?: number;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export async function runAsyncBatch<T>(
|
|
6
|
+
items: Iterable<T>,
|
|
7
|
+
worker: (item: T, index: number) => Promise<void>,
|
|
8
|
+
options: AsyncBatchOptions = {},
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const list = Array.from(items);
|
|
11
|
+
if (list.length === 0) return;
|
|
12
|
+
|
|
13
|
+
const concurrency = Math.max(1, Math.min(list.length, Math.floor(options.concurrency || 1)));
|
|
14
|
+
let nextIndex = 0;
|
|
15
|
+
|
|
16
|
+
const runners = Array.from({ length: concurrency }, async () => {
|
|
17
|
+
while (true) {
|
|
18
|
+
const currentIndex = nextIndex;
|
|
19
|
+
nextIndex += 1;
|
|
20
|
+
if (currentIndex >= list.length) return;
|
|
21
|
+
await worker(list[currentIndex], currentIndex);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await Promise.all(runners);
|
|
26
|
+
}
|
|
@@ -40,6 +40,8 @@ export interface PtyRuntimeMetadata {
|
|
|
40
40
|
runtimeKey?: string;
|
|
41
41
|
displayName?: string;
|
|
42
42
|
workspaceLabel?: string;
|
|
43
|
+
lifecycle?: string | null;
|
|
44
|
+
surfaceKind?: 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
|
|
43
45
|
writeOwner?: PtyRuntimeWriteOwner | null;
|
|
44
46
|
attachedClients?: PtyRuntimeClientInfo[];
|
|
45
47
|
restoredFromStorage?: boolean;
|
|
@@ -375,6 +375,8 @@ class SessionHostRuntimeTransport implements PtyRuntimeTransport {
|
|
|
375
375
|
runtimeKey: record.runtimeKey,
|
|
376
376
|
displayName: record.displayName,
|
|
377
377
|
workspaceLabel: record.workspaceLabel,
|
|
378
|
+
lifecycle: typeof record.lifecycle === 'string' ? record.lifecycle : null,
|
|
379
|
+
surfaceKind: record.surfaceKind,
|
|
378
380
|
writeOwner: record.writeOwner ? {
|
|
379
381
|
clientId: record.writeOwner.clientId,
|
|
380
382
|
ownerType: record.writeOwner.ownerType,
|
|
@@ -301,8 +301,11 @@ function applyProviderPatch(h: CommandHelpers, args: any, payload: any): void {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
async function executeProviderScript(h: CommandHelpers, args: any, scriptName: string): Promise<CommandResult> {
|
|
304
|
+
const explicitTargetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
305
|
+
const targetSession = explicitTargetSessionId ? h.ctx.sessionRegistry?.get(explicitTargetSessionId) : undefined;
|
|
304
306
|
const resolvedProviderType =
|
|
305
|
-
|
|
307
|
+
targetSession?.providerType
|
|
308
|
+
|| h.currentSession?.providerType
|
|
306
309
|
|| h.currentProviderType
|
|
307
310
|
|| args?.agentType
|
|
308
311
|
|| args?.providerType;
|
|
@@ -358,8 +361,8 @@ async function executeProviderScript(h: CommandHelpers, args: any, scriptName: s
|
|
|
358
361
|
if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
|
|
359
362
|
|
|
360
363
|
const cdpKey = provider.category === 'ide'
|
|
361
|
-
? (h.currentSession?.cdpManagerKey || h.currentManagerKey || resolvedProviderType)
|
|
362
|
-
: (h.currentSession?.cdpManagerKey || h.currentManagerKey);
|
|
364
|
+
? (targetSession?.cdpManagerKey || h.currentSession?.cdpManagerKey || h.currentManagerKey || resolvedProviderType)
|
|
365
|
+
: (targetSession?.cdpManagerKey || h.currentSession?.cdpManagerKey || h.currentManagerKey);
|
|
363
366
|
LOG.info('Command', `[ExtScript] provider=${provider.type} category=${provider.category} cdpKey=${cdpKey}`);
|
|
364
367
|
const cdp = h.getCdp(cdpKey);
|
|
365
368
|
if (!cdp?.isConnected) return { success: false, error: `No CDP connection for ${cdpKey || 'any'}` };
|
|
@@ -368,9 +371,9 @@ async function executeProviderScript(h: CommandHelpers, args: any, scriptName: s
|
|
|
368
371
|
let result: unknown;
|
|
369
372
|
|
|
370
373
|
if (provider.category === 'extension') {
|
|
371
|
-
const runtimeSessionId = h.currentSession?.sessionId
|
|
374
|
+
const runtimeSessionId = explicitTargetSessionId || h.currentSession?.sessionId;
|
|
372
375
|
if (!runtimeSessionId) return { success: false, error: `No target session found for ${resolvedProviderType}` };
|
|
373
|
-
const parentSessionId = h.currentSession?.parentSessionId;
|
|
376
|
+
const parentSessionId = targetSession?.parentSessionId || h.currentSession?.parentSessionId;
|
|
374
377
|
if (parentSessionId) {
|
|
375
378
|
await h.agentStream?.setActiveSession(cdp, parentSessionId, runtimeSessionId);
|
|
376
379
|
await h.agentStream?.syncActiveSession(cdp, parentSessionId);
|