@adhdev/daemon-core 0.8.58 → 0.8.60

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 (59) hide show
  1. package/dist/agent-stream/types.d.ts +3 -4
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +3 -0
  3. package/dist/commands/router.d.ts +1 -0
  4. package/dist/commands/stream-commands.d.ts +1 -0
  5. package/dist/config/recent-activity.d.ts +2 -1
  6. package/dist/config/saved-sessions.d.ts +2 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +579 -190
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +579 -190
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/providers/acp-provider-instance.d.ts +8 -2
  13. package/dist/providers/cli-provider-instance.d.ts +1 -0
  14. package/dist/providers/contracts.d.ts +3 -2
  15. package/dist/providers/extension-provider-instance.d.ts +1 -2
  16. package/dist/providers/provider-instance.d.ts +3 -4
  17. package/dist/providers/provider-patch-state.d.ts +23 -0
  18. package/dist/providers/summary-metadata.d.ts +22 -0
  19. package/dist/shared-types.d.ts +15 -9
  20. package/dist/status/snapshot.d.ts +16 -1
  21. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  22. package/package.json +1 -1
  23. package/src/agent-stream/forward.ts +1 -2
  24. package/src/agent-stream/manager.ts +2 -1
  25. package/src/agent-stream/provider-adapter.ts +7 -3
  26. package/src/agent-stream/types.d.ts +3 -4
  27. package/src/agent-stream/types.ts +3 -4
  28. package/src/cli-adapters/provider-cli-adapter.ts +26 -9
  29. package/src/commands/cli-manager.ts +10 -5
  30. package/src/commands/router.ts +155 -22
  31. package/src/commands/stream-commands.ts +19 -2
  32. package/src/config/recent-activity.d.ts +2 -1
  33. package/src/config/recent-activity.ts +12 -1
  34. package/src/config/saved-sessions.d.ts +2 -1
  35. package/src/config/saved-sessions.ts +12 -2
  36. package/src/daemon/dev-auto-implement.ts +1 -1
  37. package/src/daemon/dev-cli-debug.ts +0 -1
  38. package/src/daemon/dev-server.ts +1 -1
  39. package/src/daemon/scaffold-template.ts +8 -1
  40. package/src/index.d.ts +1 -1
  41. package/src/index.ts +2 -0
  42. package/src/providers/acp-provider-instance.d.ts +8 -2
  43. package/src/providers/acp-provider-instance.ts +80 -23
  44. package/src/providers/cli-provider-instance.ts +17 -22
  45. package/src/providers/contracts.d.ts +3 -2
  46. package/src/providers/contracts.ts +6 -4
  47. package/src/providers/control-effects.ts +3 -4
  48. package/src/providers/extension-provider-instance.d.ts +1 -2
  49. package/src/providers/extension-provider-instance.ts +26 -14
  50. package/src/providers/ide-provider-instance.ts +28 -15
  51. package/src/providers/provider-instance.d.ts +3 -4
  52. package/src/providers/provider-instance.ts +6 -7
  53. package/src/providers/provider-patch-state.ts +91 -0
  54. package/src/providers/summary-metadata.ts +118 -0
  55. package/src/shared-types.d.ts +15 -9
  56. package/src/shared-types.ts +17 -9
  57. package/src/status/builders.ts +18 -13
  58. package/src/status/reporter.ts +2 -4
  59. package/src/status/snapshot.ts +60 -2
