@adhdev/daemon-core 0.7.5 → 0.7.7

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.mts +164 -38
  2. package/dist/index.d.ts +164 -38
  3. package/dist/index.js +4051 -2547
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +3696 -2192
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/{normalize-tKg8IiDk.d.mts → normalize-auJAPmKy.d.mts} +669 -629
  8. package/dist/{normalize-tKg8IiDk.d.ts → normalize-auJAPmKy.d.ts} +669 -629
  9. package/dist/status/normalize.d.mts +1 -1
  10. package/dist/status/normalize.d.ts +1 -1
  11. package/package.json +5 -1
  12. package/src/agent-stream/forward.ts +6 -0
  13. package/src/boot/daemon-lifecycle.ts +7 -4
  14. package/src/cli-adapter-types.ts +2 -0
  15. package/src/cli-adapters/provider-cli-adapter.ts +148 -11
  16. package/src/cli-adapters/pty-transport.ts +100 -0
  17. package/src/cli-adapters/session-host-transport.ts +392 -0
  18. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +126 -0
  19. package/src/cli-adapters/terminal-backends/types.ts +17 -0
  20. package/src/cli-adapters/terminal-backends/xterm-backend.ts +87 -0
  21. package/src/cli-adapters/terminal-screen.ts +40 -53
  22. package/src/commands/cli-manager.ts +184 -55
  23. package/src/config/config.d.ts +116 -0
  24. package/src/config/workspace-activity.d.ts +22 -0
  25. package/src/config/workspaces.d.ts +84 -0
  26. package/src/daemon/dev-auto-implement.ts +1087 -0
  27. package/src/daemon/dev-cdp-handlers.ts +1003 -0
  28. package/src/daemon/dev-cli-debug.ts +288 -0
  29. package/src/daemon/dev-server-types.ts +45 -0
  30. package/src/daemon/dev-server.ts +121 -1698
  31. package/src/index.ts +5 -1
  32. package/src/providers/cli-provider-instance.ts +13 -1
  33. package/src/providers/contracts.d.ts +408 -0
  34. package/src/providers/contracts.ts +9 -0
  35. package/src/providers/extension-provider-instance.ts +50 -10
  36. package/src/providers/provider-instance-manager.ts +48 -10
  37. package/src/providers/provider-instance.d.ts +142 -0
  38. package/src/providers/provider-instance.ts +23 -1
  39. package/src/shared-types.d.ts +157 -0
  40. package/src/shared-types.ts +14 -0
  41. package/src/status/builders.ts +6 -0
  42. package/src/status/normalize.d.ts +14 -0
  43. package/src/types.d.ts +127 -0
@@ -1,95 +1,82 @@
1
1
  /**
2
- * PTY screen snapshot backed by xterm's parser.
2
+ * PTY screen snapshot abstraction.
3
3
  *
4
- * Claude Code and similar CLIs use a real terminal UI. A handwritten ANSI
5
- * parser quickly drifts from reality, so we reuse xterm's terminal model and
6
- * expose only the current visible viewport as plain text for provider scripts.
4
+ * We currently keep xterm as the default parser/model because it is already
5
+ * proven in production, but the surface is now backend-agnostic so we can
6
+ * swap in libghostty-vt once a native Node binding is available.
7
7
  */
8
8
 
9
- type XtermBufferLine = {
10
- translateToString(trimRight?: boolean): string;
11
- };
9
+ import { GhosttyVtTerminalBackend, isGhosttyVtBackendAvailable, resolveTerminalBackendPreference } from './terminal-backends/ghostty-vt-backend.js';
10
+ import type {
11
+ TerminalViewportBackend,
12
+ TerminalViewportBackendKind,
13
+ TerminalViewportBackendOptions,
14
+ TerminalViewportBackendPreference,
15
+ } from './terminal-backends/types.js';
16
+ import { XtermTerminalBackend } from './terminal-backends/xterm-backend.js';
12
17
 
13
- type XtermBuffer = {
14
- length: number;
15
- viewportY: number;
16
- getLine(index: number): XtermBufferLine | undefined;
17
- };
18
+ const DEFAULT_SCROLLBACK = 2000;
18
19
 
