@adhdev/daemon-core 0.8.34 → 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.
Files changed (109) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +4 -0
  2. package/dist/commands/router.d.ts +3 -0
  3. package/dist/config/config.d.ts +5 -0
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +585 -256
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +584 -256
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/shared-types.d.ts +286 -16
  10. package/dist/status/builders.d.ts +5 -1
  11. package/dist/status/normalize.d.ts +11 -1
  12. package/dist/status/normalize.js +36 -16
  13. package/dist/status/normalize.js.map +1 -1
  14. package/dist/status/normalize.mjs +35 -16
  15. package/dist/status/normalize.mjs.map +1 -1
  16. package/dist/status/reporter.d.ts +7 -0
  17. package/dist/status/snapshot.d.ts +5 -5
  18. package/dist/types.d.ts +1 -1
  19. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  20. package/package.json +1 -1
  21. package/src/agent-stream/forward.d.ts +8 -0
  22. package/src/agent-stream/index.d.ts +6 -0
  23. package/src/agent-stream/manager.d.ts +60 -0
  24. package/src/agent-stream/poller.d.ts +41 -0
  25. package/src/agent-stream/provider-adapter.d.ts +36 -0
  26. package/src/agent-stream/types.d.ts +68 -0
  27. package/src/boot/daemon-lifecycle.d.ts +100 -0
  28. package/src/boot/daemon-lifecycle.ts +7 -0
  29. package/src/cdp/devtools.d.ts +51 -0
  30. package/src/cdp/initializer.d.ts +50 -0
  31. package/src/cdp/manager.d.ts +147 -0
  32. package/src/cdp/scanner.d.ts +58 -0
  33. package/src/cdp/setup.d.ts +58 -0
  34. package/src/cli-adapter-types.d.ts +57 -0
  35. package/src/cli-adapters/provider-cli-adapter.d.ts +177 -0
  36. package/src/cli-adapters/provider-cli-adapter.ts +115 -91
  37. package/src/cli-adapters/provider-cli-config.d.ts +30 -0
  38. package/src/cli-adapters/provider-cli-parse.d.ts +42 -0
  39. package/src/cli-adapters/provider-cli-runtime.d.ts +29 -0
  40. package/src/cli-adapters/provider-cli-shared.d.ts +158 -0
  41. package/src/cli-adapters/pty-transport.d.ts +48 -0
  42. package/src/cli-adapters/session-host-transport.d.ts +20 -0
  43. package/src/cli-adapters/spawn-env.d.ts +8 -0
  44. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +16 -0
  45. package/src/cli-adapters/terminal-backends/types.d.ts +18 -0
  46. package/src/cli-adapters/terminal-backends/xterm-backend.d.ts +17 -0
  47. package/src/cli-adapters/terminal-screen.d.ts +33 -0
  48. package/src/commands/cdp-commands.d.ts +15 -0
  49. package/src/commands/chat-commands.d.ts +15 -0
  50. package/src/commands/chat-commands.ts +192 -17
  51. package/src/commands/cli-manager.d.ts +94 -0
  52. package/src/commands/handler.d.ts +103 -0
  53. package/src/commands/router.d.ts +98 -0
  54. package/src/commands/router.ts +25 -0
  55. package/src/commands/stream-commands.d.ts +14 -0
  56. package/src/commands/stream-commands.ts +14 -10
  57. package/src/commands/upgrade-helper.d.ts +10 -0
  58. package/src/commands/workspace-commands.d.ts +11 -0
  59. package/src/config/chat-history.d.ts +99 -0
  60. package/src/config/config.d.ts +14 -0
  61. package/src/config/config.ts +8 -0
  62. package/src/config/recent-activity.d.ts +29 -0
  63. package/src/config/saved-sessions.d.ts +22 -0
  64. package/src/config/state-store.d.ts +32 -0
  65. package/src/config/workspaces.d.ts +0 -1
  66. package/src/daemon/dev-auto-implement.d.ts +43 -0
  67. package/src/daemon/dev-cdp-handlers.d.ts +22 -0
  68. package/src/daemon/dev-cli-debug.d.ts +106 -0
  69. package/src/daemon/dev-server-types.d.ts +43 -0
  70. package/src/daemon/dev-server.d.ts +140 -0
  71. package/src/daemon/scaffold-template.d.ts +32 -0
  72. package/src/daemon-core.d.ts +36 -0
  73. package/src/detection/cli-detector.d.ts +31 -0
  74. package/src/detection/ide-detector.d.ts +35 -0
  75. package/src/index.d.ts +83 -0
  76. package/src/index.ts +34 -1
  77. package/src/installer.d.ts +50 -0
  78. package/src/ipc-protocol.d.ts +111 -0
  79. package/src/launch.d.ts +46 -0
  80. package/src/logging/command-log.d.ts +31 -0
  81. package/src/logging/logger.d.ts +89 -0
  82. package/src/providers/acp-provider-instance.d.ts +102 -0
  83. package/src/providers/approval-utils.d.ts +7 -0
  84. package/src/providers/cli-provider-instance.d.ts +86 -0
  85. package/src/providers/contracts.d.ts +193 -1
  86. package/src/providers/control-effects.d.ts +4 -0
  87. package/src/providers/extension-provider-instance.d.ts +61 -0
  88. package/src/providers/ide-provider-instance.d.ts +70 -0
  89. package/src/providers/provider-instance-manager.d.ts +84 -0
  90. package/src/providers/provider-instance.d.ts +7 -1
  91. package/src/providers/provider-loader.d.ts +299 -0
  92. package/src/providers/status-monitor.d.ts +48 -0
  93. package/src/providers/version-archive.d.ts +52 -0
  94. package/src/session-host/runtime-support.d.ts +8 -0
  95. package/src/sessions/reconcile.d.ts +22 -0
  96. package/src/sessions/registry.d.ts +24 -0
  97. package/src/shared-types-extra.d.ts +29 -0
  98. package/src/shared-types.d.ts +204 -38
  99. package/src/shared-types.ts +319 -16
  100. package/src/status/builders.d.ts +33 -0
  101. package/src/status/builders.ts +110 -54
  102. package/src/status/normalize.ts +54 -19
  103. package/src/status/reporter.d.ts +69 -0
  104. package/src/status/reporter.ts +104 -26
  105. package/src/status/snapshot.d.ts +58 -0
  106. package/src/status/snapshot.ts +79 -41
  107. package/src/system/host-memory.d.ts +19 -0
  108. package/src/types.d.ts +3 -7
  109. package/src/types.ts +1 -1
