@adhdev/daemon-core 0.9.82-rc.8 → 0.9.82-rc.81

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 (74) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
  4. package/dist/commands/router.d.ts +22 -0
  5. package/dist/config/mesh-config.d.ts +66 -1
  6. package/dist/git/git-commands.d.ts +1 -0
  7. package/dist/git/git-status.d.ts +5 -0
  8. package/dist/git/git-types.d.ts +10 -0
  9. package/dist/index.d.ts +13 -6
  10. package/dist/index.js +5074 -1177
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +5038 -1163
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/installer.d.ts +1 -4
  15. package/dist/launch.d.ts +1 -1
  16. package/dist/logging/async-batch-writer.d.ts +10 -0
  17. package/dist/mesh/beads-db.d.ts +18 -0
  18. package/dist/mesh/mesh-active-work.d.ts +60 -0
  19. package/dist/mesh/mesh-events.d.ts +29 -5
  20. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  21. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  22. package/dist/mesh/mesh-ledger.d.ts +38 -1
  23. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  24. package/dist/mesh/refine-config.d.ts +119 -0
  25. package/dist/providers/chat-message-normalization.d.ts +1 -0
  26. package/dist/providers/cli-provider-instance.d.ts +2 -1
  27. package/dist/repo-mesh-types.d.ts +39 -0
  28. package/dist/status/reporter.d.ts +2 -0
  29. package/package.json +3 -1
  30. package/src/boot/daemon-lifecycle.ts +1 -0
  31. package/src/cli-adapters/provider-cli-adapter.ts +91 -3
  32. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  33. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  34. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  35. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  36. package/src/cli-adapters/provider-cli-shared.ts +20 -10
  37. package/src/commands/chat-commands.ts +310 -12
  38. package/src/commands/cli-manager.ts +101 -0
  39. package/src/commands/handler.ts +8 -1
  40. package/src/commands/mesh-coordinator.ts +13 -143
  41. package/src/commands/router.ts +2435 -414
  42. package/src/config/chat-history.ts +9 -7
  43. package/src/config/mesh-config.ts +244 -1
  44. package/src/daemon/dev-cli-debug.ts +10 -1
  45. package/src/detection/ide-detector.ts +26 -16
  46. package/src/git/git-commands.ts +3 -3
  47. package/src/git/git-status.ts +97 -6
  48. package/src/git/git-summary.ts +3 -0
  49. package/src/git/git-types.ts +11 -0
  50. package/src/index.ts +31 -5
  51. package/src/installer.d.ts +1 -1
  52. package/src/installer.ts +8 -6
  53. package/src/launch.d.ts +1 -1
  54. package/src/launch.ts +37 -28
  55. package/src/logging/async-batch-writer.ts +55 -0
  56. package/src/logging/logger.ts +2 -1
  57. package/src/mesh/beads-db.ts +176 -0
  58. package/src/mesh/coordinator-prompt.ts +27 -7
  59. package/src/mesh/mesh-active-work.ts +243 -0
  60. package/src/mesh/mesh-events.ts +398 -46
  61. package/src/mesh/mesh-fast-forward.ts +430 -0
  62. package/src/mesh/mesh-host-ownership.ts +73 -0
  63. package/src/mesh/mesh-ledger.ts +138 -1
  64. package/src/mesh/mesh-work-queue.ts +199 -137
  65. package/src/mesh/refine-config.ts +306 -0
  66. package/src/providers/chat-message-normalization.ts +3 -1
  67. package/src/providers/cli-provider-instance.ts +91 -13
  68. package/src/providers/ide-provider-instance.ts +17 -3
  69. package/src/providers/provider-loader.ts +10 -4
  70. package/src/providers/read-chat-contract.ts +1 -1
  71. package/src/providers/version-archive.ts +38 -20
  72. package/src/repo-mesh-types.ts +43 -0
  73. package/src/status/reporter.ts +15 -0
  74. package/src/system/host-memory.ts +29 -12
