@adhdev/daemon-core 0.9.82-rc.32 → 0.9.82-rc.321

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 (306) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +8 -0
  2. package/dist/boot/process-hardening.d.ts +50 -0
  3. package/dist/build-info.d.ts +37 -0
  4. package/dist/chat/source-machine.d.ts +166 -0
  5. package/dist/chat/source-resolver.d.ts +104 -0
  6. package/dist/chat/subscription-updates.d.ts +1 -0
  7. package/dist/cli-adapter-types.d.ts +46 -2
  8. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  9. package/dist/cli-adapters/cli-state-engine.d.ts +198 -0
  10. package/dist/cli-adapters/provider-cli-adapter.d.ts +115 -64
  11. package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
  12. package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
  13. package/dist/cli-adapters/provider-cli-shared.d.ts +38 -0
  14. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  15. package/dist/cli-adapters/resolve-executable.d.ts +14 -0
  16. package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
  17. package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
  18. package/dist/cli-adapters/terminal-screen.d.ts +4 -11
  19. package/dist/commands/cli-manager.d.ts +2 -1
  20. package/dist/commands/handler.d.ts +110 -0
  21. package/dist/commands/mesh-coordinator.d.ts +85 -1
  22. package/dist/commands/router.d.ts +229 -1
  23. package/dist/commands/upgrade-helper.d.ts +12 -0
  24. package/dist/config/chat-history.d.ts +9 -0
  25. package/dist/config/config.d.ts +5 -0
  26. package/dist/config/mesh-config.d.ts +72 -1
  27. package/dist/detection/ide-detector.d.ts +13 -0
  28. package/dist/detection/win32-ide-version.d.ts +37 -0
  29. package/dist/git/git-commands.d.ts +9 -1
  30. package/dist/git/git-diff.d.ts +6 -0
  31. package/dist/git/git-executor.d.ts +11 -0
  32. package/dist/git/git-status.d.ts +9 -0
  33. package/dist/git/git-types.d.ts +2 -50
  34. package/dist/index.d.ts +51 -12
  35. package/dist/index.js +35378 -11873
  36. package/dist/index.js.map +1 -1
  37. package/dist/index.mjs +38854 -15461
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/installer.d.ts +1 -4
  40. package/dist/ipc/local-ipc-server.d.ts +91 -0
  41. package/dist/launch.d.ts +1 -1
  42. package/dist/logging/async-batch-writer.d.ts +10 -0
  43. package/dist/logging/log-redactor.d.ts +24 -0
  44. package/dist/logging/log-tail-reader.d.ts +46 -0
  45. package/dist/mesh/contracts.d.ts +164 -0
  46. package/dist/mesh/coordinator-prompt.d.ts +36 -0
  47. package/dist/mesh/coordinator-registry.d.ts +59 -0
  48. package/dist/mesh/mesh-active-work.d.ts +108 -0
  49. package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
  50. package/dist/mesh/mesh-events-coordinator.d.ts +169 -0
  51. package/dist/mesh/mesh-events-pending.d.ts +45 -0
  52. package/dist/mesh/mesh-events-stale.d.ts +40 -0
  53. package/dist/mesh/mesh-events-utils.d.ts +47 -0
  54. package/dist/mesh/mesh-events.d.ts +6 -49
  55. package/dist/mesh/mesh-fast-forward.d.ts +81 -0
  56. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  57. package/dist/mesh/mesh-init.d.ts +86 -0
  58. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
  59. package/dist/mesh/mesh-ledger.d.ts +77 -1
  60. package/dist/mesh/mesh-missions.d.ts +116 -0
  61. package/dist/mesh/mesh-reconcile-loop.d.ts +7 -0
  62. package/dist/mesh/mesh-refine-batch.d.ts +68 -0
  63. package/dist/mesh/mesh-refine-status.d.ts +62 -0
  64. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  65. package/dist/mesh/mesh-routing.d.ts +70 -0
  66. package/dist/mesh/mesh-runtime-store.d.ts +401 -0
  67. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  68. package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
  69. package/dist/mesh/mesh-work-queue.d.ts +204 -5
  70. package/dist/mesh/preview-freshness.d.ts +18 -0
  71. package/dist/mesh/refine-config.d.ts +215 -0
  72. package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
  73. package/dist/providers/acp-provider-instance.d.ts +2 -0
  74. package/dist/providers/approval-utils.d.ts +13 -0
  75. package/dist/providers/cli-provider-instance.d.ts +61 -3
  76. package/dist/providers/contracts.d.ts +130 -6
  77. package/dist/providers/external-sources.d.ts +71 -0
  78. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  79. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  80. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  81. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  82. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  83. package/dist/providers/native-history/index.d.ts +13 -0
  84. package/dist/providers/provider-instance-manager.d.ts +13 -0
  85. package/dist/providers/provider-instance.d.ts +13 -1
  86. package/dist/providers/provider-loader.d.ts +26 -4
  87. package/dist/providers/provider-trust.d.ts +31 -0
  88. package/dist/providers/read-chat-contract.d.ts +29 -0
  89. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  90. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  91. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  92. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  93. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  94. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  95. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  96. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  97. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  98. package/dist/providers/sdk/v1/index.d.ts +30 -0
  99. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  100. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  101. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  102. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  103. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  104. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  105. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  106. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  107. package/dist/providers/spec/adapter.d.ts +69 -0
  108. package/dist/providers/spec/cli-adapter.d.ts +217 -0
  109. package/dist/providers/spec/evaluator.d.ts +23 -0
  110. package/dist/providers/spec/fsm-driver.d.ts +278 -0
  111. package/dist/providers/spec/fsm-evaluator.d.ts +72 -0
  112. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  113. package/dist/providers/spec/fsm-types.d.ts +128 -0
  114. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  115. package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
  116. package/dist/providers/spec/route.d.ts +4 -0
  117. package/dist/providers/spec/types.d.ts +180 -0
  118. package/dist/providers/transcript-v2.d.ts +176 -0
  119. package/dist/providers/types/interactive-prompt.d.ts +84 -0
  120. package/dist/providers/working-dir.d.ts +17 -0
  121. package/dist/repo-mesh-types.d.ts +277 -13
  122. package/dist/sessions/registry.d.ts +6 -0
  123. package/dist/shared-types-extra.d.ts +2 -4
  124. package/dist/shared-types.d.ts +42 -1
  125. package/dist/status/normalize.d.ts +1 -1
  126. package/dist/status/normalize.js +1 -0
  127. package/dist/status/normalize.js.map +1 -1
  128. package/dist/status/normalize.mjs +1 -0
  129. package/dist/status/normalize.mjs.map +1 -1
  130. package/dist/status/reporter.d.ts +2 -0
  131. package/dist/status/snapshot.d.ts +1 -0
  132. package/dist/types.d.ts +5 -0
  133. package/package.json +7 -3
  134. package/src/agent-stream/poller.ts +2 -3
  135. package/src/boot/daemon-lifecycle.ts +55 -12
  136. package/src/boot/process-hardening.ts +89 -0
  137. package/src/build-info.ts +73 -0
  138. package/src/chat/source-machine.ts +534 -0
  139. package/src/chat/source-resolver.ts +0 -0
  140. package/src/chat/subscription-updates.ts +20 -1
  141. package/src/cli-adapter-types.d.ts +1 -0
  142. package/src/cli-adapter-types.ts +47 -2
  143. package/src/cli-adapters/cli-script-runner.ts +421 -0
  144. package/src/cli-adapters/cli-state-engine.ts +1215 -0
  145. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  146. package/src/cli-adapters/provider-cli-adapter.ts +710 -1140
  147. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  148. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  149. package/src/cli-adapters/provider-cli-runtime.ts +56 -7
  150. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  151. package/src/cli-adapters/provider-cli-shared.ts +85 -11
  152. package/src/cli-adapters/pty-transport.ts +2 -1
  153. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  154. package/src/cli-adapters/resolve-executable.ts +90 -0
  155. package/src/cli-adapters/session-host-transport.ts +2 -1
  156. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +53 -29
  157. package/src/cli-adapters/terminal-backends/types.ts +10 -3
  158. package/src/cli-adapters/terminal-screen.ts +16 -81
  159. package/src/commands/chat-commands.ts +1822 -61
  160. package/src/commands/cli-manager.ts +286 -14
  161. package/src/commands/handler.ts +809 -2
  162. package/src/commands/mesh-coordinator.ts +332 -122
  163. package/src/commands/router.ts +6369 -704
  164. package/src/commands/stream-commands.ts +8 -0
  165. package/src/commands/upgrade-helper.ts +81 -30
  166. package/src/config/chat-history.ts +483 -24
  167. package/src/config/config.ts +12 -0
  168. package/src/config/mesh-config.ts +320 -4
  169. package/src/config/recent-activity.ts +8 -2
  170. package/src/daemon/dev-auto-implement.ts +3 -2
  171. package/src/daemon/dev-cli-debug.ts +10 -1
  172. package/src/detection/cli-detector.ts +28 -9
  173. package/src/detection/ide-detector.ts +55 -16
  174. package/src/detection/win32-ide-version.ts +106 -0
  175. package/src/git/git-commands.ts +42 -12
  176. package/src/git/git-diff.ts +53 -0
  177. package/src/git/git-executor.ts +12 -0
  178. package/src/git/git-status.ts +408 -43
  179. package/src/git/git-types.ts +14 -62
  180. package/src/git/git-worktree.ts +8 -1
  181. package/src/index.ts +145 -11
  182. package/src/installer.d.ts +1 -1
  183. package/src/installer.ts +8 -6
  184. package/src/ipc/local-ipc-server.ts +278 -0
  185. package/src/launch.d.ts +1 -1
  186. package/src/launch.ts +37 -28
  187. package/src/logging/async-batch-writer.ts +55 -0
  188. package/src/logging/log-redactor.ts +100 -0
  189. package/src/logging/log-tail-reader.ts +220 -0
  190. package/src/logging/logger.ts +2 -1
  191. package/src/mesh/contracts.ts +329 -0
  192. package/src/mesh/coordinator-prompt.ts +234 -31
  193. package/src/mesh/coordinator-registry.ts +121 -0
  194. package/src/mesh/mesh-active-work.ts +469 -0
  195. package/src/mesh/mesh-delivery-policy.ts +315 -0
  196. package/src/mesh/mesh-events-coordinator.ts +2102 -0
  197. package/src/mesh/mesh-events-pending.ts +508 -0
  198. package/src/mesh/mesh-events-stale.ts +290 -0
  199. package/src/mesh/mesh-events-utils.ts +254 -0
  200. package/src/mesh/mesh-events.ts +30 -1035
  201. package/src/mesh/mesh-fast-forward.ts +839 -0
  202. package/src/mesh/mesh-host-ownership.ts +73 -0
  203. package/src/mesh/mesh-init.ts +260 -0
  204. package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
  205. package/src/mesh/mesh-ledger.ts +589 -102
  206. package/src/mesh/mesh-missions.ts +248 -0
  207. package/src/mesh/mesh-reconcile-loop.ts +496 -0
  208. package/src/mesh/mesh-refine-batch.ts +197 -0
  209. package/src/mesh/mesh-refine-status.ts +231 -0
  210. package/src/mesh/mesh-review-inbox.ts +307 -0
  211. package/src/mesh/mesh-routing.ts +291 -0
  212. package/src/mesh/mesh-runtime-store.ts +1578 -0
  213. package/src/mesh/mesh-task-stats.ts +154 -0
  214. package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
  215. package/src/mesh/mesh-work-queue.ts +663 -142
  216. package/src/mesh/preview-freshness.ts +118 -0
  217. package/src/mesh/refine-config.ts +403 -0
  218. package/src/mesh/worktree-bootstrap-config.ts +326 -0
  219. package/src/providers/acp-provider-instance.ts +19 -4
  220. package/src/providers/approval-utils.d.ts +4 -0
  221. package/src/providers/approval-utils.ts +47 -5
  222. package/src/providers/chat-message-normalization.ts +4 -11
  223. package/src/providers/cli-provider-instance.ts +958 -91
  224. package/src/providers/contracts.d.ts +55 -0
  225. package/src/providers/contracts.ts +141 -6
  226. package/src/providers/extension-provider-instance.ts +5 -1
  227. package/src/providers/external-sources.ts +218 -0
  228. package/src/providers/ide-provider-instance.ts +25 -6
  229. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  230. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  231. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  232. package/src/providers/native-history/dispatcher.ts +340 -0
  233. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  234. package/src/providers/native-history/index.ts +30 -0
  235. package/src/providers/provider-instance-manager.ts +30 -0
  236. package/src/providers/provider-instance.ts +10 -1
  237. package/src/providers/provider-loader.ts +662 -50
  238. package/src/providers/provider-schema.ts +87 -14
  239. package/src/providers/provider-trust.ts +114 -0
  240. package/src/providers/read-chat-contract.ts +76 -16
  241. package/src/providers/sdk/README.md +49 -0
  242. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  243. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  244. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  245. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  246. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  247. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  248. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  249. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  250. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  251. package/src/providers/sdk/v1/index.ts +152 -0
  252. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  253. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  254. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  255. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  256. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  257. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  258. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  259. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  260. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  261. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  262. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  263. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  264. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  265. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  266. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  267. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  268. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  269. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  270. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  271. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  272. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  273. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  274. package/src/providers/sdk/v1/validators/index.ts +19 -0
  275. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  276. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  277. package/src/providers/spec/adapter.ts +168 -0
  278. package/src/providers/spec/cli-adapter.ts +986 -0
  279. package/src/providers/spec/evaluator.ts +345 -0
  280. package/src/providers/spec/fsm-driver.ts +909 -0
  281. package/src/providers/spec/fsm-evaluator.ts +255 -0
  282. package/src/providers/spec/fsm-loader.ts +112 -0
  283. package/src/providers/spec/fsm-types.ts +222 -0
  284. package/src/providers/spec/native-history-executor.ts +1136 -0
  285. package/src/providers/spec/pre-launch-trust.ts +104 -0
  286. package/src/providers/spec/route.ts +51 -0
  287. package/src/providers/spec/types.ts +226 -0
  288. package/src/providers/transcript-v2.ts +567 -0
  289. package/src/providers/types/interactive-prompt.ts +536 -0
  290. package/src/providers/version-archive.ts +64 -24
  291. package/src/providers/working-dir.ts +23 -0
  292. package/src/repo-mesh-types.ts +356 -13
  293. package/src/sessions/registry.ts +6 -0
  294. package/src/shared-types-extra.ts +2 -4
  295. package/src/shared-types.ts +45 -1
  296. package/src/status/builders.ts +26 -6
  297. package/src/status/normalize.ts +2 -0
  298. package/src/status/reporter.ts +15 -0
  299. package/src/status/snapshot.ts +84 -25
  300. package/src/system/host-memory.ts +29 -12
  301. package/src/types.ts +5 -0
  302. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  303. package/dist/mesh/mesh-sync.d.ts +0 -53
  304. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  305. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  306. package/src/mesh/mesh-sync.ts +0 -111
