@adhdev/daemon-core 0.5.3 → 0.5.5

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 (43) hide show
  1. package/dist/index.d.ts +79 -2
  2. package/dist/index.js +1131 -433
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/providers/_builtin/extension/cline/scripts/read_chat.js +14 -1
  6. package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +24 -1
  7. package/providers/_builtin/ide/antigravity/scripts/1.107/read_chat.js +24 -1
  8. package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +3 -3
  9. package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +1 -1
  10. package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +1 -1
  11. package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +4 -4
  12. package/providers/_builtin/ide/cursor/scripts/0.49/read_chat.js +5 -1
  13. package/providers/_builtin/ide/cursor/scripts/0.49.bak/dismiss_notification.js +30 -0
  14. package/providers/_builtin/ide/cursor/scripts/0.49.bak/focus_editor.js +13 -0
  15. package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_models.js +78 -0
  16. package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_modes.js +40 -0
  17. package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_notifications.js +23 -0
  18. package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_sessions.js +42 -0
  19. package/providers/_builtin/ide/cursor/scripts/0.49.bak/new_session.js +20 -0
  20. package/providers/_builtin/ide/cursor/scripts/0.49.bak/open_panel.js +23 -0
  21. package/providers/_builtin/ide/cursor/scripts/0.49.bak/read_chat.js +79 -0
  22. package/providers/_builtin/ide/cursor/scripts/0.49.bak/resolve_action.js +19 -0
  23. package/providers/_builtin/ide/cursor/scripts/0.49.bak/scripts.js +78 -0
  24. package/providers/_builtin/ide/cursor/scripts/0.49.bak/send_message.js +23 -0
  25. package/providers/_builtin/ide/cursor/scripts/0.49.bak/set_mode.js +38 -0
  26. package/providers/_builtin/ide/cursor/scripts/0.49.bak/set_model.js +81 -0
  27. package/providers/_builtin/ide/cursor/scripts/0.49.bak/switch_session.js +28 -0
  28. package/providers/_builtin/ide/windsurf/scripts/read_chat.js +18 -1
  29. package/src/cli-adapters/provider-cli-adapter.ts +167 -7
  30. package/src/commands/cli-manager.ts +128 -30
  31. package/src/commands/handler.ts +47 -3
  32. package/src/commands/router.ts +32 -2
  33. package/src/commands/workspace-commands.ts +108 -0
  34. package/src/config/config.ts +29 -1
  35. package/src/config/workspace-activity.ts +65 -0
  36. package/src/config/workspaces.ts +250 -0
  37. package/src/daemon/dev-server.ts +1 -1
  38. package/src/index.ts +5 -0
  39. package/src/launch.ts +1 -1
  40. package/src/providers/ide-provider-instance.ts +11 -0
  41. package/src/status/reporter.ts +23 -4
  42. package/src/system/host-memory.ts +65 -0
  43. package/src/types.ts +8 -1
@@ -23,6 +23,9 @@ import { LOG } from '../logging/logger.js';
23
23
  import * as Chat from './chat-commands.js';
24
24
  import * as Cdp from './cdp-commands.js';
25
25
  import * as Stream from './stream-commands.js';
26
+ import * as WorkspaceCmd from './workspace-commands.js';
27
+ import { getWorkspaceState } from '../config/workspaces.js';
28
+ import { getWorkspaceActivity } from '../config/workspace-activity.js';
26
29
 
27
30
  export interface CommandResult {
28
31
  success: boolean;
@@ -176,6 +179,11 @@ export class DaemonCommandHandler implements CommandHelpers {
176
179
 
177
180
  /** Extract ideType from _targetInstance */
178
181
  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;
185
+ }
186
+
179
187
  if (args?._targetInstance) {
180
188
  let raw = args._targetInstance as string;
181
189
  const ideMatch = raw.match(/:ide:(.+)$/);
@@ -189,8 +197,25 @@ export class DaemonCommandHandler implements CommandHelpers {
189
197
  return this._ctx.instanceIdMap.get(raw)!;
190
198
  }
191
199
 
200
+ // Direct CDP manager key match (e.g. "cursor", "antigravity")
201
+ if (this._ctx.cdpManagers.has(raw)) {
202
+ return raw;
203
+ }
204
+
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;
210
+ }
211
+ }
212
+
213
+ // Legacy: strip trailing _N suffix (e.g. "cursor_1" → "cursor")
192
214
  const lastUnderscore = raw.lastIndexOf('_');
