@adhdev/daemon-core 0.9.82-rc.2 → 0.9.82-rc.200

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 (244) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +3 -0
  2. package/dist/boot/process-hardening.d.ts +50 -0
  3. package/dist/chat/source-machine.d.ts +166 -0
  4. package/dist/chat/source-resolver.d.ts +104 -0
  5. package/dist/chat/subscription-updates.d.ts +1 -0
  6. package/dist/cli-adapter-types.d.ts +23 -2
  7. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  8. package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
  9. package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
  10. package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
  11. package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
  12. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  13. package/dist/commands/cli-manager.d.ts +2 -1
  14. package/dist/commands/handler.d.ts +110 -0
  15. package/dist/commands/mesh-coordinator.d.ts +85 -1
  16. package/dist/commands/router.d.ts +29 -1
  17. package/dist/config/chat-history.d.ts +9 -0
  18. package/dist/config/config.d.ts +5 -0
  19. package/dist/config/mesh-config.d.ts +72 -1
  20. package/dist/git/git-commands.d.ts +8 -1
  21. package/dist/git/git-status.d.ts +5 -0
  22. package/dist/git/git-types.d.ts +12 -0
  23. package/dist/index.d.ts +39 -10
  24. package/dist/index.js +29425 -12026
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +25700 -8378
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/installer.d.ts +1 -4
  29. package/dist/ipc/local-ipc-server.d.ts +91 -0
  30. package/dist/launch.d.ts +1 -1
  31. package/dist/logging/async-batch-writer.d.ts +10 -0
  32. package/dist/mesh/contracts.d.ts +164 -0
  33. package/dist/mesh/coordinator-prompt.d.ts +30 -0
  34. package/dist/mesh/coordinator-registry.d.ts +59 -0
  35. package/dist/mesh/mesh-active-work.d.ts +90 -0
  36. package/dist/mesh/mesh-delivery-policy.d.ts +126 -0
  37. package/dist/mesh/mesh-events.d.ts +167 -6
  38. package/dist/mesh/mesh-fast-forward.d.ts +41 -0
  39. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  40. package/dist/mesh/mesh-ledger.d.ts +58 -1
  41. package/dist/mesh/mesh-refine-status.d.ts +26 -0
  42. package/dist/mesh/mesh-runtime-store.d.ts +149 -0
  43. package/dist/mesh/mesh-work-queue.d.ts +55 -6
  44. package/dist/mesh/preview-freshness.d.ts +18 -0
  45. package/dist/mesh/refine-config.d.ts +193 -0
  46. package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
  47. package/dist/providers/approval-utils.d.ts +13 -0
  48. package/dist/providers/chat-message-normalization.d.ts +1 -0
  49. package/dist/providers/cli-provider-instance.d.ts +39 -3
  50. package/dist/providers/contracts.d.ts +130 -6
  51. package/dist/providers/external-sources.d.ts +71 -0
  52. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  53. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  54. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  55. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  56. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  57. package/dist/providers/native-history/index.d.ts +13 -0
  58. package/dist/providers/provider-instance-manager.d.ts +12 -0
  59. package/dist/providers/provider-instance.d.ts +13 -1
  60. package/dist/providers/provider-loader.d.ts +26 -4
  61. package/dist/providers/provider-trust.d.ts +31 -0
  62. package/dist/providers/read-chat-contract.d.ts +29 -0
  63. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  64. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  65. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  66. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  67. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  68. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  69. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  70. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  71. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  72. package/dist/providers/sdk/v1/index.d.ts +30 -0
  73. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  74. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  75. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  76. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  77. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  78. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  79. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  80. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  81. package/dist/providers/spec/adapter.d.ts +60 -0
  82. package/dist/providers/spec/cli-adapter.d.ts +98 -0
  83. package/dist/providers/spec/driver.d.ts +190 -0
  84. package/dist/providers/spec/evaluator.d.ts +55 -0
  85. package/dist/providers/spec/loader.d.ts +14 -0
  86. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  87. package/dist/providers/spec/route.d.ts +4 -0
  88. package/dist/providers/spec/schema.gen.d.ts +599 -0
  89. package/dist/providers/spec/types.d.ts +282 -0
  90. package/dist/providers/transcript-v2.d.ts +176 -0
  91. package/dist/providers/types/interactive-prompt.d.ts +48 -0
  92. package/dist/repo-mesh-types.d.ts +230 -1
  93. package/dist/sessions/registry.d.ts +3 -0
  94. package/dist/shared-types-extra.d.ts +1 -1
  95. package/dist/shared-types.d.ts +42 -1
  96. package/dist/status/normalize.d.ts +1 -1
  97. package/dist/status/normalize.js +1 -0
  98. package/dist/status/normalize.js.map +1 -1
  99. package/dist/status/normalize.mjs +1 -0
  100. package/dist/status/normalize.mjs.map +1 -1
  101. package/dist/status/reporter.d.ts +2 -0
  102. package/dist/status/snapshot.d.ts +1 -0
  103. package/dist/types.d.ts +5 -0
  104. package/package.json +7 -2
  105. package/src/agent-stream/poller.ts +2 -3
  106. package/src/boot/daemon-lifecycle.ts +22 -10
  107. package/src/boot/process-hardening.ts +89 -0
  108. package/src/chat/source-machine.ts +534 -0
  109. package/src/chat/source-resolver.ts +0 -0
  110. package/src/chat/subscription-updates.ts +20 -1
  111. package/src/cli-adapter-types.d.ts +1 -0
  112. package/src/cli-adapter-types.ts +22 -2
  113. package/src/cli-adapters/cli-script-runner.ts +421 -0
  114. package/src/cli-adapters/cli-state-engine.ts +1138 -0
  115. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  116. package/src/cli-adapters/provider-cli-adapter.ts +670 -1137
  117. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  118. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  119. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  120. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  121. package/src/cli-adapters/provider-cli-shared.ts +59 -11
  122. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  123. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
  124. package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
  125. package/src/commands/chat-commands.ts +1787 -60
  126. package/src/commands/cli-manager.ts +283 -14
  127. package/src/commands/handler.ts +809 -2
  128. package/src/commands/mesh-coordinator.ts +331 -122
  129. package/src/commands/router.ts +3915 -546
  130. package/src/config/chat-history.ts +95 -24
  131. package/src/config/config.ts +12 -0
  132. package/src/config/mesh-config.ts +280 -2
  133. package/src/config/recent-activity.ts +8 -2
  134. package/src/daemon/dev-cli-debug.ts +10 -1
  135. package/src/detection/ide-detector.ts +26 -16
  136. package/src/git/git-commands.ts +38 -8
  137. package/src/git/git-status.ts +132 -12
  138. package/src/git/git-summary.ts +3 -0
  139. package/src/git/git-types.ts +13 -0
  140. package/src/git/git-worktree.ts +8 -1
  141. package/src/index.ts +127 -9
  142. package/src/installer.d.ts +1 -1
  143. package/src/installer.ts +8 -6
  144. package/src/ipc/local-ipc-server.ts +278 -0
  145. package/src/launch.d.ts +1 -1
  146. package/src/launch.ts +37 -28
  147. package/src/logging/async-batch-writer.ts +55 -0
  148. package/src/logging/logger.ts +2 -1
  149. package/src/mesh/contracts.ts +329 -0
  150. package/src/mesh/coordinator-prompt.ts +204 -30
  151. package/src/mesh/coordinator-registry.ts +121 -0
  152. package/src/mesh/mesh-active-work.ts +437 -0
  153. package/src/mesh/mesh-delivery-policy.ts +298 -0
  154. package/src/mesh/mesh-events.ts +1352 -109
  155. package/src/mesh/mesh-fast-forward.ts +438 -0
  156. package/src/mesh/mesh-host-ownership.ts +73 -0
  157. package/src/mesh/mesh-ledger.ts +457 -104
  158. package/src/mesh/mesh-refine-status.ts +144 -0
  159. package/src/mesh/mesh-runtime-store.ts +769 -0
  160. package/src/mesh/mesh-work-queue.ts +267 -159
  161. package/src/mesh/preview-freshness.ts +118 -0
  162. package/src/mesh/refine-config.ts +366 -0
  163. package/src/mesh/worktree-bootstrap-config.ts +247 -0
  164. package/src/providers/approval-utils.d.ts +4 -0
  165. package/src/providers/approval-utils.ts +47 -5
  166. package/src/providers/chat-message-normalization.ts +7 -12
  167. package/src/providers/cli-provider-instance.ts +806 -64
  168. package/src/providers/contracts.d.ts +55 -0
  169. package/src/providers/contracts.ts +141 -6
  170. package/src/providers/external-sources.ts +218 -0
  171. package/src/providers/ide-provider-instance.ts +19 -5
  172. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  173. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  174. package/src/providers/native-history/codex-cli-transcript.ts +419 -0
  175. package/src/providers/native-history/dispatcher.ts +340 -0
  176. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  177. package/src/providers/native-history/index.ts +30 -0
  178. package/src/providers/provider-instance-manager.ts +30 -0
  179. package/src/providers/provider-instance.ts +10 -1
  180. package/src/providers/provider-loader.ts +582 -50
  181. package/src/providers/provider-schema.ts +87 -14
  182. package/src/providers/provider-trust.ts +114 -0
  183. package/src/providers/read-chat-contract.ts +76 -16
  184. package/src/providers/sdk/README.md +49 -0
  185. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  186. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  187. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  188. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  189. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  190. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  191. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  192. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  193. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  194. package/src/providers/sdk/v1/index.ts +152 -0
  195. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  196. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  197. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  198. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  199. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  200. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  201. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  202. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  203. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  204. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  205. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  206. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  207. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  208. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  209. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  210. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  211. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  212. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  213. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  214. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  215. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  216. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  217. package/src/providers/sdk/v1/validators/index.ts +19 -0
  218. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  219. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  220. package/src/providers/spec/adapter.ts +176 -0
  221. package/src/providers/spec/cli-adapter.ts +538 -0
  222. package/src/providers/spec/driver.ts +725 -0
  223. package/src/providers/spec/evaluator.ts +373 -0
  224. package/src/providers/spec/loader.ts +130 -0
  225. package/src/providers/spec/native-history-executor.ts +939 -0
  226. package/src/providers/spec/route.ts +51 -0
  227. package/src/providers/spec/schema.gen.ts +559 -0
  228. package/src/providers/spec/schema.json +237 -0
  229. package/src/providers/spec/types.ts +301 -0
  230. package/src/providers/transcript-v2.ts +567 -0
  231. package/src/providers/types/interactive-prompt.ts +425 -0
  232. package/src/providers/version-archive.ts +38 -20
  233. package/src/repo-mesh-types.ts +250 -1
  234. package/src/sessions/registry.ts +3 -0
  235. package/src/shared-types-extra.ts +1 -1
  236. package/src/shared-types.ts +45 -1
  237. package/src/status/builders.ts +24 -6
  238. package/src/status/normalize.ts +2 -0
  239. package/src/status/reporter.ts +15 -0
  240. package/src/status/snapshot.ts +84 -25
  241. package/src/system/host-memory.ts +29 -12
  242. package/src/types.ts +5 -0
  243. package/dist/mesh/mesh-sync.d.ts +0 -53
  244. package/src/mesh/mesh-sync.ts +0 -111
