@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.
- package/dist/index.d.ts +79 -2
- package/dist/index.js +1131 -433
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/extension/cline/scripts/read_chat.js +14 -1
- package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +24 -1
- package/providers/_builtin/ide/antigravity/scripts/1.107/read_chat.js +24 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +3 -3
- package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +4 -4
- package/providers/_builtin/ide/cursor/scripts/0.49/read_chat.js +5 -1
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/dismiss_notification.js +30 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/focus_editor.js +13 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_models.js +78 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_modes.js +40 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_notifications.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_sessions.js +42 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/new_session.js +20 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/open_panel.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/read_chat.js +79 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/resolve_action.js +19 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/scripts.js +78 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/send_message.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/set_mode.js +38 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/set_model.js +81 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/switch_session.js +28 -0
- package/providers/_builtin/ide/windsurf/scripts/read_chat.js +18 -1
- package/src/cli-adapters/provider-cli-adapter.ts +167 -7
- package/src/commands/cli-manager.ts +128 -30
- package/src/commands/handler.ts +47 -3
- package/src/commands/router.ts +32 -2
- package/src/commands/workspace-commands.ts +108 -0
- package/src/config/config.ts +29 -1
- package/src/config/workspace-activity.ts +65 -0
- package/src/config/workspaces.ts +250 -0
- package/src/daemon/dev-server.ts +1 -1
- package/src/index.ts +5 -0
- package/src/launch.ts +1 -1
- package/src/providers/ide-provider-instance.ts +11 -0
- package/src/status/reporter.ts +23 -4
- package/src/system/host-memory.ts +65 -0
- package/src/types.ts +8 -1
package/src/commands/handler.ts
CHANGED
|
@@ -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)
|
|
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(
|
|
351
|
+
private async handleGetRecentWorkspaces(_args: any): Promise<CommandResult> {
|
|
316
352
|
const config = loadConfig();
|
|
317
353
|
const cliRecent = config.recentCliWorkspaces || [];
|
|
318
|
-
|
|
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> {
|
package/src/commands/router.ts
CHANGED
|
@@ -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
|
|
189
|
-
const
|
|
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
|
+
}
|
package/src/config/config.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -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
|
|
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];
|