@adhdev/daemon-core 0.7.4 → 0.7.6
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/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +195 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +195 -28
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/agent-stream/forward.ts +6 -0
- package/src/boot/daemon-lifecycle.ts +1 -1
- package/src/cdp/initializer.ts +5 -5
- package/src/cdp/scanner.ts +2 -2
- package/src/commands/handler.ts +126 -3
- package/src/commands/stream-commands.ts +0 -1
- package/src/providers/extension-provider-instance.ts +50 -10
- package/src/providers/provider-instance-manager.ts +27 -10
package/package.json
CHANGED
|
@@ -24,6 +24,12 @@ export function forwardAgentStreamsToIdeInstance(
|
|
|
24
24
|
activeModal: stream.activeModal || null,
|
|
25
25
|
model: stream.model || undefined,
|
|
26
26
|
mode: stream.mode || undefined,
|
|
27
|
+
sessionId: stream.sessionId || stream.instanceId || undefined,
|
|
28
|
+
title: stream.title || stream.agentName || undefined,
|
|
29
|
+
agentType: stream.agentType || undefined,
|
|
30
|
+
agentName: stream.agentName || undefined,
|
|
31
|
+
extensionId: stream.extensionId || undefined,
|
|
32
|
+
inputContent: stream.inputContent || '',
|
|
27
33
|
});
|
|
28
34
|
}
|
|
29
35
|
}
|
|
@@ -185,7 +185,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
185
185
|
|
|
186
186
|
if (ideInstance) {
|
|
187
187
|
instanceManager.removeInstance(instanceKey);
|
|
188
|
-
LOG.info('
|
|
188
|
+
LOG.info('IDE', `Instance removed after detach: ${instanceKey}`);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
if (ideInstance?.getInstanceId) {
|
package/src/cdp/initializer.ts
CHANGED
|
@@ -78,9 +78,9 @@ export class DaemonCdpInitializer {
|
|
|
78
78
|
|
|
79
79
|
// Summary
|
|
80
80
|
if (cdpManagers.size > 0) {
|
|
81
|
-
LOG.info('
|
|
81
|
+
LOG.info('IDE', `${cdpManagers.size} IDE window(s) attached: ${[...cdpManagers.entries()].map(([k, m]) => `${k}:${m.getPort()}`).join(', ')}`);
|
|
82
82
|
} else {
|
|
83
|
-
LOG.warn('
|
|
83
|
+
LOG.warn('IDE', `No IDE windows attached — tried: ${filtered.map(p => `${p.ide}:${p.port}`).join(', ')}`);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -113,7 +113,7 @@ export class DaemonCdpInitializer {
|
|
|
113
113
|
if (connected) {
|
|
114
114
|
registerExtensionProviders(providerLoader, manager, ide);
|
|
115
115
|
cdpManagers.set(ide, manager);
|
|
116
|
-
LOG.info('
|
|
116
|
+
LOG.info('IDE', `Attached: ${ide} (port ${port})`);
|
|
117
117
|
await this.config.onConnected?.(ide, manager, ide);
|
|
118
118
|
}
|
|
119
119
|
return;
|
|
@@ -155,7 +155,7 @@ export class DaemonCdpInitializer {
|
|
|
155
155
|
if (connected) {
|
|
156
156
|
registerExtensionProviders(providerLoader, manager, ide);
|
|
157
157
|
cdpManagers.set(managerKey, manager);
|
|
158
|
-
LOG.info('
|
|
158
|
+
LOG.info('IDE', `Attached window: ${managerKey} (port ${port}${targets.length > 1 ? `, page "${target.title}"` : ''})`);
|
|
159
159
|
await this.config.onConnected?.(ide, manager, managerKey);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
@@ -195,7 +195,7 @@ export class DaemonCdpInitializer {
|
|
|
195
195
|
for (const { key, manager, reason } of removals) {
|
|
196
196
|
try { manager.disconnect(); } catch { /* noop */ }
|
|
197
197
|
this.config.cdpManagers.delete(key);
|
|
198
|
-
LOG.info('
|
|
198
|
+
LOG.info('IDE', `Detached window: ${key} (${reason})`);
|
|
199
199
|
await this.config.onDisconnected?.(ide, manager, key, reason);
|
|
200
200
|
}
|
|
201
201
|
}
|
package/src/cdp/scanner.ts
CHANGED
|
@@ -134,7 +134,7 @@ export class DaemonCdpScanner {
|
|
|
134
134
|
|
|
135
135
|
registerExtensionProviders(this.ctx.providerLoader, manager, ide);
|
|
136
136
|
this.ctx.cdpManagers.set(ide, manager);
|
|
137
|
-
LOG.info('
|
|
137
|
+
LOG.info('IDE', `Attached: ${ide} (port ${port})`);
|
|
138
138
|
|
|
139
139
|
// Setup IDE instance
|
|
140
140
|
await setupIdeInstance(this.ctx, { ideType: ide, manager });
|
|
@@ -172,7 +172,7 @@ export class DaemonCdpScanner {
|
|
|
172
172
|
if (!manager) continue;
|
|
173
173
|
|
|
174
174
|
this.ctx.cdpManagers.set(managerKey, manager);
|
|
175
|
-
LOG.info('
|
|
175
|
+
LOG.info('IDE', `Attached window: ${managerKey} (port ${port}, page "${target.title}")`);
|
|
176
176
|
|
|
177
177
|
await setupIdeInstance(this.ctx, {
|
|
178
178
|
ideType: ide,
|
package/src/commands/handler.ts
CHANGED
|
@@ -65,6 +65,96 @@ export interface CommandHelpers {
|
|
|
65
65
|
readonly historyWriter: ChatHistoryWriter;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
const COMMAND_DEBUG_LEVELS = new Set([
|
|
69
|
+
'pty_input',
|
|
70
|
+
'pty_resize',
|
|
71
|
+
'cdp_eval',
|
|
72
|
+
'cdp_batch',
|
|
73
|
+
'cdp_dom_query',
|
|
74
|
+
'cdp_dom_dump',
|
|
75
|
+
'cdp_dom_debug',
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
function logAtLevel(level: 'debug' | 'info' | 'warn' | 'error', category: string, message: string): void {
|
|
79
|
+
switch (level) {
|
|
80
|
+
case 'debug':
|
|
81
|
+
LOG.debug(category, message);
|
|
82
|
+
return;
|
|
83
|
+
case 'warn':
|
|
84
|
+
LOG.warn(category, message);
|
|
85
|
+
return;
|
|
86
|
+
case 'error':
|
|
87
|
+
LOG.error(category, message);
|
|
88
|
+
return;
|
|
89
|
+
default:
|
|
90
|
+
LOG.info(category, message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getCommandLogLevel(cmd: string): 'debug' | 'info' {
|
|
95
|
+
return COMMAND_DEBUG_LEVELS.has(cmd) ? 'debug' : 'info';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function summarizeLogValue(value: unknown): string {
|
|
99
|
+
if (value === null) return 'null';
|
|
100
|
+
if (value === undefined) return 'undefined';
|
|
101
|
+
if (typeof value === 'string') {
|
|
102
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
103
|
+
if (!normalized) return '""';
|
|
104
|
+
if (normalized.length <= 80) return JSON.stringify(normalized);
|
|
105
|
+
return `${JSON.stringify(normalized.slice(0, 80))}…(${normalized.length} chars)`;
|
|
106
|
+
}
|
|
107
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
108
|
+
if (Array.isArray(value)) return `[${value.length} items]`;
|
|
109
|
+
if (typeof value === 'object') return '{...}';
|
|
110
|
+
return String(value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function summarizeCommandArgs(args: any): string {
|
|
114
|
+
if (!args || typeof args !== 'object') return '-';
|
|
115
|
+
|
|
116
|
+
const preferredKeys = [
|
|
117
|
+
'targetSessionId',
|
|
118
|
+
'providerType',
|
|
119
|
+
'agentType',
|
|
120
|
+
'ideType',
|
|
121
|
+
'model',
|
|
122
|
+
'mode',
|
|
123
|
+
'action',
|
|
124
|
+
'button',
|
|
125
|
+
'key',
|
|
126
|
+
'force',
|
|
127
|
+
'offset',
|
|
128
|
+
'limit',
|
|
129
|
+
'cols',
|
|
130
|
+
'rows',
|
|
131
|
+
'path',
|
|
132
|
+
'command',
|
|
133
|
+
'commandId',
|
|
134
|
+
'workspace',
|
|
135
|
+
'dir',
|
|
136
|
+
'url',
|
|
137
|
+
'text',
|
|
138
|
+
'message',
|
|
139
|
+
'data',
|
|
140
|
+
'value',
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const entries: string[] = [];
|
|
144
|
+
for (const key of preferredKeys) {
|
|
145
|
+
if (!(key in args) || args[key] === undefined) continue;
|
|
146
|
+
const value =
|
|
147
|
+
key === 'text' || key === 'message'
|
|
148
|
+
? `${String(args[key] || '').length} chars`
|
|
149
|
+
: key === 'data'
|
|
150
|
+
? `${String(args[key] || '').length} chars`
|
|
151
|
+
: summarizeLogValue(args[key]);
|
|
152
|
+
entries.push(`${key}=${value}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return entries.length ? entries.join(' ') : '{...}';
|
|
156
|
+
}
|
|
157
|
+
|
|
68
158
|
export class DaemonCommandHandler implements CommandHelpers {
|
|
69
159
|
private _ctx: CommandContext;
|
|
70
160
|
private _agentStream: DaemonAgentStreamManager | null = null;
|
|
@@ -235,6 +325,30 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
235
325
|
return undefined;
|
|
236
326
|
}
|
|
237
327
|
|
|
328
|
+
private logCommandStart(cmd: string, args: any): void {
|
|
329
|
+
const routeBits = [
|
|
330
|
+
this._currentRoute.session?.sessionId ? `session=${this._currentRoute.session.sessionId}` : '',
|
|
331
|
+
this._currentRoute.managerKey ? `manager=${this._currentRoute.managerKey}` : '',
|
|
332
|
+
this._currentRoute.providerType ? `provider=${this._currentRoute.providerType}` : '',
|
|
333
|
+
].filter(Boolean).join(' ');
|
|
334
|
+
const summary = summarizeCommandArgs(args);
|
|
335
|
+
logAtLevel(
|
|
336
|
+
getCommandLogLevel(cmd),
|
|
337
|
+
'Command',
|
|
338
|
+
`[${cmd}] start${routeBits ? ` ${routeBits}` : ''} args=${summary}`,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private logCommandEnd(cmd: string, result: CommandResult, startedAt: number): void {
|
|
343
|
+
const durationMs = Date.now() - startedAt;
|
|
344
|
+
const parts = [`[${cmd}] end`, `success=${result.success}`, `duration=${durationMs}ms`];
|
|
345
|
+
if (typeof result.error === 'string' && result.error) {
|
|
346
|
+
parts.push(`error=${JSON.stringify(result.error)}`);
|
|
347
|
+
}
|
|
348
|
+
const level = result.success ? getCommandLogLevel(cmd) : 'warn';
|
|
349
|
+
logAtLevel(level, 'Command', parts.join(' '));
|
|
350
|
+
}
|
|
351
|
+
|
|
238
352
|
setAgentStreamManager(manager: DaemonAgentStreamManager): void {
|
|
239
353
|
this._agentStream = manager;
|
|
240
354
|
}
|
|
@@ -244,20 +358,29 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
244
358
|
async handle(cmd: string, args: any): Promise<CommandResult> {
|
|
245
359
|
// Per-request: extract target session / CDP scope / provider type from args
|
|
246
360
|
this._currentRoute = this.resolveRoute(args);
|
|
361
|
+
const startedAt = Date.now();
|
|
362
|
+
this.logCommandStart(cmd, args);
|
|
247
363
|
|
|
248
364
|
// Commands without ideType CDP silently fail (prevent P2P retry spam)
|
|
365
|
+
let result: CommandResult;
|
|
249
366
|
if (!this._currentRoute.session && !this._currentRoute.managerKey && !this._currentRoute.providerType) {
|
|
250
367
|
const cdpCommands = ['send_chat', 'read_chat', 'list_chats', 'new_chat', 'switch_chat', 'set_mode', 'change_model', 'set_thought_level', 'resolve_action'];
|
|
251
368
|
if (cdpCommands.includes(cmd)) {
|
|
252
|
-
|
|
369
|
+
result = { success: false, error: 'No targetSessionId specified — cannot route command' };
|
|
370
|
+
this.logCommandEnd(cmd, result, startedAt);
|
|
371
|
+
return result;
|
|
253
372
|
}
|
|
254
373
|
}
|
|
255
374
|
|
|
256
375
|
try {
|
|
257
|
-
|
|
376
|
+
result = await this.dispatch(cmd, args);
|
|
377
|
+
this.logCommandEnd(cmd, result, startedAt);
|
|
378
|
+
return result;
|
|
258
379
|
} catch (e: any) {
|
|
259
380
|
LOG.error('Command', `[${cmd}] Unhandled error: ${e?.message || e}`);
|
|
260
|
-
|
|
381
|
+
result = { success: false, error: `Internal error: ${e?.message || 'unknown'}` };
|
|
382
|
+
this.logCommandEnd(cmd, result, startedAt);
|
|
383
|
+
return result;
|
|
261
384
|
}
|
|
262
385
|
}
|
|
263
386
|
|
|
@@ -85,7 +85,6 @@ export function handleSetProviderSetting(h: CommandHelpers, args: any): CommandR
|
|
|
85
85
|
|
|
86
86
|
export async function handleExtensionScript(h: CommandHelpers, args: any, scriptName: string): Promise<CommandResult> {
|
|
87
87
|
const { agentType, ideType } = args || {};
|
|
88
|
-
LOG.info('Command', `[ExtScript] ${scriptName} agentType=${agentType} ideType=${ideType} session=${h.currentSession?.sessionId || ''}`);
|
|
89
88
|
if (!agentType) return { success: false, error: 'agentType is required' };
|
|
90
89
|
|
|
91
90
|
const loader = h.ctx.providerLoader;
|
|
@@ -32,6 +32,10 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
32
32
|
// meta
|
|
33
33
|
private instanceId: string;
|
|
34
34
|
private ideType: string = '';
|
|
35
|
+
private chatId: string | null = null;
|
|
36
|
+
private chatTitle: string | null = null;
|
|
37
|
+
private agentName: string = '';
|
|
38
|
+
private extensionId: string = '';
|
|
35
39
|
|
|
36
40
|
constructor(provider: ProviderModule) {
|
|
37
41
|
this.type = provider.type;
|
|
@@ -69,8 +73,8 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
69
73
|
category: 'extension',
|
|
70
74
|
status: this.currentStatus as ProviderState['status'],
|
|
71
75
|
activeChat: this.messages.length > 0 ? {
|
|
72
|
-
id:
|
|
73
|
-
title: this.provider.name,
|
|
76
|
+
id: this.chatId || this.instanceId,
|
|
77
|
+
title: this.chatTitle || this.agentName || this.provider.name,
|
|
74
78
|
status: this.currentStatus,
|
|
75
79
|
messages: this.messages,
|
|
76
80
|
activeModal: this.activeModal,
|
|
@@ -94,6 +98,10 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
94
98
|
if (data?.activeModal !== undefined) this.activeModal = data.activeModal;
|
|
95
99
|
if (data?.model) this.currentModel = data.model;
|
|
96
100
|
if (data?.mode) this.currentMode = data.mode;
|
|
101
|
+
if (typeof data?.sessionId === 'string' && data.sessionId.trim()) this.chatId = data.sessionId;
|
|
102
|
+
if (typeof data?.title === 'string' && data.title.trim()) this.chatTitle = data.title;
|
|
103
|
+
if (typeof data?.agentName === 'string' && data.agentName.trim()) this.agentName = data.agentName;
|
|
104
|
+
if (typeof data?.extensionId === 'string' && data.extensionId.trim()) this.extensionId = data.extensionId;
|
|
97
105
|
if (data?.status) {
|
|
98
106
|
const newStatus = data.status;
|
|
99
107
|
this.detectTransition(newStatus, data);
|
|
@@ -117,12 +125,6 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
// ─── status transition detect ──────────────────────────────
|
|
120
|
-
// NOTE: Extension transitions are TRACKED but NOT emitted as events.
|
|
121
|
-
// The parent IdeProviderInstance already emits identical events
|
|
122
|
-
// (generating_started, generating_completed, waiting_approval)
|
|
123
|
-
// via its own detectAgentTransitions(). Emitting here would cause
|
|
124
|
-
// duplicate toasts with slightly different content.
|
|
125
|
-
|
|
126
128
|
private detectTransition(newStatus: string, data: any): void {
|
|
127
129
|
const now = Date.now();
|
|
128
130
|
const agentStatus = (newStatus === 'streaming' || newStatus === 'generating') ? 'generating'
|
|
@@ -136,13 +138,44 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
136
138
|
: undefined;
|
|
137
139
|
|
|
138
140
|
if (agentStatus !== this.lastAgentStatus) {
|
|
139
|
-
// Track generating start time (for monitor elapsed calculation)
|
|
140
141
|
if (this.lastAgentStatus === 'idle' && agentStatus === 'generating') {
|
|
141
142
|
this.generatingStartedAt = now;
|
|
143
|
+
this.pushEvent({
|
|
144
|
+
event: 'agent:generating_started',
|
|
145
|
+
chatTitle: this.resolveChatTitle(data),
|
|
146
|
+
timestamp: now,
|
|
147
|
+
ideType: this.ideType || this.type,
|
|
148
|
+
agentType: this.type,
|
|
149
|
+
agentName: this.agentName || this.provider.name,
|
|
150
|
+
extensionId: this.extensionId || this.type,
|
|
151
|
+
});
|
|
152
|
+
} else if (agentStatus === 'waiting_approval') {
|
|
153
|
+
if (!this.generatingStartedAt) this.generatingStartedAt = now;
|
|
154
|
+
this.pushEvent({
|
|
155
|
+
event: 'agent:waiting_approval',
|
|
156
|
+
chatTitle: this.resolveChatTitle(data),
|
|
157
|
+
timestamp: now,
|
|
158
|
+
ideType: this.ideType || this.type,
|
|
159
|
+
agentType: this.type,
|
|
160
|
+
agentName: this.agentName || this.provider.name,
|
|
161
|
+
extensionId: this.extensionId || this.type,
|
|
162
|
+
modalMessage: data?.activeModal?.message,
|
|
163
|
+
modalButtons: data?.activeModal?.buttons,
|
|
164
|
+
});
|
|
142
165
|
} else if (agentStatus === 'idle' && (this.lastAgentStatus === 'generating' || this.lastAgentStatus === 'waiting_approval')) {
|
|
166
|
+
const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : 0;
|
|
167
|
+
this.pushEvent({
|
|
168
|
+
event: 'agent:generating_completed',
|
|
169
|
+
chatTitle: this.resolveChatTitle(data),
|
|
170
|
+
duration,
|
|
171
|
+
timestamp: now,
|
|
172
|
+
ideType: this.ideType || this.type,
|
|
173
|
+
agentType: this.type,
|
|
174
|
+
agentName: this.agentName || this.provider.name,
|
|
175
|
+
extensionId: this.extensionId || this.type,
|
|
176
|
+
});
|
|
143
177
|
this.generatingStartedAt = 0;
|
|
144
178
|
}
|
|
145
|
-
// Do NOT pushEvent for transitions — parent IDE instance handles these
|
|
146
179
|
this.lastAgentStatus = agentStatus;
|
|
147
180
|
}
|
|
148
181
|
|
|
@@ -164,4 +197,11 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
164
197
|
this.events = [];
|
|
165
198
|
return events;
|
|
166
199
|
}
|
|
200
|
+
|
|
201
|
+
private resolveChatTitle(data: any): string {
|
|
202
|
+
const title = typeof data?.title === 'string' && data.title.trim()
|
|
203
|
+
? data.title.trim()
|
|
204
|
+
: this.chatTitle;
|
|
205
|
+
return title || this.agentName || this.provider.name;
|
|
206
|
+
}
|
|
167
207
|
}
|
|
@@ -75,16 +75,13 @@ export class ProviderInstanceManager {
|
|
|
75
75
|
try {
|
|
76
76
|
const state = instance.getState();
|
|
77
77
|
states.push(state);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
instanceId: state.instanceId,
|
|
86
|
-
targetSessionId: state.instanceId,
|
|
87
|
-
providerCategory: state.category,
|
|
78
|
+
this.emitPendingEvents(instance.type, state);
|
|
79
|
+
if (state.category === 'ide') {
|
|
80
|
+
for (const childState of state.extensions) {
|
|
81
|
+
this.emitPendingEvents(childState.type, childState, {
|
|
82
|
+
targetSessionId: childState.instanceId,
|
|
83
|
+
workspaceName: state.workspace || undefined,
|
|
84
|
+
parentSessionId: state.instanceId,
|
|
88
85
|
});
|
|
89
86
|
}
|
|
90
87
|
}
|
|
@@ -141,6 +138,26 @@ export class ProviderInstanceManager {
|
|
|
141
138
|
this.eventListeners.push(listener);
|
|
142
139
|
}
|
|
143
140
|
|
|
141
|
+
private emitPendingEvents(
|
|
142
|
+
providerType: string,
|
|
143
|
+
state: ProviderState,
|
|
144
|
+
extra: Record<string, unknown> = {},
|
|
145
|
+
): void {
|
|
146
|
+
for (const event of state.pendingEvents) {
|
|
147
|
+
for (const listener of this.eventListeners) {
|
|
148
|
+
listener({
|
|
149
|
+
...event,
|
|
150
|
+
providerType,
|
|
151
|
+
instanceId: state.instanceId,
|
|
152
|
+
targetSessionId: state.instanceId,
|
|
153
|
+
providerCategory: state.category,
|
|
154
|
+
workspaceName: state.workspace || undefined,
|
|
155
|
+
...extra,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
144
161
|
/**
|
|
145
162
|
* Forward event to specific Instance
|
|
146
163
|
*/
|