@@ -1,10 +1,13 @@
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'
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
5
3
  import * as os from 'node:os'
6
- import { dirname, isAbsolute, join, resolve } from 'node:path'
7
- import type { ProviderModule, MeshCoordinatorMcpConfigFormat } from '../providers/contracts.js'
4
+ import { basename, isAbsolute, join, resolve } from 'node:path'
5
+ import { LOG } from '../logging/logger.js'
6
+ import type {
7
+ MeshCoordinatorMcpConfigFormat,
8
+ MeshCoordinatorSystemPromptInjection,
9
+ ProviderModule,
10
+ } from '../providers/contracts.js'
8
11
 
9
12
  export interface MeshCoordinatorMcpServerLaunch {
10
13
  command: string
@@ -36,6 +39,7 @@ export type MeshCoordinatorSetup =
36
39
  command: string
37
40
  requiresRestart: boolean
38
41
  instructions: string
42
+ mcpServer: MeshCoordinatorMcpServerLaunch
39
43
  }
40
44
  | {
41
45
  kind: 'unsupported'
@@ -55,7 +59,7 @@ export interface ResolveMeshCoordinatorSetupOptions {
55
59
  }
56
60
 
57
61
  const DEFAULT_SERVER_NAME = 'adhdev-mesh'
58
- const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev-mcp'
62
+ const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev'
59
63
  const HERMES_CLI_TYPE = 'hermes-cli'
60
64
  const HERMES_MCP_CONFIG_PATH = '~/.hermes/config.yaml'
61
65
 
@@ -67,8 +71,9 @@ function isHermesProvider(provider: ProviderModule | null | undefined, cliType?:
67
71
  function resolveHermesMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupOptions): MeshCoordinatorSetup {
68
72
  const mcpServer = resolveAdhdevMcpServerLaunch({
69
73
  meshId: options.meshId,
70
- nodeExecutable: options.nodeExecutable,
74
+ adhdevMcpCommand: options.adhdevMcpCommand,
71
75
  adhdevMcpEntryPath: options.adhdevMcpEntryPath,
76
+ nodeExecutable: options.nodeExecutable,
72
77
  adhdevMcpTransport: options.adhdevMcpTransport,
73
78
  adhdevMcpPort: options.adhdevMcpPort,
74
79
  })
@@ -100,7 +105,7 @@ export function createHermesManualMeshCoordinatorSetup(meshId: string, workspace
100
105
  requiresRestart: true,
101
106
  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
107
  template: renderMeshCoordinatorTemplate(
103
- 'mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - --repo-mesh\n - {{meshId}}\n enabled: true\n',
108
+ '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
109
  {
105
110
  meshId,
106
111
  workspace,
@@ -141,8 +146,9 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
141
146
  }
142
147
  const mcpServer = resolveAdhdevMcpServerLaunch({
143
148
  meshId,
144
- nodeExecutable: options.nodeExecutable,
149
+ adhdevMcpCommand: options.adhdevMcpCommand,
145
150
  adhdevMcpEntryPath: options.adhdevMcpEntryPath,
151
+ nodeExecutable: options.nodeExecutable,
146
152
  adhdevMcpTransport: options.adhdevMcpTransport,
147
153
  adhdevMcpPort: options.adhdevMcpPort,
148
154
  })
@@ -167,22 +173,41 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
167
173
  if (!instructions || !template?.trim()) {
168
174
  return { kind: 'unsupported', reason: 'Provider manual MCP setup is missing instructions or template' }
169
175
  }
170
- const renderedTemplate = renderMeshCoordinatorTemplate(template, {
176
+ const mcpServer = resolveAdhdevMcpServerLaunch({
177
+ meshId,
178
+ adhdevMcpCommand: options.adhdevMcpCommand,
179
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
180
+ nodeExecutable: options.nodeExecutable,
181
+ adhdevMcpTransport: options.adhdevMcpTransport,
182
+ adhdevMcpPort: options.adhdevMcpPort,
183
+ })
184
+ if (!mcpServer) {
185
+ return {
186
+ kind: 'unsupported',
187
+ reason: 'Could not resolve the ADHDev MCP server entrypoint and transport arguments',
188
+ }
189
+ }
190
+ let renderedTemplate = renderMeshCoordinatorTemplate(template, {
171
191
  meshId,
172
192
  workspace,
173
193
  serverName,
174
- adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND,
194
+ adhdevMcpCommand: mcpServer.command,
195
+ adhdevMcpArgs: mcpServer.args.join(' '),
175
196
  })
176
197
  // Detect if the template is a runnable CLI command (single line, no YAML/JSON structure).
177
198
  // If so, use cli_command kind so the daemon can execute it automatically.
178
199
  const isCliCommand = !renderedTemplate.trim().includes('\n') && !renderedTemplate.trim().startsWith('{')
179
200
  if (isCliCommand) {
201
+ if (!/\{\{\s*adhdevMcpArgs\s*\}\}/.test(template)) {
202
+ renderedTemplate = replaceLegacyCliCommandMcpArgs(renderedTemplate, mcpServer.args)
203
+ }
180
204
  return {
181
205
  kind: 'cli_command',
182
206
  serverName,
183
207
  command: renderedTemplate.trim(),
184
208
  requiresRestart: mcpConfig.requiresRestart === true,
185
209
  instructions: instructions,
210
+ mcpServer,
186
211
  }
187
212
  }
188
213
  return {
@@ -203,7 +228,14 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
203
228
  }
204
229
 
205
230
  function renderMeshCoordinatorTemplate(template: string, values: Record<string, string>): string {
206
- return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand)\s*\}\}/g, (_, key: string) => values[key] || '')
231
+ return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand|adhdevMcpArgs)\s*\}\}/g, (_, key: string) => values[key] || '')
232
+ }
233
+
234
+ function replaceLegacyCliCommandMcpArgs(command: string, args: string[]): string {
235
+ return command.replace(
236
+ /\bmcp\s+--mode\s+(?:ipc|local)\s+--repo-mesh\s+\S+(?:\s+--port\s+\d+)?\s*$/,
237
+ args.join(' '),
238
+ )
207
239
  }
