@adhdev/daemon-core 0.9.33 → 0.9.35
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/cli-adapter-types.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +6 -0
- package/dist/commands/upgrade-helper.d.ts +1 -0
- package/dist/index.js +208 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +208 -33
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +3 -1
- package/dist/providers/provider-instance-manager.d.ts +3 -1
- package/dist/providers/provider-instance.d.ts +20 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapter-types.ts +1 -0
- package/src/cli-adapters/provider-cli-adapter.ts +84 -3
- package/src/cli-adapters/provider-cli-parse.ts +3 -2
- package/src/commands/handler.ts +10 -1
- package/src/commands/upgrade-helper.ts +48 -16
- package/src/daemon/dev-server.ts +3 -14
- package/src/providers/cli-provider-instance.ts +35 -1
- package/src/providers/provider-instance-manager.ts +58 -1
- package/src/providers/provider-instance.ts +22 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* collectCliData() + status transition logic from daemon-status.ts moved here.
|
|
6
6
|
*/
|
|
7
7
|
import { type ProviderModule } from './contracts.js';
|
|
8
|
-
import type { ProviderInstance, ProviderState, InstanceContext } from './provider-instance.js';
|
|
8
|
+
import type { ProviderInstance, ProviderState, InstanceContext, HotChatSessionState } from './provider-instance.js';
|
|
9
9
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
10
10
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
11
11
|
type PersistableCliHistoryMessage = {
|
|
@@ -79,6 +79,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
79
79
|
previousProviderSessionId?: string;
|
|
80
80
|
}) => void;
|
|
81
81
|
});
|
|
82
|
+
refreshProviderDefinition(provider: ProviderModule): void;
|
|
82
83
|
init(context: InstanceContext): Promise<void>;
|
|
83
84
|
onTick(): Promise<void>;
|
|
84
85
|
/**
|
|
@@ -89,6 +90,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
89
90
|
getState(): ProviderState;
|
|
90
91
|
setPresentationMode(mode: 'terminal' | 'chat'): void;
|
|
91
92
|
getPresentationMode(): 'terminal' | 'chat';
|
|
93
|
+
getHotChatSessionState(): HotChatSessionState;
|
|
92
94
|
updateSettings(newSettings: Record<string, any>): void;
|
|
93
95
|
onEvent(event: string, data?: any): void;
|
|
94
96
|
dispose(): void;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. Collect overall state
|
|
8
8
|
* 4. Event collection and propagation
|
|
9
9
|
*/
|
|
10
|
-
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
10
|
+
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, HotChatSessionState } from './provider-instance.js';
|
|
11
11
|
export declare class ProviderInstanceManager {
|
|
12
12
|
private instances;
|
|
13
13
|
private tickTimer;
|
|
@@ -45,6 +45,7 @@ export declare class ProviderInstanceManager {
|
|
|
45
45
|
* + Propagate pending events to event listeners
|
|
46
46
|
*/
|
|
47
47
|
collectAllStates(): ProviderState[];
|
|
48
|
+
collectHotChatSessionStates(): HotChatSessionState[];
|
|
48
49
|
/**
|
|
49
50
|
* Per-category status collect
|
|
50
51
|
*/
|
|
@@ -77,6 +78,7 @@ export declare class ProviderInstanceManager {
|
|
|
77
78
|
* Called when user changes settings from dashboard.
|
|
78
79
|
*/
|
|
79
80
|
updateInstanceSettings(providerType: string, settings: Record<string, any>): number;
|
|
81
|
+
refreshProviderDefinitions(resolveProvider: (providerType: string) => unknown): number;
|
|
80
82
|
/**
|
|
81
83
|
* All terminate
|
|
82
84
|
*/
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Daemon only collects via ProviderInstance.getState(),
|
|
8
8
|
* Each Instance manages its own status.
|
|
9
9
|
*/
|
|
10
|
-
import type { ProviderResumeCapability } from './contracts.js';
|
|
10
|
+
import type { ProviderModule, ProviderResumeCapability } from './contracts.js';
|
|
11
11
|
import type { AcpConfigOption, AcpMode, ProviderControlSchema, ProviderSummaryMetadata, SessionCapability } from '../shared-types.js';
|
|
12
12
|
import type { ChatMessage } from '../types.js';
|
|
13
13
|
export type ProviderStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
|
|
@@ -112,6 +112,17 @@ export interface ProviderEvent {
|
|
|
112
112
|
timestamp: number;
|
|
113
113
|
[key: string]: any;
|
|
114
114
|
}
|
|
115
|
+
export interface HotChatSessionState {
|
|
116
|
+
id: string;
|
|
117
|
+
status?: unknown;
|
|
118
|
+
unread?: unknown;
|
|
119
|
+
inboxBucket?: unknown;
|
|
120
|
+
lastMessageAt?: unknown;
|
|
121
|
+
runtimeLifecycle?: unknown;
|
|
122
|
+
runtimeSurfaceKind?: unknown;
|
|
123
|
+
runtimeRestoredFromStorage?: unknown;
|
|
124
|
+
runtimeRecoveryState?: unknown;
|
|
125
|
+
}
|
|
115
126
|
export interface InstanceContext {
|
|
116
127
|
/** CDP connection (IDE/Extension) */
|
|
117
128
|
cdp?: {
|
|
@@ -142,10 +153,18 @@ export interface ProviderInstance {
|
|
|
142
153
|
onTick(): Promise<void>;
|
|
143
154
|
/** Return current status */
|
|
144
155
|
getState(): ProviderState;
|
|
156
|
+
/**
|
|
157
|
+
* Return the cheap session metadata needed to decide whether chat-tail
|
|
158
|
+
* subscriptions should be flushed. Implementations must avoid rich transcript
|
|
159
|
+
* parsing here; callers use this on P2P hot flush paths.
|
|
160
|
+
*/
|
|
161
|
+
getHotChatSessionState?(): HotChatSessionState | HotChatSessionState[] | null;
|
|
145
162
|
/** Receive event (external → Instance) */
|
|
146
163
|
onEvent(event: string, data?: any): void;
|
|
147
164
|
/** Update settings at runtime (called when user changes settings from dashboard) */
|
|
148
165
|
updateSettings?(newSettings: Record<string, any>): void;
|
|
166
|
+
/** Refresh static provider definition/scripts without restarting the live runtime. */
|
|
167
|
+
refreshProviderDefinition?(provider: ProviderModule): void;
|
|
149
168
|
/** cleanup */
|
|
150
169
|
dispose(): void;
|
|
151
170
|
}
|
package/package.json
CHANGED
package/src/cli-adapter-types.ts
CHANGED
|
@@ -51,6 +51,7 @@ export interface CliAdapter {
|
|
|
51
51
|
isReady(): boolean;
|
|
52
52
|
setOnStatusChange(callback: () => void): void;
|
|
53
53
|
updateRuntimeSettings?(settings: Record<string, unknown>): void;
|
|
54
|
+
setCliScripts?(scripts: Record<string, unknown>): void;
|
|
54
55
|
setServerConn?(serverConn: unknown): void;
|
|
55
56
|
clearHistory?(): void;
|
|
56
57
|
resolveAction?(data: unknown): Promise<void>;
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
extractPromptRetrySnippet,
|
|
33
33
|
getLastUserPromptText,
|
|
34
34
|
listCliScriptNames,
|
|
35
|
+
normalizeComparableMessageContent,
|
|
35
36
|
normalizePromptText,
|
|
36
37
|
normalizeScreenSnapshot,
|
|
37
38
|
promptLikelyVisible,
|
|
@@ -183,6 +184,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
183
184
|
private messages: CliChatMessage[] = [];
|
|
184
185
|
private committedMessages: CliChatMessage[] = [];
|
|
185
186
|
private structuredMessages: CliChatMessage[] = [];
|
|
187
|
+
private committedMessagesActivitySignature = '';
|
|
188
|
+
private committedMessagesChangedAt = 0;
|
|
186
189
|
private currentStatus: CliSessionStatus['status'] = 'starting';
|
|
187
190
|
private onStatusChange: (() => void) | null = null;
|
|
188
191
|
|
|
@@ -288,11 +291,38 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
288
291
|
private static readonly FINISH_RETRY_DELAY_MS = 300;
|
|
289
292
|
private static readonly MAX_FINISH_RETRIES = 2;
|
|
290
293
|
|
|
294
|
+
private buildCommittedMessagesActivitySignature(): string {
|
|
295
|
+
const last = this.committedMessages[this.committedMessages.length - 1];
|
|
296
|
+
return [
|
|
297
|
+
String(this.committedMessages.length),
|
|
298
|
+
String(last?.role || ''),
|
|
299
|
+
String(last?.kind || ''),
|
|
300
|
+
String(last?.senderName || ''),
|
|
301
|
+
String(last?.timestamp || ''),
|
|
302
|
+
String(last?.receivedAt || ''),
|
|
303
|
+
normalizeComparableMessageContent(last?.content || '').slice(-240),
|
|
304
|
+
].join('|');
|
|
305
|
+
}
|
|
306
|
+
|
|
291
307
|
private syncMessageViews(): void {
|
|
308
|
+
const signature = this.buildCommittedMessagesActivitySignature();
|
|
309
|
+
if (signature !== this.committedMessagesActivitySignature) {
|
|
310
|
+
this.committedMessagesActivitySignature = signature;
|
|
311
|
+
this.committedMessagesChangedAt = Date.now();
|
|
312
|
+
}
|
|
292
313
|
this.messages = [...this.committedMessages];
|
|
293
314
|
this.structuredMessages = [...this.committedMessages];
|
|
294
315
|
}
|
|
295
316
|
|
|
317
|
+
getLastCommittedMessageActivityAt(): number {
|
|
318
|
+
const last = this.committedMessages[this.committedMessages.length - 1];
|
|
319
|
+
const messageTime = Math.max(
|
|
320
|
+
typeof last?.receivedAt === 'number' && Number.isFinite(last.receivedAt) ? last.receivedAt : 0,
|
|
321
|
+
typeof last?.timestamp === 'number' && Number.isFinite(last.timestamp) ? last.timestamp : 0,
|
|
322
|
+
);
|
|
323
|
+
return Math.max(messageTime, this.committedMessagesChangedAt || 0);
|
|
324
|
+
}
|
|
325
|
+
|
|
296
326
|
private readTerminalScreenText(now = Date.now()): string {
|
|
297
327
|
const screenText = this.terminalScreen.getText() || '';
|
|
298
328
|
this.lastScreenText = screenText;
|
|
@@ -519,10 +549,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
519
549
|
/** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
|
|
520
550
|
setCliScripts(scripts: CliScripts): void {
|
|
521
551
|
this.cliScripts = scripts;
|
|
552
|
+
this.parsedStatusCache = null;
|
|
553
|
+
this.parseErrorMessage = null;
|
|
522
554
|
const scriptNames = listCliScriptNames(scripts);
|
|
523
555
|
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
|
|
524
556
|
}
|
|
525
557
|
|
|
558
|
+
/** Refresh provider scripts/config used by this adapter without restarting the PTY runtime. */
|
|
559
|
+
refreshProviderDefinition(provider: CliProviderModule): void {
|
|
560
|
+
this.provider = provider;
|
|
561
|
+
this.setCliScripts(provider.scripts || {});
|
|
562
|
+
}
|
|
563
|
+
|
|
526
564
|
updateRuntimeSettings(settings: Record<string, any>): void {
|
|
527
565
|
this.runtimeSettings = { ...settings };
|
|
528
566
|
}
|
|
@@ -1067,7 +1105,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1067
1105
|
}
|
|
1068
1106
|
|
|
1069
1107
|
if (this.currentTurnScope && !lastParsedAssistant) {
|
|
1070
|
-
LOG.
|
|
1108
|
+
LOG.debug(
|
|
1071
1109
|
'CLI',
|
|
1072
1110
|
`[${this.cliType}] Settled without assistant: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 220)).slice(0, 260)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'}`
|
|
1073
1111
|
);
|
|
@@ -2013,9 +2051,52 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2013
2051
|
|
|
2014
2052
|
private armResponseTimeout(): void {
|
|
2015
2053
|
if (this.responseTimeout) clearTimeout(this.responseTimeout);
|
|
2054
|
+
const timeoutMs = this.timeouts.maxResponse;
|
|
2055
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
2056
|
+
this.responseTimeout = null;
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2016
2059
|
this.responseTimeout = setTimeout(() => {
|
|
2017
|
-
|
|
2018
|
-
|
|
2060
|
+
this.responseTimeout = null;
|
|
2061
|
+
if (!this.isWaitingForResponse) return;
|
|
2062
|
+
|
|
2063
|
+
const detectedStatusBeforeEval = this.runDetectStatus(this.recentOutputBuffer);
|
|
2064
|
+
this.recordTrace('response_timeout_check', {
|
|
2065
|
+
timeoutMs,
|
|
2066
|
+
detectedStatus: detectedStatusBeforeEval,
|
|
2067
|
+
currentStatus: this.currentStatus,
|
|
2068
|
+
isWaitingForResponse: this.isWaitingForResponse,
|
|
2069
|
+
hasActionableApproval: this.hasActionableApproval(),
|
|
2070
|
+
...buildCliTraceParseSnapshot({
|
|
2071
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
2072
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
2073
|
+
responseBuffer: this.responseBuffer,
|
|
2074
|
+
partialResponse: this.responseBuffer,
|
|
2075
|
+
scope: this.currentTurnScope,
|
|
2076
|
+
}),
|
|
2077
|
+
});
|
|
2078
|
+
|
|
2079
|
+
// maxResponse is a watchdog/checkpoint, not a completion signal. The old
|
|
2080
|
+
// behavior called finishResponse() unconditionally at the default 300s,
|
|
2081
|
+
// which fabricated idle transitions and downstream generating_completed
|
|
2082
|
+
// notifications while long-running CLIs were still generating. Re-run the
|
|
2083
|
+
// normal settled parser instead and keep the turn open unless the provider
|
|
2084
|
+
// actually reports an idle, commit-ready state.
|
|
2085
|
+
this.settledBuffer = this.recentOutputBuffer;
|
|
2086
|
+
this.evaluateSettled();
|
|
2087
|
+
|
|
2088
|
+
if (this.isWaitingForResponse && !this.hasActionableApproval()) {
|
|
2089
|
+
const detectedStatusAfterEval = this.runDetectStatus(this.recentOutputBuffer);
|
|
2090
|
+
this.recordTrace('response_timeout_kept_open', {
|
|
2091
|
+
timeoutMs,
|
|
2092
|
+
detectedStatusBeforeEval,
|
|
2093
|
+
detectedStatusAfterEval,
|
|
2094
|
+
currentStatus: this.currentStatus,
|
|
2095
|
+
isWaitingForResponse: this.isWaitingForResponse,
|
|
2096
|
+
});
|
|
2097
|
+
this.armResponseTimeout();
|
|
2098
|
+
}
|
|
2099
|
+
}, timeoutMs);
|
|
2019
2100
|
}
|
|
2020
2101
|
|
|
2021
2102
|
private writeSubmitKeyForRetry(mode: string): void {
|
|
@@ -31,6 +31,7 @@ export function hydrateCliParsedMessages(
|
|
|
31
31
|
): any[] {
|
|
32
32
|
const { committedMessages, scope, lastOutputAt } = options;
|
|
33
33
|
const referenceMessages = [...committedMessages];
|
|
34
|
+
const referenceComparables = referenceMessages.map((message) => normalizeComparableMessageContent(message?.content || ''));
|
|
34
35
|
const usedReferenceIndexes = new Set<number>();
|
|
35
36
|
const now = options.now ?? Date.now();
|
|
36
37
|
|
|
@@ -43,7 +44,7 @@ export function hydrateCliParsedMessages(
|
|
|
43
44
|
sameIndex
|
|
44
45
|
&& !usedReferenceIndexes.has(parsedIndex)
|
|
45
46
|
&& sameIndex.role === role
|
|
46
|
-
&&
|
|
47
|
+
&& referenceComparables[parsedIndex] === normalizedContent
|
|
47
48
|
&& typeof sameIndex.timestamp === 'number'
|
|
48
49
|
&& Number.isFinite(sameIndex.timestamp)
|
|
49
50
|
) {
|
|
@@ -55,7 +56,7 @@ export function hydrateCliParsedMessages(
|
|
|
55
56
|
if (usedReferenceIndexes.has(i)) continue;
|
|
56
57
|
const candidate = referenceMessages[i];
|
|
57
58
|
if (!candidate || candidate.role !== role) continue;
|
|
58
|
-
const candidateContent =
|
|
59
|
+
const candidateContent = referenceComparables[i];
|
|
59
60
|
if (!candidateContent) continue;
|
|
60
61
|
const exactMatch = candidateContent === normalizedContent;
|
|
61
62
|
const fuzzyMatch = candidateContent.includes(normalizedContent) || normalizedContent.includes(candidateContent);
|
package/src/commands/handler.ts
CHANGED
|
@@ -507,7 +507,16 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
507
507
|
if (this._ctx.providerLoader) {
|
|
508
508
|
await this._ctx.providerLoader.fetchLatest().catch(() => {});
|
|
509
509
|
this._ctx.providerLoader.reload();
|
|
510
|
-
|
|
510
|
+
this._ctx.providerLoader.registerToDetector();
|
|
511
|
+
const refreshedInstances = this._ctx.instanceManager
|
|
512
|
+
? this._ctx.instanceManager.refreshProviderDefinitions((providerType) => this._ctx.providerLoader!.resolve(providerType))
|
|
513
|
+
: 0;
|
|
514
|
+
const providers = this._ctx.providerLoader.getAll().map((provider) => ({
|
|
515
|
+
type: provider.type,
|
|
516
|
+
name: provider.name,
|
|
517
|
+
category: provider.category,
|
|
518
|
+
}));
|
|
519
|
+
return { success: true, refreshedInstances, providers };
|
|
511
520
|
}
|
|
512
521
|
return { success: false, error: 'ProviderLoader not initialized' };
|
|
513
522
|
}
|
|
@@ -161,6 +161,52 @@ function killPid(pid: number): boolean {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
function getWindowsProcessCommandLine(pid: number): string | null {
|
|
165
|
+
const pidFilter = `ProcessId=${pid}`;
|
|
166
|
+
try {
|
|
167
|
+
const psOut = execFileSync('powershell.exe', [
|
|
168
|
+
'-NoProfile',
|
|
169
|
+
'-NonInteractive',
|
|
170
|
+
'-ExecutionPolicy', 'Bypass',
|
|
171
|
+
'-Command',
|
|
172
|
+
`(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`,
|
|
173
|
+
], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
174
|
+
if (psOut) return psOut;
|
|
175
|
+
} catch {
|
|
176
|
+
// fall through to wmic fallback
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const wmicOut = execFileSync('wmic', [
|
|
181
|
+
'process', 'where', pidFilter, 'get', 'CommandLine',
|
|
182
|
+
], { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
183
|
+
if (wmicOut) return wmicOut;
|
|
184
|
+
} catch {
|
|
185
|
+
// noop
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getProcessCommandLine(pid: number): string | null {
|
|
191
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
192
|
+
if (process.platform === 'win32') return getWindowsProcessCommandLine(pid);
|
|
193
|
+
try {
|
|
194
|
+
const text = execFileSync('ps', ['-o', 'command=', '-p', String(pid)], {
|
|
195
|
+
encoding: 'utf8',
|
|
196
|
+
timeout: 3000,
|
|
197
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
198
|
+
}).trim();
|
|
199
|
+
return text || null;
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isManagedSessionHostPid(pid: number): boolean {
|
|
206
|
+
const commandLine = getProcessCommandLine(pid);
|
|
207
|
+
return !!commandLine && /session-host-daemon/i.test(commandLine);
|
|
208
|
+
}
|
|
209
|
+
|
|
164
210
|
async function waitForPidExit(pid: number, timeoutMs: number): Promise<void> {
|
|
165
211
|
const start = Date.now();
|
|
166
212
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -173,12 +219,12 @@ async function waitForPidExit(pid: number, timeoutMs: number): Promise<void> {
|
|
|
173
219
|
}
|
|
174
220
|
}
|
|
175
221
|
|
|
176
|
-
function stopSessionHostProcesses(appName: string): void {
|
|
222
|
+
export function stopSessionHostProcesses(appName: string): void {
|
|
177
223
|
const pidFile = path.join(os.homedir(), '.adhdev', `${appName}-session-host.pid`);
|
|
178
224
|
try {
|
|
179
225
|
if (fs.existsSync(pidFile)) {
|
|
180
226
|
const pid = Number.parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
181
|
-
if (Number.isFinite(pid)) {
|
|
227
|
+
if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid(pid)) {
|
|
182
228
|
killPid(pid);
|
|
183
229
|
}
|
|
184
230
|
}
|
|
@@ -191,20 +237,6 @@ function stopSessionHostProcesses(appName: string): void {
|
|
|
191
237
|
// noop
|
|
192
238
|
}
|
|
193
239
|
}
|
|
194
|
-
|
|
195
|
-
if (process.platform !== 'win32') {
|
|
196
|
-
try {
|
|
197
|
-
const raw = execFileSync('pgrep', ['-f', 'session-host-daemon'], { encoding: 'utf8' }).trim();
|
|
198
|
-
for (const line of raw.split('\n')) {
|
|
199
|
-
const pid = Number.parseInt(line.trim(), 10);
|
|
200
|
-
if (Number.isFinite(pid)) {
|
|
201
|
-
killPid(pid);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
} catch {
|
|
205
|
-
// noop
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
function removeDaemonPidFile(): void {
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -507,20 +507,9 @@ export class DevServer implements DevServerContext {
|
|
|
507
507
|
private async handleReload(_req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
508
508
|
try {
|
|
509
509
|
this.providerLoader.reload();
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const instance = this.instanceManager.getInstance(id) as any;
|
|
514
|
-
const providerType = typeof instance?.type === 'string' ? instance.type : '';
|
|
515
|
-
if (!providerType) continue;
|
|
516
|
-
const resolved = this.providerLoader.resolve(providerType);
|
|
517
|
-
if (!resolved) continue;
|
|
518
|
-
if (instance && typeof instance === 'object' && 'provider' in instance) {
|
|
519
|
-
instance.provider = resolved;
|
|
520
|
-
refreshedInstances += 1;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
510
|
+
const refreshedInstances = this.instanceManager
|
|
511
|
+
? this.instanceManager.refreshProviderDefinitions((providerType) => this.providerLoader.resolve(providerType))
|
|
512
|
+
: 0;
|
|
524
513
|
const providers = this.providerLoader.getAll().map(p => ({
|
|
525
514
|
type: p.type, name: p.name, category: p.category,
|
|
526
515
|
}));
|
|
@@ -12,7 +12,7 @@ import * as fs from 'fs';
|
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import { normalizeInputEnvelope, type ProviderModule, flattenContent } from './contracts.js';
|
|
14
14
|
import { assertTextOnlyInput } from './provider-input-support.js';
|
|
15
|
-
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, ProviderErrorReason } from './provider-instance.js';
|
|
15
|
+
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, ProviderErrorReason, HotChatSessionState } from './provider-instance.js';
|
|
16
16
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
17
17
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
18
18
|
import type { PtyRuntimeMetadata, PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
@@ -219,6 +219,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
219
219
|
this.historyWriter = new ChatHistoryWriter();
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
refreshProviderDefinition(provider: ProviderModule): void {
|
|
223
|
+
if (provider.type !== this.type || provider.category !== 'cli') return;
|
|
224
|
+
this.provider = provider;
|
|
225
|
+
this.adapter.refreshProviderDefinition(provider as CliProviderModule);
|
|
226
|
+
}
|
|
227
|
+
|
|
222
228
|
// ─── Lifecycle ─────────────────────────────────
|
|
223
229
|
|
|
224
230
|
async init(context: InstanceContext): Promise<void> {
|
|
@@ -487,6 +493,25 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
487
493
|
return this.presentationMode;
|
|
488
494
|
}
|
|
489
495
|
|
|
496
|
+
getHotChatSessionState(): HotChatSessionState {
|
|
497
|
+
const adapterStatus = this.adapter.getStatus();
|
|
498
|
+
const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
499
|
+
const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
|
|
500
|
+
const runtime = this.adapter.getRuntimeMetadata();
|
|
501
|
+
const lastCommittedMessageActivityAt = typeof this.adapter.getLastCommittedMessageActivityAt === 'function'
|
|
502
|
+
? this.adapter.getLastCommittedMessageActivityAt()
|
|
503
|
+
: 0;
|
|
504
|
+
return {
|
|
505
|
+
id: this.instanceId,
|
|
506
|
+
status: visibleStatus,
|
|
507
|
+
lastMessageAt: lastCommittedMessageActivityAt || undefined,
|
|
508
|
+
runtimeLifecycle: runtime?.lifecycle ?? null,
|
|
509
|
+
runtimeSurfaceKind: runtime?.surfaceKind,
|
|
510
|
+
runtimeRestoredFromStorage: runtime?.restoredFromStorage === true,
|
|
511
|
+
runtimeRecoveryState: runtime?.recoveryState ?? null,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
490
515
|
updateSettings(newSettings: Record<string, any>): void {
|
|
491
516
|
this.settings = { ...newSettings };
|
|
492
517
|
this.adapter.updateRuntimeSettings?.(this.settings);
|
|
@@ -650,6 +675,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
650
675
|
this.completedDebouncePending = { chatTitle, duration, timestamp: now };
|
|
651
676
|
this.completedDebounceTimer = setTimeout(() => {
|
|
652
677
|
if (this.completedDebouncePending) {
|
|
678
|
+
const latestStatus = this.adapter.getStatus();
|
|
679
|
+
const latestAutoApproveActive = latestStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
680
|
+
const latestVisibleStatus = latestAutoApproveActive ? 'generating' : latestStatus.status;
|
|
681
|
+
if (latestVisibleStatus !== 'idle') {
|
|
682
|
+
LOG.info('CLI', `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
683
|
+
this.completedDebouncePending = null;
|
|
684
|
+
this.completedDebounceTimer = null;
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
653
687
|
LOG.info('CLI', `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
|
|
654
688
|
this.pushEvent({ event: 'agent:generating_completed', ...this.completedDebouncePending });
|
|
655
689
|
this.completedDebouncePending = null;
|
|
@@ -8,9 +8,28 @@
|
|
|
8
8
|
* 4. Event collection and propagation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
11
|
+
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, HotChatSessionState } from './provider-instance.js';
|
|
12
12
|
import { LOG } from '../logging/logger.js';
|
|
13
13
|
|
|
14
|
+
function projectHotChatSessionStatesFromProviderState(state: ProviderState): HotChatSessionState[] {
|
|
15
|
+
const project = (item: ProviderState): HotChatSessionState => ({
|
|
16
|
+
id: item.instanceId,
|
|
17
|
+
status: item.activeChat?.status || item.status,
|
|
18
|
+
unread: (item as any).unread,
|
|
19
|
+
inboxBucket: (item as any).inboxBucket,
|
|
20
|
+
lastMessageAt: (item as any).lastMessageAt ?? (item.activeChat as any)?.lastMessageAt,
|
|
21
|
+
runtimeLifecycle: item.runtime?.lifecycle ?? null,
|
|
22
|
+
runtimeSurfaceKind: item.runtime?.surfaceKind,
|
|
23
|
+
runtimeRestoredFromStorage: item.runtime?.restoredFromStorage === true,
|
|
24
|
+
runtimeRecoveryState: item.runtime?.recoveryState ?? null,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (state.category === 'ide') {
|
|
28
|
+
return [project(state), ...state.extensions.map(project)];
|
|
29
|
+
}
|
|
30
|
+
return [project(state)];
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
export class ProviderInstanceManager {
|
|
15
34
|
private instances = new Map<string, ProviderInstance>();
|
|
16
35
|
private tickTimer: NodeJS.Timeout | null = null;
|
|
@@ -120,6 +139,32 @@ export class ProviderInstanceManager {
|
|
|
120
139
|
return states;
|
|
121
140
|
}
|
|
122
141
|
|
|
142
|
+
collectHotChatSessionStates(): HotChatSessionState[] {
|
|
143
|
+
const sessions: HotChatSessionState[] = [];
|
|
144
|
+
for (const [id, instance] of this.instances) {
|
|
145
|
+
try {
|
|
146
|
+
const projected = instance.getHotChatSessionState?.();
|
|
147
|
+
if (Array.isArray(projected)) {
|
|
148
|
+
sessions.push(...projected.filter((session): session is HotChatSessionState => !!session?.id));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (projected?.id) {
|
|
152
|
+
sessions.push(projected);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Fallback for provider types that have not implemented the cheap
|
|
157
|
+
// projection yet. CLI implements getHotChatSessionState() because
|
|
158
|
+
// its full getState() may run rich transcript parsing.
|
|
159
|
+
const state = instance.getState();
|
|
160
|
+
sessions.push(...projectHotChatSessionStatesFromProviderState(state));
|
|
161
|
+
} catch (e) {
|
|
162
|
+
LOG.warn('InstanceMgr', `[InstanceManager] Failed to collect hot chat metadata from ${id}: ${(e as Error).message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return sessions;
|
|
166
|
+
}
|
|
167
|
+
|
|
123
168
|
/**
|
|
124
169
|
* Per-category status collect
|
|
125
170
|
*/
|
|
@@ -216,6 +261,18 @@ export class ProviderInstanceManager {
|
|
|
216
261
|
return updated;
|
|
217
262
|
}
|
|
218
263
|
|
|
264
|
+
refreshProviderDefinitions(resolveProvider: (providerType: string) => unknown): number {
|
|
265
|
+
let refreshed = 0;
|
|
266
|
+
for (const instance of this.instances.values()) {
|
|
267
|
+
if (typeof instance.refreshProviderDefinition !== 'function') continue;
|
|
268
|
+
const provider = resolveProvider(instance.type);
|
|
269
|
+
if (!provider || typeof provider !== 'object') continue;
|
|
270
|
+
instance.refreshProviderDefinition(provider as any);
|
|
271
|
+
refreshed += 1;
|
|
272
|
+
}
|
|
273
|
+
return refreshed;
|
|
274
|
+
}
|
|
275
|
+
|
|
219
276
|
// ─── cleanup ──────────────────────────────────────
|
|
220
277
|
|
|
221
278
|
/**
|
|
@@ -135,6 +135,18 @@ export interface ProviderEvent {
|
|
|
135
135
|
[key: string]: any;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
export interface HotChatSessionState {
|
|
139
|
+
id: string;
|
|
140
|
+
status?: unknown;
|
|
141
|
+
unread?: unknown;
|
|
142
|
+
inboxBucket?: unknown;
|
|
143
|
+
lastMessageAt?: unknown;
|
|
144
|
+
runtimeLifecycle?: unknown;
|
|
145
|
+
runtimeSurfaceKind?: unknown;
|
|
146
|
+
runtimeRestoredFromStorage?: unknown;
|
|
147
|
+
runtimeRecoveryState?: unknown;
|
|
148
|
+
}
|
|
149
|
+
|
|
138
150
|
// ─── ProviderInstance interface ─────────────────
|
|
139
151
|
|
|
140
152
|
export interface InstanceContext {
|
|
@@ -172,12 +184,22 @@ export interface ProviderInstance {
|
|
|
172
184
|
/** Return current status */
|
|
173
185
|
getState(): ProviderState;
|
|
174
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Return the cheap session metadata needed to decide whether chat-tail
|
|
189
|
+
* subscriptions should be flushed. Implementations must avoid rich transcript
|
|
190
|
+
* parsing here; callers use this on P2P hot flush paths.
|
|
191
|
+
*/
|
|
192
|
+
getHotChatSessionState?(): HotChatSessionState | HotChatSessionState[] | null;
|
|
193
|
+
|
|
175
194
|
/** Receive event (external → Instance) */
|
|
176
195
|
onEvent(event: string, data?: any): void;
|
|
177
196
|
|
|
178
197
|
/** Update settings at runtime (called when user changes settings from dashboard) */
|
|
179
198
|
updateSettings?(newSettings: Record<string, any>): void;
|
|
180
199
|
|
|
200
|
+
/** Refresh static provider definition/scripts without restarting the live runtime. */
|
|
201
|
+
refreshProviderDefinition?(provider: ProviderModule): void;
|
|
202
|
+
|
|
181
203
|
/** cleanup */
|
|
182
204
|
dispose(): void;
|
|
183
205
|
}
|