@@ -15,6 +15,7 @@ export declare function buildCliParseInput(options: {
15
15
  accumulatedRawBuffer: string;
16
16
  recentOutputBuffer: string;
17
17
  terminalScreenText: string;
18
+ workingDir?: string;
18
19
  baseMessages: CliChatMessage[];
19
20
  partialResponse: string;
20
21
  isWaitingForResponse?: boolean;
@@ -35,22 +35,30 @@ export function buildCliParseInput(options: {
35
35
  accumulatedRawBuffer: string;
36
36
  recentOutputBuffer: string;
37
37
  terminalScreenText: string;
38
+ workingDir?: string;
39
+ providerSessionId?: string;
40
+ historySessionId?: string;
38
41
  baseMessages: CliChatMessage[];
39
42
  partialResponse: string;
40
43
  isWaitingForResponse?: boolean;
41
44
  scope?: TurnParseScope | null;
42
45
  runtimeSettings: Record<string, any>;
46
+ spawnAt?: number;
43
47
  }): CliScriptInput {
44
48
  const {
45
49
  accumulatedBuffer,
46
50
  accumulatedRawBuffer,
47
51
  recentOutputBuffer,
48
52
  terminalScreenText,
53
+ workingDir,
54
+ providerSessionId,
55
+ historySessionId,
49
56
  baseMessages,
50
57
  partialResponse,
51
58
  isWaitingForResponse,
52
59
  scope,
53
60
  runtimeSettings,
61
+ spawnAt,
54
62
  } = options;
55
63
  const buffer = scope
56
64
  ? sliceFromOffset(accumulatedBuffer, scope.bufferStart)
@@ -66,6 +74,10 @@ export function buildCliParseInput(options: {
66
74
  rawBuffer,
67
75
  recentBuffer,
68
76
  screenText,
77
+ workspace: workingDir,
78
+ workingDir,
79
+ providerSessionId,
80
+ historySessionId,
69
81
  screen: buildCliScreenSnapshot(screenText),
70
82
  bufferScreen: buildCliScreenSnapshot(buffer),
71
83
  recentScreen: buildCliScreenSnapshot(recentBuffer),
@@ -74,6 +86,7 @@ export function buildCliParseInput(options: {
74
86
  isWaitingForResponse,
75
87
  promptText: scope?.prompt || '',
76
88
  settings: { ...runtimeSettings },
89
+ ...(typeof spawnAt === 'number' && spawnAt > 0 ? { spawnAt } : {}),
77
90
  };
78
91
  }
79
92
 
@@ -34,20 +34,69 @@ export function resolveCliSpawnPlan(options: {
34
34
  const configuredCommand = typeof runtimeSettings.executablePath === 'string' && runtimeSettings.executablePath.trim()
35
35
  ? runtimeSettings.executablePath.trim()
36
36
  : spawnConfig.command;
37
- const binaryPath = findBinary(configuredCommand);
37
+ return resolveCliSpawnPlanFromParts({
38
+ command: configuredCommand,
39
+ baseArgs: spawnConfig.args,
40
+ shell: spawnConfig.shell,
41
+ baseEnv: spawnConfig.env,
42
+ workingDir,
43
+ extraArgs,
44
+ extraEnv,
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Cols/rows for a PTY spawn. Defaults to the session-host terminal size; the
50
+ * spec driver may override per-instance.
51
+ */
52
+ export interface CliSpawnGeometry {
53
+ cols?: number;
54
+ rows?: number;
55
+ }
56
+
57
+ /**
58
+ * Core spawn-plan resolution from already-flattened spawn parts — the single
59
+ * source of truth for binary resolution (findBinary: PATH + npm-global / Node
60
+ * dir fallback), `{{workingDir}}` token substitution, shell wrapping
61
+ * (script-shims / non-absolute / non-native binaries), and env sanitization +
62
+ * TERMINAL_CWD. Both the legacy provider-module path (resolveCliSpawnPlan) and
63
+ * the spec/FSM path (FsmDriver.buildAdapterOpts) feed into this so the two
64
+ * spawn paths can never diverge.
65
+ */
66
+ export function resolveCliSpawnPlanFromParts(options: {
67
+ /** Raw command from the spec/provider (`binary` / `spawn.command`), or an
68
+ * operator-configured executable path override. */
69
+ command: string;
70
+ /** Base launch args declared by the spec/provider (`spawn_args` /
71
+ * `spawn.args`). */
72
+ baseArgs?: string[];
73
+ /** When true, always wrap the launch in a login shell. */
74
+ shell?: boolean;
75
+ /** Base env declared by the spec/provider (`env` / `spawn.env`). */
76
+ baseEnv?: Record<string, string>;
77
+ workingDir: string;
78
+ /** Per-launch extra args (e.g. resume session id) appended after baseArgs. */
79
+ extraArgs?: string[];
80
+ extraEnv?: Record<string, string>;
81
+ geometry?: CliSpawnGeometry;
82
+ }): CliSpawnPlan {
83
+ const { command, baseArgs, shell, baseEnv, workingDir, extraArgs, extraEnv, geometry } = options;
84
+ const binaryPath = findBinary(command);
38
85
  const isWin = os.platform() === 'win32';
39
- const allArgs = [...spawnConfig.args, ...extraArgs];
86
+ const allArgs = [...(baseArgs ?? []), ...(extraArgs ?? [])].map((arg) =>
87
+ typeof arg === 'string' ? arg.replace(/\{\{workingDir\}\}/g, workingDir) : arg,
88
+ );
40
89
 
41
90
  let shellCmd: string;
42
91
  let shellArgs: string[];
43
92
  const useShellUnix = !isWin && (
44
- !!spawnConfig.shell
93
+ !!shell
45
94
  || !path.isAbsolute(binaryPath)
46
95
  || isScriptBinary(binaryPath)
47
96
  || !looksLikeMachOOrElf(binaryPath)
48
97
  );
49
98
  const isCmdShim = isWin && /\.(cmd|bat)$/i.test(binaryPath);
50
- const useShellWin = !!spawnConfig.shell
99
+ const useShellWin = !!shell
51
100
  || isCmdShim
52
101
  || !path.isAbsolute(binaryPath)
53
102
  || isScriptBinary(binaryPath);
@@ -66,7 +115,7 @@ export function resolveCliSpawnPlan(options: {
66
115
  shellArgs = allArgs;
67
116
  }
68
117
 
69
- const env = buildCliSpawnEnv(process.env, { ...(spawnConfig.env || {}), ...(extraEnv || {}) });
118
+ const env = buildCliSpawnEnv(process.env, { ...(baseEnv || {}), ...(extraEnv || {}) });
70
119
  // Some CLI agents, notably Hermes, route their tools through TERMINAL_CWD
71
120
  // rather than process.cwd(). Keep the generic ADHDev launch workspace as
72
121
  // the single source of truth so PTY cwd and tool cwd cannot diverge.
@@ -80,8 +129,8 @@ export function resolveCliSpawnPlan(options: {
80
129
  isWin,
81
130
  useShell,
82
131
  ptyOptions: {
83
- cols: DEFAULT_SESSION_HOST_COLS,
84
- rows: DEFAULT_SESSION_HOST_ROWS,
132
+ cols: geometry?.cols ?? DEFAULT_SESSION_HOST_COLS,
133
+ rows: geometry?.rows ?? DEFAULT_SESSION_HOST_ROWS,
85
134
  cwd: workingDir,
86
135
  env,
87
136
  },
@@ -55,6 +55,8 @@ export interface CliScriptInput {
55
55
  rawBuffer: string;
56
56
  recentBuffer: string;
57
57
  screenText: string;
58
+ workspace?: string;
59
+ workingDir?: string;
58
60
  screen: CliScreenSnapshot;
59
61
  bufferScreen: CliScreenSnapshot;
60
62
  recentScreen: CliScreenSnapshot;
@@ -4,6 +4,7 @@ import { execSync } from 'child_process';
4
4
  import type { ProviderResumeCapability } from '../providers/contracts.js';
5
5
  import type { ChatMessageKind } from '../providers/chat-message-normalization.js';
6
6
  import type { ChatBubbleState } from '../types.js';
7
+ import type { InteractivePrompt } from '../providers/types/interactive-prompt.js';
7
8
  import { sanitizeSpawnEnv } from './spawn-env.js';
8
9
 
9
10
  export interface CliChatMessage {
@@ -28,8 +29,27 @@ export interface CliSessionStatus {
28
29
  messages: CliChatMessage[];
29
30
  workingDir: string;
30
31
  activeModal: { message: string; buttons: string[] } | null;
32
+ /**
33
+ * Monotonic counter identifying which approval *entry* the current modal
34
+ * belongs to. Bumped by the FSM on every fresh waiting_approval entry.
35
+ * Consumers (e.g. auto-approval) use it to distinguish a genuinely new
36
+ * approval from the same approval re-observed across TUI paint flaps —
37
+ * two distinct approvals can carry identical message/button text, so the
38
+ * seq is the only reliable discriminator.
39
+ */
40
+ approvalEntrySeq?: number;
41
+ activeInteractivePrompt?: InteractivePrompt | null;
42
+ pendingOutboundCount?: number;
43
+ pendingOutboundMessages?: Array<{
44
+ id: string;
45
+ role: 'user';
46
+ content: string;
47
+ queuedAt: number;
48
+ source: string;
49
+ }>;
31
50
  errorMessage?: string;
32
51
  errorReason?: string;
52
+ providerSessionId?: string;
33
53
  bufferState?: {
34
54
  responseBuffer?: { truncated: boolean; droppedChars: number; maxChars: number };
35
55
  recentOutputBuffer?: { truncated: boolean; droppedChars: number; maxChars: number };
@@ -43,6 +63,9 @@ export interface ParsedSession {
43
63
  messages: any[];
44
64
  modal: { message: string; buttons: string[] } | null;
45
65
  parsedStatus: string | null;
66
+ errorMessage?: string;
67
+ errorReason?: string;
68
+ providerSessionId?: string;
46
69
  transcriptAuthority?: 'provider' | 'daemon';
47
70
  coverage?: 'full' | 'tail' | 'current-turn';
48
71
  }
@@ -94,6 +117,10 @@ export interface CliScriptInput {
94
117
  rawBuffer: string;
95
118
  recentBuffer: string;
96
119
  screenText: string;
120
+ workspace?: string;
121
+ workingDir?: string;
122
+ providerSessionId?: string;
123
+ historySessionId?: string;
97
124
  screen: CliScreenSnapshot;
98
125
  bufferScreen: CliScreenSnapshot;
99
126
  recentScreen: CliScreenSnapshot;
@@ -103,6 +130,7 @@ export interface CliScriptInput {
103
130
  promptText?: string;
104
131
  settings?: Record<string, any>;
105
132
  args?: Record<string, any>;
133
+ spawnAt?: number;
106
134
  }
107
135
 
108
136
  export interface CliStatusInput {
@@ -141,16 +169,26 @@ export interface CliProviderModule {
141
169
  binary: string;
142
170
  approvalKeys?: Record<number, string>;
143
171
  sendDelayMs?: number;
172
+ /** Wall-clock budget (ms) for a single provider script invocation. Default 50. Range 1..5000.
173
+ * Exceeding the budget records `timedOut: true` on the invocation trace and emits a
174
+ * throttled WARN — it does NOT abort the script (Node CJS can't interrupt sync code). */
175
+ scriptCallBudgetMs?: number;
144
176
  sendKey?: string;
145
177
  submitStrategy?: 'wait_for_echo' | 'immediate';
146
178
  /** Require the typed prompt to be visible on the PTY screen before sending Enter. */
147
179
  requirePromptEchoBeforeSubmit?: boolean;
148
180
  /** Allow sending another prompt while the CLI is still generating so users can intervene mid-turn. */
149
181
  allowInputDuringGeneration?: boolean;
182
+ /** When true, only transition to idle after the parsed transcript includes a final standard assistant message. */
183
+ requiresFinalAssistantBeforeIdle?: boolean;
184
+ /** When true, allow providers to augment stale snapshot data before parse. Reserved for future use. */
185
+ augmentStaleSnapshot?: boolean;
150
186
  /** When provider-owned, daemon treats provider parser output as canonical transcript authority. */
151
187
  transcriptAuthority?: 'provider' | 'daemon';
152
188
  /** Full context lets provider-owned parsers canonicalize retained history instead of daemon prefix stitching. */
153
189
  transcriptContext?: 'full' | 'tail';
190
+ /** v1 declarative tui block — used by CliScriptRunner to synthesize SDK helpers (declarativeDetectStatus, declarativeParseApproval). */
191
+ tui?: Record<string, unknown>;
154
192
  scripts?: CliScripts;
155
193
  spawn: {
156
194
  command: string;
@@ -342,7 +380,18 @@ export class TerminalTranscriptAccumulator {
342
380
  this.ensureRow();
343
381
  if (final === 'A') this.row = Math.max(0, this.row - count);
344
382
  else if (final === 'B') this.row += count;
345
- else if (final === 'C') this.col += count;
383
+ else if (final === 'C') {
384
+ // (fix) Cursor-forward must materialize spaces in the cells it
385
+ // skips, otherwise rendered transcripts collapse "Do you" written
386
+ // as "Do\x1b[1Cyou" into "Doyou". That mis-rendering broke
387
+ // Claude Code's approval-prompt and prompt-line detection
388
+ // (parseApproval saw "Doyouwanttoproceed?" and returned null).
389
+ const line = this.lines[this.row];
390
+ for (let c = this.col; c < this.col + count; c += 1) {
391
+ if (line[c] === undefined) line[c] = ' ';
392
+ }
393
+ this.col += count;
394
+ }
346
395
  else if (final === 'D') this.col = Math.max(0, this.col - count);
347
396
  else if (final === 'G') this.col = Math.max(0, count - 1);
348
397
  else if (final === 'H' || final === 'f') {
@@ -484,17 +533,42 @@ export function findBinary(name: string): string {
484
533
  return path.isAbsolute(expanded) ? expanded : path.resolve(expanded);
485
534
  }
486
535
  const isWin = os.platform() === 'win32';
487
- try {
488
- const cmd = isWin ? `where ${trimmed}` : `which ${trimmed}`;
489
- return execSync(cmd, {
490
- encoding: 'utf-8',
491
- timeout: 5000,
492
- stdio: ['pipe', 'pipe', 'pipe'],
493
- ...(isWin ? { windowsHide: true } : {}),
494
- }).trim().split('\n')[0].trim();
495
- } catch {
496
- return isWin ? `${trimmed}.cmd` : trimmed;
536
+ const paths = (process.env.PATH || '').split(path.delimiter);
537
+ // Also search well-known global-bin directories that are frequently NOT on
538
+ // the daemon's inherited PATH. A daemon running under one Node install (e.g.
539
+ // nvm) never sees another npm prefix's bin dir — notably npm's Windows
540
+ // default prefix at %APPDATA%\npm, where `npm i -g @openai/codex` lands. We
541
+ // append these after PATH (so explicit PATH entries still win) so that an
542
+ // npm-global CLI resolves to an absolute path; the spawn layer's .cmd-shim
543
+ // handling then launches it correctly regardless of PATH.
544
+ const extraDirs: string[] = [];
545
+ if (isWin) {
546
+ if (process.env.APPDATA) extraDirs.push(path.join(process.env.APPDATA, 'npm'));
547
+ try { extraDirs.push(path.dirname(process.execPath)); } catch { /* best-effort */ }
548
+ } else {
549
+ extraDirs.push(path.join(os.homedir(), '.npm-global', 'bin'));
550
+ extraDirs.push('/usr/local/bin', '/opt/homebrew/bin');
551
+ try { extraDirs.push(path.dirname(process.execPath)); } catch { /* best-effort */ }
552
+ }
553
+ const searchDirs = [...paths, ...extraDirs];
554
+ const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
555
+
556
+ for (const p of searchDirs) {
557
+ if (!p) continue;
558
+ for (const ext of exes) {
559
+ const fullPath = path.join(p, trimmed + ext);
560
+ try {
561
+ const fs = require('fs');
562
+ if (fs.existsSync(fullPath)) {
563
+ const stat = fs.statSync(fullPath);
564
+ if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
565
+ return fullPath;
566
+ }
567
+ }
568
+ } catch { }
569
+ }
497
570
  }
571
+ return isWin ? `${trimmed}.cmd` : trimmed;
498
572
  }
499
573
 
500
574
  export function isScriptBinary(binaryPath: string): boolean {
@@ -1,5 +1,6 @@
1
1
  import * as os from 'os';
2
2
  import { ensureNodePtySpawnHelperPermissions } from './spawn-env.js';
3
+ import { resolveWin32Executable } from './resolve-executable.js';
3
4
 
4
5
  let cachedPty: any | null | undefined;
5
6
 
@@ -119,7 +120,7 @@ export class NodePtyTransportFactory implements PtyTransportFactory {
119
120
  cwd = os.homedir();
120
121
  }
121
122
  }
122
- const handle = pty.spawn(command, args, {
123
+ const handle = pty.spawn(resolveWin32Executable(command), args, {
123
124
  name: 'xterm-256color',
124
125
  cols: options.cols,
125
126
  rows: options.rows,
@@ -0,0 +1,252 @@
1
+ import { randomUUID } from 'crypto';
2
+ import {
3
+ SessionHostClient,
4
+ type SessionHostEndpoint,
5
+ type SessionHostRequest,
6
+ type SessionHostResponse,
7
+ type SessionTerminalSnapshot,
8
+ type SessionTerminalState,
9
+ } from '@adhdev/session-host-core';
10
+
11
+ type LowercaseLetter =
12
+ | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm'
13
+ | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
14
+
15
+ type FunctionKey = 'f1' | 'f2' | 'f3' | 'f4' | 'f5' | 'f6' | 'f7' | 'f8' | 'f9' | 'f10' | 'f11' | 'f12';
16
+ type BaseNamedKey =
17
+ | 'enter' | 'escape' | 'tab' | 'backspace'
18
+ | 'up' | 'down' | 'left' | 'right'
19
+ | 'home' | 'end' | 'pageup' | 'pagedown' | 'space'
20
+ | FunctionKey;
21
+ type ShiftNamedKey = BaseNamedKey | LowercaseLetter | `ctrl+${LowercaseLetter}` | `alt+${LowercaseLetter}`;
22
+
23
+ export type NamedKey =
24
+ | BaseNamedKey
25
+ | `ctrl+${LowercaseLetter}`
26
+ | `alt+${LowercaseLetter}`
27
+ | `shift+${ShiftNamedKey}`;
28
+
29
+ const BASE_KEY_SEQUENCES: Record<BaseNamedKey, string> = {
30
+ enter: '\r',
31
+ escape: '\x1b',
32
+ tab: '\t',
33
+ backspace: '\x7f',
34
+ up: '\x1b[A',
35
+ down: '\x1b[B',
36
+ right: '\x1b[C',
37
+ left: '\x1b[D',
38
+ home: '\x1b[H',
39
+ end: '\x1b[F',
40
+ pageup: '\x1b[5~',
41
+ pagedown: '\x1b[6~',
42
+ space: ' ',
43
+ f1: '\x1bOP',
44
+ f2: '\x1bOQ',
45
+ f3: '\x1bOR',
46
+ f4: '\x1bOS',
47
+ f5: '\x1b[15~',
48
+ f6: '\x1b[17~',
49
+ f7: '\x1b[18~',
50
+ f8: '\x1b[19~',
51
+ f9: '\x1b[20~',
52
+ f10: '\x1b[21~',
53
+ f11: '\x1b[23~',
54
+ f12: '\x1b[24~',
55
+ };
56
+
57
+ const SHIFTED_CSI_KEYS: Partial<Record<BaseNamedKey, string>> = {
58
+ up: '\x1b[1;2A',
59
+ down: '\x1b[1;2B',
60
+ right: '\x1b[1;2C',
61
+ left: '\x1b[1;2D',
62
+ home: '\x1b[1;2H',
63
+ end: '\x1b[1;2F',
64
+ pageup: '\x1b[5;2~',
65
+ pagedown: '\x1b[6;2~',
66
+ f1: '\x1b[1;2P',
67
+ f2: '\x1b[1;2Q',
68
+ f3: '\x1b[1;2R',
69
+ f4: '\x1b[1;2S',
70
+ f5: '\x1b[15;2~',
71
+ f6: '\x1b[17;2~',
72
+ f7: '\x1b[18;2~',
73
+ f8: '\x1b[19;2~',
74
+ f9: '\x1b[20;2~',
75
+ f10: '\x1b[21;2~',
76
+ f11: '\x1b[23;2~',
77
+ f12: '\x1b[24;2~',
78
+ };
79
+
80
+ function isLowercaseLetter(value: string): value is LowercaseLetter {
81
+ return /^[a-z]$/.test(value);
82
+ }
83
+
84
+ function encodeControlLetter(letter: LowercaseLetter): string {
85
+ return String.fromCharCode(letter.charCodeAt(0) - 96);
86
+ }
87
+
88
+ function encodeShiftedKey(key: string): string {
89
+ if (isLowercaseLetter(key)) return key.toUpperCase();
90
+ if (key.startsWith('ctrl+') && isLowercaseLetter(key.slice(5))) {
91
+ return encodeControlLetter(key.slice(5) as LowercaseLetter);
92
+ }
93
+ if (key.startsWith('alt+') && isLowercaseLetter(key.slice(4))) {
94
+ return `\x1b${key.slice(4).toUpperCase()}`;
95
+ }
96
+ if (key === 'tab') return '\x1b[Z';
97
+ if (key in SHIFTED_CSI_KEYS) return SHIFTED_CSI_KEYS[key as BaseNamedKey]!;
98
+ if (key in BASE_KEY_SEQUENCES) return BASE_KEY_SEQUENCES[key as BaseNamedKey];
99
+ throw new Error(`Unsupported named key: shift+${key}`);
100
+ }
101
+
102
+ export function namedKeyToAnsi(key: NamedKey | string): string {
103
+ const normalized = String(key || '').trim().toLowerCase();
104
+ if (normalized in BASE_KEY_SEQUENCES) return BASE_KEY_SEQUENCES[normalized as BaseNamedKey];
105
+ if (normalized.startsWith('ctrl+') && isLowercaseLetter(normalized.slice(5))) {
106
+ return encodeControlLetter(normalized.slice(5) as LowercaseLetter);
107
+ }
108
+ if (normalized.startsWith('alt+') && isLowercaseLetter(normalized.slice(4))) {
109
+ return `\x1b${normalized.slice(4)}`;
110
+ }
111
+ if (normalized.startsWith('shift+')) return encodeShiftedKey(normalized.slice(6));
112
+ throw new Error(`Unsupported named key: ${key}`);
113
+ }
114
+
115
+ export function namedKeysToAnsi(keys: readonly (NamedKey | string)[]): string {
116
+ if (!Array.isArray(keys)) throw new Error('keys must be an array');
117
+ return keys.map(namedKeyToAnsi).join('');
118
+ }
119
+
120
+ export interface RawTerminalSessionHostClient {
121
+ connect(): Promise<void>;
122
+ request<T = unknown>(request: SessionHostRequest): Promise<SessionHostResponse<T>>;
123
+ close(): Promise<void>;
124
+ }
125
+
126
+ export interface RawTerminalAttachmentOptions {
127
+ endpoint?: SessionHostEndpoint;
128
+ sessionId: string;
129
+ mode?: 'read' | 'write';
130
+ clientId?: string;
131
+ client?: RawTerminalSessionHostClient;
132
+ }
133
+
134
+ export class RawTerminalAttachment {
135
+ private closed = false;
136
+
137
+ private constructor(
138
+ readonly sessionId: string,
139
+ private readonly clientId: string,
140
+ private readonly mode: 'read' | 'write',
141
+ private readonly client: RawTerminalSessionHostClient,
142
+ ) {}
143
+
144
+ static async attach(options: RawTerminalAttachmentOptions): Promise<RawTerminalAttachment> {
145
+ const sessionId = String(options.sessionId || '').trim();
146
+ if (!sessionId) throw new Error('sessionId is required');
147
+ const mode = options.mode || 'read';
148
+ const clientId = options.clientId || `raw-terminal-${process.pid}-${randomUUID().slice(0, 8)}`;
149
+ const client = options.client || new SessionHostClient({ endpoint: options.endpoint });
150
+ await client.connect();
151
+
152
+ const attachResponse = await client.request({
153
+ type: 'attach_session',
154
+ payload: {
155
+ sessionId,
156
+ clientId,
157
+ clientType: 'web',
158
+ readOnly: mode === 'read',
159
+ },
160
+ });
161
+ if (!attachResponse.success) {
162
+ await client.close().catch(() => {});
163
+ throw new Error(attachResponse.error || `Failed to attach terminal session ${sessionId}`);
164
+ }
165
+
166
+ if (mode === 'write') {
167
+ const ownerResponse = await client.request({
168
+ type: 'acquire_write',
169
+ payload: {
170
+ sessionId,
171
+ clientId,
172
+ ownerType: 'user',
173
+ force: true,
174
+ },
175
+ });
176
+ if (!ownerResponse.success) {
177
+ await client.request({
178
+ type: 'detach_session',
179
+ payload: { sessionId, clientId },
180
+ }).catch(() => ({ success: false }));
181
+ await client.close().catch(() => {});
182
+ throw new Error(ownerResponse.error || `Failed to acquire terminal session ${sessionId}`);
183
+ }
184
+ }
185
+
186
+ return new RawTerminalAttachment(sessionId, clientId, mode, client);
187
+ }
188
+
189
+ async readSnapshot(): Promise<SessionTerminalSnapshot> {
190
+ const response = await this.client.request<SessionTerminalSnapshot>({
191
+ type: 'get_terminal_snapshot',
192
+ payload: { sessionId: this.sessionId },
193
+ });
194
+ if (!response.success || !response.result) {
195
+ throw new Error(response.error || `Terminal screen unavailable for ${this.sessionId}`);
196
+ }
197
+ return response.result;
198
+ }
199
+
200
+ async readScreenText(): Promise<string> {
201
+ return (await this.readSnapshot()).text;
202
+ }
203
+
204
+ async readState(): Promise<SessionTerminalState> {
205
+ return (await this.readSnapshot()).state;
206
+ }
207
+
208
+ async writeInput(text: string): Promise<void> {
209
+ if (this.mode !== 'write') throw new Error('Raw terminal attachment is read-only');
210
+ const response = await this.client.request({
211
+ type: 'send_input',
212
+ payload: {
213
+ sessionId: this.sessionId,
214
+ clientId: this.clientId,
215
+ data: text,
216
+ },
217
+ });
218
+ if (!response.success) throw new Error(response.error || `Failed to write terminal input to ${this.sessionId}`);
219
+ }
220
+
221
+ async writeKeys(keys: readonly (NamedKey | string)[]): Promise<void> {
222
+ await this.writeInput(namedKeysToAnsi(keys));
223
+ }
224
+
225
+ async close(): Promise<void> {
226
+ if (this.closed) return;
227
+ this.closed = true;
228
+ if (this.mode === 'write') {
229
+ await this.client.request({
230
+ type: 'release_write',
231
+ payload: { sessionId: this.sessionId, clientId: this.clientId },
232
+ }).catch(() => ({ success: false }));
233
+ }
234
+ await this.client.request({
235
+ type: 'detach_session',
236
+ payload: { sessionId: this.sessionId, clientId: this.clientId },
237
+ }).catch(() => ({ success: false }));
238
+ await this.client.close().catch(() => {});
239
+ }
240
+ }
241
+
242
+ export async function withRawTerminalAttachment<T>(
243
+ options: RawTerminalAttachmentOptions,
244
+ operation: (attachment: RawTerminalAttachment) => Promise<T>,
245
+ ): Promise<T> {
246
+ const attachment = await RawTerminalAttachment.attach(options);
247
+ try {
248
+ return await operation(attachment);
249
+ } finally {
250
+ await attachment.close();
251
+ }
252
+ }
@@ -0,0 +1,90 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ import * as path from 'path';
4
+
5
+ // Extensions ConPTY/CreateProcess can launch directly (not .cmd/.bat, which
6
+ // need a cmd.exe wrapper).
7
+ const DIRECT_EXEC_EXT = new Set(['.exe', '.com']);
8
+
9
+ // Executable extensions to probe when scanning a directory ourselves, ordered
10
+ // most-directly-launchable first. node-pty's ConPTY backend launches an
11
+ // absolute `.cmd`/`.bat` shim fine (verified) — it only fails to *resolve* a
12
+ // bare command against an incomplete PATH — so once we hand it an absolute
13
+ // path, a `.cmd` shim works just as well as a real `.exe`.
14
+ const WIN_EXEC_EXT = ['.exe', '.com', '.cmd', '.bat'];
15
+
16
+ /**
17
+ * Resolve a bare command against well-known global-bin directories that are
18
+ * frequently NOT on the daemon's inherited PATH, so `where` (which only
19
+ * searches PATH) misses them. A daemon running under one Node install (e.g.
20
+ * nvm) never sees another npm prefix's bin dir — notably npm's Windows default
21
+ * prefix at %APPDATA%\npm, where `npm i -g @openai/codex` lands. Returns an
22
+ * absolute path on the first hit, or null. Mirrors findBinary()'s extraDirs in
23
+ * provider-cli-shared.ts; kept inline here so this lightweight module (loaded
24
+ * by pty-transport) need not pull in the heavier shared module.
25
+ */
26
+ function resolveWin32GlobalBin(trimmed: string): string | null {
27
+ // Only resolve a bare command name — anything with a path separator is the
28
+ // caller's explicit location and must not be re-pointed at a global bin dir.
29
+ if (path.isAbsolute(trimmed) || trimmed.includes('/') || trimmed.includes('\\')) {
30
+ return null;
31
+ }
32
+ const extraDirs: string[] = [];
33
+ if (process.env.APPDATA) extraDirs.push(path.join(process.env.APPDATA, 'npm'));
34
+ try { extraDirs.push(path.dirname(process.execPath)); } catch { /* best-effort */ }
35
+ for (const dir of extraDirs) {
36
+ if (!dir) continue;
37
+ for (const ext of WIN_EXEC_EXT) {
38
+ const full = path.join(dir, trimmed + ext);
39
+ if (existsSync(full)) return full;
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * Resolve a launch command to an absolute executable path on Windows.
47
+ *
48
+ * node-pty's ConPTY backend resolves a bare/relative command against the
49
+ * *calling process's* `Path` env var and — critically — does NOT apply PATHEXT.
50
+ * So a provider command like `claude` never matches `claude.exe` and the native
51
+ * layer throws `File not found:` (empty), which crashes the daemon. We resolve
52
+ * it to an absolute `.exe` here (in the daemon process, which has the full PATH)
53
+ * before the command ever reaches node-pty.
54
+ *
55
+ * No-op on non-Windows and when the command is already an existing absolute path
56
+ * or cannot be resolved (caller keeps the original behaviour).
57
+ */
58
+ export function resolveWin32Executable(command: string): string {
59
+ if (process.platform !== 'win32') return command;
60
+ const trimmed = (command || '').trim();
61
+ if (!trimmed) return command;
62
+
63
+ // Already an absolute path that exists — keep it.
64
+ if (path.isAbsolute(trimmed) && existsSync(trimmed)) return trimmed;
65
+
66
+ try {
67
+ const out = execFileSync('where', [trimmed], {
68
+ encoding: 'utf8',
69
+ windowsHide: true,
70
+ }).trim();
71
+ if (out) {
72
+ const matches = out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
73
+ // Prefer a directly-launchable executable (.exe/.com) over .cmd/.bat shims.
74
+ const direct = matches.find((m) => DIRECT_EXEC_EXT.has(path.extname(m).toLowerCase()));
75
+ return direct || matches[0] || command;
76
+ }
77
+ } catch {
78
+ // `where` not found / non-zero exit — fall through to the global-bin scan.
79
+ }
80
+
81
+ // `where` found nothing on PATH. Before giving up (and letting node-pty crash
82
+ // with "File not found:" on the bare command), search npm's off-PATH global
83
+ // bin dir(s). This is the codex case: `npm i -g @openai/codex` installs to
84
+ // %APPDATA%\npm, which is absent from a nvm-launched daemon's PATH, so a spec
85
+ // binary of "codex" never resolved and the spawn ENOENT'd.
86
+ const globalBin = resolveWin32GlobalBin(trimmed);
87
+ if (globalBin) return globalBin;
88
+
89
+ return command;
90
+ }