208
240
 
209
241
  function resolveHermesCoordinatorHome(meshId: string, workspace: string): string {
@@ -222,25 +254,53 @@ function resolveMcpConfigPath(configPath: string, workspace: string): string {
222
254
 
223
255
  function resolveAdhdevMcpServerLaunch(options: {
224
256
  meshId: string
225
- nodeExecutable?: string
257
+ adhdevMcpCommand?: string
226
258
  adhdevMcpEntryPath?: string
259
+ nodeExecutable?: string
227
260
  adhdevMcpTransport?: 'local' | 'ipc'
228
261
  adhdevMcpPort?: number
229
262
  }): MeshCoordinatorMcpServerLaunch | null {
230
- const entryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
231
- if (!entryPath) return null
232
- const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable)
233
- if (!nodeExecutable) return null
263
+ const directEntryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
264
+ if (directEntryPath) {
265
+ const transport = resolveMcpTransport(options.adhdevMcpTransport)
266
+ const args = [directEntryPath, '--mode', transport, '--repo-mesh', options.meshId]
267
+ const port = resolveMcpPort(options.adhdevMcpPort)
268
+ if (port !== undefined) args.push('--port', String(port))
269
+ return {
270
+ command: resolveNodeExecutable(options.nodeExecutable),
271
+ args,
272
+ }
273
+ }
274
+
275
+ const command = resolveAdhdevCommand(options.adhdevMcpCommand)
234
276
  const transport = resolveMcpTransport(options.adhdevMcpTransport)
235
- const args = [entryPath, '--mode', transport, '--repo-mesh', options.meshId]
277
+ const directMcpEntrypoint = basename(command).startsWith('adhdev-mcp')
278
+ || command.includes('/vendor/mcp-server/')
279
+ || command.includes('\\vendor\\mcp-server\\')
280
+ const args = [...(directMcpEntrypoint ? [] : ['mcp']), '--mode', transport, '--repo-mesh', options.meshId]
236
281
  const port = resolveMcpPort(options.adhdevMcpPort)
237
282
  if (port !== undefined) args.push('--port', String(port))
238
283
  return {
239
- command: nodeExecutable,
284
+ command,
240
285
  args,
241
286
  }
242
287
  }
243
288
 
289
+ function resolveAdhdevCommand(explicitCommand?: string): string {
290
+ return explicitCommand?.trim() || process.env.ADHDEV_COORDINATOR_MCP_COMMAND?.trim() || DEFAULT_ADHDEV_MCP_COMMAND
291
+ }
292
+
293
+ function resolveAdhdevMcpEntryPath(explicitEntryPath?: string): string | null {
294
+ const entryPath = explicitEntryPath?.trim() || process.env.ADHDEV_COORDINATOR_MCP_ENTRY_PATH?.trim()
295
+ return entryPath || null
296
+ }
297
+
298
+ function resolveNodeExecutable(explicitNodeExecutable?: string): string {
299
+ return explicitNodeExecutable?.trim()
300
+ || process.env.ADHDEV_COORDINATOR_NODE_EXECUTABLE?.trim()
301
+ || process.execPath
302
+ }
303
+
244
304
  function resolveMcpTransport(explicitTransport?: 'local' | 'ipc'): 'local' | 'ipc' {
245
305
  if (explicitTransport === 'local' || explicitTransport === 'ipc') return explicitTransport
246
306
  const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim()
@@ -255,127 +315,276 @@ function resolveMcpPort(explicitPort?: number): number | undefined {
255
315
  return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined
256
316
  }
257
317
 
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
- }
318
+ /**
319
+ * Apply a provider's declared system-prompt injection rule, mutating `cliArgs`
320
+ * and `launchEnv` in place and writing any required workspace context file.
321
+ *
322
+ * Replaces the previous hard-coded `if (cliType === 'claude-cli') ... else if
323
+ * (cliType === 'hermes-cli') ...` branches in router.ts. Adding a new CLI is
324
+ * now provider.v1.json data, not a router edit.
325
+ *
326
+ * Failures are non-fatal: log and continue with whatever was applied so the
327
+ * coordinator session still launches, just without the prompt for that
328
+ * provider. A missing/unknown rule means "skip injection" — safe by default
329
+ * (the previous fallback unconditionally pushed --append-system-prompt onto
330
+ * every non-Claude CLI, which crashed agy on launch).
331
+ */
332
+ export interface CoordinatorInjectionEffect {
333
+ /** Absolute path the daemon wrote a wrapper-blocked file to. Only set for
334
+ * context_file injection. R48 schedules a strip after launch so workers
335
+ * don't see the wrapper on disk; R47's unregister cleanup uses it as a
336
+ * fallback if the timer never fires (process crash, etc). */
337
+ contextFilePath?: string
338
+ }
269
339
 
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
340
+ export function applyMeshCoordinatorSystemPromptInjection(
341
+ systemPrompt: string,
342
+ injection: MeshCoordinatorSystemPromptInjection | undefined,
343
+ ctx: { cliArgs: string[]; launchEnv: Record<string, string>; workspace: string; cliType: string },
344
+ ): CoordinatorInjectionEffect {
345
+ if (!systemPrompt || !injection) return {}
346
+ return applyInjectionRule(systemPrompt, injection, ctx)
284
347
  }
285
348
 
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'))
349
+ function applyInjectionRule(
350
+ systemPrompt: string,
351
+ injection: MeshCoordinatorSystemPromptInjection,
352
+ ctx: { cliArgs: string[]; launchEnv: Record<string, string>; workspace: string; cliType: string },
353
+ ): CoordinatorInjectionEffect {
354
+ switch (injection.mode) {
355
+ case 'cli_arg': {
356
+ if (!injection.flag) return {}
357
+ ctx.cliArgs.push(injection.flag, systemPrompt)
358
+ return {}
359
+ }
360
+ case 'config_override': {
361
+ if (!injection.flag || !injection.template) return {}
362
+ const rendered = injection.template
363
+ .replace(/\{prompt_json\}/g, JSON.stringify(systemPrompt))
364
+ .replace(/\{prompt\}/g, systemPrompt)
365
+ ctx.cliArgs.push(injection.flag, rendered)
366
+ return {}
367
+ }
368
+ case 'env_var': {
369
+ if (!injection.name) return {}
370
+ ctx.launchEnv[injection.name] = systemPrompt
371
+ return {}
372
+ }
373
+ case 'context_file': {
374
+ if (!injection.path) return {}
375
+ const target = isAbsolute(injection.path)
376
+ ? injection.path
377
+ : join(ctx.workspace, injection.path)
378
+ const wrapper = injection.wrapper && injection.wrapper.includes('{prompt}')
379
+ ? injection.wrapper
380
+ : '{prompt}'
381
+ // Prepend a short managed-by hint inside the wrapper block so a user
382
+ // opening AGENTS.md / GEMINI.md immediately understands the block is
383
+ // auto-regenerated by the daemon on every coordinator launch. The hint
384
+ // sits between the opening sentinel and the prompt body, so the stable
385
+ // sentinels declared in provider.v1.json are untouched and the
386
+ // idempotent replace regex above still matches on relaunches.
387
+ const managedNote =
388
+ '> _Managed by adhdev mesh coordinator — do not hand-edit this block. ' +
389
+ 'Changes inside the sentinels are overwritten on next coordinator launch._'
390
+ const promptWithNote = `${managedNote}\n\n${systemPrompt}`
391
+ const rendered = wrapper.replace(/\{prompt\}/g, promptWithNote)
392
+ // If the wrapper has a stable opening sentinel, treat everything up to
393
+ // the matching closing sentinel as our previously-written block and
394
+ // replace it. Otherwise just append. The marker is the first non-
395
+ // placeholder line of the wrapper; this keeps relaunches idempotent
396
+ // without forcing spec authors to declare an explicit marker.
397
+ const sentinel = wrapper.split('{prompt}')[0].trim()
398
+ try {
399
+ if (existsSync(target)) {
400
+ const existing = readFileSync(target, 'utf-8')
401
+ if (sentinel && existing.includes(sentinel)) {
402
+ const closing = wrapper.split('{prompt}')[1]?.trim()
403
+ const safeOpen = sentinel.replace(/[.+^${}()|[\]\\]/g, '\\$&')
404
+ const safeClose = closing
405
+ ? closing.replace(/[.+^${}()|[\]\\]/g, '\\$&')
406
+ : ''
407
+ const re = closing
408
+ ? new RegExp(`${safeOpen}[\\s\\S]*?${safeClose}`, 'g')
409
+ : new RegExp(`${safeOpen}[\\s\\S]*$`, 'g')
410
+ writeFileSync(target, existing.replace(re, rendered), 'utf-8')
411
+ } else {
412
+ writeFileSync(target, `${existing}\n\n${rendered}`, 'utf-8')
413
+ }
414
+ } else {
415
+ writeFileSync(target, rendered, 'utf-8')
416
+ }
417
+ LOG.info('MeshCoordinator', `Wrote coordinator prompt to ${target} (${ctx.cliType})`)
418
+ return { contextFilePath: target }
419
+ } catch (error: any) {
420
+ LOG.warn('MeshCoordinator', `Could not write ${target}: ${error?.message || error}`)
421
+ return {}
422
+ }
423
+ }
424
+ default:
425
+ // Unknown future mode — skip silently. Adding the new mode is a spec-
426
+ // language extension, not a runtime crash.
427
+ return {}
291
428
  }
292
429
  }
293
430
 
294
- function addNodeCandidatesFromNvm(homeDir: string, addCandidate: (candidate?: string | null) => void) {
295
- const versionsDir = join(homeDir, '.nvm', 'versions', 'node')
431
+ /**
432
+ * Strip the daemon's wrapper block from a context_file we previously wrote.
433
+ *
434
+ * Used in two places:
435
+ * 1. R48 inject-then-remove: ~5s after spawn, after agy/gemini have read
436
+ * the file into their in-memory system-prompt cache. Removing it from
437
+ * disk at that point doesn't affect the running coordinator but keeps
438
+ * worker sessions (or fresh non-coordinator launches) in the same
439
+ * workspace from picking up our wrapper.
440
+ * 2. R47 unregister fallback: if the timer never fires (process crash,
441
+ * kill -9), coordinator-registry.unregisterMeshCoordinator runs the
442
+ * same logic when its entry is dropped.
443
+ *
444
+ * Idempotent: missing file is fine, missing sentinels are fine, returns
445
+ * silently. Leaves user-authored content outside the sentinels intact.
446
+ * Deletes the file outright if our wrapper was the only content.
447
+ */
448
+ export function stripCoordinatorWrapperFile(filePath: string): void {
449
+ const OPEN = '<!-- adhdev-mesh-coordinator-prompt -->'
450
+ const CLOSE = '<!-- /adhdev-mesh-coordinator-prompt -->'
296
451
  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'))
452
+ if (!existsSync(filePath)) return
453
+ const existing = readFileSync(filePath, 'utf-8')
454
+ const openIdx = existing.indexOf(OPEN)
455
+ if (openIdx < 0) return
456
+ const closeIdx = existing.indexOf(CLOSE, openIdx)
457
+ if (closeIdx < 0) return
458
+ const remaining = (existing.slice(0, openIdx) + existing.slice(closeIdx + CLOSE.length))
459
+ .replace(/^\s*\n+/, '')
460
+ .replace(/\n+\s*$/, '')
461
+ if (!remaining.trim()) {
462
+ try {
463
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
464
+ const fs = require('node:fs')
465
+ fs.unlinkSync(filePath)
466
+ } catch { /* best-effort */ }
467
+ } else {
468
+ writeFileSync(filePath, remaining + '\n', 'utf-8')
303
469
  }
304
- } catch {
305
- // nvm is optional; PATH and process.execPath candidates still cover normal installs.
306
- }
470
+ } catch { /* best-effort */ }
307
471
  }
