@adhdev/daemon-core 0.7.41 → 0.7.42
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/cli-adapters/provider-cli-adapter.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +4 -2
- package/dist/config/config.d.ts +2 -22
- package/dist/index.js +314 -88
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -88
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +4 -10
- package/dist/providers/contracts.d.ts +0 -79
- package/dist/providers/extension-provider-instance.d.ts +1 -0
- package/dist/providers/provider-instance.d.ts +0 -2
- package/dist/shared-types.d.ts +1 -3
- package/dist/status/normalize.js +67 -1
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +67 -1
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +1 -0
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/agent-stream/forward.ts +21 -1
- package/src/agent-stream/poller.ts +6 -1
- package/src/cli-adapters/provider-cli-adapter.ts +37 -2
- package/src/commands/cli-manager.ts +44 -39
- package/src/commands/router.ts +1 -0
- package/src/commands/stream-commands.ts +14 -0
- package/src/config/config.d.ts +5 -50
- package/src/config/config.ts +71 -49
- package/src/config/workspaces.d.ts +1 -4
- package/src/providers/cli-provider-instance.ts +18 -32
- package/src/providers/contracts.ts +0 -81
- package/src/providers/extension-provider-instance.ts +27 -0
- package/src/providers/ide-provider-instance.ts +12 -0
- package/src/providers/provider-instance.ts +0 -2
- package/src/shared-types.ts +1 -3
- package/src/status/builders.ts +7 -2
- package/src/status/normalize.ts +85 -0
- package/src/status/reporter.ts +31 -2
|
@@ -60,7 +60,6 @@ export interface HostedCliRuntimeDescriptor {
|
|
|
60
60
|
cliType: string;
|
|
61
61
|
workspace: string;
|
|
62
62
|
cliArgs?: string[];
|
|
63
|
-
launchMode?: string;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
const chalkApi: any = (chalk as any)?.yellow
|
|
@@ -91,6 +90,19 @@ export class DaemonCliManager {
|
|
|
91
90
|
return `${cliType}_${hash}`;
|
|
92
91
|
}
|
|
93
92
|
|
|
93
|
+
getSessionPresentationMode(sessionId: string): 'terminal' | 'chat' | null {
|
|
94
|
+
if (!sessionId) return null;
|
|
95
|
+
const instance = this.deps.getInstanceManager()?.getInstance(sessionId) as any;
|
|
96
|
+
const mode = instance?.category === 'cli'
|
|
97
|
+
? instance.getPresentationMode?.()
|
|
98
|
+
: null;
|
|
99
|
+
return mode === 'chat' || mode === 'terminal' ? mode : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
isTerminalSession(sessionId: string): boolean {
|
|
103
|
+
return this.getSessionPresentationMode(sessionId) === 'terminal';
|
|
104
|
+
}
|
|
105
|
+
|
|
94
106
|
private persistRecentActivity(entry: {
|
|
95
107
|
kind: 'ide' | 'cli' | 'acp';
|
|
96
108
|
providerType: string;
|
|
@@ -179,13 +191,12 @@ export class DaemonCliManager {
|
|
|
179
191
|
provider: any,
|
|
180
192
|
settings: Record<string, any>,
|
|
181
193
|
attachExisting = false,
|
|
182
|
-
launchModeId?: string,
|
|
183
194
|
): Promise<void> {
|
|
184
195
|
const instanceManager = this.deps.getInstanceManager();
|
|
185
196
|
const sessionRegistry = this.deps.getSessionRegistry?.() || null;
|
|
186
197
|
if (!instanceManager) throw new Error('InstanceManager not available');
|
|
187
198
|
const transportFactory = this.getTransportFactory(key, normalizedType, resolvedDir, cliArgs, attachExisting);
|
|
188
|
-
const cliInstance = new CliProviderInstance(provider, resolvedDir, cliArgs, key, transportFactory
|
|
199
|
+
const cliInstance = new CliProviderInstance(provider, resolvedDir, cliArgs, key, transportFactory);
|
|
189
200
|
try {
|
|
190
201
|
await instanceManager.addInstance(key, cliInstance, {
|
|
191
202
|
serverConn: this.deps.getServerConn(),
|
|
@@ -214,7 +225,7 @@ export class DaemonCliManager {
|
|
|
214
225
|
|
|
215
226
|
// ─── Session start/management ──────────────────────────────
|
|
216
227
|
|
|
217
|
-
async startSession(cliType: string, workingDir: string, cliArgs?: string[], initialModel?: string
|
|
228
|
+
async startSession(cliType: string, workingDir: string, cliArgs?: string[], initialModel?: string): Promise<void> {
|
|
218
229
|
const trimmed = (workingDir || '').trim();
|
|
219
230
|
if (!trimmed) throw new Error('working directory required');
|
|
220
231
|
const resolvedDir = trimmed.startsWith('~')
|
|
@@ -321,38 +332,7 @@ export class DaemonCliManager {
|
|
|
321
332
|
}
|
|
322
333
|
|
|
323
334
|
// ─── Resolve launch options → extra args ───
|
|
324
|
-
|
|
325
|
-
let resolvedLaunchMode = launchMode;
|
|
326
|
-
|
|
327
|
-
const activeMode = provider?.launchModes?.length
|
|
328
|
-
? (launchMode
|
|
329
|
-
? provider.launchModes.find((m: any) => m.id === launchMode)
|
|
330
|
-
: provider.launchModes.find((m: any) => m.default))
|
|
331
|
-
: undefined;
|
|
332
|
-
|
|
333
|
-
if (activeMode) {
|
|
334
|
-
resolvedLaunchMode = activeMode.id;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (provider?.launchArgBuilder) {
|
|
338
|
-
// Build option values: schema defaults → mode preset → user args?.launchOptionValues
|
|
339
|
-
const defaults: Record<string, string | boolean | number> = {};
|
|
340
|
-
for (const opt of (provider.launchOptions || [])) {
|
|
341
|
-
if (opt.default !== undefined) defaults[opt.id] = opt.default;
|
|
342
|
-
}
|
|
343
|
-
const modeOptions: Record<string, string | boolean | number> = activeMode?.options || {};
|
|
344
|
-
const userOptions: Record<string, string | boolean | number> = launchOptionValues || {};
|
|
345
|
-
const merged = { ...defaults, ...modeOptions, ...userOptions };
|
|
346
|
-
const extraArgs = provider.launchArgBuilder(merged);
|
|
347
|
-
if (extraArgs.length) {
|
|
348
|
-
resolvedCliArgs = [...(cliArgs || []), ...extraArgs];
|
|
349
|
-
console.log(colorize('cyan', ` 🚀 Launch options applied: ${extraArgs.join(' ')}`));
|
|
350
|
-
}
|
|
351
|
-
} else if (activeMode?.extraArgs?.length) {
|
|
352
|
-
// Fallback: simple extraArgs from mode (no launchArgBuilder)
|
|
353
|
-
resolvedCliArgs = [...(cliArgs || []), ...activeMode.extraArgs];
|
|
354
|
-
console.log(colorize('cyan', ` 🚀 Launch mode '${activeMode.name}': appending args ${activeMode.extraArgs.join(' ')}`));
|
|
355
|
-
}
|
|
335
|
+
const resolvedCliArgs = cliArgs;
|
|
356
336
|
|
|
357
337
|
// If InstanceManager exists, manage as CliProviderInstance unified
|
|
358
338
|
const instanceManager = this.deps.getInstanceManager();
|
|
@@ -367,7 +347,6 @@ export class DaemonCliManager {
|
|
|
367
347
|
resolvedProvider,
|
|
368
348
|
{},
|
|
369
349
|
false,
|
|
370
|
-
resolvedLaunchMode,
|
|
371
350
|
);
|
|
372
351
|
console.log(colorize('green', ` ✓ CLI started: ${cliInfo.displayName} v${cliInfo.version || 'unknown'} in ${resolvedDir}`));
|
|
373
352
|
} else {
|
|
@@ -495,7 +474,6 @@ export class DaemonCliManager {
|
|
|
495
474
|
resolvedProvider,
|
|
496
475
|
{},
|
|
497
476
|
true,
|
|
498
|
-
record.launchMode,
|
|
499
477
|
);
|
|
500
478
|
restored += 1;
|
|
501
479
|
LOG.info('CLI', `♻ Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
|
|
@@ -545,6 +523,15 @@ export class DaemonCliManager {
|
|
|
545
523
|
return null;
|
|
546
524
|
}
|
|
547
525
|
|
|
526
|
+
private findAdapterBySessionId(instanceKey?: string): { adapter: CliAdapter; key: string } | null {
|
|
527
|
+
if (!instanceKey) return null;
|
|
528
|
+
let ik = instanceKey;
|
|
529
|
+
const colonIdx = ik.lastIndexOf(':');
|
|
530
|
+
if (colonIdx >= 0) ik = ik.substring(colonIdx + 1);
|
|
531
|
+
const adapter = this.adapters.get(ik);
|
|
532
|
+
return adapter ? { adapter, key: ik } : null;
|
|
533
|
+
}
|
|
534
|
+
|
|
548
535
|
// ─── CLI command handling ────────────────────────────
|
|
549
536
|
|
|
550
537
|
async handleCliCommand(cmd: string, args: any): Promise<CommandResult | null> {
|
|
@@ -575,7 +562,7 @@ export class DaemonCliManager {
|
|
|
575
562
|
const launchSource = resolved.source;
|
|
576
563
|
if (!cliType) throw new Error('cliType required');
|
|
577
564
|
|
|
578
|
-
await this.startSession(cliType, dir, args?.cliArgs, args?.initialModel
|
|
565
|
+
await this.startSession(cliType, dir, args?.cliArgs, args?.initialModel);
|
|
579
566
|
|
|
580
567
|
// On startSession success, new UUID key exists in adapters (last added item)
|
|
581
568
|
let newKey: string | null = null;
|
|
@@ -601,6 +588,24 @@ export class DaemonCliManager {
|
|
|
601
588
|
}
|
|
602
589
|
return { success: true, cliType, dir, stopped: true, mode };
|
|
603
590
|
}
|
|
591
|
+
case 'set_cli_view_mode': {
|
|
592
|
+
const mode = args?.mode === 'chat' ? 'chat' : 'terminal';
|
|
593
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId : '';
|
|
594
|
+
const cliType = args?.cliType || args?.agentType || '';
|
|
595
|
+
const dir = args?.dir || '';
|
|
596
|
+
const found = this.findAdapterBySessionId(targetSessionId)
|
|
597
|
+
|| (cliType ? this.findAdapter(cliType, { instanceKey: targetSessionId, dir }) : null);
|
|
598
|
+
if (!found) {
|
|
599
|
+
return { success: false, error: 'CLI session not found', code: 'CLI_SESSION_NOT_FOUND' };
|
|
600
|
+
}
|
|
601
|
+
const instance = this.deps.getInstanceManager()?.getInstance(found.key);
|
|
602
|
+
if (!(instance instanceof CliProviderInstance)) {
|
|
603
|
+
return { success: false, error: 'CLI instance not found', code: 'CLI_INSTANCE_NOT_FOUND' };
|
|
604
|
+
}
|
|
605
|
+
instance.setPresentationMode(mode);
|
|
606
|
+
this.deps.onStatusChange();
|
|
607
|
+
return { success: true, id: found.key, mode };
|
|
608
|
+
}
|
|
604
609
|
case 'restart_session': {
|
|
605
610
|
const cliType = args?.cliType || args?.agentType || args?.ideType;
|
|
606
611
|
const cfg = loadConfig();
|
package/src/commands/router.ts
CHANGED
|
@@ -7,6 +7,14 @@ import type { CommandResult, CommandHelpers } from './handler.js';
|
|
|
7
7
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
8
8
|
import { LOG } from '../logging/logger.js';
|
|
9
9
|
|
|
10
|
+
function getCliPresentationMode(h: CommandHelpers, targetSessionId?: string): 'terminal' | 'chat' | null {
|
|
11
|
+
if (!targetSessionId) return null;
|
|
12
|
+
const instance = h.ctx.instanceManager?.getInstance(targetSessionId) as any;
|
|
13
|
+
if (instance?.category !== 'cli') return null;
|
|
14
|
+
const mode = instance.getPresentationMode?.();
|
|
15
|
+
return mode === 'chat' || mode === 'terminal' ? mode : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
export async function handleFocusSession(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
11
19
|
if (!h.agentStream || !h.getCdp()) return { success: false, error: 'AgentStream or CDP not available' };
|
|
12
20
|
const sessionId = args?.targetSessionId || h.currentSession?.sessionId;
|
|
@@ -20,6 +28,9 @@ export async function handleFocusSession(h: CommandHelpers, args: any): Promise<
|
|
|
20
28
|
export function handlePtyInput(h: CommandHelpers, args: any): CommandResult {
|
|
21
29
|
const { cliType, data, targetSessionId } = args || {};
|
|
22
30
|
if (!data) return { success: false, error: 'data required' };
|
|
31
|
+
if (getCliPresentationMode(h, targetSessionId) === 'chat') {
|
|
32
|
+
return { success: false, error: 'CLI session is in chat mode', code: 'CLI_VIEW_MODE_NOT_TERMINAL' };
|
|
33
|
+
}
|
|
23
34
|
const adapter = h.getCliAdapter(targetSessionId || cliType);
|
|
24
35
|
if (!adapter || typeof adapter.writeRaw !== 'function') {
|
|
25
36
|
return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || 'unknown'}` };
|
|
@@ -31,6 +42,9 @@ export function handlePtyInput(h: CommandHelpers, args: any): CommandResult {
|
|
|
31
42
|
export function handlePtyResize(h: CommandHelpers, args: any): CommandResult {
|
|
32
43
|
const { cliType, cols, rows, force, targetSessionId } = args || {};
|
|
33
44
|
if (!cols || !rows) return { success: false, error: 'cols and rows required' };
|
|
45
|
+
if (getCliPresentationMode(h, targetSessionId) === 'chat') {
|
|
46
|
+
return { success: false, error: 'CLI session is in chat mode', code: 'CLI_VIEW_MODE_NOT_TERMINAL' };
|
|
47
|
+
}
|
|
34
48
|
const adapter = h.getCliAdapter(targetSessionId || cliType);
|
|
35
49
|
if (!adapter || typeof adapter.resize !== 'function') {
|
|
36
50
|
return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || 'unknown'}` };
|
package/src/config/config.d.ts
CHANGED
|
@@ -1,65 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ADHDev Launcher — Configuration
|
|
3
3
|
*
|
|
4
|
-
* Manages launcher config,
|
|
4
|
+
* Manages launcher config, machine auth, and user preferences.
|
|
5
5
|
*/
|
|
6
6
|
import type { WorkspaceEntry } from './workspaces.js';
|
|
7
|
-
import type { WorkspaceActivityEntry } from './workspace-activity.js';
|
|
8
7
|
import type { RecentActivityEntry } from './recent-activity.js';
|
|
9
8
|
export type { WorkspaceEntry } from './workspaces.js';
|
|
10
|
-
export type { WorkspaceActivityEntry } from './workspace-activity.js';
|
|
11
9
|
export type { RecentActivityEntry } from './recent-activity.js';
|
|
12
10
|
export interface ADHDevConfig {
|
|
13
11
|
serverUrl: string;
|
|
14
|
-
apiToken: string | null;
|
|
15
|
-
connectionToken: string | null;
|
|
16
12
|
selectedIde: string | null;
|
|
17
13
|
configuredIdes: string[];
|
|
18
14
|
installedExtensions: string[];
|
|
19
|
-
autoConnect: boolean;
|
|
20
|
-
/**
|
|
21
|
-
* @deprecated Not read at runtime. Notification preferences are now managed by:
|
|
22
|
-
* - Web UI layer: useNotificationPrefs (localStorage)
|
|
23
|
-
* - Daemon layer: per-provider settings (approvalAlert, longGeneratingAlert)
|
|
24
|
-
* Kept for backward config compat — will be removed in v0.7+.
|
|
25
|
-
*/
|
|
26
|
-
notifications: boolean;
|
|
27
15
|
userEmail: string | null;
|
|
28
16
|
userName: string | null;
|
|
29
17
|
setupCompleted: boolean;
|
|
30
18
|
setupDate: string | null;
|
|
31
|
-
configuredCLIs: string[];
|
|
32
19
|
enabledIdes: string[];
|
|
33
|
-
recentCliWorkspaces: string[];
|
|
34
20
|
/** Saved workspaces for IDE/CLI/ACP launch (daemon-local) */
|
|
35
21
|
workspaces?: WorkspaceEntry[];
|
|
36
22
|
/** Default workspace id (from workspaces[]) — never used implicitly for launch */
|
|
37
23
|
defaultWorkspaceId?: string | null;
|
|
38
|
-
/** Recently used workspaces (IDE / CLI / ACP / default) for quick resume */
|
|
39
|
-
recentWorkspaceActivity?: WorkspaceActivityEntry[];
|
|
40
24
|
/** Unified recent activity across IDE / CLI / ACP launch flows */
|
|
41
25
|
recentActivity?: RecentActivityEntry[];
|
|
26
|
+
/** Last seen timestamps for live sessions, keyed by sessionId */
|
|
27
|
+
sessionReads?: Record<string, number>;
|
|
28
|
+
/** Last seen completion marker for live sessions, keyed by sessionId */
|
|
29
|
+
sessionReadMarkers?: Record<string, string>;
|
|
42
30
|
machineNickname: string | null;
|
|
43
31
|
/**
|
|
44
32
|
* Stable local machine ID (prefix: `mach_`) — generated locally on first run.
|
|
45
33
|
* Used as daemon instance key (`daemon_<machineId>`) and in status reports.
|
|
46
|
-
* NOT the same as the server-side D1 `machines.id` — see `registeredMachineId`.
|
|
47
34
|
*/
|
|
48
35
|
machineId?: string;
|
|
49
36
|
machineSecret?: string | null;
|
|
50
|
-
/**
|
|
51
|
-
* Server-side D1 `machines.id` — the row ID assigned when daemon registers via
|
|
52
|
-
* `POST /cli/complete`. Corresponds to `machineId` in server DO context
|
|
53
|
-
* (`DaemonConnection.machineId`, `StatusContext.machineId`).
|
|
54
|
-
*
|
|
55
|
-
* Naming differs from server-side `machineId` to avoid confusion with the local
|
|
56
|
-
* `config.machineId` (mach_ prefix) which is a different value.
|
|
57
|
-
*
|
|
58
|
-
* @deprecated Legacy bridge field — will be removed after 2026-04-06.
|
|
59
|
-
* Modern auth flow uses `machineSecret` (adm_) to identify machines.
|
|
60
|
-
*/
|
|
61
|
-
registeredMachineId?: string;
|
|
62
|
-
cliHistory: CliHistoryEntry[];
|
|
63
37
|
providerSettings: Record<string, Record<string, any>>;
|
|
64
38
|
ideSettings: Record<string, {
|
|
65
39
|
extensions?: Record<string, {
|
|
@@ -69,17 +43,6 @@ export interface ADHDevConfig {
|
|
|
69
43
|
disableUpstream?: boolean;
|
|
70
44
|
providerDir?: string;
|
|
71
45
|
}
|
|
72
|
-
export interface CliHistoryEntry {
|
|
73
|
-
category?: 'ide' | 'cli' | 'acp';
|
|
74
|
-
cliType: string;
|
|
75
|
-
dir: string;
|
|
76
|
-
cliArgs?: string[];
|
|
77
|
-
workspace?: string;
|
|
78
|
-
newWindow?: boolean;
|
|
79
|
-
model?: string;
|
|
80
|
-
timestamp: number;
|
|
81
|
-
label?: string;
|
|
82
|
-
}
|
|
83
46
|
export declare function generateMachineId(): string;
|
|
84
47
|
export declare function isStableMachineId(machineId?: string | null): boolean;
|
|
85
48
|
/**
|
|
@@ -110,11 +73,3 @@ export declare function isSetupComplete(): boolean;
|
|
|
110
73
|
* Reset configuration
|
|
111
74
|
*/
|
|
112
75
|
export declare function resetConfig(): void;
|
|
113
|
-
/**
|
|
114
|
-
* Generate a connection token for server authentication
|
|
115
|
-
*/
|
|
116
|
-
export declare function generateConnectionToken(): string;
|
|
117
|
-
/**
|
|
118
|
-
* Add launch to history (max 20, dedup by category+type+dir+args+workspace+model)
|
|
119
|
-
*/
|
|
120
|
-
export declare function addCliHistory(entry: Omit<CliHistoryEntry, 'timestamp'>): void;
|
package/src/config/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ADHDev Launcher — Configuration
|
|
3
|
-
*
|
|
4
|
-
* Manages launcher config,
|
|
3
|
+
*
|
|
4
|
+
* Manages launcher config, machine auth, and user preferences.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { homedir } from 'os';
|
|
@@ -16,8 +16,6 @@ export type { RecentActivityEntry } from './recent-activity.js';
|
|
|
16
16
|
export interface ADHDevConfig {
|
|
17
17
|
// Server connection
|
|
18
18
|
serverUrl: string;
|
|
19
|
-
apiToken: string | null;
|
|
20
|
-
connectionToken: string | null;
|
|
21
19
|
|
|
22
20
|
// Selected IDE (primary)
|
|
23
21
|
selectedIde: string | null;
|
|
@@ -28,16 +26,6 @@ export interface ADHDevConfig {
|
|
|
28
26
|
// Installed extensions
|
|
29
27
|
installedExtensions: string[];
|
|
30
28
|
|
|
31
|
-
// User preferences
|
|
32
|
-
autoConnect: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* @deprecated Not read at runtime. Notification preferences are now managed by:
|
|
35
|
-
* - Web UI layer: useNotificationPrefs (localStorage)
|
|
36
|
-
* - Daemon layer: per-provider settings (approvalAlert, longGeneratingAlert)
|
|
37
|
-
* Kept for backward config compat — will be removed in v0.7+.
|
|
38
|
-
*/
|
|
39
|
-
notifications: boolean;
|
|
40
|
-
|
|
41
29
|
// Auth
|
|
42
30
|
userEmail: string | null;
|
|
43
31
|
userName: string | null;
|
|
@@ -46,9 +34,6 @@ export interface ADHDevConfig {
|
|
|
46
34
|
setupCompleted: boolean;
|
|
47
35
|
setupDate: string | null;
|
|
48
36
|
|
|
49
|
-
// Configured CLI agents
|
|
50
|
-
configuredCLIs: string[];
|
|
51
|
-
|
|
52
37
|
// Daemon: which IDEs to connect (empty = all)
|
|
53
38
|
enabledIdes: string[];
|
|
54
39
|
|
|
@@ -70,20 +55,15 @@ export interface ADHDevConfig {
|
|
|
70
55
|
/**
|
|
71
56
|
* Stable local machine ID (prefix: `mach_`) — generated locally on first run.
|
|
72
57
|
* Used as daemon instance key (`daemon_<machineId>`) and in status reports.
|
|
73
|
-
* NOT the same as the server-side D1 `machines.id` — see `registeredMachineId`.
|
|
74
58
|
*/
|
|
75
59
|
machineId?: string;
|
|
76
60
|
|
|
77
|
-
// Machine secret for server auth
|
|
61
|
+
// Machine secret for server auth
|
|
78
62
|
machineSecret?: string | null;
|
|
79
63
|
|
|
80
64
|
/**
|
|
81
65
|
* Server-side D1 `machines.id` — the row ID assigned when daemon registers via
|
|
82
|
-
* `POST /cli/complete`.
|
|
83
|
-
* (`DaemonConnection.machineId`, `StatusContext.machineId`).
|
|
84
|
-
*
|
|
85
|
-
* Naming differs from server-side `machineId` to avoid confusion with the local
|
|
86
|
-
* `config.machineId` (mach_ prefix) which is a different value.
|
|
66
|
+
* `POST /cli/complete`. Used as fallback for machine lookup on re-auth.
|
|
87
67
|
*
|
|
88
68
|
* @deprecated Legacy bridge field — will be removed after 2026-04-06.
|
|
89
69
|
* Modern auth flow uses `machineSecret` (adm_) to identify machines.
|
|
@@ -108,18 +88,13 @@ export interface ADHDevConfig {
|
|
|
108
88
|
|
|
109
89
|
const DEFAULT_CONFIG: ADHDevConfig = {
|
|
110
90
|
serverUrl: 'https://api.adhf.dev',
|
|
111
|
-
apiToken: null,
|
|
112
|
-
connectionToken: null,
|
|
113
91
|
selectedIde: null,
|
|
114
92
|
configuredIdes: [],
|
|
115
93
|
installedExtensions: [],
|
|
116
|
-
autoConnect: true,
|
|
117
|
-
notifications: true,
|
|
118
94
|
userEmail: null,
|
|
119
95
|
userName: null,
|
|
120
96
|
setupCompleted: false,
|
|
121
97
|
setupDate: null,
|
|
122
|
-
configuredCLIs: [],
|
|
123
98
|
enabledIdes: [],
|
|
124
99
|
workspaces: [],
|
|
125
100
|
defaultWorkspaceId: null,
|
|
@@ -137,6 +112,68 @@ const DEFAULT_CONFIG: ADHDevConfig = {
|
|
|
137
112
|
|
|
138
113
|
const MACHINE_ID_PREFIX = 'mach_';
|
|
139
114
|
|
|
115
|
+
function isPlainObject(value: unknown): value is Record<string, any> {
|
|
116
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function asStringArray(value: unknown): string[] {
|
|
120
|
+
if (!Array.isArray(value)) return [];
|
|
121
|
+
return value.filter((item): item is string => typeof item === 'string');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function asNullableString(value: unknown): string | null {
|
|
125
|
+
return typeof value === 'string' ? value : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function asOptionalString(value: unknown): string | undefined {
|
|
129
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function asBoolean(value: unknown, fallback: boolean): boolean {
|
|
133
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeConfig(raw: unknown): ADHDevConfig & { activeWorkspaceId?: string | null } {
|
|
137
|
+
const parsed = isPlainObject(raw) ? raw : {};
|
|
138
|
+
const legacySessionReads = isPlainObject(parsed.recentSessionReads) ? parsed.recentSessionReads : {};
|
|
139
|
+
const sessionReads = isPlainObject(parsed.sessionReads) ? parsed.sessionReads : {};
|
|
140
|
+
const mergedSessionReads = Object.fromEntries(
|
|
141
|
+
Object.entries({ ...legacySessionReads, ...sessionReads })
|
|
142
|
+
.filter(([, value]) => typeof value === 'number' && Number.isFinite(value))
|
|
143
|
+
);
|
|
144
|
+
const sessionReadMarkers = Object.fromEntries(
|
|
145
|
+
Object.entries(isPlainObject(parsed.sessionReadMarkers) ? parsed.sessionReadMarkers : {})
|
|
146
|
+
.filter(([, value]) => typeof value === 'string')
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
serverUrl: typeof parsed.serverUrl === 'string' && parsed.serverUrl.trim()
|
|
151
|
+
? parsed.serverUrl
|
|
152
|
+
: DEFAULT_CONFIG.serverUrl,
|
|
153
|
+
selectedIde: asNullableString(parsed.selectedIde),
|
|
154
|
+
configuredIdes: asStringArray(parsed.configuredIdes),
|
|
155
|
+
installedExtensions: asStringArray(parsed.installedExtensions),
|
|
156
|
+
userEmail: asNullableString(parsed.userEmail),
|
|
157
|
+
userName: asNullableString(parsed.userName),
|
|
158
|
+
setupCompleted: asBoolean(parsed.setupCompleted, DEFAULT_CONFIG.setupCompleted),
|
|
159
|
+
setupDate: asNullableString(parsed.setupDate),
|
|
160
|
+
enabledIdes: asStringArray(parsed.enabledIdes),
|
|
161
|
+
workspaces: Array.isArray(parsed.workspaces) ? parsed.workspaces as WorkspaceEntry[] : [],
|
|
162
|
+
defaultWorkspaceId: asNullableString(parsed.defaultWorkspaceId) ?? asNullableString(parsed.activeWorkspaceId),
|
|
163
|
+
recentActivity: Array.isArray(parsed.recentActivity) ? parsed.recentActivity as RecentActivityEntry[] : [],
|
|
164
|
+
sessionReads: mergedSessionReads,
|
|
165
|
+
sessionReadMarkers,
|
|
166
|
+
machineNickname: asNullableString(parsed.machineNickname),
|
|
167
|
+
machineId: asOptionalString(parsed.machineId),
|
|
168
|
+
machineSecret: parsed.machineSecret === null ? null : asOptionalString(parsed.machineSecret),
|
|
169
|
+
registeredMachineId: asOptionalString(parsed.registeredMachineId),
|
|
170
|
+
providerSettings: isPlainObject(parsed.providerSettings) ? parsed.providerSettings : {},
|
|
171
|
+
ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
|
|
172
|
+
disableUpstream: asBoolean(parsed.disableUpstream, DEFAULT_CONFIG.disableUpstream ?? false),
|
|
173
|
+
providerDir: asOptionalString(parsed.providerDir),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
140
177
|
export function generateMachineId(): string {
|
|
141
178
|
return `${MACHINE_ID_PREFIX}${randomUUID().replace(/-/g, '')}`;
|
|
142
179
|
}
|
|
@@ -201,14 +238,10 @@ export function loadConfig(): ADHDevConfig {
|
|
|
201
238
|
try {
|
|
202
239
|
const raw = readFileSync(configPath, 'utf-8');
|
|
203
240
|
const parsed = JSON.parse(raw);
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
(merged as ADHDevConfig).defaultWorkspaceId = merged.activeWorkspaceId;
|
|
207
|
-
}
|
|
208
|
-
delete (merged as any).activeWorkspaceId;
|
|
209
|
-
const ensured = ensureMachineId(merged);
|
|
241
|
+
const normalizedInput = normalizeConfig(parsed);
|
|
242
|
+
const ensured = ensureMachineId(normalizedInput);
|
|
210
243
|
const normalized = ensured.config as ADHDevConfig & { activeWorkspaceId?: string | null };
|
|
211
|
-
if (ensured.changed) {
|
|
244
|
+
if (ensured.changed || JSON.stringify(parsed) !== JSON.stringify(normalized)) {
|
|
212
245
|
try {
|
|
213
246
|
saveConfig(normalized);
|
|
214
247
|
} catch { /* ignore */ }
|
|
@@ -226,12 +259,13 @@ export function loadConfig(): ADHDevConfig {
|
|
|
226
259
|
export function saveConfig(config: ADHDevConfig): void {
|
|
227
260
|
const configPath = getConfigPath();
|
|
228
261
|
const dir = getConfigDir();
|
|
262
|
+
const normalized = normalizeConfig(config);
|
|
229
263
|
|
|
230
264
|
if (!existsSync(dir)) {
|
|
231
265
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
232
266
|
}
|
|
233
267
|
|
|
234
|
-
writeFileSync(configPath, JSON.stringify(
|
|
268
|
+
writeFileSync(configPath, JSON.stringify(normalized, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
235
269
|
try { chmodSync(configPath, 0o600); } catch { /* Windows etc. not supported */ }
|
|
236
270
|
}
|
|
237
271
|
|
|
@@ -276,15 +310,3 @@ export function isSetupComplete(): boolean {
|
|
|
276
310
|
export function resetConfig(): void {
|
|
277
311
|
saveConfig({ ...DEFAULT_CONFIG });
|
|
278
312
|
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Generate a connection token for server authentication
|
|
282
|
-
*/
|
|
283
|
-
export function generateConnectionToken(): string {
|
|
284
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
285
|
-
let token = 'db_';
|
|
286
|
-
for (let i = 0; i < 32; i++) {
|
|
287
|
-
token += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
288
|
-
}
|
|
289
|
-
return token;
|
|
290
|
-
}
|
|
@@ -17,10 +17,7 @@ export declare function validateWorkspacePath(absPath: string): {
|
|
|
17
17
|
};
|
|
18
18
|
/** Default workspace label from path */
|
|
19
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;
|
|
20
|
+
|
|
24
21
|
export declare function getDefaultWorkspacePath(config: ADHDevConfig): string | null;
|
|
25
22
|
export declare function getWorkspaceState(config: ADHDevConfig): {
|
|
26
23
|
workspaces: WorkspaceEntry[];
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import * as crypto from 'crypto';
|
|
10
|
-
import type { ProviderModule
|
|
10
|
+
import type { ProviderModule } from './contracts.js';
|
|
11
11
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
12
12
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
13
13
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
@@ -33,8 +33,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
33
33
|
private historyWriter: ChatHistoryWriter;
|
|
34
34
|
readonly instanceId: string;
|
|
35
35
|
|
|
36
|
-
private
|
|
37
|
-
private resolvedOutputFormat: 'terminal' | 'stream-json';
|
|
36
|
+
private presentationMode: 'terminal' | 'chat';
|
|
38
37
|
|
|
39
38
|
constructor(
|
|
40
39
|
private provider: ProviderModule,
|
|
@@ -42,37 +41,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
42
41
|
private cliArgs: string[] = [],
|
|
43
42
|
instanceId?: string,
|
|
44
43
|
transportFactory?: PtyTransportFactory,
|
|
45
|
-
launchModeId?: string,
|
|
46
44
|
) {
|
|
47
45
|
this.type = provider.type;
|
|
48
46
|
this.instanceId = instanceId || crypto.randomUUID();
|
|
49
|
-
this.
|
|
50
|
-
this.resolvedOutputFormat = this.resolveOutputFormat();
|
|
47
|
+
this.presentationMode = 'terminal';
|
|
51
48
|
this.adapter = new ProviderCliAdapter(provider as any as CliProviderModule, workingDir, cliArgs, transportFactory);
|
|
52
49
|
this.monitor = new StatusMonitor();
|
|
53
50
|
this.historyWriter = new ChatHistoryWriter();
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
/**
|
|
57
|
-
* Determine output rendering format from:
|
|
58
|
-
* 1. launchMode.outputFormat (explicit override)
|
|
59
|
-
* 2. launchOptions[].outputFormatMap — check actual args for matching values
|
|
60
|
-
* 3. Default: 'terminal'
|
|
61
|
-
*/
|
|
62
|
-
private resolveOutputFormat(): 'terminal' | 'stream-json' {
|
|
63
|
-
if (this.launchMode?.outputFormat) return this.launchMode.outputFormat;
|
|
64
|
-
if (this.provider.launchOptions?.length) {
|
|
65
|
-
for (const opt of this.provider.launchOptions) {
|
|
66
|
-
if (!opt.outputFormatMap) continue;
|
|
67
|
-
// Check if any cliArg matches a value with an outputFormatMap entry
|
|
68
|
-
for (const [val, fmt] of Object.entries(opt.outputFormatMap)) {
|
|
69
|
-
if (this.cliArgs.includes(val)) return fmt;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return 'terminal';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
53
|
// ─── Lifecycle ─────────────────────────────────
|
|
77
54
|
|
|
78
55
|
async init(context: InstanceContext): Promise<void> {
|
|
@@ -110,6 +87,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
110
87
|
|
|
111
88
|
getState(): ProviderState {
|
|
112
89
|
const adapterStatus = this.adapter.getStatus();
|
|
90
|
+
const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
|
|
113
91
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
114
92
|
|
|
115
93
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
@@ -128,14 +106,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
128
106
|
name: this.provider.name,
|
|
129
107
|
category: 'cli',
|
|
130
108
|
status: adapterStatus.status,
|
|
131
|
-
mode: this.
|
|
132
|
-
launchMode: this.launchMode?.id,
|
|
109
|
+
mode: this.presentationMode,
|
|
133
110
|
activeChat: {
|
|
134
111
|
id: `${this.type}_${this.workingDir}`,
|
|
135
|
-
title: `${this.provider.name} · ${dirName}`,
|
|
136
|
-
status: adapterStatus.status,
|
|
137
|
-
messages: [],
|
|
138
|
-
activeModal: adapterStatus.activeModal,
|
|
112
|
+
title: parsedStatus?.title || `${this.provider.name} · ${dirName}`,
|
|
113
|
+
status: parsedStatus?.status || adapterStatus.status,
|
|
114
|
+
messages: Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [],
|
|
115
|
+
activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
139
116
|
terminalHistory: adapterStatus.terminalHistory,
|
|
140
117
|
inputContent: '',
|
|
141
118
|
},
|
|
@@ -158,6 +135,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
158
135
|
};
|
|
159
136
|
}
|
|
160
137
|
|
|
138
|
+
setPresentationMode(mode: 'terminal' | 'chat'): void {
|
|
139
|
+
if (this.presentationMode === mode) return;
|
|
140
|
+
this.presentationMode = mode;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getPresentationMode(): 'terminal' | 'chat' {
|
|
144
|
+
return this.presentationMode;
|
|
145
|
+
}
|
|
146
|
+
|
|
161
147
|
onEvent(event: string, data?: any): void {
|
|
162
148
|
if (event === 'send_message' && data?.text) {
|
|
163
149
|
void this.adapter.sendMessage(data.text).catch((e: any) => {
|