@@ -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;
@@ -38,6 +47,7 @@ export class DaemonStatusReporter {
38
47
  private lastStatusSentAt = 0;
39
48
  private statusPendingThrottle = false;
40
49
  private lastP2PStatusHash = '';
50
+ private lastServerStatusHash = '';
41
51
  private lastStatusSummary = '';
42
52
 
43
53
  private statusTimer: NodeJS.Timeout | null = null;
@@ -52,12 +62,12 @@ export class DaemonStatusReporter {
52
62
 
53
63
  startReporting(): void {
54
64
  setTimeout(() => {
55
- this.sendUnifiedStatusReport().catch(e => LOG.warn('Status', `Initial report failed: ${e?.message}`));
65
+ this.sendUnifiedStatusReport({ forceServer: true, reason: 'initial' }).catch(e => LOG.warn('Status', `Initial report failed: ${e?.message}`));
56
66
  }, 2000);
57
67
 
58
68
  const scheduleServerReport = () => {
59
69
  this.statusTimer = setTimeout(() => {
60
- this.sendUnifiedStatusReport().catch(e => LOG.warn('Status', `Periodic report failed: ${e?.message}`));
70
+ this.sendUnifiedStatusReport({ forceServer: true, reason: 'periodic' }).catch(e => LOG.warn('Status', `Periodic report failed: ${e?.message}`));
61
71
  scheduleServerReport();
62
72
  }, 30_000);
63
73
  };
@@ -93,10 +103,73 @@ export class DaemonStatusReporter {
93
103
  }
94
104
  }
95
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
+
96
165
  emitStatusEvent(event: Record<string, unknown>): void {
97
166
  LOG.info('StatusEvent', `${event.event} (${event.providerType || event.ideType || ''})`);
98
- // Send via WS (server relay → dashboard + push notifications)
99
- 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);
100
173
  }
101
174
 
102
175
  removeAgentTracking(_key: string): void { /* Managed by Instance itself */ }
@@ -135,7 +208,7 @@ export class DaemonStatusReporter {
135
208
  .join(', ');
136
209
  }
137
210
 
138
- async sendUnifiedStatusReport(opts?: { p2pOnly?: boolean }): Promise<void> {
211
+ async sendUnifiedStatusReport(opts?: { p2pOnly?: boolean; forceServer?: boolean; reason?: string }): Promise<void> {
139
212
  const { serverConn, p2p } = this.deps;
140
213
  if (!serverConn?.isConnected()) return;
141
214
  this.lastStatusSentAt = Date.now();
@@ -188,7 +261,6 @@ export class DaemonStatusReporter {
188
261
  detectedIdes: this.deps.detectedIdes || [],
189
262
  instanceId: this.deps.instanceId,
190
263
  version: this.deps.daemonVersion || 'unknown',
191
- daemonMode: true,
192
264
  timestamp: now,
193
265
  p2p: {
194
266
  available: p2p?.isAvailable || false,
@@ -196,9 +268,9 @@ export class DaemonStatusReporter {
196
268
  peers: p2p?.connectedPeerCount || 0,
197
269
  screenshotActive: p2p?.screenshotActive || false,
198
270
  },
271
+ profile: 'live',
199
272
  }),
200
273
  screenshotUsage: this.deps.getScreenshotUsage?.() || null,
201
- connectedExtensions: [],
202
274
  };
203
275
 
204
276
  // ═══ P2P transmit ═══
@@ -216,51 +288,57 @@ export class DaemonStatusReporter {
216
288
 
217
289
  // ═══ Server transmit (minimal routing meta only) ═══
218
290
  if (opts?.p2pOnly) return;
219
- const wsPayload = {
220
- daemonMode: true,
291
+ // Server relay only needs compact session metadata for routing, compact status,
292
+ // initial_state fallback, and lightweight API/session inspection.
293
+ const wsPayload: CloudStatusReportPayload = {
221
294
  sessions: sessions.map((session) => ({
222
295
  id: session.id,
223
296
  parentId: session.parentId,
224
297
  providerType: session.providerType,
225
- providerName: session.providerName,
298
+ providerName: session.providerName || session.providerType,
226
299
  kind: session.kind,
227
300
  transport: session.transport,
228
301
  status: session.status,
229
- workspace: session.workspace,
302
+ workspace: session.workspace ?? null,
230
303
  title: session.title,
231
304
  cdpConnected: session.cdpConnected,
232
305
  currentModel: session.currentModel,
233
306
  currentPlan: session.currentPlan,
234
307
  currentAutoApprove: session.currentAutoApprove,
235
- lastUpdated: session.lastUpdated,
236
- unread: session.unread,
237
- lastSeenAt: session.lastSeenAt,
238
- inboxBucket: session.inboxBucket,
239
- surfaceHidden: session.surfaceHidden,
240
- controlValues: session.controlValues,
241
- providerControls: session.providerControls,
242
- acpConfigOptions: session.acpConfigOptions,
243
- acpModes: session.acpModes,
244
308
  })),
245
309
  p2p: payload.p2p,
246
310
  timestamp: now,
247
- detectedIdes: payload.detectedIdes,
248
- availableProviders: payload.availableProviders,
249
311
  };
312
+ const wsHash = this.simpleHash(JSON.stringify({
313
+ ...wsPayload,
314
+ timestamp: undefined,
315
+ }));
316
+ if (!opts?.forceServer && wsHash === this.lastServerStatusHash) {
317
+ LOG.debug('Server', `skip duplicate status_report${opts?.reason ? ` (${opts.reason})` : ''}`);
318
+ return;
319
+ }
320
+ this.lastServerStatusHash = wsHash;
250
321
  serverConn.sendMessage('status_report', wsPayload);
251
- LOG.debug('Server', `sent status_report (${JSON.stringify(wsPayload).length} bytes)`);
322
+ LOG.debug('Server', `sent status_report (${JSON.stringify(wsPayload).length} bytes)${opts?.reason ? ` [${opts.reason}]` : ''}`);
252
323
  }
253
324
 
254
325
  // ─── P2P ─────────────────────────────────────────
255
326
 
256
327
  private sendP2PPayload(payload: { timestamp?: number; system?: unknown; machine?: MachineInfo; [key: string]: unknown }): boolean {
257
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;
258
336
  const hashPayload = hashTarget.machine
259
337
  ? (() => {
260
338
  const { freeMem: _f, availableMem: _a, loadavg: _l, uptime: _u, ...stableMachine } = hashTarget.machine;
261
- return { ...hashTarget, machine: stableMachine };
339
+ return { ...hashTarget, sessions, machine: stableMachine };
262
340
  })()
263
- : hashTarget;
341
+ : { ...hashTarget, sessions };
264
342
  const h = this.simpleHash(JSON.stringify(hashPayload));
265
343
  if (h !== this.lastP2PStatusHash) {
266
344
  this.lastP2PStatusHash = h;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared status snapshot builders.
3
+ *
4
+ * Used by:
5
+ * - DaemonStatusReporter (cloud)
6
+ * - daemon-standalone HTTP/WS status responses
7
+ */
8
+ import type { DaemonCdpManager } from '../cdp/manager.js';
9
+ import type { ProviderState } from '../providers/provider-instance.js';
10
+ import type { AvailableProviderInfo, StatusReportPayload } from '../shared-types.js';
11
+ export interface StatusSnapshotOptions {
12
+ allStates: ProviderState[];
13
+ cdpManagers: Map<string, DaemonCdpManager>;
14
+ providerLoader: {
15
+ getAll(): Array<{
16
+ type: string;
17
+ icon?: string;
18
+ displayName?: string;
19
+ category: 'ide' | 'extension' | 'cli' | 'acp';
20
+ }>;
21
+ getAvailableProviderInfos?: () => Array<{
22
+ type: string;
23
+ icon?: string;
24
+ displayName?: string;
25
+ category: 'ide' | 'extension' | 'cli' | 'acp';
26
+ installed?: boolean;
27
+ detectedPath?: string | null;
28
+ }>;
29
+ };
30
+ detectedIdes: Array<{
31
+ id: string;
32
+ name?: string;
33
+ displayName?: string;
34
+ installed?: boolean;
35
+ path?: string;
36
+ }>;
37
+ instanceId: string;
38
+ version: string;
39
+ daemonMode: boolean;
40
+ timestamp?: number;
41
+ p2p?: StatusReportPayload['p2p'];
42
+ machineNickname?: string | null;
43
+ }
44
+ export interface StatusSnapshot extends StatusReportPayload {
45
+ availableProviders: AvailableProviderInfo[];
46
+ }
47
+ export declare function getSessionCompletionMarker(session: {
48
+ activeChat?: {
49
+ messages?: Array<{
50
+ role?: string;
51
+ id?: string;
52
+ index?: number;
53
+ receivedAt?: number | string;
54
+ _turnKey?: string;
55
+ }> | null;
56
+ } | null;
57
+ }): string;
58
+ export declare function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnapshot;
@@ -15,11 +15,12 @@ import { getHostMemorySnapshot } from '../system/host-memory.js';
15
15
  import { getTerminalBackendRuntimeStatus } from '../cli-adapters/terminal-screen.js';
16
16
  import { LOG } from '../logging/logger.js';
17
17
  import type { DaemonCdpManager } from '../cdp/manager.js';
18
- import { buildSessionEntries, isCdpConnected } from './builders.js';
18
+ import { buildSessionEntries, isCdpConnected, type SessionEntryProfile } from './builders.js';
19
19
  import type { ProviderState } from '../providers/provider-instance.js';
20
20
  import type {
21
21
  AvailableProviderInfo,
22
22
  DetectedIdeInfo,
23
+ MachineInfo,
23
24
  RecentLaunchEntry,
24
25
  RecentSessionBucket,
25
26
  SessionEntry,
@@ -54,15 +55,13 @@ export interface StatusSnapshotOptions {
54
55
  }>;
55
56
  instanceId: string;
56
57
  version: string;
57
- daemonMode: boolean;
58
58
  timestamp?: number;
59
59
  p2p?: StatusReportPayload['p2p'];
60
60
  machineNickname?: string | null;
61
+ profile?: SessionEntryProfile;
61
62
  }
62
63
 
63
- export interface StatusSnapshot extends StatusReportPayload {
64
- availableProviders: AvailableProviderInfo[];
65
- }
64
+ export type StatusSnapshot = StatusReportPayload;
66
65
 
67
66
  const READ_DEBUG_ENABLED = process.argv.includes('--dev') || process.env.ADHDEV_READ_DEBUG === '1';
68
67
 
@@ -103,6 +102,41 @@ function buildAvailableProviders(
103
102
  }));
104
103
  }
105
104
 
105
+ export function buildMachineInfo(profile: 'full' | 'live' | 'metadata' = 'full'): MachineInfo {
106
+ const base: MachineInfo = {
107
+ hostname: os.hostname(),
108
+ platform: os.platform(),
109
+ };
110
+
111
+ if (profile === 'live') {
112
+ return base;
113
+ }
114
+
115
+ if (profile === 'metadata') {
116
+ const memSnap = getHostMemorySnapshot();
117
+ return {
118
+ ...base,
119
+ arch: os.arch(),
120
+ cpus: os.cpus().length,
121
+ totalMem: memSnap.totalMem,
122
+ release: os.release(),
123
+ };
124
+ }
125
+
126
+ const memSnap = getHostMemorySnapshot();
127
+ return {
128
+ ...base,
129
+ arch: os.arch(),
130
+ cpus: os.cpus().length,
131
+ totalMem: memSnap.totalMem,
132
+ freeMem: memSnap.freeMem,
133
+ availableMem: memSnap.availableMem,
134
+ loadavg: os.loadavg(),
135
+ uptime: os.uptime(),
136
+ release: os.release(),
137
+ };
138
+ }
139
+
106
140
  function parseMessageTime(value: unknown): number {
107
141
  if (typeof value === 'number' && Number.isFinite(value)) return value;
108
142
  if (typeof value === 'string') {
@@ -207,28 +241,39 @@ function buildRecentLaunches(
207
241
  }
208
242
 
209
243
  export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnapshot {
244
+ const profile = options.profile || 'full';
210
245
  const cfg = loadConfig();
211
246
  const state = loadState();
212
247
  const wsState = getWorkspaceState(cfg);
213
- const memSnap = getHostMemorySnapshot();
214
248
  const recentActivity = getRecentActivity(state, 20);
215
- const sessions = buildSessionEntries(
249
+ const unreadSourceSessions = buildSessionEntries(
216
250
  options.allStates,
217
251
  options.cdpManagers,
252
+ { profile: 'full' },
218
253
  );
219
- for (const session of sessions) {
220
- const lastSeenAt = getSessionSeenAt(state, session.id);
221
- const seenCompletionMarker = getSessionSeenMarker(state, session.id);
222
- const lastUsedAt = getSessionLastUsedAt(session);
223
- const completionMarker = getSessionCompletionMarker(session);
224
- const { unread, inboxBucket } = session.surfaceHidden
254
+ const sessions = profile === 'full'
255
+ ? unreadSourceSessions
256
+ : buildSessionEntries(
257
+ options.allStates,
258
+ options.cdpManagers,
259
+ { profile },
260
+ );
261
+ const sessionsById = new Map(sessions.map((session) => [session.id, session]));
262
+ for (const sourceSession of unreadSourceSessions) {
263
+ const session = sessionsById.get(sourceSession.id);
264
+ if (!session) continue;
265
+ const lastSeenAt = getSessionSeenAt(state, sourceSession.id);
266
+ const seenCompletionMarker = getSessionSeenMarker(state, sourceSession.id);
267
+ const lastUsedAt = getSessionLastUsedAt(sourceSession);
268
+ const completionMarker = getSessionCompletionMarker(sourceSession);
269
+ const { unread, inboxBucket } = sourceSession.surfaceHidden
225
270
  ? { unread: false, inboxBucket: 'idle' as RecentSessionBucket }
226
271
  : getUnreadState(
227
- getSessionMessageUpdatedAt(session) > 0,
228
- session.status,
272
+ getSessionMessageUpdatedAt(sourceSession) > 0,
273
+ sourceSession.status,
229
274
  lastUsedAt,
230
275
  lastSeenAt,
231
- getLastMessageRole(session),
276
+ getLastMessageRole(sourceSession),
232
277
  completionMarker,
233
278
  seenCompletionMarker,
234
279
  );
@@ -238,39 +283,32 @@ export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnaps
238
283
  if (READ_DEBUG_ENABLED && (session.unread || session.inboxBucket !== 'idle' || session.providerType.includes('codex'))) {
239
284
  LOG.info(
240
285
  'RecentRead',
241
- `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(session)} msgUpdatedAt=${getSessionMessageUpdatedAt(session)}`,
286
+ `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)}`,
242
287
  );
243
288
  }
244
289
  }
245
- const terminalBackend = getTerminalBackendRuntimeStatus();
290
+ const includeMachineMetadata = profile !== 'live';
291
+ const terminalBackend = includeMachineMetadata
292
+ ? getTerminalBackendRuntimeStatus()
293
+ : undefined;
246
294
 
247
295
  return {
248
296
  instanceId: options.instanceId,
249
- version: options.version,
250
- daemonMode: options.daemonMode,
251
- machine: {
252
- hostname: os.hostname(),
253
- platform: os.platform(),
254
- arch: os.arch(),
255
- cpus: os.cpus().length,
256
- totalMem: memSnap.totalMem,
257
- freeMem: memSnap.freeMem,
258
- availableMem: memSnap.availableMem,
259
- loadavg: os.loadavg(),
260
- uptime: os.uptime(),
261
- release: os.release(),
262
- },
263
- machineNickname: options.machineNickname ?? cfg.machineNickname ?? null,
297
+ ...(includeMachineMetadata ? { version: options.version } : {}),
298
+ machine: buildMachineInfo(profile),
299
+ ...(includeMachineMetadata ? { machineNickname: options.machineNickname ?? cfg.machineNickname ?? null } : {}),
264
300
  timestamp: options.timestamp ?? Date.now(),
265
- detectedIdes: buildDetectedIdeInfos(options.detectedIdes, options.cdpManagers),
266
301
  ...(options.p2p ? { p2p: options.p2p } : {}),
267
302
  sessions,
268
- workspaces: wsState.workspaces,
269
- defaultWorkspaceId: wsState.defaultWorkspaceId,
270
- defaultWorkspacePath: wsState.defaultWorkspacePath,
271
- terminalSizingMode: cfg.terminalSizingMode || 'measured',
272
- recentLaunches: buildRecentLaunches(recentActivity),
273
- terminalBackend,
274
- availableProviders: buildAvailableProviders(options.providerLoader),
303
+ ...(terminalBackend ? { terminalBackend } : {}),
304
+ ...(includeMachineMetadata && {
305
+ detectedIdes: buildDetectedIdeInfos(options.detectedIdes, options.cdpManagers),
306
+ workspaces: wsState.workspaces,
307
+ defaultWorkspaceId: wsState.defaultWorkspaceId,
308
+ defaultWorkspacePath: wsState.defaultWorkspacePath,
309
+ terminalSizingMode: cfg.terminalSizingMode || 'measured',
310
+ recentLaunches: buildRecentLaunches(recentActivity),
311
+ availableProviders: buildAvailableProviders(options.providerLoader),
312
+ }),
275
313
  };
276
314
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Host memory metrics — macOS-aware "available" memory.
3
+ *
4
+ * Node's os.freemem() on darwin reports only the tiny truly-free pool; most RAM
5
+ * sits in inactive/file-backed cache that the OS can reclaim. Dashboard "used %"
6
+ * based on (total - freemem) looks ~99% almost always — misleading.
7
+ *
8
+ * On macOS we parse `vm_stat` and approximate available bytes as:
9
+ * (free + inactive + speculative + purgeable [+ file_backed]) × page size
10
+ * (aligned with common Activity Monitor–style interpretations.)
11
+ */
12
+ export interface HostMemorySnapshot {
13
+ totalMem: number;
14
+ /** Raw kernel "free" — small on macOS; kept for debugging / API compat */
15
+ freeMem: number;
16
+ /** Use this for UI "used %" — on darwin from vm_stat; else equals freeMem */
17
+ availableMem: number;
18
+ }
19
+ export declare function getHostMemorySnapshot(): HostMemorySnapshot;
package/src/types.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Shared types referenced by daemon-core, daemon-standalone, and web-core.
5
5
  * When modifying this file, also update interface contracts in AGENT_PROTOCOL.md.
6
6
  */
7
- import type { StatusReportPayload } from './shared-types.js';
7
+ import type { StatusReportPayload, AvailableProviderInfo } from './shared-types.js';
8
8
  /** Full status response from /api/v1/status and WS events */
9
9
  export interface StatusResponse extends StatusReportPayload {
10
10
  /** For standalone API compat */
@@ -15,7 +15,7 @@ export interface StatusResponse extends StatusReportPayload {
15
15
  /** User display name from config */
16
16
  userName?: string;
17
17
  /** Available providers */
18
- availableProviders: ProviderInfo[];
18
+ availableProviders: AvailableProviderInfo[];
19
19
  /** System info (legacy compat) */
20
20
  system?: SystemInfo;
21
21
  }
@@ -111,11 +111,7 @@ export interface DetectedIde {
111
111
  installed: boolean;
112
112
  running: boolean;
113
113
  }
114
- export interface ProviderInfo {
115
- type: string;
116
- icon: string;
117
- displayName: string;
118
- category: string;
114
+ export interface ProviderInfo extends AvailableProviderInfo {
119
115
  }
120
116
  /** Flattened agent entry from /api/v1/agents */
121
117
  export interface AgentEntry {
package/src/types.ts CHANGED
@@ -18,7 +18,7 @@ export interface StatusResponse extends StatusReportPayload {
18
18
  /** User display name from config */
19
19
  userName?: string;
20
20
  /** Available providers */
21
- availableProviders: AvailableProviderInfo[];
21
+ availableProviders?: AvailableProviderInfo[];
22
22
  /** System info (legacy compat) */
23
23
  system?: SystemInfo;
24
24
  }