19
- type XtermTerminal = {
20
- buffer: { active: XtermBuffer };
21
- write(data: string, callback?: () => void): void;
22
- resize(cols: number, rows: number): void;
23
- dispose(): void;
24
- };
25
-
26
- let TerminalCtor: (new (options: { cols: number; rows: number; scrollback: number }) => XtermTerminal) | null = null;
20
+ function createTerminalBackend(
21
+ options: TerminalViewportBackendOptions,
22
+ preference: TerminalViewportBackendPreference,
23
+ ): TerminalViewportBackend {
24
+ if (preference === 'ghostty-vt') {
25
+ return new GhosttyVtTerminalBackend(options);
26
+ }
27
27
 
28
- function loadTerminalCtor(): new (options: { cols: number; rows: number; scrollback: number }) => XtermTerminal {
29
- if (!TerminalCtor) {
30
- // eslint-disable-next-line @typescript-eslint/no-var-requires
31
- const mod = require('@xterm/xterm');
32
- TerminalCtor = mod.Terminal || mod.default?.Terminal || mod.default;
33
- if (!TerminalCtor) {
34
- throw new Error('@xterm/xterm Terminal export not found');
35
- }
28
+ if (preference === 'auto' && isGhosttyVtBackendAvailable()) {
29
+ return new GhosttyVtTerminalBackend(options);
36
30
  }
37
- return TerminalCtor;
31
+
32
+ return new XtermTerminalBackend(options);
38
33
  }
39
34
 
40
35
  export class TerminalScreen {
36
+ readonly backendKind: TerminalViewportBackendKind;
41
37
  private rows: number;
42
38
  private cols: number;
43
- private terminal: XtermTerminal;
39
+ private readonly preference: TerminalViewportBackendPreference;
40
+ private terminal: TerminalViewportBackend;
44
41
 
45
42
  constructor(rows = 40, cols = 120) {
46
43
  this.rows = Math.max(1, rows | 0);
47
44
  this.cols = Math.max(1, cols | 0);
48
- this.terminal = this.createTerminal();
45
+ this.preference = resolveTerminalBackendPreference();
46
+ this.terminal = this.createBackend();
47
+ this.backendKind = this.terminal.kind;
49
48
  }
50
49
 
51
50
  reset(rows = this.rows, cols = this.cols): void {
52
51
  this.rows = Math.max(1, rows | 0);
53
52
  this.cols = Math.max(1, cols | 0);
54
53
  this.terminal.dispose();
55
- this.terminal = this.createTerminal();
54
+ this.terminal = this.createBackend();
56
55
  }
57
56
 
58
57
  resize(rows: number, cols: number): void {
59
58
  this.rows = Math.max(1, rows | 0);
60
59
  this.cols = Math.max(1, cols | 0);
61
- this.terminal.resize(this.cols, this.rows);
60
+ this.terminal.resize(this.rows, this.cols);
62
61
  }
63
62
 
64
63
  write(data: string): void {
65
- if (!data) return;
66
64
  this.terminal.write(data);
67
65
  }
68
66
 
69
67
  getText(): string {
70
- const buffer = this.terminal.buffer.active;
71
- const start = Math.max(0, buffer.viewportY || 0);
72
- const end = Math.max(start, Math.min(buffer.length || 0, start + this.rows));
73
- const lines: string[] = [];
74
-
75
- for (let i = start; i < end; i++) {
76
- const line = buffer.getLine(i);
77
- lines.push(line ? line.translateToString(true) : '');
78
- }
68
+ return this.terminal.getText();
69
+ }
79
70
 
80
- let first = 0;
81
- let last = lines.length;
82
- while (first < last && !lines[first]?.trim()) first++;
83
- while (last > first && !lines[last - 1]?.trim()) last--;
84
- return lines.slice(first, last).join('\n');
71
+ dispose(): void {
72
+ this.terminal.dispose();
85
73
  }
86
74
 
87
- private createTerminal(): XtermTerminal {
88
- const Terminal = loadTerminalCtor();
89
- return new Terminal({
75
+ private createBackend(): TerminalViewportBackend {
76
+ return createTerminalBackend({
90
77
  cols: this.cols,
91
78
  rows: this.rows,
92
- scrollback: 2000,
93
- });
79
+ scrollback: DEFAULT_SCROLLBACK,
80
+ }, this.preference);
94
81
  }
95
82
  }
@@ -19,6 +19,7 @@ import { AcpProviderInstance } from '../providers/acp-provider-instance.js';
19
19
  import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
20
20
  import { ProviderLoader } from '../providers/provider-loader.js';
21
21
  import type { CliAdapter } from '../cli-adapter-types.js';
22
+ import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
22
23
  import type { SessionRegistry } from '../sessions/registry.js';
23
24
  import { LOG } from '../logging/logger.js';
24
25
 
@@ -35,10 +36,32 @@ export interface CliManagerDeps {
35
36
  /** InstanceManager — register in CLI unified status */
36
37
  getInstanceManager(): ProviderInstanceManager | null;
37
38
  getSessionRegistry?(): SessionRegistry | null;
39
+ createPtyTransportFactory?: (params: CliTransportFactoryParams) => PtyTransportFactory | null;
40
+ listHostedCliRuntimes?: () => Promise<HostedCliRuntimeDescriptor[]>;
38
41
  }
39
42
 
40
43
  type CommandResult = { success: boolean;[key: string]: unknown };
41
44
 
45
+ export interface CliTransportFactoryParams {
46
+ runtimeId: string;
47
+ providerType: string;
48
+ workspace: string;
49
+ cliArgs?: string[];
50
+ attachExisting?: boolean;
51
+ }
52
+
53
+ export interface HostedCliRuntimeDescriptor {
54
+ runtimeId: string;
55
+ runtimeKey?: string;
56
+ displayName?: string;
57
+ workspaceLabel?: string;
58
+ lifecycle?: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';
59
+ recoveryState?: string | null;
60
+ cliType: string;
61
+ workspace: string;
62
+ cliArgs?: string[];
63
+ }
64
+
42
65
  // ─── DaemonCliManager ────────────────────────────
43
66
 
44
67
  export class DaemonCliManager {
@@ -77,7 +100,29 @@ export class DaemonCliManager {
77
100
  }
78
101
  }
79
102
 
80
- private createAdapter(cliType: string, workingDir: string, cliArgs?: string[]): CliAdapter {
103
+ private getTransportFactory(
104
+ runtimeId: string,
105
+ providerType: string,
106
+ workspace: string,
107
+ cliArgs?: string[],
108
+ attachExisting = false,
109
+ ): PtyTransportFactory | undefined {
110
+ return this.deps.createPtyTransportFactory?.({
111
+ runtimeId,
112
+ providerType,
113
+ workspace,
114
+ cliArgs,
115
+ attachExisting,
116
+ }) || undefined;
117
+ }
118
+
119
+ private createAdapter(
120
+ cliType: string,
121
+ workingDir: string,
122
+ cliArgs: string[] | undefined,
123
+ runtimeId: string,
124
+ attachExisting = false,
125
+ ): CliAdapter {
81
126
  // cliType normalize (Resolve alias)
82
127
  const normalizedType = this.providerLoader.resolveAlias(cliType);
83
128
 
@@ -86,12 +131,80 @@ export class DaemonCliManager {
86
131
  if (provider && provider.category === 'cli' && provider.patterns && provider.spawn) {
87
132
  console.log(chalk.cyan(` 📦 Using provider: ${provider.name} (${provider.type})`));
88
133
  const resolvedProvider = this.providerLoader.resolve(normalizedType) || provider;
89
- return new ProviderCliAdapter(resolvedProvider as any, workingDir, cliArgs);
134
+ const transportFactory = this.getTransportFactory(runtimeId, normalizedType, workingDir, cliArgs, attachExisting);
135
+ return new ProviderCliAdapter(resolvedProvider as any, workingDir, cliArgs, transportFactory);
90
136
  }
91
137
 
92
138
  throw new Error(`No CLI provider found for '${cliType}'. Create a provider.js in providers/cli/${cliType}/`);
93
139
  }
94
140
 
141
+ private startCliExitMonitor(key: string, cliType: string): void {
142
+ const sessionRegistry = this.deps.getSessionRegistry?.() || null;
143
+ const instanceManager = this.deps.getInstanceManager();
144
+ const checkStopped = setInterval(() => {
145
+ try {
146
+ const adapter = this.adapters.get(key);
147
+ if (!adapter) { clearInterval(checkStopped); return; }
148
+ const status = adapter.getStatus?.();
149
+ if (status?.status === 'stopped' || status?.status === 'error') {
150
+ clearInterval(checkStopped);
151
+ setTimeout(() => {
152
+ if (this.adapters.has(key)) {
153
+ this.adapters.delete(key);
154
+ this.deps.removeAgentTracking(key);
155
+ sessionRegistry?.unregisterByInstanceKey(key);
156
+ instanceManager?.removeInstance(key);
157
+ LOG.info('CLI', `🧹 Auto-cleaned ${status.status} CLI: ${cliType}`);
158
+ this.deps.onStatusChange();
159
+ }
160
+ }, 5000);
161
+ }
162
+ } catch { /* ignore */ }
163
+ }, 3000);
164
+ }
165
+
166
+ private async registerCliInstance(
167
+ key: string,
168
+ normalizedType: string,
169
+ cliType: string,
170
+ resolvedDir: string,
171
+ cliArgs: string[] | undefined,
172
+ provider: any,
173
+ settings: Record<string, any>,
174
+ attachExisting = false,
175
+ ): Promise<void> {
176
+ const instanceManager = this.deps.getInstanceManager();
177
+ const sessionRegistry = this.deps.getSessionRegistry?.() || null;
178
+ if (!instanceManager) throw new Error('InstanceManager not available');
179
+ const transportFactory = this.getTransportFactory(key, normalizedType, resolvedDir, cliArgs, attachExisting);
180
+ const cliInstance = new CliProviderInstance(provider, resolvedDir, cliArgs, key, transportFactory);
181
+ try {
182
+ await instanceManager.addInstance(key, cliInstance, {
183
+ serverConn: this.deps.getServerConn(),
184
+ settings,
185
+ onPtyData: (data: string) => {
186
+ this.deps.getP2p()?.broadcastPtyOutput(cliInstance.instanceId, data);
187
+ },
188
+ });
189
+ sessionRegistry?.register({
190
+ sessionId: cliInstance.instanceId,
191
+ parentSessionId: null,
192
+ providerType: normalizedType,
193
+ providerCategory: 'cli',
194
+ transport: 'pty',
195
+ adapterKey: key,
196
+ instanceKey: key,
197
+ });
198
+ } catch (spawnErr: any) {
199
+ LOG.error('CLI', `[${cliType}] Spawn failed: ${spawnErr?.message}`);
200
+ instanceManager.removeInstance(key);
201
+ throw new Error(`Failed to start ${provider.displayName || provider.name || cliType}: ${spawnErr?.message}`);
202
+ }
203
+
204
+ this.adapters.set(key, cliInstance.getAdapter() as any);
205
+ this.startCliExitMonitor(key, cliType);
206
+ }
207
+
95
208
  // ─── Session start/management ──────────────────────────────
96
209
 
97
210
  async startSession(cliType: string, workingDir: string, cliArgs?: string[], initialModel?: string): Promise<void> {
@@ -197,59 +310,20 @@ export class DaemonCliManager {
197
310
  const instanceManager = this.deps.getInstanceManager();
198
311
  if (provider && instanceManager) {
199
312
  const resolvedProvider = this.providerLoader.resolve(cliType, { version: cliInfo.version }) || provider;
200
- const cliInstance = new CliProviderInstance(resolvedProvider, resolvedDir, cliArgs, key);
201
- try {
202
- await instanceManager.addInstance(key, cliInstance, {
203
- serverConn: this.deps.getServerConn(),
204
- settings: {},
205
- onPtyData: (data: string) => {
206
- this.deps.getP2p()?.broadcastPtyOutput(cliInstance.instanceId, data);
207
- },
208
- });
209
- sessionRegistry?.register({
210
- sessionId: cliInstance.instanceId,
211
- parentSessionId: null,
212
- providerType: normalizedType,
213
- providerCategory: 'cli',
214
- transport: 'pty',
215
- adapterKey: key,
216
- instanceKey: key,
217
- });
218
- } catch (spawnErr: any) {
219
- // Spawn failed — cleanup and propagate error
220
- LOG.error('CLI', `[${cliType}] Spawn failed: ${spawnErr?.message}`);
221
- instanceManager.removeInstance(key);
222
- throw new Error(`Failed to start ${cliInfo.displayName}: ${spawnErr?.message}`);
223
- }
224
-
225
- // Keep adapter ref too (backward compat — write, resize etc)
226
- this.adapters.set(key, cliInstance.getAdapter() as any);
313
+ await this.registerCliInstance(
314
+ key,
315
+ normalizedType,
316
+ cliType,
317
+ resolvedDir,
318
+ cliArgs,
319
+ resolvedProvider,
320
+ {},
321
+ false,
322
+ );
227
323
  console.log(chalk.green(` ✓ CLI started: ${cliInfo.displayName} v${cliInfo.version || 'unknown'} in ${resolvedDir}`));
228
-
229
- // Monitor for stopped/error → auto-cleanup
230
- const checkStopped = setInterval(() => {
231
- try {
232
- const adapter = this.adapters.get(key);
233
- if (!adapter) { clearInterval(checkStopped); return; }
234
- const status = adapter.getStatus?.();
235
- if (status?.status === 'stopped' || status?.status === 'error') {
236
- clearInterval(checkStopped);
237
- setTimeout(() => {
238
- if (this.adapters.has(key)) {
239
- this.adapters.delete(key);
240
- this.deps.removeAgentTracking(key);
241
- sessionRegistry?.unregisterByInstanceKey(key);
242
- instanceManager.removeInstance(key);
243
- LOG.info('CLI', `🧹 Auto-cleaned ${status.status} CLI: ${cliType}`);
244
- this.deps.onStatusChange();
245
- }
246
- }, 5000);
247
- }
248
- } catch { /* ignore */ }
249
- }, 3000);
250
324
  } else {
251
325
  // Fallback: InstanceManager without directly adapter manage
252
- const adapter = this.createAdapter(cliType, resolvedDir, cliArgs);
326
+ const adapter = this.createAdapter(cliType, resolvedDir, cliArgs, key, false);
253
327
  try {
254
328
  await adapter.spawn();
255
329
  } catch (spawnErr: any) {
@@ -292,10 +366,18 @@ export class DaemonCliManager {
292
366
  }
293
367
 
294
368
  async stopSession(key: string): Promise<void> {
369
+ return this.stopSessionWithMode(key, 'hard');
370
+ }
371
+
372
+ async stopSessionWithMode(key: string, mode: 'hard' | 'save'): Promise<void> {
295
373
  const adapter = this.adapters.get(key);
296
374
  if (adapter) {
297
375
  try {
298
- adapter.shutdown();
376
+ if (mode === 'save' && typeof adapter.saveAndStop === 'function') {
377
+ await adapter.saveAndStop();
378
+ } else {
379
+ adapter.shutdown();
380
+ }
299
381
  } catch (e: any) {
300
382
  LOG.warn('CLI', `Shutdown error for ${adapter.cliType}: ${e?.message} (force-cleaning)`);
301
383
  }
@@ -324,6 +406,52 @@ export class DaemonCliManager {
324
406
  this.adapters.clear();
325
407
  }
326
408
 
409
+ detachAll(): void {
410
+ for (const adapter of this.adapters.values()) {
411
+ if (typeof adapter.detach === 'function') adapter.detach();
412
+ else adapter.shutdown();
413
+ }
414
+ this.adapters.clear();
415
+ }
416
+
417
+ async restoreHostedSessions(records?: HostedCliRuntimeDescriptor[]): Promise<number> {
418
+ const instanceManager = this.deps.getInstanceManager();
419
+ if (!instanceManager) return 0;
420
+ const sessions = records || await this.deps.listHostedCliRuntimes?.() || [];
421
+ let restored = 0;
422
+
423
+ for (const record of sessions) {
424
+ if (!record?.runtimeId || !record?.cliType || !record?.workspace) continue;
425
+ if (this.adapters.has(record.runtimeId) || instanceManager.getInstance(record.runtimeId)) continue;
426
+ const normalizedType = this.providerLoader.resolveAlias(record.cliType);
427
+ const providerMeta = this.providerLoader.getMeta(normalizedType);
428
+ if (!providerMeta || providerMeta.category !== 'cli') continue;
429
+
430
+ const resolvedProvider = this.providerLoader.resolve(normalizedType) || providerMeta;
431
+ try {
432
+ await this.registerCliInstance(
433
+ record.runtimeId,
434
+ normalizedType,
435
+ record.cliType,
436
+ record.workspace,
437
+ record.cliArgs,
438
+ resolvedProvider,
439
+ {},
440
+ true,
441
+ );
442
+ restored += 1;
443
+ LOG.info('CLI', `♻ Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
444
+ } catch (error: any) {
445
+ LOG.warn('CLI', `Failed to restore hosted runtime ${record.runtimeId}: ${error?.message || error}`);
446
+ }
447
+ }
448
+
449
+ if (restored > 0) {
450
+ this.deps.onStatusChange();
451
+ }
452
+ return restored;
453
+ }
454
+
327
455
  // ─── Adapter search ─────────────────────────────
328
456
 
329
457
  /**
@@ -406,15 +534,16 @@ export class DaemonCliManager {
406
534
  case 'stop_cli': {
407
535
  const cliType = args?.cliType;
408
536
  const dir = args?.dir || '';
537
+ const mode = args?.mode === 'save' ? 'save' : 'hard';
409
538
  if (!cliType) throw new Error('cliType required');
410
539
  // UUID session target based search priority
411
540
  const found = this.findAdapter(cliType, { instanceKey: args?.targetSessionId, dir });
412
541
  if (found) {
413
- await this.stopSession(found.key);
542
+ await this.stopSessionWithMode(found.key, mode);
414
543
  } else {
415
544
  console.log(chalk.yellow(` ⚠ No adapter found for ${cliType}`));
416
545
  }
417
- return { success: true, cliType, dir, stopped: true };
546
+ return { success: true, cliType, dir, stopped: true, mode };
418
547
  }
419
548
  case 'restart_session': {
420
549
  const cliType = args?.cliType || args?.agentType || args?.ideType;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * ADHDev Launcher — Configuration
3
+ *
4
+ * Manages launcher config, server connection tokens, and user preferences.
5
+ */
6
+ import type { WorkspaceEntry } from './workspaces.js';
7
+ import type { WorkspaceActivityEntry } from './workspace-activity.js';
8
+ export type { WorkspaceEntry } from './workspaces.js';
9
+ export type { WorkspaceActivityEntry } from './workspace-activity.js';
10
+ export interface ADHDevConfig {
11
+ serverUrl: string;
12
+ apiToken: string | null;
13
+ connectionToken: string | null;
14
+ selectedIde: string | null;
15
+ configuredIdes: string[];
16
+ installedExtensions: string[];
17
+ autoConnect: boolean;
18
+ /**
19
+ * @deprecated Not read at runtime. Notification preferences are now managed by:
20
+ * - Web UI layer: useNotificationPrefs (localStorage)
21
+ * - Daemon layer: per-provider settings (approvalAlert, longGeneratingAlert)
22
+ * Kept for backward config compat — will be removed in v0.7+.
23
+ */
24
+ notifications: boolean;
25
+ userEmail: string | null;
26
+ userName: string | null;
27
+ setupCompleted: boolean;
28
+ setupDate: string | null;
29
+ configuredCLIs: string[];
30
+ enabledIdes: string[];
31
+ recentCliWorkspaces: string[];
32
+ /** Saved workspaces for IDE/CLI/ACP launch (daemon-local) */
33
+ workspaces?: WorkspaceEntry[];
34
+ /** Default workspace id (from workspaces[]) — never used implicitly for launch */
35
+ defaultWorkspaceId?: string | null;
36
+ /** Recently used workspaces (IDE / CLI / ACP / default) for quick resume */
37
+ recentWorkspaceActivity?: WorkspaceActivityEntry[];
38
+ machineNickname: string | null;
39
+ /**
40
+ * Stable local machine ID (prefix: `mach_`) — generated locally on first run.
41
+ * Used as daemon instance key (`daemon_<machineId>`) and in status reports.
42
+ * NOT the same as the server-side D1 `machines.id` — see `registeredMachineId`.
43
+ */
44
+ machineId?: string;
45
+ machineSecret?: string | null;
46
+ /**
47
+ * Server-side D1 `machines.id` — the row ID assigned when daemon registers via
48
+ * `POST /cli/complete`. Corresponds to `machineId` in server DO context
49
+ * (`DaemonConnection.machineId`, `StatusContext.machineId`).
50
+ *
51
+ * Naming differs from server-side `machineId` to avoid confusion with the local
52
+ * `config.machineId` (mach_ prefix) which is a different value.
53
+ *
54
+ * @deprecated Legacy bridge field — will be removed after 2026-04-06.
55
+ * Modern auth flow uses `machineSecret` (adm_) to identify machines.
56
+ */
57
+ registeredMachineId?: string;
58
+ cliHistory: CliHistoryEntry[];
59
+ providerSettings: Record<string, Record<string, any>>;
60
+ ideSettings: Record<string, {
61
+ extensions?: Record<string, {
62
+ enabled: boolean;
63
+ }>;
64
+ }>;
65
+ disableUpstream?: boolean;
66
+ providerDir?: string;
67
+ }
68
+ export interface CliHistoryEntry {
69
+ category?: 'ide' | 'cli' | 'acp';
70
+ cliType: string;
71
+ dir: string;
72
+ cliArgs?: string[];
73
+ workspace?: string;
74
+ newWindow?: boolean;
75
+ model?: string;
76
+ timestamp: number;
77
+ label?: string;
78
+ }
79
+ export declare function generateMachineId(): string;
80
+ export declare function isStableMachineId(machineId?: string | null): boolean;
81
+ /**
82
+ * Get the config directory path
83
+ */
84
+ export declare function getConfigDir(): string;
85
+ /**
86
+ * Load configuration from disk
87
+ */
88
+ export declare function loadConfig(): ADHDevConfig;
89
+ /**
90
+ * Save configuration to disk
91
+ */
92
+ export declare function saveConfig(config: ADHDevConfig): void;
93
+ /**
94
+ * Update specific config fields
95
+ */
96
+ export declare function updateConfig(updates: Partial<ADHDevConfig>): ADHDevConfig;
97
+ /**
98
+ * Mark setup as completed
99
+ */
100
+ export declare function markSetupComplete(ideId: string | string[], extensions: string[]): ADHDevConfig;
101
+ /**
102
+ * Check if setup has been completed before
103
+ */
104
+ export declare function isSetupComplete(): boolean;
105
+ /**
106
+ * Reset configuration
107
+ */
108
+ export declare function resetConfig(): void;
109
+ /**
110
+ * Generate a connection token for server authentication
111
+ */
112
+ export declare function generateConnectionToken(): string;
113
+ /**
114
+ * Add launch to history (max 20, dedup by category+type+dir+args+workspace+model)
115
+ */
116
+ export declare function addCliHistory(entry: Omit<CliHistoryEntry, 'timestamp'>): void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Recent workspace activity — quick "pick up where you left off" (daemon-local).
3
+ */
4
+ import type { ADHDevConfig } from './config.js';
5
+ export interface WorkspaceActivityEntry {
6
+ path: string;
7
+ lastUsedAt: number;
8
+ /** `active` legacy — same meaning as default */
9
+ kind?: 'ide' | 'cli' | 'acp' | 'default' | 'active';
10
+ /** IDE id or CLI/ACP provider type */
11
+ agentType?: string;
12
+ }
13
+ export declare function normWorkspacePath(p: string): string;
14
+ /**
15
+ * Append or bump a path to the front of recent activity (returns new config object).
16
+ */
17
+ export declare function appendWorkspaceActivity(config: ADHDevConfig, rawPath: string, meta?: {
18
+ kind?: WorkspaceActivityEntry['kind'];
19
+ agentType?: string;
20
+ }): ADHDevConfig;
21
+ export declare function getWorkspaceActivity(config: ADHDevConfig, limit?: number): WorkspaceActivityEntry[];
22
+ export declare function removeActivityForPath(config: ADHDevConfig, rawPath: string): ADHDevConfig;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Saved workspaces — shared by IDE launch, CLI, ACP (daemon-local).
3
+ */
4
+ import type { ADHDevConfig } from './config.js';
5
+ export interface WorkspaceEntry {
6
+ id: string;
7
+ path: string;
8
+ label?: string;
9
+ addedAt: number;
10
+ }
11
+ export declare function expandPath(p: string): string;
12
+ export declare function validateWorkspacePath(absPath: string): {
13
+ ok: true;
14
+ } | {
15
+ ok: false;
16
+ error: string;
17
+ };
18
+ /** Default workspace label from path */
19
+ export declare function defaultWorkspaceLabel(absPath: string): string;
20
+ /**
21
+ * Ensure config.workspaces exists; seed from recentCliWorkspaces once (same paths).
22
+ */
23
+ export declare function migrateWorkspacesFromRecent(config: ADHDevConfig): ADHDevConfig;
24
+ export declare function getDefaultWorkspacePath(config: ADHDevConfig): string | null;
25
+ export declare function getWorkspaceState(config: ADHDevConfig): {
26
+ workspaces: WorkspaceEntry[];
27
+ defaultWorkspaceId: string | null;
28
+ defaultWorkspacePath: string | null;
29
+ };
30
+ export type LaunchDirectorySource = 'dir' | 'workspaceId' | 'defaultWorkspace' | 'home';
31
+ export type ResolveLaunchDirectoryResult = {
32
+ ok: true;
33
+ path: string;
34
+ source: LaunchDirectorySource;
35
+ } | {
36
+ ok: false;
37
+ code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED';
38
+ message: string;
39
+ };
40
+ /**
41
+ * Resolve cwd for CLI/ACP. No implicit default workspace or home — caller must pass
42
+ * useDefaultWorkspace or useHome (or an explicit dir / workspaceId).
43
+ */
44
+ export declare function resolveLaunchDirectory(args: {
45
+ dir?: string;
46
+ workspaceId?: string;
47
+ useDefaultWorkspace?: boolean;
48
+ useHome?: boolean;
49
+ } | undefined, config: ADHDevConfig): ResolveLaunchDirectoryResult;
50
+ /**
51
+ * IDE folder from explicit args only (`workspace`, `workspaceId`, or `useDefaultWorkspace: true`).
52
+ */
53
+ export declare function resolveIdeWorkspaceFromArgs(args: {
54
+ workspace?: string;
55
+ workspaceId?: string;
56
+ useDefaultWorkspace?: boolean;
57
+ } | undefined, config: ADHDevConfig): string | undefined;
58
+ /**
59
+ * IDE launch folder — same saved workspaces + default as CLI/ACP.
60
+ * After explicit `workspace` / `workspaceId` / `useDefaultWorkspace: true`, falls back to
61
+ * config default workspace when set. Pass `useDefaultWorkspace: false` to open IDE without that folder.
62
+ */
63
+ export declare function resolveIdeLaunchWorkspace(args: {
64
+ workspace?: string;
65
+ workspaceId?: string;
66
+ useDefaultWorkspace?: boolean;
67
+ } | undefined, config: ADHDevConfig): string | undefined;
68
+ export declare function findWorkspaceByPath(config: ADHDevConfig, rawPath: string): WorkspaceEntry | undefined;
69
+ export declare function addWorkspaceEntry(config: ADHDevConfig, rawPath: string, label?: string): {
70
+ config: ADHDevConfig;
71
+ entry: WorkspaceEntry;
72
+ } | {
73
+ error: string;
74
+ };
75
+ export declare function removeWorkspaceEntry(config: ADHDevConfig, id: string): {
76
+ config: ADHDevConfig;
77
+ } | {
78
+ error: string;
79
+ };
80
+ export declare function setDefaultWorkspaceId(config: ADHDevConfig, id: string | null): {
81
+ config: ADHDevConfig;
82
+ } | {
83
+ error: string;
84
+ };