308
472
 
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)
473
+ export interface PtyExecResult {
474
+ exitCode: number | null
475
+ signal: number | null
476
+ output: string
477
+ timedOut: boolean
318
478
  }
319
479
 
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
- }
480
+ export interface MeshCoordinatorRegistrationStep {
481
+ command: string
482
+ args: string[]
483
+ required: boolean
484
+ label: 'remove_existing' | 'register'
330
485
  }
331
486
 
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
487
+ /**
488
+ * Codex rejects `mcp add` when a server with the same name already exists.
489
+ * Replace that entry before registering so transport/mesh changes do not leave
490
+ * a fresh coordinator attached to stale MCP launch arguments.
491
+ */
492
+ export function buildMeshCoordinatorRegistrationPlan(
493
+ cliType: string,
494
+ serverName: string,
495
+ registrationCommand: string,
496
+ ): MeshCoordinatorRegistrationStep[] {
497
+ const commandParts = registrationCommand.trim().split(/\s+/).filter(Boolean)
498
+ const [command, ...args] = commandParts
499
+ if (!command) return []
500
+
501
+ const register: MeshCoordinatorRegistrationStep = {
502
+ command,
503
+ args,
504
+ required: true,
505
+ label: 'register',
362
506
  }
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
507
+ if (
508
+ cliType === 'codex-cli'
509
+ && basename(command) === 'codex'
510
+ && args[0] === 'mcp'
511
+ && args[1] === 'add'
512
+ ) {
513
+ return [
514
+ {
515
+ command,
516
+ args: ['mcp', 'remove', serverName],
517
+ required: false,
518
+ label: 'remove_existing',
519
+ },
520
+ register,
521
+ ]
371
522
  }
523
+ return [register]
372
524
  }
