@adhdev/daemon-core 0.5.18 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.5.18",
3
+ "version": "0.5.20",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -204,8 +204,14 @@
204
204
  // 5. 모달/승인 감지 — Run⌥⏎/Reject 인라인 + Deny/Allow 브라우저 승인
205
205
  let activeModal = null;
206
206
  try {
207
+ // Strip Mac symbols and Windows shortcut labels (e.g. "RunAlt+⏎" → "Run")
208
+ const stripShortcut = (s) => s
209
+ .replace(/[⌥⏎⇧⌫⌘⌃↵]/g, '')
210
+ .replace(/\s*(Alt|Ctrl|Shift|Cmd|Enter|Return|Esc|Tab|Backspace)(\+\s*\w+)*/gi, '')
211
+ .trim();
207
212
  const isApprovalLike = (el) => {
208
- const t = (el.textContent || '').trim().toLowerCase();
213
+ const raw = (el.textContent || '').trim();
214
+ const t = stripShortcut(raw).toLowerCase();
209
215
  // 드롭다운 옵션 제외
210
216
  if (t === 'ask every time') return false;
211
217
  return /^(run|reject|skip|approve|allow|deny|cancel|accept|yes|no)\b/i.test(t)
@@ -231,11 +237,9 @@
231
237
  const approvalBtns = panelBtns.filter(isApprovalLike);
232
238
  if (approvalBtns.length > 0) {
233
239
  const hasActionBtn = approvalBtns.some(b => {
234
- const t = (b.textContent || '').trim().toLowerCase();
235
- return t.indexOf('run') === 0 || t === 'reject' || t.indexOf('reject') === 0
236
- || t === 'skip' || t.indexOf('skip') === 0
237
- || t === 'deny' || t === 'allow' || t === 'always allow' || t === 'always deny'
238
- || t === 'accept' || t === 'approve';
240
+ const t = stripShortcut((b.textContent || '').trim()).toLowerCase();
241
+ return /^(run|reject|skip|deny|allow|accept|approve|yes|no)\b/.test(t)
242
+ || t === 'always allow' || t === 'always deny';
239
243
  });
240
244
  if (hasActionBtn) {
241
245
  const btnTexts = [...new Set(
@@ -266,8 +266,14 @@
266
266
  // 5. 모달/승인 감지 — Run⌥⏎/Reject 인라인 + Deny/Allow 브라우저 승인
267
267
  let activeModal = null;
268
268
  try {
269
+ // Strip Mac symbols and Windows shortcut labels (e.g. "RunAlt+⏎" → "Run")
270
+ const stripShortcut = (s) => s
271
+ .replace(/[⌥⏎⇧⌫⌘⌃↵]/g, '')
272
+ .replace(/\s*(Alt|Ctrl|Shift|Cmd|Enter|Return|Esc|Tab|Backspace)(\+\s*\w+)*/gi, '')
273
+ .trim();
269
274
  const isApprovalLike = (el) => {
270
- const t = (el.textContent || '').trim().toLowerCase();
275
+ const raw = (el.textContent || '').trim();
276
+ const t = stripShortcut(raw).toLowerCase();
271
277
  // 드롭다운 옵션 제외
272
278
  if (t === 'ask every time') return false;
273
279
  return /^(run|reject|skip|approve|allow|deny|cancel|accept|yes|no)\b/i.test(t)
@@ -293,11 +299,9 @@
293
299
  const approvalBtns = panelBtns.filter(isApprovalLike);
294
300
  if (approvalBtns.length > 0) {
295
301
  const hasActionBtn = approvalBtns.some(b => {
296
- const t = (b.textContent || '').trim().toLowerCase();
297
- return t.indexOf('run') === 0 || t === 'reject' || t.indexOf('reject') === 0
298
- || t === 'skip' || t.indexOf('skip') === 0
299
- || t === 'deny' || t === 'allow' || t === 'always allow' || t === 'always deny'
300
- || t === 'accept' || t === 'approve';
302
+ const t = stripShortcut((b.textContent || '').trim()).toLowerCase();
303
+ return /^(run|reject|skip|deny|allow|accept|approve|yes|no)\b/.test(t)
304
+ || t === 'always allow' || t === 'always deny';
301
305
  });
302
306
  if (hasActionBtn) {
303
307
  const btnTexts = [...new Set(
@@ -1,16 +1,19 @@
1
1
  /**
2
- * Daemon Commands — unified command handler
3
- *
4
- * Routes all functionality from legacy Extension commands.ts to Daemon.
5
- *
6
- * Command implementations are split into sub-modules:
7
- * - chat-commands.ts — Chat I/O, session management, mode/model control
8
- * - cdp-commands.ts — CDP eval, screenshot, batch, remote actions, file ops
9
- * - stream-commands.ts — Agent stream, PTY I/O, provider settings, extension scripts
2
+ * DaemonCommandHandler — unified command routing for CDP & CLI
3
+ *
4
+ * Routes incoming commands (from server WS, P2P, or local WS) to
5
+ * the correct CDP manager or CLI adapter.
6
+ *
7
+ * Key concepts:
8
+ * - extractIdeType(): determines target IDE from _targetInstance
9
+ * - getCdp(): returns the DaemonCdpManager for current command
10
+ * - getProvider(): returns the ProviderModule for current command
11
+ * - handle(): main entry point, sets context then dispatches
10
12
  */
11
13
 
12
- import { DaemonCdpManager } from '../cdp/manager.js';
14
+ import type { DaemonCdpManager } from '../cdp/manager.js';
13
15
  import { CdpDomHandlers } from '../cdp/devtools.js';
16
+ import { findCdpManager } from '../status/builders.js';
14
17
  import { ProviderLoader } from '../providers/provider-loader.js';
15
18
  import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
16
19
  import type { ProviderModule } from '../providers/contracts.js';
@@ -85,12 +88,16 @@ export class DaemonCommandHandler implements CommandHelpers {
85
88
  get currentIdeType(): string | undefined { return this._currentIdeType; }
86
89
  get currentProviderType(): string | undefined { return this._currentProviderType; }
87
90
 
88
- /** Get CDP manager for a specific ideType.
91
+ /** Get CDP manager for a specific ideType or managerKey.
92
+ * Supports exact match, multi-window prefix match, and instanceIdMap UUID lookup.
89
93
  * Returns null if no match — never falls back to another IDE. */
90
94
  getCdp(ideType?: string): DaemonCdpManager | null {
91
95
  const key = ideType || this._currentIdeType;
92
96
  if (!key) return null;
93
- const m = this._ctx.cdpManagers.get(key.toLowerCase());
97
+ // 1. Try instanceIdMap (UUID → managerKey)
98
+ const resolved = this._ctx.instanceIdMap?.get(key) || key;
99
+ // 2. Use findCdpManager (exact + prefix match)
100
+ const m = findCdpManager(this._ctx.cdpManagers, resolved.toLowerCase());
94
101
  if (m?.isConnected) return m;
95
102
  return null;
96
103
  }
@@ -177,11 +184,22 @@ export class DaemonCommandHandler implements CommandHelpers {
177
184
  return managed?.sessionId || null;
178
185
  }
179
186
 
180
- /** Extract ideType from _targetInstance */
187
+ /** Extract ideType from _targetInstance or explicit ideType */
181
188
  private extractIdeType(args: any): string | undefined {
182
- // Also accept explicit ideType from args (agentType for extensions)
183
- if (args?.ideType && this._ctx.cdpManagers.has(args.ideType)) {
184
- return args.ideType;
189
+ // Also accept explicit ideType from args (P2P input, agentType for extensions)
190
+ if (args?.ideType) {
191
+ // Exact match first
192
+ if (this._ctx.cdpManagers.has(args.ideType)) {
193
+ return args.ideType;
194
+ }
195
+ // Prefix match for multi-window (e.g. "cursor" matches "cursor_remote_vs")
196
+ const found = findCdpManager(this._ctx.cdpManagers, args.ideType);
197
+ if (found) {
198
+ // Return the actual key so getCdp() finds it
199
+ for (const [k, m] of this._ctx.cdpManagers.entries()) {
200
+ if (m === found) return k;
201
+ }
202
+ }
185
203
  }
186
204
 
187
205
  if (args?._targetInstance) {
@@ -197,19 +215,24 @@ export class DaemonCommandHandler implements CommandHelpers {
197
215
  return this._ctx.instanceIdMap.get(raw)!;
198
216
  }
199
217
 
200
- // Direct CDP manager key match (e.g. "cursor", "antigravity")
218
+ // Direct CDP manager key match (e.g. "cursor", "cursor_remote_vs")
201
219
  if (this._ctx.cdpManagers.has(raw)) {
202
220
  return raw;
203
221
  }
204
222
 
205
- // Fallback: if no structured format matched and raw looks like a machine ID
206
- // (e.g. "standalone_hostname"), find first available connected CDP
207
- if (!ideMatch && !cliMatch && !acpMatch) {
208
- for (const [key, mgr] of this._ctx.cdpManagers.entries()) {
209
- if (mgr.isConnected) return key;
223
+ // Prefix match for multi-window keys
224
+ const found = findCdpManager(this._ctx.cdpManagers, raw);
225
+ if (found) {
226
+ for (const [k, m] of this._ctx.cdpManagers.entries()) {
227
+ if (m === found) return k;
210
228
  }
211
229
  }
212
230
 
231
+ // Fallback removed: returning first-connected CDP was the root cause of
232
+ // input routing to wrong IDE (e.g. screenshot shows Cursor but input goes
233
+ // to Antigravity). If no match is found, return undefined so the caller
234
+ // gets an explicit error rather than silently routing to the wrong IDE.
235
+
213
236
  // Legacy: strip trailing _N suffix (e.g. "cursor_1" → "cursor")
214
237
  const lastUnderscore = raw.lastIndexOf('_');
215
238
  if (lastUnderscore > 0) {
@@ -48,6 +48,8 @@ export interface CommandRouterDeps {
48
48
  onPostChatCommand?: () => void;
49
49
  /** Get a connected CDP manager (for agent stream reset check) */
50
50
  getCdpLogFn?: (ideType: string) => (msg: string) => void;
51
+ /** Package name for upgrade detection ('adhdev' or '@adhdev/daemon-standalone') */
52
+ packageName?: string;
51
53
  }
52
54
 
53
55
  export interface CommandRouterResult {
@@ -257,12 +259,17 @@ export class DaemonCommandRouter {
257
259
  try {
258
260
  const { execSync } = await import('child_process');
259
261
 
262
+ // Detect package: standalone uses @adhdev/daemon-standalone, cloud uses adhdev
263
+ const isStandalone = this.deps.packageName === '@adhdev/daemon-standalone'
264
+ || process.argv[1]?.includes('daemon-standalone');
265
+ const pkgName = isStandalone ? '@adhdev/daemon-standalone' : 'adhdev';
266
+
260
267
  // Check latest version
261
- const latest = execSync('npm view adhdev version', { encoding: 'utf-8', timeout: 10000 }).trim();
262
- LOG.info('Upgrade', `Latest available: v${latest}`);
268
+ const latest = execSync(`npm view ${pkgName} version`, { encoding: 'utf-8', timeout: 10000 }).trim();
269
+ LOG.info('Upgrade', `Latest ${pkgName}: v${latest}`);
263
270
 
264
271
  // Install latest
265
- execSync('npm install -g adhdev@latest', {
272
+ execSync(`npm install -g ${pkgName}@latest`, {
266
273
  encoding: 'utf-8',
267
274
  timeout: 60000,
268
275
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -272,8 +279,15 @@ export class DaemonCommandRouter {
272
279
  // Schedule restart after response is sent
273
280
  setTimeout(() => {
274
281
  LOG.info('Upgrade', 'Restarting daemon with new version...');
282
+ // Remove PID file so the new process doesn't see 'already running'
283
+ try {
284
+ const path = require('path');
285
+ const fs = require('fs');
286
+ const pidFile = path.join(process.env.HOME || process.env.USERPROFILE || '', '.adhdev', 'daemon.pid');
287
+ if (fs.existsSync(pidFile)) fs.unlinkSync(pidFile);
288
+ } catch { /* ignore */ }
275
289
  const { spawn } = require('child_process');
276
- const child = spawn(process.execPath, [process.argv[1], 'daemon', '-p', '19222'], {
290
+ const child = spawn(process.execPath, process.argv.slice(1), {
277
291
  detached: true,
278
292
  stdio: 'ignore',
279
293
  env: { ...process.env },
@@ -367,7 +367,10 @@ export class DevServer {
367
367
  return;
368
368
  }
369
369
 
370
- const cdp = ideType ? this.cdpManagers.get(ideType) : this.getAnyCdp();
370
+ const cdp = this.getCdp(ideType);
371
+ if (!cdp && !ideType) {
372
+ LOG.warn('DevServer', 'CDP evaluate without ideType — picked first connected manager');
373
+ }
371
374
  if (!cdp?.isConnected) {
372
375
  this.json(res, 503, { error: 'No CDP connection available' });
373
376
  return;
@@ -626,7 +629,7 @@ export class DevServer {
626
629
  this.sendSSE({ type: 'watch_error', error: `Script '${this.watchScriptName}' not found` });
627
630
  return;
628
631
  }
629
- const cdp = this.getAnyCdp();
632
+ const cdp = this.getCdp();
630
633
  if (!cdp) {
631
634
  this.sendSSE({ type: 'watch_error', error: 'No CDP connection' });
632
635
  return;
@@ -2361,22 +2364,27 @@ export class DevServer {
2361
2364
  }
2362
2365
  }
2363
2366
 
2364
- /** Get CDP manager — matching IDE when ideType specified, first connected one otherwise */
2367
+ /** Get CDP manager — matching IDE when ideType specified, first connected one otherwise.
2368
+ * DevServer is a debugging tool so first-connected fallback is acceptable,
2369
+ * but callers should pass ideType when possible. */
2365
2370
  private getCdp(ideType?: string): DaemonCdpManager | null {
2366
2371
  if (ideType) {
2367
2372
  const cdp = this.cdpManagers.get(ideType);
2368
2373
  if (cdp?.isConnected) return cdp;
2374
+ // Prefix match for multi-window keys
2375
+ for (const [k, m] of this.cdpManagers.entries()) {
2376
+ if (k.startsWith(ideType + '_') && m.isConnected) return m;
2377
+ }
2378
+ LOG.warn('DevServer', `getCdp: no manager found for ideType '${ideType}', available: [${[...this.cdpManagers.keys()].join(', ')}]`);
2379
+ return null;
2369
2380
  }
2381
+ // No ideType — return first connected (dev convenience)
2370
2382
  for (const cdp of this.cdpManagers.values()) {
2371
2383
  if (cdp.isConnected) return cdp;
2372
2384
  }
2373
2385
  return null;
2374
2386
  }
2375
2387
 
2376
- private getAnyCdp(): DaemonCdpManager | null {
2377
- return this.getCdp();
2378
- }
2379
-
2380
2388
  private json(res: http.ServerResponse, status: number, data: any): void {
2381
2389
  res.writeHead(status, { 'Content-Type': 'application/json' });
2382
2390
  res.end(JSON.stringify(data, null, 2));
package/src/index.ts CHANGED
@@ -75,6 +75,7 @@ export type { CommandRouterDeps, CommandRouterResult } from './commands/router.j
75
75
 
76
76
  // ── Status ──
77
77
  export { DaemonStatusReporter } from './status/reporter.js';
78
+ export { buildManagedIdes, buildManagedClis, buildManagedAcps, buildAllManagedEntries, findCdpManager, hasCdpManager, isCdpConnected } from './status/builders.js';
78
79
 
79
80
  // ── Logger ──
80
81
  export { LOG, installGlobalInterceptor, setLogLevel, getLogLevel, getRecentLogs } from './logging/logger.js';
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Status Builders — shared conversion functions for ProviderState → ManagedEntry
3
+ *
4
+ * Used by:
5
+ * - daemon-cloud (DaemonStatusReporter)
6
+ * - daemon-standalone (StandaloneServer.getStatus)
7
+ *
8
+ * Consolidates duplicated ProviderState→ManagedEntry mapping that was
9
+ * previously copy-pasted between cloud and standalone codebases.
10
+ */
11
+
12
+ import type { DaemonCdpManager } from '../cdp/manager.js';
13
+ import type { ManagedIdeEntry, ManagedCliEntry, ManagedAcpEntry } from '../shared-types.js';
14
+ import type {
15
+ IdeProviderState,
16
+ CliProviderState,
17
+ AcpProviderState,
18
+ ProviderState,
19
+ } from '../providers/provider-instance.js';
20
+
21
+ // ─── CDP Manager lookup helpers ──────────────────────
22
+
23
+ /**
24
+ * Find a CDP manager by key, with prefix matching for multi-window support.
25
+ *
26
+ * Lookup order:
27
+ * 1. Exact match: cdpManagers.get(key)
28
+ * 2. Prefix match: key starts with `${ideType}_` (multi-window: "cursor_remote_vs")
29
+ * 3. null
30
+ *
31
+ * This replaces raw `cdpManagers.get(ideType)` calls that broke when
32
+ * multi-window keys like "cursor_remote_vs" were used.
33
+ */
34
+ export function findCdpManager(
35
+ cdpManagers: Map<string, DaemonCdpManager>,
36
+ key: string,
37
+ ): DaemonCdpManager | null {
38
+ // 1. Exact match (single-window: "cursor", or full managerKey: "cursor_remote_vs")
39
+ const exact = cdpManagers.get(key);
40
+ if (exact) return exact;
41
+
42
+ // 2. Prefix match (key = ideType like "cursor", managerKey = "cursor_remote_vs")
43
+ const prefix = key + '_';
44
+ for (const [k, m] of cdpManagers.entries()) {
45
+ if (k.startsWith(prefix) && m.isConnected) return m;
46
+ }
47
+
48
+ return null;
49
+ }
50
+
51
+ /**
52
+ * Check if any CDP manager matches the given key (exact or prefix).
53
+ */
54
+ export function hasCdpManager(
55
+ cdpManagers: Map<string, DaemonCdpManager>,
56
+ key: string,
57
+ ): boolean {
58
+ if (cdpManagers.has(key)) return true;
59
+ const prefix = key + '_';
60
+ for (const k of cdpManagers.keys()) {
61
+ if (k.startsWith(prefix)) return true;
62
+ }
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Check if any CDP manager matching the key is connected.
68
+ */
69
+ export function isCdpConnected(
70
+ cdpManagers: Map<string, DaemonCdpManager>,
71
+ key: string,
72
+ ): boolean {
73
+ const m = findCdpManager(cdpManagers, key);
74
+ return m?.isConnected ?? false;
75
+ }
76
+
77
+ // ─── ProviderState → ManagedEntry builders ───────────
78
+
79
+ /**
80
+ * Convert IdeProviderState[] → ManagedIdeEntry[]
81
+ *
82
+ * @param ideStates - from instanceManager.collectAllStates() filtered to ide
83
+ * @param cdpManagers - for cdpConnected lookup
84
+ * @param opts.detectedIdes - include CDPs that have no instance yet
85
+ */
86
+ export function buildManagedIdes(
87
+ ideStates: IdeProviderState[],
88
+ cdpManagers: Map<string, DaemonCdpManager>,
89
+ opts?: { detectedIdes?: { id: string; installed: boolean }[] },
90
+ ): ManagedIdeEntry[] {
91
+ const result: ManagedIdeEntry[] = [];
92
+
93
+ for (const state of ideStates) {
94
+ // Use cdpConnected from IdeProviderState if available (it checks internally),
95
+ // otherwise fall back to CDP manager lookup
96
+ const cdpConnected = state.cdpConnected ?? isCdpConnected(cdpManagers, state.type);
97
+ result.push({
98
+ ideType: state.type,
99
+ ideVersion: '',
100
+ instanceId: state.instanceId || state.type,
101
+ workspace: state.workspace || null,
102
+ terminals: 0,
103
+ aiAgents: [],
104
+ activeChat: state.activeChat,
105
+ chats: [],
106
+ agentStreams: state.extensions.map((ext) => ({
107
+ agentType: ext.type,
108
+ agentName: ext.name,
109
+ extensionId: ext.type,
110
+ status: ext.status || 'idle',
111
+ messages: ext.activeChat?.messages || [],
112
+ inputContent: ext.activeChat?.inputContent || '',
113
+ activeModal: ext.activeChat?.activeModal || null,
114
+ })),
115
+ cdpConnected,
116
+ currentModel: state.currentModel,
117
+ currentPlan: state.currentPlan,
118
+ currentAutoApprove: state.currentAutoApprove,
119
+ });
120
+ }
121
+
122
+ // Include CDPs with no ProviderInstance yet (newly detected IDEs)
123
+ if (opts?.detectedIdes) {
124
+ const coveredTypes = new Set(ideStates.map((s) => s.type));
125
+ for (const ide of opts.detectedIdes) {
126
+ if (!ide.installed || coveredTypes.has(ide.id)) continue;
127
+ if (!isCdpConnected(cdpManagers, ide.id)) continue;
128
+ result.push({
129
+ ideType: ide.id,
130
+ ideVersion: '',
131
+ instanceId: ide.id,
132
+ workspace: null,
133
+ terminals: 0,
134
+ aiAgents: [],
135
+ activeChat: null,
136
+ chats: [],
137
+ agentStreams: [],
138
+ cdpConnected: true,
139
+ currentModel: undefined,
140
+ currentPlan: undefined,
141
+ });
142
+ }
143
+ }
144
+
145
+ return result;
146
+ }
147
+
148
+ /**
149
+ * Convert CliProviderState[] → ManagedCliEntry[]
150
+ */
151
+ export function buildManagedClis(
152
+ cliStates: CliProviderState[],
153
+ ): ManagedCliEntry[] {
154
+ return cliStates.map((s) => ({
155
+ id: s.instanceId,
156
+ instanceId: s.instanceId,
157
+ cliType: s.type,
158
+ cliName: s.name,
159
+ status: s.status,
160
+ mode: s.mode as 'terminal' | 'chat',
161
+ workspace: s.workspace || '',
162
+ activeChat: s.activeChat,
163
+ }));
164
+ }
165
+
166
+ /**
167
+ * Convert AcpProviderState[] → ManagedAcpEntry[]
168
+ */
169
+ export function buildManagedAcps(
170
+ acpStates: AcpProviderState[],
171
+ ): ManagedAcpEntry[] {
172
+ return acpStates.map((s) => ({
173
+ id: s.instanceId,
174
+ acpType: s.type,
175
+ acpName: s.name,
176
+ status: s.status,
177
+ mode: 'chat' as const,
178
+ workspace: s.workspace || '',
179
+ activeChat: s.activeChat,
180
+ currentModel: s.currentModel,
181
+ currentPlan: s.currentPlan,
182
+ acpConfigOptions: s.acpConfigOptions,
183
+ acpModes: s.acpModes,
184
+ errorMessage: s.errorMessage,
185
+ errorReason: s.errorReason,
186
+ }));
187
+ }
188
+
189
+ /**
190
+ * Convenience: collect & build all managed entries from instanceManager
191
+ */
192
+ export function buildAllManagedEntries(
193
+ allStates: ProviderState[],
194
+ cdpManagers: Map<string, DaemonCdpManager>,
195
+ opts?: { detectedIdes?: { id: string; installed: boolean }[] },
196
+ ): {
197
+ managedIdes: ManagedIdeEntry[];
198
+ managedClis: ManagedCliEntry[];
199
+ managedAcps: ManagedAcpEntry[];
200
+ } {
201
+ const ideStates = allStates.filter((s): s is IdeProviderState => s.category === 'ide');
202
+ const cliStates = allStates.filter((s): s is CliProviderState => s.category === 'cli');
203
+ const acpStates = allStates.filter((s): s is AcpProviderState => s.category === 'acp');
204
+
205
+ return {
206
+ managedIdes: buildManagedIdes(ideStates, cdpManagers, opts),
207
+ managedClis: buildManagedClis(cliStates),
208
+ managedAcps: buildManagedAcps(acpStates),
209
+ };
210
+ }
@@ -12,13 +12,12 @@ import { getWorkspaceState } from '../config/workspaces.js';
12
12
  import { getHostMemorySnapshot } from '../system/host-memory.js';
13
13
  import { getWorkspaceActivity } from '../config/workspace-activity.js';
14
14
  import { LOG } from '../logging/logger.js';
15
- import type { ManagedIdeEntry, ManagedCliEntry, ManagedAcpEntry } from '../shared-types.js';
15
+ import { buildAllManagedEntries } from './builders.js';
16
16
  import type {
17
17
  ProviderState,
18
18
  IdeProviderState,
19
19
  CliProviderState,
20
20
  AcpProviderState,
21
- ExtensionProviderState,
22
21
  } from '../providers/provider-instance.js';
23
22
 
24
23
  // ─── Daemon dependency interface ──────────────────────
@@ -156,62 +155,13 @@ export class DaemonStatusReporter {
156
155
  }
157
156
  }
158
157
 
159
- // IDE states → managedIdes
160
- const managedIdes: ManagedIdeEntry[] = ideStates.map((s) => ({
161
- ideType: s.type,
162
- ideVersion: '',
163
- instanceId: s.instanceId,
164
- workspace: s.workspace || null,
165
- terminals: 0,
166
- aiAgents: [],
167
- activeChat: s.activeChat,
168
- chats: [],
169
- agentStreams: s.extensions.map((ext) => ({
170
- agentType: ext.type,
171
- agentName: ext.name,
172
- extensionId: ext.type,
173
- status: ext.status || 'idle',
174
- messages: ext.activeChat?.messages || [],
175
- inputContent: ext.activeChat?.inputContent || '',
176
- activeModal: ext.activeChat?.activeModal || null,
177
- })),
178
- cdpConnected: s.cdpConnected,
179
- currentModel: s.currentModel,
180
- currentPlan: s.currentPlan,
181
- currentAutoApprove: s.currentAutoApprove,
182
- }));
183
-
184
- // Merge/add Extension data
185
-
186
-
187
- // CLI states → managedClis
188
- const managedClis: ManagedCliEntry[] = cliStates.map((s) => ({
189
- id: s.instanceId,
190
- instanceId: s.instanceId,
191
- cliType: s.type,
192
- cliName: s.name,
193
- status: s.status,
194
- mode: s.mode,
195
- workspace: s.workspace || '',
196
- activeChat: s.activeChat,
197
- }));
198
-
199
- // ACP states → managedAcps
200
- const managedAcps: ManagedAcpEntry[] = acpStates.map((s) => ({
201
- id: s.instanceId,
202
- acpType: s.type,
203
- acpName: s.name,
204
- status: s.status,
205
- mode: s.mode,
206
- workspace: s.workspace || '',
207
- activeChat: s.activeChat,
208
- currentModel: s.currentModel,
209
- currentPlan: s.currentPlan,
210
- acpConfigOptions: s.acpConfigOptions,
211
- acpModes: s.acpModes,
212
- errorMessage: s.errorMessage,
213
- errorReason: s.errorReason,
214
- }));
158
+ // IDE/CLI/ACP states → managed entries (shared builder)
159
+ const { managedIdes, managedClis, managedAcps } = buildAllManagedEntries(
160
+ allStates,
161
+ this.deps.cdpManagers as Map<string, any>,
162
+ );
163
+
164
+
215
165
 
216
166
 
217
167
 
@@ -265,33 +215,30 @@ export class DaemonStatusReporter {
265
215
 
266
216
  // ═══ Server transmit (minimal routing meta only — sanitizeForRelay removes everything else) ═══
267
217
  if (opts?.p2pOnly) return;
268
- const plan = serverConn.getUserPlan();
269
- if (plan !== 'free') {
270
- const wsPayload = {
271
- daemonMode: true,
272
- machineNickname: payload.machineNickname,
273
- defaultWorkspaceId: wsState.defaultWorkspaceId,
274
- workspaceCount: (wsState.workspaces || []).length,
218
+ const wsPayload = {
219
+ daemonMode: true,
220
+ machineNickname: payload.machineNickname,
221
+ defaultWorkspaceId: wsState.defaultWorkspaceId,
222
+ workspaceCount: (wsState.workspaces || []).length,
275
223
  // managedIdes: server only saves id, type, cdpConnected
276
- managedIdes: managedIdes.map(ide => ({
277
- ideType: ide.ideType,
278
- instanceId: ide.instanceId,
279
- cdpConnected: ide.cdpConnected,
280
- })),
224
+ managedIdes: managedIdes.map(ide => ({
225
+ ideType: ide.ideType,
226
+ instanceId: ide.instanceId,
227
+ cdpConnected: ide.cdpConnected,
228
+ })),
281
229
  // managedClis: server only saves id, type, name
282
- managedClis: managedClis.map(c => ({
283
- id: c.id, cliType: c.cliType, cliName: c.cliName,
284
- })),
230
+ managedClis: managedClis.map(c => ({
231
+ id: c.id, cliType: c.cliType, cliName: c.cliName,
232
+ })),
285
233
  // managedAcps: server only saves id, type, name
286
- managedAcps: managedAcps?.map((a: any) => ({
287
- id: a.id, acpType: a.acpType, acpName: a.acpName,
288
- })),
289
- p2p: payload.p2p,
290
- timestamp: now,
291
- };
292
- serverConn.sendMessage('status_report', wsPayload);
293
- LOG.debug('Server', `sent status_report (${JSON.stringify(wsPayload).length} bytes)`);
294
- }
234
+ managedAcps: managedAcps?.map((a: any) => ({
235
+ id: a.id, acpType: a.acpType, acpName: a.acpName,
236
+ })),
237
+ p2p: payload.p2p,
238
+ timestamp: now,
239
+ };
240
+ serverConn.sendMessage('status_report', wsPayload);
241
+ LOG.debug('Server', `sent status_report (${JSON.stringify(wsPayload).length} bytes)`);
295
242
  }
296
243
 
297
244
  // ─── P2P ─────────────────────────────────────────