@@ -0,0 +1,91 @@
1
+ import type { ProviderControlDef } from './contracts.js'
2
+ import { extractProviderControlValues } from './control-effects.js'
3
+ import { resolveProviderStateSummaryMetadata } from './summary-metadata.js'
4
+
5
+
6
+ export type ProviderControlValue = string | number | boolean
7
+ export type ProviderControlValueMap = Record<string, ProviderControlValue>
8
+
9
+ function isControlValue(value: unknown): value is ProviderControlValue {
10
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
11
+ }
12
+
13
+ function asControlValueMap(value: unknown): ProviderControlValueMap | undefined {
14
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
15
+
16
+ const result: ProviderControlValueMap = {}
17
+ for (const [entryKey, entryValue] of Object.entries(value as Record<string, unknown>)) {
18
+ if (isControlValue(entryValue)) result[entryKey] = entryValue
19
+ }
20
+
21
+ return Object.keys(result).length > 0 ? result : undefined
22
+ }
23
+
24
+ function getLegacyModelModeValues(data: any): ProviderControlValueMap | undefined {
25
+ if (!data || typeof data !== 'object') return undefined
26
+
27
+ const legacy: ProviderControlValueMap = {}
28
+ if (typeof data.model === 'string' && data.model.trim()) legacy.model = data.model.trim()
29
+ if (typeof data.mode === 'string' && data.mode.trim()) legacy.mode = data.mode.trim()
30
+
31
+ return Object.keys(legacy).length > 0 ? legacy : undefined
32
+ }
33
+
34
+ export function mergeProviderPatchState(params: {
35
+ providerControls?: ProviderControlDef[]
36
+ data: any
37
+ currentControlValues?: ProviderControlValueMap
38
+ currentSummaryMetadata?: unknown
39
+ mergeWithCurrent?: boolean
40
+ }): {
41
+ controlValues: ProviderControlValueMap
42
+ summaryMetadata: unknown
43
+ } {
44
+ const {
45
+ providerControls,
46
+ data,
47
+ currentControlValues,
48
+ currentSummaryMetadata,
49
+ mergeWithCurrent = true,
50
+ } = params
51
+
52
+ const sources = [
53
+ mergeWithCurrent ? asControlValueMap(currentControlValues) : undefined,
54
+ asControlValueMap(data?.controlValues),
55
+ asControlValueMap(extractProviderControlValues(providerControls, data)),
56
+ getLegacyModelModeValues(data),
57
+ ]
58
+
59
+ const controlValues = Object.assign({}, ...sources.filter(Boolean)) as ProviderControlValueMap
60
+ return {
61
+ controlValues,
62
+ summaryMetadata: data?.summaryMetadata !== undefined ? data.summaryMetadata : currentSummaryMetadata,
63
+ }
64
+ }
65
+
66
+ export function normalizeProviderStateControlValues(
67
+ controlValues: ProviderControlValueMap | undefined,
68
+ ): ProviderControlValueMap | undefined {
69
+ return controlValues && Object.keys(controlValues).length > 0 ? controlValues : undefined
70
+ }
71
+
72
+ export function resolveProviderStateSurface(params: {
73
+ controlValues?: ProviderControlValueMap
74
+ summaryMetadata?: unknown
75
+ modelLabel?: string | null
76
+ modeLabel?: string | null
77
+ }): {
78
+ controlValues: ProviderControlValueMap | undefined
79
+ summaryMetadata: unknown
80
+ } {
81
+ const controlValues = normalizeProviderStateControlValues(params.controlValues)
82
+ return {
83
+ controlValues,
84
+ summaryMetadata: resolveProviderStateSummaryMetadata({
85
+ summaryMetadata: params.summaryMetadata as any,
86
+ controlValues,
87
+ modelLabel: params.modelLabel,
88
+ modeLabel: params.modeLabel,
89
+ }),
90
+ }
91
+ }
@@ -0,0 +1,118 @@
1
+ import type { ProviderSummaryItem, ProviderSummaryMetadata } from '../shared-types.js'
2
+
3
+ function normalizeSummaryItem(item: ProviderSummaryItem | null | undefined): ProviderSummaryItem | null {
4
+ if (!item || typeof item !== 'object') return null
5
+
6
+ const id = String(item.id || '').trim()
7
+ const value = String(item.value || '').trim()
8
+ if (!id || !value) return null
9
+
10
+ const normalized: ProviderSummaryItem = {
11
+ id,
12
+ value,
13
+ }
14
+
15
+ if (typeof item.label === 'string' && item.label.trim()) normalized.label = item.label.trim()
16
+ if (typeof item.shortValue === 'string' && item.shortValue.trim()) normalized.shortValue = item.shortValue.trim()
17
+ if (typeof item.icon === 'string' && item.icon.trim()) normalized.icon = item.icon.trim()
18
+ if (typeof item.order === 'number' && Number.isFinite(item.order)) normalized.order = item.order
19
+
20
+ return normalized
21
+ }
22
+
23
+ export function normalizeProviderSummaryMetadata(
24
+ summary: ProviderSummaryMetadata | null | undefined,
25
+ ): ProviderSummaryMetadata | undefined {
26
+ if (!summary || !Array.isArray(summary.items)) return undefined
27
+
28
+ const items = summary.items
29
+ .map((item) => normalizeSummaryItem(item))
30
+ .filter((item): item is ProviderSummaryItem => !!item)
31
+ .sort((left, right) => {
32
+ const orderDiff = (left.order ?? Number.MAX_SAFE_INTEGER) - (right.order ?? Number.MAX_SAFE_INTEGER)
33
+ if (orderDiff !== 0) return orderDiff
34
+ return left.id.localeCompare(right.id)
35
+ })
36
+
37
+ return items.length > 0 ? { items } : undefined
38
+ }
39
+
40
+ export function buildProviderSummaryMetadata(
41
+ items: Array<ProviderSummaryItem | null | undefined>,
42
+ ): ProviderSummaryMetadata | undefined {
43
+ return normalizeProviderSummaryMetadata({ items: items.filter(Boolean) as ProviderSummaryItem[] })
44
+ }
45
+
46
+ export function getProviderSummaryItem(
47
+ summary: ProviderSummaryMetadata | null | undefined,
48
+ id: string,
49
+ ): ProviderSummaryItem | undefined {
50
+ const normalized = normalizeProviderSummaryMetadata(summary)
51
+ const targetId = String(id || '').trim()
52
+ if (!normalized || !targetId) return undefined
53
+ return normalized.items.find((item) => item.id === targetId)
54
+ }
55
+
56
+ export function getProviderSummaryValue(
57
+ summary: ProviderSummaryMetadata | null | undefined,
58
+ id: string,
59
+ options: { preferShortValue?: boolean } = {},
60
+ ): string | undefined {
61
+ const item = getProviderSummaryItem(summary, id)
62
+ if (!item) return undefined
63
+ if (options.preferShortValue) return item.shortValue || item.value
64
+ return item.value || item.shortValue
65
+ }
66
+
67
+ export function buildLegacyModelModeSummaryMetadata(params: {
68
+ model?: string | null
69
+ mode?: string | null
70
+ modelLabel?: string | null
71
+ modeLabel?: string | null
72
+ }): ProviderSummaryMetadata | undefined {
73
+ return buildProviderSummaryMetadata([
74
+ params.model
75
+ ? {
76
+ id: 'model',
77
+ label: 'Model',
78
+ value: String(params.modelLabel || params.model).trim(),
79
+ shortValue: String(params.model).trim(),
80
+ order: 10,
81
+ }
82
+ : null,
83
+ params.mode
84
+ ? {
85
+ id: 'mode',
86
+ label: 'Mode',
87
+ value: String(params.modeLabel || params.mode).trim(),
88
+ shortValue: String(params.mode).trim(),
89
+ order: 20,
90
+ }
91
+ : null,
92
+ ])
93
+ }
94
+
95
+ export function resolveProviderStateSummaryMetadata(params: {
96
+ summaryMetadata?: ProviderSummaryMetadata | null
97
+ controlValues?: Record<string, string | number | boolean> | null
98
+ modelLabel?: string | null
99
+ modeLabel?: string | null
100
+ }): ProviderSummaryMetadata | undefined {
101
+ const explicit = normalizeProviderSummaryMetadata(params.summaryMetadata)
102
+ if (explicit) return explicit
103
+
104
+ const model = typeof params.controlValues?.model === 'string' ? params.controlValues.model : undefined
105
+ const mode = typeof params.controlValues?.mode === 'string' ? params.controlValues.mode : undefined
106
+ return buildLegacyModelModeSummaryMetadata({
107
+ model,
108
+ mode,
109
+ modelLabel: params.modelLabel,
110
+ modeLabel: params.modeLabel,
111
+ })
112
+ }
113
+
114
+ export function normalizePersistedSummaryMetadata(params: {
115
+ summaryMetadata?: ProviderSummaryMetadata | null
116
+ }): ProviderSummaryMetadata | undefined {
117
+ return normalizeProviderSummaryMetadata(params.summaryMetadata)
118
+ }
@@ -52,6 +52,17 @@ export interface ReadChatSyncResult {
52
52
  totalMessages: number;
53
53
  lastMessageSignature: string;
54
54
  }
