@adhdev/daemon-core 0.5.3 → 0.5.6
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 +88 -2
- package/dist/index.js +1230 -439
- 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 +231 -12
- package/src/commands/chat-commands.ts +36 -0
- 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/cli-provider-instance.ts +7 -2
- 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
|
@@ -12,6 +12,8 @@ import chalk from 'chalk';
|
|
|
12
12
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
13
13
|
import { detectCLI } from '../detection/cli-detector.js';
|
|
14
14
|
import { loadConfig, saveConfig, addCliHistory } from '../config/config.js';
|
|
15
|
+
import { getWorkspaceState, resolveLaunchDirectory } from '../config/workspaces.js';
|
|
16
|
+
import { appendWorkspaceActivity } from '../config/workspace-activity.js';
|
|
15
17
|
import { CliProviderInstance } from '../providers/cli-provider-instance.js';
|
|
16
18
|
import { AcpProviderInstance } from '../providers/acp-provider-instance.js';
|
|
17
19
|
import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
@@ -54,6 +56,25 @@ export class DaemonCliManager {
|
|
|
54
56
|
return `${cliType}_${hash}`;
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
private persistRecentDir(cliType: string, dir: string): void {
|
|
60
|
+
try {
|
|
61
|
+
const normalizedType = this.providerLoader.resolveAlias(cliType);
|
|
62
|
+
const provider = this.providerLoader.getByAlias(cliType);
|
|
63
|
+
const actKind = provider?.category === 'acp' ? 'acp' : 'cli';
|
|
64
|
+
let next = loadConfig();
|
|
65
|
+
console.log(chalk.cyan(` 📂 Saving recent workspace: ${dir}`));
|
|
66
|
+
const recent = next.recentCliWorkspaces || [];
|
|
67
|
+
if (!recent.includes(dir)) {
|
|
68
|
+
next = { ...next, recentCliWorkspaces: [dir, ...recent].slice(0, 10) };
|
|
69
|
+
}
|
|
70
|
+
next = appendWorkspaceActivity(next, dir, { kind: actKind, agentType: normalizedType });
|
|
71
|
+
saveConfig(next);
|
|
72
|
+
console.log(chalk.green(` ✓ Recent workspace saved: ${dir}`));
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.error(chalk.red(` ✗ Failed to save recent workspace: ${e}`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
private createAdapter(cliType: string, workingDir: string, cliArgs?: string[]): CliAdapter {
|
|
58
79
|
// cliType normalize (Resolve alias)
|
|
59
80
|
const normalizedType = this.providerLoader.resolveAlias(cliType);
|
|
@@ -71,7 +92,8 @@ export class DaemonCliManager {
|
|
|
71
92
|
// ─── Session start/management ──────────────────────────────
|
|
72
93
|
|
|
73
94
|
async startSession(cliType: string, workingDir: string, cliArgs?: string[], initialModel?: string): Promise<void> {
|
|
74
|
-
const trimmed = (workingDir ||
|
|
95
|
+
const trimmed = (workingDir || '').trim();
|
|
96
|
+
if (!trimmed) throw new Error('working directory required');
|
|
75
97
|
const resolvedDir = trimmed.startsWith('~')
|
|
76
98
|
? trimmed.replace(/^~/, os.homedir())
|
|
77
99
|
: path.resolve(trimmed);
|
|
@@ -161,21 +183,54 @@ export class DaemonCliManager {
|
|
|
161
183
|
const instanceManager = this.deps.getInstanceManager();
|
|
162
184
|
if (provider && instanceManager) {
|
|
163
185
|
const cliInstance = new CliProviderInstance(provider, resolvedDir, cliArgs, key);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
186
|
+
try {
|
|
187
|
+
await instanceManager.addInstance(key, cliInstance, {
|
|
188
|
+
serverConn: this.deps.getServerConn(),
|
|
189
|
+
settings: {},
|
|
190
|
+
onPtyData: (data: string) => {
|
|
191
|
+
this.deps.getP2p()?.broadcastPtyOutput(key, data);
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
} catch (spawnErr: any) {
|
|
195
|
+
// Spawn failed — cleanup and propagate error
|
|
196
|
+
LOG.error('CLI', `[${cliType}] Spawn failed: ${spawnErr?.message}`);
|
|
197
|
+
instanceManager.removeInstance(key);
|
|
198
|
+
throw new Error(`Failed to start ${cliInfo.displayName}: ${spawnErr?.message}`);
|
|
199
|
+
}
|
|
171
200
|
|
|
172
201
|
// Keep adapter ref too (backward compat — write, resize etc)
|
|
173
202
|
this.adapters.set(key, cliInstance.getAdapter() as any);
|
|
174
203
|
console.log(chalk.green(` ✓ CLI started: ${cliInfo.displayName} v${cliInfo.version || 'unknown'} in ${resolvedDir}`));
|
|
204
|
+
|
|
205
|
+
// Monitor for stopped/error → auto-cleanup
|
|
206
|
+
const checkStopped = setInterval(() => {
|
|
207
|
+
try {
|
|
208
|
+
const adapter = this.adapters.get(key);
|
|
209
|
+
if (!adapter) { clearInterval(checkStopped); return; }
|
|
210
|
+
const status = adapter.getStatus?.();
|
|
211
|
+
if (status?.status === 'stopped' || status?.status === 'error') {
|
|
212
|
+
clearInterval(checkStopped);
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
if (this.adapters.has(key)) {
|
|
215
|
+
this.adapters.delete(key);
|
|
216
|
+
this.deps.removeAgentTracking(key);
|
|
217
|
+
instanceManager.removeInstance(key);
|
|
218
|
+
LOG.info('CLI', `🧹 Auto-cleaned ${status.status} CLI: ${cliType}`);
|
|
219
|
+
this.deps.onStatusChange();
|
|
220
|
+
}
|
|
221
|
+
}, 5000);
|
|
222
|
+
}
|
|
223
|
+
} catch { /* ignore */ }
|
|
224
|
+
}, 3000);
|
|
175
225
|
} else {
|
|
176
226
|
// Fallback: InstanceManager without directly adapter manage
|
|
177
227
|
const adapter = this.createAdapter(cliType, resolvedDir, cliArgs);
|
|
178
|
-
|
|
228
|
+
try {
|
|
229
|
+
await adapter.spawn();
|
|
230
|
+
} catch (spawnErr: any) {
|
|
231
|
+
LOG.error('CLI', `[${cliType}] Spawn failed: ${spawnErr?.message}`);
|
|
232
|
+
throw new Error(`Failed to start ${cliInfo.displayName}: ${spawnErr?.message}`);
|
|
233
|
+
}
|
|
179
234
|
|
|
180
235
|
const serverConn = this.deps.getServerConn();
|
|
181
236
|
if (serverConn && typeof adapter.setServerConn === 'function') {
|
|
@@ -184,12 +239,12 @@ export class DaemonCliManager {
|
|
|
184
239
|
adapter.setOnStatusChange(() => {
|
|
185
240
|
this.deps.onStatusChange();
|
|
186
241
|
const status = adapter.getStatus?.();
|
|
187
|
-
if (status?.status === 'stopped') {
|
|
242
|
+
if (status?.status === 'stopped' || status?.status === 'error') {
|
|
188
243
|
setTimeout(() => {
|
|
189
244
|
if (this.adapters.get(key) === adapter) {
|
|
190
245
|
this.adapters.delete(key);
|
|
191
246
|
this.deps.removeAgentTracking(key);
|
|
192
|
-
|
|
247
|
+
LOG.info('CLI', `🧹 Auto-cleaned ${status.status} CLI: ${adapter.cliType}`);
|
|
193
248
|
this.deps.onStatusChange();
|
|
194
249
|
}
|
|
195
250
|
}, 3000);
|
|
@@ -214,13 +269,26 @@ export class DaemonCliManager {
|
|
|
214
269
|
async stopSession(key: string): Promise<void> {
|
|
215
270
|
const adapter = this.adapters.get(key);
|
|
216
271
|
if (adapter) {
|
|
217
|
-
|
|
272
|
+
try {
|
|
273
|
+
adapter.shutdown();
|
|
274
|
+
} catch (e: any) {
|
|
275
|
+
LOG.warn('CLI', `Shutdown error for ${adapter.cliType}: ${e?.message} (force-cleaning)`);
|
|
276
|
+
}
|
|
277
|
+
// Always cleanup regardless of shutdown success
|
|
218
278
|
this.adapters.delete(key);
|
|
219
279
|
this.deps.removeAgentTracking(key);
|
|
220
|
-
// Also remove from InstanceManager
|
|
221
280
|
this.deps.getInstanceManager()?.removeInstance(key);
|
|
222
|
-
|
|
281
|
+
LOG.info('CLI', `🛑 Agent stopped: ${adapter.cliType} in ${adapter.workingDir}`);
|
|
223
282
|
this.deps.onStatusChange();
|
|
283
|
+
} else {
|
|
284
|
+
// Adapter not found — try InstanceManager direct removal
|
|
285
|
+
const im = this.deps.getInstanceManager();
|
|
286
|
+
if (im) {
|
|
287
|
+
im.removeInstance(key);
|
|
288
|
+
this.deps.removeAgentTracking(key);
|
|
289
|
+
LOG.warn('CLI', `🧹 Force-removed orphan entry: ${key}`);
|
|
290
|
+
this.deps.onStatusChange();
|
|
291
|
+
}
|
|
224
292
|
}
|
|
225
293
|
}
|
|
226
294
|
|
|
@@ -270,8 +338,28 @@ export class DaemonCliManager {
|
|
|
270
338
|
switch (cmd) {
|
|
271
339
|
case 'launch_cli': {
|
|
272
340
|
const cliType = args?.cliType;
|
|
273
|
-
const
|
|
274
|
-
const
|
|
341
|
+
const config = loadConfig();
|
|
342
|
+
const resolved = resolveLaunchDirectory(
|
|
343
|
+
{
|
|
344
|
+
dir: args?.dir,
|
|
345
|
+
workspaceId: args?.workspaceId,
|
|
346
|
+
useDefaultWorkspace: args?.useDefaultWorkspace === true,
|
|
347
|
+
useHome: args?.useHome === true,
|
|
348
|
+
},
|
|
349
|
+
config,
|
|
350
|
+
);
|
|
351
|
+
if (!resolved.ok) {
|
|
352
|
+
const ws = getWorkspaceState(config);
|
|
353
|
+
return {
|
|
354
|
+
success: false,
|
|
355
|
+
error: resolved.message,
|
|
356
|
+
code: resolved.code,
|
|
357
|
+
workspaces: ws.workspaces,
|
|
358
|
+
defaultWorkspacePath: ws.defaultWorkspacePath,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const dir = resolved.path;
|
|
362
|
+
const launchSource = resolved.source;
|
|
275
363
|
if (!cliType) throw new Error('cliType required');
|
|
276
364
|
|
|
277
365
|
await this.startSession(cliType, dir, args?.cliArgs, args?.initialModel);
|
|
@@ -284,20 +372,9 @@ export class DaemonCliManager {
|
|
|
284
372
|
}
|
|
285
373
|
}
|
|
286
374
|
|
|
287
|
-
|
|
288
|
-
const config = loadConfig();
|
|
289
|
-
console.log(chalk.cyan(` 📂 Saving recent workspace: ${dir}`));
|
|
290
|
-
const recent = config.recentCliWorkspaces || [];
|
|
291
|
-
if (!recent.includes(dir)) {
|
|
292
|
-
const updated = [dir, ...recent].slice(0, 10);
|
|
293
|
-
saveConfig({ ...config, recentCliWorkspaces: updated });
|
|
294
|
-
console.log(chalk.green(` ✓ Recent workspace saved: ${dir}`));
|
|
295
|
-
}
|
|
296
|
-
} catch (e) {
|
|
297
|
-
console.error(chalk.red(` ✗ Failed to save recent workspace: ${e}`));
|
|
298
|
-
}
|
|
375
|
+
this.persistRecentDir(cliType, dir);
|
|
299
376
|
|
|
300
|
-
return { success: true, cliType, dir, id: newKey,
|
|
377
|
+
return { success: true, cliType, dir, id: newKey, launchSource };
|
|
301
378
|
}
|
|
302
379
|
case 'stop_cli': {
|
|
303
380
|
const cliType = args?.cliType;
|
|
@@ -314,11 +391,32 @@ export class DaemonCliManager {
|
|
|
314
391
|
}
|
|
315
392
|
case 'restart_session': {
|
|
316
393
|
const cliType = args?.cliType || args?.agentType || args?.ideType;
|
|
317
|
-
const
|
|
394
|
+
const cfg = loadConfig();
|
|
395
|
+
const rdir = resolveLaunchDirectory(
|
|
396
|
+
{
|
|
397
|
+
dir: args?.dir,
|
|
398
|
+
workspaceId: args?.workspaceId,
|
|
399
|
+
useDefaultWorkspace: args?.useDefaultWorkspace === true,
|
|
400
|
+
useHome: args?.useHome === true,
|
|
401
|
+
},
|
|
402
|
+
cfg,
|
|
403
|
+
);
|
|
404
|
+
if (!rdir.ok) {
|
|
405
|
+
const ws = getWorkspaceState(cfg);
|
|
406
|
+
return {
|
|
407
|
+
success: false,
|
|
408
|
+
error: rdir.message,
|
|
409
|
+
code: rdir.code,
|
|
410
|
+
workspaces: ws.workspaces,
|
|
411
|
+
defaultWorkspacePath: ws.defaultWorkspacePath,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
const dir = rdir.path;
|
|
318
415
|
if (!cliType) throw new Error('cliType required');
|
|
319
416
|
const found = this.findAdapter(cliType, { instanceKey: args?._targetInstance, dir });
|
|
320
417
|
if (found) await this.stopSession(found.key);
|
|
321
418
|
await this.startSession(cliType, dir);
|
|
419
|
+
this.persistRecentDir(cliType, dir);
|
|
322
420
|
return { success: true, restarted: true };
|
|
323
421
|
}
|
|
324
422
|
case 'agent_command': {
|
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
|
+
}
|