@skaile/workspaces 2.3.0 → 2.5.0

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 (217) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +32 -13
  3. package/dist/app-sdk/index.js +1 -1
  4. package/dist/{asset-feeds-DKNYRLED.js → asset-feeds-4AWAT5PW.js} +6 -6
  5. package/dist/{asset-feeds-DKNYRLED.js.map → asset-feeds-4AWAT5PW.js.map} +1 -1
  6. package/dist/asset-manager/catalog-deployer.js +1 -1
  7. package/dist/asset-manager/index.js +5 -5
  8. package/dist/asset-manager/installer.js +2 -2
  9. package/dist/bridge/drivers/claude-sdk.js +108 -5
  10. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  11. package/dist/bridge/drivers/codex.js +1 -1
  12. package/dist/bridge/drivers/continue.js +2 -2
  13. package/dist/bridge/drivers/echo.js +1 -1
  14. package/dist/bridge/drivers/gemini.js +3 -3
  15. package/dist/bridge/drivers/goose.js +2 -2
  16. package/dist/bridge/drivers/omp.js +1 -1
  17. package/dist/bridge/drivers/opencode.js +2 -2
  18. package/dist/bridge/drivers/qwen.js +3 -3
  19. package/dist/bridge/index.js +2 -2
  20. package/dist/bridge/src/drivers/claude-sdk.d.ts +33 -0
  21. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  22. package/dist/{chunk-CERGXDGG.js → chunk-2OCYS5EP.js} +3 -3
  23. package/dist/{chunk-CERGXDGG.js.map → chunk-2OCYS5EP.js.map} +1 -1
  24. package/dist/{chunk-W3Q37HEF.js → chunk-3HVASHBF.js} +3 -3
  25. package/dist/{chunk-W3Q37HEF.js.map → chunk-3HVASHBF.js.map} +1 -1
  26. package/dist/{chunk-UQ3RJKDD.js → chunk-4O2RAXKJ.js} +179 -52
  27. package/dist/chunk-4O2RAXKJ.js.map +1 -0
  28. package/dist/{chunk-DZM33LQO.js → chunk-4QROZZ5Z.js} +15 -3
  29. package/dist/chunk-4QROZZ5Z.js.map +1 -0
  30. package/dist/{chunk-G2ILW47R.js → chunk-57RFYEVM.js} +3 -3
  31. package/dist/{chunk-G2ILW47R.js.map → chunk-57RFYEVM.js.map} +1 -1
  32. package/dist/{chunk-BLWNI5KW.js → chunk-AKVAZ5CB.js} +2 -2
  33. package/dist/{chunk-BLWNI5KW.js.map → chunk-AKVAZ5CB.js.map} +1 -1
  34. package/dist/{chunk-BFIK6NPZ.js → chunk-AM5LFRXJ.js} +3 -3
  35. package/dist/{chunk-BFIK6NPZ.js.map → chunk-AM5LFRXJ.js.map} +1 -1
  36. package/dist/{chunk-RDE43ORT.js → chunk-D4VHBAJC.js} +4 -4
  37. package/dist/{chunk-RDE43ORT.js.map → chunk-D4VHBAJC.js.map} +1 -1
  38. package/dist/{chunk-PTS3QN5P.js → chunk-EEHOMCRU.js} +1021 -115
  39. package/dist/chunk-EEHOMCRU.js.map +1 -0
  40. package/dist/{chunk-CXX3UGJP.js → chunk-ENBTXMPL.js} +7 -7
  41. package/dist/{chunk-CXX3UGJP.js.map → chunk-ENBTXMPL.js.map} +1 -1
  42. package/dist/{chunk-LD6YGIKZ.js → chunk-EUWN4EK4.js} +60 -21
  43. package/dist/chunk-EUWN4EK4.js.map +1 -0
  44. package/dist/{chunk-FB4GPWYC.js → chunk-FKBQRV4J.js} +57 -16
  45. package/dist/chunk-FKBQRV4J.js.map +1 -0
  46. package/dist/{chunk-WG3L5VHF.js → chunk-HY3MBSNC.js} +2 -2
  47. package/dist/{chunk-WG3L5VHF.js.map → chunk-HY3MBSNC.js.map} +1 -1
  48. package/dist/{chunk-7IRH7MGV.js → chunk-IHX2SN35.js} +9 -9
  49. package/dist/{chunk-7IRH7MGV.js.map → chunk-IHX2SN35.js.map} +1 -1
  50. package/dist/{chunk-3F54MH2T.js → chunk-JAFEMG7W.js} +4 -4
  51. package/dist/{chunk-3F54MH2T.js.map → chunk-JAFEMG7W.js.map} +1 -1
  52. package/dist/{chunk-X6BPVUV2.js → chunk-L3TS7ZO4.js} +3 -3
  53. package/dist/{chunk-X6BPVUV2.js.map → chunk-L3TS7ZO4.js.map} +1 -1
  54. package/dist/{chunk-RIEXN7WA.js → chunk-MADO7PH5.js} +2 -2
  55. package/dist/{chunk-RIEXN7WA.js.map → chunk-MADO7PH5.js.map} +1 -1
  56. package/dist/{chunk-BCTU4TN7.js → chunk-MWFYXUFK.js} +3 -3
  57. package/dist/{chunk-BCTU4TN7.js.map → chunk-MWFYXUFK.js.map} +1 -1
  58. package/dist/{chunk-TK66CMXV.js → chunk-NU4E7C5I.js} +7 -3
  59. package/dist/chunk-NU4E7C5I.js.map +1 -0
  60. package/dist/{chunk-ZE2KEPE2.js → chunk-OEGHKO5N.js} +258 -24
  61. package/dist/chunk-OEGHKO5N.js.map +1 -0
  62. package/dist/{chunk-63ZPKREA.js → chunk-P43HBWKK.js} +11 -4
  63. package/dist/chunk-P43HBWKK.js.map +1 -0
  64. package/dist/{chunk-7UZGKVSK.js → chunk-PJ6INYDD.js} +3 -3
  65. package/dist/{chunk-7UZGKVSK.js.map → chunk-PJ6INYDD.js.map} +1 -1
  66. package/dist/{chunk-CA5CWLGN.js → chunk-PKEAYYEJ.js} +7 -4
  67. package/dist/chunk-PKEAYYEJ.js.map +1 -0
  68. package/dist/{chunk-ETFETLLB.js → chunk-UZYTSIX6.js} +6 -6
  69. package/dist/{chunk-ETFETLLB.js.map → chunk-UZYTSIX6.js.map} +1 -1
  70. package/dist/{chunk-SIF5F5SN.js → chunk-V4ZLZJFN.js} +3 -3
  71. package/dist/{chunk-SIF5F5SN.js.map → chunk-V4ZLZJFN.js.map} +1 -1
  72. package/dist/{chunk-ZR5Q4KZK.js → chunk-VNIKF66R.js} +3 -3
  73. package/dist/{chunk-ZR5Q4KZK.js.map → chunk-VNIKF66R.js.map} +1 -1
  74. package/dist/{chunk-CILACPUC.js → chunk-WSARNOPP.js} +5 -5
  75. package/dist/{chunk-CILACPUC.js.map → chunk-WSARNOPP.js.map} +1 -1
  76. package/dist/{chunk-TAJBFYCT.js → chunk-YWPEDILM.js} +83 -36
  77. package/dist/chunk-YWPEDILM.js.map +1 -0
  78. package/dist/{chunk-UNP4M5ZZ.js → chunk-ZR2X4SIG.js} +4 -4
  79. package/dist/{chunk-UNP4M5ZZ.js.map → chunk-ZR2X4SIG.js.map} +1 -1
  80. package/dist/cli/index.js +50 -50
  81. package/dist/connectors/config.js +1 -1
  82. package/dist/connectors/index.js +6 -6
  83. package/dist/core/index.js +1 -1
  84. package/dist/core/src/subprocess.d.ts +5 -1
  85. package/dist/core/src/subprocess.d.ts.map +1 -1
  86. package/dist/deploy/index.js +1 -1
  87. package/dist/discovery/index.js +1 -1
  88. package/dist/{ensure-sources-R6KW7K3M.js → ensure-sources-QF72HVUM.js} +8 -8
  89. package/dist/{ensure-sources-R6KW7K3M.js.map → ensure-sources-QF72HVUM.js.map} +1 -1
  90. package/dist/factory-assets/connectors/box.js +6 -6
  91. package/dist/factory-assets/connectors/deploy.js +6 -6
  92. package/dist/factory-assets/connectors/devserver.js +6 -6
  93. package/dist/factory-assets/connectors/flow/CONNECTOR.md +61 -14
  94. package/dist/factory-assets/connectors/flow/adapter.d.ts +78 -34
  95. package/dist/factory-assets/connectors/flow/adapter.d.ts.map +1 -1
  96. package/dist/factory-assets/connectors/flow/adapter.js +6 -6
  97. package/dist/factory-assets/connectors/flow/contract/expression.d.ts +55 -0
  98. package/dist/factory-assets/connectors/flow/contract/expression.d.ts.map +1 -1
  99. package/dist/factory-assets/connectors/flow/contract/flow.v2.schema.json +61 -134
  100. package/dist/factory-assets/connectors/flow/contract/index.d.ts +1 -1
  101. package/dist/factory-assets/connectors/flow/contract/index.d.ts.map +1 -1
  102. package/dist/factory-assets/connectors/flow/contract/normalize.d.ts.map +1 -1
  103. package/dist/factory-assets/connectors/flow/contract/schema.d.ts +14 -2
  104. package/dist/factory-assets/connectors/flow/contract/schema.d.ts.map +1 -1
  105. package/dist/factory-assets/connectors/flow/contract.js +2 -2
  106. package/dist/factory-assets/connectors/flow/engine/bindings.d.ts +9 -0
  107. package/dist/factory-assets/connectors/flow/engine/bindings.d.ts.map +1 -0
  108. package/dist/factory-assets/connectors/flow/engine/engine.d.ts +11 -6
  109. package/dist/factory-assets/connectors/flow/engine/engine.d.ts.map +1 -1
  110. package/dist/factory-assets/connectors/flow/engine/flow-kind-provider.d.ts.map +1 -1
  111. package/dist/factory-assets/connectors/flow/engine/index.d.ts +2 -0
  112. package/dist/factory-assets/connectors/flow/engine/index.d.ts.map +1 -1
  113. package/dist/factory-assets/connectors/flow/engine/node-shape.d.ts +22 -0
  114. package/dist/factory-assets/connectors/flow/engine/node-shape.d.ts.map +1 -1
  115. package/dist/factory-assets/connectors/flow/engine/output-schema.d.ts +18 -0
  116. package/dist/factory-assets/connectors/flow/engine/output-schema.d.ts.map +1 -0
  117. package/dist/factory-assets/connectors/flow/engine/types.d.ts +21 -15
  118. package/dist/factory-assets/connectors/flow/engine/types.d.ts.map +1 -1
  119. package/dist/factory-assets/connectors/flow/engine.js +4 -4
  120. package/dist/factory-assets/connectors/flow/index.d.ts +3 -0
  121. package/dist/factory-assets/connectors/flow/index.d.ts.map +1 -1
  122. package/dist/factory-assets/connectors/flow/inline-sub-flow-host.d.ts +48 -0
  123. package/dist/factory-assets/connectors/flow/inline-sub-flow-host.d.ts.map +1 -0
  124. package/dist/factory-assets/connectors/flow/node-executor.d.ts +45 -0
  125. package/dist/factory-assets/connectors/flow/node-executor.d.ts.map +1 -0
  126. package/dist/factory-assets/connectors/flow/prompt-fragments.d.ts +3 -2
  127. package/dist/factory-assets/connectors/flow/prompt-fragments.d.ts.map +1 -1
  128. package/dist/factory-assets/connectors/flow/prompt-fragments.js +2 -2
  129. package/dist/factory-assets/connectors/flow/run-flow.d.ts +10 -9
  130. package/dist/factory-assets/connectors/flow/run-flow.d.ts.map +1 -1
  131. package/dist/factory-assets/connectors/flow/run-flow.js +7 -7
  132. package/dist/factory-assets/connectors/flow/sub-flow-runner.d.ts +73 -0
  133. package/dist/factory-assets/connectors/flow/sub-flow-runner.d.ts.map +1 -0
  134. package/dist/factory-assets/connectors/flow.js +6 -6
  135. package/dist/factory-assets/connectors/git.js +6 -6
  136. package/dist/factory-assets/connectors/gmail.js +6 -6
  137. package/dist/factory-assets/connectors/googledrive.js +6 -6
  138. package/dist/factory-assets/connectors/local.js +6 -6
  139. package/dist/factory-assets/connectors/mattermost.js +6 -6
  140. package/dist/factory-assets/connectors/memory.js +6 -6
  141. package/dist/factory-assets/connectors/minio.js +6 -6
  142. package/dist/factory-assets/connectors/postgres.js +6 -6
  143. package/dist/factory-assets/connectors/s3.js +6 -6
  144. package/dist/factory-assets/connectors/sharepoint.js +6 -6
  145. package/dist/factory-assets/connectors/sqlite.js +6 -6
  146. package/dist/factory-assets/connectors/static-server.js +6 -6
  147. package/dist/factory-assets/connectors/tunnel.js +6 -6
  148. package/dist/factory-assets/connectors/webdav.js +6 -6
  149. package/dist/factory-assets/connectors/xstate-store.js +6 -6
  150. package/dist/factory-assets/connectors/xstate.js +6 -6
  151. package/dist/{flows-F3CJNMK6.js → flows-YIW4HL3L.js} +4 -4
  152. package/dist/{flows-F3CJNMK6.js.map → flows-YIW4HL3L.js.map} +1 -1
  153. package/dist/{helpers-KPOLCGKX.js → helpers-LURCZHA4.js} +8 -8
  154. package/dist/{helpers-KPOLCGKX.js.map → helpers-LURCZHA4.js.map} +1 -1
  155. package/dist/library/index.js +3 -3
  156. package/dist/library/install.js +1 -1
  157. package/dist/{library-publish-ZAPZS64O.js → library-publish-2GMJM3NZ.js} +14 -14
  158. package/dist/{library-publish-ZAPZS64O.js.map → library-publish-2GMJM3NZ.js.map} +1 -1
  159. package/dist/{open-library-I4F2UNOY.js → open-library-POFOXZWH.js} +12 -12
  160. package/dist/{open-library-I4F2UNOY.js.map → open-library-POFOXZWH.js.map} +1 -1
  161. package/dist/{paths-KBACHM5B.js → paths-ZLX4ITAQ.js} +6 -6
  162. package/dist/{paths-KBACHM5B.js.map → paths-ZLX4ITAQ.js.map} +1 -1
  163. package/dist/{plugin-store-CYSNVZIX.js → plugin-store-YIMNH2FH.js} +3 -3
  164. package/dist/{plugin-store-CYSNVZIX.js.map → plugin-store-YIMNH2FH.js.map} +1 -1
  165. package/dist/{project-SIHZBTGB.js → project-GVOPZEJZ.js} +9 -9
  166. package/dist/{project-SIHZBTGB.js.map → project-GVOPZEJZ.js.map} +1 -1
  167. package/dist/resolver/index.js +1 -1
  168. package/dist/runner/index.js +15 -12
  169. package/dist/runner/src/serve.d.ts +3 -1
  170. package/dist/runner/src/serve.d.ts.map +1 -1
  171. package/dist/sdk/asset-manager.js +5 -5
  172. package/dist/sdk/bridge.js +2 -2
  173. package/dist/sdk/core.js +1 -1
  174. package/dist/sdk/flow.js +4 -4
  175. package/dist/sdk/index.js +15 -12
  176. package/dist/sdk/index.js.map +1 -1
  177. package/dist/sdk/resolver.js +1 -1
  178. package/dist/sdk/runner.js +15 -12
  179. package/dist/sdk/session.js +2 -2
  180. package/dist/sdk/types.js +1 -1
  181. package/dist/session/index.js +2 -2
  182. package/dist/session/src/dispatcher.d.ts.map +1 -1
  183. package/dist/{setup-KMBXMV25.js → setup-RHWARBBB.js} +5 -5
  184. package/dist/{setup-KMBXMV25.js.map → setup-RHWARBBB.js.map} +1 -1
  185. package/dist/{skills-LHS4TOKK.js → skills-HGVH6UTA.js} +5 -5
  186. package/dist/{skills-LHS4TOKK.js.map → skills-HGVH6UTA.js.map} +1 -1
  187. package/dist/{source-X226ZJPR.js → source-FB3T4IHP.js} +13 -13
  188. package/dist/{source-X226ZJPR.js.map → source-FB3T4IHP.js.map} +1 -1
  189. package/dist/{source-manifest-publish-N2QVUR5U.js → source-manifest-publish-XTRDVPSC.js} +15 -15
  190. package/dist/{source-manifest-publish-N2QVUR5U.js.map → source-manifest-publish-XTRDVPSC.js.map} +1 -1
  191. package/dist/tui/index.js +15 -12
  192. package/dist/tui/index.js.map +1 -1
  193. package/dist/types/index.js +1 -1
  194. package/dist/types/src/events.d.ts +20 -1
  195. package/dist/types/src/events.d.ts.map +1 -1
  196. package/dist/types/src/flow.d.ts +26 -0
  197. package/dist/types/src/flow.d.ts.map +1 -1
  198. package/dist/types/src/index.d.ts +1 -1
  199. package/dist/types/src/index.d.ts.map +1 -1
  200. package/dist/types/src/protocol.d.ts +1 -0
  201. package/dist/types/src/protocol.d.ts.map +1 -1
  202. package/dist/types/src/version.d.ts +12 -1
  203. package/dist/types/src/version.d.ts.map +1 -1
  204. package/dist/workspace-plugin/adapters/mcp.js +4 -4
  205. package/dist/workspace-plugin/adapters/omp.js +7 -7
  206. package/dist/workspace-plugin/index.js +1 -1
  207. package/package.json +1 -1
  208. package/dist/chunk-63ZPKREA.js.map +0 -1
  209. package/dist/chunk-CA5CWLGN.js.map +0 -1
  210. package/dist/chunk-DZM33LQO.js.map +0 -1
  211. package/dist/chunk-FB4GPWYC.js.map +0 -1
  212. package/dist/chunk-LD6YGIKZ.js.map +0 -1
  213. package/dist/chunk-PTS3QN5P.js.map +0 -1
  214. package/dist/chunk-TAJBFYCT.js.map +0 -1
  215. package/dist/chunk-TK66CMXV.js.map +0 -1
  216. package/dist/chunk-UQ3RJKDD.js.map +0 -1
  217. package/dist/chunk-ZE2KEPE2.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#500](https://github.com/skaile-ai/workspaces/pull/500) [`5104b98`](https://github.com/skaile-ai/workspaces/commit/5104b980e3e7243f19142079257c5fc42be20722) Thanks [@peteralbert](https://github.com/peteralbert)! - Protocol 3.8 — acknowledge receipt of a prompt
