@adhdev/daemon-core 0.9.82-rc.16 → 0.9.82-rc.161

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