@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.
Files changed (53) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -1
  2. package/dist/cli-adapters/provider-cli-runtime.d.ts +1 -0
  3. package/dist/commands/cli-manager.d.ts +17 -4
  4. package/dist/commands/mesh-coordinator.d.ts +2 -0
  5. package/dist/commands/router.d.ts +11 -0
  6. package/dist/config/mesh-config.d.ts +3 -0
  7. package/dist/git/git-types.d.ts +1 -1
  8. package/dist/git/git-worktree.d.ts +64 -0
  9. package/dist/git/index.d.ts +2 -0
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +1525 -446
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +1550 -477
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/mesh/coordinator-prompt.d.ts +1 -0
  16. package/dist/mesh/mesh-events.d.ts +9 -0
  17. package/dist/providers/chat-message-normalization.d.ts +11 -0
  18. package/dist/providers/cli-provider-instance.d.ts +3 -0
  19. package/dist/providers/provider-instance-manager.d.ts +1 -0
  20. package/dist/providers/provider-instance.d.ts +2 -0
  21. package/dist/repo-mesh-types.d.ts +27 -0
  22. package/dist/session-host/runtime-support.d.ts +2 -1
  23. package/dist/shared-types.d.ts +4 -0
  24. package/dist/types.d.ts +9 -0
  25. package/package.json +4 -5
  26. package/src/cli-adapters/provider-cli-adapter.ts +28 -7
  27. package/src/cli-adapters/provider-cli-runtime.ts +3 -2
  28. package/src/commands/chat-commands.ts +126 -11
  29. package/src/commands/cli-manager.ts +78 -5
  30. package/src/commands/handler.ts +13 -4
  31. package/src/commands/mesh-coordinator.ts +148 -5
  32. package/src/commands/router.d.ts +1 -0
  33. package/src/commands/router.ts +553 -34
  34. package/src/config/mesh-config.ts +23 -2
  35. package/src/git/git-commands.ts +5 -1
  36. package/src/git/git-types.ts +1 -0
  37. package/src/git/git-worktree.ts +214 -0
  38. package/src/git/index.ts +14 -0
  39. package/src/index.ts +3 -0
  40. package/src/mesh/coordinator-prompt.ts +29 -14
  41. package/src/mesh/mesh-events.ts +109 -43
  42. package/src/providers/chat-message-normalization.ts +80 -0
  43. package/src/providers/cli-provider-instance.d.ts +2 -0
  44. package/src/providers/cli-provider-instance.ts +93 -8
  45. package/src/providers/provider-instance-manager.ts +20 -1
  46. package/src/providers/provider-instance.ts +2 -0
  47. package/src/providers/read-chat-contract.ts +8 -0
  48. package/src/repo-mesh-types.ts +30 -0
  49. package/src/session-host/runtime-support.ts +55 -7
  50. package/src/shared-types.ts +4 -0
  51. package/src/status/builders.ts +17 -12
  52. package/src/status/reporter.ts +6 -0
  53. 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?: { resumeSessionId?: string, settingsOverride?: Record<string, any> },
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
- { resumeSessionId: args?.resumeSessionId, settingsOverride: args?.settings },
976
+ {
977
+ resumeSessionId: args?.resumeSessionId,
978
+ settingsOverride,
979
+ extraEnv: delegatedLaunch ? delegatedLaunch.env : args?.env,
980
+ },
908
981
  );
909
982
 
910
983
  return {
@@ -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
- || args?.agentType
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
- if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd)) {
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 { existsSync, realpathSync } from 'node:fs'
1
+ import { execFileSync } from 'node:child_process'
2
+ import { existsSync, readdirSync, realpathSync } from 'node:fs'
2
3
  import { createRequire } from 'node:module'
3
- import { dirname, join, resolve } from 'node:path'
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 without relying on a PATH bin shim',
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: join(workspace, path),
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: options.nodeExecutable?.trim() || process.execPath,
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
@@ -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>;