193
- if (lastUnderscore > 0) return raw.substring(0, lastUnderscore);
215
+ if (lastUnderscore > 0) {
216
+ const stripped = raw.substring(0, lastUnderscore);
217
+ if (this._ctx.cdpManagers.has(stripped)) return stripped;
218
+ }
194
219
  return raw;
195
220
  }
196
221
  return undefined;
@@ -273,6 +298,17 @@ export class DaemonCommandHandler implements CommandHelpers {
273
298
  case 'get_commands':
274
299
  return { success: false, error: `${cmd} requires bridge-extension (removed)` };
275
300
  case 'get_recent_workspaces': return this.handleGetRecentWorkspaces(args);
301
+ case 'get_cli_history': {
302
+ const config = loadConfig();
303
+ return { success: true, history: config.cliHistory || [] };
304
+ }
305
+
306
+ case 'workspace_list': return WorkspaceCmd.handleWorkspaceList();
307
+ case 'workspace_add': return WorkspaceCmd.handleWorkspaceAdd(args);
308
+ case 'workspace_remove': return WorkspaceCmd.handleWorkspaceRemove(args);
309
+ case 'workspace_set_default':
310
+ case 'workspace_set_active':
311
+ return WorkspaceCmd.handleWorkspaceSetDefault(args);
276
312
 
277
313
  // ─── Script manage ───────────────────
278
314
  case 'refresh_scripts': return this.handleRefreshScripts(args);
@@ -312,10 +348,18 @@ export class DaemonCommandHandler implements CommandHelpers {
312
348
 
313
349
  // ─── Misc (kept in handler — too small to extract) ───────
314
350
 
315
- private async handleGetRecentWorkspaces(args: any): Promise<CommandResult> {
351
+ private async handleGetRecentWorkspaces(_args: any): Promise<CommandResult> {
316
352
  const config = loadConfig();
317
353
  const cliRecent = config.recentCliWorkspaces || [];
318
- return { success: true, result: cliRecent };
354
+ const ws = getWorkspaceState(config);
355
+ return {
356
+ success: true,
357
+ result: cliRecent,
358
+ workspaces: ws.workspaces,
359
+ defaultWorkspaceId: ws.defaultWorkspaceId,
360
+ defaultWorkspacePath: ws.defaultWorkspacePath,
361
+ activity: getWorkspaceActivity(config, 25),
362
+ };
319
363
  }
320
364
 
321
365
  private async handleRefreshScripts(_args: any): Promise<CommandResult> {
@@ -17,6 +17,9 @@ import { DaemonCliManager } from './cli-manager.js';
17
17
  import type { ProviderLoader } from '../providers/provider-loader.js';
18
18
  import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
19
19
  import { launchWithCdp } from '../launch.js';
20
+ import { loadConfig, saveConfig, updateConfig } from '../config/config.js';
21
+ import { resolveIdeLaunchWorkspace } from '../config/workspaces.js';
22
+ import { appendWorkspaceActivity } from '../config/workspace-activity.js';
20
23
  import { detectIDEs } from '../detection/ide-detector.js';
21
24
  import { LOG } from '../logging/logger.js';
22
25
  import { logCommand } from '../logging/command-log.js';
@@ -185,8 +188,20 @@ export class DaemonCommandRouter {
185
188
 
186
189
  // ─── IDE launch + CDP connect ───
187
190
  case 'launch_ide': {
188
- const launchArgs = { ...args, ideId: args?.ideId || args?.ideType };
189
- const ideKey = launchArgs.ideId;
191
+ const ideKey = args?.ideId || args?.ideType;
192
+ const resolvedWorkspace = resolveIdeLaunchWorkspace(
193
+ {
194
+ workspace: args?.workspace,
195
+ workspaceId: args?.workspaceId,
196
+ useDefaultWorkspace: args?.useDefaultWorkspace,
197
+ },
198
+ loadConfig(),
199
+ );
200
+ const launchArgs = {
201
+ ideId: ideKey,
202
+ workspace: resolvedWorkspace,
203
+ newWindow: args?.newWindow,
204
+ };
190
205
  LOG.info('LaunchIDE', `target=${ideKey || 'auto'}`);
191
206
  const result = await launchWithCdp(launchArgs);
192
207
 
@@ -209,6 +224,14 @@ export class DaemonCommandRouter {
209
224
  }
210
225
  }
211
226
  this.deps.onIdeConnected?.();
227
+ if (result.success && resolvedWorkspace) {
228
+ try {
229
+ saveConfig(appendWorkspaceActivity(loadConfig(), resolvedWorkspace, {
230
+ kind: 'ide',
231
+ agentType: result.ideId,
232
+ }));
233
+ } catch { /* ignore activity persist errors */ }
234
+ }
212
235
  return { success: result.success, ...result as any };
213
236
  }
214
237
 
@@ -217,6 +240,13 @@ export class DaemonCommandRouter {
217
240
  this.deps.detectedIdes.value = await detectIDEs();
218
241
  return { success: true, ides: this.deps.detectedIdes.value };
219
242
  }
243
+
244
+ // ─── Machine Settings ───
245
+ case 'set_machine_nickname': {
246
+ const nickname = args?.nickname;
247
+ updateConfig({ machineNickname: nickname || null });
248
+ return { success: true };
249
+ }
220
250
  }
221
251
 
222
252
  return null; // Not handled at this level → delegate to CommandHandler
@@ -0,0 +1,108 @@
1
+ /**
2
+ * workspace_* commands — list/add/remove/default (config.json)
3
+ */
4
+
5
+ import { loadConfig, saveConfig } from '../config/config.js';
6
+ import * as W from '../config/workspaces.js';
7
+ import { appendWorkspaceActivity, getWorkspaceActivity, removeActivityForPath } from '../config/workspace-activity.js';
8
+
9
+ export type WorkspaceCommandResult = { success: boolean;[key: string]: unknown };
10
+
11
+ export function handleWorkspaceList(): WorkspaceCommandResult {
12
+ const config = loadConfig();
13
+ const state = W.getWorkspaceState(config);
14
+ return {
15
+ success: true,
16
+ workspaces: state.workspaces,
17
+ defaultWorkspaceId: state.defaultWorkspaceId,
18
+ defaultWorkspacePath: state.defaultWorkspacePath,
19
+ legacyRecentPaths: config.recentCliWorkspaces || [],
20
+ activity: getWorkspaceActivity(config, 25),
21
+ };
22
+ }
23
+
24
+ export function handleWorkspaceAdd(args: any): WorkspaceCommandResult {
25
+ const rawPath = (args?.path || args?.dir || '').trim();
26
+ const label = (args?.label || '').trim() || undefined;
27
+ if (!rawPath) return { success: false, error: 'path required' };
28
+
29
+ const config = loadConfig();
30
+ const result = W.addWorkspaceEntry(config, rawPath, label);
31
+ if ('error' in result) return { success: false, error: result.error };
32
+
33
+ let cfg = appendWorkspaceActivity(result.config, result.entry.path, {});
34
+ saveConfig(cfg);
35
+ const state = W.getWorkspaceState(cfg);
36
+ return { success: true, entry: result.entry, ...state, activity: getWorkspaceActivity(cfg, 25) };
37
+ }
38
+
39
+ export function handleWorkspaceRemove(args: any): WorkspaceCommandResult {
40
+ const id = (args?.id || '').trim();
41
+ if (!id) return { success: false, error: 'id required' };
42
+
43
+ const config = loadConfig();
44
+ const removed = (config.workspaces || []).find(w => w.id === id);
45
+ const result = W.removeWorkspaceEntry(config, id);
46
+ if ('error' in result) return { success: false, error: result.error };
47
+
48
+ let cfg = result.config;
49
+ if (removed) {
50
+ cfg = removeActivityForPath(cfg, removed.path);
51
+ }
52
+ saveConfig(cfg);
53
+ const state = W.getWorkspaceState(cfg);
54
+ return { success: true, removedId: id, ...state, activity: getWorkspaceActivity(cfg, 25) };
55
+ }
56
+
57
+ export function handleWorkspaceSetDefault(args: any): WorkspaceCommandResult {
58
+ const clear = args?.clear === true || args?.id === null || args?.id === '';
59
+ if (clear) {
60
+ const config = loadConfig();
61
+ const result = W.setDefaultWorkspaceId(config, null);
62
+ if ('error' in result) return { success: false, error: result.error };
63
+ saveConfig(result.config);
64
+ const state = W.getWorkspaceState(result.config);
65
+ return {
66
+ success: true,
67
+ ...state,
68
+ activity: getWorkspaceActivity(result.config, 25),
69
+ };
70
+ }
71
+
72
+ const pathArg = (args?.path != null && String(args.path).trim()) ? String(args.path).trim() : '';
73
+ const idArg = args?.id !== undefined && args?.id !== null && String(args.id).trim()
74
+ ? String(args.id).trim()
75
+ : '';
76
+
77
+ if (!pathArg && !idArg) {
78
+ return { success: false, error: 'id or path required (or clear: true)' };
79
+ }
80
+
81
+ let config = loadConfig();
82
+ let nextId: string;
83
+
84
+ if (pathArg) {
85
+ let w = W.findWorkspaceByPath(config, pathArg);
86
+ if (!w) {
87
+ const add = W.addWorkspaceEntry(config, pathArg);
88
+ if ('error' in add) return { success: false, error: add.error };
89
+ config = add.config;
90
+ w = add.entry;
91
+ }
92
+ nextId = w.id;
93
+ } else {
94
+ nextId = idArg;
95
+ }
96
+
97
+ const result = W.setDefaultWorkspaceId(config, nextId);
98
+ if ('error' in result) return { success: false, error: result.error };
99
+
100
+ let out = result.config;
101
+ const ap = W.getDefaultWorkspacePath(out);
102
+ if (ap) {
103
+ out = appendWorkspaceActivity(out, ap, { kind: 'default' });
104
+ }
105
+ saveConfig(out);
106
+ const state = W.getWorkspaceState(out);
107
+ return { success: true, ...state, activity: getWorkspaceActivity(out, 25) };
108
+ }
@@ -7,6 +7,11 @@
7
7
  import { homedir } from 'os';
8
8
  import { join } from 'path';
9
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'fs';
10
+ import { migrateWorkspacesFromRecent } from './workspaces.js';
11
+ import type { WorkspaceEntry } from './workspaces.js';
12
+ import type { WorkspaceActivityEntry } from './workspace-activity.js';
13
+ export type { WorkspaceEntry } from './workspaces.js';
14
+ export type { WorkspaceActivityEntry } from './workspace-activity.js';
10
15
 
11
16
  export interface ADHDevConfig {
12
17
  // Server connection
@@ -42,6 +47,14 @@ export interface ADHDevConfig {
42
47
  enabledIdes: string[];
43
48
  recentCliWorkspaces: string[];
44
49
 
50
+ /** Saved workspaces for IDE/CLI/ACP launch (daemon-local) */
51
+ workspaces?: WorkspaceEntry[];
52
+ /** Default workspace id (from workspaces[]) — never used implicitly for launch */
53
+ defaultWorkspaceId?: string | null;
54
+
55
+ /** Recently used workspaces (IDE / CLI / ACP / default) for quick resume */
56
+ recentWorkspaceActivity?: WorkspaceActivityEntry[];
57
+
45
58
  // Machine nickname (user-customizable label for this machine)
46
59
  machineNickname: string | null;
47
60
 
@@ -81,6 +94,9 @@ const DEFAULT_CONFIG: ADHDevConfig = {
81
94
  configuredCLIs: [],
82
95
  enabledIdes: [],
83
96
  recentCliWorkspaces: [],
97
+ workspaces: [],
98
+ defaultWorkspaceId: null,
99
+ recentWorkspaceActivity: [],
84
100
  machineNickname: null,
85
101
  cliHistory: [],
86
102
  providerSettings: {},
@@ -118,7 +134,19 @@ export function loadConfig(): ADHDevConfig {
118
134
  try {
119
135
  const raw = readFileSync(configPath, 'utf-8');
120
136
  const parsed = JSON.parse(raw);
121
- return { ...DEFAULT_CONFIG, ...parsed };
137
+ const merged = { ...DEFAULT_CONFIG, ...parsed } as ADHDevConfig & { activeWorkspaceId?: string | null };
138
+ if (merged.defaultWorkspaceId == null && merged.activeWorkspaceId != null) {
139
+ (merged as ADHDevConfig).defaultWorkspaceId = merged.activeWorkspaceId;
140
+ }
141
+ delete (merged as any).activeWorkspaceId;
142
+ const hadStoredWorkspaces = Array.isArray(parsed.workspaces) && parsed.workspaces.length > 0;
143
+ migrateWorkspacesFromRecent(merged);
144
+ if (!hadStoredWorkspaces && (merged.workspaces?.length || 0) > 0) {
145
+ try {
146
+ saveConfig(merged);
147
+ } catch { /* ignore */ }
148
+ }
149
+ return merged;
122
150
  } catch {
123
151
  return { ...DEFAULT_CONFIG };
124
152
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Recent workspace activity — quick "pick up where you left off" (daemon-local).
3
+ */
4
+
5
+ import * as path from 'path';
6
+ import type { ADHDevConfig } from './config.js';
7
+ import { expandPath } from './workspaces.js';
8
+
9
+ export interface WorkspaceActivityEntry {
10
+ path: string;
11
+ lastUsedAt: number;
12
+ /** `active` legacy — same meaning as default */
13
+ kind?: 'ide' | 'cli' | 'acp' | 'default' | 'active';
14
+ /** IDE id or CLI/ACP provider type */
15
+ agentType?: string;
16
+ }
17
+
18
+ const MAX_ACTIVITY = 30;
19
+
20
+ export function normWorkspacePath(p: string): string {
21
+ try {
22
+ return path.resolve(expandPath(p));
23
+ } catch {
24
+ return path.resolve(p);
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Append or bump a path to the front of recent activity (returns new config object).
30
+ */
31
+ export function appendWorkspaceActivity(
32
+ config: ADHDevConfig,
33
+ rawPath: string,
34
+ meta?: { kind?: WorkspaceActivityEntry['kind']; agentType?: string },
35
+ ): ADHDevConfig {
36
+ const abs = normWorkspacePath(rawPath);
37
+ if (!abs) return config;
38
+
39
+ const prev = config.recentWorkspaceActivity || [];
40
+ const filtered = prev.filter(e => normWorkspacePath(e.path) !== abs);
41
+ const entry: WorkspaceActivityEntry = {
42
+ path: abs,
43
+ lastUsedAt: Date.now(),
44
+ kind: meta?.kind,
45
+ agentType: meta?.agentType,
46
+ };
47
+ const recentWorkspaceActivity = [entry, ...filtered].slice(0, MAX_ACTIVITY);
48
+ return { ...config, recentWorkspaceActivity };
49
+ }
50
+
51
+ export function getWorkspaceActivity(config: ADHDevConfig, limit = 20): WorkspaceActivityEntry[] {
52
+ const list = [...(config.recentWorkspaceActivity || [])];
53
+ list.sort((a, b) => b.lastUsedAt - a.lastUsedAt);
54
+ return list.slice(0, limit);
55
+ }
56
+
57
+ export function removeActivityForPath(config: ADHDevConfig, rawPath: string): ADHDevConfig {
58
+ const n = normWorkspacePath(rawPath);
59
+ return {
60
+ ...config,
61
+ recentWorkspaceActivity: (config.recentWorkspaceActivity || []).filter(
62
+ e => normWorkspacePath(e.path) !== n,
63
+ ),
64
+ };
65
+ }
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Saved workspaces — shared by IDE launch, CLI, ACP (daemon-local).
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as os from 'os';
7
+ import * as path from 'path';
8
+ import { randomUUID } from 'crypto';
9
+ import type { ADHDevConfig } from './config.js';
10
+
11
+ export interface WorkspaceEntry {
12
+ id: string;
13
+ path: string;
14
+ label?: string;
15
+ addedAt: number;
16
+ }
17
+
18
+ const MAX_WORKSPACES = 50;
19
+
20
+ export function expandPath(p: string): string {
21
+ const t = (p || '').trim();
22
+ if (!t) return '';
23
+ if (t.startsWith('~')) return path.join(os.homedir(), t.slice(1).replace(/^\//, ''));
24
+ return path.resolve(t);
25
+ }
26
+
27
+ export function validateWorkspacePath(absPath: string): { ok: true } | { ok: false; error: string } {
28
+ try {
29
+ if (!absPath) return { ok: false, error: 'Path required' };
30
+ if (!fs.existsSync(absPath)) return { ok: false, error: 'Path does not exist' };
31
+ const st = fs.statSync(absPath);
32
+ if (!st.isDirectory()) return { ok: false, error: 'Not a directory' };
33
+ return { ok: true };
34
+ } catch (e: any) {
35
+ return { ok: false, error: e?.message || 'Invalid path' };
36
+ }
37
+ }
38
+
39
+ /** Default workspace label from path */
40
+ export function defaultWorkspaceLabel(absPath: string): string {
41
+ const base = path.basename(absPath) || absPath;
42
+ return base;
43
+ }
44
+
45
+ /**
46
+ * Ensure config.workspaces exists; seed from recentCliWorkspaces once (same paths).
47
+ */
48
+ export function migrateWorkspacesFromRecent(config: ADHDevConfig): ADHDevConfig {
49
+ if (!config.workspaces) config.workspaces = [];
50
+ if (config.workspaces.length > 0) return config;
51
+
52
+ const recent = config.recentCliWorkspaces || [];
53
+ const now = Date.now();
54
+ for (const raw of recent) {
55
+ const abs = expandPath(raw);
56
+ if (!abs || validateWorkspacePath(abs).ok !== true) continue;
57
+ if (config.workspaces.some(w => path.resolve(w.path) === abs)) continue;
58
+ config.workspaces.push({
59
+ id: randomUUID(),
60
+ path: abs,
61
+ label: defaultWorkspaceLabel(abs),
62
+ addedAt: now,
63
+ });
64
+ if (config.workspaces.length >= MAX_WORKSPACES) break;
65
+ }
66
+ return config;
67
+ }
68
+
69
+ export function getDefaultWorkspacePath(config: ADHDevConfig): string | null {
70
+ const id = config.defaultWorkspaceId;
71
+ if (!id) return null;
72
+ const w = (config.workspaces || []).find(x => x.id === id);
73
+ if (!w) return null;
74
+ const abs = expandPath(w.path);
75
+ if (validateWorkspacePath(abs).ok !== true) return null;
76
+ return abs;
77
+ }
78
+
79
+ export function getWorkspaceState(config: ADHDevConfig): {
80
+ workspaces: WorkspaceEntry[];
81
+ defaultWorkspaceId: string | null;
82
+ defaultWorkspacePath: string | null;
83
+ } {
84
+ const workspaces = [...(config.workspaces || [])].sort((a, b) => b.addedAt - a.addedAt);
85
+ const defaultWorkspacePath = getDefaultWorkspacePath(config);
86
+ return {
87
+ workspaces,
88
+ defaultWorkspaceId: config.defaultWorkspaceId ?? null,
89
+ defaultWorkspacePath,
90
+ };
91
+ }
92
+
93
+ export type LaunchDirectorySource = 'dir' | 'workspaceId' | 'defaultWorkspace' | 'home';
94
+
95
+ export type ResolveLaunchDirectoryResult =
96
+ | { ok: true; path: string; source: LaunchDirectorySource }
97
+ | { ok: false; code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED'; message: string };
98
+
99
+ /**
100
+ * Resolve cwd for CLI/ACP. No implicit default workspace or home — caller must pass
101
+ * useDefaultWorkspace or useHome (or an explicit dir / workspaceId).
102
+ */
103
+ export function resolveLaunchDirectory(
104
+ args: {
105
+ dir?: string;
106
+ workspaceId?: string;
107
+ useDefaultWorkspace?: boolean;
108
+ useHome?: boolean;
109
+ } | undefined,
110
+ config: ADHDevConfig,
111
+ ): ResolveLaunchDirectoryResult {
112
+ const a = args || {};
113
+ if (a.dir != null && String(a.dir).trim()) {
114
+ const abs = expandPath(String(a.dir).trim());
115
+ if (abs && validateWorkspacePath(abs).ok === true) {
116
+ return { ok: true, path: abs, source: 'dir' };
117
+ }
118
+ return {
119
+ ok: false,
120
+ code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED',
121
+ message: abs ? 'Directory path is not valid or does not exist' : 'Invalid directory path',
122
+ };
123
+ }
124
+ if (a.workspaceId) {
125
+ const w = (config.workspaces || []).find(x => x.id === a.workspaceId);
126
+ if (w) {
127
+ const abs = expandPath(w.path);
128
+ if (validateWorkspacePath(abs).ok === true) {
129
+ return { ok: true, path: abs, source: 'workspaceId' };
130
+ }
131
+ }
132
+ return {
133
+ ok: false,
134
+ code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED',
135
+ message: 'Saved workspace not found or path is no longer valid',
136
+ };
137
+ }
138
+ if (a.useDefaultWorkspace === true) {
139
+ const d = getDefaultWorkspacePath(config);
140
+ if (d) return { ok: true, path: d, source: 'defaultWorkspace' };
141
+ return {
142
+ ok: false,
143
+ code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED',
144
+ message: 'No default workspace is set',
145
+ };
146
+ }
147
+ if (a.useHome === true) {
148
+ return { ok: true, path: os.homedir(), source: 'home' };
149
+ }
150
+ return {
151
+ ok: false,
152
+ code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED',
153
+ message: 'Choose a directory, saved workspace, default workspace, or home before launching.',
154
+ };
155
+ }
156
+
157
+ /**
158
+ * IDE folder from explicit args only (`workspace`, `workspaceId`, or `useDefaultWorkspace: true`).
159
+ */
160
+ export function resolveIdeWorkspaceFromArgs(
161
+ args: {
162
+ workspace?: string;
163
+ workspaceId?: string;
164
+ useDefaultWorkspace?: boolean;
165
+ } | undefined,
166
+ config: ADHDevConfig,
167
+ ): string | undefined {
168
+ const ar = args || {};
169
+ if (ar.workspace) {
170
+ const abs = expandPath(ar.workspace);
171
+ if (abs && validateWorkspacePath(abs).ok === true) return abs;
172
+ }
173
+ if (ar.workspaceId) {
174
+ const w = (config.workspaces || []).find(x => x.id === ar.workspaceId);
175
+ if (w) {
176
+ const abs = expandPath(w.path);
177
+ if (validateWorkspacePath(abs).ok === true) return abs;
178
+ }
179
+ }
180
+ if (ar.useDefaultWorkspace === true) {
181
+ return getDefaultWorkspacePath(config) || undefined;
182
+ }
183
+ return undefined;
184
+ }
185
+
186
+ /**
187
+ * IDE launch folder — same saved workspaces + default as CLI/ACP.
188
+ * After explicit `workspace` / `workspaceId` / `useDefaultWorkspace: true`, falls back to
189
+ * config default workspace when set. Pass `useDefaultWorkspace: false` to open IDE without that folder.
190
+ */
191
+ export function resolveIdeLaunchWorkspace(
192
+ args: {
193
+ workspace?: string;
194
+ workspaceId?: string;
195
+ useDefaultWorkspace?: boolean;
196
+ } | undefined,
197
+ config: ADHDevConfig,
198
+ ): string | undefined {
199
+ const direct = resolveIdeWorkspaceFromArgs(args, config);
200
+ if (direct) return direct;
201
+ if (args?.useDefaultWorkspace === false) return undefined;
202
+ return getDefaultWorkspacePath(config) || undefined;
203
+ }
204
+
205
+ export function findWorkspaceByPath(config: ADHDevConfig, rawPath: string): WorkspaceEntry | undefined {
206
+ const abs = path.resolve(expandPath(rawPath));
207
+ if (!abs) return undefined;
208
+ return (config.workspaces || []).find(w => path.resolve(expandPath(w.path)) === abs);
209
+ }
210
+
211
+ export function addWorkspaceEntry(config: ADHDevConfig, rawPath: string, label?: string): { config: ADHDevConfig; entry: WorkspaceEntry } | { error: string } {
212
+ const abs = expandPath(rawPath);
213
+ const v = validateWorkspacePath(abs);
214
+ if (!v.ok) return { error: v.error };
215
+
216
+ const list = [...(config.workspaces || [])];
217
+ if (list.some(w => path.resolve(w.path) === abs)) {
218
+ return { error: 'Workspace already in list' };
219
+ }
220
+ if (list.length >= MAX_WORKSPACES) {
221
+ return { error: `Maximum ${MAX_WORKSPACES} workspaces` };
222
+ }
223
+ const entry: WorkspaceEntry = {
224
+ id: randomUUID(),
225
+ path: abs,
226
+ label: (label || '').trim() || defaultWorkspaceLabel(abs),
227
+ addedAt: Date.now(),
228
+ };
229
+ list.push(entry);
230
+ return { config: { ...config, workspaces: list }, entry };
231
+ }
232
+
233
+ export function removeWorkspaceEntry(config: ADHDevConfig, id: string): { config: ADHDevConfig } | { error: string } {
234
+ const list = (config.workspaces || []).filter(w => w.id !== id);
235
+ if (list.length === (config.workspaces || []).length) return { error: 'Workspace not found' };
236
+ let defaultWorkspaceId = config.defaultWorkspaceId;
237
+ if (defaultWorkspaceId === id) defaultWorkspaceId = null;
238
+ return { config: { ...config, workspaces: list, defaultWorkspaceId } };
239
+ }
240
+
241
+ export function setDefaultWorkspaceId(config: ADHDevConfig, id: string | null): { config: ADHDevConfig } | { error: string } {
242
+ if (id === null) {
243
+ return { config: { ...config, defaultWorkspaceId: null } };
244
+ }
245
+ const w = (config.workspaces || []).find(x => x.id === id);
246
+ if (!w) return { error: 'Workspace not found' };
247
+ const abs = expandPath(w.path);
248
+ if (validateWorkspacePath(abs).ok !== true) return { error: 'Workspace path is no longer valid' };
249
+ return { config: { ...config, defaultWorkspaceId: id } };
250
+ }
@@ -2238,7 +2238,7 @@ export class DevServer {
2238
2238
  lines.push('## Required Return Format');
2239
2239
  lines.push('| Function | Return JSON |');
2240
2240
  lines.push('|---|---|');
2241
- lines.push('| readChat | `{ id, status, title, messages: [{role, content, index}], inputContent, activeModal }` |');
2241
+ lines.push('| readChat | `{ id, status, title, messages: [{role, content, index, kind?, meta?}], inputContent, activeModal }` — optional `kind`: standard, thought, tool, terminal; optional `meta`: e.g. `{ label, isRunning }` for dashboard |');
2242
2242
  lines.push('| sendMessage | `{ sent: false, needsTypeAndSend: true, selector }` |');
2243
2243
  lines.push('| resolveAction | `{ resolved: true/false, clicked? }` |');
2244
2244
  lines.push('| listSessions | `{ sessions: [{ id, title, active, index }] }` |');
package/src/index.ts CHANGED
@@ -29,11 +29,16 @@ export type { IDaemonCore, DaemonCoreOptions } from './daemon-core.js';
29
29
 
30
30
  // ── Config ──
31
31
  export { loadConfig, saveConfig, resetConfig, isSetupComplete, addCliHistory, markSetupComplete, updateConfig } from './config/config.js';
32
+ export { getWorkspaceState } from './config/workspaces.js';
33
+ export { getWorkspaceActivity } from './config/workspace-activity.js';
34
+ export type { WorkspaceEntry } from './config/workspaces.js';
32
35
 
33
36
  // ── Detection ──
34
37
  export { detectIDEs } from './detection/ide-detector.js';
35
38
  export type { IDEInfo } from './detection/ide-detector.js';
36
39
  export { detectCLIs } from './detection/cli-detector.js';
40
+ export { getHostMemorySnapshot } from './system/host-memory.js';
41
+ export type { HostMemorySnapshot } from './system/host-memory.js';
37
42
 
38
43
  // ── CDP ──
39
44
  export { DaemonCdpManager } from './cdp/manager.js';
package/src/launch.ts CHANGED
@@ -224,7 +224,7 @@ function detectCurrentWorkspace(ideId: string): string | undefined {
224
224
  );
225
225
  if (fs.existsSync(storagePath)) {
226
226
  const data = JSON.parse(fs.readFileSync(storagePath, 'utf-8'));
227
- // openedPathsList.workspaces3 has recent folders
227
+ // openedPathsList.workspaces3 has recent workspace paths
228
228
  const workspaces = data?.openedPathsList?.workspaces3 || data?.openedPathsList?.entries || [];
229
229
  if (workspaces.length > 0) {
230
230
  const recent = workspaces[0];