@adhdev/daemon-core 0.9.76-rc.6 → 0.9.76-rc.60
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 +2 -1
- package/dist/cli-adapters/provider-cli-runtime.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +17 -4
- package/dist/commands/mesh-coordinator.d.ts +2 -0
- package/dist/commands/router.d.ts +11 -0
- package/dist/config/mesh-config.d.ts +3 -0
- package/dist/git/git-types.d.ts +1 -1
- package/dist/git/git-worktree.d.ts +64 -0
- package/dist/git/index.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1525 -446
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1550 -477
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +1 -0
- package/dist/mesh/mesh-events.d.ts +9 -0
- package/dist/providers/chat-message-normalization.d.ts +11 -0
- package/dist/providers/cli-provider-instance.d.ts +3 -0
- package/dist/providers/provider-instance-manager.d.ts +1 -0
- package/dist/providers/provider-instance.d.ts +2 -0
- package/dist/repo-mesh-types.d.ts +27 -0
- package/dist/session-host/runtime-support.d.ts +2 -1
- package/dist/shared-types.d.ts +4 -0
- package/dist/types.d.ts +9 -0
- package/package.json +4 -5
- package/src/cli-adapters/provider-cli-adapter.ts +28 -7
- package/src/cli-adapters/provider-cli-runtime.ts +3 -2
- package/src/commands/chat-commands.ts +126 -11
- package/src/commands/cli-manager.ts +78 -5
- package/src/commands/handler.ts +13 -4
- package/src/commands/mesh-coordinator.ts +148 -5
- package/src/commands/router.d.ts +1 -0
- package/src/commands/router.ts +553 -34
- package/src/config/mesh-config.ts +23 -2
- package/src/git/git-commands.ts +5 -1
- package/src/git/git-types.ts +1 -0
- package/src/git/git-worktree.ts +214 -0
- package/src/git/index.ts +14 -0
- package/src/index.ts +3 -0
- package/src/mesh/coordinator-prompt.ts +29 -14
- package/src/mesh/mesh-events.ts +109 -43
- package/src/providers/chat-message-normalization.ts +80 -0
- package/src/providers/cli-provider-instance.d.ts +2 -0
- package/src/providers/cli-provider-instance.ts +93 -8
- package/src/providers/provider-instance-manager.ts +20 -1
- package/src/providers/provider-instance.ts +2 -0
- package/src/providers/read-chat-contract.ts +8 -0
- package/src/repo-mesh-types.ts +30 -0
- package/src/session-host/runtime-support.ts +55 -7
- package/src/shared-types.ts +4 -0
- package/src/status/builders.ts +17 -12
- package/src/status/reporter.ts +6 -0
- package/src/types.ts +9 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import * as os from 'os';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
|
-
import { existsSync } from 'fs';
|
|
11
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
12
12
|
import { execFileSync } from 'child_process';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
@@ -132,6 +132,62 @@ type CliAdapterWithExtraArgs = CliAdapter & {
|
|
|
132
132
|
extraArgs?: string[];
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
+
type CliStartOptions = {
|
|
136
|
+
resumeSessionId?: string;
|
|
137
|
+
settingsOverride?: Record<string, any>;
|
|
138
|
+
extraEnv?: Record<string, string>;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const COORDINATOR_DELEGATED_ENV_UNSETS: Record<string, string> = {
|
|
142
|
+
ADHDEV_INLINE_MESH: '',
|
|
143
|
+
ADHDEV_MCP_TRANSPORT: '',
|
|
144
|
+
ADHDEV_MESH_ID: '',
|
|
145
|
+
HERMES_EPHEMERAL_SYSTEM_PROMPT: '',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export interface CoordinatorDelegatedCliLaunchOptionsInput {
|
|
149
|
+
cliType: string;
|
|
150
|
+
workspace: string;
|
|
151
|
+
cliArgs?: string[];
|
|
152
|
+
env?: Record<string, string>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface CoordinatorDelegatedCliLaunchOptions {
|
|
156
|
+
cliArgs: string[];
|
|
157
|
+
env: Record<string, string>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function hasCliArg(args: string[], flag: string): boolean {
|
|
161
|
+
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function ensureEmptyDelegatedMcpConfig(workspace: string): string {
|
|
165
|
+
const baseDir = path.join(os.tmpdir(), 'adhdev-delegated-agent-empty-mcp');
|
|
166
|
+
mkdirSync(baseDir, { recursive: true });
|
|
167
|
+
const workspaceHash = crypto.createHash('sha256').update(path.resolve(workspace || os.tmpdir())).digest('hex').slice(0, 16);
|
|
168
|
+
const filePath = path.join(baseDir, `${workspaceHash}.json`);
|
|
169
|
+
writeFileSync(filePath, JSON.stringify({ mcpServers: {} }, null, 2), 'utf-8');
|
|
170
|
+
return filePath;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function buildCoordinatorDelegatedCliLaunchOptions(
|
|
174
|
+
input: CoordinatorDelegatedCliLaunchOptionsInput,
|
|
175
|
+
): CoordinatorDelegatedCliLaunchOptions {
|
|
176
|
+
const cliType = String(input.cliType || '').trim();
|
|
177
|
+
const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
|
|
178
|
+
const env: Record<string, string> = { ...(input.env || {}), ...COORDINATOR_DELEGATED_ENV_UNSETS };
|
|
179
|
+
|
|
180
|
+
if (cliType === 'hermes-cli' && !hasCliArg(cliArgs, '--ignore-user-config')) {
|
|
181
|
+
cliArgs.unshift('--ignore-user-config');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (cliType === 'claude-cli' && !hasCliArg(cliArgs, '--mcp-config')) {
|
|
185
|
+
cliArgs.unshift('--mcp-config', ensureEmptyDelegatedMcpConfig(input.workspace));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { cliArgs, env };
|
|
189
|
+
}
|
|
190
|
+
|
|
135
191
|
function isUuid(value: string): boolean {
|
|
136
192
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
137
193
|
}
|
|
@@ -365,6 +421,7 @@ export class DaemonCliManager {
|
|
|
365
421
|
runtimeId: string,
|
|
366
422
|
providerSessionId?: string,
|
|
367
423
|
attachExisting = false,
|
|
424
|
+
extraEnv?: Record<string, string>,
|
|
368
425
|
): CliAdapter {
|
|
369
426
|
// cliType normalize (Resolve alias)
|
|
370
427
|
const normalizedType = this.providerLoader.resolveAlias(cliType);
|
|
@@ -382,7 +439,7 @@ export class DaemonCliManager {
|
|
|
382
439
|
providerSessionId,
|
|
383
440
|
attachExisting,
|
|
384
441
|
);
|
|
385
|
-
return new ProviderCliAdapter(resolvedProvider as CliProviderModule, workingDir, cliArgs, transportFactory);
|
|
442
|
+
return new ProviderCliAdapter(resolvedProvider as CliProviderModule, workingDir, cliArgs, extraEnv || {}, transportFactory);
|
|
386
443
|
}
|
|
387
444
|
|
|
388
445
|
throw new Error(`No CLI provider found for '${cliType}'. Create a provider.js in providers/cli/${cliType}/`);
|
|
@@ -425,6 +482,7 @@ export class DaemonCliManager {
|
|
|
425
482
|
options?: {
|
|
426
483
|
providerSessionId?: string;
|
|
427
484
|
launchMode?: CliLaunchMode;
|
|
485
|
+
extraEnv?: Record<string, string>;
|
|
428
486
|
onProviderSessionResolved?: (info: {
|
|
429
487
|
instanceId: string;
|
|
430
488
|
providerType: string;
|
|
@@ -480,7 +538,7 @@ export class DaemonCliManager {
|
|
|
480
538
|
workingDir: string,
|
|
481
539
|
cliArgs?: string[],
|
|
482
540
|
initialModel?: string,
|
|
483
|
-
options?:
|
|
541
|
+
options?: CliStartOptions,
|
|
484
542
|
): Promise<{ runtimeSessionId: string; providerSessionId?: string }> {
|
|
485
543
|
const trimmed = (workingDir || '').trim();
|
|
486
544
|
if (!trimmed) throw new Error('working directory required');
|
|
@@ -629,6 +687,7 @@ export class DaemonCliManager {
|
|
|
629
687
|
{
|
|
630
688
|
providerSessionId: sessionBinding.providerSessionId,
|
|
631
689
|
launchMode: sessionBinding.launchMode,
|
|
690
|
+
extraEnv: options?.extraEnv,
|
|
632
691
|
onProviderSessionResolved: ({ providerSessionId, providerName, providerType, workspace }) => {
|
|
633
692
|
this.persistRecentActivity({
|
|
634
693
|
kind: 'cli',
|
|
@@ -651,6 +710,7 @@ export class DaemonCliManager {
|
|
|
651
710
|
key,
|
|
652
711
|
sessionBinding.providerSessionId,
|
|
653
712
|
false,
|
|
713
|
+
options?.extraEnv,
|
|
654
714
|
);
|
|
655
715
|
try {
|
|
656
716
|
await adapter.spawn();
|
|
@@ -899,12 +959,25 @@ export class DaemonCliManager {
|
|
|
899
959
|
const launchSource = resolved.source;
|
|
900
960
|
if (!cliType) throw new Error('cliType required');
|
|
901
961
|
|
|
962
|
+
const settingsOverride = args?.settings && typeof args.settings === 'object' ? args.settings : undefined;
|
|
963
|
+
const delegatedLaunch = settingsOverride?.launchedByCoordinator === true
|
|
964
|
+
? buildCoordinatorDelegatedCliLaunchOptions({
|
|
965
|
+
cliType,
|
|
966
|
+
workspace: dir,
|
|
967
|
+
cliArgs: args?.cliArgs,
|
|
968
|
+
env: args?.env,
|
|
969
|
+
})
|
|
970
|
+
: null;
|
|
902
971
|
const started = await this.startSession(
|
|
903
972
|
cliType,
|
|
904
973
|
dir,
|
|
905
|
-
args?.cliArgs,
|
|
974
|
+
delegatedLaunch ? delegatedLaunch.cliArgs : args?.cliArgs,
|
|
906
975
|
args?.initialModel,
|
|
907
|
-
{
|
|
976
|
+
{
|
|
977
|
+
resumeSessionId: args?.resumeSessionId,
|
|
978
|
+
settingsOverride,
|
|
979
|
+
extraEnv: delegatedLaunch ? delegatedLaunch.env : args?.env,
|
|
980
|
+
},
|
|
908
981
|
);
|
|
909
982
|
|
|
910
983
|
return {
|
package/src/commands/handler.ts
CHANGED
|
@@ -303,14 +303,15 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
303
303
|
const sessionLookupFailed = !!targetSessionId && !session;
|
|
304
304
|
|
|
305
305
|
const managerKey = this.extractIdeType(args, sessionLookupFailed);
|
|
306
|
-
let providerType: string | undefined;
|
|
306
|
+
let providerType: string | undefined = args?.agentType || args?.providerType;
|
|
307
307
|
|
|
308
308
|
if (!sessionLookupFailed) {
|
|
309
309
|
providerType =
|
|
310
310
|
session?.providerType
|
|
311
|
-
||
|
|
312
|
-
|| args?.providerType
|
|
311
|
+
|| providerType
|
|
313
312
|
|| this.inferProviderType(managerKey);
|
|
313
|
+
} else if (!providerType) {
|
|
314
|
+
providerType = this.inferProviderType(managerKey);
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
return { session, managerKey, providerType, sessionLookupFailed };
|
|
@@ -407,7 +408,15 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
407
408
|
'invoke_provider_script',
|
|
408
409
|
]);
|
|
409
410
|
|
|
410
|
-
|
|
411
|
+
const allowsInactiveReadChatFallback =
|
|
412
|
+
cmd === 'read_chat'
|
|
413
|
+
&& !!this._currentRoute.providerType
|
|
414
|
+
&& (
|
|
415
|
+
(typeof args?.providerSessionId === 'string' && args.providerSessionId.trim().length > 0)
|
|
416
|
+
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim().length > 0)
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd) && !allowsInactiveReadChatFallback) {
|
|
411
420
|
const result = {
|
|
412
421
|
success: false,
|
|
413
422
|
error: `Live session not found for targetSessionId: ${String(args?.targetSessionId || '').trim() || 'unknown'}`,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, readdirSync, realpathSync } from 'node:fs'
|
|
2
3
|
import { createRequire } from 'node:module'
|
|
3
|
-
import
|
|
4
|
+
import * as os from 'node:os'
|
|
5
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path'
|
|
4
6
|
import type { ProviderModule, MeshCoordinatorMcpConfigFormat } from '../providers/contracts.js'
|
|
5
7
|
|
|
6
8
|
export interface MeshCoordinatorMcpServerLaunch {
|
|
@@ -32,6 +34,7 @@ export type MeshCoordinatorSetup =
|
|
|
32
34
|
|
|
33
35
|
export interface ResolveMeshCoordinatorSetupOptions {
|
|
34
36
|
provider?: ProviderModule | null
|
|
37
|
+
cliType?: string
|
|
35
38
|
meshId: string
|
|
36
39
|
workspace: string
|
|
37
40
|
adhdevMcpCommand?: string
|
|
@@ -41,6 +44,58 @@ export interface ResolveMeshCoordinatorSetupOptions {
|
|
|
41
44
|
|
|
42
45
|
const DEFAULT_SERVER_NAME = 'adhdev-mesh'
|
|
43
46
|
const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev-mcp'
|
|
47
|
+
const HERMES_CLI_TYPE = 'hermes-cli'
|
|
48
|
+
const HERMES_MCP_CONFIG_PATH = '~/.hermes/config.yaml'
|
|
49
|
+
|
|
50
|
+
function isHermesProvider(provider: ProviderModule | null | undefined, cliType?: string): boolean {
|
|
51
|
+
const type = cliType?.trim() || provider?.type?.trim() || ''
|
|
52
|
+
return type === HERMES_CLI_TYPE
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveHermesMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupOptions): MeshCoordinatorSetup {
|
|
56
|
+
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
57
|
+
meshId: options.meshId,
|
|
58
|
+
nodeExecutable: options.nodeExecutable,
|
|
59
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
60
|
+
})
|
|
61
|
+
if (!mcpServer) {
|
|
62
|
+
return {
|
|
63
|
+
kind: 'unsupported',
|
|
64
|
+
reason: 'Could not resolve the ADHDev MCP server entrypoint and a Node runtime with WebSocket support for daemon IPC mode',
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const configPath = resolveMcpConfigPath(HERMES_MCP_CONFIG_PATH, options.workspace)
|
|
68
|
+
if (!configPath.trim()) {
|
|
69
|
+
return createHermesManualMeshCoordinatorSetup(options.meshId, options.workspace)
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
kind: 'auto_import',
|
|
73
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
74
|
+
configPath,
|
|
75
|
+
configFormat: 'hermes_config_yaml',
|
|
76
|
+
mcpServer,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createHermesManualMeshCoordinatorSetup(meshId: string, workspace: string): MeshCoordinatorSetup {
|
|
81
|
+
return {
|
|
82
|
+
kind: 'manual',
|
|
83
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
84
|
+
configFormat: 'hermes_config_yaml',
|
|
85
|
+
configPathCommand: HERMES_MCP_CONFIG_PATH,
|
|
86
|
+
requiresRestart: true,
|
|
87
|
+
instructions: 'Hermes CLI does not auto-import repo-local .mcp.json. Add this MCP server to Hermes config under mcp_servers, then start a fresh Hermes session.',
|
|
88
|
+
template: renderMeshCoordinatorTemplate(
|
|
89
|
+
'mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - --repo-mesh\n - {{meshId}}\n enabled: true\n',
|
|
90
|
+
{
|
|
91
|
+
meshId,
|
|
92
|
+
workspace,
|
|
93
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
94
|
+
adhdevMcpCommand: DEFAULT_ADHDEV_MCP_COMMAND,
|
|
95
|
+
},
|
|
96
|
+
),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
44
99
|
|
|
45
100
|
export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupOptions): MeshCoordinatorSetup {
|
|
46
101
|
const { provider, meshId, workspace } = options
|
|
@@ -52,6 +107,10 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
52
107
|
}
|
|
53
108
|
}
|
|
54
109
|
|
|
110
|
+
if (isHermesProvider(provider, options.cliType)) {
|
|
111
|
+
return resolveHermesMeshCoordinatorSetup(options)
|
|
112
|
+
}
|
|
113
|
+
|
|
55
114
|
const mcpConfig = config.mcpConfig
|
|
56
115
|
if (!mcpConfig || mcpConfig.mode === 'none') {
|
|
57
116
|
return {
|
|
@@ -74,13 +133,13 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
74
133
|
if (!mcpServer) {
|
|
75
134
|
return {
|
|
76
135
|
kind: 'unsupported',
|
|
77
|
-
reason: 'Could not resolve the ADHDev MCP server entrypoint
|
|
136
|
+
reason: 'Could not resolve the ADHDev MCP server entrypoint and a Node runtime with WebSocket support for daemon IPC mode',
|
|
78
137
|
}
|
|
79
138
|
}
|
|
80
139
|
return {
|
|
81
140
|
kind: 'auto_import',
|
|
82
141
|
serverName,
|
|
83
|
-
configPath:
|
|
142
|
+
configPath: resolveMcpConfigPath(path, workspace),
|
|
84
143
|
configFormat: mcpConfig.format,
|
|
85
144
|
mcpServer,
|
|
86
145
|
}
|
|
@@ -118,6 +177,14 @@ function renderMeshCoordinatorTemplate(template: string, values: Record<string,
|
|
|
118
177
|
return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand)\s*\}\}/g, (_, key: string) => values[key] || '')
|
|
119
178
|
}
|
|
120
179
|
|
|
180
|
+
function resolveMcpConfigPath(configPath: string, workspace: string): string {
|
|
181
|
+
const trimmed = configPath.trim()
|
|
182
|
+
if (trimmed === '~') return os.homedir()
|
|
183
|
+
if (trimmed.startsWith('~/')) return join(os.homedir(), trimmed.slice(2))
|
|
184
|
+
if (isAbsolute(trimmed)) return trimmed
|
|
185
|
+
return join(workspace, trimmed)
|
|
186
|
+
}
|
|
187
|
+
|
|
121
188
|
function resolveAdhdevMcpServerLaunch(options: {
|
|
122
189
|
meshId: string
|
|
123
190
|
nodeExecutable?: string
|
|
@@ -125,12 +192,88 @@ function resolveAdhdevMcpServerLaunch(options: {
|
|
|
125
192
|
}): MeshCoordinatorMcpServerLaunch | null {
|
|
126
193
|
const entryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
|
|
127
194
|
if (!entryPath) return null
|
|
195
|
+
const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable)
|
|
196
|
+
if (!nodeExecutable) return null
|
|
128
197
|
return {
|
|
129
|
-
command:
|
|
198
|
+
command: nodeExecutable,
|
|
130
199
|
args: [entryPath, '--mode', 'ipc', '--repo-mesh', options.meshId],
|
|
131
200
|
}
|
|
132
201
|
}
|
|
133
202
|
|
|
203
|
+
function resolveMcpNodeExecutable(explicitExecutable?: string): string | null {
|
|
204
|
+
const explicit = explicitExecutable?.trim()
|
|
205
|
+
if (explicit) return explicit
|
|
206
|
+
|
|
207
|
+
const candidates: string[] = []
|
|
208
|
+
const addCandidate = (candidate?: string | null) => {
|
|
209
|
+
const trimmed = candidate?.trim()
|
|
210
|
+
if (!trimmed) return
|
|
211
|
+
const normalized = normalizeExistingPath(trimmed) || trimmed
|
|
212
|
+
if (!candidates.includes(normalized)) candidates.push(normalized)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
addCandidate(process.env.ADHDEV_MCP_NODE_EXECUTABLE)
|
|
216
|
+
addCandidate(process.env.ADHDEV_NODE_EXECUTABLE)
|
|
217
|
+
addCandidate(process.env.npm_node_execpath)
|
|
218
|
+
addNodeCandidatesFromPath(process.env.PATH, addCandidate)
|
|
219
|
+
addNodeCandidatesFromNvm(os.homedir(), addCandidate)
|
|
220
|
+
addCandidate('/opt/homebrew/bin/node')
|
|
221
|
+
addCandidate('/usr/local/bin/node')
|
|
222
|
+
addCandidate('/usr/bin/node')
|
|
223
|
+
addCandidate(process.execPath)
|
|
224
|
+
|
|
225
|
+
for (const candidate of candidates) {
|
|
226
|
+
if (nodeRuntimeSupportsWebSocket(candidate)) return candidate
|
|
227
|
+
}
|
|
228
|
+
return null
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function addNodeCandidatesFromPath(pathValue: string | undefined, addCandidate: (candidate?: string | null) => void) {
|
|
232
|
+
for (const entry of (pathValue || '').split(':')) {
|
|
233
|
+
const dir = entry.trim()
|
|
234
|
+
if (!dir) continue
|
|
235
|
+
addCandidate(join(dir, 'node'))
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function addNodeCandidatesFromNvm(homeDir: string, addCandidate: (candidate?: string | null) => void) {
|
|
240
|
+
const versionsDir = join(homeDir, '.nvm', 'versions', 'node')
|
|
241
|
+
try {
|
|
242
|
+
const versionDirs = readdirSync(versionsDir, { withFileTypes: true })
|
|
243
|
+
.filter((entry) => entry.isDirectory())
|
|
244
|
+
.map((entry) => entry.name)
|
|
245
|
+
.sort(compareNodeVersionNamesDescending)
|
|
246
|
+
for (const versionDir of versionDirs) {
|
|
247
|
+
addCandidate(join(versionsDir, versionDir, 'bin', 'node'))
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
// nvm is optional; PATH and process.execPath candidates still cover normal installs.
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function compareNodeVersionNamesDescending(a: string, b: string): number {
|
|
255
|
+
const parse = (value: string) => value.replace(/^v/, '').split('.').map((part) => Number.parseInt(part, 10) || 0)
|
|
256
|
+
const left = parse(a)
|
|
257
|
+
const right = parse(b)
|
|
258
|
+
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
259
|
+
const diff = (right[i] || 0) - (left[i] || 0)
|
|
260
|
+
if (diff !== 0) return diff
|
|
261
|
+
}
|
|
262
|
+
return b.localeCompare(a)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function nodeRuntimeSupportsWebSocket(nodeExecutable: string): boolean {
|
|
266
|
+
try {
|
|
267
|
+
execFileSync(nodeExecutable, ['-e', "process.exit(typeof WebSocket === 'function' ? 0 : 42)"], {
|
|
268
|
+
stdio: 'ignore',
|
|
269
|
+
timeout: 3000,
|
|
270
|
+
})
|
|
271
|
+
return true
|
|
272
|
+
} catch {
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
134
277
|
function resolveAdhdevMcpEntryPath(explicitPath?: string): string | null {
|
|
135
278
|
const explicit = explicitPath?.trim()
|
|
136
279
|
if (explicit) return normalizeExistingPath(explicit) || explicit
|
package/src/commands/router.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface SessionHostControlPlane {
|
|
|
21
21
|
}): Promise<any>;
|
|
22
22
|
listSessions(): Promise<any[]>;
|
|
23
23
|
stopSession(sessionId: string): Promise<any>;
|
|
24
|
+
deleteSession(sessionId: string, opts?: { force?: boolean }): Promise<any>;
|
|
24
25
|
resumeSession(sessionId: string): Promise<any>;
|
|
25
26
|
restartSession(sessionId: string): Promise<any>;
|
|
26
27
|
sendSignal(sessionId: string, signal: string): Promise<any>;
|