@@ -80,6 +80,89 @@ export interface CliManagerDeps {
80
80
 
81
81
  type CommandResult = { success: boolean;[key: string]: unknown };
82
82
 
83
+ const BUSY_AGENT_STATUSES = new Set(['generating', 'running', 'streaming', 'starting', 'busy', 'waiting', 'waiting_approval', 'long_generating']);
84
+ const ZERO_MESSAGE_STARTING_SEND_WAIT_MS = 2_000;
85
+
86
+ function normalizeAgentStatus(value: unknown): string {
87
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
88
+ }
89
+
90
+ function hasNonEmptyModalButtons(activeModal: unknown): boolean {
91
+ const buttons = (activeModal as any)?.buttons;
92
+ return Array.isArray(buttons) && buttons.some((button) => String(button || '').trim().length > 0);
93
+ }
94
+
95
+ function hasAdapterPendingResponse(adapter: any): boolean {
96
+ if (adapter?.isWaitingForResponse === true) return true;
97
+ if (adapter?.currentTurnScope) return true;
98
+ try {
99
+ if (typeof adapter?.isProcessing === 'function' && adapter.isProcessing()) return true;
100
+ } catch { /* defensive: send guard should not fail on diagnostics */ }
101
+ try {
102
+ const partial = typeof adapter?.getPartialResponse === 'function' ? adapter.getPartialResponse() : '';
103
+ if (typeof partial === 'string' && partial.trim()) return true;
104
+ } catch { /* defensive: missing partial means no pending evidence */ }
105
+ return false;
106
+ }
107
+
108
+ function countMessages(value: unknown): number {
109
+ return Array.isArray(value) ? value.length : 0;
110
+ }
111
+
112
+ function hasZeroMessageStartingLaunch(adapter: any): boolean {
113
+ const adapterStatus = adapter?.getStatus?.({ allowParse: false }) ?? adapter?.getStatus?.() ?? {};
114
+ const parsedStatus = typeof adapter?.getScriptParsedStatus === 'function'
115
+ ? adapter.getScriptParsedStatus()
116
+ : {};
117
+ const adapterRawStatus = normalizeAgentStatus(adapterStatus?.status);
118
+ const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
119
+ if (adapterRawStatus !== 'starting') return false;
120
+ if (parsedRawStatus && parsedRawStatus !== 'starting' && parsedRawStatus !== 'generating') return false;
121
+ if (hasNonEmptyModalButtons(adapterStatus?.activeModal ?? adapterStatus?.modal ?? parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
122
+ if (countMessages(adapterStatus?.messages) > 0 || countMessages(parsedStatus?.messages) > 0) return false;
123
+ return !hasAdapterPendingResponse(adapter);
124
+ }
125
+
126
+ function shouldSuppressStaleParsedBusyStatus(adapterStatus: string, parsedStatus: any, adapter: any): boolean {
127
+ const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
128
+ if (!BUSY_AGENT_STATUSES.has(parsedRawStatus)) return false;
129
+ if (adapterStatus !== 'idle') return false;
130
+ if (hasNonEmptyModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
131
+ return !hasAdapterPendingResponse(adapter);
132
+ }
133
+
134
+ function getEffectiveAgentSendStatus(adapter: any): string {
135
+ const adapterStatus = normalizeAgentStatus(adapter?.getStatus?.({ allowParse: false })?.status ?? adapter?.getStatus?.()?.status);
136
+ if (adapterStatus && adapterStatus !== 'idle') return adapterStatus;
137
+ if (adapterStatus !== 'idle') return adapterStatus;
138
+
139
+ if (typeof adapter?.getScriptParsedStatus !== 'function') return adapterStatus;
140
+ try {
141
+ const parsedStatus = adapter.getScriptParsedStatus();
142
+ const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
143
+ if (BUSY_AGENT_STATUSES.has(parsedRawStatus) && !shouldSuppressStaleParsedBusyStatus(adapterStatus, parsedStatus, adapter)) {
144
+ return parsedRawStatus;
145
+ }
146
+ } catch {
147
+ return adapterStatus;
148
+ }
149
+ return adapterStatus;
150
+ }
151
+
152
+ async function waitForZeroMessageStartingLaunch(adapter: any): Promise<boolean> {
153
+ try {
154
+ if (!hasZeroMessageStartingLaunch(adapter)) return false;
155
+ } catch {
156
+ return false;
157
+ }
158
+ await new Promise(resolve => setTimeout(resolve, ZERO_MESSAGE_STARTING_SEND_WAIT_MS));
159
+ try {
160
+ return hasZeroMessageStartingLaunch(adapter);
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+
83
166
  export interface CliTransportFactoryParams {
84
167
  runtimeId: string;
85
168
  providerType: string;
@@ -1073,6 +1156,24 @@ export class DaemonCliManager {
1073
1156
  const { adapter, key } = found;
1074
1157
 
1075
1158
  if (action === 'send_chat') {
1159
+ let currentStatus = getEffectiveAgentSendStatus(adapter);
1160
+ if (currentStatus === 'starting' && await waitForZeroMessageStartingLaunch(adapter)) {
1161
+ currentStatus = 'idle';
1162
+ } else if (currentStatus === 'starting') {
1163
+ currentStatus = getEffectiveAgentSendStatus(adapter);
1164
+ }
1165
+ if (BUSY_AGENT_STATUSES.has(currentStatus)) {
1166
+ return {
1167
+ success: false,
1168
+ code: 'agent_runtime_busy',
1169
+ reason: 'agent_runtime_busy',
1170
+ retryable: true,
1171
+ retryRecommended: true,
1172
+ status: currentStatus,
1173
+ targetSessionId: args?.targetSessionId,
1174
+ error: `CLI agent '${agentType}' is currently ${currentStatus}; retry after the current turn finishes.`,
1175
+ };
1176
+ }
1076
1177
  const input = normalizeInputEnvelope(args?.input ? { input: args.input } : args);
1077
1178
  const provider = this.providerLoader.resolve(agentType) || this.providerLoader.getMeta(agentType);
1078
1179
  if (provider?.category === 'acp') {
@@ -408,12 +408,19 @@ export class DaemonCommandHandler implements CommandHelpers {
408
408
  'invoke_provider_script',
409
409
  ]);
410
410
 
411
+ // read_chat and get_chat_debug_bundle can serve historical transcript data even
412
+ // when the live session record is gone (stopped/destroyed). Allow the fallback
413
+ // when the provider type is known and any session identity hint is present:
414
+ // an explicit providerSessionId/historySessionId, or the targetSessionId itself
415
+ // (which getHistorySessionId already uses as a fallback history key).
416
+ const isReadOrDebugCmd = cmd === 'read_chat' || cmd === 'get_chat_debug_bundle';
411
417
  const allowsInactiveReadChatFallback =
412
- cmd === 'read_chat'
418
+ isReadOrDebugCmd
413
419
  && !!this._currentRoute.providerType
414
420
  && (
415
421
  (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim().length > 0)
416
422
  || (typeof args?.historySessionId === 'string' && args.historySessionId.trim().length > 0)
423
+ || (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim().length > 0)
417
424
  );
418
425
 
419
426
  if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd) && !allowsInactiveReadChatFallback) {
@@ -1,9 +1,6 @@
1
- import { execFileSync } from 'node:child_process'
2
1
  import { createHash } from 'node:crypto'
3
- import { existsSync, readdirSync, realpathSync } from 'node:fs'
4
- import { createRequire } from 'node:module'
5
2
  import * as os from 'node:os'
6
- import { dirname, isAbsolute, join, resolve } from 'node:path'
3
+ import { isAbsolute, join, resolve } from 'node:path'
7
4
  import type { ProviderModule, MeshCoordinatorMcpConfigFormat } from '../providers/contracts.js'
8
5
 
9
6
  export interface MeshCoordinatorMcpServerLaunch {
@@ -55,7 +52,7 @@ export interface ResolveMeshCoordinatorSetupOptions {
55
52
  }
56
53
 
57
54
  const DEFAULT_SERVER_NAME = 'adhdev-mesh'
58
- const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev-mcp'
55
+ const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev'
59
56
  const HERMES_CLI_TYPE = 'hermes-cli'
60
57
  const HERMES_MCP_CONFIG_PATH = '~/.hermes/config.yaml'
61
58
 
@@ -67,8 +64,7 @@ function isHermesProvider(provider: ProviderModule | null | undefined, cliType?:
67
64
  function resolveHermesMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupOptions): MeshCoordinatorSetup {
68
65
  const mcpServer = resolveAdhdevMcpServerLaunch({
69
66
  meshId: options.meshId,
70
- nodeExecutable: options.nodeExecutable,
71
- adhdevMcpEntryPath: options.adhdevMcpEntryPath,
67
+ adhdevMcpCommand: options.adhdevMcpCommand,
72
68
  adhdevMcpTransport: options.adhdevMcpTransport,
73
69
  adhdevMcpPort: options.adhdevMcpPort,
74
70
  })
@@ -100,7 +96,7 @@ export function createHermesManualMeshCoordinatorSetup(meshId: string, workspace
100
96
  requiresRestart: true,
101
97
  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.',
102
98
  template: renderMeshCoordinatorTemplate(
103
- 'mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - --repo-mesh\n - {{meshId}}\n enabled: true\n',
99
+ 'mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - mcp\n - --mode\n - ipc\n - --repo-mesh\n - {{meshId}}\n enabled: true\n',
104
100
  {
105
101
  meshId,
106
102
  workspace,
@@ -141,8 +137,7 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
141
137
  }
142
138
  const mcpServer = resolveAdhdevMcpServerLaunch({
143
139
  meshId,
144
- nodeExecutable: options.nodeExecutable,
145
- adhdevMcpEntryPath: options.adhdevMcpEntryPath,
140
+ adhdevMcpCommand: options.adhdevMcpCommand,
146
141
  adhdevMcpTransport: options.adhdevMcpTransport,
147
142
  adhdevMcpPort: options.adhdevMcpPort,
148
143
  })
@@ -222,25 +217,25 @@ function resolveMcpConfigPath(configPath: string, workspace: string): string {
222
217
 
223
218
  function resolveAdhdevMcpServerLaunch(options: {
224
219
  meshId: string
225
- nodeExecutable?: string
226
- adhdevMcpEntryPath?: string
220
+ adhdevMcpCommand?: string
227
221
  adhdevMcpTransport?: 'local' | 'ipc'
228
222
  adhdevMcpPort?: number
229
223
  }): MeshCoordinatorMcpServerLaunch | null {
230
- const entryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
231
- if (!entryPath) return null
232
- const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable)
233
- if (!nodeExecutable) return null
224
+ const command = resolveAdhdevCommand(options.adhdevMcpCommand)
234
225
  const transport = resolveMcpTransport(options.adhdevMcpTransport)
235
- const args = [entryPath, '--mode', transport, '--repo-mesh', options.meshId]
226
+ const args = ['mcp', '--mode', transport, '--repo-mesh', options.meshId]
236
227
  const port = resolveMcpPort(options.adhdevMcpPort)
237
228
  if (port !== undefined) args.push('--port', String(port))
238
229
  return {
239
- command: nodeExecutable,
230
+ command,
240
231
  args,
241
232
  }
242
233
  }
243
234
 
235
+ function resolveAdhdevCommand(explicitCommand?: string): string {
236
+ return explicitCommand?.trim() || process.env.ADHDEV_COORDINATOR_MCP_COMMAND?.trim() || DEFAULT_ADHDEV_MCP_COMMAND
237
+ }
238
+
244
239
  function resolveMcpTransport(explicitTransport?: 'local' | 'ipc'): 'local' | 'ipc' {
245
240
  if (explicitTransport === 'local' || explicitTransport === 'ipc') return explicitTransport
246
241
  const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim()
@@ -254,128 +249,3 @@ function resolveMcpPort(explicitPort?: number): number | undefined {
254
249
  const parsed = Number(raw)
255
250
  return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined
256
251
  }
257
-
258
- function resolveMcpNodeExecutable(explicitExecutable?: string): string | null {
259
- const explicit = explicitExecutable?.trim()
260
- if (explicit) return explicit
261
-
262
- const candidates: string[] = []
263
- const addCandidate = (candidate?: string | null) => {
264
- const trimmed = candidate?.trim()
265
- if (!trimmed) return
266
- const normalized = normalizeExistingPath(trimmed) || trimmed
267
- if (!candidates.includes(normalized)) candidates.push(normalized)
268
- }
269
-
270
- addCandidate(process.env.ADHDEV_MCP_NODE_EXECUTABLE)
271
- addCandidate(process.env.ADHDEV_NODE_EXECUTABLE)
272
- addCandidate(process.env.npm_node_execpath)
273
- addNodeCandidatesFromPath(process.env.PATH, addCandidate)
274
- addNodeCandidatesFromNvm(os.homedir(), addCandidate)
275
- addCandidate('/opt/homebrew/bin/node')
276
- addCandidate('/usr/local/bin/node')
277
- addCandidate('/usr/bin/node')
278
- addCandidate(process.execPath)
279
-
280
- for (const candidate of candidates) {
281
- if (nodeRuntimeSupportsWebSocket(candidate)) return candidate
282
- }
283
- return null
284
- }
285
-
286
- function addNodeCandidatesFromPath(pathValue: string | undefined, addCandidate: (candidate?: string | null) => void) {
287
- for (const entry of (pathValue || '').split(':')) {
288
- const dir = entry.trim()
289
- if (!dir) continue
290
- addCandidate(join(dir, 'node'))
291
- }
292
- }
293
-
294
- function addNodeCandidatesFromNvm(homeDir: string, addCandidate: (candidate?: string | null) => void) {
295
- const versionsDir = join(homeDir, '.nvm', 'versions', 'node')
296
- try {
297
- const versionDirs = readdirSync(versionsDir, { withFileTypes: true })
298
- .filter((entry) => entry.isDirectory())
299
- .map((entry) => entry.name)
300
- .sort(compareNodeVersionNamesDescending)
301
- for (const versionDir of versionDirs) {
302
- addCandidate(join(versionsDir, versionDir, 'bin', 'node'))
303
- }
304
- } catch {
305
- // nvm is optional; PATH and process.execPath candidates still cover normal installs.
306
- }
307
- }
308
-
309
- function compareNodeVersionNamesDescending(a: string, b: string): number {
310
- const parse = (value: string) => value.replace(/^v/, '').split('.').map((part) => Number.parseInt(part, 10) || 0)
311
- const left = parse(a)
312
- const right = parse(b)
313
- for (let i = 0; i < Math.max(left.length, right.length); i++) {
314
- const diff = (right[i] || 0) - (left[i] || 0)
315
- if (diff !== 0) return diff
316
- }
317
- return b.localeCompare(a)
318
- }
319
-
320
- function nodeRuntimeSupportsWebSocket(nodeExecutable: string): boolean {
321
- try {
322
- execFileSync(nodeExecutable, ['-e', "process.exit(typeof WebSocket === 'function' ? 0 : 42)"], {
323
- stdio: 'ignore',
324
- timeout: 3000,
325
- })
326
- return true
327
- } catch {
328
- return false
329
- }
330
- }
331
-
332
- function resolveAdhdevMcpEntryPath(explicitPath?: string): string | null {
333
- const explicit = explicitPath?.trim()
334
- if (explicit) return normalizeExistingPath(explicit) || explicit
335
-
336
- const envPath = process.env.ADHDEV_MCP_SERVER_PATH?.trim()
337
- if (envPath) return normalizeExistingPath(envPath) || envPath
338
-
339
- const candidates: string[] = []
340
- const addCandidate = (candidate: string) => {
341
- if (!candidates.includes(candidate)) candidates.push(candidate)
342
- }
343
- const addPackagedCandidates = (baseFile?: string) => {
344
- if (!baseFile) return
345
- const realBase = normalizeExistingPath(baseFile) || baseFile
346
- const dir = dirname(realBase)
347
- addCandidate(resolve(dir, '../vendor/mcp-server/index.js'))
348
- addCandidate(resolve(dir, '../../vendor/mcp-server/index.js'))
349
- addCandidate(resolve(dir, '../../../vendor/mcp-server/index.js'))
350
- // Source checkout/dev mode does not vendor the MCP server into daemon-standalone.
351
- // Resolve the sibling workspace build directly so Repo Mesh auto-import still
352
- // writes an absolute Node entrypoint instead of falling back to a PATH bin shim.
353
- addCandidate(resolve(dir, '../../mcp-server/dist/index.js'))
354
- addCandidate(resolve(dir, '../../../mcp-server/dist/index.js'))
355
- }
356
-
357
- addPackagedCandidates(process.argv[1])
358
-
359
- for (const candidate of candidates) {
360
- const normalized = normalizeExistingPath(candidate)
361
- if (normalized) return normalized
362
- }
363
-
364
- try {
365
- const requireBase = process.argv[1] ? (normalizeExistingPath(process.argv[1]) || process.argv[1]) : join(process.cwd(), 'adhdev-daemon.js')
366
- const req = createRequire(requireBase)
367
- const resolvedModule = req.resolve('@adhdev/mcp-server')
368
- return normalizeExistingPath(resolvedModule) || resolvedModule
369
- } catch {
370
- return null
371
- }
372
- }
373
-
374
- function normalizeExistingPath(filePath: string): string | null {
375
- try {
376
- if (!existsSync(filePath)) return null
377
- return realpathSync.native(filePath)
378
- } catch {
379
- return null
380
- }
381
- }