@adhdev/daemon-core 0.9.18 → 0.9.20

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.
@@ -26,7 +26,15 @@ export interface CliSessionStatus {
26
26
  errorReason?: string;
27
27
  }
28
28
 
29
+ export interface ParsedSession {
30
+ status: string;
31
+ messages: any[];
32
+ modal: { message: string; buttons: string[] } | null;
33
+ parsedStatus: string | null;
34
+ }
35
+
29
36
  export interface CliScripts {
37
+ parseSession?: (input: CliScriptInput & { tail?: string; tailScreen?: CliScreenSnapshot }) => ParsedSession | null;
30
38
  parseOutput?: (input: CliScriptInput) => any;
31
39
  detectStatus?: (input: CliStatusInput) => string | null;
32
40
  parseApproval?: (input: CliApprovalInput) => { message: string; buttons: string[] } | null;
@@ -190,30 +190,31 @@ function buildReadChatReplayCollapseSignature(message: ChatMessage | null | unde
190
190
  function shouldCollapseReadChatReplayDuplicate(message: ChatMessage | null | undefined): boolean {
191
191
  if (!message) return false;
192
192
  const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
193
- if (role !== 'assistant' && role !== 'system') return false;
194
- const kind = typeof message.kind === 'string' ? message.kind.trim().toLowerCase() : 'standard';
195
- return kind === 'tool' || kind === 'terminal' || kind === 'thought' || kind === 'system';
193
+ return role === 'assistant' || role === 'system';
196
194
  }
197
195
 
198
196
  function collapseReplayDuplicatesFromReadChat(messages: ChatMessage[]): ChatMessage[] {
199
197
  const collapsed: ChatMessage[] = [];
200
- let lastReplayTurnSignature = '';
198
+ const replaySignaturesInCurrentTurn = new Set<string>();
201
199
 
202
200
  for (const message of messages) {
201
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
202
+ if (role === 'user') {
203
+ replaySignaturesInCurrentTurn.clear();
204
+ }
205
+
203
206
  const signature = buildReadChatReplayCollapseSignature(message);
204
207
  const previous = collapsed[collapsed.length - 1];
205
208
  const previousSignature = buildReadChatReplayCollapseSignature(previous);
206
209
 
207
210
  if (shouldCollapseReadChatReplayDuplicate(message) && signature) {
208
211
  if (previousSignature === signature) continue;
209
- if (lastReplayTurnSignature === signature) continue;
212
+ if (replaySignaturesInCurrentTurn.has(signature)) continue;
210
213
  }
211
214
 
212
215
  collapsed.push(message);
213
216
  if (shouldCollapseReadChatReplayDuplicate(message) && signature) {
214
- lastReplayTurnSignature = signature;
215
- } else if ((message.role || '').toLowerCase() === 'user') {
216
- lastReplayTurnSignature = '';
217
+ replaySignaturesInCurrentTurn.add(signature);
217
218
  }
218
219
  }
219
220
 
package/src/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Core logic for daemon: CDP, Provider, IDE detection, CLI/ACP adapters and more.
5
5
  */
6
6
  export type { ChatBubbleState, ChatMessage, ExtensionInfo, CommandResult as CoreCommandResult, ProviderConfig, DaemonEvent, StatusResponse, SystemInfo, DetectedIde, ProviderInfo, AgentEntry, } from './types.js';
7
- export type { SessionEntry, CompactSessionEntry, CompactDaemonEntry, SessionTransport, SessionKind, SessionCapability, AgentSessionStream, ReadChatCursor, ReadChatSyncMode, ReadChatSyncResult, TransportTopic, SessionChatTailSubscriptionParams, MachineRuntimeSubscriptionParams, SessionHostDiagnosticsSubscriptionParams, SessionModalSubscriptionParams, DaemonMetadataSubscriptionParams, SessionChatTailUpdate, MachineRuntimeUpdate, SessionHostDiagnosticsUpdate, SessionModalUpdate, DaemonMetadataUpdate, TopicUpdateEnvelope, SubscribeRequest, UnsubscribeRequest, StandaloneWsStatusPayload, AvailableProviderInfo, AcpConfigOption, AcpMode, ProviderControlSchema, StatusReportPayload, MachineInfo, SessionHostDiagnosticsSnapshot, SessionHostRecord, SessionHostWriteOwner, SessionHostAttachedClient, SessionHostLogEntry, SessionHostRequestTrace, SessionHostRuntimeTransition, DetectedIdeInfo, WorkspaceEntry, ProviderSummaryItem, ProviderSummaryMetadata, ProviderState, ProviderStatus, ProviderErrorReason, ActiveChatData, IdeProviderState, CliProviderState, AcpProviderState, ExtensionProviderState, } from './shared-types.js';
7
+ export type { SessionEntry, CompactSessionEntry, CompactDaemonEntry, SessionTransport, SessionKind, SessionCapability, AgentSessionStream, ReadChatCursor, ReadChatSyncMode, ReadChatSyncResult, TransportTopic, SessionChatTailSubscriptionParams, SessionRuntimeOutputSubscriptionParams, MachineRuntimeSubscriptionParams, SessionHostDiagnosticsSubscriptionParams, SessionModalSubscriptionParams, DaemonMetadataSubscriptionParams, SessionChatTailUpdate, SessionRuntimeOutputUpdate, MachineRuntimeUpdate, SessionHostDiagnosticsUpdate, SessionModalUpdate, DaemonMetadataUpdate, TopicUpdateEnvelope, SubscribeRequest, UnsubscribeRequest, StandaloneWsStatusPayload, AvailableProviderInfo, AcpConfigOption, AcpMode, ProviderControlSchema, StatusReportPayload, MachineInfo, SessionHostDiagnosticsSnapshot, SessionHostRecord, SessionHostWriteOwner, SessionHostAttachedClient, SessionHostLogEntry, SessionHostRequestTrace, SessionHostRuntimeTransition, DetectedIdeInfo, WorkspaceEntry, ProviderSummaryItem, ProviderSummaryMetadata, ProviderState, ProviderStatus, ProviderErrorReason, ActiveChatData, IdeProviderState, CliProviderState, AcpProviderState, ExtensionProviderState, } from './shared-types.js';
8
8
  import type { RuntimeWriteOwner as _RuntimeWriteOwner } from './shared-types-extra.js';
9
9
  import type { RuntimeAttachedClient as _RuntimeAttachedClient } from './shared-types-extra.js';
10
10
  import type { RecentLaunchEntry as _RecentLaunchEntry } from './shared-types.js';
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export type {
39
39
  ReadChatSyncResult,
40
40
  TransportTopic,
41
41
  SessionChatTailSubscriptionParams,
42
+ SessionRuntimeOutputSubscriptionParams,
42
43
  MachineRuntimeSubscriptionParams,
43
44
  SessionHostDiagnosticsSubscriptionParams,
44
45
  SessionModalSubscriptionParams,
@@ -66,11 +66,14 @@ export interface ProviderSummaryItem {
66
66
  export interface ProviderSummaryMetadata {
67
67
  items: ProviderSummaryItem[];
68
68
  }
69
- export type TransportTopic = 'session.chat_tail' | 'machine.runtime' | 'session_host.diagnostics' | 'session.modal' | 'daemon.metadata';
69
+ export type TransportTopic = 'session.chat_tail' | 'session.runtime_output' | 'machine.runtime' | 'session_host.diagnostics' | 'session.modal' | 'daemon.metadata';
70
70
  export interface SessionChatTailSubscriptionParams extends ReadChatCursor {
71
71
  targetSessionId: string;
72
72
  historySessionId?: string;
73
73
  }
74
+ export interface SessionRuntimeOutputSubscriptionParams {
75
+ targetSessionId: string;
76
+ }
74
77
  export interface MachineRuntimeSubscriptionParams {
75
78
  intervalMs?: number;
76
79
  }
@@ -93,6 +96,13 @@ export interface SessionChatTailUpdate extends ReadChatSyncResult {
93
96
  seq: number;
94
97
  timestamp: number;
95
98
  }
99
+ export interface SessionRuntimeOutputUpdate {
100
+ topic: 'session.runtime_output';
101
+ key: string;
102
+ sessionId: string;
103
+ seq: number;
104
+ timestamp: number;
105
+ }
96
106
  export interface MachineRuntimeUpdate {
97
107
  topic: 'machine.runtime';
98
108
  key: string;
@@ -129,6 +139,7 @@ export interface DaemonMetadataUpdate {
129
139
  }
130
140
  export interface TopicUpdateEnvelopeMap {
131
141
  'session.chat_tail': SessionChatTailUpdate;
142
+ 'session.runtime_output': SessionRuntimeOutputUpdate;
132
143
  'machine.runtime': MachineRuntimeUpdate;
133
144
  'session_host.diagnostics': SessionHostDiagnosticsUpdate;
134
145
  'session.modal': SessionModalUpdate;
@@ -137,6 +148,7 @@ export interface TopicUpdateEnvelopeMap {
137
148
  export type TopicUpdateEnvelope = TopicUpdateEnvelopeMap[TransportTopic];
138
149
  export interface SubscribeRequestMap {
139
150
  'session.chat_tail': SessionChatTailSubscriptionParams;
151
+ 'session.runtime_output': SessionRuntimeOutputSubscriptionParams;
140
152
  'machine.runtime': MachineRuntimeSubscriptionParams;
141
153
  'session_host.diagnostics': SessionHostDiagnosticsSubscriptionParams;
142
154
  'session.modal': SessionModalSubscriptionParams;
@@ -178,13 +178,17 @@ export interface SessionHostDiagnosticsSnapshot {
178
178
  recentTransitions: SessionHostRuntimeTransition[];
179
179
  }
180
180
 
181
- export type TransportTopic = 'session.chat_tail' | 'machine.runtime' | 'session_host.diagnostics' | 'session.modal' | 'daemon.metadata';
181
+ export type TransportTopic = 'session.chat_tail' | 'session.runtime_output' | 'machine.runtime' | 'session_host.diagnostics' | 'session.modal' | 'daemon.metadata';
182
182
 
183
183
  export interface SessionChatTailSubscriptionParams extends ReadChatCursor {
184
184
  targetSessionId: string;
185
185
  historySessionId?: string;
186
186
  }
187
187
 
188
+ export interface SessionRuntimeOutputSubscriptionParams {
189
+ targetSessionId: string;
190
+ }
191
+
188
192
  export interface MachineRuntimeSubscriptionParams {
189
193
  intervalMs?: number;
190
194
  }
@@ -213,6 +217,14 @@ export interface SessionChatTailUpdate extends ReadChatSyncResult {
213
217
  timestamp: number;
214
218
  }
215
219
 
220
+ export interface SessionRuntimeOutputUpdate {
221
+ topic: 'session.runtime_output';
222
+ key: string;
223
+ sessionId: string;
224
+ seq: number;
225
+ timestamp: number;
226
+ }
227
+
216
228
  export interface MachineRuntimeUpdate {
217
229
  topic: 'machine.runtime';
218
230
  key: string;
@@ -254,6 +266,7 @@ export interface DaemonMetadataUpdate {
254
266
 
255
267
  export interface TopicUpdateEnvelopeMap {
256
268
  'session.chat_tail': SessionChatTailUpdate;
269
+ 'session.runtime_output': SessionRuntimeOutputUpdate;
257
270
  'machine.runtime': MachineRuntimeUpdate;
258
271
  'session_host.diagnostics': SessionHostDiagnosticsUpdate;
259
272
  'session.modal': SessionModalUpdate;
@@ -264,6 +277,7 @@ export type TopicUpdateEnvelope = TopicUpdateEnvelopeMap[TransportTopic];
264
277
 
265
278
  export interface SubscribeRequestMap {
266
279
  'session.chat_tail': SessionChatTailSubscriptionParams;
280
+ 'session.runtime_output': SessionRuntimeOutputSubscriptionParams;
267
281
  'machine.runtime': MachineRuntimeSubscriptionParams;
268
282
  'session_host.diagnostics': SessionHostDiagnosticsSubscriptionParams;
269
283
  'session.modal': SessionModalSubscriptionParams;
@@ -155,14 +155,14 @@ function buildIdeWorkspaceSession(
155
155
  id: state.instanceId || state.type,
156
156
  parentId: null,
157
157
  providerType: state.type,
158
- ...(includeSessionMetadata && { providerName: state.name }),
158
+ providerName: state.name,
159
159
  kind: 'workspace',
160
160
  transport: 'cdp-page',
161
161
  status: normalizeManagedStatus(activeChat?.status || state.status, {
162
162
  activeModal: activeChat?.activeModal || null,
163
163
  }),
164
164
  title,
165
- ...(includeSessionMetadata && { workspace: state.workspace || null }),
165
+ workspace: state.workspace || null,
166
166
  activeChat,
167
167
  ...(summaryMetadata && { summaryMetadata }),
168
168
  ...(includeSessionMetadata && { capabilities: state.sessionCapabilities || IDE_SESSION_CAPABILITIES }),
@@ -192,7 +192,7 @@ function buildExtensionAgentSession(
192
192
  id: ext.instanceId || `${parent.instanceId}:${ext.type}`,
193
193
  parentId: parent.instanceId || parent.type,
194
194
  providerType: ext.type,
195
- ...(includeSessionMetadata && { providerName: ext.name }),
195
+ providerName: ext.name,
196
196
  providerSessionId: ext.providerSessionId,
197
197
  kind: 'agent',
198
198
  transport: 'cdp-webview',
@@ -200,7 +200,7 @@ function buildExtensionAgentSession(
200
200
  activeModal: activeChat?.activeModal || null,
201
201
  }),
202
202
  title: activeChat?.title || ext.name,
203
- ...(includeSessionMetadata && { workspace: parent.workspace || null }),
203
+ workspace: parent.workspace || null,
204
204
  activeChat,
205
205
  ...(summaryMetadata && { summaryMetadata }),
206
206
  ...(includeSessionMetadata && { capabilities: ext.sessionCapabilities || EXTENSION_SESSION_CAPABILITIES }),
@@ -253,7 +253,7 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
253
253
  id: state.instanceId,
254
254
  parentId: null,
255
255
  providerType: state.type,
256
- ...(includeSessionMetadata && { providerName: state.name }),
256
+ providerName: state.name,
257
257
  providerSessionId: state.providerSessionId,
258
258
  kind: 'agent',
259
259
  transport: 'pty',
@@ -261,7 +261,7 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
261
261
  activeModal: activeChat?.activeModal || null,
262
262
  }),
263
263
  title: activeChat?.title || state.name,
264
- ...(includeSessionMetadata && { workspace: state.workspace || null }),
264
+ workspace: state.workspace || null,
265
265
  ...(includeRuntimeMetadata && {
266
266
  runtimeKey: state.runtime?.runtimeKey,
267
267
  runtimeDisplayName: state.runtime?.displayName,
@@ -301,14 +301,14 @@ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOpti
301
301
  id: state.instanceId,
302
302
  parentId: null,
303
303
  providerType: state.type,
304
- ...(includeSessionMetadata && { providerName: state.name }),
304
+ providerName: state.name,
305
305
  kind: 'agent',
306
306
  transport: 'acp',
307
307
  status: normalizeManagedStatus(activeChat?.status || state.status, {
308
308
  activeModal: activeChat?.activeModal || null,
309
309
  }),
310
310
  title: activeChat?.title || state.name,
311
- ...(includeSessionMetadata && { workspace: state.workspace || null }),
311
+ workspace: state.workspace || null,
312
312
  activeChat,
313
313
  ...(summaryMetadata && { summaryMetadata }),
314
314
  ...(includeSessionMetadata && { capabilities: ACP_SESSION_CAPABILITIES }),
@@ -14,7 +14,6 @@ import {
14
14
  import type { DaemonCdpManager } from '../cdp/manager.js';
15
15
  import type { MachineInfo } from '../shared-types.js';
16
16
  import type { CloudStatusReportPayload, DaemonStatusEventPayload } from '../shared-types.js';
17
- import { buildSessionEntries } from './builders.js';
18
17
  import { buildStatusSnapshot } from './snapshot.js';
19
18
  import type {
20
19
  ProviderState,
@@ -258,13 +257,7 @@ export class DaemonStatusReporter {
258
257
  }
259
258
  }
260
259
 
261
- // IDE/CLI/ACP states managed entries (shared builder)
262
- const sessions = buildSessionEntries(
263
- allStates,
264
- this.deps.cdpManagers,
265
- );
266
-
267
- // ═══ Assemble payload (P2P — required data only) ═══
260
+ // ═══ Assemble payload (P2P required data only) ═══
268
261
  const payload: Record<string, any> = {
269
262
  ...buildStatusSnapshot({
270
263
  allStates,
@@ -286,9 +279,9 @@ export class DaemonStatusReporter {
286
279
  };
287
280
 
288
281
  // ═══ P2P transmit ═══
289
- const payloadBytes = JSON.stringify(payload).length;
290
282
  const p2pSent = this.sendP2PPayload(payload);
291
283
  if (p2pSent) {
284
+ const payloadBytes = JSON.stringify(payload).length;
292
285
  LOG.debug('P2P', `sent (${payloadBytes} bytes)`);
293
286
  if (payloadBytes > 256 * 1024) {
294
287
  LOG.warn(
@@ -300,10 +293,12 @@ export class DaemonStatusReporter {
300
293
 
301
294
  // ═══ Server transmit (minimal routing meta only) ═══
302
295
  if (opts?.p2pOnly) return;
296
+ if (!serverConnected || !serverConn) return;
303
297
  // Server relay only needs compact session metadata for routing, compact status,
304
298
  // initial_state fallback, and lightweight API/session inspection.
299
+ const payloadSessions = Array.isArray(payload.sessions) ? payload.sessions : [];
305
300
  const wsPayload: CloudStatusReportPayload = {
306
- sessions: sessions.map((session) => ({
301
+ sessions: payloadSessions.map((session) => ({
307
302
  id: session.id,
308
303
  parentId: session.parentId,
309
304
  providerType: session.providerType,
@@ -329,8 +324,9 @@ export class DaemonStatusReporter {
329
324
  return;
330
325
  }
331
326
  this.lastServerStatusHash = wsHash;
327
+ const wsPayloadBytes = JSON.stringify(wsPayload).length;
332
328
  serverConn.sendMessage('status_report', wsPayload);
333
- LOG.debug('Server', `sent status_report (${JSON.stringify(wsPayload).length} bytes)${opts?.reason ? ` [${opts.reason}]` : ''}`);
329
+ LOG.debug('Server', `sent status_report (${wsPayloadBytes} bytes)${opts?.reason ? ` [${opts.reason}]` : ''}`);
334
330
  }
335
331
 
336
332
  // ─── P2P ─────────────────────────────────────────
@@ -16,6 +16,7 @@ import { getTerminalBackendRuntimeStatus } from '../cli-adapters/terminal-screen
16
16
  import { LOG } from '../logging/logger.js';
17
17
  import type { DaemonCdpManager } from '../cdp/manager.js';
18
18
  import { buildSessionEntries, isCdpConnected, type SessionEntryProfile } from './builders.js';
19
+ import { LIVE_STATUS_ACTIVE_CHAT_OPTIONS, normalizeActiveChatData } from './normalize.js';
19
20
  import type { ProviderState } from '../providers/provider-instance.js';
20
21
  import type {
21
22
  AvailableProviderInfo,
@@ -336,6 +337,19 @@ function getUnreadState(
336
337
  return { unread, inboxBucket: unread ? 'task_complete' : 'idle' };
337
338
  }
338
339
 
340
+ function projectLiveSessionFromFull(session: SessionEntry): SessionEntry {
341
+ const {
342
+ capabilities: _capabilities,
343
+ controlValues: _controlValues,
344
+ providerControls: _providerControls,
345
+ ...rest
346
+ } = session as SessionEntry & Record<string, unknown>;
347
+ return {
348
+ ...rest,
349
+ activeChat: normalizeActiveChatData(session.activeChat as any, LIVE_STATUS_ACTIVE_CHAT_OPTIONS),
350
+ } as SessionEntry;
351
+ }
352
+
339
353
  function buildRecentLaunches(
340
354
  recentActivity: ReturnType<typeof getRecentActivity>,
341
355
  ): RecentLaunchEntry[] {
@@ -368,11 +382,13 @@ export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnaps
368
382
  );
369
383
  const sessions = profile === 'full'
370
384
  ? unreadSourceSessions
371
- : buildSessionEntries(
372
- options.allStates,
373
- options.cdpManagers,
374
- { profile },
375
- );
385
+ : profile === 'live'
386
+ ? unreadSourceSessions.map(projectLiveSessionFromFull)
387
+ : buildSessionEntries(
388
+ options.allStates,
389
+ options.cdpManagers,
390
+ { profile },
391
+ );
376
392
  const sessionsById = new Map(sessions.map((session) => [session.id, session]));
377
393
  for (const sourceSession of unreadSourceSessions) {
378
394
  const session = sessionsById.get(sourceSession.id);