@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.
Files changed (36) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
  3. package/dist/config/recent-activity.d.ts +14 -0
  4. package/dist/config/state-store.d.ts +4 -0
  5. package/dist/index.js +363 -117
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +363 -117
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/providers/acp-provider-instance.d.ts +0 -2
  10. package/dist/providers/cli-provider-instance.d.ts +2 -0
  11. package/dist/providers/provider-instance.d.ts +1 -1
  12. package/dist/shared-types.d.ts +3 -1
  13. package/dist/status/chat-tail-hot-sessions.d.ts +2 -0
  14. package/dist/status/snapshot.d.ts +1 -0
  15. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  16. package/package.json +1 -1
  17. package/src/cli-adapter-types.d.ts +2 -0
  18. package/src/cli-adapters/provider-cli-adapter.ts +107 -30
  19. package/src/cli-adapters/provider-cli-shared.d.ts +3 -4
  20. package/src/cli-adapters/provider-cli-shared.ts +2 -0
  21. package/src/cli-adapters/terminal-screen.ts +6 -4
  22. package/src/commands/chat-commands.ts +8 -3
  23. package/src/commands/router.ts +59 -1
  24. package/src/config/recent-activity.ts +122 -0
  25. package/src/config/state-store.ts +16 -0
  26. package/src/logging/command-log.ts +2 -0
  27. package/src/providers/acp-provider-instance.ts +2 -17
  28. package/src/providers/cli-provider-instance.ts +33 -10
  29. package/src/providers/extension-provider-instance.ts +0 -2
  30. package/src/providers/ide-provider-instance.ts +0 -2
  31. package/src/providers/provider-instance.d.ts +1 -1
  32. package/src/providers/provider-instance.ts +1 -0
  33. package/src/shared-types.d.ts +3 -0
  34. package/src/shared-types.ts +5 -1
  35. package/src/status/chat-tail-hot-sessions.ts +15 -1
  36. package/src/status/snapshot.ts +20 -3
@@ -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
 
@@ -118,6 +118,8 @@ const SKIP_COMMANDS = new Set([
118
118
  'status_report',
119
119
  'read_chat',
120
120
  'mark_session_seen',
121
+ 'delete_notification',
122
+ 'mark_notification_unread',
121
123
  ]);
122
124
 
