@adhdev/daemon-core 0.8.81 → 0.8.83
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-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/dist/config/recent-activity.d.ts +14 -0
- package/dist/config/state-store.d.ts +4 -0
- package/dist/index.js +363 -117
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +363 -117
- package/dist/index.mjs.map +1 -1
- package/dist/providers/acp-provider-instance.d.ts +0 -2
- package/dist/providers/cli-provider-instance.d.ts +2 -0
- package/dist/providers/provider-instance.d.ts +1 -1
- package/dist/shared-types.d.ts +3 -1
- package/dist/status/chat-tail-hot-sessions.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapter-types.d.ts +2 -0
- package/src/cli-adapters/provider-cli-adapter.ts +107 -30
- package/src/cli-adapters/provider-cli-shared.d.ts +3 -4
- package/src/cli-adapters/provider-cli-shared.ts +2 -0
- package/src/cli-adapters/terminal-screen.ts +6 -4
- package/src/commands/chat-commands.ts +8 -3
- package/src/commands/router.ts +59 -1
- package/src/config/recent-activity.ts +122 -0
- package/src/config/state-store.ts +16 -0
- package/src/logging/command-log.ts +2 -0
- package/src/providers/acp-provider-instance.ts +2 -17
- package/src/providers/cli-provider-instance.ts +33 -10
- package/src/providers/extension-provider-instance.ts +0 -2
- package/src/providers/ide-provider-instance.ts +0 -2
- package/src/providers/provider-instance.d.ts +1 -1
- package/src/providers/provider-instance.ts +1 -0
- package/src/shared-types.d.ts +3 -0
- package/src/shared-types.ts +5 -1
- package/src/status/chat-tail-hot-sessions.ts +15 -1
- package/src/status/snapshot.ts +20 -3
|
@@ -91,8 +91,6 @@ export declare class AcpProviderInstance implements ProviderInstance {
|
|
|
91
91
|
private handleLegacyUpdate;
|
|
92
92
|
/** Map SDK ToolCallStatus to internal status */
|
|
93
93
|
private mapToolCallStatus;
|
|
94
|
-
/** Truncate content for transport (text: 2000 chars, images preserved) */
|
|
95
|
-
private truncateContent;
|
|
96
94
|
/** Build ContentBlock[] from current partial state */
|
|
97
95
|
private buildPartialBlocks;
|
|
98
96
|
private buildPartialThoughtMessage;
|
|
@@ -41,6 +41,8 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
41
41
|
private runtimeMessages;
|
|
42
42
|
readonly instanceId: string;
|
|
43
43
|
private suppressIdleHistoryReplay;
|
|
44
|
+
private errorMessage;
|
|
45
|
+
private errorReason;
|
|
44
46
|
private presentationMode;
|
|
45
47
|
private providerSessionId?;
|
|
46
48
|
private launchMode;
|
|
@@ -44,7 +44,7 @@ export interface ActiveChatData {
|
|
|
44
44
|
inputContent?: string;
|
|
45
45
|
}
|
|
46
46
|
/** Standardized error reasons across all provider categories */
|
|
47
|
-
export type ProviderErrorReason = 'not_installed' | 'auth_failed' | 'spawn_error' | 'init_failed' | 'crash' | 'timeout' | 'cdp_error' | 'disconnected';
|
|
47
|
+
export type ProviderErrorReason = 'not_installed' | 'auth_failed' | 'spawn_error' | 'init_failed' | 'parse_error' | 'crash' | 'timeout' | 'cdp_error' | 'disconnected';
|
|
48
48
|
/** Common fields shared by all provider categories */
|
|
49
49
|
interface ProviderStateBase {
|
|
50
50
|
/** Provider type (e.g. 'gemini-cli', 'cursor', 'cline') */
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -225,7 +225,7 @@ export type UnsubscribeRequest = {
|
|
|
225
225
|
export type StandaloneWsStatusPayload = StatusReportPayload;
|
|
226
226
|
export type SessionTransport = 'cdp-page' | 'cdp-webview' | 'pty' | 'acp';
|
|
227
227
|
export type SessionKind = 'workspace' | 'agent';
|
|
228
|
-
export type SessionCapability = 'read_chat' | 'send_message' | 'new_session' | 'list_sessions' | 'switch_session' | 'resolve_action' | 'open_panel' | 'terminal_io' | 'resize_terminal' | 'change_model' | 'set_mode' | 'set_thought_level';
|
|
228
|
+
export type SessionCapability = 'read_chat' | 'send_message' | 'new_session' | 'list_sessions' | 'switch_session' | 'resolve_action' | 'open_panel' | 'terminal_io' | 'resize_terminal' | 'change_model' | 'set_mode' | 'set_thought_level' | 'delete_notification' | 'mark_notification_unread';
|
|
229
229
|
import type { RuntimeWriteOwner, RuntimeAttachedClient, SessionStatus } from './shared-types-extra.js';
|
|
230
230
|
export type { RuntimeWriteOwner, RuntimeAttachedClient, SessionStatus } from './shared-types-extra.js';
|
|
231
231
|
export interface SessionEntry {
|
|
@@ -270,6 +270,8 @@ export interface SessionEntry {
|
|
|
270
270
|
unread?: boolean;
|
|
271
271
|
lastSeenAt?: number;
|
|
272
272
|
inboxBucket?: RecentSessionBucket;
|
|
273
|
+
completionMarker?: string;
|
|
274
|
+
seenCompletionMarker?: string;
|
|
273
275
|
surfaceHidden?: boolean;
|
|
274
276
|
}
|
|
275
277
|
/**
|
|
@@ -3,6 +3,8 @@ export declare const DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS = 8000;
|
|
|
3
3
|
export interface HotChatSessionLike {
|
|
4
4
|
id?: string | null;
|
|
5
5
|
status?: unknown;
|
|
6
|
+
unread?: unknown;
|
|
7
|
+
inboxBucket?: unknown;
|
|
6
8
|
lastMessageAt?: unknown;
|
|
7
9
|
runtimeLifecycle?: unknown;
|
|
8
10
|
runtimeSurfaceKind?: unknown;
|
|
@@ -59,6 +59,7 @@ export interface RecentReadDebugSnapshot {
|
|
|
59
59
|
}
|
|
60
60
|
export declare function shouldEmitRecentReadDebugLog(cache: Map<string, string>, snapshot: RecentReadDebugSnapshot): boolean;
|
|
61
61
|
export declare function buildMachineInfo(profile?: 'full' | 'live' | 'metadata'): MachineInfo;
|
|
62
|
+
export { getSessionCurrentNotificationId, applySessionNotificationOverlay } from '../config/recent-activity.js';
|
|
62
63
|
export declare function getSessionCompletionMarker(session: {
|
|
63
64
|
activeChat?: {
|
|
64
65
|
messages?: Array<{
|
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import * as os from 'os';
|
|
18
18
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
19
19
|
import { LOG } from '../logging/logger.js';
|
|
20
|
+
import { getDebugRuntimeConfig } from '../logging/debug-config.js';
|
|
20
21
|
import { TerminalScreen } from './terminal-screen.js';
|
|
21
22
|
import {
|
|
22
23
|
NodePtyTransportFactory,
|
|
@@ -113,6 +114,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
113
114
|
private recentOutputBuffer = '';
|
|
114
115
|
private isWaitingForResponse = false;
|
|
115
116
|
private activeModal: { message: string; buttons: string[] } | null = null;
|
|
117
|
+
private parseErrorMessage: string | null = null;
|
|
116
118
|
private responseTimeout: NodeJS.Timeout | null = null;
|
|
117
119
|
private idleTimeout: NodeJS.Timeout | null = null;
|
|
118
120
|
private ready = false;
|
|
@@ -181,8 +183,23 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
181
183
|
private currentTurnScope: TurnParseScope | null = null;
|
|
182
184
|
private traceEntries: CliTraceEntry[] = [];
|
|
183
185
|
private traceSeq = 0;
|
|
184
|
-
private traceSessionId =
|
|
186
|
+
private traceSessionId = '';
|
|
187
|
+
private parsedStatusCache: {
|
|
188
|
+
committedMessagesRef: CliChatMessage[];
|
|
189
|
+
responseBuffer: string;
|
|
190
|
+
currentTurnScope: TurnParseScope | null;
|
|
191
|
+
recentOutputBuffer: string;
|
|
192
|
+
accumulatedBuffer: string;
|
|
193
|
+
accumulatedRawBuffer: string;
|
|
194
|
+
screenText: string;
|
|
195
|
+
currentStatus: CliSessionStatus['status'];
|
|
196
|
+
activeModal: { message: string; buttons: string[] } | null;
|
|
197
|
+
cliName: string;
|
|
198
|
+
lastOutputAt: number;
|
|
199
|
+
result: any;
|
|
200
|
+
} | null = null;
|
|
185
201
|
private static readonly MAX_TRACE_ENTRIES = 250;
|
|
202
|
+
|
|
186
203
|
private readonly providerResolutionMeta: ProviderResolutionMeta;
|
|
187
204
|
private static readonly FINISH_RETRY_DELAY_MS = 300;
|
|
188
205
|
private static readonly MAX_FINISH_RETRIES = 2;
|
|
@@ -322,7 +339,19 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
322
339
|
`[${this.cliType}] Provider resolution: providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'} source=${this.providerResolutionMeta.scriptsSource || '-'} version=${this.providerResolutionMeta.resolvedVersion || '-'}`
|
|
323
340
|
);
|
|
324
341
|
} else {
|
|
325
|
-
|
|
342
|
+
const resolutionSummary = `providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'} source=${this.providerResolutionMeta.scriptsSource || '-'} version=${this.providerResolutionMeta.resolvedVersion || '-'}`;
|
|
343
|
+
const hasResolvedProviderScripts = Boolean(
|
|
344
|
+
this.providerResolutionMeta.providerDir
|
|
345
|
+
|| this.providerResolutionMeta.scriptDir
|
|
346
|
+
|| this.providerResolutionMeta.scriptsPath
|
|
347
|
+
|| this.providerResolutionMeta.scriptsSource
|
|
348
|
+
|| this.providerResolutionMeta.resolvedVersion,
|
|
349
|
+
);
|
|
350
|
+
if (hasResolvedProviderScripts) {
|
|
351
|
+
LOG.warn('CLI', `[${this.cliType}] ⚠ No CLI scripts loaded! Provider needs scripts/{version}/scripts.js (${resolutionSummary})`);
|
|
352
|
+
} else {
|
|
353
|
+
LOG.info('CLI', `[${this.cliType}] CLI scripts not yet resolved (${resolutionSummary})`);
|
|
354
|
+
}
|
|
326
355
|
}
|
|
327
356
|
}
|
|
328
357
|
|
|
@@ -484,7 +513,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
484
513
|
this.terminalScreen.write(rawData);
|
|
485
514
|
const cleanData = sanitizeTerminalText(rawData);
|
|
486
515
|
const now = Date.now();
|
|
487
|
-
const
|
|
516
|
+
const screenText = this.terminalScreen.getText();
|
|
517
|
+
const normalizedScreenSnapshot = normalizeScreenSnapshot(screenText);
|
|
488
518
|
this.lastOutputAt = now;
|
|
489
519
|
if (cleanData.trim()) this.lastNonEmptyOutputAt = now;
|
|
490
520
|
if (normalizedScreenSnapshot !== this.lastScreenSnapshot) {
|
|
@@ -497,13 +527,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
497
527
|
if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
|
|
498
528
|
this.clearIdleFinishCandidate('new_output');
|
|
499
529
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
530
|
+
if (getDebugRuntimeConfig().collectDebugTrace) {
|
|
531
|
+
this.recordTrace('output', {
|
|
532
|
+
rawLength: rawData.length,
|
|
533
|
+
cleanLength: cleanData.length,
|
|
534
|
+
rawPreview: summarizeCliTraceText(rawData, 300),
|
|
535
|
+
cleanPreview: summarizeCliTraceText(cleanData, 300),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
507
538
|
|
|
508
539
|
if (this.startupParseGate) {
|
|
509
540
|
this.scheduleStartupSettleCheck();
|
|
@@ -1267,10 +1298,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1267
1298
|
|
|
1268
1299
|
getStatus(): CliSessionStatus {
|
|
1269
1300
|
return {
|
|
1270
|
-
status: this.currentStatus,
|
|
1301
|
+
status: this.parseErrorMessage ? 'error' : this.currentStatus,
|
|
1271
1302
|
messages: [...this.committedMessages],
|
|
1272
1303
|
workingDir: this.workingDir,
|
|
1273
1304
|
activeModal: this.activeModal,
|
|
1305
|
+
errorMessage: this.parseErrorMessage || undefined,
|
|
1306
|
+
errorReason: this.parseErrorMessage ? 'parse_error' : undefined,
|
|
1274
1307
|
};
|
|
1275
1308
|
}
|
|
1276
1309
|
|
|
@@ -1301,15 +1334,36 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1301
1334
|
* Called by command handler / dashboard for rich content rendering.
|
|
1302
1335
|
*/
|
|
1303
1336
|
getScriptParsedStatus(): any {
|
|
1337
|
+
const screenText = this.terminalScreen.getText();
|
|
1338
|
+
const cached = this.parsedStatusCache;
|
|
1339
|
+
if (
|
|
1340
|
+
cached
|
|
1341
|
+
&& cached.committedMessagesRef === this.committedMessages
|
|
1342
|
+
&& cached.responseBuffer === this.responseBuffer
|
|
1343
|
+
&& cached.currentTurnScope === this.currentTurnScope
|
|
1344
|
+
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
1345
|
+
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
1346
|
+
&& cached.accumulatedRawBuffer === this.accumulatedRawBuffer
|
|
1347
|
+
&& cached.screenText === screenText
|
|
1348
|
+
&& cached.currentStatus === this.currentStatus
|
|
1349
|
+
&& cached.activeModal === this.activeModal
|
|
1350
|
+
&& cached.cliName === this.cliName
|
|
1351
|
+
&& cached.lastOutputAt === this.lastOutputAt
|
|
1352
|
+
) {
|
|
1353
|
+
return cached.result;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1304
1356
|
const parsed = this.parseCurrentTranscript(
|
|
1305
1357
|
this.committedMessages,
|
|
1306
1358
|
this.responseBuffer,
|
|
1307
1359
|
this.currentTurnScope,
|
|
1360
|
+
screenText,
|
|
1308
1361
|
);
|
|
1309
1362
|
const shouldPreferCommittedMessages =
|
|
1310
1363
|
!this.currentTurnScope
|
|
1311
1364
|
&& this.currentStatus === 'idle'
|
|
1312
1365
|
&& !this.activeModal;
|
|
1366
|
+
let result: any;
|
|
1313
1367
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
1314
1368
|
const hydratedMessages = shouldPreferCommittedMessages
|
|
1315
1369
|
? this.committedMessages.map((message, index) => buildChatMessage({
|
|
@@ -1325,7 +1379,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1325
1379
|
scope: this.currentTurnScope,
|
|
1326
1380
|
lastOutputAt: this.lastOutputAt,
|
|
1327
1381
|
});
|
|
1328
|
-
|
|
1382
|
+
result = {
|
|
1329
1383
|
id: parsed.id || 'cli_session',
|
|
1330
1384
|
status: parsed.status || this.currentStatus,
|
|
1331
1385
|
title: parsed.title || this.cliName,
|
|
@@ -1333,23 +1387,39 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1333
1387
|
activeModal: parsed.activeModal ?? this.activeModal,
|
|
1334
1388
|
providerSessionId: typeof parsed.providerSessionId === 'string' ? parsed.providerSessionId : undefined,
|
|
1335
1389
|
};
|
|
1390
|
+
} else {
|
|
1391
|
+
const messages = [...this.committedMessages];
|
|
1392
|
+
result = {
|
|
1393
|
+
id: 'cli_session',
|
|
1394
|
+
status: this.currentStatus,
|
|
1395
|
+
title: this.cliName,
|
|
1396
|
+
messages: messages.map((message, index) => buildChatMessage({
|
|
1397
|
+
...message,
|
|
1398
|
+
id: message.id || `msg_${index}`,
|
|
1399
|
+
index: typeof message.index === 'number' ? message.index : index,
|
|
1400
|
+
receivedAt: typeof message.receivedAt === 'number'
|
|
1401
|
+
? message.receivedAt
|
|
1402
|
+
: message.timestamp,
|
|
1403
|
+
})),
|
|
1404
|
+
activeModal: this.activeModal,
|
|
1405
|
+
};
|
|
1336
1406
|
}
|
|
1337
1407
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
receivedAt: typeof message.receivedAt === 'number'
|
|
1348
|
-
? message.receivedAt
|
|
1349
|
-
: message.timestamp,
|
|
1350
|
-
})),
|
|
1408
|
+
this.parsedStatusCache = {
|
|
1409
|
+
committedMessagesRef: this.committedMessages,
|
|
1410
|
+
responseBuffer: this.responseBuffer,
|
|
1411
|
+
currentTurnScope: this.currentTurnScope,
|
|
1412
|
+
recentOutputBuffer: this.recentOutputBuffer,
|
|
1413
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1414
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1415
|
+
screenText,
|
|
1416
|
+
currentStatus: this.currentStatus,
|
|
1351
1417
|
activeModal: this.activeModal,
|
|
1418
|
+
cliName: this.cliName,
|
|
1419
|
+
lastOutputAt: this.lastOutputAt,
|
|
1420
|
+
result,
|
|
1352
1421
|
};
|
|
1422
|
+
return result;
|
|
1353
1423
|
}
|
|
1354
1424
|
|
|
1355
1425
|
async invokeScript(scriptName: string, args?: Record<string, any>): Promise<any> {
|
|
@@ -1374,14 +1444,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1374
1444
|
}));
|
|
1375
1445
|
}
|
|
1376
1446
|
|
|
1377
|
-
private parseCurrentTranscript(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): any {
|
|
1378
|
-
if (!this.cliScripts?.parseOutput)
|
|
1447
|
+
private parseCurrentTranscript(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null, screenTextOverride?: string): any {
|
|
1448
|
+
if (!this.cliScripts?.parseOutput) {
|
|
1449
|
+
this.parseErrorMessage = null;
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1379
1452
|
try {
|
|
1453
|
+
const screenText = typeof screenTextOverride === 'string' ? screenTextOverride : this.terminalScreen.getText();
|
|
1380
1454
|
const input = buildCliParseInput({
|
|
1381
1455
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
1382
1456
|
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1383
1457
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1384
|
-
terminalScreenText:
|
|
1458
|
+
terminalScreenText: screenText,
|
|
1385
1459
|
baseMessages,
|
|
1386
1460
|
partialResponse,
|
|
1387
1461
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
@@ -1403,10 +1477,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1403
1477
|
lastAssistant.content = trimPromptEchoPrefix(lastAssistant.content, promptForTrim);
|
|
1404
1478
|
}
|
|
1405
1479
|
}
|
|
1480
|
+
this.parseErrorMessage = null;
|
|
1406
1481
|
return parsed;
|
|
1407
1482
|
} catch (e: any) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1483
|
+
const message = e?.message || String(e);
|
|
1484
|
+
this.parseErrorMessage = message;
|
|
1485
|
+
LOG.warn('CLI', `[${this.cliType}] parseOutput error: ${message}`);
|
|
1486
|
+
throw e;
|
|
1410
1487
|
}
|
|
1411
1488
|
}
|
|
1412
1489
|
|
|
@@ -15,10 +15,9 @@ export interface CliSessionStatus {
|
|
|
15
15
|
status: 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
|
|
16
16
|
messages: CliChatMessage[];
|
|
17
17
|
workingDir: string;
|
|
18
|
-
activeModal: {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} | null;
|
|
18
|
+
activeModal: { message: string; buttons: string[] } | null;
|
|
19
|
+
errorMessage?: string;
|
|
20
|
+
errorReason?: string;
|
|
22
21
|
}
|
|
23
22
|
export interface CliScripts {
|
|
24
23
|
parseOutput?: (input: CliScriptInput) => any;
|
|
@@ -67,10 +67,12 @@ function logTerminalBackendSelection(
|
|
|
67
67
|
if (loggedTerminalBackends.has(key)) return;
|
|
68
68
|
loggedTerminalBackends.add(key);
|
|
69
69
|
if (backendKind === 'xterm' && preference !== 'xterm' && !ghosttyAvailable) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const message = `[terminal-screen] ghostty-vt unavailable; using xterm fallback (preference=${preference})`;
|
|
71
|
+
if (preference === 'auto') {
|
|
72
|
+
LOG.info('Terminal', message);
|
|
73
|
+
} else {
|
|
74
|
+
LOG.warn('Terminal', message);
|
|
75
|
+
}
|
|
74
76
|
return;
|
|
75
77
|
}
|
|
76
78
|
LOG.info(
|
|
@@ -469,9 +469,14 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
469
469
|
const adapter = getTargetedCliAdapter(h, args, provider?.type);
|
|
470
470
|
if (adapter) {
|
|
471
471
|
_log(`${transport} adapter: ${adapter.cliType}`);
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
472
|
+
let parsedStatus: any = null;
|
|
473
|
+
if (typeof adapter.getScriptParsedStatus === 'function') {
|
|
474
|
+
try {
|
|
475
|
+
parsedStatus = parseMaybeJson(adapter.getScriptParsedStatus());
|
|
476
|
+
} catch (error: any) {
|
|
477
|
+
return { success: false, error: error?.message || String(error) };
|
|
478
|
+
}
|
|
479
|
+
}
|
|
475
480
|
const parsedRecord = parsedStatus && typeof parsedStatus === 'object'
|
|
476
481
|
? parsedStatus as Record<string, any>
|
|
477
482
|
: null;
|
package/src/commands/router.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { launchWithCdp, killIdeProcess, isIdeRunning } from '../launch.js';
|
|
|
21
21
|
import { loadConfig, saveConfig, updateConfig } from '../config/config.js';
|
|
22
22
|
import { loadState, saveState } from '../config/state-store.js';
|
|
23
23
|
import { resolveIdeLaunchWorkspace } from '../config/workspaces.js';
|
|
24
|
-
import { appendRecentActivity, getRecentActivity, markSessionSeen } from '../config/recent-activity.js';
|
|
24
|
+
import { appendRecentActivity, getRecentActivity, markSessionSeen, dismissSessionNotification, markSessionNotificationUnread } from '../config/recent-activity.js';
|
|
25
25
|
import { getSavedProviderSessions } from '../config/saved-sessions.js';
|
|
26
26
|
import { listSavedHistorySessions } from '../config/chat-history.js';
|
|
27
27
|
import { detectIDEs } from '../detection/ide-detector.js';
|
|
@@ -733,6 +733,64 @@ export class DaemonCommandRouter {
|
|
|
733
733
|
};
|
|
734
734
|
}
|
|
735
735
|
|
|
736
|
+
case 'delete_notification': {
|
|
737
|
+
const sessionId = args?.sessionId;
|
|
738
|
+
const notificationId = typeof args?.notificationId === 'string' ? args.notificationId.trim() : '';
|
|
739
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
740
|
+
return { success: false, error: 'sessionId is required' };
|
|
741
|
+
}
|
|
742
|
+
if (!notificationId) {
|
|
743
|
+
return { success: false, error: 'notificationId is required' };
|
|
744
|
+
}
|
|
745
|
+
const sessionEntries = buildSessionEntries(
|
|
746
|
+
this.deps.instanceManager.collectAllStates(),
|
|
747
|
+
this.deps.cdpManagers,
|
|
748
|
+
);
|
|
749
|
+
const targetSession = sessionEntries.find((entry) => entry.id === sessionId);
|
|
750
|
+
const next = dismissSessionNotification(
|
|
751
|
+
loadState(),
|
|
752
|
+
sessionId,
|
|
753
|
+
notificationId,
|
|
754
|
+
targetSession?.providerSessionId,
|
|
755
|
+
);
|
|
756
|
+
saveState(next);
|
|
757
|
+
this.deps.onStatusChange?.();
|
|
758
|
+
return {
|
|
759
|
+
success: true,
|
|
760
|
+
sessionId,
|
|
761
|
+
notificationId,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
case 'mark_notification_unread': {
|
|
766
|
+
const sessionId = args?.sessionId;
|
|
767
|
+
const notificationId = typeof args?.notificationId === 'string' ? args.notificationId.trim() : '';
|
|
768
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
769
|
+
return { success: false, error: 'sessionId is required' };
|
|
770
|
+
}
|
|
771
|
+
if (!notificationId) {
|
|
772
|
+
return { success: false, error: 'notificationId is required' };
|
|
773
|
+
}
|
|
774
|
+
const sessionEntries = buildSessionEntries(
|
|
775
|
+
this.deps.instanceManager.collectAllStates(),
|
|
776
|
+
this.deps.cdpManagers,
|
|
777
|
+
);
|
|
778
|
+
const targetSession = sessionEntries.find((entry) => entry.id === sessionId);
|
|
779
|
+
const next = markSessionNotificationUnread(
|
|
780
|
+
loadState(),
|
|
781
|
+
sessionId,
|
|
782
|
+
notificationId,
|
|
783
|
+
targetSession?.providerSessionId,
|
|
784
|
+
);
|
|
785
|
+
saveState(next);
|
|
786
|
+
this.deps.onStatusChange?.();
|
|
787
|
+
return {
|
|
788
|
+
success: true,
|
|
789
|
+
sessionId,
|
|
790
|
+
notificationId,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
|
|
736
794
|
// ─── Daemon Self-Upgrade ───
|
|
737
795
|
case 'daemon_upgrade': {
|
|
738
796
|
LOG.info('Upgrade', 'Remote upgrade requested from dashboard');
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import type { ProviderSummaryMetadata } from '../shared-types.js';
|
|
12
|
+
import type { RecentSessionBucket, SessionEntry } from '../shared-types.js';
|
|
12
13
|
import type { DaemonState } from './state-store.js';
|
|
13
14
|
import { expandPath } from './workspaces.js';
|
|
14
15
|
import { normalizePersistedSummaryMetadata } from '../providers/summary-metadata.js';
|
|
@@ -99,6 +100,121 @@ export function getSessionSeenMarker(state: DaemonState, sessionId: string, prov
|
|
|
99
100
|
return state.sessionReadMarkers?.[providerKey] || state.sessionReadMarkers?.[sessionId] || '';
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
export function getSessionNotificationDismissal(state: DaemonState, sessionId: string, providerSessionId?: string | null): string {
|
|
104
|
+
const providerKey = buildSessionReadStateKey(sessionId, providerSessionId);
|
|
105
|
+
return state.sessionNotificationDismissals?.[providerKey] || state.sessionNotificationDismissals?.[sessionId] || '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getSessionNotificationUnreadOverride(state: DaemonState, sessionId: string, providerSessionId?: string | null): string {
|
|
109
|
+
const providerKey = buildSessionReadStateKey(sessionId, providerSessionId);
|
|
110
|
+
return state.sessionNotificationUnreadOverrides?.[providerKey] || state.sessionNotificationUnreadOverrides?.[sessionId] || '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function dismissSessionNotification(
|
|
114
|
+
state: DaemonState,
|
|
115
|
+
sessionId: string,
|
|
116
|
+
notificationId: string,
|
|
117
|
+
providerSessionId?: string | null,
|
|
118
|
+
): DaemonState {
|
|
119
|
+
const dismissalId = String(notificationId || '').trim();
|
|
120
|
+
if (!dismissalId) return state;
|
|
121
|
+
const dismissalKeys = Array.from(new Set([
|
|
122
|
+
sessionId,
|
|
123
|
+
buildSessionReadStateKey(sessionId, providerSessionId),
|
|
124
|
+
].filter(Boolean)));
|
|
125
|
+
const nextSessionNotificationDismissals = { ...(state.sessionNotificationDismissals || {}) };
|
|
126
|
+
const nextSessionNotificationUnreadOverrides = { ...(state.sessionNotificationUnreadOverrides || {}) };
|
|
127
|
+
for (const key of dismissalKeys) {
|
|
128
|
+
nextSessionNotificationDismissals[key] = dismissalId;
|
|
129
|
+
delete nextSessionNotificationUnreadOverrides[key];
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
...state,
|
|
133
|
+
sessionNotificationDismissals: nextSessionNotificationDismissals,
|
|
134
|
+
sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function markSessionNotificationUnread(
|
|
139
|
+
state: DaemonState,
|
|
140
|
+
sessionId: string,
|
|
141
|
+
notificationId: string,
|
|
142
|
+
providerSessionId?: string | null,
|
|
143
|
+
): DaemonState {
|
|
144
|
+
const unreadId = String(notificationId || '').trim();
|
|
145
|
+
if (!unreadId) return state;
|
|
146
|
+
const unreadKeys = Array.from(new Set([
|
|
147
|
+
sessionId,
|
|
148
|
+
buildSessionReadStateKey(sessionId, providerSessionId),
|
|
149
|
+
].filter(Boolean)));
|
|
150
|
+
const nextSessionNotificationDismissals = { ...(state.sessionNotificationDismissals || {}) };
|
|
151
|
+
const nextSessionNotificationUnreadOverrides = { ...(state.sessionNotificationUnreadOverrides || {}) };
|
|
152
|
+
for (const key of unreadKeys) {
|
|
153
|
+
nextSessionNotificationUnreadOverrides[key] = unreadId;
|
|
154
|
+
delete nextSessionNotificationDismissals[key];
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
...state,
|
|
158
|
+
sessionNotificationDismissals: nextSessionNotificationDismissals,
|
|
159
|
+
sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function getSessionNotificationTargetValue(session: Pick<SessionEntry, 'id' | 'providerSessionId'>): string {
|
|
164
|
+
const providerSessionId = typeof session.providerSessionId === 'string' ? session.providerSessionId.trim() : '';
|
|
165
|
+
return providerSessionId || session.id;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getSessionCurrentNotificationId(session: Pick<SessionEntry, 'id' | 'providerSessionId' | 'inboxBucket' | 'unread' | 'lastMessageHash' | 'lastMessageAt' | 'lastUpdated' | 'status'>): string {
|
|
169
|
+
const inboxBucket = session.inboxBucket || 'idle';
|
|
170
|
+
const isNeedsAttention = inboxBucket === 'needs_attention' || session.status === 'waiting_approval';
|
|
171
|
+
const isTaskComplete = inboxBucket === 'task_complete' && !!session.unread;
|
|
172
|
+
const type = isNeedsAttention ? 'needs_attention' : isTaskComplete ? 'task_complete' : '';
|
|
173
|
+
if (!type) return '';
|
|
174
|
+
const target = getSessionNotificationTargetValue(session);
|
|
175
|
+
const lastMessageHash = typeof session.lastMessageHash === 'string' ? session.lastMessageHash : '';
|
|
176
|
+
const timestamp = Number(session.lastMessageAt || session.lastUpdated || 0);
|
|
177
|
+
return [type, target, lastMessageHash, String(timestamp)].join('|');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function applySessionNotificationOverlay(
|
|
181
|
+
session: Pick<SessionEntry, 'id' | 'providerSessionId' | 'inboxBucket' | 'unread' | 'lastMessageHash' | 'lastMessageAt' | 'lastUpdated' | 'status'>,
|
|
182
|
+
overlay: { dismissedNotificationId?: string | null; unreadNotificationId?: string | null },
|
|
183
|
+
): { unread: boolean; inboxBucket: RecentSessionBucket } {
|
|
184
|
+
const currentNotificationId = getSessionCurrentNotificationId(session);
|
|
185
|
+
const taskCompleteNotificationId = (() => {
|
|
186
|
+
const target = getSessionNotificationTargetValue(session);
|
|
187
|
+
const lastMessageHash = typeof session.lastMessageHash === 'string' ? session.lastMessageHash : '';
|
|
188
|
+
const timestamp = Number(session.lastMessageAt || session.lastUpdated || 0);
|
|
189
|
+
if (!target || !lastMessageHash || !timestamp) return '';
|
|
190
|
+
return ['task_complete', target, lastMessageHash, String(timestamp)].join('|');
|
|
191
|
+
})();
|
|
192
|
+
const dismissedNotificationId = typeof overlay.dismissedNotificationId === 'string' ? overlay.dismissedNotificationId.trim() : '';
|
|
193
|
+
const unreadNotificationId = typeof overlay.unreadNotificationId === 'string' ? overlay.unreadNotificationId.trim() : '';
|
|
194
|
+
if (
|
|
195
|
+
unreadNotificationId
|
|
196
|
+
&& (currentNotificationId === unreadNotificationId || taskCompleteNotificationId === unreadNotificationId)
|
|
197
|
+
) {
|
|
198
|
+
const forcedInboxBucket = session.inboxBucket === 'needs_attention' || session.status === 'waiting_approval'
|
|
199
|
+
? 'needs_attention'
|
|
200
|
+
: 'task_complete';
|
|
201
|
+
return {
|
|
202
|
+
unread: true,
|
|
203
|
+
inboxBucket: forcedInboxBucket,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (!currentNotificationId || !dismissedNotificationId || currentNotificationId !== dismissedNotificationId) {
|
|
207
|
+
return {
|
|
208
|
+
unread: !!session.unread,
|
|
209
|
+
inboxBucket: session.inboxBucket || 'idle',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
unread: false,
|
|
214
|
+
inboxBucket: 'idle',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
102
218
|
export function markSessionSeen(
|
|
103
219
|
state: DaemonState,
|
|
104
220
|
sessionId: string,
|
|
@@ -115,13 +231,19 @@ export function markSessionSeen(
|
|
|
115
231
|
].filter(Boolean)));
|
|
116
232
|
const nextSessionReads = { ...prev };
|
|
117
233
|
const nextSessionReadMarkers = { ...prevMarkers };
|
|
234
|
+
const nextSessionNotificationDismissals = { ...(state.sessionNotificationDismissals || {}) };
|
|
235
|
+
const nextSessionNotificationUnreadOverrides = { ...(state.sessionNotificationUnreadOverrides || {}) };
|
|
118
236
|
for (const key of readKeys) {
|
|
119
237
|
nextSessionReads[key] = Math.max(prev[key] || 0, seenAt);
|
|
120
238
|
if (nextMarker) nextSessionReadMarkers[key] = nextMarker;
|
|
239
|
+
delete nextSessionNotificationDismissals[key];
|
|
240
|
+
delete nextSessionNotificationUnreadOverrides[key];
|
|
121
241
|
}
|
|
122
242
|
return {
|
|
123
243
|
...state,
|
|
124
244
|
sessionReads: nextSessionReads,
|
|
125
245
|
sessionReadMarkers: nextMarker ? nextSessionReadMarkers : prevMarkers,
|
|
246
|
+
sessionNotificationDismissals: nextSessionNotificationDismissals,
|
|
247
|
+
sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides,
|
|
126
248
|
};
|
|
127
249
|
}
|