@adhdev/daemon-core 0.8.35 → 0.8.36

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.
@@ -17,7 +17,35 @@ import type {
17
17
  ExtensionProviderState,
18
18
  ProviderState,
19
19
  } from '../providers/provider-instance.js';
20
- import { normalizeActiveChatData, normalizeManagedStatus } from './normalize.js';
20
+ import {
21
+ LIVE_STATUS_ACTIVE_CHAT_OPTIONS,
22
+ normalizeActiveChatData,
23
+ normalizeManagedStatus,
24
+ type NormalizeActiveChatOptions,
25
+ } from './normalize.js';
26
+
27
+ export type SessionEntryProfile = 'full' | 'live' | 'metadata';
28
+
29
+ export interface SessionEntryBuildOptions {
30
+ profile?: SessionEntryProfile;
31
+ }
32
+
33
+ function getActiveChatOptions(profile: SessionEntryProfile): NormalizeActiveChatOptions {
34
+ if (profile === 'full') return {};
35
+ return LIVE_STATUS_ACTIVE_CHAT_OPTIONS;
36
+ }
37
+
38
+ function shouldIncludeSessionControls(profile: SessionEntryProfile): boolean {
39
+ return profile !== 'live';
40
+ }
41
+
42
+ function shouldIncludeSessionMetadata(profile: SessionEntryProfile): boolean {
43
+ return profile !== 'live';
44
+ }
45
+
46
+ function shouldIncludeRuntimeMetadata(profile: SessionEntryProfile): boolean {
47
+ return profile !== 'live';
48
+ }
21
49
 
22
50
  // ─── CDP Manager lookup helpers ──────────────────────
23
51
 
