@adhdev/daemon-core 0.7.6 → 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.
- package/dist/index.d.mts +158 -38
- package/dist/index.d.ts +158 -38
- package/dist/index.js +3965 -2521
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3455 -2011
- package/dist/index.mjs.map +1 -1
- package/dist/{normalize-tKg8IiDk.d.mts → normalize-auJAPmKy.d.mts} +669 -629
- package/dist/{normalize-tKg8IiDk.d.ts → normalize-auJAPmKy.d.ts} +669 -629
- package/dist/status/normalize.d.mts +1 -1
- package/dist/status/normalize.d.ts +1 -1
- package/package.json +5 -1
- package/src/boot/daemon-lifecycle.ts +7 -4
- package/src/cli-adapter-types.ts +2 -0
- package/src/cli-adapters/provider-cli-adapter.ts +148 -11
- package/src/cli-adapters/pty-transport.ts +100 -0
- package/src/cli-adapters/session-host-transport.ts +392 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +126 -0
- package/src/cli-adapters/terminal-backends/types.ts +17 -0
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +87 -0
- package/src/cli-adapters/terminal-screen.ts +40 -53
- package/src/commands/cli-manager.ts +184 -55
- package/src/config/config.d.ts +116 -0
- package/src/config/workspace-activity.d.ts +22 -0
- package/src/config/workspaces.d.ts +84 -0
- package/src/daemon/dev-auto-implement.ts +1087 -0
- package/src/daemon/dev-cdp-handlers.ts +1003 -0
- package/src/daemon/dev-cli-debug.ts +288 -0
- package/src/daemon/dev-server-types.ts +45 -0
- package/src/daemon/dev-server.ts +121 -1698
- package/src/index.ts +5 -1
- package/src/providers/cli-provider-instance.ts +13 -1
- package/src/providers/contracts.d.ts +408 -0
- package/src/providers/contracts.ts +9 -0
- package/src/providers/provider-instance-manager.ts +21 -0
- package/src/providers/provider-instance.d.ts +142 -0
- package/src/providers/provider-instance.ts +23 -1
- package/src/shared-types.d.ts +157 -0
- package/src/shared-types.ts +14 -0
- package/src/status/builders.ts +6 -0
- package/src/status/normalize.d.ts +14 -0
- package/src/types.d.ts +127 -0
|
@@ -1,95 +1,82 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PTY screen snapshot
|
|
2
|
+
* PTY screen snapshot abstraction.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
14
|
-
length: number;
|
|
15
|
-
viewportY: number;
|
|
16
|
-
getLine(index: number): XtermBufferLine | undefined;
|
|
17
|
-
};
|
|
18
|
+
const DEFAULT_SCROLLBACK = 2000;
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
return new Terminal({
|
|
75
|
+
private createBackend(): TerminalViewportBackend {
|
|
76
|
+
return createTerminalBackend({
|
|
90
77
|
cols: this.cols,
|
|
91
78
|
rows: this.rows,
|
|
92
|
-
scrollback:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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.
|
|
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.
|
|
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
|
+
};
|