8
+
9
+ `prompt` was the only frame in the protocol with no acknowledgement. A host sent
10
+ it into the socket and got nothing back, so a runner that had stopped executing
11
+ while its WebSocket stayed open (an in-container OOM is enough) swallowed every
12
+ subsequent user message with no trace anywhere: no log, no event, no failed
13
+ send. The host had no way to distinguish "delivered, still thinking" from "never
14
+ arrived".
15
+
16
+ The `prompt` command gains an optional `messageId`. When it is set the runner
17
+ emits exactly one new `prompt_ack` event echoing it back, as the **first act** of
18
+ `handlePrompt` — before `awaitSessionReadyOrFail()` (which drops prompts
19
+ silently), before the secrets check, and before the driver starts. When it is
20
+ absent nothing is emitted, so a 3.7 host sees byte-identical behaviour, and a
21
+ 3.8 host talking to a 3.7 runner must feature-detect on
22
+ `major === 3 && minor >= 8` rather than read the missing ack as a lost prompt.
23
+
24
+ The ack is deliberately receipt-only: it says the frame arrived and was decoded,
25
+ never that the turn ran. A driver that accepts a prompt and then leaves the turn
26
+ promise pending is a separate failure mode and needs turn-level supervision.
27
+
28
+ `prompt_ack` is transient — system RPC, not chat. It is on the dispatcher's
29
+ denylist so it is never persisted (an unlisted ack would also split the
30
+ assistant's streaming bubble mid-turn).
31
+
32
+ ### Patch Changes
33
+
34
+ - [#501](https://github.com/skaile-ai/workspaces/pull/501) [`1076360`](https://github.com/skaile-ai/workspaces/commit/107636009ebbbfeb7d82b93a2a8633f84dfc59b6) Thanks [@peteralbert](https://github.com/peteralbert)! - Fix `ClaudeSdkDriver` so aborting a turn (Stop) actually kills an in-flight
35
+ tool subprocess (e.g. a running Bash command) instead of leaving it orphaned.
36
+ `query.close()` only tore down the SDK's connection to the CLI subprocess, not
37
+ whatever process that CLI in turn spawned for the tool call, and a
38
+ process-group signal to the CLI didn't reach it either since the CLI spawns
39
+ the tool's shell into its own new process group. The driver now captures the
40
+ CLI subprocess's own OS pid via `spawnClaudeCodeProcess`, snapshots its
41
+ descendant processes before sending any abort signal (reparenting on exit
42
+ would otherwise make a post-abort snapshot come back empty), and SIGKILLs
43
+ that snapshot from `abort()` and `kill()` alike.
44
+
45
+ ## 2.4.0
46
+
47
+ ### Minor Changes
48
+
49
+ - [#497](https://github.com/skaile-ai/workspaces/pull/497) [`b282668`](https://github.com/skaile-ai/workspaces/commit/b2826683a48b4075e42e4a86704912cb9f9c8634) Thanks [@peteralbert](https://github.com/peteralbert)! - Add `sub-flow` as the seventh Flow v2 node kind and run delegated flows once,
50
+ inline, through the existing session agent, deterministic executor, gate, and
51
+ FlowAdapter lifecycles. Exact legacy sub-flow nodes now normalize into this
52
+ active shape, and dependency discovery emits duplicate-safe flow requirements.
53
+ Parked child interactions project onto the durable parent node while resumable
54
+ child adapters remain transient in memory; canonical identity, ancestry, and
55
+ depth guards bound delegation without adding child records or state fields.
56
+
57
+ - [#493](https://github.com/skaile-ai/workspaces/pull/493) [`6bcbbfd`](https://github.com/skaile-ai/workspaces/commit/6bcbbfd5697f5b2528944ff8954d11f28a8e9e48) Thanks [@peteralbert](https://github.com/peteralbert)! - Execute Flow v2 function nodes through the session workspace without starting a model turn, including non-success signal termination.
58
+
59
+ - [#496](https://github.com/skaile-ai/workspaces/pull/496) [`92da12c`](https://github.com/skaile-ai/workspaces/commit/92da12cd71ccf7b8e393880efa675ccf2c1f994d) Thanks [@peteralbert](https://github.com/peteralbert)! - Run strict Flow v2 gate nodes as mandatory runtime-owned human decisions that park without a model, complete on approval, and preserve rejection feedback for revision and re-request.
60
+
61
+ - [#495](https://github.com/skaile-ai/workspaces/pull/495) [`39c2da3`](https://github.com/skaile-ai/workspaces/commit/39c2da359591d6727f1b2a843d81dafb43912de0) Thanks [@peteralbert](https://github.com/peteralbert)! - Resolve Flow v2 node bindings with provenance, enforce preconditions, and validate downstream-consumed output fields with one durable agent correction.
62
+
3
63
  ## 2.3.0
4
64
 
5
65
  ### Minor Changes
package/README.md CHANGED
@@ -11,8 +11,8 @@ published package. Every internal folder is exposed as a subpath export and the
11
11
  | **core** | `@skaile/workspaces/core` | Shared types, manifest parsing, settings, store, framework config. Zero heavy deps. |
12
12
  | **types** | `@skaile/workspaces/types` | Unified message types, event types, and runtime interfaces. The shared contract. |
13
13
  | **bridge** | `@skaile/workspaces/bridge` | Pluggable agent driver abstraction. Backends: Claude SDK, Codex, OMP. |
14
- | **flow engine** | `@skaile/workspaces/factory-assets/connectors/flow/engine` | Flow state machine with active strict v2 agent nodes and legacy skill compatibility. |
15
- | **flow contract** | `@skaile/workspaces/factory-assets/connectors/flow/contract` | Strict Flow v2 authoring schemas, v1 normalization, bounded expressions, and canonical content identity. |
14
+ | **flow engine** | `@skaile/workspaces/factory-assets/connectors/flow/engine` | Flow state machine with typed bindings, precondition admission, consumed-output validation, active strict v2 agent/function/gate nodes, and legacy skill compatibility. |
15
+ | **flow contract** | `@skaile/workspaces/factory-assets/connectors/flow/contract` | Strict Flow v2 authoring schemas, v1 normalization, bounded expressions, binding origins, and canonical content identity. |
16
16
  | **resolver** | `@skaile/workspaces/resolver` | Parses SKILL.md requirements (reads_from, writes_to), validates gates, generates agent preamble. |
17
17
  | **client** | `@skaile/workspaces/client` | Framework-agnostic protocol client. Implements IAgentRuntime over a pluggable transport. |
18
18
  | **transport** | `@skaile/workspaces/transport` | Transport adapters (WebSocket client/server). Pluggable — add stdio, IPC, etc. |
@@ -56,9 +56,9 @@ cli (top-level CLI — orchestrates everything below)
56
56
 
57
57
  1. **User runs `skaile run <flow>`** → **cli** parses args, resolves the flow file, and delegates to **runner**.
58
58
 
59
- 2. **runner** loads the flow through the shared v2 parser, then the flow engine computes which agent nodes are available from completed/skipped state.
59
+ 2. **runner** loads the flow through the shared v2 parser, then the flow engine computes which agent, function, and gate nodes are available from completed/skipped state, hard edges, and deterministic preconditions.
60
60
 
61
- 3. For the selected agent node, the flow connector presents its instruction, supplied input bindings, reusable asset requirements, and predecessor context to the existing session agent.
61
+ 3. Available function nodes run with resolved bindings in the existing session workspace without a model turn. Available gate nodes park for a human decision without an executor or model. For an agent node, the flow connector presents its interpolated instruction, resolved values and origins, reusable asset requirements, and predecessor context to the session agent.
62
62
 
63
63
  4. **bridge** starts the LLM backend (Claude SDK or Codex in-process, or OMP as a subprocess) and streams `AgentEvent`s (defined in **types**) back to the runner.
64
64
 
@@ -84,20 +84,39 @@ import {
84
84
  } from "@skaile/workspaces/factory-assets/connectors/flow/contract"
85
85
  ```
86
86
 
87
- The contract describes six node kinds (`agent`, `subprompt`, `function`,
88
- `check`, `gate`, and `router`), rejects unknown v2 keys with precise paths, and
89
- normalizes recognized v1 definitions without changing their existing state trace.
87
+ Flow Engine v2 defines seven node kinds: `agent`, `subprompt`, `function`,
88
+ `check`, `gate`, `router`, and inline `sub-flow`. It
89
+ rejects unknown v2 keys with precise paths, and
90
+ normalizes recognized v1 definitions deterministically. Exact legacy sub-flow
91
+ nodes now activate, while every other legacy kind retains its existing
92
+ compatibility state trace.
90
93
  Structured output references are checked against declared producer fields, and
91
94
  gates carry a strict response schema. The generated schema is published as both
92
95
  `contract/flow.v2.schema.json` and `contract/schema.json`.
93
- Only explicit legacy `type: skill` nodes normalize to executable agents; every
94
- other legacy kind remains an inert compatibility placeholder with its IDs,
95
- parameters, assets, and dependency semantics retained.
96
- The runtime currently activates only `agent`: its first-class instruction and
97
- supplied bindings reach the session agent through `build_handoff(nodeId)`.
96
+ Explicit legacy `type: skill` and exact `type: sub-flow` nodes normalize to
97
+ their executable v2 kinds; every other legacy kind remains an inert
98
+ compatibility placeholder with its IDs, parameters, assets, and dependency
99
+ semantics retained.
100
+ The runtime activates `agent`, `function`, `gate`, and `sub-flow`. Interpolated agent instructions,
101
+ resolved values, and origins reach the session agent through `build_handoff(nodeId)`; function nodes
102
+ run shell, Node, or Python commands in the existing session workspace without a
103
+ model turn, with resolved values exported as environment variables. Gate nodes
104
+ park immediately through the durable approval state, complete on approval, and
105
+ preserve rejection feedback for revision and a later approval re-request.
106
+ Sub-flow nodes load one child inline through the same session, driver,
107
+ `NodeExecutor`, gate operations, and `FlowAdapter` lifecycle. Parked approval and
108
+ input interactions are published on the parent sub-flow node, while the child
109
+ adapter remains transient and resumable in memory. Canonical flow identities,
110
+ cycle detection, and a depth bound keep resolution contained. Sub-flows create
111
+ no run group, loop behavior, second executor, child record, or new durable-state
112
+ field.
113
+ Downstream-consumed output schemas are enforced at completion. Agents receive one
114
+ durable corrective call; deterministic functions fail directly. Unconsumed schemas
115
+ remain prose-only.
98
116
  Skills, connectors, mounts, MCP servers, and knowledge stay reusable
99
117
  `run.assets` requirements, and shared refs are deduplicated in the dependency
100
- graph. The other five authored kinds remain inert for later releases.
118
+ graph. This release activates `agent`, `function`, `gate`, and `sub-flow`; other current
119
+ kinds remain inert for later releases.
101
120
  `FlowDefinition` is the strict authored-v2 type; parser results use the wider
102
121
  `ParsedFlowDefinition` so unknown legacy edge kinds and their metadata can be
103
122
  retained inertly without weakening authored validation.
@@ -1,4 +1,4 @@
1
- import { PROTOCOL_VERSION_STRING } from '../chunk-RDE43ORT.js';
1
+ import { PROTOCOL_VERSION_STRING } from '../chunk-D4VHBAJC.js';
2
2
  import '../chunk-NSBPE2FW.js';
3
3
  import * as z from 'zod';
4
4
 
@@ -1,6 +1,6 @@
1
- import { assignDomain, buildDomainIndex, DOMAIN_MD_FILENAME, parseDomainMd } from './chunk-TK66CMXV.js';
1
+ import { assignDomain, buildDomainIndex, DOMAIN_MD_FILENAME, parseDomainMd } from './chunk-NU4E7C5I.js';
2
2
  import './chunk-JXT46QFC.js';
3
- import './chunk-63ZPKREA.js';
3
+ import './chunk-P43HBWKK.js';
4
4
  import './chunk-F3HNW354.js';
5
5
  import './chunk-K5GBV4SA.js';
6
6
  import './chunk-ZAP2BDHG.js';
@@ -65,7 +65,7 @@ async function libraryAssets() {
65
65
  const domains = [];
66
66
  try {
67
67
  const [{ openLibraryManager }, { skaileHomeDir }, { getGlobalCacheDir }] = await Promise.all([
68
- import('./open-library-I4F2UNOY.js'),
68
+ import('./open-library-POFOXZWH.js'),
69
69
  import('./library/index.js'),
70
70
  import('./core/index.js')
71
71
  ]);
@@ -98,7 +98,7 @@ async function libraryAssets() {
98
98
  async function storeAssets(projectDir) {
99
99
  const notes = [];
100
100
  try {
101
- const { resolveCatalogSource } = await import('./open-library-I4F2UNOY.js');
101
+ const { resolveCatalogSource } = await import('./open-library-POFOXZWH.js');
102
102
  const { source, close } = await resolveCatalogSource({ projectDir });
103
103
  try {
104
104
  const assets = await source.listAssets();
@@ -202,5 +202,5 @@ function dedupeBySlug(domains) {
202
202
  }
203
203
 
204
204
  export { gatherAssetFeeds };
205
- //# sourceMappingURL=asset-feeds-DKNYRLED.js.map
206
- //# sourceMappingURL=asset-feeds-DKNYRLED.js.map
205
+ //# sourceMappingURL=asset-feeds-4AWAT5PW.js.map
206
+ //# sourceMappingURL=asset-feeds-4AWAT5PW.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../cli/src/asset-feeds.ts"],"names":[],"mappings":"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","file":"asset-feeds-DKNYRLED.js","sourcesContent":["/**\n * asset-feeds.ts — gather installable assets from every feed the manage TUI\n * surfaces.\n *\n * Four feeds, normalized into `CatalogEntry`:\n * - **Project sources** — `skaile.yaml` `sources:` entries (scanned via\n * `AssetManager.search()`; the new `sources:` field is merged into the\n * legacy repositories map in `AssetManager.loadConfig`).\n * - **Local libraries** — user authoring places from `LocalIndex` (filesystem\n * scan of each library's `path`).\n * - **Factory assets** — the bundled first-party `factory-assets` tree\n * (`factoryAssetRoots()`), scanned from disk. Always present; no network.\n * - **Remote store** — the configured catalog source when `catalog.url` is\n * not `local`. Failures are non-fatal — they surface as notes.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n type AssetKind,\n type CatalogDomain,\n type CatalogEntry,\n factoryAssetRoots,\n parseFrontmatter,\n scanRepo,\n walkDir,\n} from \"@skaile/workspaces/core\";\nimport {\n assignDomain,\n buildDomainIndex,\n DOMAIN_MD_FILENAME,\n type DomainMd,\n parseDomainMd,\n} from \"@skaile/workspaces/discovery\";\n\n/** One non-fatal note returned to the caller for surfacing in the UI. */\nexport interface FeedNote {\n feed: \"sources\" | \"libraries\" | \"store\" | \"factory\";\n message: string;\n}\n\n/**\n * Which feed an asset came from. This is the top-level grouping axis in the\n * manage TUI (\"where is this skill coming from?\") and is intentionally distinct\n * from `publisher` (asset-intrinsic identity): the same publisher can legitimately\n * appear under more than one origin (e.g. a github repo you added as a `source`\n * that is also listed in the remote `store`).\n */\nexport type AssetOrigin = \"source\" | \"library\" | \"factory\" | \"store\";\n\n/** A catalog entry tagged with the feed it was gathered from. */\nexport interface OriginEntry {\n origin: AssetOrigin;\n entry: CatalogEntry;\n /**\n * Second-level grouping label for the manage tree. Defaults (when unset) to the\n * entry's `publisher`; the source feed sets it to the source's short repo name\n * so the Sources band groups by source — \"which repo did this come from\" — not\n * by the asset-intrinsic publisher.\n */\n group?: string;\n}\n\nexport interface AssetFeedResult {\n entries: OriginEntry[];\n /**\n * Display-only domain metadata, keyed (by the consumer) on `slug`. Populated\n * from the store's `catalog.listDomains` when the connected catalog source\n * exposes it; empty otherwise. The manage TUI joins `entry.domainSlug` → a\n * domain for grouping headers, descriptions, and relation badges.\n */\n domains: CatalogDomain[];\n notes: FeedNote[];\n}\n\n// ── Domain stamping (on-disk feeds) ──────────────────────────────────────────\n//\n// `scanRepo` (the source / library / factory scan path) does not read the\n// `DOMAIN.md` sidecars, so its entries arrive with no `domainSlug` and render\n// flat under their publisher band. We re-derive the grouping here from the\n// root's `DOMAIN.md` files (the same sidecars the remote store computes\n// server-side), and fall back to the repo/source name for any entry no domain\n// claims so it still groups instead of landing in the synthetic \"no domain\" pile.\n\n/** Repo-relative POSIX path (DOMAIN.md slugs are matched whole-segment, `/`-joined). */\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\n/** Parse every `DOMAIN.md` sidecar under `root` into a {@link DomainMd}. */\nasync function loadDomainMds(root: string): Promise<DomainMd[]> {\n const out: DomainMd[] = [];\n for await (const { path: filePath, name } of walkDir(root)) {\n if (name !== DOMAIN_MD_FILENAME) continue;\n try {\n const { data } = parseFrontmatter(readFileSync(filePath, \"utf8\"));\n out.push(parseDomainMd(toPosix(path.relative(root, filePath)), data));\n } catch {}\n }\n return out;\n}\n\n/**\n * Stamp `domainSlug` onto every entry scanned from `root` using the root's\n * `DOMAIN.md` sidecars. Returns the `CatalogDomain` registry for the real\n * domains, plus a synthetic fallback domain when any entry used the fallback.\n * Mutates `entries` in place (sets `entry.domainSlug`).\n *\n * `synthesizeFallback` (default true): entries no domain claims fall back to\n * `fallbackSlug` (the repo/source name). Pass `false` when the caller already\n * groups these entries one level up (e.g. the source feed groups by source\n * node) — unmatched entries then get no domain (`\"\"`) and hang directly under\n * that parent, instead of nesting under a same-named fallback domain.\n */\nasync function stampDomains(\n root: string,\n fallbackSlug: string,\n entries: CatalogEntry[],\n synthesizeFallback = true,\n): Promise<CatalogDomain[]> {\n const domainMds = await loadDomainMds(root);\n const assignables = entries.map((entry) => ({\n entry,\n ref: `${entry.kind}:${entry.name}`,\n relativePath: toPosix(path.relative(root, entry.source)),\n }));\n\n let usedFallback = false;\n for (const a of assignables) {\n const slug = assignDomain(a.relativePath, domainMds);\n if (slug) a.entry.domainSlug = slug;\n else if (synthesizeFallback) {\n a.entry.domainSlug = fallbackSlug;\n usedFallback = true;\n } else a.entry.domainSlug = \"\";\n }\n\n const { index } = buildDomainIndex(assignables, domainMds);\n const catalog: CatalogDomain[] = Object.values(index).map((d) => ({\n slug: d.slug,\n title: d.title,\n ...(d.description ? { description: d.description } : {}),\n ...(d.relation ? { relation: d.relation } : {}),\n ...(d.order !== undefined ? { order: d.order } : {}),\n ...(d.recommended[0] ? { recommendedRef: d.recommended[0] } : {}),\n }));\n if (usedFallback) catalog.push({ slug: fallbackSlug, title: fallbackSlug });\n return catalog;\n}\n\n/**\n * Pull assets from local libraries (the authoring places under\n * `~/.skaile/libraries/<name>/`, not the `~/.skaile/cache/sources/` cache). Each\n * library's `path` is scanned with `scanRepo` and grouped under the bare library\n * name (`group: <name>`) so the manage TUI shows a clean `<name>` band — no\n * `library:` prefix and no redundant same-named fallback domain (mirrors the\n * source feed).\n */\nasync function libraryAssets(): Promise<{\n entries: OriginEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const [{ openLibraryManager }, { skaileHomeDir }, { getGlobalCacheDir }] = await Promise.all([\n import(\"./open-library.ts\"),\n import(\"@skaile/workspaces/library\"),\n import(\"@skaile/workspaces/core\"),\n ]);\n const { manager, close } = await openLibraryManager();\n try {\n // Authoring libraries are the rows that are NOT source clones. index.db\n // rows predating the cache migration still carry the legacy\n // ~/.skaile/sources path, so exclude both roots.\n const newRoot = getGlobalCacheDir();\n const legacyRoot = path.join(skaileHomeDir(), \"sources\");\n const libs = (await manager.listLibraries()).filter(\n (l) => !l.path.startsWith(newRoot) && !l.path.startsWith(legacyRoot),\n );\n const entries: OriginEntry[] = [];\n for (const lib of libs) {\n if (!existsSync(lib.path)) continue;\n // Scan + group under the bare library name. `synthesizeFallback: false`\n // (as the source feed does) keeps domainless assets directly under the\n // library band instead of a redundant same-named fallback domain.\n const libEntries = [...(await scanRepo(lib.path, lib.name))];\n domains.push(...(await stampDomains(lib.path, lib.name, libEntries, false)));\n for (const entry of libEntries) entries.push({ origin: \"library\", entry, group: lib.name });\n }\n return { entries, domains, notes };\n } finally {\n close();\n }\n } catch (err) {\n notes.push({\n feed: \"libraries\",\n message: err instanceof Error ? err.message : String(err),\n });\n return { entries: [], domains, notes };\n }\n}\n\n/**\n * Pull a (best-effort) snapshot of the connected remote store catalog.\n *\n * - Returns no entries when the resolved catalog is local-only or the host\n * is unreachable (the failure becomes a note, not an exception).\n * - Maps `CatalogAsset` → `CatalogEntry`, carrying `publisher`, `repo`, and\n * `category` straight through and using `source = \"store://<id>\"`; per-file\n * `source` paths are not available for remote assets so the canonical ref is\n * a stable placeholder.\n */\nasync function storeAssets(projectDir: string): Promise<{\n entries: CatalogEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n try {\n const { resolveCatalogSource } = await import(\"./open-library.ts\");\n const { source, close } = await resolveCatalogSource({ projectDir });\n try {\n const assets = await source.listAssets();\n const entries: CatalogEntry[] = assets.map((a) => ({\n name: a.name,\n kind: (a.kind as AssetKind) ?? \"skill\",\n description: a.description ?? \"\",\n source: `store://${a.id}`,\n publisher: a.publisher,\n repo: a.repo,\n domainSlug: a.domainSlug,\n category: a.category,\n version: a.version,\n requires: [],\n dependencies: [],\n }));\n // Domains are optional on the catalog source — only the store backend\n // (post Part 4 `catalog.listDomains`) exposes them. Duck-type the call so\n // older sources and the local catalog degrade to no domains, not an error.\n const domains = await loadDomains(source);\n return { entries, domains, notes };\n } finally {\n close();\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Local-only catalog throws a recognizable message — silence it; absence\n // is the user's choice, not an error.\n if (!/local/i.test(msg)) {\n notes.push({ feed: \"store\", message: msg });\n }\n return { entries: [], domains: [], notes };\n }\n}\n\n/** Optional `listDomains` capability — returns `[]` when the source lacks it. */\nasync function loadDomains(source: unknown): Promise<CatalogDomain[]> {\n const fn = (source as { listDomains?: () => Promise<CatalogDomain[]> }).listDomains;\n if (typeof fn !== \"function\") return [];\n const domains = await fn.call(source);\n return Array.isArray(domains) ? domains : [];\n}\n\n/** The slice of `AssetManager` the source feed needs (kept narrow for tests). */\ninterface AssetManagerLike {\n search: () => Promise<CatalogEntry[]>;\n searchRepoRoots: () => { repoName: string; root: string }[];\n}\n\n/**\n * Project-source feed: leans on `AssetManager.search()`, which now merges\n * `skaile.yaml`'s `sources:` field into the repositories map, then stamps\n * `DOMAIN.md`-derived domain grouping onto the scanned entries via\n * `searchRepoRoots()`.\n */\nasync function sourceAssets(\n am: AssetManagerLike,\n): Promise<{ entries: OriginEntry[]; domains: CatalogDomain[] }> {\n // The bundled factory tree is injected into the repositories map as an implicit\n // source, so `am.search()` returns it too. It has its own `factory` origin band,\n // so drop it here — otherwise the same first-party skills list twice under Sources.\n const factoryPrefixes = factoryAssetRoots().map((r) => (r.endsWith(path.sep) ? r : r + path.sep));\n const isFactoryPath = (p: string) =>\n factoryPrefixes.some((fp) => p === fp.slice(0, -1) || p.startsWith(fp));\n\n const all = (await am.search()).filter((e) => !isFactoryPath(e.source));\n const entries: OriginEntry[] = [];\n const domains: CatalogDomain[] = [];\n // Group each source's entries under the source node (its short repo name), not\n // the publisher: under Sources the navigational question is \"which repo did\n // this come from\". Each entry's `source` (absolute manifest path) lives under\n // exactly one declared root, so group + stamp by prefix. The cache slug is\n // URL-keyed `github.com/<org>/<repo>` — take the trailing segment.\n for (const { repoName, root } of am.searchRepoRoots()) {\n if (isFactoryPath(root)) continue;\n const prefix = root.endsWith(path.sep) ? root : root + path.sep;\n const owned = all.filter((e) => e.source === root || e.source.startsWith(prefix));\n if (owned.length === 0) continue;\n const sourceName = repoName.split(\"/\").pop() || repoName;\n // No synthetic fallback domain — the source node already groups these, so a\n // same-named fallback would just nest the source under itself.\n domains.push(...(await stampDomains(root, sourceName, owned, false)));\n for (const entry of owned) entries.push({ origin: \"source\", entry, group: sourceName });\n }\n return { entries, domains };\n}\n\n/**\n * Factory-asset feed: scan the bundled first-party `factory-assets` tree(s)\n * resolved by `factoryAssetRoots()` (the `SKAILE_FACTORY_ASSETS_DIR` override\n * layer ahead of the bundled tree). Local disk only, no network — the entries are\n * the built-in skills / connectors / bundles that ship with `@skaile/workspaces`.\n * `factoryAssetRoots()` already dedupes, so the env override pointing at the\n * bundled dir collapses to one root.\n */\nasync function factoryAssets(): Promise<{\n entries: CatalogEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const entries: CatalogEntry[] = [];\n for (const root of factoryAssetRoots()) {\n const rootEntries = [...(await scanRepo(root, \"skaile-factory\"))];\n domains.push(...(await stampDomains(root, \"skaile-factory\", rootEntries)));\n entries.push(...rootEntries);\n }\n return { entries, domains, notes };\n } catch (err) {\n notes.push({\n feed: \"factory\",\n message: err instanceof Error ? err.message : String(err),\n });\n return { entries: [], domains, notes };\n }\n}\n\n/** Tag a flat entry list with a single origin. */\nfunction tag(origin: AssetOrigin, entries: CatalogEntry[]): OriginEntry[] {\n return entries.map((entry) => ({ origin, entry }));\n}\n\n/**\n * Run all feeds and return a single normalized result.\n *\n * Feeds run in parallel where possible — the store fetch is the slow one\n * (one HTTP call) and is fired alongside the local library walk. The factory\n * scan is local disk only, so it runs inline.\n */\nexport async function gatherAssetFeeds(\n am: AssetManagerLike,\n projectDir: string,\n): Promise<AssetFeedResult> {\n const [libs, store] = await Promise.all([libraryAssets(), storeAssets(projectDir)]);\n const factory = await factoryAssets();\n const src = await sourceAssets(am);\n return {\n entries: [\n ...src.entries,\n ...libs.entries,\n ...tag(\"factory\", factory.entries),\n ...tag(\"store\", store.entries),\n ],\n // Store domains first (server-curated), then the on-disk feeds; dedupe by\n // slug, first-wins, so a store-served domain shadows a same-slug local one.\n domains: dedupeBySlug([...store.domains, ...src.domains, ...libs.domains, ...factory.domains]),\n notes: [...libs.notes, ...store.notes, ...factory.notes],\n };\n}\n\n/** First-wins dedupe of a domain list by `slug`. */\nfunction dedupeBySlug(domains: CatalogDomain[]): CatalogDomain[] {\n const seen = new Set<string>();\n const out: CatalogDomain[] = [];\n for (const d of domains) {\n if (seen.has(d.slug)) continue;\n seen.add(d.slug);\n out.push(d);\n }\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../cli/src/asset-feeds.ts"],"names":[],"mappings":"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","file":"asset-feeds-4AWAT5PW.js","sourcesContent":["/**\n * asset-feeds.ts — gather installable assets from every feed the manage TUI\n * surfaces.\n *\n * Four feeds, normalized into `CatalogEntry`:\n * - **Project sources** — `skaile.yaml` `sources:` entries (scanned via\n * `AssetManager.search()`; the new `sources:` field is merged into the\n * legacy repositories map in `AssetManager.loadConfig`).\n * - **Local libraries** — user authoring places from `LocalIndex` (filesystem\n * scan of each library's `path`).\n * - **Factory assets** — the bundled first-party `factory-assets` tree\n * (`factoryAssetRoots()`), scanned from disk. Always present; no network.\n * - **Remote store** — the configured catalog source when `catalog.url` is\n * not `local`. Failures are non-fatal — they surface as notes.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n type AssetKind,\n type CatalogDomain,\n type CatalogEntry,\n factoryAssetRoots,\n parseFrontmatter,\n scanRepo,\n walkDir,\n} from \"@skaile/workspaces/core\";\nimport {\n assignDomain,\n buildDomainIndex,\n DOMAIN_MD_FILENAME,\n type DomainMd,\n parseDomainMd,\n} from \"@skaile/workspaces/discovery\";\n\n/** One non-fatal note returned to the caller for surfacing in the UI. */\nexport interface FeedNote {\n feed: \"sources\" | \"libraries\" | \"store\" | \"factory\";\n message: string;\n}\n\n/**\n * Which feed an asset came from. This is the top-level grouping axis in the\n * manage TUI (\"where is this skill coming from?\") and is intentionally distinct\n * from `publisher` (asset-intrinsic identity): the same publisher can legitimately\n * appear under more than one origin (e.g. a github repo you added as a `source`\n * that is also listed in the remote `store`).\n */\nexport type AssetOrigin = \"source\" | \"library\" | \"factory\" | \"store\";\n\n/** A catalog entry tagged with the feed it was gathered from. */\nexport interface OriginEntry {\n origin: AssetOrigin;\n entry: CatalogEntry;\n /**\n * Second-level grouping label for the manage tree. Defaults (when unset) to the\n * entry's `publisher`; the source feed sets it to the source's short repo name\n * so the Sources band groups by source — \"which repo did this come from\" — not\n * by the asset-intrinsic publisher.\n */\n group?: string;\n}\n\nexport interface AssetFeedResult {\n entries: OriginEntry[];\n /**\n * Display-only domain metadata, keyed (by the consumer) on `slug`. Populated\n * from the store's `catalog.listDomains` when the connected catalog source\n * exposes it; empty otherwise. The manage TUI joins `entry.domainSlug` → a\n * domain for grouping headers, descriptions, and relation badges.\n */\n domains: CatalogDomain[];\n notes: FeedNote[];\n}\n\n// ── Domain stamping (on-disk feeds) ──────────────────────────────────────────\n//\n// `scanRepo` (the source / library / factory scan path) does not read the\n// `DOMAIN.md` sidecars, so its entries arrive with no `domainSlug` and render\n// flat under their publisher band. We re-derive the grouping here from the\n// root's `DOMAIN.md` files (the same sidecars the remote store computes\n// server-side), and fall back to the repo/source name for any entry no domain\n// claims so it still groups instead of landing in the synthetic \"no domain\" pile.\n\n/** Repo-relative POSIX path (DOMAIN.md slugs are matched whole-segment, `/`-joined). */\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\n/** Parse every `DOMAIN.md` sidecar under `root` into a {@link DomainMd}. */\nasync function loadDomainMds(root: string): Promise<DomainMd[]> {\n const out: DomainMd[] = [];\n for await (const { path: filePath, name } of walkDir(root)) {\n if (name !== DOMAIN_MD_FILENAME) continue;\n try {\n const { data } = parseFrontmatter(readFileSync(filePath, \"utf8\"));\n out.push(parseDomainMd(toPosix(path.relative(root, filePath)), data));\n } catch {}\n }\n return out;\n}\n\n/**\n * Stamp `domainSlug` onto every entry scanned from `root` using the root's\n * `DOMAIN.md` sidecars. Returns the `CatalogDomain` registry for the real\n * domains, plus a synthetic fallback domain when any entry used the fallback.\n * Mutates `entries` in place (sets `entry.domainSlug`).\n *\n * `synthesizeFallback` (default true): entries no domain claims fall back to\n * `fallbackSlug` (the repo/source name). Pass `false` when the caller already\n * groups these entries one level up (e.g. the source feed groups by source\n * node) — unmatched entries then get no domain (`\"\"`) and hang directly under\n * that parent, instead of nesting under a same-named fallback domain.\n */\nasync function stampDomains(\n root: string,\n fallbackSlug: string,\n entries: CatalogEntry[],\n synthesizeFallback = true,\n): Promise<CatalogDomain[]> {\n const domainMds = await loadDomainMds(root);\n const assignables = entries.map((entry) => ({\n entry,\n ref: `${entry.kind}:${entry.name}`,\n relativePath: toPosix(path.relative(root, entry.source)),\n }));\n\n let usedFallback = false;\n for (const a of assignables) {\n const slug = assignDomain(a.relativePath, domainMds);\n if (slug) a.entry.domainSlug = slug;\n else if (synthesizeFallback) {\n a.entry.domainSlug = fallbackSlug;\n usedFallback = true;\n } else a.entry.domainSlug = \"\";\n }\n\n const { index } = buildDomainIndex(assignables, domainMds);\n const catalog: CatalogDomain[] = Object.values(index).map((d) => ({\n slug: d.slug,\n title: d.title,\n ...(d.description ? { description: d.description } : {}),\n ...(d.relation ? { relation: d.relation } : {}),\n ...(d.order !== undefined ? { order: d.order } : {}),\n ...(d.recommended[0] ? { recommendedRef: d.recommended[0] } : {}),\n }));\n if (usedFallback) catalog.push({ slug: fallbackSlug, title: fallbackSlug });\n return catalog;\n}\n\n/**\n * Pull assets from local libraries (the authoring places under\n * `~/.skaile/libraries/<name>/`, not the `~/.skaile/cache/sources/` cache). Each\n * library's `path` is scanned with `scanRepo` and grouped under the bare library\n * name (`group: <name>`) so the manage TUI shows a clean `<name>` band — no\n * `library:` prefix and no redundant same-named fallback domain (mirrors the\n * source feed).\n */\nasync function libraryAssets(): Promise<{\n entries: OriginEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const [{ openLibraryManager }, { skaileHomeDir }, { getGlobalCacheDir }] = await Promise.all([\n import(\"./open-library.ts\"),\n import(\"@skaile/workspaces/library\"),\n import(\"@skaile/workspaces/core\"),\n ]);\n const { manager, close } = await openLibraryManager();\n try {\n // Authoring libraries are the rows that are NOT source clones. index.db\n // rows predating the cache migration still carry the legacy\n // ~/.skaile/sources path, so exclude both roots.\n const newRoot = getGlobalCacheDir();\n const legacyRoot = path.join(skaileHomeDir(), \"sources\");\n const libs = (await manager.listLibraries()).filter(\n (l) => !l.path.startsWith(newRoot) && !l.path.startsWith(legacyRoot),\n );\n const entries: OriginEntry[] = [];\n for (const lib of libs) {\n if (!existsSync(lib.path)) continue;\n // Scan + group under the bare library name. `synthesizeFallback: false`\n // (as the source feed does) keeps domainless assets directly under the\n // library band instead of a redundant same-named fallback domain.\n const libEntries = [...(await scanRepo(lib.path, lib.name))];\n domains.push(...(await stampDomains(lib.path, lib.name, libEntries, false)));\n for (const entry of libEntries) entries.push({ origin: \"library\", entry, group: lib.name });\n }\n return { entries, domains, notes };\n } finally {\n close();\n }\n } catch (err) {\n notes.push({\n feed: \"libraries\",\n message: err instanceof Error ? err.message : String(err),\n });\n return { entries: [], domains, notes };\n }\n}\n\n/**\n * Pull a (best-effort) snapshot of the connected remote store catalog.\n *\n * - Returns no entries when the resolved catalog is local-only or the host\n * is unreachable (the failure becomes a note, not an exception).\n * - Maps `CatalogAsset` → `CatalogEntry`, carrying `publisher`, `repo`, and\n * `category` straight through and using `source = \"store://<id>\"`; per-file\n * `source` paths are not available for remote assets so the canonical ref is\n * a stable placeholder.\n */\nasync function storeAssets(projectDir: string): Promise<{\n entries: CatalogEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n try {\n const { resolveCatalogSource } = await import(\"./open-library.ts\");\n const { source, close } = await resolveCatalogSource({ projectDir });\n try {\n const assets = await source.listAssets();\n const entries: CatalogEntry[] = assets.map((a) => ({\n name: a.name,\n kind: (a.kind as AssetKind) ?? \"skill\",\n description: a.description ?? \"\",\n source: `store://${a.id}`,\n publisher: a.publisher,\n repo: a.repo,\n domainSlug: a.domainSlug,\n category: a.category,\n version: a.version,\n requires: [],\n dependencies: [],\n }));\n // Domains are optional on the catalog source — only the store backend\n // (post Part 4 `catalog.listDomains`) exposes them. Duck-type the call so\n // older sources and the local catalog degrade to no domains, not an error.\n const domains = await loadDomains(source);\n return { entries, domains, notes };\n } finally {\n close();\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Local-only catalog throws a recognizable message — silence it; absence\n // is the user's choice, not an error.\n if (!/local/i.test(msg)) {\n notes.push({ feed: \"store\", message: msg });\n }\n return { entries: [], domains: [], notes };\n }\n}\n\n/** Optional `listDomains` capability — returns `[]` when the source lacks it. */\nasync function loadDomains(source: unknown): Promise<CatalogDomain[]> {\n const fn = (source as { listDomains?: () => Promise<CatalogDomain[]> }).listDomains;\n if (typeof fn !== \"function\") return [];\n const domains = await fn.call(source);\n return Array.isArray(domains) ? domains : [];\n}\n\n/** The slice of `AssetManager` the source feed needs (kept narrow for tests). */\ninterface AssetManagerLike {\n search: () => Promise<CatalogEntry[]>;\n searchRepoRoots: () => { repoName: string; root: string }[];\n}\n\n/**\n * Project-source feed: leans on `AssetManager.search()`, which now merges\n * `skaile.yaml`'s `sources:` field into the repositories map, then stamps\n * `DOMAIN.md`-derived domain grouping onto the scanned entries via\n * `searchRepoRoots()`.\n */\nasync function sourceAssets(\n am: AssetManagerLike,\n): Promise<{ entries: OriginEntry[]; domains: CatalogDomain[] }> {\n // The bundled factory tree is injected into the repositories map as an implicit\n // source, so `am.search()` returns it too. It has its own `factory` origin band,\n // so drop it here — otherwise the same first-party skills list twice under Sources.\n const factoryPrefixes = factoryAssetRoots().map((r) => (r.endsWith(path.sep) ? r : r + path.sep));\n const isFactoryPath = (p: string) =>\n factoryPrefixes.some((fp) => p === fp.slice(0, -1) || p.startsWith(fp));\n\n const all = (await am.search()).filter((e) => !isFactoryPath(e.source));\n const entries: OriginEntry[] = [];\n const domains: CatalogDomain[] = [];\n // Group each source's entries under the source node (its short repo name), not\n // the publisher: under Sources the navigational question is \"which repo did\n // this come from\". Each entry's `source` (absolute manifest path) lives under\n // exactly one declared root, so group + stamp by prefix. The cache slug is\n // URL-keyed `github.com/<org>/<repo>` — take the trailing segment.\n for (const { repoName, root } of am.searchRepoRoots()) {\n if (isFactoryPath(root)) continue;\n const prefix = root.endsWith(path.sep) ? root : root + path.sep;\n const owned = all.filter((e) => e.source === root || e.source.startsWith(prefix));\n if (owned.length === 0) continue;\n const sourceName = repoName.split(\"/\").pop() || repoName;\n // No synthetic fallback domain — the source node already groups these, so a\n // same-named fallback would just nest the source under itself.\n domains.push(...(await stampDomains(root, sourceName, owned, false)));\n for (const entry of owned) entries.push({ origin: \"source\", entry, group: sourceName });\n }\n return { entries, domains };\n}\n\n/**\n * Factory-asset feed: scan the bundled first-party `factory-assets` tree(s)\n * resolved by `factoryAssetRoots()` (the `SKAILE_FACTORY_ASSETS_DIR` override\n * layer ahead of the bundled tree). Local disk only, no network — the entries are\n * the built-in skills / connectors / bundles that ship with `@skaile/workspaces`.\n * `factoryAssetRoots()` already dedupes, so the env override pointing at the\n * bundled dir collapses to one root.\n */\nasync function factoryAssets(): Promise<{\n entries: CatalogEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const entries: CatalogEntry[] = [];\n for (const root of factoryAssetRoots()) {\n const rootEntries = [...(await scanRepo(root, \"skaile-factory\"))];\n domains.push(...(await stampDomains(root, \"skaile-factory\", rootEntries)));\n entries.push(...rootEntries);\n }\n return { entries, domains, notes };\n } catch (err) {\n notes.push({\n feed: \"factory\",\n message: err instanceof Error ? err.message : String(err),\n });\n return { entries: [], domains, notes };\n }\n}\n\n/** Tag a flat entry list with a single origin. */\nfunction tag(origin: AssetOrigin, entries: CatalogEntry[]): OriginEntry[] {\n return entries.map((entry) => ({ origin, entry }));\n}\n\n/**\n * Run all feeds and return a single normalized result.\n *\n * Feeds run in parallel where possible — the store fetch is the slow one\n * (one HTTP call) and is fired alongside the local library walk. The factory\n * scan is local disk only, so it runs inline.\n */\nexport async function gatherAssetFeeds(\n am: AssetManagerLike,\n projectDir: string,\n): Promise<AssetFeedResult> {\n const [libs, store] = await Promise.all([libraryAssets(), storeAssets(projectDir)]);\n const factory = await factoryAssets();\n const src = await sourceAssets(am);\n return {\n entries: [\n ...src.entries,\n ...libs.entries,\n ...tag(\"factory\", factory.entries),\n ...tag(\"store\", store.entries),\n ],\n // Store domains first (server-curated), then the on-disk feeds; dedupe by\n // slug, first-wins, so a store-served domain shadows a same-slug local one.\n domains: dedupeBySlug([...store.domains, ...src.domains, ...libs.domains, ...factory.domains]),\n notes: [...libs.notes, ...store.notes, ...factory.notes],\n };\n}\n\n/** First-wins dedupe of a domain list by `slug`. */\nfunction dedupeBySlug(domains: CatalogDomain[]): CatalogDomain[] {\n const seen = new Set<string>();\n const out: CatalogDomain[] = [];\n for (const d of domains) {\n if (seen.has(d.slug)) continue;\n seen.add(d.slug);\n out.push(d);\n }\n return out;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  export { WORKSPACE_MOUNT_ENV_VAR, deployAll, deployCatalogEntry, findAiAssetsRoot, isUnderWorkspaceMount, resolveFlowSkills, undeployCatalogEntry, workspaceMountDirs } from '../chunk-BEAIJRVF.js';
2
- import '../chunk-63ZPKREA.js';
2
+ import '../chunk-P43HBWKK.js';
3
3
  import '../chunk-F3HNW354.js';
4
4
  import '../chunk-K5GBV4SA.js';
5
5
  import '../chunk-ZAP2BDHG.js';
@@ -1,12 +1,12 @@
1
- export { AssetManager, FACTORY_SOURCE_URL, MissingManifestError, NON_DEPLOYABLE_KINDS, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, softDepName } from '../chunk-CILACPUC.js';
2
- import '../chunk-W3Q37HEF.js';
3
- import '../chunk-TK66CMXV.js';
1
+ export { AssetManager, FACTORY_SOURCE_URL, MissingManifestError, NON_DEPLOYABLE_KINDS, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, softDepName } from '../chunk-WSARNOPP.js';
2
+ import '../chunk-3HVASHBF.js';
3
+ import '../chunk-NU4E7C5I.js';
4
4
  import '../chunk-UKJMQZV6.js';
5
5
  import '../chunk-R7FOF242.js';
6
6
  import '../chunk-JXT46QFC.js';
7
- export { createScaffold, deployAll, installAgent, removeAsset } from '../chunk-CERGXDGG.js';
7
+ export { createScaffold, deployAll, installAgent, removeAsset } from '../chunk-2OCYS5EP.js';
8
8
  export { BUILT_IN_FRAGMENTS, loadPromptExtensions, resolveFragments } from '../chunk-CGYEHQOX.js';
9
- import '../chunk-63ZPKREA.js';
9
+ import '../chunk-P43HBWKK.js';
10
10
  import '../chunk-F3HNW354.js';
11
11
  import '../chunk-K5GBV4SA.js';
12
12
  import '../chunk-ZAP2BDHG.js';
@@ -1,6 +1,6 @@
1
- export { createScaffold, deployAll, deployAsset, removeAsset } from '../chunk-CERGXDGG.js';
1
+ export { createScaffold, deployAll, deployAsset, removeAsset } from '../chunk-2OCYS5EP.js';
2
2
  import '../chunk-CGYEHQOX.js';
3
- import '../chunk-63ZPKREA.js';
3
+ import '../chunk-P43HBWKK.js';
4
4
  import '../chunk-F3HNW354.js';
5
5
  import '../chunk-K5GBV4SA.js';
6
6
  import '../chunk-ZAP2BDHG.js';
@@ -2,9 +2,9 @@ import { SUPPORTED_CLOUDS, buildClaudeCloudEnv } from '../../chunk-ISQWMBKD.js';
2
2
  import { dispatchCapability } from '../../chunk-RRVQAE5D.js';
3
3
  import { fetchProviderModels } from '../../chunk-BMQKMOQY.js';
4
4
  import { AuthError, classifyClaudeSdkError, reclassifyAuthAsAccountUnavailable, AccountUnavailableError } from '../../chunk-GAH4YRCI.js';
5
- import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-WG3L5VHF.js';
5
+ import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-HY3MBSNC.js';
6
6
  import '../../chunk-6E6PKKAD.js';
7
- import '../../chunk-63ZPKREA.js';
7
+ import '../../chunk-P43HBWKK.js';
8
8
  import '../../chunk-F3HNW354.js';
9
9
  import '../../chunk-K5GBV4SA.js';
10
10
  import '../../chunk-ZAP2BDHG.js';
@@ -17,8 +17,8 @@ import '../../chunk-GEEW6SY2.js';
17
17
  import '../../chunk-U5POE4BA.js';
18
18
  import '../../chunk-7GOCNYUV.js';
19
19
  import '../../chunk-NSBPE2FW.js';
20
- import { spawnSync } from 'child_process';
21
- import { existsSync, createReadStream, createWriteStream } from 'fs';
20
+ import { spawnSync, spawn } from 'child_process';
21
+ import { readFileSync, existsSync, createReadStream, createWriteStream } from 'fs';
22
22
  import { createRequire } from 'module';
23
23
  import { homedir } from 'os';
24
24
  import { join } from 'path';
@@ -393,6 +393,12 @@ var ClaudeSdkDriver = class extends AgentDriver {
393
393
  suppressingAuthStream = false;
394
394
  /** Active query — may stay alive between turns if streamInput works. */
395
395
  query = null;
396
+ /**
397
+ * OS pid of the CLI subprocess backing `query`, captured via
398
+ * `spawnClaudeCodeProcess` in {@link startQuery}. Feeds
399
+ * {@link snapshotDescendantPids}; see `_devlog/notes/2026-08-12-abort-kills-tool-subprocess.md`.
400
+ */
401
+ cliProcessPid = null;
396
402
  /** Resolves when the current turn completes. */
397
403
  turnResolve = null;
398
404
  /** Rejects when the current turn errors. */
@@ -629,7 +635,9 @@ var ClaudeSdkDriver = class extends AgentDriver {
629
635
  onTurnStalled(idleMs) {
630
636
  const idle = idleMs < 6e4 ? `${Math.round(idleMs / 1e3)}s` : `${Math.round(idleMs / 6e4)}m`;
631
637
  this.log.error("turn stalled \u2014 no SDK message received; resetting the query", { idleMs });
638
+ const descendants = this.snapshotDescendantPids();
632
639
  this.hardResetQuery();
640
+ this.killPids(descendants);
633
641
  this.failTurn(
634
642
  new Error(
635
643
  `Agent stalled: no response from the model process for ${idle}. The conversation is intact \u2014 send your message again.`
@@ -642,6 +650,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
642
650
  * Deliberately does NOT clear `sessionId` / `hasSession` — the next
643
651
  * `startQuery()` must resume the same SDK session so the conversation
644
652
  * survives. That is what separates this from {@link kill}.
653
+ *
654
+ * Does NOT sweep the CLI's descendant processes — callers needing that must
655
+ * snapshot via {@link snapshotDescendantPids} *before* this (and any other
656
+ * abort signal) runs, then {@link killPids} after.
645
657
  */
646
658
  hardResetQuery() {
647
659
  if (this.query) {
@@ -653,6 +665,60 @@ var ClaudeSdkDriver = class extends AgentDriver {
653
665
  }
654
666
  this.abortController?.abort();
655
667
  }
668
+ /**
669
+ * Capture every current OS descendant of the CLI subprocess (SIGKILLed
670
+ * later via {@link killPids}). **Call BEFORE `interrupt()` /
671
+ * `abortController.abort()` / `query.close()` — never after**: once the CLI
672
+ * exits, Linux reparents its children immediately, so a later walk finds
673
+ * nothing. Linux-only; `[]` if there's no captured pid or no `/proc`. Full
674
+ * rationale: `_devlog/notes/2026-08-12-abort-kills-tool-subprocess.md`.
675
+ */
676
+ snapshotDescendantPids() {
677
+ const rootPid = this.cliProcessPid;
678
+ if (rootPid == null) return [];
679
+ return this.collectDescendantPids(rootPid);
680
+ }
681
+ /** SIGKILL every pid in a snapshot from {@link snapshotDescendantPids}. */
682
+ killPids(pids) {
683
+ if (pids.length === 0) return;
684
+ let killed = 0;
685
+ for (const pid of pids) {
686
+ try {
687
+ process.kill(pid, "SIGKILL");
688
+ killed++;
689
+ } catch {
690
+ }
691
+ }
692
+ this.log.info("swept CLI descendant processes on abort", {
693
+ attempted: pids.length,
694
+ killed
695
+ });
696
+ }
697
+ /** Direct children of `pid` via Linux procfs — no external binary required. */
698
+ childPids(pid) {
699
+ try {
700
+ const raw = readFileSync(`/proc/${pid}/task/${pid}/children`, "utf8");
701
+ return raw.trim().split(/\s+/).filter(Boolean).map(Number);
702
+ } catch {
703
+ return [];
704
+ }
705
+ }
706
+ /** Every descendant of `rootPid` (excluding `rootPid` itself), breadth-first. */
707
+ collectDescendantPids(rootPid) {
708
+ const descendants = [];
709
+ let frontier = [rootPid];
710
+ while (frontier.length > 0) {
711
+ const next = [];
712
+ for (const pid of frontier) {
713
+ for (const child of this.childPids(pid)) {
714
+ descendants.push(child);
715
+ next.push(child);
716
+ }
717
+ }
718
+ frontier = next;
719
+ }
720
+ return descendants;
721
+ }
656
722
  /**
657
723
  * Side-effects of the stale-resume recovery path: emit the resume-outcome and
658
724
  * non-fatal error events, then reset session state so the recursive prompt()
@@ -850,6 +916,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
850
916
  const apiKey = cloud === "default" ? this.config.apiKeys?.anthropic || this.config.env?.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY : void 0;
851
917
  this.usingOauthCredential = cloud === "default" && !apiKey;
852
918
  const claudePath = this.findClaudeBinary();
919
+ const onStderr = (data) => this.log.warn("claude stderr", { line: data.trim() });
853
920
  const options = {
854
921
  cwd: this.config.cwd,
855
922
  // Load project + user settings so .claude/skills/ are discovered
@@ -872,7 +939,29 @@ var ClaudeSdkDriver = class extends AgentDriver {
872
939
  // Reasoning effort — passed directly to the SDK
873
940
  ...this.config.effort ? { effort: this.config.effort } : {},
874
941
  hooks: {},
875
- stderr: (data) => this.log.warn("claude stderr", { line: data.trim() }),
942
+ stderr: onStderr,
943
+ // Spawn the CLI ourselves to capture its OS pid for snapshotDescendantPids()
944
+ // (Query never exposes it otherwise). This replaces the SDK's own default
945
+ // spawn, so we also have to re-wire child.stderr -> onStderr ourselves below.
946
+ spawnClaudeCodeProcess: (spawnOpts) => {
947
+ const child = spawn(spawnOpts.command, spawnOpts.args, {
948
+ cwd: spawnOpts.cwd,
949
+ env: spawnOpts.env,
950
+ stdio: ["pipe", "pipe", "pipe"]
951
+ });
952
+ this.cliProcessPid = child.pid ?? null;
953
+ child.stderr?.on("data", (chunk) => onStderr(chunk.toString()));
954
+ child.once("exit", () => {
955
+ if (this.cliProcessPid === child.pid) this.cliProcessPid = null;
956
+ });
957
+ spawnOpts.signal.addEventListener("abort", () => {
958
+ try {
959
+ child.kill();
960
+ } catch {
961
+ }
962
+ });
963
+ return child;
964
+ },
876
965
  ...claudePath ? { pathToClaudeCodeExecutable: claudePath } : {},
877
966
  ...this.config.systemPrompt ? {
878
967
  systemPrompt: {
@@ -1542,6 +1631,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
1542
1631
  * Requests the current SDK query to stop processing via `query.interrupt()` and
1543
1632
  * signals the underlying `AbortController`. The driver remains usable after abort.
1544
1633
  *
1634
+ * Also SIGKILLs every OS descendant of the CLI subprocess (e.g. a running
1635
+ * Bash command) on either path the turn ends — see
1636
+ * {@link snapshotDescendantPids}'s doc for why that's needed.
1637
+ *
1545
1638
  * @remarks Best-effort: if `interrupt()` throws (e.g. query already ended) the
1546
1639
  * error is silently swallowed and the abort signal is still sent. A query
1547
1640
  * whose generator refuses to unwind within {@link ABORT_GRACE_MS} is torn
@@ -1551,6 +1644,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
1551
1644
  this.denyPendingQuestion("turn aborted before the question was answered");
1552
1645
  const abortedQuery = this.query;
1553
1646
  const abortedController = this.abortController;
1647
+ const descendants = this.snapshotDescendantPids();
1554
1648
  if (abortedQuery) {
1555
1649
  try {
1556
1650
  await abortedQuery.interrupt();
@@ -1569,6 +1663,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
1569
1663
  this.hardResetQuery();
1570
1664
  this.completeTurn();
1571
1665
  }
1666
+ this.killPids(descendants);
1572
1667
  }
1573
1668
  /** `true` while the driver is processing a `prompt()` call. */
1574
1669
  get isRunning() {
@@ -1579,9 +1674,14 @@ var ClaudeSdkDriver = class extends AgentDriver {
1579
1674
  *
1580
1675
  * The driver instance must not be reused after `kill()`. Session IDs are cleared,
1581
1676
  * so a new `ClaudeSdkDriver` will start a fresh session.
1677
+ *
1678
+ * Also SIGKILLs every OS descendant of the CLI subprocess (e.g. a running
1679
+ * Bash command) — see {@link snapshotDescendantPids}'s doc for why that
1680
+ * needs to be snapshotted before `query.close()` runs, not after.
1582
1681
  */
1583
1682
  kill() {
1584
1683
  this.denyPendingQuestion("session terminated before the question was answered");
1684
+ const descendants = this.snapshotDescendantPids();
1585
1685
  if (this.query) {
1586
1686
  try {
1587
1687
  this.query.close();
@@ -1590,6 +1690,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
1590
1690
  this.query = null;
1591
1691
  }
1592
1692
  this.abortController?.abort();
1693
+ this.killPids(descendants);
1593
1694
  this.running = false;
1594
1695
  this.sessionId = "";
1595
1696
  this.hasSession = false;
@@ -1609,6 +1710,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
1609
1710
  }
1610
1711
  async resetSession() {
1611
1712
  this.denyPendingQuestion("session reset before the question was answered");
1713
+ const descendants = this.snapshotDescendantPids();
1612
1714
  if (this.query) {
1613
1715
  try {
1614
1716
  this.query.close();
@@ -1617,6 +1719,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
1617
1719
  this.query = null;
1618
1720
  }
1619
1721
  this.abortController?.abort();
1722
+ this.killPids(descendants);
1620
1723
  this.sessionId = "";
1621
1724
  this.hasSession = false;
1622
1725
  this._lastTokens = null;