55
+ export interface ProviderSummaryItem {
56
+ id: string;
57
+ value: string;
58
+ label?: string;
59
+ shortValue?: string;
60
+ icon?: string;
61
+ order?: number;
62
+ }
63
+ export interface ProviderSummaryMetadata {
64
+ items: ProviderSummaryItem[];
65
+ }
55
66
  export type TransportTopic = 'session.chat_tail' | 'machine.runtime' | 'session_host.diagnostics' | 'session.modal' | 'daemon.metadata';
56
67
  export interface SessionChatTailSubscriptionParams extends ReadChatCursor {
57
68
  targetSessionId: string;
@@ -171,15 +182,12 @@ export interface SessionEntry {
171
182
  activeChat: _ActiveChatData | null;
172
183
  capabilities?: SessionCapability[];
173
184
  cdpConnected?: boolean;
174
- currentModel?: string;
175
- currentPlan?: string;
176
- currentAutoApprove?: string;
177
- acpConfigOptions?: AcpConfigOption[];
178
- acpModes?: AcpMode[];
179
185
  /** Dynamic control current values (generic key-value) */
180
186
  controlValues?: Record<string, string | number | boolean>;
181
187
  /** Provider-declared controls schema (transmitted once, cached by frontend) */
182
188
  providerControls?: ProviderControlSchema[];
189
+ /** Flexible always-visible metadata for compact/live surfaces. */
190
+ summaryMetadata?: ProviderSummaryMetadata;
183
191
  errorMessage?: string;
184
192
  errorReason?: _ProviderErrorReason;
185
193
  lastUpdated?: number;
@@ -203,9 +211,7 @@ export interface CompactSessionEntry {
203
211
  title: string;
204
212
  workspace: string | null;
205
213
  cdpConnected?: boolean;
206
- currentModel?: string;
207
- currentPlan?: string;
208
- currentAutoApprove?: string;
214
+ summaryMetadata?: ProviderSummaryMetadata;
209
215
  }
210
216
  /** Available provider information */
211
217
  export interface AvailableProviderInfo {
@@ -303,7 +309,7 @@ export interface RecentLaunchEntry {
303
309
  providerSessionId?: string;
304
310
  title?: string;
305
311
  workspace?: string | null;
306
- currentModel?: string;
312
+ summaryMetadata?: ProviderSummaryMetadata;
307
313
  lastLaunchedAt: number;
308
314
  }
309
315
  /** Compact machine payload broadcast by UserSessionDO to cloud dashboards. */
@@ -88,6 +88,19 @@ export interface ReadChatSyncResult {
88
88
  lastMessageSignature: string;
89
89
  }
90
90
 
91
+ export interface ProviderSummaryItem {
92
+ id: string;
93
+ value: string;
94
+ label?: string;
95
+ shortValue?: string;
96
+ icon?: string;
97
+ order?: number;
98
+ }
99
+
100
+ export interface ProviderSummaryMetadata {
101
+ items: ProviderSummaryItem[];
102
+ }
103
+
91
104
  export interface SessionHostAttachedClient {
92
105
  clientId: string;
93
106
  type: string;
@@ -303,15 +316,12 @@ export interface SessionEntry {
303
316
  activeChat: _ActiveChatData | null;
304
317
  capabilities?: SessionCapability[];
305
318
  cdpConnected?: boolean;
306
- currentModel?: string;
307
- currentPlan?: string;
308
- currentAutoApprove?: string;
309
- acpConfigOptions?: AcpConfigOption[];
310
- acpModes?: AcpMode[];
311
319
  /** Dynamic control current values (generic key-value) */
312
320
  controlValues?: Record<string, string | number | boolean>;
313
321
  /** Provider-declared controls schema (transmitted once, cached by frontend) */
314
322
  providerControls?: ProviderControlSchema[];
323
+ /** Flexible always-visible metadata for compact/live surfaces. */
324
+ summaryMetadata?: ProviderSummaryMetadata;
315
325
  errorMessage?: string;
316
326
  errorReason?: _ProviderErrorReason;
317
327
  lastMessagePreview?: string;
@@ -340,9 +350,7 @@ export interface CompactSessionEntry {
340
350
  title: string;
341
351
  workspace: string | null;
342
352
  cdpConnected?: boolean;
343
- currentModel?: string;
344
- currentPlan?: string;
345
- currentAutoApprove?: string;
353
+ summaryMetadata?: ProviderSummaryMetadata;
346
354
  }
347
355
 
348
356
  export type VersionUpdateReason =
@@ -457,7 +465,7 @@ export interface RecentLaunchEntry {
457
465
  providerSessionId?: string;
458
466
  title?: string;
459
467
  workspace?: string | null;
460
- currentModel?: string;
468
+ summaryMetadata?: ProviderSummaryMetadata;
461
469
  lastLaunchedAt: number;
462
470
  }
463
471
 
@@ -23,6 +23,8 @@ import {
23
23
  normalizeManagedStatus,
24
24
  type NormalizeActiveChatOptions,
25
25
  } from './normalize.js';
26
+ import { normalizeProviderStateControlValues } from '../providers/provider-patch-state.js';
27
+ import { normalizeProviderSummaryMetadata } from '../providers/summary-metadata.js';
26
28
 
27
29
  export type SessionEntryProfile = 'full' | 'live' | 'metadata';
28
30
 
@@ -159,6 +161,8 @@ function buildIdeWorkspaceSession(
159
161
  ): SessionEntry {
160
162
  const profile = options.profile || 'full';
161
163
  const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
164
+ const summaryMetadata = normalizeProviderSummaryMetadata(state.summaryMetadata);
165
+ const controlValues = normalizeProviderStateControlValues(state.controlValues);
162
166
  const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
163
167
  const includeSessionControls = shouldIncludeSessionControls(profile);
164
168
  const title = activeChat?.title || state.name;
@@ -175,13 +179,11 @@ function buildIdeWorkspaceSession(
175
179
  title,
176
180
  ...(includeSessionMetadata && { workspace: state.workspace || null }),
177
181
  activeChat,
182
+ ...(summaryMetadata && { summaryMetadata }),
178
183
  ...(includeSessionMetadata && { capabilities: IDE_SESSION_CAPABILITIES }),
179
184
  cdpConnected: state.cdpConnected ?? isCdpConnected(cdpManagers, state.type),
180
- currentModel: state.currentModel,
181
- currentPlan: state.currentPlan,
182
- currentAutoApprove: state.currentAutoApprove,
183
185
  ...(includeSessionControls && {
184
- controlValues: state.controlValues,
186
+ ...(controlValues && { controlValues }),
185
187
  providerControls: state.providerControls,
186
188
  }),
187
189
  errorMessage: state.errorMessage,
@@ -197,6 +199,8 @@ function buildExtensionAgentSession(
197
199
  ): SessionEntry {
198
200
  const profile = options.profile || 'full';
199
201
  const activeChat = normalizeActiveChatData(ext.activeChat, getActiveChatOptions(profile));
202
+ const summaryMetadata = normalizeProviderSummaryMetadata(ext.summaryMetadata);
203
+ const controlValues = normalizeProviderStateControlValues(ext.controlValues);
200
204
  const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
201
205
  const includeSessionControls = shouldIncludeSessionControls(profile);
202
206
  return {
@@ -212,11 +216,10 @@ function buildExtensionAgentSession(
212
216
  title: activeChat?.title || ext.name,
213
217
  ...(includeSessionMetadata && { workspace: parent.workspace || null }),
214
218
  activeChat,
219
+ ...(summaryMetadata && { summaryMetadata }),
215
220
  ...(includeSessionMetadata && { capabilities: EXTENSION_SESSION_CAPABILITIES }),
216
- currentModel: ext.currentModel,
217
- currentPlan: ext.currentPlan,
218
221
  ...(includeSessionControls && {
219
- controlValues: ext.controlValues,
222
+ ...(controlValues && { controlValues }),
220
223
  providerControls: ext.providerControls,
221
224
  }),
222
225
  errorMessage: ext.errorMessage,
@@ -228,6 +231,8 @@ function buildExtensionAgentSession(
228
231
  function buildCliSession(state: CliProviderState, options: SessionEntryBuildOptions): SessionEntry {
229
232
  const profile = options.profile || 'full';
230
233
  const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
234
+ const summaryMetadata = normalizeProviderSummaryMetadata(state.summaryMetadata);
235
+ const controlValues = normalizeProviderStateControlValues(state.controlValues);
231
236
  const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
232
237
  const includeRuntimeMetadata = shouldIncludeRuntimeMetadata(profile);
233
238
  const includeSessionControls = shouldIncludeSessionControls(profile);
@@ -254,11 +259,12 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
254
259
  mode: state.mode,
255
260
  resume: state.resume,
256
261
  activeChat,
262
+ ...(summaryMetadata && { summaryMetadata }),
257
263
  ...(includeSessionMetadata && {
258
264
  capabilities: state.mode === 'terminal' ? PTY_SESSION_CAPABILITIES : CLI_CHAT_SESSION_CAPABILITIES,
259
265
  }),
260
266
  ...(includeSessionControls && {
261
- controlValues: state.controlValues,
267
+ ...(controlValues && { controlValues }),
262
268
  providerControls: state.providerControls,
263
269
  }),
264
270
  errorMessage: state.errorMessage,
@@ -270,6 +276,8 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
270
276
  function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOptions): SessionEntry {
271
277
  const profile = options.profile || 'full';
272
278
  const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
279
+ const summaryMetadata = normalizeProviderSummaryMetadata(state.summaryMetadata);
280
+ const controlValues = normalizeProviderStateControlValues(state.controlValues);
273
281
  const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
274
282
  const includeSessionControls = shouldIncludeSessionControls(profile);
275
283
  return {
@@ -285,13 +293,10 @@ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOpti
285
293
  title: activeChat?.title || state.name,
286
294
  ...(includeSessionMetadata && { workspace: state.workspace || null }),
287
295
  activeChat,
296
+ ...(summaryMetadata && { summaryMetadata }),
288
297
  ...(includeSessionMetadata && { capabilities: ACP_SESSION_CAPABILITIES }),
289
- currentModel: state.currentModel,
290
- currentPlan: state.currentPlan,
291
298
  ...(includeSessionControls && {
292
- acpConfigOptions: state.acpConfigOptions,
293
- acpModes: state.acpModes,
294
- controlValues: state.controlValues,
299
+ ...(controlValues && { controlValues }),
295
300
  providerControls: state.providerControls,
296
301
  }),
297
302
  errorMessage: state.errorMessage,
@@ -224,7 +224,7 @@ export class DaemonStatusReporter {
224
224
  const ideSummary = ideStates.map((s) => {
225
225
  const msgs = s.activeChat?.messages?.length || 0;
226
226
  const exts = s.extensions.length;
227
- return `${s.type}(${s.status},${msgs}msg,${exts}ext${s.currentModel ? ',model=' + s.currentModel : ''})`;
227
+ return `${s.type}(${s.status},${msgs}msg,${exts}ext)`;
228
228
  }).join(', ');
229
229
 
230
230
  // CLI summary
@@ -302,9 +302,7 @@ export class DaemonStatusReporter {
302
302
  workspace: session.workspace ?? null,
303
303
  title: session.title,
304
304
  cdpConnected: session.cdpConnected,
305
- currentModel: session.currentModel,
306
- currentPlan: session.currentPlan,
307
- currentAutoApprove: session.currentAutoApprove,
305
+ summaryMetadata: session.summaryMetadata,
308
306
  })),
309
307
  p2p: payload.p2p,
310
308
  timestamp: now,
@@ -64,6 +64,49 @@ export interface StatusSnapshotOptions {
64
64
  export type StatusSnapshot = StatusReportPayload;
65
65
 
66
66
  const READ_DEBUG_ENABLED = process.argv.includes('--dev') || process.env.ADHDEV_READ_DEBUG === '1';
67
+ const recentReadDebugSignatureBySession = new Map<string, string>();
68
+
69
+ export interface RecentReadDebugSnapshot {
70
+ sessionId: string;
71
+ providerType: string;
72
+ status: string;
73
+ inboxBucket: RecentSessionBucket;
74
+ unread: boolean;
75
+ lastSeenAt: number;
76
+ completionMarker: string;
77
+ seenCompletionMarker: string;
78
+ lastUpdated: number;
79
+ lastUsedAt: number;
80
+ lastRole: string;
81
+ messageUpdatedAt: number;
82
+ }
83
+
84
+ function buildRecentReadDebugSignature(snapshot: RecentReadDebugSnapshot): string {
85
+ return [
86
+ snapshot.providerType,
87
+ snapshot.status,
88
+ snapshot.inboxBucket,
89
+ snapshot.unread ? '1' : '0',
90
+ String(snapshot.lastSeenAt),
91
+ snapshot.completionMarker,
92
+ snapshot.seenCompletionMarker,
93
+ String(snapshot.lastUpdated),
94
+ String(snapshot.lastUsedAt),
95
+ snapshot.lastRole,
96
+ String(snapshot.messageUpdatedAt),
97
+ ].join('|');
98
+ }
99
+
100
+ export function shouldEmitRecentReadDebugLog(
101
+ cache: Map<string, string>,
102
+ snapshot: RecentReadDebugSnapshot,
103
+ ): boolean {
104
+ const nextSignature = buildRecentReadDebugSignature(snapshot);
105
+ const previousSignature = cache.get(snapshot.sessionId);
106
+ if (previousSignature === nextSignature) return false;
107
+ cache.set(snapshot.sessionId, nextSignature);
108
+ return true;
109
+ }
67
110
 
68
111
  function buildDetectedIdeInfos(
69
112
  detectedIdes: StatusSnapshotOptions['detectedIdes'],
@@ -297,7 +340,7 @@ function buildRecentLaunches(
297
340
  providerSessionId: item.providerSessionId,
298
341
  title: item.title || item.providerName,
299
342
  workspace: item.workspace,
300
- currentModel: item.currentModel,
343
+ summaryMetadata: item.summaryMetadata,
301
344
  lastLaunchedAt: item.lastUsedAt,
302
345
  }))
303
346
  .sort((a, b) => b.lastLaunchedAt - a.lastLaunchedAt)
@@ -345,9 +388,24 @@ export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnaps
345
388
  session.unread = unread;
346
389
  session.inboxBucket = inboxBucket;
347
390
  if (READ_DEBUG_ENABLED && (session.unread || session.inboxBucket !== 'idle' || session.providerType.includes('codex'))) {
391
+ const recentReadSnapshot: RecentReadDebugSnapshot = {
392
+ sessionId: session.id,
393
+ providerType: session.providerType,
394
+ status: String(session.status || ''),
395
+ inboxBucket,
396
+ unread,
397
+ lastSeenAt,
398
+ completionMarker: completionMarker || '-',
399
+ seenCompletionMarker: seenCompletionMarker || '-',
400
+ lastUpdated: Number(session.lastUpdated || 0),
401
+ lastUsedAt,
402
+ lastRole: getLastMessageRole(sourceSession),
403
+ messageUpdatedAt: getSessionMessageUpdatedAt(sourceSession),
404
+ };
405
+ if (!shouldEmitRecentReadDebugLog(recentReadDebugSignatureBySession, recentReadSnapshot)) continue;
348
406
  LOG.info(
349
407
  'RecentRead',
350
- `snapshot session id=${session.id} provider=${session.providerType} status=${String(session.status || '')} bucket=${inboxBucket} unread=${String(unread)} lastSeenAt=${lastSeenAt} completionMarker=${completionMarker || '-'} seenMarker=${seenCompletionMarker || '-'} lastUpdated=${String(session.lastUpdated || 0)} lastUsedAt=${lastUsedAt} lastRole=${getLastMessageRole(sourceSession)} msgUpdatedAt=${getSessionMessageUpdatedAt(sourceSession)}`,
408
+ `snapshot session id=${recentReadSnapshot.sessionId} provider=${recentReadSnapshot.providerType} status=${recentReadSnapshot.status} bucket=${recentReadSnapshot.inboxBucket} unread=${String(recentReadSnapshot.unread)} lastSeenAt=${recentReadSnapshot.lastSeenAt} completionMarker=${recentReadSnapshot.completionMarker} seenMarker=${recentReadSnapshot.seenCompletionMarker} lastUpdated=${String(recentReadSnapshot.lastUpdated)} lastUsedAt=${recentReadSnapshot.lastUsedAt} lastRole=${recentReadSnapshot.lastRole} msgUpdatedAt=${recentReadSnapshot.messageUpdatedAt}`,
351
409
  );
352
410
  }
353
411
  const lastDisplayMessage = getLastDisplayMessage(sourceSession);