@@ -186,33 +214,39 @@ const ACP_SESSION_CAPABILITIES: SessionCapability[] = [
186
214
  function buildIdeWorkspaceSession(
187
215
  state: IdeProviderState,
188
216
  cdpManagers: Map<string, DaemonCdpManager>,
217
+ options: SessionEntryBuildOptions,
189
218
  ): SessionEntry {
190
- const activeChat = normalizeActiveChatData(state.activeChat);
219
+ const profile = options.profile || 'full';
220
+ const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
221
+ const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
222
+ const includeSessionControls = shouldIncludeSessionControls(profile);
191
223
  const title = activeChat?.title || state.name;
192
224
  return {
193
225
  id: state.instanceId || state.type,
194
226
  parentId: null,
195
227
  providerType: state.type,
196
- providerName: state.name,
228
+ ...(includeSessionMetadata && { providerName: state.name }),
197
229
  kind: 'workspace',
198
230
  transport: 'cdp-page',
199
231
  status: normalizeManagedStatus(activeChat?.status || state.status, {
200
232
  activeModal: activeChat?.activeModal || null,
201
233
  }),
202
234
  title,
203
- workspace: state.workspace || null,
235
+ ...(includeSessionMetadata && { workspace: state.workspace || null }),
204
236
  activeChat,
205
- capabilities: IDE_SESSION_CAPABILITIES,
237
+ ...(includeSessionMetadata && { capabilities: IDE_SESSION_CAPABILITIES }),
206
238
  cdpConnected: state.cdpConnected ?? isCdpConnected(cdpManagers, state.type),
207
239
  currentModel: state.currentModel,
208
240
  currentPlan: state.currentPlan,
209
241
  currentAutoApprove: state.currentAutoApprove,
210
- controlValues: state.controlValues,
211
- providerControls: buildFallbackControls(
212
- state.providerControls,
213
- state.currentModel,
214
- state.currentPlan
215
- ),
242
+ ...(includeSessionControls && {
243
+ controlValues: state.controlValues,
244
+ providerControls: buildFallbackControls(
245
+ state.providerControls,
246
+ state.currentModel,
247
+ state.currentPlan
248
+ ),
249
+ }),
216
250
  errorMessage: state.errorMessage,
217
251
  errorReason: state.errorReason,
218
252
  lastUpdated: state.lastUpdated,
@@ -222,43 +256,53 @@ function buildIdeWorkspaceSession(
222
256
  function buildExtensionAgentSession(
223
257
  parent: IdeProviderState,
224
258
  ext: ExtensionProviderState,
259
+ options: SessionEntryBuildOptions,
225
260
  ): SessionEntry {
226
- const activeChat = normalizeActiveChatData(ext.activeChat);
261
+ const profile = options.profile || 'full';
262
+ const activeChat = normalizeActiveChatData(ext.activeChat, getActiveChatOptions(profile));
263
+ const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
264
+ const includeSessionControls = shouldIncludeSessionControls(profile);
227
265
  return {
228
266
  id: ext.instanceId || `${parent.instanceId}:${ext.type}`,
229
267
  parentId: parent.instanceId || parent.type,
230
268
  providerType: ext.type,
231
- providerName: ext.name,
269
+ ...(includeSessionMetadata && { providerName: ext.name }),
232
270
  kind: 'agent',
233
271
  transport: 'cdp-webview',
234
272
  status: normalizeManagedStatus(activeChat?.status || ext.status, {
235
273
  activeModal: activeChat?.activeModal || null,
236
274
  }),
237
275
  title: activeChat?.title || ext.name,
238
- workspace: parent.workspace || null,
276
+ ...(includeSessionMetadata && { workspace: parent.workspace || null }),
239
277
  activeChat,
240
- capabilities: EXTENSION_SESSION_CAPABILITIES,
278
+ ...(includeSessionMetadata && { capabilities: EXTENSION_SESSION_CAPABILITIES }),
241
279
  currentModel: ext.currentModel,
242
280
  currentPlan: ext.currentPlan,
243
- controlValues: ext.controlValues,
244
- providerControls: buildFallbackControls(
245
- ext.providerControls,
246
- ext.currentModel,
247
- ext.currentPlan
248
- ),
281
+ ...(includeSessionControls && {
282
+ controlValues: ext.controlValues,
283
+ providerControls: buildFallbackControls(
284
+ ext.providerControls,
285
+ ext.currentModel,
286
+ ext.currentPlan
287
+ ),
288
+ }),
249
289
  errorMessage: ext.errorMessage,
250
290
  errorReason: ext.errorReason,
251
291
  lastUpdated: ext.lastUpdated,
252
292
  };
253
293
  }
254
294
 
255
- function buildCliSession(state: CliProviderState): SessionEntry {
256
- const activeChat = normalizeActiveChatData(state.activeChat);
295
+ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOptions): SessionEntry {
296
+ const profile = options.profile || 'full';
297
+ const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
298
+ const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
299
+ const includeRuntimeMetadata = shouldIncludeRuntimeMetadata(profile);
300
+ const includeSessionControls = shouldIncludeSessionControls(profile);
257
301
  return {
258
302
  id: state.instanceId,
259
303
  parentId: null,
260
304
  providerType: state.type,
261
- providerName: state.name,
305
+ ...(includeSessionMetadata && { providerName: state.name }),
262
306
  providerSessionId: state.providerSessionId,
263
307
  kind: 'agent',
264
308
  transport: 'pty',
@@ -266,54 +310,65 @@ function buildCliSession(state: CliProviderState): SessionEntry {
266
310
  activeModal: activeChat?.activeModal || null,
267
311
  }),
268
312
  title: activeChat?.title || state.name,
269
- workspace: state.workspace || null,
270
- runtimeKey: state.runtime?.runtimeKey,
271
- runtimeDisplayName: state.runtime?.displayName,
272
- runtimeWorkspaceLabel: state.runtime?.workspaceLabel,
273
- runtimeWriteOwner: state.runtime?.writeOwner || null,
274
- runtimeAttachedClients: state.runtime?.attachedClients || [],
313
+ ...(includeSessionMetadata && { workspace: state.workspace || null }),
314
+ ...(includeRuntimeMetadata && {
315
+ runtimeKey: state.runtime?.runtimeKey,
316
+ runtimeDisplayName: state.runtime?.displayName,
317
+ runtimeWorkspaceLabel: state.runtime?.workspaceLabel,
318
+ runtimeWriteOwner: state.runtime?.writeOwner || null,
319
+ runtimeAttachedClients: state.runtime?.attachedClients || [],
320
+ }),
275
321
  mode: state.mode,
276
322
  resume: state.resume,
277
323
  activeChat,
278
- capabilities: state.mode === 'terminal' ? PTY_SESSION_CAPABILITIES : CLI_CHAT_SESSION_CAPABILITIES,
279
- controlValues: state.controlValues,
280
- providerControls: buildFallbackControls(
281
- state.providerControls
282
- ),
324
+ ...(includeSessionMetadata && {
325
+ capabilities: state.mode === 'terminal' ? PTY_SESSION_CAPABILITIES : CLI_CHAT_SESSION_CAPABILITIES,
326
+ }),
327
+ ...(includeSessionControls && {
328
+ controlValues: state.controlValues,
329
+ providerControls: buildFallbackControls(
330
+ state.providerControls
331
+ ),
332
+ }),
283
333
  errorMessage: state.errorMessage,
284
334
  errorReason: state.errorReason,
285
335
  lastUpdated: state.lastUpdated,
286
336
  };
287
337
  }
288
338
 
289
- function buildAcpSession(state: AcpProviderState): SessionEntry {
290
- const activeChat = normalizeActiveChatData(state.activeChat);
339
+ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOptions): SessionEntry {
340
+ const profile = options.profile || 'full';
341
+ const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
342
+ const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
343
+ const includeSessionControls = shouldIncludeSessionControls(profile);
291
344
  return {
292
345
  id: state.instanceId,
293
346
  parentId: null,
294
347
  providerType: state.type,
295
- providerName: state.name,
348
+ ...(includeSessionMetadata && { providerName: state.name }),
296
349
  kind: 'agent',
297
350
  transport: 'acp',
298
351
  status: normalizeManagedStatus(activeChat?.status || state.status, {
299
352
  activeModal: activeChat?.activeModal || null,
300
353
  }),
301
354
  title: activeChat?.title || state.name,
302
- workspace: state.workspace || null,
355
+ ...(includeSessionMetadata && { workspace: state.workspace || null }),
303
356
  activeChat,
304
- capabilities: ACP_SESSION_CAPABILITIES,
357
+ ...(includeSessionMetadata && { capabilities: ACP_SESSION_CAPABILITIES }),
305
358
  currentModel: state.currentModel,
306
359
  currentPlan: state.currentPlan,
307
- acpConfigOptions: state.acpConfigOptions,
308
- acpModes: state.acpModes,
309
- controlValues: state.controlValues,
310
- providerControls: buildFallbackControls(
311
- state.providerControls,
312
- state.currentModel,
313
- state.currentPlan,
314
- state.acpConfigOptions,
315
- state.acpModes
316
- ),
360
+ ...(includeSessionControls && {
361
+ acpConfigOptions: state.acpConfigOptions,
362
+ acpModes: state.acpModes,
363
+ controlValues: state.controlValues,
364
+ providerControls: buildFallbackControls(
365
+ state.providerControls,
366
+ state.currentModel,
367
+ state.currentPlan,
368
+ state.acpConfigOptions,
369
+ state.acpModes
370
+ ),
371
+ }),
317
372
  errorMessage: state.errorMessage,
318
373
  errorReason: state.errorReason,
319
374
  lastUpdated: state.lastUpdated,
@@ -323,6 +378,7 @@ function buildAcpSession(state: AcpProviderState): SessionEntry {
323
378
  export function buildSessionEntries(
324
379
  allStates: ProviderState[],
325
380
  cdpManagers: Map<string, DaemonCdpManager>,
381
+ options: SessionEntryBuildOptions = {},
326
382
  ): SessionEntry[] {
327
383
  const sessions: SessionEntry[] = [];
328
384
 
@@ -331,18 +387,18 @@ export function buildSessionEntries(
331
387
  const acpStates = allStates.filter((s): s is AcpProviderState => s.category === 'acp');
332
388
 
333
389
  for (const state of ideStates) {
334
- sessions.push(buildIdeWorkspaceSession(state, cdpManagers));
390
+ sessions.push(buildIdeWorkspaceSession(state, cdpManagers, options));
335
391
  for (const ext of state.extensions as ExtensionProviderState[]) {
336
- sessions.push(buildExtensionAgentSession(state, ext));
392
+ sessions.push(buildExtensionAgentSession(state, ext, options));
337
393
  }
338
394
  }
339
395
 
340
396
  for (const state of cliStates) {
341
- sessions.push(buildCliSession(state));
397
+ sessions.push(buildCliSession(state, options));
342
398
  }
343
399
 
344
400
  for (const state of acpStates) {
345
- sessions.push(buildAcpSession(state));
401
+ sessions.push(buildAcpSession(state, options));
346
402
  }
347
403
 
348
404
  // Hide native IDE parent rows from inbox/recent surfaces when extension tabs exist.
@@ -20,13 +20,39 @@ const WORKING_STATUSES = new Set([
20
20
  'active',
21
21
  ]);
22
22
 
23
- // Status snapshots are sent over P2P every 5s, so keep only a recent live window here.
24
- // Older history is fetched on demand via `chat_history`, and CLI terminals stream via runtime events.
25
- const STATUS_ACTIVE_CHAT_MESSAGE_LIMIT = 60;
26
- const STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT = 96 * 1024;
27
- const STATUS_ACTIVE_CHAT_STRING_LIMIT = 4 * 1024;
28
- const STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT = 1024;
29
- const STATUS_INPUT_CONTENT_LIMIT = 2 * 1024;
23
+ export interface NormalizeActiveChatOptions {
24
+ includeMessages?: boolean;
25
+ includeInputContent?: boolean;
26
+ includeActiveModal?: boolean;
27
+ messageLimit?: number;
28
+ totalBytesLimit?: number;
29
+ stringLimit?: number;
30
+ fallbackStringLimit?: number;
31
+ }
32
+
33
+ // Full snapshots are still capped, but can carry recent chat context for API/inspection use.
34
+ const FULL_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {
35
+ includeMessages: true,
36
+ includeInputContent: true,
37
+ includeActiveModal: true,
38
+ messageLimit: 60,
39
+ totalBytesLimit: 96 * 1024,
40
+ stringLimit: 4 * 1024,
41
+ fallbackStringLimit: 1024,
42
+ };
43
+
44
+ // Live/metadata snapshots only need routing + UI summary. Current chat text is loaded
45
+ // on demand via `read_chat`, and older history via `chat_history`.
46
+ export const LIVE_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {
47
+ includeMessages: false,
48
+ includeInputContent: false,
49
+ includeActiveModal: false,
50
+ messageLimit: 0,
51
+ totalBytesLimit: 0,
52
+ stringLimit: 512,
53
+ fallbackStringLimit: 256,
54
+ };
55
+
30
56
  const STATUS_MODAL_MESSAGE_LIMIT = 2 * 1024;
31
57
  const STATUS_MODAL_BUTTON_LIMIT = 120;
32
58
 
@@ -81,23 +107,27 @@ function normalizeMessageTime(message: unknown): unknown {
81
107
  return msg;
82
108
  }
83
109
 
84
- function trimMessagesForStatus(messages: unknown[] | null | undefined): unknown[] {
110
+ function trimMessagesForStatus(
111
+ messages: unknown[] | null | undefined,
112
+ options: Required<NormalizeActiveChatOptions>,
113
+ ): unknown[] {
114
+ if (!options.includeMessages || options.messageLimit <= 0 || options.totalBytesLimit <= 0) return [];
85
115
  if (!Array.isArray(messages) || messages.length === 0) return [];
86
116
 
87
- const recent = messages.slice(-STATUS_ACTIVE_CHAT_MESSAGE_LIMIT);
117
+ const recent = messages.slice(-options.messageLimit);
88
118
  const kept: unknown[] = [];
89
119
  let totalBytes = 0;
90
120
 
91
121
  for (let i = recent.length - 1; i >= 0; i -= 1) {
92
- let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_STRING_LIMIT));
122
+ let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.stringLimit));
93
123
  let size = estimateBytes(normalized);
94
124
 
95
- if (size > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
96
- normalized = normalizeMessageTime(trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT));
125
+ if (size > options.totalBytesLimit) {
126
+ normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.fallbackStringLimit));
97
127
  size = estimateBytes(normalized);
98
128
  }
99
129
 
100
- if (kept.length > 0 && (totalBytes + size) > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
130
+ if (kept.length > 0 && (totalBytes + size) > options.totalBytesLimit) {
101
131
  continue;
102
132
  }
103
133
 
@@ -143,20 +173,25 @@ export function isManagedStatusWaiting(
143
173
 
144
174
  export function normalizeActiveChatData<T extends ActiveChatData | null | undefined>(
145
175
  activeChat: T,
176
+ options: NormalizeActiveChatOptions = FULL_STATUS_ACTIVE_CHAT_OPTIONS,
146
177
  ): T {
147
178
  if (!activeChat) return activeChat;
179
+ const resolvedOptions: Required<NormalizeActiveChatOptions> = {
180
+ ...FULL_STATUS_ACTIVE_CHAT_OPTIONS,
181
+ ...options,
182
+ };
148
183
  return {
149
184
  ...activeChat,
150
185
  status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),
151
- messages: trimMessagesForStatus(activeChat.messages) as T extends { messages: infer M } ? M : never,
152
- activeModal: activeChat.activeModal ? {
186
+ messages: trimMessagesForStatus(activeChat.messages, resolvedOptions) as T extends { messages: infer M } ? M : never,
187
+ activeModal: resolvedOptions.includeActiveModal && activeChat.activeModal ? {
153
188
  message: truncateString(activeChat.activeModal.message || '', STATUS_MODAL_MESSAGE_LIMIT),
154
189
  buttons: (activeChat.activeModal.buttons || []).map((button) =>
155
190
  truncateString(String(button || ''), STATUS_MODAL_BUTTON_LIMIT)
156
191
  ),
157
- } : activeChat.activeModal,
158
- inputContent: activeChat.inputContent
159
- ? truncateString(activeChat.inputContent, STATUS_INPUT_CONTENT_LIMIT)
160
- : activeChat.inputContent,
192
+ } : null,
193
+ inputContent: resolvedOptions.includeInputContent && activeChat.inputContent
194
+ ? truncateString(activeChat.inputContent, 2 * 1024)
195
+ : undefined,
161
196
  } as T;
162
197
  }
@@ -8,6 +8,7 @@
8
8
  import { LOG } from '../logging/logger.js';
9
9
  import type { DaemonCdpManager } from '../cdp/manager.js';
10
10
  import type { MachineInfo } from '../shared-types.js';
11
+ import type { CloudStatusReportPayload, DaemonStatusEventPayload } from '../shared-types.js';
11
12
  import { buildSessionEntries } from './builders.js';
12
13
  import { buildStatusSnapshot } from './snapshot.js';
13
14
  import type {
@@ -22,7 +23,15 @@ import type {
22
23
  export interface StatusReporterDeps {
23
24
  serverConn: { isConnected(): boolean; sendMessage(type: string, data: any): void; getUserPlan(): string } | null;
24
25
  cdpManagers: Map<string, DaemonCdpManager>;
25
- p2p: { isConnected: boolean; isAvailable: boolean; connectionState: string; connectedPeerCount: number; screenshotActive: boolean; sendStatus(data: any): void } | null;
26
+ p2p: {
27
+ isConnected: boolean;
28
+ isAvailable: boolean;
29
+ connectionState: string;
30
+ connectedPeerCount: number;
31
+ screenshotActive: boolean;
32
+ sendStatus(data: any): void;
33
+ sendStatusEvent(event: DaemonStatusEventPayload): void;
34
+ } | null;
26
35
  providerLoader: { resolve(type: string): any; getAll(): any[] };
27
36
  detectedIdes: any[];
28
37
  instanceId: string;
@@ -94,10 +103,73 @@ export class DaemonStatusReporter {
94
103
  }
95
104
  }
96
105
 
106
+ private toDaemonStatusEventName(value: unknown): DaemonStatusEventPayload['event'] | null {
107
+ switch (value) {
108
+ case 'agent:generating_started':
109
+ case 'agent:waiting_approval':
110
+ case 'agent:generating_completed':
111
+ case 'agent:stopped':
112
+ case 'monitor:long_generating':
113
+ return value;
114
+ default:
115
+ return null;
116
+ }
117
+ }
118
+
119
+ private buildServerStatusEvent(event: Record<string, unknown>): DaemonStatusEventPayload | null {
120
+ const eventName = this.toDaemonStatusEventName(event.event);
121
+ if (!eventName) return null;
122
+
123
+ // Provider UI effects can carry arbitrary text content and are not required
124
+ // for server-side routing, push, or dashboard session targeting.
125
+ if (eventName.startsWith('provider:')) {
126
+ return null;
127
+ }
128
+
129
+ const payload: DaemonStatusEventPayload = {
130
+ event: eventName,
131
+ timestamp: typeof event.timestamp === 'number' && Number.isFinite(event.timestamp)
132
+ ? event.timestamp
133
+ : Date.now(),
134
+ };
135
+
136
+ if (typeof event.targetSessionId === 'string' && event.targetSessionId.trim()) {
137
+ payload.targetSessionId = event.targetSessionId.trim();
138
+ }
139
+ const providerType = typeof event.providerType === 'string' && event.providerType.trim()
140
+ ? event.providerType.trim()
141
+ : (typeof event.ideType === 'string' && event.ideType.trim() ? event.ideType.trim() : '');
142
+ if (providerType) {
143
+ payload.providerType = providerType;
144
+ }
145
+ if (typeof event.duration === 'number' && Number.isFinite(event.duration)) {
146
+ payload.duration = event.duration;
147
+ }
148
+ if (typeof event.elapsedSec === 'number' && Number.isFinite(event.elapsedSec)) {
149
+ payload.elapsedSec = event.elapsedSec;
150
+ }
151
+ if (typeof event.modalMessage === 'string' && event.modalMessage.trim()) {
152
+ payload.modalMessage = event.modalMessage;
153
+ }
154
+ if (Array.isArray(event.modalButtons)) {
155
+ const modalButtons = event.modalButtons
156
+ .filter((button): button is string => typeof button === 'string' && button.trim().length > 0);
157
+ if (modalButtons.length > 0) {
158
+ payload.modalButtons = modalButtons;
159
+ }
160
+ }
161
+
162
+ return payload;
163
+ }
164
+
97
165
  emitStatusEvent(event: Record<string, unknown>): void {
98
166
  LOG.info('StatusEvent', `${event.event} (${event.providerType || event.ideType || ''})`);
99
- // Send via WS (server relay → dashboard + push notifications)
100
- this.deps.serverConn?.sendMessage('status_event', event);
167
+ const serverEvent = this.buildServerStatusEvent(event);
168
+ if (!serverEvent) return;
169
+ // Dashboard delivery is P2P-only, but the server still receives the event
170
+ // for push notifications, webhook dispatch, and audit-side effects.
171
+ this.deps.p2p?.sendStatusEvent(serverEvent);
172
+ this.deps.serverConn?.sendMessage('status_event', serverEvent);
101
173
  }
102
174
 
103
175
  removeAgentTracking(_key: string): void { /* Managed by Instance itself */ }
@@ -189,7 +261,6 @@ export class DaemonStatusReporter {
189
261
  detectedIdes: this.deps.detectedIdes || [],
190
262
  instanceId: this.deps.instanceId,
191
263
  version: this.deps.daemonVersion || 'unknown',
192
- daemonMode: true,
193
264
  timestamp: now,
194
265
  p2p: {
195
266
  available: p2p?.isAvailable || false,
@@ -197,9 +268,9 @@ export class DaemonStatusReporter {
197
268
  peers: p2p?.connectedPeerCount || 0,
198
269
  screenshotActive: p2p?.screenshotActive || false,
199
270
  },
271
+ profile: 'live',
200
272
  }),
201
273
  screenshotUsage: this.deps.getScreenshotUsage?.() || null,
202
- connectedExtensions: [],
203
274
  };
204
275
 
205
276
  // ═══ P2P transmit ═══
@@ -219,17 +290,16 @@ export class DaemonStatusReporter {
219
290
  if (opts?.p2pOnly) return;
220
291
  // Server relay only needs compact session metadata for routing, compact status,
221
292
  // initial_state fallback, and lightweight API/session inspection.
222
- const wsPayload = {
223
- daemonMode: true,
293
+ const wsPayload: CloudStatusReportPayload = {
224
294
  sessions: sessions.map((session) => ({
225
295
  id: session.id,
226
296
  parentId: session.parentId,
227
297
  providerType: session.providerType,
228
- providerName: session.providerName,
298
+ providerName: session.providerName || session.providerType,
229
299
  kind: session.kind,
230
300
  transport: session.transport,
231
301
  status: session.status,
232
- workspace: session.workspace,
302
+ workspace: session.workspace ?? null,
233
303
  title: session.title,
234
304
  cdpConnected: session.cdpConnected,
235
305
  currentModel: session.currentModel,
@@ -238,8 +308,6 @@ export class DaemonStatusReporter {
238
308
  })),
239
309
  p2p: payload.p2p,
240
310
  timestamp: now,
241
- detectedIdes: payload.detectedIdes,
242
- availableProviders: payload.availableProviders,
243
311
  };
244
312
  const wsHash = this.simpleHash(JSON.stringify({
245
313
  ...wsPayload,
@@ -258,12 +326,19 @@ export class DaemonStatusReporter {
258
326
 
259
327
  private sendP2PPayload(payload: { timestamp?: number; system?: unknown; machine?: MachineInfo; [key: string]: unknown }): boolean {
260
328
  const { timestamp: _ts, system: _sys, ...hashTarget } = payload;
329
+ const sessions = Array.isArray(hashTarget.sessions)
330
+ ? hashTarget.sessions.map((session) => {
331
+ if (!session || typeof session !== 'object') return session;
332
+ const { lastUpdated: _lu, ...stableSession } = session as Record<string, unknown>;
333
+ return stableSession;
334
+ })
335
+ : hashTarget.sessions;
261
336
  const hashPayload = hashTarget.machine
262
337
  ? (() => {
263
338
  const { freeMem: _f, availableMem: _a, loadavg: _l, uptime: _u, ...stableMachine } = hashTarget.machine;
264
- return { ...hashTarget, machine: stableMachine };
339
+ return { ...hashTarget, sessions, machine: stableMachine };
265
340
  })()
266
- : hashTarget;
341
+ : { ...hashTarget, sessions };
267
342
  const h = this.simpleHash(JSON.stringify(hashPayload));
268
343
  if (h !== this.lastP2PStatusHash) {
269
344
  this.lastP2PStatusHash = h;