123
125
  export function shouldLogCommand(cmd: string): boolean {
@@ -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
- // Recent 50 messages
290
- const recentMessages = normalizeChatMessages(this.messages.slice(-50).map(m => {
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;
@@ -229,6 +231,7 @@ export class CliProviderInstance implements ProviderInstance {
229
231
 
230
232
  async onTick(): Promise<void> {
231
233
  if (this.providerSessionId) return;
234
+ if (this.type === 'hermes-cli' && this.launchMode === 'new') return;
232
235
 
233
236
  let probedSessionId: string | null = null;
234
237
 
@@ -302,9 +305,26 @@ export class CliProviderInstance implements ProviderInstance {
302
305
 
303
306
  getState(): ProviderState {
304
307
  const adapterStatus = this.adapter.getStatus();
305
- const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
308
+ let parsedStatus: any = null;
309
+ let parseErrorMessage: string | undefined;
310
+ if (typeof this.adapter.getScriptParsedStatus === 'function') {
311
+ try {
312
+ parsedStatus = this.adapter.getScriptParsedStatus() || null;
313
+ this.errorMessage = undefined;
314
+ this.errorReason = undefined;
315
+ } catch (error: any) {
316
+ parseErrorMessage = error?.message || String(error);
317
+ this.errorMessage = parseErrorMessage;
318
+ this.errorReason = 'parse_error';
319
+ }
320
+ } else {
321
+ this.errorMessage = undefined;
322
+ this.errorReason = undefined;
323
+ }
306
324
  const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
307
- const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
325
+ const visibleStatus = parseErrorMessage
326
+ ? 'error'
327
+ : (autoApproveActive ? 'generating' : adapterStatus.status);
308
328
  const parsedProviderSessionId = normalizeProviderSessionId(
309
329
  this.type,
310
330
  typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
@@ -314,7 +334,11 @@ export class CliProviderInstance implements ProviderInstance {
314
334
  }
315
335
  const runtime = this.adapter.getRuntimeMetadata();
316
336
  this.maybeAppendRuntimeRecoveryMessage(runtime);
317
- let parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
337
+ let parsedMessages = Array.isArray(parsedStatus?.messages)
338
+ ? parsedStatus.messages
339
+ : (parseErrorMessage
340
+ ? normalizeChatMessages(Array.isArray(adapterStatus.messages) ? adapterStatus.messages as any : [])
341
+ : []);
318
342
  const historyMessageCount = Number.isFinite(parsedStatus?.historyMessageCount)
319
343
  ? Math.max(0, Number(parsedStatus.historyMessageCount))
320
344
  : null;
@@ -365,7 +389,9 @@ export class CliProviderInstance implements ProviderInstance {
365
389
  activeChat: {
366
390
  id: `${this.type}_${this.workingDir}`,
367
391
  title: parsedStatus?.title || dirName,
368
- status: autoApproveActive && parsedStatus?.status === 'waiting_approval'
392
+ status: parseErrorMessage
393
+ ? 'error'
394
+ : autoApproveActive && parsedStatus?.status === 'waiting_approval'
369
395
  ? 'generating'
370
396
  : (parsedStatus?.status || visibleStatus),
371
397
  messages: mergedMessages,
@@ -394,6 +420,8 @@ export class CliProviderInstance implements ProviderInstance {
394
420
  controlValues: surface.controlValues,
395
421
  providerControls: this.provider.controls,
396
422
  summaryMetadata: surface.summaryMetadata as any,
423
+ errorMessage: this.errorMessage,
424
+ errorReason: this.errorReason,
397
425
  };
398
426
  }
399
427
 
@@ -591,8 +619,6 @@ export class CliProviderInstance implements ProviderInstance {
591
619
 
592
620
  private pushEvent(event: ProviderEvent): void {
593
621
  this.events.push(event);
594
- // Max 50
595
- if (this.events.length > 50) this.events = this.events.slice(-50);
596
622
  }
597
623
 
598
624
  private flushEvents(): ProviderEvent[] {
@@ -805,9 +831,6 @@ export class CliProviderInstance implements ProviderInstance {
805
831
  key: dedupKey,
806
832
  message: normalizedMessage,
807
833
  });
808
- if (this.runtimeMessages.length > 50) {
809
- this.runtimeMessages = this.runtimeMessages.slice(-50);
810
- }
811
834
 
812
835
  if (normalizedContent) {
813
836
  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)
@@ -190,10 +190,13 @@ export interface SessionEntry {
190
190
  summaryMetadata?: ProviderSummaryMetadata;
191
191
  errorMessage?: string;
192
192
  errorReason?: _ProviderErrorReason;
193
+ lastMessageHash?: string;
193
194
  lastUpdated?: number;
194
195
  unread?: boolean;
195
196
  lastSeenAt?: number;
196
197
  inboxBucket?: RecentSessionBucket;
198
+ completionMarker?: string;
199
+ seenCompletionMarker?: string;
197
200
  surfaceHidden?: boolean;
198
201
  }
199
202
  /**
@@ -290,7 +290,9 @@ export type SessionCapability =
290
290
  | 'resize_terminal'
291
291
  | 'change_model'
292
292
  | 'set_mode'
293
- | 'set_thought_level';
293
+ | 'set_thought_level'
294
+ | 'delete_notification'
295
+ | 'mark_notification_unread';
294
296
 
295
297
  import type { RuntimeWriteOwner, RuntimeAttachedClient, SessionStatus } from './shared-types-extra.js';
296
298
  export type { RuntimeWriteOwner, RuntimeAttachedClient, SessionStatus } from './shared-types-extra.js';
@@ -337,6 +339,8 @@ export interface SessionEntry {
337
339
  unread?: boolean;
338
340
  lastSeenAt?: number;
339
341
  inboxBucket?: RecentSessionBucket;
342
+ completionMarker?: string;
343
+ seenCompletionMarker?: string;
340
344
  surfaceHidden?: boolean;
341
345
  }
342
346
 
@@ -13,6 +13,8 @@ const LIVE_RUNTIME_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'int
13
13
  export interface HotChatSessionLike {
14
14
  id?: string | null;
15
15
  status?: unknown;
16
+ unread?: unknown;
17
+ inboxBucket?: unknown;
16
18
  lastMessageAt?: unknown;
17
19
  runtimeLifecycle?: unknown;
18
20
  runtimeSurfaceKind?: unknown;
@@ -79,10 +81,22 @@ export function classifyHotChatSessionsForSubscriptionFlush(
79
81
  }
80
82
 
81
83
  const status = String(session?.status || '').toLowerCase();
84
+ const unread = session?.unread === true;
85
+ const inboxBucket = String(session?.inboxBucket || '').toLowerCase();
86
+ const runtimeSurfaceKind = String(session?.runtimeSurfaceKind || '').toLowerCase();
87
+ const runtimeLifecycle = String(session?.runtimeLifecycle || '').toLowerCase();
88
+ const isLiveRuntime = runtimeSurfaceKind === 'live_runtime' || LIVE_RUNTIME_LIFECYCLES.has(runtimeLifecycle);
82
89
  const lastMessageAt = parseMessageTimestamp(session?.lastMessageAt);
83
90
  const recentlyUpdated = lastMessageAt > 0 && (now - lastMessageAt) <= recentMessageGraceMs;
91
+ const shouldKeepRecentTailHot = recentlyUpdated && (
92
+ unread
93
+ || inboxBucket === 'task_complete'
94
+ || inboxBucket === 'needs_attention'
95
+ || isLiveRuntime
96
+ || activeStatuses.has(status)
97
+ );
84
98
 
85
- if (activeStatuses.has(status) || recentlyUpdated) {
99
+ if (activeStatuses.has(status) || shouldKeepRecentTailHot) {
86
100
  active.add(sessionId);
87
101
  }
88
102
  }
@@ -9,7 +9,7 @@
9
9
  import * as os from 'os';
10
10
  import { loadConfig } from '../config/config.js';
11
11
  import { loadState } from '../config/state-store.js';
12
- import { getRecentActivity, getSessionSeenAt, getSessionSeenMarker } from '../config/recent-activity.js';
12
+ import { getRecentActivity, getSessionSeenAt, getSessionSeenMarker, getSessionNotificationDismissal, getSessionNotificationUnreadOverride, applySessionNotificationOverlay, getSessionCurrentNotificationId } from '../config/recent-activity.js';
13
13
  import { getWorkspaceState } from '../config/workspaces.js';
14
14
  import { getHostMemorySnapshot } from '../system/host-memory.js';
15
15
  import { getTerminalBackendRuntimeStatus } from '../cli-adapters/terminal-screen.js';
@@ -259,6 +259,8 @@ function getLastDisplayMessage(session: {
259
259
  return null;
260
260
  }
261
261
 
262
+ export { getSessionCurrentNotificationId, applySessionNotificationOverlay } from '../config/recent-activity.js';
263
+
262
264
  function getSessionMessageUpdatedAt(session: {
263
265
  activeChat?: {
264
266
  messages?: Array<{ receivedAt?: number | string; timestamp?: number | string }> | null
@@ -390,9 +392,24 @@ export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnaps
390
392
  completionMarker,
391
393
  seenCompletionMarker,
392
394
  );
395
+ const { unread: overlayUnread, inboxBucket: overlayInboxBucket } = applySessionNotificationOverlay({
396
+ id: sourceSession.id,
397
+ providerSessionId: sourceSession.providerSessionId,
398
+ status: sourceSession.status,
399
+ unread,
400
+ inboxBucket,
401
+ lastMessageHash: sourceSession.lastMessageHash,
402
+ lastMessageAt: sourceSession.lastMessageAt,
403
+ lastUpdated: sourceSession.lastUpdated,
404
+ }, {
405
+ dismissedNotificationId: getSessionNotificationDismissal(state, sourceSession.id, sourceSession.providerSessionId),
406
+ unreadNotificationId: getSessionNotificationUnreadOverride(state, sourceSession.id, sourceSession.providerSessionId),
407
+ });
393
408
  session.lastSeenAt = lastSeenAt;
394
- session.unread = unread;
395
- session.inboxBucket = inboxBucket;
409
+ session.unread = overlayUnread;
410
+ session.inboxBucket = overlayInboxBucket;
411
+ session.completionMarker = completionMarker;
412
+ session.seenCompletionMarker = seenCompletionMarker;
396
413
  if (READ_DEBUG_ENABLED && (session.unread || session.inboxBucket !== 'idle' || session.providerType.includes('codex'))) {
397
414
  const recentReadSnapshot: RecentReadDebugSnapshot = {
398
415
  sessionId: session.id,