@adhdev/daemon-core 0.8.80 → 0.8.82
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 +1 -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 +232 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +232 -41
- 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 +1 -1
- 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 +14 -5
- package/src/cli-adapters/provider-cli-shared.d.ts +3 -4
- package/src/cli-adapters/provider-cli-shared.ts +2 -0
- 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 +32 -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.ts +3 -1
- package/src/status/snapshot.ts +18 -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 {
|
|
@@ -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
|
@@ -113,6 +113,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
113
113
|
private recentOutputBuffer = '';
|
|
114
114
|
private isWaitingForResponse = false;
|
|
115
115
|
private activeModal: { message: string; buttons: string[] } | null = null;
|
|
116
|
+
private parseErrorMessage: string | null = null;
|
|
116
117
|
private responseTimeout: NodeJS.Timeout | null = null;
|
|
117
118
|
private idleTimeout: NodeJS.Timeout | null = null;
|
|
118
119
|
private ready = false;
|
|
@@ -1267,10 +1268,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1267
1268
|
|
|
1268
1269
|
getStatus(): CliSessionStatus {
|
|
1269
1270
|
return {
|
|
1270
|
-
status: this.currentStatus,
|
|
1271
|
+
status: this.parseErrorMessage ? 'error' : this.currentStatus,
|
|
1271
1272
|
messages: [...this.committedMessages],
|
|
1272
1273
|
workingDir: this.workingDir,
|
|
1273
1274
|
activeModal: this.activeModal,
|
|
1275
|
+
errorMessage: this.parseErrorMessage || undefined,
|
|
1276
|
+
errorReason: this.parseErrorMessage ? 'parse_error' : undefined,
|
|
1274
1277
|
};
|
|
1275
1278
|
}
|
|
1276
1279
|
|
|
@@ -1340,7 +1343,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1340
1343
|
id: 'cli_session',
|
|
1341
1344
|
status: this.currentStatus,
|
|
1342
1345
|
title: this.cliName,
|
|
1343
|
-
messages: messages.
|
|
1346
|
+
messages: messages.map((message, index) => buildChatMessage({
|
|
1344
1347
|
...message,
|
|
1345
1348
|
id: message.id || `msg_${index}`,
|
|
1346
1349
|
index: typeof message.index === 'number' ? message.index : index,
|
|
@@ -1375,7 +1378,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1375
1378
|
}
|
|
1376
1379
|
|
|
1377
1380
|
private parseCurrentTranscript(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): any {
|
|
1378
|
-
if (!this.cliScripts?.parseOutput)
|
|
1381
|
+
if (!this.cliScripts?.parseOutput) {
|
|
1382
|
+
this.parseErrorMessage = null;
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1379
1385
|
try {
|
|
1380
1386
|
const input = buildCliParseInput({
|
|
1381
1387
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
@@ -1403,10 +1409,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1403
1409
|
lastAssistant.content = trimPromptEchoPrefix(lastAssistant.content, promptForTrim);
|
|
1404
1410
|
}
|
|
1405
1411
|
}
|
|
1412
|
+
this.parseErrorMessage = null;
|
|
1406
1413
|
return parsed;
|
|
1407
1414
|
} catch (e: any) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1415
|
+
const message = e?.message || String(e);
|
|
1416
|
+
this.parseErrorMessage = message;
|
|
1417
|
+
LOG.warn('CLI', `[${this.cliType}] parseOutput error: ${message}`);
|
|
1418
|
+
throw e;
|
|
1410
1419
|
}
|
|
1411
1420
|
}
|
|
1412
1421
|
|
|
@@ -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;
|
|
@@ -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
|
}
|
|
@@ -23,6 +23,10 @@ export interface DaemonState {
|
|
|
23
23
|
sessionReads: Record<string, number>;
|
|
24
24
|
/** Last seen completion marker for live sessions, keyed by sessionId */
|
|
25
25
|
sessionReadMarkers: Record<string, string>;
|
|
26
|
+
/** Current notification dismissal ids keyed by stable session target */
|
|
27
|
+
sessionNotificationDismissals: Record<string, string>;
|
|
28
|
+
/** Current notification unread override ids keyed by stable session target */
|
|
29
|
+
sessionNotificationUnreadOverrides: Record<string, string>;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
const DEFAULT_STATE: DaemonState = {
|
|
@@ -30,6 +34,8 @@ const DEFAULT_STATE: DaemonState = {
|
|
|
30
34
|
savedProviderSessions: [],
|
|
31
35
|
sessionReads: {},
|
|
32
36
|
sessionReadMarkers: {},
|
|
37
|
+
sessionNotificationDismissals: {},
|
|
38
|
+
sessionNotificationUnreadOverrides: {},
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
function isPlainObject(value: unknown): value is Record<string, any> {
|
|
@@ -71,12 +77,22 @@ function normalizeState(raw: unknown): DaemonState {
|
|
|
71
77
|
Object.entries(isPlainObject(parsed.sessionReadMarkers) ? parsed.sessionReadMarkers : {})
|
|
72
78
|
.filter(([key, value]) => !isLegacyVolatileSessionReadKey(key) && typeof value === 'string')
|
|
73
79
|
);
|
|
80
|
+
const sessionNotificationDismissals = Object.fromEntries(
|
|
81
|
+
Object.entries(isPlainObject(parsed.sessionNotificationDismissals) ? parsed.sessionNotificationDismissals : {})
|
|
82
|
+
.filter(([key, value]) => !isLegacyVolatileSessionReadKey(key) && typeof value === 'string' && value.length > 0)
|
|
83
|
+
);
|
|
84
|
+
const sessionNotificationUnreadOverrides = Object.fromEntries(
|
|
85
|
+
Object.entries(isPlainObject(parsed.sessionNotificationUnreadOverrides) ? parsed.sessionNotificationUnreadOverrides : {})
|
|
86
|
+
.filter(([key, value]) => !isLegacyVolatileSessionReadKey(key) && typeof value === 'string' && value.length > 0)
|
|
87
|
+
);
|
|
74
88
|
|
|
75
89
|
return {
|
|
76
90
|
recentActivity,
|
|
77
91
|
savedProviderSessions,
|
|
78
92
|
sessionReads,
|
|
79
93
|
sessionReadMarkers,
|
|
94
|
+
sessionNotificationDismissals,
|
|
95
|
+
sessionNotificationUnreadOverrides,
|
|
80
96
|
};
|
|
81
97
|
}
|
|
82
98
|
|
|
@@ -286,9 +286,8 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
286
286
|
getState(): AcpProviderState {
|
|
287
287
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const content = this.truncateContent(m.content);
|
|
289
|
+
const recentMessages = normalizeChatMessages(this.messages.map(m => {
|
|
290
|
+
const content = m.content;
|
|
292
291
|
return buildChatMessage({
|
|
293
292
|
...m,
|
|
294
293
|
content,
|
|
@@ -1239,19 +1238,6 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1239
1238
|
|
|
1240
1239
|
// ─── Rich Content Helpers ────────────────────────────
|
|
1241
1240
|
|
|
1242
|
-
/** Truncate content for transport (text: 2000 chars, images preserved) */
|
|
1243
|
-
private truncateContent(content: string | ContentBlock[]): string | ContentBlock[] {
|
|
1244
|
-
if (typeof content === 'string') {
|
|
1245
|
-
return content.length > 2000 ? content.slice(0, 2000) + '\n... (truncated)' : content;
|
|
1246
|
-
}
|
|
1247
|
-
return content.map(b => {
|
|
1248
|
-
if (b.type === 'text' && b.text.length > 2000) {
|
|
1249
|
-
return { ...b, text: b.text.slice(0, 2000) + '\n... (truncated)' };
|
|
1250
|
-
}
|
|
1251
|
-
return b;
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
1241
|
/** Build ContentBlock[] from current partial state */
|
|
1256
1242
|
private buildPartialBlocks(): ContentBlock[] {
|
|
1257
1243
|
const blocks: ContentBlock[] = [];
|
|
@@ -1437,7 +1423,6 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1437
1423
|
|
|
1438
1424
|
private pushEvent(event: ProviderEvent): void {
|
|
1439
1425
|
this.events.push(event);
|
|
1440
|
-
if (this.events.length > 50) this.events = this.events.slice(-50);
|
|
1441
1426
|
}
|
|
1442
1427
|
|
|
1443
1428
|
private appendSystemMessage(content: string, timestamp = Date.now()): void {
|
|
@@ -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 } from './provider-instance.js';
|
|
15
|
+
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, ProviderErrorReason } 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';
|
|
@@ -114,6 +114,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
114
114
|
private runtimeMessages: Array<{ key: string; message: ChatMessage }> = [];
|
|
115
115
|
readonly instanceId: string;
|
|
116
116
|
private suppressIdleHistoryReplay = false;
|
|
117
|
+
private errorMessage: string | undefined = undefined;
|
|
118
|
+
private errorReason: ProviderErrorReason | undefined = undefined;
|
|
117
119
|
|
|
118
120
|
private presentationMode: 'terminal' | 'chat';
|
|
119
121
|
private providerSessionId?: string;
|
|
@@ -302,9 +304,26 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
302
304
|
|
|
303
305
|
getState(): ProviderState {
|
|
304
306
|
const adapterStatus = this.adapter.getStatus();
|
|
305
|
-
|
|
307
|
+
let parsedStatus: any = null;
|
|
308
|
+
let parseErrorMessage: string | undefined;
|
|
309
|
+
if (typeof this.adapter.getScriptParsedStatus === 'function') {
|
|
310
|
+
try {
|
|
311
|
+
parsedStatus = this.adapter.getScriptParsedStatus() || null;
|
|
312
|
+
this.errorMessage = undefined;
|
|
313
|
+
this.errorReason = undefined;
|
|
314
|
+
} catch (error: any) {
|
|
315
|
+
parseErrorMessage = error?.message || String(error);
|
|
316
|
+
this.errorMessage = parseErrorMessage;
|
|
317
|
+
this.errorReason = 'parse_error';
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
this.errorMessage = undefined;
|
|
321
|
+
this.errorReason = undefined;
|
|
322
|
+
}
|
|
306
323
|
const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
307
|
-
const visibleStatus =
|
|
324
|
+
const visibleStatus = parseErrorMessage
|
|
325
|
+
? 'error'
|
|
326
|
+
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
308
327
|
const parsedProviderSessionId = normalizeProviderSessionId(
|
|
309
328
|
this.type,
|
|
310
329
|
typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
|
|
@@ -314,7 +333,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
314
333
|
}
|
|
315
334
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
316
335
|
this.maybeAppendRuntimeRecoveryMessage(runtime);
|
|
317
|
-
let parsedMessages = Array.isArray(parsedStatus?.messages)
|
|
336
|
+
let parsedMessages = Array.isArray(parsedStatus?.messages)
|
|
337
|
+
? parsedStatus.messages
|
|
338
|
+
: (parseErrorMessage
|
|
339
|
+
? normalizeChatMessages(Array.isArray(adapterStatus.messages) ? adapterStatus.messages as any : [])
|
|
340
|
+
: []);
|
|
318
341
|
const historyMessageCount = Number.isFinite(parsedStatus?.historyMessageCount)
|
|
319
342
|
? Math.max(0, Number(parsedStatus.historyMessageCount))
|
|
320
343
|
: null;
|
|
@@ -365,7 +388,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
365
388
|
activeChat: {
|
|
366
389
|
id: `${this.type}_${this.workingDir}`,
|
|
367
390
|
title: parsedStatus?.title || dirName,
|
|
368
|
-
status:
|
|
391
|
+
status: parseErrorMessage
|
|
392
|
+
? 'error'
|
|
393
|
+
: autoApproveActive && parsedStatus?.status === 'waiting_approval'
|
|
369
394
|
? 'generating'
|
|
370
395
|
: (parsedStatus?.status || visibleStatus),
|
|
371
396
|
messages: mergedMessages,
|
|
@@ -394,6 +419,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
394
419
|
controlValues: surface.controlValues,
|
|
395
420
|
providerControls: this.provider.controls,
|
|
396
421
|
summaryMetadata: surface.summaryMetadata as any,
|
|
422
|
+
errorMessage: this.errorMessage,
|
|
423
|
+
errorReason: this.errorReason,
|
|
397
424
|
};
|
|
398
425
|
}
|
|
399
426
|
|
|
@@ -591,8 +618,6 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
591
618
|
|
|
592
619
|
private pushEvent(event: ProviderEvent): void {
|
|
593
620
|
this.events.push(event);
|
|
594
|
-
// Max 50
|
|
595
|
-
if (this.events.length > 50) this.events = this.events.slice(-50);
|
|
596
621
|
}
|
|
597
622
|
|
|
598
623
|
private flushEvents(): ProviderEvent[] {
|
|
@@ -805,9 +830,6 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
805
830
|
key: dedupKey,
|
|
806
831
|
message: normalizedMessage,
|
|
807
832
|
});
|
|
808
|
-
if (this.runtimeMessages.length > 50) {
|
|
809
|
-
this.runtimeMessages = this.runtimeMessages.slice(-50);
|
|
810
|
-
}
|
|
811
833
|
|
|
812
834
|
if (normalizedContent) {
|
|
813
835
|
this.historyWriter.appendNewMessages(
|
|
@@ -246,7 +246,6 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
246
246
|
|
|
247
247
|
private pushEvent(event: ProviderEvent): void {
|
|
248
248
|
this.events.push(event);
|
|
249
|
-
if (this.events.length > 50) this.events = this.events.slice(-50);
|
|
250
249
|
}
|
|
251
250
|
|
|
252
251
|
private applyProviderResponse(data: any, options: { phase: 'immediate' | 'turn_completed' }): void {
|
|
@@ -335,7 +334,6 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
335
334
|
key: dedupKey,
|
|
336
335
|
message: normalizedMessage,
|
|
337
336
|
});
|
|
338
|
-
if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
|
|
339
337
|
|
|
340
338
|
if (normalizedContent) {
|
|
341
339
|
this.historyWriter.appendNewMessages(
|
|
@@ -473,7 +473,6 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
473
473
|
|
|
474
474
|
private pushEvent(event: ProviderEvent): void {
|
|
475
475
|
this.events.push(event);
|
|
476
|
-
if (this.events.length > 50) this.events = this.events.slice(-50);
|
|
477
476
|
}
|
|
478
477
|
|
|
479
478
|
private applyProviderResponse(data: any, options: { phase: 'immediate' | 'turn_completed' }): void {
|
|
@@ -576,7 +575,6 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
576
575
|
key: dedupKey,
|
|
577
576
|
message: normalizedMessage,
|
|
578
577
|
});
|
|
579
|
-
if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
|
|
580
578
|
|
|
581
579
|
if (normalizedContent) {
|
|
582
580
|
this.historyWriter.appendNewMessages(
|
|
@@ -40,7 +40,7 @@ export interface ActiveChatData {
|
|
|
40
40
|
inputContent?: string;
|
|
41
41
|
}
|
|
42
42
|
/** Standardized error reasons across all provider categories */
|
|
43
|
-
export type ProviderErrorReason = 'not_installed' | 'auth_failed' | 'spawn_error' | 'init_failed' | 'crash' | 'timeout' | 'cdp_error' | 'disconnected';
|
|
43
|
+
export type ProviderErrorReason = 'not_installed' | 'auth_failed' | 'spawn_error' | 'init_failed' | 'parse_error' | 'crash' | 'timeout' | 'cdp_error' | 'disconnected';
|
|
44
44
|
/** Common fields shared by all provider categories */
|
|
45
45
|
interface ProviderStateBase {
|
|
46
46
|
/** Provider type (e.g. 'gemini-cli', 'cursor', 'cline') */
|
|
@@ -55,6 +55,7 @@ export type ProviderErrorReason =
|
|
|
55
55
|
| 'auth_failed' // Authentication/API key error
|
|
56
56
|
| 'spawn_error' // Process spawn failure
|
|
57
57
|
| 'init_failed' // Initialization/handshake failure
|
|
58
|
+
| 'parse_error' // Provider parser/adapter script failure
|
|
58
59
|
| 'crash' // Unexpected process crash
|
|
59
60
|
| 'timeout' // Operation timeout
|
|
60
61
|
| 'cdp_error' // CDP connection failure (IDE)
|