373
525
 
374
- function normalizeExistingPath(filePath: string): string | null {
526
+ /**
527
+ * Run a one-shot CLI command under a real PTY and collect its output.
528
+ *
529
+ * Some provider mcp-registration commands (`agy mcp add`, future bubbletea
530
+ * TUIs) refuse to run without `/dev/tty`. Daemon-side `execFileSync` runs
531
+ * pipe-only, so those commands fail with errors like
532
+ * `bubbletea: error opening TTY: open /dev/tty: device not configured`
533
+ * and silently skip the registration, leaving the launched session
534
+ * without the adhdev-mesh MCP tools — which is exactly what we saw with
535
+ * agy coordinators.
536
+ *
537
+ * Wrapping the registration through node-pty gives the child a real PTY,
538
+ * so the bubbletea check passes. We close stdin immediately and just
539
+ * collect stdout until the process exits or the timeout fires.
540
+ */
541
+ export async function execUnderPty(
542
+ command: string,
543
+ args: string[],
544
+ options: { cwd?: string; env?: Record<string, string>; timeoutMs?: number } = {},
545
+ ): Promise<PtyExecResult> {
546
+ let ptyLib: any
375
547
  try {
376
- if (!existsSync(filePath)) return null
377
- return realpathSync.native(filePath)
378
- } catch {
379
- return null
548
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
549
+ ptyLib = require('node-pty')
550
+ } catch (error: any) {
551
+ throw new Error(`node-pty is not available: ${error?.message || error}`)
380
552
  }
553
+ const env = { ...(options.env ?? (process.env as Record<string, string>)), TERM: 'xterm-256color' }
554
+ const timeoutMs = typeof options.timeoutMs === 'number' && options.timeoutMs > 0 ? options.timeoutMs : 20_000
555
+ return new Promise<PtyExecResult>((resolveResult) => {
556
+ let child: any
557
+ try {
558
+ child = ptyLib.spawn(command, args, {
559
+ name: 'xterm-256color',
560
+ cols: 120,
561
+ rows: 30,
562
+ cwd: options.cwd ?? process.cwd(),
563
+ env,
564
+ })
565
+ } catch (error: any) {
566
+ resolveResult({ exitCode: null, signal: null, output: String(error?.message || error), timedOut: false })
567
+ return
568
+ }
569
+ let buffer = ''
570
+ let settled = false
571
+ const timer = setTimeout(() => {
572
+ if (settled) return
573
+ settled = true
574
+ try { child.kill() } catch { /* ignore */ }
575
+ resolveResult({ exitCode: null, signal: null, output: buffer, timedOut: true })
576
+ }, timeoutMs)
577
+ child.onData((chunk: string) => {
578
+ buffer += chunk
579
+ if (buffer.length > 256 * 1024) {
580
+ buffer = buffer.slice(-128 * 1024)
581
+ }
582
+ })
583
+ child.onExit(({ exitCode, signal }: { exitCode: number; signal?: number }) => {
584
+ if (settled) return
585
+ settled = true
586
+ clearTimeout(timer)
587
+ resolveResult({ exitCode, signal: signal ?? null, output: buffer, timedOut: false })
588
+ })
589
+ })
381
590
  }