@interf/compiler 0.9.5 → 0.16.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 (365) hide show
  1. package/README.md +135 -91
  2. package/TRADEMARKS.md +2 -13
  3. package/agent-skills/interf-actions/SKILL.md +143 -38
  4. package/agent-skills/interf-actions/references/cli.md +134 -67
  5. package/builtin-methods/interf-default/README.md +3 -4
  6. package/builtin-methods/interf-default/compile/stages/shape/SKILL.md +2 -2
  7. package/builtin-methods/interf-default/compile/stages/summarize/SKILL.md +2 -1
  8. package/builtin-methods/interf-default/improve/SKILL.md +1 -1
  9. package/builtin-methods/interf-default/method.json +10 -4
  10. package/builtin-methods/interf-default/method.schema.json +0 -9
  11. package/builtin-methods/interf-default/use/query/SKILL.md +5 -5
  12. package/dist/cli/commands/agents.d.ts +2 -0
  13. package/dist/cli/commands/agents.js +213 -0
  14. package/dist/cli/commands/compile.d.ts +8 -25
  15. package/dist/cli/commands/compile.js +83 -359
  16. package/dist/cli/commands/doctor.js +2 -2
  17. package/dist/cli/commands/login.d.ts +7 -0
  18. package/dist/cli/commands/login.js +39 -0
  19. package/dist/cli/commands/logout.d.ts +2 -0
  20. package/dist/cli/commands/logout.js +16 -0
  21. package/dist/cli/commands/mcp.d.ts +42 -0
  22. package/dist/cli/commands/mcp.js +239 -0
  23. package/dist/cli/commands/method.d.ts +2 -0
  24. package/dist/cli/commands/method.js +113 -0
  25. package/dist/cli/commands/prep.d.ts +2 -0
  26. package/dist/cli/commands/prep.js +152 -0
  27. package/dist/cli/commands/reset.d.ts +8 -1
  28. package/dist/cli/commands/reset.js +47 -26
  29. package/dist/cli/commands/runs.d.ts +2 -0
  30. package/dist/cli/commands/runs.js +120 -0
  31. package/dist/cli/commands/status.d.ts +6 -1
  32. package/dist/cli/commands/status.js +68 -111
  33. package/dist/cli/commands/verify.d.ts +7 -1
  34. package/dist/cli/commands/verify.js +69 -85
  35. package/dist/cli/commands/web.d.ts +0 -9
  36. package/dist/cli/commands/web.js +201 -121
  37. package/dist/cli/commands/wizard.d.ts +9 -0
  38. package/dist/cli/commands/wizard.js +689 -0
  39. package/dist/cli/index.d.ts +10 -7
  40. package/dist/cli/index.js +19 -12
  41. package/dist/compiler-ui/404.html +1 -1
  42. package/dist/compiler-ui/__next.__PAGE__.txt +2 -2
  43. package/dist/compiler-ui/__next._full.txt +3 -3
  44. package/dist/compiler-ui/__next._head.txt +1 -1
  45. package/dist/compiler-ui/__next._index.txt +2 -2
  46. package/dist/compiler-ui/__next._tree.txt +2 -2
  47. package/dist/compiler-ui/_next/static/chunks/{177mvn4rse235.js → 0jipmpez3_ehh.js} +16 -16
  48. package/dist/compiler-ui/_next/static/chunks/{18a8f2jkv3z.c.css → 13awzu4tooflw.css} +1 -1
  49. package/dist/compiler-ui/_not-found/__next._full.txt +2 -2
  50. package/dist/compiler-ui/_not-found/__next._head.txt +1 -1
  51. package/dist/compiler-ui/_not-found/__next._index.txt +2 -2
  52. package/dist/compiler-ui/_not-found/__next._not-found.__PAGE__.txt +1 -1
  53. package/dist/compiler-ui/_not-found/__next._not-found.txt +1 -1
  54. package/dist/compiler-ui/_not-found/__next._tree.txt +2 -2
  55. package/dist/compiler-ui/_not-found.html +1 -1
  56. package/dist/compiler-ui/_not-found.txt +2 -2
  57. package/dist/compiler-ui/index.html +1 -1
  58. package/dist/compiler-ui/index.txt +3 -3
  59. package/dist/index.d.ts +2 -2
  60. package/dist/index.js +2 -2
  61. package/dist/packages/contracts/index.d.ts +2 -1
  62. package/dist/packages/contracts/index.js +1 -0
  63. package/dist/packages/contracts/lib/preparation-paths.d.ts +117 -0
  64. package/dist/packages/contracts/lib/preparation-paths.js +177 -0
  65. package/dist/packages/contracts/lib/schema.d.ts +187 -13
  66. package/dist/packages/contracts/lib/schema.js +148 -3
  67. package/dist/packages/contracts/utils/filesystem.d.ts +9 -0
  68. package/dist/packages/contracts/utils/filesystem.js +142 -0
  69. package/dist/packages/{local-service → engine}/action-definitions.d.ts +14 -14
  70. package/dist/packages/{local-service → engine}/action-definitions.js +35 -29
  71. package/dist/packages/{local-service → engine}/action-planner.d.ts +1 -1
  72. package/dist/packages/{local-service → engine}/action-planner.js +3 -2
  73. package/dist/packages/{agents → engine/agents}/index.d.ts +3 -0
  74. package/dist/packages/{agents → engine/agents}/index.js +3 -0
  75. package/dist/packages/{agents → engine/agents}/lib/compiled-bootstrap.js +2 -2
  76. package/dist/packages/engine/agents/lib/detection.d.ts +13 -0
  77. package/dist/packages/{agents → engine/agents}/lib/detection.js +11 -0
  78. package/dist/packages/{agents → engine/agents}/lib/executors.d.ts +2 -2
  79. package/dist/packages/{agents → engine/agents}/lib/shells.d.ts +5 -5
  80. package/dist/packages/{agents → engine/agents}/lib/shells.js +117 -58
  81. package/dist/packages/{agents → engine/agents}/lib/user-config.d.ts +4 -2
  82. package/dist/packages/engine/agents/lib/user-config.js +24 -0
  83. package/dist/packages/engine/agents/registry.d.ts +91 -0
  84. package/dist/packages/engine/agents/registry.js +321 -0
  85. package/dist/packages/engine/agents/role-executors.d.ts +35 -0
  86. package/dist/packages/engine/agents/role-executors.js +88 -0
  87. package/dist/packages/engine/agents/role-router.d.ts +66 -0
  88. package/dist/packages/engine/agents/role-router.js +73 -0
  89. package/dist/packages/{local-service → engine}/client.d.ts +59 -60
  90. package/dist/packages/{local-service → engine}/client.js +137 -145
  91. package/dist/packages/{compiler → engine/compile}/artifact-counts.js +1 -1
  92. package/dist/packages/{compiler → engine/compile}/compiled-paths.d.ts +9 -2
  93. package/dist/packages/{compiler → engine/compile}/compiled-paths.js +30 -15
  94. package/dist/packages/{compiler → engine/compile}/compiled-pipeline.d.ts +12 -1
  95. package/dist/packages/{compiler → engine/compile}/compiled-pipeline.js +39 -9
  96. package/dist/packages/{compiler → engine/compile}/compiled-schema.d.ts +2 -2
  97. package/dist/packages/{compiler → engine/compile}/compiled-schema.js +4 -4
  98. package/dist/packages/{compiler → engine/compile}/compiled-stage-plan.d.ts +1 -1
  99. package/dist/packages/{compiler → engine/compile}/compiled-stage-plan.js +8 -4
  100. package/dist/packages/{compiler → engine/compile}/compiled-stage-runner.d.ts +1 -1
  101. package/dist/packages/{compiler → engine/compile}/compiled-stage-runner.js +3 -3
  102. package/dist/packages/{compiler → engine/compile}/compiled-target.d.ts +3 -3
  103. package/dist/packages/{compiler → engine/compile}/compiled-target.js +3 -3
  104. package/dist/packages/{compiler → engine/compile}/discovery.js +1 -1
  105. package/dist/packages/{compiler → engine/compile}/index.d.ts +1 -0
  106. package/dist/packages/{compiler → engine/compile}/index.js +1 -0
  107. package/dist/packages/{compiler → engine/compile}/lib/schema.d.ts +26 -31
  108. package/dist/packages/{compiler → engine/compile}/lib/schema.js +2 -13
  109. package/dist/packages/engine/compile/method-runs.d.ts +14 -0
  110. package/dist/packages/{compiler → engine/compile}/method-runs.js +5 -6
  111. package/dist/packages/{compiler → engine/compile}/reset.js +3 -1
  112. package/dist/packages/{compiler → engine/compile}/runtime-acceptance.js +17 -14
  113. package/dist/packages/{compiler → engine/compile}/runtime-contracts.js +0 -3
  114. package/dist/packages/{compiler → engine/compile}/runtime-prompt.js +1 -1
  115. package/dist/packages/{compiler → engine/compile}/runtime-reconcile.d.ts +1 -1
  116. package/dist/packages/{compiler → engine/compile}/runtime-reconcile.js +12 -10
  117. package/dist/packages/{compiler → engine/compile}/runtime-runs.d.ts +1 -2
  118. package/dist/packages/{compiler → engine/compile}/runtime-runs.js +3 -43
  119. package/dist/packages/{compiler → engine/compile}/runtime-types.d.ts +1 -5
  120. package/dist/packages/{compiler → engine/compile}/runtime.d.ts +2 -2
  121. package/dist/packages/{compiler → engine/compile}/runtime.js +1 -1
  122. package/dist/packages/engine/compile/source-files.d.ts +46 -0
  123. package/dist/packages/engine/compile/source-files.js +149 -0
  124. package/dist/packages/engine/compile/state-artifacts.d.ts +9 -0
  125. package/dist/packages/{compiler → engine/compile}/state-artifacts.js +4 -3
  126. package/dist/packages/{compiler → engine/compile}/state-health.js +2 -2
  127. package/dist/packages/{compiler → engine/compile}/state-io.d.ts +3 -2
  128. package/dist/packages/{compiler → engine/compile}/state-io.js +13 -7
  129. package/dist/packages/{compiler → engine/compile}/state-paths.d.ts +2 -1
  130. package/dist/packages/engine/compile/state-paths.js +16 -0
  131. package/dist/packages/engine/compile/state-view.d.ts +5 -0
  132. package/dist/packages/{compiler → engine/compile}/state-view.js +20 -30
  133. package/dist/packages/engine/compile/state.d.ts +7 -0
  134. package/dist/packages/{compiler → engine/compile}/state.js +3 -3
  135. package/dist/packages/{compiler → engine/compile}/validate-compiled.js +2 -2
  136. package/dist/packages/{compiler → engine/compile}/validate.d.ts +1 -1
  137. package/dist/packages/{compiler → engine/compile}/validate.js +3 -3
  138. package/dist/packages/engine/connection-config.d.ts +38 -0
  139. package/dist/packages/engine/connection-config.js +75 -0
  140. package/dist/packages/{execution → engine/execution}/lib/schema.d.ts +52 -72
  141. package/dist/packages/{execution → engine/execution}/lib/schema.js +3 -3
  142. package/dist/packages/engine/index.d.ts +22 -0
  143. package/dist/packages/engine/index.js +15 -0
  144. package/dist/packages/engine/instance-paths.d.ts +100 -0
  145. package/dist/packages/engine/instance-paths.js +165 -0
  146. package/dist/packages/{local-service → engine}/lib/schema.d.ts +392 -2408
  147. package/dist/packages/{local-service → engine}/lib/schema.js +164 -76
  148. package/dist/packages/{local-service → engine}/native-run-handlers.d.ts +7 -5
  149. package/dist/packages/{local-service → engine}/native-run-handlers.js +71 -27
  150. package/dist/packages/engine/preparation-store.d.ts +104 -0
  151. package/dist/packages/engine/preparation-store.js +194 -0
  152. package/dist/packages/{local-service → engine}/readiness-check-draft.d.ts +2 -2
  153. package/dist/packages/engine/routes.d.ts +78 -0
  154. package/dist/packages/engine/routes.js +92 -0
  155. package/dist/packages/{local-service → engine}/run-observability.d.ts +3 -3
  156. package/dist/packages/{local-service → engine}/run-observability.js +25 -24
  157. package/dist/packages/engine/runtime-caches.d.ts +76 -0
  158. package/dist/packages/engine/runtime-caches.js +191 -0
  159. package/dist/packages/engine/runtime-event-applier.d.ts +12 -0
  160. package/dist/packages/engine/runtime-event-applier.js +177 -0
  161. package/dist/packages/engine/runtime-persistence.d.ts +47 -0
  162. package/dist/packages/engine/runtime-persistence.js +137 -0
  163. package/dist/packages/engine/runtime-proposal-helpers.d.ts +35 -0
  164. package/dist/packages/engine/runtime-proposal-helpers.js +251 -0
  165. package/dist/packages/engine/runtime-resource-builders.d.ts +52 -0
  166. package/dist/packages/engine/runtime-resource-builders.js +149 -0
  167. package/dist/packages/engine/runtime.d.ts +318 -0
  168. package/dist/packages/{local-service → engine}/runtime.js +835 -1011
  169. package/dist/packages/{local-service → engine}/server.d.ts +15 -0
  170. package/dist/packages/engine/server.js +1257 -0
  171. package/dist/packages/engine/service-registry.d.ts +47 -0
  172. package/dist/packages/engine/service-registry.js +137 -0
  173. package/dist/packages/{testing → engine/verify}/lib/schema.d.ts +11 -11
  174. package/dist/packages/{testing → engine/verify}/lib/schema.js +3 -3
  175. package/dist/packages/{testing → engine/verify}/readiness-check-run.d.ts +9 -16
  176. package/dist/packages/{testing → engine/verify}/readiness-check-run.js +38 -94
  177. package/dist/packages/{testing → engine/verify}/test-execution.js +6 -6
  178. package/dist/packages/{testing → engine/verify}/test-paths.js +5 -4
  179. package/dist/packages/{testing → engine/verify}/test-sandbox.d.ts +0 -1
  180. package/dist/packages/{testing → engine/verify}/test-sandbox.js +17 -33
  181. package/dist/packages/{testing → engine/verify}/test-specs.js +1 -1
  182. package/dist/packages/{testing → engine/verify}/test-targets.d.ts +1 -1
  183. package/dist/packages/{testing → engine/verify}/test-targets.js +9 -9
  184. package/dist/packages/{testing → engine/verify}/test.d.ts +1 -1
  185. package/dist/packages/{testing → engine/verify}/test.js +1 -1
  186. package/dist/packages/{method-authoring → methods/authoring}/method-authoring.d.ts +12 -4
  187. package/dist/packages/{method-authoring → methods/authoring}/method-authoring.js +70 -7
  188. package/dist/packages/{method-authoring → methods/authoring}/method-edit-session.d.ts +2 -2
  189. package/dist/packages/{method-authoring → methods/authoring}/method-improvement.d.ts +4 -4
  190. package/dist/packages/{method-authoring → methods/authoring}/method-improvement.js +16 -10
  191. package/dist/packages/{method-package → methods/package}/builtin-compiled-method.d.ts +4 -5
  192. package/dist/packages/{method-package → methods/package}/builtin-compiled-method.js +10 -16
  193. package/dist/packages/{method-package → methods/package}/context-interface.d.ts +5 -41
  194. package/dist/packages/{method-package → methods/package}/context-interface.js +3 -25
  195. package/dist/packages/{method-package → methods/package}/interf-method-package.d.ts +4 -4
  196. package/dist/packages/{method-package → methods/package}/interf-method-package.js +24 -35
  197. package/dist/packages/{method-package → methods/package}/lib/package-root.js +2 -2
  198. package/dist/packages/{method-package → methods/package}/local-methods.d.ts +18 -8
  199. package/dist/packages/{method-package → methods/package}/local-methods.js +64 -45
  200. package/dist/packages/{method-package → methods/package}/method-definitions.d.ts +16 -36
  201. package/dist/packages/{method-package → methods/package}/method-definitions.js +53 -40
  202. package/dist/packages/{method-package → methods/package}/method-helpers.d.ts +2 -14
  203. package/dist/packages/{method-package → methods/package}/method-helpers.js +12 -46
  204. package/dist/packages/{method-package → methods/package}/method-review-paths.d.ts +1 -1
  205. package/dist/packages/{method-package → methods/package}/method-review-paths.js +1 -1
  206. package/dist/packages/{method-package → methods/package}/method-stage-runner.d.ts +4 -9
  207. package/dist/packages/{method-package → methods/package}/method-stage-runner.js +3 -31
  208. package/dist/packages/methods/package/user-methods.d.ts +17 -0
  209. package/dist/packages/methods/package/user-methods.js +77 -0
  210. package/dist/packages/{project-model → project}/index.d.ts +0 -1
  211. package/dist/packages/{project-model → project}/index.js +0 -1
  212. package/dist/packages/{project-model → project}/interf-bootstrap.d.ts +1 -1
  213. package/dist/packages/{project-model → project}/interf-bootstrap.js +1 -1
  214. package/dist/packages/{project-model → project}/interf-detect.d.ts +8 -3
  215. package/dist/packages/{project-model → project}/interf-detect.js +38 -38
  216. package/dist/packages/project/interf-scaffold.d.ts +3 -0
  217. package/dist/packages/{project-model → project}/interf-scaffold.js +30 -39
  218. package/dist/packages/{project-model → project}/lib/schema.d.ts +2 -2
  219. package/dist/packages/{project-model → project}/lib/schema.js +39 -2
  220. package/dist/packages/project/preparation-entries.d.ts +11 -0
  221. package/dist/packages/{project-model → project}/preparation-entries.js +14 -14
  222. package/dist/packages/{project-model → project}/source-config.d.ts +12 -12
  223. package/dist/packages/{project-model → project}/source-config.js +81 -53
  224. package/dist/packages/{project-model → project}/source-folders.d.ts +5 -5
  225. package/dist/packages/{project-model → project}/source-folders.js +16 -16
  226. package/package.json +8 -8
  227. package/CHANGELOG.md +0 -93
  228. package/LICENSE +0 -183
  229. package/dist/cli/commands/action-input-cli.d.ts +0 -25
  230. package/dist/cli/commands/action-input-cli.js +0 -73
  231. package/dist/cli/commands/control-path.d.ts +0 -11
  232. package/dist/cli/commands/control-path.js +0 -72
  233. package/dist/cli/commands/create-method-wizard.d.ts +0 -64
  234. package/dist/cli/commands/create-method-wizard.js +0 -434
  235. package/dist/cli/commands/create.d.ts +0 -6
  236. package/dist/cli/commands/create.js +0 -183
  237. package/dist/cli/commands/default.d.ts +0 -2
  238. package/dist/cli/commands/default.js +0 -39
  239. package/dist/cli/commands/executor-flow.d.ts +0 -29
  240. package/dist/cli/commands/executor-flow.js +0 -163
  241. package/dist/cli/commands/init.d.ts +0 -26
  242. package/dist/cli/commands/init.js +0 -771
  243. package/dist/cli/commands/list.d.ts +0 -2
  244. package/dist/cli/commands/list.js +0 -30
  245. package/dist/cli/commands/preparation-action.d.ts +0 -8
  246. package/dist/cli/commands/preparation-action.js +0 -29
  247. package/dist/cli/commands/preparation-picker.d.ts +0 -5
  248. package/dist/cli/commands/preparation-picker.js +0 -36
  249. package/dist/cli/commands/preparation-selection.d.ts +0 -6
  250. package/dist/cli/commands/preparation-selection.js +0 -11
  251. package/dist/cli/commands/service-action-flow.d.ts +0 -9
  252. package/dist/cli/commands/service-action-flow.js +0 -19
  253. package/dist/cli/commands/source-config-wizard.d.ts +0 -51
  254. package/dist/cli/commands/source-config-wizard.js +0 -670
  255. package/dist/cli/commands/test.d.ts +0 -17
  256. package/dist/cli/commands/test.js +0 -188
  257. package/dist/packages/agents/lib/detection.d.ts +0 -7
  258. package/dist/packages/agents/lib/user-config.js +0 -16
  259. package/dist/packages/compiler/method-runs.d.ts +0 -15
  260. package/dist/packages/compiler/raw-snapshot.d.ts +0 -49
  261. package/dist/packages/compiler/raw-snapshot.js +0 -101
  262. package/dist/packages/compiler/state-artifacts.d.ts +0 -8
  263. package/dist/packages/compiler/state-paths.js +0 -13
  264. package/dist/packages/compiler/state-view.d.ts +0 -4
  265. package/dist/packages/compiler/state.d.ts +0 -7
  266. package/dist/packages/local-service/index.d.ts +0 -18
  267. package/dist/packages/local-service/index.js +0 -13
  268. package/dist/packages/local-service/routes.d.ts +0 -32
  269. package/dist/packages/local-service/routes.js +0 -37
  270. package/dist/packages/local-service/runtime.d.ts +0 -133
  271. package/dist/packages/local-service/server.js +0 -627
  272. package/dist/packages/method-package/index.d.ts +0 -11
  273. package/dist/packages/method-package/index.js +0 -11
  274. package/dist/packages/method-package/method-stage-policy.d.ts +0 -5
  275. package/dist/packages/method-package/method-stage-policy.js +0 -31
  276. package/dist/packages/project-model/interf-scaffold.d.ts +0 -3
  277. package/dist/packages/project-model/preparation-entries.d.ts +0 -11
  278. package/dist/packages/project-model/project-paths.d.ts +0 -12
  279. package/dist/packages/project-model/project-paths.js +0 -33
  280. package/dist/packages/shared/filesystem.d.ts +0 -2
  281. package/dist/packages/shared/filesystem.js +0 -55
  282. /package/dist/compiler-ui/_next/static/{84FaeF3EzBF9kKTMjSEVN → a3UiUF0DiMEbfWy_0gihg}/_buildManifest.js +0 -0
  283. /package/dist/compiler-ui/_next/static/{84FaeF3EzBF9kKTMjSEVN → a3UiUF0DiMEbfWy_0gihg}/_clientMiddlewareManifest.js +0 -0
  284. /package/dist/compiler-ui/_next/static/{84FaeF3EzBF9kKTMjSEVN → a3UiUF0DiMEbfWy_0gihg}/_ssgManifest.js +0 -0
  285. /package/dist/packages/{shared → contracts/utils}/file-types.d.ts +0 -0
  286. /package/dist/packages/{shared → contracts/utils}/file-types.js +0 -0
  287. /package/dist/packages/{shared → contracts/utils}/logger.d.ts +0 -0
  288. /package/dist/packages/{shared → contracts/utils}/logger.js +0 -0
  289. /package/dist/packages/{shared → contracts/utils}/naming.d.ts +0 -0
  290. /package/dist/packages/{shared → contracts/utils}/naming.js +0 -0
  291. /package/dist/packages/{shared → contracts/utils}/parse.d.ts +0 -0
  292. /package/dist/packages/{shared → contracts/utils}/parse.js +0 -0
  293. /package/dist/packages/{shared → contracts/utils}/path-guards.d.ts +0 -0
  294. /package/dist/packages/{shared → contracts/utils}/path-guards.js +0 -0
  295. /package/dist/packages/{local-service → engine}/action-values.d.ts +0 -0
  296. /package/dist/packages/{local-service → engine}/action-values.js +0 -0
  297. /package/dist/packages/{agents → engine/agents}/lib/agents.d.ts +0 -0
  298. /package/dist/packages/{agents → engine/agents}/lib/agents.js +0 -0
  299. /package/dist/packages/{agents → engine/agents}/lib/args.d.ts +0 -0
  300. /package/dist/packages/{agents → engine/agents}/lib/args.js +0 -0
  301. /package/dist/packages/{agents → engine/agents}/lib/chart-guidance.d.ts +0 -0
  302. /package/dist/packages/{agents → engine/agents}/lib/chart-guidance.js +0 -0
  303. /package/dist/packages/{agents → engine/agents}/lib/compiled-bootstrap.d.ts +0 -0
  304. /package/dist/packages/{agents → engine/agents}/lib/constants.d.ts +0 -0
  305. /package/dist/packages/{agents → engine/agents}/lib/constants.js +0 -0
  306. /package/dist/packages/{agents → engine/agents}/lib/execution-profile.d.ts +0 -0
  307. /package/dist/packages/{agents → engine/agents}/lib/execution-profile.js +0 -0
  308. /package/dist/packages/{agents → engine/agents}/lib/execution.d.ts +0 -0
  309. /package/dist/packages/{agents → engine/agents}/lib/execution.js +0 -0
  310. /package/dist/packages/{agents → engine/agents}/lib/executors.js +0 -0
  311. /package/dist/packages/{agents → engine/agents}/lib/logs.d.ts +0 -0
  312. /package/dist/packages/{agents → engine/agents}/lib/logs.js +0 -0
  313. /package/dist/packages/{agents → engine/agents}/lib/preflight.d.ts +0 -0
  314. /package/dist/packages/{agents → engine/agents}/lib/preflight.js +0 -0
  315. /package/dist/packages/{agents → engine/agents}/lib/render.d.ts +0 -0
  316. /package/dist/packages/{agents → engine/agents}/lib/render.js +0 -0
  317. /package/dist/packages/{agents → engine/agents}/lib/schema.d.ts +0 -0
  318. /package/dist/packages/{agents → engine/agents}/lib/schema.js +0 -0
  319. /package/dist/packages/{agents → engine/agents}/lib/status.d.ts +0 -0
  320. /package/dist/packages/{agents → engine/agents}/lib/status.js +0 -0
  321. /package/dist/packages/{agents → engine/agents}/lib/types.d.ts +0 -0
  322. /package/dist/packages/{agents → engine/agents}/lib/types.js +0 -0
  323. /package/dist/packages/{compiler → engine/compile}/artifact-counts.d.ts +0 -0
  324. /package/dist/packages/{compiler → engine/compile}/compiled-compile.d.ts +0 -0
  325. /package/dist/packages/{compiler → engine/compile}/compiled-compile.js +0 -0
  326. /package/dist/packages/{compiler → engine/compile}/discovery.d.ts +0 -0
  327. /package/dist/packages/{compiler → engine/compile}/method-primitives.d.ts +0 -0
  328. /package/dist/packages/{compiler → engine/compile}/method-primitives.js +0 -0
  329. /package/dist/packages/{compiler → engine/compile}/reset.d.ts +0 -0
  330. /package/dist/packages/{compiler → engine/compile}/runtime-acceptance.d.ts +0 -0
  331. /package/dist/packages/{compiler → engine/compile}/runtime-contracts.d.ts +0 -0
  332. /package/dist/packages/{compiler → engine/compile}/runtime-inventory.d.ts +0 -0
  333. /package/dist/packages/{compiler → engine/compile}/runtime-inventory.js +0 -0
  334. /package/dist/packages/{compiler → engine/compile}/runtime-paths.d.ts +0 -0
  335. /package/dist/packages/{compiler → engine/compile}/runtime-paths.js +0 -0
  336. /package/dist/packages/{compiler → engine/compile}/runtime-prompt.d.ts +0 -0
  337. /package/dist/packages/{compiler → engine/compile}/runtime-types.js +0 -0
  338. /package/dist/packages/{compiler → engine/compile}/state-health.d.ts +0 -0
  339. /package/dist/packages/{compiler → engine/compile}/validate-compiled.d.ts +0 -0
  340. /package/dist/packages/{compiler → engine/compile}/validate-helpers.d.ts +0 -0
  341. /package/dist/packages/{compiler → engine/compile}/validate-helpers.js +0 -0
  342. /package/dist/packages/{execution → engine/execution}/adapters.d.ts +0 -0
  343. /package/dist/packages/{execution → engine/execution}/adapters.js +0 -0
  344. /package/dist/packages/{execution → engine/execution}/events.d.ts +0 -0
  345. /package/dist/packages/{execution → engine/execution}/events.js +0 -0
  346. /package/dist/packages/{execution → engine/execution}/index.d.ts +0 -0
  347. /package/dist/packages/{execution → engine/execution}/index.js +0 -0
  348. /package/dist/packages/{local-service → engine}/readiness-check-draft.js +0 -0
  349. /package/dist/packages/{testing → engine/verify}/index.d.ts +0 -0
  350. /package/dist/packages/{testing → engine/verify}/index.js +0 -0
  351. /package/dist/packages/{testing → engine/verify}/test-execution.d.ts +0 -0
  352. /package/dist/packages/{testing → engine/verify}/test-paths.d.ts +0 -0
  353. /package/dist/packages/{testing → engine/verify}/test-profile-presets.d.ts +0 -0
  354. /package/dist/packages/{testing → engine/verify}/test-profile-presets.js +0 -0
  355. /package/dist/packages/{testing → engine/verify}/test-specs.d.ts +0 -0
  356. /package/dist/packages/{testing → engine/verify}/test-types.d.ts +0 -0
  357. /package/dist/packages/{testing → engine/verify}/test-types.js +0 -0
  358. /package/dist/packages/{method-authoring → methods/authoring}/index.d.ts +0 -0
  359. /package/dist/packages/{method-authoring → methods/authoring}/index.js +0 -0
  360. /package/dist/packages/{method-authoring → methods/authoring}/lib/method-edit-utils.d.ts +0 -0
  361. /package/dist/packages/{method-authoring → methods/authoring}/lib/method-edit-utils.js +0 -0
  362. /package/dist/packages/{method-authoring → methods/authoring}/method-edit-session.js +0 -0
  363. /package/dist/packages/{method-package → methods/package}/lib/package-root.d.ts +0 -0
  364. /package/dist/packages/{project-model → project}/interf.d.ts +0 -0
  365. /package/dist/packages/{project-model → project}/interf.js +0 -0
@@ -1,714 +1,240 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
2
- import { dirname, join, resolve } from "node:path";
3
- import { CompileRunSchema, } from "../execution/lib/schema.js";
4
- import { createRunEventId, createRunEventTimestamp, } from "../execution/events.js";
5
- import { compiledRuntimeRunHistoryPath, compiledRuntimeRoot, testRootForCompiled, } from "../compiler/compiled-paths.js";
6
- import { loadState, } from "../compiler/state.js";
7
- import { RuntimeRunSchema, } from "../compiler/lib/schema.js";
1
+ import { existsSync, mkdirSync, rmSync, statSync, } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { CompileRunSchema, } from "./execution/lib/schema.js";
4
+ import { createRunEventId, createRunEventTimestamp, } from "./execution/events.js";
5
+ import { loadState, } from "./compile/state.js";
6
+ import { actionProposalPath, actionProposalsRoot, byCreatedAtDesc, compileRunPath, compileRunsRoot, listJsonFiles, localJobPath, localJobsRoot, newestFirst, readActionProposalAt, readCompileRunAt, readLocalJobRunAt, readRuntimeRunHistory, readVerifyRunAt, verifyRunPath, verifyRunsRoot, timestampKey, writeJsonFile, } from "./runtime-persistence.js";
7
+ import { MethodListingCache, MtimeListingCache, ReadinessCache, RunListingCache, } from "./runtime-caches.js";
8
+ import { applyEventToCompileRun, applyEventToLocalJob, } from "./runtime-event-applier.js";
9
+ import { buildMethodResource, buildPreparationResource, createRunId, logsForRuntimeRun, logsForStageRun, proofForStage, readinessStateToPreparationReadiness, readinessSummaryForStatus, readinessTargetResult, stageArtifactRefs, } from "./runtime-resource-builders.js";
10
+ import { ACTION_PLANNER_CLARIFICATION_MESSAGE, actionAssistantMessage, actionCommandPreview, actionTypeFromValues, actionValueMethodTaskPrompt, configuredAgentName, createActionProposalId, detachMethodFromPreparation, detectedExecutorOptions, directServiceEndpointForAction, hasCompiledTestTarget, methodAuthoringHintFromPrompt, methodAuthoringPromptFallback, methodIdForProposal, methodLabelFromId, numberValue, requireSelectedMethod, sanitizeActionProposalPlan, stringValue, testModeFromValues, } from "./runtime-proposal-helpers.js";
8
11
  import { ReadinessStateSchema, } from "../contracts/lib/schema.js";
9
- import { discoverSourceFiles, } from "../compiler/discovery.js";
10
- import { resetCompiledGeneratedState, } from "../compiler/reset.js";
11
- import { ensurePortableContextScaffold, readInterfConfig, } from "../project-model/interf.js";
12
- import { findSourcePreparationConfig, fingerprintReadinessChecks, listSourcePreparationConfigs, loadSourceFolderConfig, DEFAULT_METHOD_ID, methodIdForSourcePreparationConfig, resolveConfiguredSourceFolderPath, resolveSourcePreparationPath, removeSourcePreparationConfig, saveSourceFolderConfig, syncCompiledInterfConfigFromSourcePreparationConfig, upsertSourcePreparationConfig, } from "../project-model/source-config.js";
13
- import { listSourceFolderChoices, } from "../project-model/source-folders.js";
14
- import { portableContextPath, } from "../project-model/project-paths.js";
15
- import { getCompiledMethod, listCompiledMethodChoices, } from "../method-package/method-definitions.js";
16
- import { contextInterfaceArtifactPath, } from "../method-package/context-interface.js";
17
- import { methodDefinitionPath, resolveMethodPackageSourcePath, } from "../method-package/local-methods.js";
18
- import { seedLocalMethodPackageFromBase, } from "../method-package/interf-method-package.js";
19
- import { resolveAgent, detectAgents, supportsAutomatedRuns, } from "../agents/lib/detection.js";
20
- import { AGENTS, } from "../agents/lib/constants.js";
21
- import { loadUserConfig, saveUserConfig, } from "../agents/lib/user-config.js";
22
- import { readSavedReadinessCheckRun, } from "../testing/readiness-check-run.js";
23
- import { createCompiledTestTarget, } from "../testing/test-targets.js";
24
- import { ActionProposalApprovalRequestSchema, ActionProposalCreateRequestSchema, ActionProposalPlanSchema, ActionProposalResourceSchema, ActionProposalPlanActionTypeSchema, ActionProposalTypeSchema, CompileRunCreateRequestSchema, CompileRunResourceSchema, MethodResourceSchema, LocalExecutorStatusSchema, LocalExecutorSelectRequestSchema, LocalServiceHealthSchema, PreparationReadinessStateSchema, LocalRunHandlerResultSchema, LocalJobEventAppendRequestSchema, LocalJobRunCreateRequestSchema, LocalJobRunResourceSchema, SourceFileResourceSchema, WorkspaceFileResourceSchema, PortableContextResourceSchema, PreparationSetupCreateRequestSchema, PreparationSetupResultSchema, PreparationResourceSchema, MethodChangeCreateRequestSchema, MethodChangeResultSchema, PreparationChangeCreateRequestSchema, PreparationChangeResultSchema, ReadinessCheckDraftCreateRequestSchema, ReadinessCheckDraftResultSchema, ResetRequestSchema, ResetResultSchema, TestRunCreateRequestSchema, TestRunResourceSchema, MethodAuthoringCreateRequestSchema, MethodAuthoringResultSchema, } from "./lib/schema.js";
12
+ import { discoverSourceFiles, } from "./compile/discovery.js";
13
+ import { resetCompiledGeneratedState, } from "./compile/reset.js";
14
+ import { ensurePortableContextScaffold, readInterfConfig, } from "../project/interf.js";
15
+ import { findSourcePreparationConfig, fingerprintReadinessChecks, listSourcePreparationConfigs, loadSourceFolderConfig, DEFAULT_METHOD_ID, methodIdForSourcePreparationConfig, resolveConfiguredSourceFolderPath, resolveSourcePreparationPath, removeSourcePreparationConfig, saveSourceFolderConfig, syncCompiledInterfConfigFromSourcePreparationConfig, upsertSourcePreparationConfig, } from "../project/source-config.js";
16
+ import { listSourceFolderChoices, } from "../project/source-folders.js";
17
+ import { asPreparationDataDir, preparationPortableContextPath, userMethodsRoot, preparationConfigPath, preparationMethodPackagePath, preparationMethodsRoot, } from "../contracts/lib/preparation-paths.js";
18
+ import { getCompiledMethod, listCompiledMethodChoices, } from "../methods/package/method-definitions.js";
19
+ import { contextInterfaceArtifactPath, } from "../methods/package/context-interface.js";
20
+ import { methodDefinitionPath, resolveMethodPackageSourcePath, } from "../methods/package/local-methods.js";
21
+ import { seedLocalMethodPackageFromBase, } from "../methods/package/interf-method-package.js";
22
+ import { PACKAGE_ROOT } from "../methods/package/lib/package-root.js";
23
+ import { resolveAgent, detectAgents, supportsAutomatedRuns, } from "./agents/lib/detection.js";
24
+ import { loadUserConfig, saveUserConfig, } from "./agents/lib/user-config.js";
25
+ import { loadAgentsRegistry, registerCustomAgent, unregisterCustomAgent, patchRoleMap, setActiveAgent, } from "./agents/registry.js";
26
+ import { readSavedReadinessCheckRun, } from "./verify/readiness-check-run.js";
27
+ import { createCompiledTestTarget, } from "./verify/test-targets.js";
28
+ import { ActionProposalApprovalRequestSchema, ActionProposalCreateRequestSchema, ActionProposalPlanSchema, ActionProposalResourceSchema, ActionProposalTypeSchema, CompileRunCreateRequestSchema, CompileRunResourceSchema, LocalExecutorStatusSchema, LocalExecutorSelectRequestSchema, LocalServiceHealthSchema, LocalRunHandlerResultSchema, LocalJobEventAppendRequestSchema, LocalJobRunCreateRequestSchema, LocalJobRunResourceSchema, SourceFileResourceSchema, WorkspaceFileResourceSchema, PortableContextResourceSchema, PreparationSetupCreateRequestSchema, PreparationSetupResultSchema, MethodChangeCreateRequestSchema, MethodChangeResultSchema, PreparationChangeCreateRequestSchema, PreparationChangeResultSchema, ReadinessCheckDraftCreateRequestSchema, ReadinessCheckDraftResultSchema, ResetRequestSchema, ResetResultSchema, ServiceRegistryWorkspaceSchema, VerifyRunCreateRequestSchema, VerifyRunResourceSchema, MethodAuthoringCreateRequestSchema, MethodAuthoringResultSchema, } from "./lib/schema.js";
25
29
  import { buildLocalServiceUrl, } from "./routes.js";
26
- import { methodAuthoringTaskPrompt, MethodAuthoringActionValuesSchema, PreparationSetupActionValuesSchema, } from "./action-values.js";
27
- import { compileRunToObservability, jobRunToObservability, testRunToObservability, uniqueArtifacts, } from "./run-observability.js";
28
- function createRunId(prefix) {
29
- return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
30
- }
31
- function createActionProposalId() {
32
- return `action_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
33
- }
34
- const ACTION_PLANNER_CLARIFICATION_MESSAGE = "I can help with this Interf Workspace. Ask a question about Interf, or ask me to create a Preparation, prepare, check readiness, improve, or draft a Method and I will prepare an approval proposal.";
35
- function readJsonFile(filePath) {
36
- try {
37
- return JSON.parse(readFileSync(filePath, "utf8"));
38
- }
39
- catch {
40
- return null;
41
- }
42
- }
43
- function writeJsonFile(filePath, value) {
44
- mkdirSync(dirname(filePath), { recursive: true });
45
- writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
46
- }
47
- function sanitizeActionProposalPlan(value) {
48
- if (!value || typeof value !== "object" || Array.isArray(value))
49
- return value;
50
- const plan = { ...value };
51
- for (const key of ["preparation", "method", "title", "summary", "assistant_message", "command_preview"]) {
52
- const current = plan[key];
53
- if (current === null || current === undefined) {
54
- delete plan[key];
55
- continue;
56
- }
57
- if (typeof current === "string" && current.trim().length === 0) {
58
- delete plan[key];
59
- }
60
- }
61
- return plan;
62
- }
63
- function compileRunsRoot(compiledPath) {
64
- return join(compiledRuntimeRoot(compiledPath), "compile-runs");
65
- }
66
- function compileRunPath(compiledPath, runId) {
67
- return join(compileRunsRoot(compiledPath), `${runId}.json`);
68
- }
69
- function testRunsRoot(compiledPath) {
70
- return join(testRootForCompiled(compiledPath), "service-runs");
71
- }
72
- function testRunPath(compiledPath, runId) {
73
- return join(testRunsRoot(compiledPath), `${runId}.json`);
74
- }
75
- function localJobsRoot(rootPath) {
76
- return join(rootPath, "interf", ".service", "jobs");
77
- }
78
- function localJobPath(rootPath, runId) {
79
- return join(localJobsRoot(rootPath), `${runId}.json`);
80
- }
81
- function actionProposalsRoot(rootPath) {
82
- return join(rootPath, "interf", ".service", "action-proposals");
83
- }
84
- function actionProposalPath(rootPath, proposalId) {
85
- return join(actionProposalsRoot(rootPath), `${proposalId}.json`);
86
- }
87
- function listJsonFiles(dirPath) {
88
- if (!existsSync(dirPath))
89
- return [];
90
- try {
91
- if (!statSync(dirPath).isDirectory())
92
- return [];
93
- }
94
- catch {
95
- return [];
96
- }
97
- return readdirSync(dirPath)
98
- .filter((entry) => entry.endsWith(".json"))
99
- .map((entry) => join(dirPath, entry));
100
- }
101
- function readCompileRunAt(filePath) {
102
- const parsed = CompileRunSchema.safeParse(readJsonFile(filePath));
103
- return parsed.success ? parsed.data : null;
104
- }
105
- function readRuntimeRunHistory(compiledPath) {
106
- const historyPath = compiledRuntimeRunHistoryPath(compiledPath);
107
- if (!existsSync(historyPath))
108
- return [];
109
- try {
110
- return readFileSync(historyPath, "utf8")
111
- .split(/\r?\n/)
112
- .map((line) => line.trim())
113
- .filter((line) => line.length > 0)
114
- .map((line) => {
115
- try {
116
- return RuntimeRunSchema.safeParse(JSON.parse(line));
117
- }
118
- catch {
119
- return { success: false };
120
- }
121
- })
122
- .filter((parsed) => parsed.success)
123
- .map((parsed) => parsed.data);
124
- }
125
- catch {
126
- return [];
127
- }
128
- }
129
- function readTestRunAt(filePath) {
130
- const parsed = TestRunResourceSchema.safeParse(readJsonFile(filePath));
131
- return parsed.success ? parsed.data : null;
132
- }
133
- function readLocalJobRunAt(filePath) {
134
- const parsed = LocalJobRunResourceSchema.safeParse(readJsonFile(filePath));
135
- return parsed.success ? parsed.data : null;
136
- }
137
- function readActionProposalAt(filePath) {
138
- const parsed = ActionProposalResourceSchema.safeParse(readJsonFile(filePath));
139
- return parsed.success ? parsed.data : null;
140
- }
141
- function newestFirst(items) {
142
- return [...items].sort((left, right) => {
143
- const leftTime = Date.parse(left.started_at ?? left.finished_at ?? "");
144
- const rightTime = Date.parse(right.started_at ?? right.finished_at ?? "");
145
- return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
146
- });
147
- }
148
- function newestJobFirst(items) {
149
- return [...items].sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at));
150
- }
151
- function newestActionProposalFirst(items) {
152
- return [...items].sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at));
153
- }
154
- function newestCompileFirst(items) {
155
- return [...items].sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at));
156
- }
157
- function configuredAgentName() {
158
- const config = loadUserConfig();
159
- if (!config)
160
- return null;
161
- const configured = AGENTS.find((agent) => agent.command === config.agentCommand) ??
162
- AGENTS.find((agent) => agent.name === config.agent) ??
163
- AGENTS.find((agent) => agent.displayName === config.agent);
164
- return configured?.name ?? null;
165
- }
166
- function detectedExecutorOptions(currentAgentName) {
167
- return detectAgents()
168
- .filter(supportsAutomatedRuns)
169
- .map((agent) => ({
170
- name: agent.name,
171
- display_name: agent.displayName,
172
- command: agent.command,
173
- current: agent.name === currentAgentName,
174
- }));
175
- }
176
- function stageArtifactRefs(stageId, artifacts) {
177
- return (artifacts ?? []).map((path) => ({
178
- path,
179
- role: "output",
180
- stage_id: stageId,
181
- label: path,
182
- }));
183
- }
184
- function proofForStage(options) {
185
- return {
186
- id: `${options.runId}-${options.stageId}-proof`,
187
- run_id: options.runId,
188
- stage_id: options.stageId,
189
- generated_at: options.stageState.finished_at ?? new Date().toISOString(),
190
- summary: options.summary ?? `${options.stageId} produced stage evidence.`,
191
- files_processed: options.stageState.counts?.source_total,
192
- artifacts: options.artifacts,
193
- checks: [
194
- {
195
- id: `${options.stageId}-status`,
196
- label: "stage completed",
197
- ok: options.stageState.status === "succeeded",
198
- ...(options.stageState.status === "succeeded"
199
- ? {}
200
- : { detail: options.stageState.summary ?? "Stage did not complete successfully." }),
201
- },
202
- {
203
- id: `${options.stageId}-artifacts`,
204
- label: "artifacts recorded",
205
- ok: options.artifacts.length > 0,
206
- ...(options.artifacts.length > 0
207
- ? {}
208
- : { detail: "No stage artifacts were recorded." }),
209
- },
210
- ],
211
- };
212
- }
213
- function logsForStageRun(stageState) {
214
- const runId = stageState?.run_id;
215
- if (!runId)
216
- return undefined;
217
- return {
218
- prompt_path: `.interf/runtime/logs/${runId}.prompt.txt`,
219
- event_stream_path: `.interf/runtime/logs/${runId}.events.ndjson`,
220
- status_path: `.interf/runtime/logs/${runId}.status.log`,
221
- contract_path: `.interf/runtime/logs/${runId}.stage-contract.json`,
222
- };
223
- }
224
- function logsForRuntimeRun(run) {
225
- if (!run)
226
- return undefined;
227
- return {
228
- ...(run.logs?.prompt_path ? { prompt_path: run.logs.prompt_path } : {}),
229
- ...(run.logs?.event_stream_path ? { event_stream_path: run.logs.event_stream_path } : {}),
230
- ...(run.logs?.status_path ? { status_path: run.logs.status_path } : {}),
231
- contract_path: run.contract_path,
232
- };
233
- }
234
- function timestampKey(value) {
235
- const parsed = Date.parse(value ?? "");
236
- return Number.isFinite(parsed) ? parsed : 0;
237
- }
238
- function applyEventToCompileRun(run, event) {
239
- const now = event.timestamp;
240
- const stageFor = (stageId) => {
241
- const existing = run.stages.find((stage) => stage.stage_id === stageId);
242
- if (existing)
243
- return existing;
244
- const created = {
245
- run_id: run.run_id,
246
- stage_id: stageId,
247
- status: "queued",
248
- artifacts: [],
249
- };
250
- run.stages.push(created);
251
- return created;
252
- };
253
- const updateStage = (stageId, patch) => {
254
- const current = stageFor(stageId);
255
- Object.assign(current, patch);
256
- };
257
- switch (event.type) {
258
- case "run.started":
259
- return {
260
- ...run,
261
- status: "running",
262
- started_at: run.started_at ?? now,
263
- events: [...run.events, event],
264
- };
265
- case "stage.started":
266
- updateStage(event.stage_id, {
267
- status: "running",
268
- started_at: now,
269
- stage_index: event.stage_index,
270
- stage_total: event.stage_total,
271
- });
272
- break;
273
- case "artifact.written": {
274
- const stage = stageFor(event.stage_id);
275
- updateStage(event.stage_id, {
276
- artifacts: uniqueArtifacts([...(stage.artifacts ?? []), event.artifact]),
277
- });
278
- break;
279
- }
280
- case "proof.updated":
281
- if (event.stage_id) {
282
- updateStage(event.stage_id, {
283
- latest_proof: event.proof,
284
- });
285
- }
286
- return {
287
- ...run,
288
- latest_proof: event.proof,
289
- events: [...run.events, event],
290
- };
291
- case "log.appended":
292
- break;
293
- case "stage.passed":
294
- updateStage(event.stage_id, {
295
- status: "succeeded",
296
- finished_at: now,
297
- summary: event.summary ?? null,
298
- failure: null,
299
- });
300
- break;
301
- case "stage.failed":
302
- updateStage(event.stage_id, {
303
- status: "failed",
304
- finished_at: now,
305
- summary: event.error,
306
- failure: event.error,
307
- });
308
- break;
309
- case "run.completed":
310
- return {
311
- ...run,
312
- status: "succeeded",
313
- finished_at: run.finished_at ?? now,
314
- events: [...run.events, event],
315
- };
316
- case "run.failed":
317
- return {
318
- ...run,
319
- status: "failed",
320
- finished_at: run.finished_at ?? now,
321
- events: [...run.events, event],
322
- };
323
- case "readiness.updated":
324
- return {
325
- ...run,
326
- readiness: event.readiness,
327
- events: [...run.events, event],
328
- };
329
- default:
330
- break;
331
- }
332
- return {
333
- ...run,
334
- events: [...run.events, event],
335
- };
336
- }
337
- function applyEventToLocalJob(run, event) {
338
- const stepFor = (stepId) => {
339
- const existing = run.steps.find((step) => step.id === stepId);
340
- if (existing)
341
- return existing;
342
- const created = {
343
- id: stepId,
344
- label: stepId,
345
- status: "queued",
346
- };
347
- run.steps.push(created);
348
- return created;
349
- };
350
- const updateStep = (stepId, patch) => {
351
- if (!stepId)
352
- return;
353
- Object.assign(stepFor(stepId), patch);
354
- };
355
- switch (event.type) {
356
- case "job.started":
357
- return {
358
- ...run,
359
- status: "running",
360
- started_at: run.started_at ?? event.timestamp,
361
- events: [...run.events, event],
362
- };
363
- case "step.started":
364
- updateStep(event.step_id, {
365
- status: "running",
366
- started_at: event.timestamp,
367
- ...(event.input ? { input: event.input } : {}),
368
- });
369
- break;
370
- case "step.completed":
371
- updateStep(event.step_id, {
372
- status: "succeeded",
373
- finished_at: event.timestamp,
374
- summary: event.message ?? null,
375
- ...(event.output ? { output: event.output } : {}),
376
- });
377
- break;
378
- case "step.failed":
379
- updateStep(event.step_id, {
380
- status: "failed",
381
- finished_at: event.timestamp,
382
- summary: event.message ?? null,
383
- ...(event.output ? { output: event.output } : {}),
384
- });
385
- break;
386
- case "job.completed":
387
- return {
388
- ...run,
389
- status: "succeeded",
390
- finished_at: run.finished_at ?? event.timestamp,
391
- events: [...run.events, event],
392
- };
393
- case "job.failed":
394
- return {
395
- ...run,
396
- status: "failed",
397
- finished_at: run.finished_at ?? event.timestamp,
398
- error: event.message ?? "Job failed.",
399
- events: [...run.events, event],
400
- };
401
- case "artifact.written":
402
- case "log.appended":
403
- break;
404
- default:
405
- break;
406
- }
407
- return {
408
- ...run,
409
- events: [...run.events, event],
410
- };
411
- }
412
- function slugFromText(value) {
413
- const slug = value
414
- .toLowerCase()
415
- .replace(/[^a-z0-9]+/g, "-")
416
- .replace(/^-+|-+$/g, "")
417
- .slice(0, 42)
418
- .replace(/-+$/g, "");
419
- return slug || "custom";
420
- }
421
- function escapeRegExp(value) {
422
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
423
- }
424
- function stringValue(values, key) {
425
- const value = values?.[key];
426
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
427
- }
428
- function actionTypeFromValues(values) {
429
- const explicit = stringValue(values, "action_type") ?? stringValue(values, "service_action");
430
- if (explicit) {
431
- const parsed = ActionProposalPlanActionTypeSchema.safeParse(explicit);
432
- if (parsed.success && parsed.data !== "clarification")
433
- return parsed.data;
434
- }
435
- const action = stringValue(values, "action");
436
- if (action === "compile" || action === "prepare-run" || action === "run-preparation")
437
- return "compile";
438
- if (action === "test" || action === "check-readiness")
439
- return "test";
440
- if (action === "draft-readiness-checks" || action === "readiness-check-draft")
441
- return "readiness-check-draft";
442
- if (action === "create-method" || action === "method-authoring")
443
- return "method-authoring";
444
- if (action === "method-duplicate" || action === "method-remove" || action === "method-change")
445
- return "method-change";
446
- if (action === "preparation-remove" || action === "preparation-change")
447
- return "preparation-change";
448
- if (action === "improve-preparation" || action === "method-improvement")
449
- return "method-improvement";
450
- return null;
451
- }
452
- function directServiceEndpointForAction(actionType) {
453
- if (actionType === "preparation-setup")
454
- return "/v1/preparation-setups";
455
- if (actionType === "method-change")
456
- return "/v1/method-changes";
457
- if (actionType === "preparation-change")
458
- return "/v1/preparation-changes";
459
- return null;
460
- }
461
- function numberValue(values, key) {
462
- const value = values?.[key];
463
- return typeof value === "number" && Number.isFinite(value) ? value : null;
464
- }
465
- function testModeFromValues(values) {
466
- const value = stringValue(values, "mode") ?? stringValue(values, "target");
467
- if (value === "source-files")
468
- return "raw";
469
- if (value === "portable-context")
470
- return "compiled";
471
- return value === "raw" || value === "compiled" || value === "both" ? value : null;
472
- }
473
- function testModeValue(values, defaultMode = "both") {
474
- return testModeFromValues(values) ?? defaultMode;
475
- }
476
- function testModeCliTarget(mode) {
477
- if (mode === "raw")
478
- return "source-files";
479
- if (mode === "compiled")
480
- return "portable-context";
481
- return "both";
482
- }
483
- function methodIdForProposal(message, values) {
484
- const explicit = stringValue(values, "method_id") ??
485
- stringValue(values, "method");
486
- const fromMessage = message.match(/\b(?:create|reate|eate|draft|author|build|make)\s+(?:a\s+new\s+)?(?:interf\s+)?method\s+([a-z0-9][a-z0-9-]{0,79})\b/i)?.[1];
487
- return explicit ?? fromMessage ?? `custom-${slugFromText(message)}`;
488
- }
489
- function actionValueMethodTaskPrompt(values) {
490
- const parsed = MethodAuthoringActionValuesSchema.safeParse(values);
491
- return parsed.success ? methodAuthoringTaskPrompt(parsed.data) : null;
492
- }
493
- const METHOD_AUTHORING_INTERNAL_INSTRUCTION = /Use the attached values as the Method authoring request before proposing the action\.?/gi;
494
- const METHOD_AUTHORING_LABELS = [
495
- "Agent work",
496
- "Portable-context output",
497
- "Readiness checks",
498
- "CLI preview",
499
- ];
500
- function normalizeMethodAuthoringText(value) {
501
- return value
502
- .replace(/[│]+/g, " ")
503
- .replace(METHOD_AUTHORING_INTERNAL_INSTRUCTION, " ")
504
- .replace(/\s+/g, " ")
505
- .trim();
506
- }
507
- function extractMethodAuthoringSection(value, label) {
508
- const otherLabels = METHOD_AUTHORING_LABELS
509
- .filter((candidate) => candidate !== label)
510
- .map(escapeRegExp)
511
- .join("|");
512
- const match = value.match(new RegExp(`${escapeRegExp(label)}\\s*:\\s*([\\s\\S]*?)(?=\\s*(?:${otherLabels})\\s*:|$)`, "i"));
513
- return match?.[1] ? normalizeMethodAuthoringText(match[1]) : null;
514
- }
515
- function stripMethodCommandPrefix(value, methodId) {
516
- const specific = new RegExp(`^(?:create|reate|eate|draft|author|build|make)\\s+(?:a\\s+new\\s+)?(?:interf\\s+)?method\\s+${escapeRegExp(methodId)}\\.?\\s*`, "i");
517
- const generic = /^(?:create|reate|eate|draft|author|build|make)\s+(?:a\s+new\s+)?(?:interf\s+)?method\b[^.]{0,180}\.\s*/i;
518
- const stripped = normalizeMethodAuthoringText(value)
519
- .replace(specific, "")
520
- .replace(generic, "")
521
- .trim();
522
- return stripped || normalizeMethodAuthoringText(value);
523
- }
524
- function methodAuthoringPromptFallback(message, methodId) {
525
- const cleaned = message
526
- .replace(/[│]+/g, " ")
527
- .replace(METHOD_AUTHORING_INTERNAL_INSTRUCTION, " ")
528
- .trim();
529
- const agentWork = extractMethodAuthoringSection(cleaned, "Agent work");
530
- const portableOutput = extractMethodAuthoringSection(cleaned, "Portable-context output");
531
- const readinessNotes = extractMethodAuthoringSection(cleaned, "Readiness checks");
532
- const lines = [
533
- agentWork ? `Agent work: ${stripMethodCommandPrefix(agentWork, methodId)}` : null,
534
- portableOutput ? `Portable-context output: ${portableOutput}` : null,
535
- readinessNotes ? `Readiness checks: ${readinessNotes}` : null,
536
- ].filter((line) => Boolean(line));
537
- if (lines.length > 0)
538
- return lines.join("\n");
539
- return stripMethodCommandPrefix(cleaned, methodId);
540
- }
541
- function methodAuthoringHintFromPrompt(prompt) {
542
- const plain = normalizeMethodAuthoringText(prompt.replace(/\b(?:Agent work|Portable-context output|Readiness checks)\s*:/gi, " "));
543
- if (plain.length <= 180)
544
- return plain || "Custom Method";
545
- const clipped = plain.slice(0, 177).replace(/\s+\S*$/, "").trim();
546
- return `${clipped || plain.slice(0, 177)}...`;
547
- }
548
- function methodLabelFromId(methodId) {
549
- return methodId
550
- .split("-")
551
- .filter(Boolean)
552
- .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
553
- .join(" ") || methodId;
554
- }
555
- function detachMethodFromPreparation(preparation, methodId) {
556
- if (methodIdForSourcePreparationConfig(preparation) !== methodId)
557
- return preparation;
558
- const { method: _removedMethod, ...detachedPreparation } = preparation;
559
- return detachedPreparation;
560
- }
561
- function requireSelectedMethod(preparation) {
562
- const methodId = methodIdForSourcePreparationConfig(preparation);
563
- if (methodId)
564
- return methodId;
565
- throw new Error(`Select a Method for Preparation "${preparation.name}" before preparing it.`);
566
- }
567
- function actionCommandPreview(actionType, preparationName, methodId, values) {
568
- if (actionType === "compile") {
569
- const methodSuffix = methodId ? ` # Method: ${methodId}` : "";
570
- return preparationName
571
- ? `interf compile --preparation ${preparationName}${methodSuffix}`
572
- : `interf compile${methodSuffix}`;
573
- }
574
- if (actionType === "test") {
575
- const mode = testModeCliTarget(testModeValue(values));
576
- return preparationName
577
- ? `interf test --preparation ${preparationName} --target ${mode}`
578
- : `interf test --target ${mode}`;
579
- }
580
- if (actionType === "readiness-check-draft") {
581
- return "interf # choose Auto-create readiness checks";
582
- }
583
- if (actionType === "method-authoring" || actionType === "method-improvement") {
584
- return "interf create method";
585
- }
586
- return "Try: create a Preparation, prepare, check readiness, draft readiness checks, or draft a Method.";
587
- }
588
- function hasCompiledTestTarget(sourcePath, preparationConfig) {
589
- const compiledPath = portableContextPath(sourcePath, preparationConfig.name);
590
- if (!existsSync(compiledPath))
591
- return false;
592
- return createCompiledTestTarget(compiledPath, preparationConfig.name, methodIdForSourcePreparationConfig(preparationConfig) ?? DEFAULT_METHOD_ID).eligible;
593
- }
594
- function actionAssistantMessage(actionType, preparationName, commandPreview) {
595
- const preparationSuffix = preparationName ? ` for Preparation "${preparationName}"` : "";
596
- if (actionType === "compile") {
597
- return `Interf prepared a prepare-run proposal${preparationSuffix}. Approve to submit it through the local Interf service and watch the run in Interf. CLI equivalent: ${commandPreview}`;
598
- }
599
- if (actionType === "test") {
600
- return `Interf prepared a readiness-check proposal${preparationSuffix}. Approve to run the requested target against saved readiness checks. CLI equivalent: ${commandPreview}`;
601
- }
602
- if (actionType === "readiness-check-draft") {
603
- return `Interf prepared a proposal to draft readiness checks${preparationSuffix}. Approve to ask the configured local executor to draft saved checks as a visible run. CLI equivalent: ${commandPreview}`;
604
- }
605
- if (actionType === "method-authoring" || actionType === "method-improvement") {
606
- return `Interf prepared a Method draft proposal${preparationSuffix}. Approve to draft a reusable local Method as a visible run. CLI equivalent: ${commandPreview}`;
607
- }
608
- return "I could not map that to a safe Interf action. Ask for one action in plain English, such as create a Preparation, prepare the data, check readiness, draft readiness checks, or draft a Method.";
609
- }
610
- function passRate(passed, total) {
611
- if (total <= 0)
612
- return null;
613
- return Math.round((passed / total) * 100);
614
- }
615
- function readinessTargetResult(summary, currentFingerprint, readinessRunFingerprint) {
616
- if (!summary)
617
- return null;
618
- const resultFingerprint = readinessRunFingerprint ?? null;
619
- return {
620
- passed: summary.passed_cases,
621
- total: summary.total_cases,
622
- pass_rate: passRate(summary.passed_cases, summary.total_cases),
623
- checks_fingerprint: resultFingerprint,
624
- stale: Boolean(currentFingerprint && resultFingerprint && currentFingerprint !== resultFingerprint),
625
- run_id: null,
626
- run_path: summary.run_path,
627
- };
628
- }
629
- function readinessSummaryForStatus(status) {
630
- if (status === "ready")
631
- return "Ready for agent work.";
632
- if (status === "not-ready")
633
- return "Readiness checks did not pass.";
634
- if (status === "stale")
635
- return "Readiness checks are stale for the current saved checks.";
636
- if (status === "checking")
637
- return "Readiness checks are running.";
638
- if (status === "building")
639
- return "Portable context is building.";
640
- if (status === "built")
641
- return "Portable context is built; readiness has not been proven yet.";
642
- if (status === "not-built")
643
- return "Portable context has not been built yet.";
644
- if (status === "not-configured")
645
- return "No readiness checks are configured.";
646
- return "Latest preparation failed.";
647
- }
648
- function readinessStateToPreparationReadiness(readiness) {
649
- return PreparationReadinessStateSchema.parse({
650
- ...readiness,
651
- checks: readiness.checks.map((check) => ({ ...check })),
652
- });
653
- }
654
- function buildPreparationResource(rootPath, preparation, readiness, latestCompileRunId, latestTestRunId) {
655
- const methodId = methodIdForSourcePreparationConfig(preparation);
656
- return PreparationResourceSchema.parse({
657
- id: preparation.name,
658
- name: preparation.name,
659
- preparation,
660
- source_path: resolveSourcePreparationPath(rootPath, preparation),
661
- method_id: methodId,
662
- checks: preparation.checks,
663
- portable_context: {
664
- preparation: preparation.name,
665
- path: readiness.portable_context_path,
666
- exists: readiness.portable_context_path !== null,
667
- method_id: methodId,
668
- latest_compile_run_id: latestCompileRunId,
669
- latest_test_run_id: latestTestRunId,
670
- },
671
- portable_context_path: readiness.portable_context_path,
672
- readiness: readinessStateToPreparationReadiness(readiness),
673
- runs: {
674
- latest_compile_run_id: latestCompileRunId,
675
- latest_test_run_id: latestTestRunId,
676
- },
677
- latest_compile_run_id: latestCompileRunId,
678
- latest_test_run_id: latestTestRunId,
679
- });
680
- }
681
- function buildMethodResource(resource) {
682
- return MethodResourceSchema.parse({
683
- id: resource.id,
684
- method_id: resource.id,
685
- path: resource.path,
686
- ...(resource.label ? { label: resource.label } : {}),
687
- ...(resource.hint ? { hint: resource.hint } : {}),
688
- source_kind: resource.source_kind,
689
- built_in: resource.built_in,
690
- active_for_preparations: resource.active_for_preparations,
691
- output_paths: resource.output_paths,
692
- stages: resource.stages,
693
- });
694
- }
30
+ import { MethodAuthoringActionValuesSchema, PreparationSetupActionValuesSchema, } from "./action-values.js";
31
+ import { compileRunToObservability, jobRunToObservability, verifyRunToObservability, uniqueArtifacts, } from "./run-observability.js";
32
+ /** TTL for `POST /v1/compile-runs` idempotency-key dedupe entries. */
33
+ const IDEMPOTENCY_TTL_MS = 60 * 60 * 1000;
34
+ /** Idempotency cache size at which to schedule an opportunistic prune. */
35
+ const IDEMPOTENCY_PRUNE_THRESHOLD = 64;
695
36
  export class LocalServiceRuntime {
696
- rootPath;
697
37
  host;
698
38
  port;
699
39
  startedAt;
700
40
  packageVersion;
701
41
  handlers;
42
+ /**
43
+ * The seed root path the runtime was constructed with. Used as a
44
+ * non-preparation fallback when a preparation-independent route
45
+ * (methods, action proposals, runs listings) needs an anchor to load
46
+ * shared state (user-library methods, bundled methods, etc).
47
+ */
48
+ rootPath;
49
+ /**
50
+ * Per-instance bearer token. Mutating routes require this on the
51
+ * Authorization header. `null` means token-less mode (test harness).
52
+ */
53
+ authToken;
54
+ /** Map of prepDataDir -> PreparationContext. */
55
+ preparationContexts = new Map();
56
+ /** Hook called whenever a preparation is registered or deregistered. */
57
+ onRegistryChanged = null;
58
+ /** In-flight runs across all preparations. Used for `idle_for_seconds`. */
59
+ activeRunCount = 0;
60
+ /**
61
+ * Active compile-run cancellation handles, keyed by run id. Populated
62
+ * when a compile run is launched and cleared once the run reaches a
63
+ * terminal state. Each entry remembers where the persisted record lives
64
+ * so cancel can mark it without re-resolving the Preparation.
65
+ */
66
+ activeCompileRuns = new Map();
67
+ /**
68
+ * Idempotency-key cache for `POST /v1/compile-runs`. Outer key is the
69
+ * resolved preparation root; inner key is the client-supplied idempotency
70
+ * value. Namespacing per preparation prevents key collisions across
71
+ * tenants on the same engine (CSO finding: a malicious preparation could
72
+ * otherwise hijack another preparation's run id by reusing its key).
73
+ * Entries expire after `IDEMPOTENCY_TTL_MS`.
74
+ */
75
+ idempotencyKeyCache = new Map();
76
+ /**
77
+ * Read-side caches. Polling clients (Compiler UI, CLI status loops)
78
+ * hit list/get endpoints multiple times per second; without these,
79
+ * every request re-walks the filesystem and re-parses every JSON
80
+ * record through Zod. The runtime invalidates each cache on the
81
+ * matching write path. See {@link runtime-caches} for design notes.
82
+ */
83
+ compileRunCache = new RunListingCache();
84
+ verifyRunCache = new RunListingCache();
85
+ readinessCache = new ReadinessCache();
86
+ sourceFilesCache = new MtimeListingCache();
87
+ methodListingCache = new MethodListingCache();
702
88
  constructor(options) {
703
- this.rootPath = resolve(options.rootPath);
704
89
  this.host = options.host;
705
90
  this.port = options.port;
706
91
  this.startedAt = options.startedAt ?? new Date().toISOString();
707
92
  this.packageVersion = options.packageVersion;
708
93
  this.handlers = options.handlers ?? {};
94
+ this.authToken = options.authToken ?? null;
95
+ this.rootPath = resolve(options.rootPath);
96
+ // Auto-register the initial preparation so single-preparation callers
97
+ // (existing tests, the current `interf web` command) work without
98
+ // additional bootstrapping. The constructor seed is the only role
99
+ // `options.rootPath` plays; runtime methods take `prepDataDir`
100
+ // explicitly afterwards.
101
+ this.registerPreparation(this.rootPath);
102
+ }
103
+ setBoundPort(port) {
104
+ this.port = port;
105
+ }
106
+ /** Set a hook that fires whenever the registered preparations change. */
107
+ setOnRegistryChanged(handler) {
108
+ this.onRegistryChanged = handler;
109
+ }
110
+ /**
111
+ * Register a preparation with this runtime. Returns the PreparationContext.
112
+ * Idempotent: re-registering an existing preparation updates `lastActivity`.
113
+ */
114
+ registerPreparation(prepDataDir) {
115
+ const resolved = resolve(prepDataDir);
116
+ const now = new Date().toISOString();
117
+ const existing = this.preparationContexts.get(resolved);
118
+ if (existing) {
119
+ existing.lastActivity = now;
120
+ this.onRegistryChanged?.();
121
+ return existing;
122
+ }
123
+ const context = {
124
+ rootPath: resolved,
125
+ startedAt: now,
126
+ lastActivity: now,
127
+ };
128
+ this.preparationContexts.set(resolved, context);
129
+ this.onRegistryChanged?.();
130
+ return context;
131
+ }
132
+ /**
133
+ * Remove a preparation from the runtime. Returns true if a preparation was
134
+ * removed.
135
+ */
136
+ deregisterPreparation(prepDataDir) {
137
+ const resolved = resolve(prepDataDir);
138
+ const removed = this.preparationContexts.delete(resolved);
139
+ if (removed) {
140
+ this.onRegistryChanged?.();
141
+ }
142
+ return removed;
143
+ }
144
+ /**
145
+ * Most recently active preparation, or the first registered if none has
146
+ * activity yet. Server code uses this as the fallback when a request
147
+ * does not specify a preparation. Throws if none are registered.
148
+ */
149
+ defaultPreparationDataDir() {
150
+ if (this.preparationContexts.size === 0) {
151
+ throw new Error("Local service has no registered preparations.");
152
+ }
153
+ let best = null;
154
+ let bestKey = -Infinity;
155
+ for (const context of this.preparationContexts.values()) {
156
+ const key = Date.parse(context.lastActivity);
157
+ if (Number.isFinite(key) && key > bestKey) {
158
+ best = context;
159
+ bestKey = key;
160
+ }
161
+ }
162
+ return (best ?? this.preparationContexts.values().next().value).rootPath;
163
+ }
164
+ /** Look up a preparation context by rootPath. */
165
+ getPreparationContext(prepDataDir) {
166
+ return this.preparationContexts.get(resolve(prepDataDir)) ?? null;
167
+ }
168
+ /** All registered preparations, ordered by registration time. */
169
+ listRegisteredPreparations() {
170
+ return Array.from(this.preparationContexts.values()).sort((left, right) => Date.parse(left.startedAt) - Date.parse(right.startedAt));
171
+ }
172
+ /** True when no preparations are registered. */
173
+ hasNoPreparations() {
174
+ return this.preparationContexts.size === 0;
175
+ }
176
+ /** Number of registered preparations. */
177
+ registeredPreparationCount() {
178
+ return this.preparationContexts.size;
179
+ }
180
+ /** Increment in-flight run counter. Call when a long-running run starts. */
181
+ beginActiveRun() {
182
+ this.activeRunCount += 1;
183
+ }
184
+ /** Decrement in-flight run counter. Pair with `beginActiveRun`. */
185
+ endActiveRun() {
186
+ if (this.activeRunCount > 0)
187
+ this.activeRunCount -= 1;
188
+ }
189
+ /** Sum of in-flight runs across all preparations. */
190
+ activeRuns() {
191
+ return this.activeRunCount;
192
+ }
193
+ /**
194
+ * Mark the preparation as recently active. Routes call this on entry so
195
+ * `idleForSeconds` and the registry snapshots stay in sync with the
196
+ * actual request cadence.
197
+ */
198
+ touchPreparation(prepDataDir) {
199
+ const context = this.preparationContexts.get(resolve(prepDataDir));
200
+ if (context) {
201
+ context.lastActivity = new Date().toISOString();
202
+ }
203
+ }
204
+ /**
205
+ * Snapshot of registered preparations for the registry / status output.
206
+ * Wire shape (`ServiceRegistryWorkspace`) keeps the legacy
207
+ * "workspace" name for backward compatibility with the public health
208
+ * response and `~/.interf/services.json`. Synthetic-workspace bridge
209
+ * code; do not rename without coordinating an API break.
210
+ */
211
+ registeredPreparationSnapshots() {
212
+ return this.listRegisteredPreparations().map((context) => ServiceRegistryWorkspaceSchema.parse({
213
+ control_path: context.rootPath,
214
+ registered_at: context.startedAt,
215
+ last_activity: context.lastActivity,
216
+ }));
217
+ }
218
+ /** Seconds since the most recent preparation activity (0 if active). */
219
+ idleForSeconds() {
220
+ const all = this.listRegisteredPreparations();
221
+ if (all.length === 0)
222
+ return 0;
223
+ if (this.activeRunCount > 0)
224
+ return 0;
225
+ let mostRecent = 0;
226
+ for (const context of all) {
227
+ const ts = Date.parse(context.lastActivity);
228
+ if (Number.isFinite(ts) && ts > mostRecent)
229
+ mostRecent = ts;
230
+ }
231
+ if (mostRecent <= 0)
232
+ return 0;
233
+ const elapsed = Math.max(0, Date.now() - mostRecent);
234
+ return Math.floor(elapsed / 1000);
709
235
  }
710
- health() {
711
- const sourceFolderPath = resolveConfiguredSourceFolderPath(this.rootPath);
236
+ health(prepDataDir) {
237
+ const sourceFolderPath = prepDataDir ? resolveConfiguredSourceFolderPath(prepDataDir) : null;
712
238
  return LocalServiceHealthSchema.parse({
713
239
  kind: "interf-local-service-health",
714
240
  version: 1,
@@ -716,52 +242,59 @@ export class LocalServiceRuntime {
716
242
  host: this.host,
717
243
  port: this.port,
718
244
  service_url: buildLocalServiceUrl({ host: this.host, port: this.port }),
719
- control_path: this.rootPath,
245
+ ...(prepDataDir ? { control_path: prepDataDir } : {}),
720
246
  source_folder_path: sourceFolderPath,
721
247
  started_at: this.startedAt,
722
248
  ...(this.packageVersion ? { package_version: this.packageVersion } : {}),
249
+ instance_started_at: this.startedAt,
250
+ registered_workspaces: this.registeredPreparationSnapshots(),
251
+ active_runs: this.activeRunCount,
252
+ idle_for_seconds: this.idleForSeconds(),
723
253
  });
724
254
  }
725
- listPreparations() {
726
- const config = loadSourceFolderConfig(this.rootPath);
255
+ listPreparations(prepDataDir) {
256
+ const config = loadSourceFolderConfig(prepDataDir);
727
257
  return listSourcePreparationConfigs(config).map((preparation) => {
728
- const compileRuns = this.listCompileRunsForPreparation(preparation.name);
729
- const testRuns = this.listTestRunsForPreparation(preparation.name);
730
- const readiness = this.computePreparationReadiness(preparation);
731
- return buildPreparationResource(this.rootPath, preparation, readiness, compileRuns[0]?.run_id ?? null, testRuns[0]?.run_id ?? null);
258
+ const compileRuns = this.listCompileRunsForPreparation(prepDataDir, preparation.name);
259
+ const verifyRuns = this.listVerifyRunsForPreparation(prepDataDir, preparation.name);
260
+ const readiness = this.computePreparationReadiness(prepDataDir, preparation);
261
+ return buildPreparationResource(prepDataDir, preparation, readiness, compileRuns[0]?.run_id ?? null, verifyRuns[0]?.run_id ?? null);
732
262
  });
733
263
  }
734
- getPreparation(preparationName) {
735
- return this.listPreparations().find((preparation) => preparation.name === preparationName) ?? null;
264
+ getPreparation(prepDataDir, preparationName) {
265
+ return this.listPreparations(prepDataDir).find((preparation) => preparation.name === preparationName) ?? null;
736
266
  }
737
- listPreparationReadiness() {
738
- return this.listReadiness().map(readinessStateToPreparationReadiness);
267
+ listPreparationReadiness(prepDataDir) {
268
+ return this.listReadiness(prepDataDir).map(readinessStateToPreparationReadiness);
739
269
  }
740
- getPreparationReadiness(preparationName) {
741
- const readiness = this.getReadiness(preparationName);
270
+ getPreparationReadiness(prepDataDir, preparationName) {
271
+ const readiness = this.getReadiness(prepDataDir, preparationName);
742
272
  return readiness ? readinessStateToPreparationReadiness(readiness) : null;
743
273
  }
744
- listReadiness() {
745
- const config = loadSourceFolderConfig(this.rootPath);
746
- return listSourcePreparationConfigs(config).map((preparation) => this.computePreparationReadiness(preparation));
274
+ listReadiness(prepDataDir) {
275
+ const config = loadSourceFolderConfig(prepDataDir);
276
+ return listSourcePreparationConfigs(config).map((preparation) => this.computePreparationReadiness(prepDataDir, preparation));
747
277
  }
748
- getReadiness(preparationName) {
749
- const preparation = findSourcePreparationConfig(loadSourceFolderConfig(this.rootPath), preparationName);
750
- return preparation ? this.computePreparationReadiness(preparation) : null;
278
+ getReadiness(prepDataDir, preparationName) {
279
+ const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), preparationName);
280
+ return preparation ? this.computePreparationReadiness(prepDataDir, preparation) : null;
751
281
  }
752
- computePreparationReadiness(preparation) {
282
+ computePreparationReadiness(prepDataDir, preparation) {
283
+ return this.readinessCache.get(prepDataDir, preparation.name, () => this.computePreparationReadinessUncached(prepDataDir, preparation));
284
+ }
285
+ computePreparationReadinessUncached(prepDataDir, preparation) {
753
286
  const generatedAt = new Date().toISOString();
754
- const compiledPath = portableContextPath(this.rootPath, preparation.name);
287
+ const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparation.name);
755
288
  const contextExists = existsSync(compiledPath);
756
289
  const compiledTarget = createCompiledTestTarget(compiledPath, preparation.name, methodIdForSourcePreparationConfig(preparation) ?? DEFAULT_METHOD_ID);
757
290
  const contextReady = compiledTarget.eligible;
758
- const compileRun = this.listCompileRunsForPreparation(preparation.name)[0] ?? null;
759
- const testRun = this.listTestRunsForPreparation(preparation.name)[0] ?? null;
760
- const readinessRun = this.readLatestReadinessRun(preparation.name);
291
+ const compileRun = this.listCompileRunsForPreparation(prepDataDir, preparation.name)[0] ?? null;
292
+ const verifyRun = this.listVerifyRunsForPreparation(prepDataDir, preparation.name)[0] ?? null;
293
+ const readinessRun = this.readLatestReadinessRun(prepDataDir, preparation.name);
761
294
  const configuredChecks = preparation.checks.length;
762
295
  const currentFingerprint = configuredChecks > 0 ? fingerprintReadinessChecks(preparation.checks) : null;
763
296
  const readinessRunFingerprint = readinessRun?.checks_fingerprint ?? null;
764
- const sourceResult = readinessTargetResult(readinessRun?.raw, currentFingerprint, readinessRunFingerprint);
297
+ const sourceResult = readinessTargetResult(readinessRun?.source_files, currentFingerprint, readinessRunFingerprint);
765
298
  const contextResult = readinessTargetResult(readinessRun?.compiled, currentFingerprint, readinessRunFingerprint);
766
299
  const checksStale = Boolean(currentFingerprint && readinessRunFingerprint && currentFingerprint !== readinessRunFingerprint);
767
300
  const compileCheck = (() => {
@@ -843,7 +376,7 @@ export class LocalServiceRuntime {
843
376
  const status = (() => {
844
377
  if (compileRun?.status === "queued" || compileRun?.status === "running")
845
378
  return "building";
846
- if (testRun?.status === "queued" || testRun?.status === "running")
379
+ if (verifyRun?.status === "queued" || verifyRun?.status === "running")
847
380
  return "checking";
848
381
  if (compileRun?.status === "failed" || compileRun?.status === "cancelled")
849
382
  return "failed";
@@ -868,7 +401,7 @@ export class LocalServiceRuntime {
868
401
  summary: readinessSummaryForStatus(status),
869
402
  portable_context_path: contextReady ? compiledPath : null,
870
403
  latest_compile_run_id: compileRun?.run_id ?? null,
871
- latest_test_run_id: testRun?.run_id ?? null,
404
+ latest_test_run_id: verifyRun?.run_id ?? null,
872
405
  compile: compileCheck,
873
406
  check_results: {
874
407
  configured: configuredChecks,
@@ -879,39 +412,52 @@ export class LocalServiceRuntime {
879
412
  checks,
880
413
  });
881
414
  }
882
- listSourceFiles(preparationName) {
883
- const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath))
415
+ listSourceFiles(prepDataDir, preparationName) {
416
+ const config = loadSourceFolderConfig(prepDataDir);
417
+ const preparations = listSourcePreparationConfigs(config)
884
418
  .filter((preparation) => !preparationName || preparation.name === preparationName);
419
+ // 0.13+ source binding: the source folder is the user-supplied
420
+ // absolute path on `source_folder.path`. `preparation.path` is the
421
+ // portable-context name inside `prepDataDir` (legacy field; in the
422
+ // synthetic-workspace bridge it equals `preparation.name`). Walk
423
+ // the actual source bytes, not the portable-context subdir.
424
+ const sourceFolderPath = resolveConfiguredSourceFolderPath(prepDataDir, config) ?? prepDataDir;
885
425
  return preparations.flatMap((preparation) => {
886
- const sourceFolderPath = resolveSourcePreparationPath(this.rootPath, preparation);
887
- const compiledPath = portableContextPath(this.rootPath, preparation.name);
888
- return discoverSourceFiles(sourceFolderPath, compiledPath).sourceFiles.map((relativePath) => {
889
- const absolutePath = join(sourceFolderPath, relativePath);
890
- let sizeBytes = 0;
891
- let modifiedAt = null;
892
- try {
893
- const stat = statSync(absolutePath);
894
- sizeBytes = stat.size;
895
- modifiedAt = stat.mtime.toISOString();
896
- }
897
- catch {
898
- sizeBytes = 0;
899
- modifiedAt = null;
900
- }
901
- return SourceFileResourceSchema.parse({
902
- preparation: preparation.name,
903
- path: relativePath,
904
- absolute_path: absolutePath,
905
- size_bytes: sizeBytes,
906
- modified_at: modifiedAt,
907
- source_folder_path: sourceFolderPath,
426
+ const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparation.name);
427
+ // Cache by source-folder root mtime so identical UI polls do not
428
+ // re-walk and re-stat the entire tree. The cache imposes a short
429
+ // TTL (see runtime-caches.ts) so deeper changes are still picked
430
+ // up promptly.
431
+ const cacheKey = `${preparation.name}\0${sourceFolderPath}\0${compiledPath}`;
432
+ return this.sourceFilesCache.get(cacheKey, sourceFolderPath, () => {
433
+ return discoverSourceFiles(sourceFolderPath, compiledPath).sourceFiles.map((relativePath) => {
434
+ const absolutePath = join(sourceFolderPath, relativePath);
435
+ let sizeBytes = 0;
436
+ let modifiedAt = null;
437
+ try {
438
+ const stat = statSync(absolutePath);
439
+ sizeBytes = stat.size;
440
+ modifiedAt = stat.mtime.toISOString();
441
+ }
442
+ catch {
443
+ sizeBytes = 0;
444
+ modifiedAt = null;
445
+ }
446
+ return SourceFileResourceSchema.parse({
447
+ preparation: preparation.name,
448
+ path: relativePath,
449
+ absolute_path: absolutePath,
450
+ size_bytes: sizeBytes,
451
+ modified_at: modifiedAt,
452
+ source_folder_path: sourceFolderPath,
453
+ });
908
454
  });
909
455
  });
910
456
  });
911
457
  }
912
- listWorkspaceFiles() {
913
- const sourceFolderPath = resolveConfiguredSourceFolderPath(this.rootPath) ?? this.rootPath;
914
- return discoverSourceFiles(sourceFolderPath, join(this.rootPath, "interf")).sourceFiles.map((relativePath) => {
458
+ listWorkspaceFiles(prepDataDir) {
459
+ const sourceFolderPath = resolveConfiguredSourceFolderPath(prepDataDir) ?? prepDataDir;
460
+ return discoverSourceFiles(sourceFolderPath, prepDataDir).sourceFiles.map((relativePath) => {
915
461
  const absolutePath = join(sourceFolderPath, relativePath);
916
462
  let sizeBytes = 0;
917
463
  let modifiedAt = null;
@@ -932,51 +478,62 @@ export class LocalServiceRuntime {
932
478
  });
933
479
  });
934
480
  }
935
- listMethods() {
936
- const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath));
937
- const choices = listCompiledMethodChoices(this.rootPath);
938
- return choices.map((method) => {
939
- const activeForPreparations = preparations
940
- .filter((preparation) => methodIdForSourcePreparationConfig(preparation) === method.id)
941
- .map((preparation) => preparation.name);
942
- return buildMethodResource({
943
- id: method.id,
944
- path: resolveMethodPackageSourcePath(this.rootPath, method.id) ?? method.id,
945
- label: method.label,
946
- hint: method.hint,
947
- source_kind: method.scope === "builtin" ? "builtin" : "local",
948
- built_in: method.scope === "builtin",
949
- active_for_preparations: activeForPreparations,
950
- output_paths: (method.contextInterface?.zones ?? [])
951
- .filter((zone) => zone.role === "output")
952
- .map((zone) => contextInterfaceArtifactPath(zone))
953
- .sort(),
954
- stages: method.stages.map((stage) => ({
955
- id: stage.id,
956
- label: stage.label,
957
- description: stage.description,
958
- contract_type: stage.contractType,
959
- skill_dir: stage.skillDir,
960
- reads: stage.reads,
961
- writes: stage.writes,
962
- ...(stage.acceptance ? { acceptance: stage.acceptance } : {}),
963
- })),
481
+ listMethods(prepDataDir) {
482
+ // The Method choices list is dominated by repeated reads of
483
+ // method.json + context-interface across builtin / user / workspace
484
+ // method roots. Key the cache off mtimes for the three roots; if
485
+ // any of them changes (a new local Method, an edit to the user
486
+ // library, etc.) the cache misses and we re-resolve.
487
+ const builtinRoot = join(PACKAGE_ROOT, "builtin-methods");
488
+ const localRoot = preparationMethodsRoot(asPreparationDataDir(prepDataDir));
489
+ const userRoot = userMethodsRoot();
490
+ return this.methodListingCache.get(prepDataDir, [builtinRoot, localRoot, userRoot], () => {
491
+ const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir));
492
+ const choices = listCompiledMethodChoices(prepDataDir);
493
+ return choices.map((method) => {
494
+ const activeForPreparations = preparations
495
+ .filter((preparation) => methodIdForSourcePreparationConfig(preparation) === method.id)
496
+ .map((preparation) => preparation.name);
497
+ return buildMethodResource({
498
+ id: method.id,
499
+ path: resolveMethodPackageSourcePath(prepDataDir, method.id) ?? method.id,
500
+ label: method.label,
501
+ hint: method.hint,
502
+ source_kind: method.scope === "builtin" ? "builtin" : "local",
503
+ built_in: method.scope === "builtin",
504
+ active_for_preparations: activeForPreparations,
505
+ output_paths: (method.contextInterface?.zones ?? [])
506
+ .filter((zone) => zone.role === "output")
507
+ .map((zone) => contextInterfaceArtifactPath(zone))
508
+ .sort(),
509
+ stages: method.stages.map((stage) => ({
510
+ id: stage.id,
511
+ label: stage.label,
512
+ description: stage.description,
513
+ contract_type: stage.contractType,
514
+ skill_dir: stage.skillDir,
515
+ role: stage.role && stage.role.trim().length > 0 ? stage.role : "general",
516
+ reads: stage.reads,
517
+ writes: stage.writes,
518
+ ...(stage.acceptance ? { acceptance: stage.acceptance } : {}),
519
+ })),
520
+ });
964
521
  });
965
522
  });
966
523
  }
967
- getMethod(methodId) {
968
- return this.listMethods().find((method) => method.id === methodId) ?? null;
524
+ getMethod(prepDataDir, methodId) {
525
+ return this.listMethods(prepDataDir).find((method) => method.id === methodId) ?? null;
969
526
  }
970
- listJobs() {
971
- return newestJobFirst(listJsonFiles(localJobsRoot(this.rootPath))
527
+ listJobs(prepDataDir) {
528
+ return byCreatedAtDesc(listJsonFiles(localJobsRoot(prepDataDir))
972
529
  .map(readLocalJobRunAt)
973
530
  .filter((run) => run !== null));
974
531
  }
975
- getJob(runId) {
976
- return this.listJobs().find((run) => run.run_id === runId) ?? null;
532
+ getJob(prepDataDir, runId) {
533
+ return this.listJobs(prepDataDir).find((run) => run.run_id === runId) ?? null;
977
534
  }
978
- getJobEvents(runId) {
979
- return this.getJob(runId)?.events ?? null;
535
+ getJobEvents(prepDataDir, runId) {
536
+ return this.getJob(prepDataDir, runId)?.events ?? null;
980
537
  }
981
538
  getExecutorStatus() {
982
539
  const checkedAt = new Date().toISOString();
@@ -1044,28 +601,68 @@ export class LocalServiceRuntime {
1044
601
  skillsInstalled: current?.skillsInstalled ?? false,
1045
602
  initialized: current?.initialized ?? new Date().toISOString(),
1046
603
  });
604
+ // Mirror the selection into the 0.15 role-map. `setActiveAgent`
605
+ // also sweeps any role still pointing at the previous active so
606
+ // single-active-agent setups stay coherent across CLIs.
607
+ try {
608
+ setActiveAgent(selected.name);
609
+ }
610
+ catch {
611
+ // Best effort — the registry update is convenience for the new
612
+ // role-aware path. The user_config.json write above is the
613
+ // source of truth for the legacy `getExecutorStatus()`.
614
+ }
1047
615
  return this.getExecutorStatus();
1048
616
  }
1049
- listActionProposals() {
1050
- return newestActionProposalFirst(listJsonFiles(actionProposalsRoot(this.rootPath))
617
+ // ─── 0.15 connected-agents primitive ─────────────────────────────
618
+ /**
619
+ * Snapshot of the merged agents registry (built-in detected + custom)
620
+ * with the current role-map and resolved active agent.
621
+ */
622
+ getAgentsRegistry() {
623
+ const registry = loadAgentsRegistry();
624
+ return {
625
+ agents: registry.agents,
626
+ role_map: registry.roleMap,
627
+ active_agent: registry.activeAgent,
628
+ };
629
+ }
630
+ registerCustomAgent(input) {
631
+ registerCustomAgent(input);
632
+ return this.getAgentsRegistry();
633
+ }
634
+ unregisterCustomAgent(name) {
635
+ unregisterCustomAgent(name);
636
+ return this.getAgentsRegistry();
637
+ }
638
+ patchAgentsRoleMap(patch) {
639
+ patchRoleMap(patch);
640
+ const registry = loadAgentsRegistry();
641
+ return {
642
+ role_map: registry.roleMap,
643
+ active_agent: registry.activeAgent,
644
+ };
645
+ }
646
+ listActionProposals(prepDataDir) {
647
+ return byCreatedAtDesc(listJsonFiles(actionProposalsRoot(prepDataDir))
1051
648
  .map(readActionProposalAt)
1052
649
  .filter((proposal) => proposal !== null));
1053
650
  }
1054
- getActionProposal(proposalId) {
1055
- return this.listActionProposals().find((proposal) => proposal.proposal_id === proposalId) ?? null;
651
+ getActionProposal(prepDataDir, proposalId) {
652
+ return this.listActionProposals(prepDataDir).find((proposal) => proposal.proposal_id === proposalId) ?? null;
1056
653
  }
1057
- async createActionProposal(requestValue) {
654
+ async createActionProposal(prepDataDir, requestValue) {
1058
655
  const request = ActionProposalCreateRequestSchema.parse(requestValue);
1059
656
  const proposal = ActionProposalResourceSchema.parse({
1060
- ...(await this.buildActionProposal(request)),
657
+ ...(await this.buildActionProposal(prepDataDir, request)),
1061
658
  client_origin: request.client_origin,
1062
659
  });
1063
- this.writeActionProposal(proposal);
660
+ this.writeActionProposal(prepDataDir, proposal);
1064
661
  return proposal;
1065
662
  }
1066
- async decideActionProposal(proposalId, requestValue) {
663
+ async decideActionProposal(prepDataDir, proposalId, requestValue) {
1067
664
  const decision = ActionProposalApprovalRequestSchema.parse(requestValue);
1068
- const current = this.getActionProposal(proposalId);
665
+ const current = this.getActionProposal(prepDataDir, proposalId);
1069
666
  if (!current)
1070
667
  return null;
1071
668
  if (current.status !== "awaiting_approval") {
@@ -1082,11 +679,11 @@ export class LocalServiceRuntime {
1082
679
  ...(decision.note ? { note: decision.note } : {}),
1083
680
  },
1084
681
  });
1085
- this.writeActionProposal(decided);
682
+ this.writeActionProposal(prepDataDir, decided);
1086
683
  if (!decision.approved)
1087
684
  return decided;
1088
685
  try {
1089
- const submission = await this.submitActionProposal(decided);
686
+ const submission = await this.submitActionProposal(prepDataDir, decided);
1090
687
  const submitted = ActionProposalResourceSchema.parse({
1091
688
  ...decided,
1092
689
  status: "submitted",
@@ -1094,7 +691,7 @@ export class LocalServiceRuntime {
1094
691
  submitted_run_id: submission.runId,
1095
692
  submitted_run_type: submission.runType,
1096
693
  });
1097
- this.writeActionProposal(submitted);
694
+ this.writeActionProposal(prepDataDir, submitted);
1098
695
  return submitted;
1099
696
  }
1100
697
  catch (error) {
@@ -1104,25 +701,39 @@ export class LocalServiceRuntime {
1104
701
  updated_at: new Date().toISOString(),
1105
702
  error: error instanceof Error ? error.message : String(error),
1106
703
  });
1107
- this.writeActionProposal(failed);
704
+ this.writeActionProposal(prepDataDir, failed);
1108
705
  return failed;
1109
706
  }
1110
707
  }
1111
- listRunObservability() {
708
+ listRunObservability(prepDataDir) {
1112
709
  return [
1113
- ...this.listCompileRuns().map((resource) => compileRunToObservability(resource.run)),
1114
- ...this.listTestRuns().map(testRunToObservability),
1115
- ...this.listJobs().map(jobRunToObservability),
710
+ ...this.listCompileRuns(prepDataDir).map((resource) => compileRunToObservability(resource.run)),
711
+ ...this.listVerifyRuns(prepDataDir).map(verifyRunToObservability),
712
+ ...this.listJobs(prepDataDir).map(jobRunToObservability),
1116
713
  ].sort((left, right) => {
1117
714
  const leftTime = timestampKey(left.started_at ?? left.created_at ?? left.finished_at);
1118
715
  const rightTime = timestampKey(right.started_at ?? right.created_at ?? right.finished_at);
1119
716
  return rightTime - leftTime;
1120
717
  });
1121
718
  }
1122
- getRunObservability(runId) {
1123
- return this.listRunObservability().find((run) => run.run_id === runId) ?? null;
719
+ getRunObservability(prepDataDir, runId) {
720
+ return this.listRunObservability(prepDataDir).find((run) => run.run_id === runId) ?? null;
721
+ }
722
+ /**
723
+ * Method-scoped runs: every method-authoring or method-improvement job
724
+ * whose `method` matches `methodId`. Surfaced through
725
+ * `GET /v1/methods/<id>/runs` so Method Detail can show the full audit
726
+ * trail of authoring + improvement work for a Method.
727
+ */
728
+ listMethodRuns(prepDataDir, methodId) {
729
+ return this.listRunObservability(prepDataDir).filter((run) => {
730
+ if (run.method !== methodId)
731
+ return false;
732
+ return (run.run_type === "method-authoring" ||
733
+ run.run_type === "method-improvement");
734
+ });
1124
735
  }
1125
- createJobRun(requestValue) {
736
+ createJobRun(prepDataDir, requestValue) {
1126
737
  const request = LocalJobRunCreateRequestSchema.parse(requestValue);
1127
738
  const runId = createRunId("job");
1128
739
  const now = new Date().toISOString();
@@ -1154,12 +765,12 @@ export class LocalServiceRuntime {
1154
765
  },
1155
766
  ],
1156
767
  });
1157
- this.writeJobRun(run);
768
+ this.writeJobRun(prepDataDir, run);
1158
769
  return run;
1159
770
  }
1160
- appendJobRunEvent(runId, requestValue) {
771
+ appendJobRunEvent(prepDataDir, runId, requestValue) {
1161
772
  const request = LocalJobEventAppendRequestSchema.parse(requestValue);
1162
- const current = this.getJob(runId);
773
+ const current = this.getJob(prepDataDir, runId);
1163
774
  if (!current)
1164
775
  return null;
1165
776
  const event = {
@@ -1174,12 +785,12 @@ export class LocalServiceRuntime {
1174
785
  ...(request.output ? { output: request.output } : {}),
1175
786
  };
1176
787
  const next = LocalJobRunResourceSchema.parse(applyEventToLocalJob(current, event));
1177
- this.writeJobRun(next);
788
+ this.writeJobRun(prepDataDir, next);
1178
789
  return next;
1179
790
  }
1180
- async createReadinessCheckDraftRun(requestValue) {
791
+ async createReadinessCheckDraftRun(prepDataDir, requestValue) {
1181
792
  const request = ReadinessCheckDraftCreateRequestSchema.parse(requestValue);
1182
- const job = this.createJobRun({
793
+ const job = this.createJobRun(prepDataDir, {
1183
794
  job_type: "readiness-check-draft",
1184
795
  title: `Draft readiness checks for ${request.preparation}`,
1185
796
  preparation: request.preparation,
@@ -1207,7 +818,7 @@ export class LocalServiceRuntime {
1207
818
  },
1208
819
  ],
1209
820
  });
1210
- this.appendJobRunEvent(job.run_id, {
821
+ this.appendJobRunEvent(prepDataDir, job.run_id, {
1211
822
  type: "step.started",
1212
823
  step_id: "read-source",
1213
824
  message: "Reading source files for readiness-check evidence.",
@@ -1216,7 +827,7 @@ export class LocalServiceRuntime {
1216
827
  source_folder_path: request.source_folder_path,
1217
828
  },
1218
829
  });
1219
- this.appendJobRunEvent(job.run_id, {
830
+ this.appendJobRunEvent(prepDataDir, job.run_id, {
1220
831
  type: "step.completed",
1221
832
  step_id: "read-source",
1222
833
  message: "Source folder is ready for drafting readiness checks.",
@@ -1225,7 +836,7 @@ export class LocalServiceRuntime {
1225
836
  source_folder_path: request.source_folder_path,
1226
837
  },
1227
838
  });
1228
- this.appendJobRunEvent(job.run_id, {
839
+ this.appendJobRunEvent(prepDataDir, job.run_id, {
1229
840
  type: "step.started",
1230
841
  step_id: "agent-draft",
1231
842
  message: "Drafting saved readiness checks from the source files.",
@@ -1234,30 +845,31 @@ export class LocalServiceRuntime {
1234
845
  target_count: request.target_count,
1235
846
  },
1236
847
  });
1237
- void this.runReadinessCheckDraftInBackground(request, job.run_id);
1238
- return this.getJob(job.run_id) ?? job;
848
+ void this.runReadinessCheckDraftInBackground(prepDataDir, request, job.run_id);
849
+ return this.getJob(prepDataDir, job.run_id) ?? job;
1239
850
  }
1240
- applyMethodChange(requestValue) {
851
+ applyMethodChange(prepDataDir, requestValue) {
1241
852
  const request = MethodChangeCreateRequestSchema.parse(requestValue);
1242
853
  const outputPath = request.operation === "duplicate"
1243
- ? methodDefinitionPath(this.rootPath, request.new_method_id)
1244
- : methodDefinitionPath(this.rootPath, request.method);
854
+ ? methodDefinitionPath(prepDataDir, request.new_method_id)
855
+ : methodDefinitionPath(prepDataDir, request.method);
1245
856
  if (request.operation === "duplicate") {
1246
- if (resolveMethodPackageSourcePath(this.rootPath, request.new_method_id)) {
857
+ if (resolveMethodPackageSourcePath(prepDataDir, request.new_method_id)) {
1247
858
  throw new Error(`Method "${request.new_method_id}" already exists.`);
1248
859
  }
1249
- if (!resolveMethodPackageSourcePath(this.rootPath, request.method)) {
860
+ if (!resolveMethodPackageSourcePath(prepDataDir, request.method)) {
1250
861
  throw new Error(`Method "${request.method}" does not exist.`);
1251
862
  }
1252
863
  const label = request.label ?? methodLabelFromId(request.new_method_id);
1253
864
  const hint = request.hint ?? `Duplicate of ${request.method}`;
1254
865
  const methodPath = seedLocalMethodPackageFromBase({
1255
- sourcePath: this.rootPath,
866
+ prepDataDir,
1256
867
  baseMethodId: request.method,
1257
868
  methodId: request.new_method_id,
1258
869
  label,
1259
870
  hint,
1260
871
  });
872
+ this.methodListingCache.invalidate(prepDataDir);
1261
873
  return MethodChangeResultSchema.parse({
1262
874
  kind: "interf-method-change-result",
1263
875
  version: 1,
@@ -1273,19 +885,24 @@ export class LocalServiceRuntime {
1273
885
  if (request.confirmation !== request.method) {
1274
886
  throw new Error(`Type ${request.method} to confirm Method removal.`);
1275
887
  }
1276
- const localMethodPath = methodDefinitionPath(this.rootPath, request.method);
888
+ const localMethodPath = methodDefinitionPath(prepDataDir, request.method);
1277
889
  if (request.method === DEFAULT_METHOD_ID || !existsSync(localMethodPath)) {
1278
890
  throw new Error(`Method "${request.method}" is not a removable local Method.`);
1279
891
  }
1280
- const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath));
892
+ const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir));
1281
893
  const updatedPreparations = preparations
1282
894
  .filter((preparation) => methodIdForSourcePreparationConfig(preparation) === request.method);
1283
895
  if (updatedPreparations.length > 0) {
1284
- saveSourceFolderConfig(this.rootPath, {
896
+ saveSourceFolderConfig(prepDataDir, {
1285
897
  preparations: preparations.map((preparation) => detachMethodFromPreparation(preparation, request.method)),
1286
898
  });
899
+ // Detaching a Method changes readiness shape for those Preparations.
900
+ for (const preparation of updatedPreparations) {
901
+ this.readinessCache.invalidatePreparation(prepDataDir, preparation.name);
902
+ }
1287
903
  }
1288
904
  rmSync(outputPath, { recursive: true, force: true });
905
+ this.methodListingCache.invalidate(prepDataDir);
1289
906
  return MethodChangeResultSchema.parse({
1290
907
  kind: "interf-method-change-result",
1291
908
  version: 1,
@@ -1299,7 +916,7 @@ export class LocalServiceRuntime {
1299
916
  : `Removed Method ${request.method}.`,
1300
917
  });
1301
918
  }
1302
- applyPreparationSetup(requestValue) {
919
+ applyPreparationSetup(prepDataDir, requestValue) {
1303
920
  const request = PreparationSetupCreateRequestSchema.parse(requestValue);
1304
921
  const preparationConfig = request.preparation;
1305
922
  const methodId = methodIdForSourcePreparationConfig(preparationConfig) ?? DEFAULT_METHOD_ID;
@@ -1307,11 +924,18 @@ export class LocalServiceRuntime {
1307
924
  ...preparationConfig,
1308
925
  method: methodId,
1309
926
  };
1310
- const sourceFolderPath = resolveSourcePreparationPath(this.rootPath, normalizedPreparationConfig);
927
+ const sourceFolderPath = resolveSourcePreparationPath(prepDataDir, normalizedPreparationConfig);
1311
928
  if (!existsSync(sourceFolderPath) || !statSync(sourceFolderPath).isDirectory()) {
1312
929
  throw new Error(`Source folder "${preparationConfig.path}" is not available.`);
1313
930
  }
1314
- upsertSourcePreparationConfig(this.rootPath, normalizedPreparationConfig);
931
+ upsertSourcePreparationConfig(prepDataDir, normalizedPreparationConfig);
932
+ // The Preparation's bound source folder + Method may have changed:
933
+ // bust the per-preparation readiness, runs, and method-listing
934
+ // caches so the next read reflects the new shape.
935
+ this.readinessCache.invalidatePreparation(prepDataDir, normalizedPreparationConfig.name);
936
+ this.compileRunCache.invalidatePreparation(prepDataDir, normalizedPreparationConfig.name);
937
+ this.verifyRunCache.invalidatePreparation(prepDataDir, normalizedPreparationConfig.name);
938
+ this.methodListingCache.invalidate(prepDataDir);
1315
939
  const operation = request.setup_mode === "select-method" ? "select-method" : "create";
1316
940
  return PreparationSetupResultSchema.parse({
1317
941
  kind: "interf-preparation-setup-result",
@@ -1320,47 +944,55 @@ export class LocalServiceRuntime {
1320
944
  preparation: normalizedPreparationConfig.name,
1321
945
  method: methodId,
1322
946
  source_folder_path: sourceFolderPath,
1323
- config_path: join(this.rootPath, "interf", "interf.json"),
1324
- portable_context_path: portableContextPath(this.rootPath, normalizedPreparationConfig.name),
947
+ config_path: preparationConfigPath(asPreparationDataDir(prepDataDir)),
948
+ portable_context_path: preparationPortableContextPath(asPreparationDataDir(prepDataDir), normalizedPreparationConfig.name),
1325
949
  changed: true,
1326
950
  message: operation === "select-method"
1327
951
  ? `Preparation ${normalizedPreparationConfig.name} now uses Method ${methodId}.`
1328
952
  : `Preparation ${normalizedPreparationConfig.name} is saved.`,
1329
953
  });
1330
954
  }
1331
- applyPreparationChange(requestValue) {
955
+ applyPreparationChange(prepDataDir, requestValue) {
1332
956
  const request = PreparationChangeCreateRequestSchema.parse(requestValue);
1333
957
  if (request.confirmation !== request.preparation) {
1334
958
  throw new Error(`Type ${request.preparation} to confirm Preparation removal.`);
1335
959
  }
1336
- const preparation = findSourcePreparationConfig(loadSourceFolderConfig(this.rootPath), request.preparation);
960
+ const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), request.preparation);
1337
961
  if (!preparation) {
1338
962
  throw new Error(`Preparation "${request.preparation}" is not saved.`);
1339
963
  }
1340
- removeSourcePreparationConfig(this.rootPath, request.preparation);
964
+ removeSourcePreparationConfig(prepDataDir, request.preparation);
965
+ this.readinessCache.invalidatePreparation(prepDataDir, request.preparation);
966
+ this.compileRunCache.invalidatePreparation(prepDataDir, request.preparation);
967
+ this.verifyRunCache.invalidatePreparation(prepDataDir, request.preparation);
968
+ this.methodListingCache.invalidate(prepDataDir);
1341
969
  return PreparationChangeResultSchema.parse({
1342
970
  kind: "interf-preparation-change-result",
1343
971
  version: 1,
1344
972
  operation: "remove",
1345
973
  preparation: request.preparation,
1346
- config_path: join(this.rootPath, "interf", "interf.json"),
1347
- portable_context_path: portableContextPath(this.rootPath, request.preparation),
974
+ config_path: preparationConfigPath(asPreparationDataDir(prepDataDir)),
975
+ portable_context_path: preparationPortableContextPath(asPreparationDataDir(prepDataDir), request.preparation),
1348
976
  portable_context_retained: true,
1349
977
  changed: true,
1350
978
  message: `Removed Preparation ${request.preparation}. Portable Context files were retained.`,
1351
979
  });
1352
980
  }
1353
- applyReset(requestValue) {
981
+ applyReset(prepDataDir, requestValue) {
1354
982
  const request = ResetRequestSchema.parse(requestValue);
1355
- const preparation = findSourcePreparationConfig(loadSourceFolderConfig(this.rootPath), request.preparation);
983
+ const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), request.preparation);
1356
984
  if (!preparation) {
1357
985
  throw new Error(`Preparation "${request.preparation}" is not saved.`);
1358
986
  }
1359
- const compiledPath = portableContextPath(this.rootPath, request.preparation);
987
+ const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), request.preparation);
1360
988
  if (!existsSync(compiledPath)) {
1361
989
  throw new Error(`Portable Context for Preparation "${request.preparation}" does not exist.`);
1362
990
  }
1363
991
  resetCompiledGeneratedState(compiledPath, request.scope);
992
+ // Reset wipes generated state, including saved compile/test/readiness records.
993
+ this.compileRunCache.invalidatePreparation(prepDataDir, request.preparation);
994
+ this.verifyRunCache.invalidatePreparation(prepDataDir, request.preparation);
995
+ this.readinessCache.invalidatePreparation(prepDataDir, request.preparation);
1364
996
  return ResetResultSchema.parse({
1365
997
  kind: "interf-reset-result",
1366
998
  version: 1,
@@ -1371,16 +1003,16 @@ export class LocalServiceRuntime {
1371
1003
  message: `Reset ${request.scope} state for Preparation ${request.preparation}.`,
1372
1004
  });
1373
1005
  }
1374
- async createMethodAuthoringRun(requestValue, jobType = "method-authoring") {
1006
+ async createMethodAuthoringRun(prepDataDir, requestValue, jobType = "method-authoring") {
1375
1007
  const request = MethodAuthoringCreateRequestSchema.parse(requestValue);
1376
1008
  const isImprovement = jobType === "method-improvement";
1377
- const job = this.createJobRun({
1009
+ const job = this.createJobRun(prepDataDir, {
1378
1010
  job_type: jobType,
1379
1011
  title: isImprovement ? `Improve Method ${request.method_id}` : `Draft Method ${request.method_id}`,
1380
1012
  preparation: request.preparation ?? null,
1381
1013
  method: request.method_id,
1382
1014
  source_path: request.source_folder_path,
1383
- output_path: join(this.rootPath, "interf", "methods", request.method_id),
1015
+ output_path: preparationMethodPackagePath(asPreparationDataDir(prepDataDir), request.method_id),
1384
1016
  steps: [
1385
1017
  {
1386
1018
  id: "inspect-source",
@@ -1409,7 +1041,7 @@ export class LocalServiceRuntime {
1409
1041
  },
1410
1042
  ],
1411
1043
  });
1412
- this.appendJobRunEvent(job.run_id, {
1044
+ this.appendJobRunEvent(prepDataDir, job.run_id, {
1413
1045
  type: "step.started",
1414
1046
  step_id: "inspect-source",
1415
1047
  message: isImprovement ? "Inspecting source files for Method improvement." : "Inspecting source files for Method drafting.",
@@ -1419,7 +1051,7 @@ export class LocalServiceRuntime {
1419
1051
  checks: request.checks.length,
1420
1052
  },
1421
1053
  });
1422
- this.appendJobRunEvent(job.run_id, {
1054
+ this.appendJobRunEvent(prepDataDir, job.run_id, {
1423
1055
  type: "step.completed",
1424
1056
  step_id: "inspect-source",
1425
1057
  message: "Source context is ready.",
@@ -1428,7 +1060,7 @@ export class LocalServiceRuntime {
1428
1060
  checks: request.checks.length,
1429
1061
  },
1430
1062
  });
1431
- this.appendJobRunEvent(job.run_id, {
1063
+ this.appendJobRunEvent(prepDataDir, job.run_id, {
1432
1064
  type: "step.started",
1433
1065
  step_id: "draft-package",
1434
1066
  message: isImprovement ? "Improving Method package." : "Drafting Method package.",
@@ -1438,23 +1070,23 @@ export class LocalServiceRuntime {
1438
1070
  task_prompt: request.task_prompt,
1439
1071
  },
1440
1072
  });
1441
- void this.runMethodAuthoringInBackground(request, job.run_id);
1442
- return this.getJob(job.run_id) ?? job;
1073
+ void this.runMethodAuthoringInBackground(prepDataDir, request, job.run_id);
1074
+ return this.getJob(prepDataDir, job.run_id) ?? job;
1443
1075
  }
1444
- listPortableContexts() {
1445
- return listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath))
1446
- .map((preparation) => this.getPortableContext(preparation.name))
1076
+ listPortableContexts(prepDataDir) {
1077
+ return listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))
1078
+ .map((preparation) => this.getPortableContext(prepDataDir, preparation.name))
1447
1079
  .filter((context) => context !== null);
1448
1080
  }
1449
- getPortableContext(preparationName) {
1450
- const preparation = findSourcePreparationConfig(loadSourceFolderConfig(this.rootPath), preparationName);
1081
+ getPortableContext(prepDataDir, preparationName) {
1082
+ const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), preparationName);
1451
1083
  if (!preparation)
1452
1084
  return null;
1453
- const path = portableContextPath(this.rootPath, preparation.name);
1085
+ const path = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparation.name);
1454
1086
  const config = readInterfConfig(path);
1455
- const compileRuns = this.listCompileRunsForPreparation(preparation.name);
1456
- const testRuns = this.listTestRunsForPreparation(preparation.name);
1457
- const readiness = this.computePreparationReadiness(preparation);
1087
+ const compileRuns = this.listCompileRunsForPreparation(prepDataDir, preparation.name);
1088
+ const verifyRuns = this.listVerifyRunsForPreparation(prepDataDir, preparation.name);
1089
+ const readiness = this.computePreparationReadiness(prepDataDir, preparation);
1458
1090
  const method = config?.method ?? methodIdForSourcePreparationConfig(preparation);
1459
1091
  return PortableContextResourceSchema.parse({
1460
1092
  preparation: preparation.name,
@@ -1463,56 +1095,69 @@ export class LocalServiceRuntime {
1463
1095
  readiness,
1464
1096
  method,
1465
1097
  latest_compile_run_id: compileRuns[0]?.run_id ?? null,
1466
- latest_test_run_id: testRuns[0]?.run_id ?? null,
1098
+ latest_test_run_id: verifyRuns[0]?.run_id ?? null,
1467
1099
  artifacts: uniqueArtifacts(compileRuns[0]?.stages.flatMap((stage) => stage.artifacts) ?? []),
1468
1100
  });
1469
1101
  }
1470
- listCompileRuns() {
1471
- return newestCompileFirst(listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath))
1472
- .flatMap((preparation) => this.listCompileRunsForPreparation(preparation.name))).map((run) => CompileRunResourceSchema.parse({ run }));
1473
- }
1474
- listCompileRunsForPreparation(preparationName) {
1475
- const compiledPath = portableContextPath(this.rootPath, preparationName);
1476
- return newestCompileFirst(listJsonFiles(compileRunsRoot(compiledPath))
1477
- .map(readCompileRunAt)
1478
- .filter((run) => run !== null));
1479
- }
1480
- getCompileRun(runId) {
1481
- for (const resource of this.listCompileRuns()) {
1102
+ listCompileRuns(prepDataDir) {
1103
+ return byCreatedAtDesc(listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))
1104
+ .flatMap((preparation) => this.listCompileRunsForPreparation(prepDataDir, preparation.name))).map((run) => CompileRunResourceSchema.parse({ run }));
1105
+ }
1106
+ listCompileRunsForPreparation(prepDataDir, preparationName) {
1107
+ return this.compileRunCache.get(prepDataDir, preparationName, () => {
1108
+ const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparationName);
1109
+ return byCreatedAtDesc(listJsonFiles(compileRunsRoot(compiledPath))
1110
+ .map(readCompileRunAt)
1111
+ .filter((run) => run !== null));
1112
+ }, (run) => run.run_id);
1113
+ }
1114
+ getCompileRun(prepDataDir, runId) {
1115
+ // Fast path: if the runId was seen during a recent listing, look up
1116
+ // its owning preparation directly and return that preparation's
1117
+ // cached entry instead of scanning every preparation on disk.
1118
+ const known = this.compileRunCache.preparationFor(prepDataDir, runId);
1119
+ if (known) {
1120
+ const found = this.listCompileRunsForPreparation(prepDataDir, known).find((entry) => entry.run_id === runId);
1121
+ if (found)
1122
+ return CompileRunResourceSchema.parse({ run: found });
1123
+ }
1124
+ // Slow path: scan all preparations. Falls through after a cache
1125
+ // miss for an in-flight run created before this process restarted.
1126
+ for (const resource of this.listCompileRuns(prepDataDir)) {
1482
1127
  if (resource.run.run_id === runId)
1483
1128
  return resource;
1484
1129
  }
1485
1130
  return null;
1486
1131
  }
1487
- getCompileRunEvents(runId) {
1488
- return this.getCompileRun(runId)?.run.events ?? null;
1132
+ getCompileRunEvents(prepDataDir, runId) {
1133
+ return this.getCompileRun(prepDataDir, runId)?.run.events ?? null;
1489
1134
  }
1490
- getCompileRunProof(runId) {
1491
- const run = this.getCompileRun(runId)?.run;
1135
+ getCompileRunProof(prepDataDir, runId) {
1136
+ const run = this.getCompileRun(prepDataDir, runId)?.run;
1492
1137
  if (!run)
1493
1138
  return null;
1494
1139
  return run.stages
1495
1140
  .map((stage) => stage.latest_proof ?? null)
1496
1141
  .filter((proof) => proof !== null);
1497
1142
  }
1498
- getCompileRunArtifacts(runId) {
1499
- const run = this.getCompileRun(runId)?.run;
1143
+ getCompileRunArtifacts(prepDataDir, runId) {
1144
+ const run = this.getCompileRun(prepDataDir, runId)?.run;
1500
1145
  if (!run)
1501
1146
  return null;
1502
1147
  return uniqueArtifacts(run.stages.flatMap((stage) => stage.artifacts));
1503
1148
  }
1504
- async createCompileRun(requestValue) {
1149
+ async createCompileRun(prepDataDir, requestValue) {
1505
1150
  const request = CompileRunCreateRequestSchema.parse(requestValue);
1506
- const preparationConfig = this.resolvePreparationConfig(request.preparation, {
1151
+ const preparationConfig = this.resolvePreparationConfig(prepDataDir, request.preparation, {
1507
1152
  method: request.method,
1508
1153
  max_attempts: request.max_attempts,
1509
1154
  max_loops: request.max_loops,
1510
1155
  });
1511
- const compiledPath = this.ensureCompiledForRun(preparationConfig);
1156
+ const compiledPath = this.ensureCompiledForRun(prepDataDir, preparationConfig);
1512
1157
  const runId = createRunId("compile");
1513
1158
  const now = new Date().toISOString();
1514
1159
  const method = getCompiledMethod(requireSelectedMethod(preparationConfig), {
1515
- sourcePath: this.rootPath,
1160
+ prepDataDir,
1516
1161
  });
1517
1162
  const stageTotal = method.stages.length;
1518
1163
  const run = CompileRunSchema.parse({
@@ -1523,7 +1168,7 @@ export class LocalServiceRuntime {
1523
1168
  preparation: preparationConfig.name,
1524
1169
  method: method.id,
1525
1170
  backend: "native",
1526
- source_path: resolveSourcePreparationPath(this.rootPath, preparationConfig),
1171
+ source_path: resolveSourcePreparationPath(prepDataDir, preparationConfig),
1527
1172
  portable_context_path: compiledPath,
1528
1173
  created_at: now,
1529
1174
  started_at: now,
@@ -1549,8 +1194,14 @@ export class LocalServiceRuntime {
1549
1194
  }),
1550
1195
  events: [],
1551
1196
  });
1552
- this.writeCompileRun(compiledPath, run);
1553
- await this.recordCompileRunEvent(compiledPath, runId, {
1197
+ this.writeCompileRun(prepDataDir, compiledPath, run);
1198
+ this.activeCompileRuns.set(runId, {
1199
+ prepDataDir,
1200
+ compiledPath,
1201
+ preparation: preparationConfig.name,
1202
+ cancelled: false,
1203
+ });
1204
+ await this.recordCompileRunEvent(prepDataDir, compiledPath, runId, {
1554
1205
  type: "run.started",
1555
1206
  event_id: createRunEventId("event"),
1556
1207
  run_id: runId,
@@ -1561,11 +1212,11 @@ export class LocalServiceRuntime {
1561
1212
  backend: "native",
1562
1213
  });
1563
1214
  const sink = {
1564
- emit: (event) => this.recordCompileRunEvent(compiledPath, runId, event),
1215
+ emit: (event) => this.recordCompileRunEvent(prepDataDir, compiledPath, runId, event),
1565
1216
  };
1566
- void this.runCompileInBackground(request, {
1217
+ void this.runCompileInBackground(prepDataDir, request, {
1567
1218
  runId,
1568
- sourcePath: this.rootPath,
1219
+ sourcePath: prepDataDir,
1569
1220
  compiledPath,
1570
1221
  preparationConfig,
1571
1222
  events: sink,
@@ -1573,56 +1224,198 @@ export class LocalServiceRuntime {
1573
1224
  const saved = this.readCompileRun(compiledPath, runId) ?? run;
1574
1225
  return CompileRunResourceSchema.parse({ run: saved });
1575
1226
  }
1576
- listTestRuns() {
1577
- return newestFirst(listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath))
1578
- .flatMap((preparation) => this.listTestRunsForPreparation(preparation.name)));
1227
+ /**
1228
+ * Cancel an in-flight compile run. Marks the persisted record as
1229
+ * `cancelled`, emits a `run.failed` event to capture the cancellation in
1230
+ * the run timeline, and clears the active handle so retries may start a
1231
+ * fresh run. If the run already finished, returns
1232
+ * `{ cancelled: false, reason: "already finished" }` and persists nothing.
1233
+ */
1234
+ cancelCompileRun(runId) {
1235
+ const handle = this.activeCompileRuns.get(runId);
1236
+ if (!handle) {
1237
+ // Either unknown or already terminal. The server route already 404s
1238
+ // unknown ids before calling this, so anything reaching here is a run
1239
+ // we already finalized.
1240
+ return { cancelled: false, reason: "already finished" };
1241
+ }
1242
+ if (handle.cancelled) {
1243
+ return { cancelled: false, reason: "already cancelled" };
1244
+ }
1245
+ const cancelledAt = new Date().toISOString();
1246
+ handle.cancelled = true;
1247
+ handle.cancelledAt = cancelledAt;
1248
+ const current = this.readCompileRun(handle.compiledPath, runId);
1249
+ if (current && current.status !== "succeeded" && current.status !== "failed" && current.status !== "cancelled") {
1250
+ const cancelledRun = {
1251
+ ...current,
1252
+ status: "cancelled",
1253
+ finished_at: current.finished_at ?? cancelledAt,
1254
+ events: [
1255
+ ...current.events,
1256
+ {
1257
+ type: "run.failed",
1258
+ event_id: createRunEventId("event"),
1259
+ run_id: runId,
1260
+ timestamp: cancelledAt,
1261
+ error: "Compile run cancelled by request.",
1262
+ },
1263
+ ],
1264
+ };
1265
+ this.writeCompileRun(handle.prepDataDir, handle.compiledPath, cancelledRun);
1266
+ }
1267
+ return { cancelled: true };
1268
+ }
1269
+ /**
1270
+ * Look up the run id previously associated with this idempotency key in
1271
+ * `prepDataDir`. Returns null when the key is unknown or its TTL has
1272
+ * elapsed. The preparation argument is required so that the same key in
1273
+ * two different preparations always returns two different runs.
1274
+ */
1275
+ findIdempotentCompileRun(prepDataDir, key) {
1276
+ const resolvedRoot = resolve(prepDataDir);
1277
+ const bucket = this.idempotencyKeyCache.get(resolvedRoot);
1278
+ if (!bucket)
1279
+ return null;
1280
+ const entry = bucket.get(key);
1281
+ if (!entry)
1282
+ return null;
1283
+ if (entry.expiresAt <= Date.now()) {
1284
+ // Opportunistic single-key prune. The bulk prune runs on writes
1285
+ // when the cache crosses the size threshold (see
1286
+ // {@link recordIdempotentCompileRun}).
1287
+ bucket.delete(key);
1288
+ if (bucket.size === 0)
1289
+ this.idempotencyKeyCache.delete(resolvedRoot);
1290
+ return null;
1291
+ }
1292
+ return entry.runId;
1293
+ }
1294
+ /**
1295
+ * Cache the run id created (or returned) for this idempotency key in
1296
+ * `prepDataDir`. Entries expire after `IDEMPOTENCY_TTL_MS`. Pruning
1297
+ * is opportunistic: the previous implementation walked every entry on
1298
+ * every read AND write, which was O(N) per request. Now we only sweep
1299
+ * when the cache grows past {@link IDEMPOTENCY_PRUNE_THRESHOLD}.
1300
+ */
1301
+ recordIdempotentCompileRun(prepDataDir, key, runId) {
1302
+ const resolvedRoot = resolve(prepDataDir);
1303
+ let bucket = this.idempotencyKeyCache.get(resolvedRoot);
1304
+ if (!bucket) {
1305
+ bucket = new Map();
1306
+ this.idempotencyKeyCache.set(resolvedRoot, bucket);
1307
+ }
1308
+ bucket.set(key, {
1309
+ runId,
1310
+ expiresAt: Date.now() + IDEMPOTENCY_TTL_MS,
1311
+ });
1312
+ if (this.totalIdempotencyEntries() > IDEMPOTENCY_PRUNE_THRESHOLD) {
1313
+ this.pruneIdempotencyKeyCache();
1314
+ }
1579
1315
  }
1580
- listTestRunsForPreparation(preparationName) {
1581
- const compiledPath = portableContextPath(this.rootPath, preparationName);
1582
- return newestFirst(listJsonFiles(testRunsRoot(compiledPath))
1583
- .map(readTestRunAt)
1584
- .filter((run) => run !== null));
1316
+ /** Total cached idempotency entries across all preparations. */
1317
+ totalIdempotencyEntries() {
1318
+ let total = 0;
1319
+ for (const bucket of this.idempotencyKeyCache.values())
1320
+ total += bucket.size;
1321
+ return total;
1322
+ }
1323
+ pruneIdempotencyKeyCache() {
1324
+ const now = Date.now();
1325
+ for (const [prepDataDir, bucket] of this.idempotencyKeyCache) {
1326
+ for (const [key, entry] of bucket) {
1327
+ if (entry.expiresAt <= now)
1328
+ bucket.delete(key);
1329
+ }
1330
+ if (bucket.size === 0)
1331
+ this.idempotencyKeyCache.delete(prepDataDir);
1332
+ }
1585
1333
  }
1586
- getTestRun(runId) {
1587
- return this.listTestRuns().find((run) => run.run_id === runId) ?? null;
1334
+ /**
1335
+ * Test seam: force the cached entry for `key` in `prepDataDir` to be
1336
+ * expired so the next lookup returns null. Returns true when an entry was
1337
+ * found and expired. Tests use this in place of fake timers because the
1338
+ * idempotency TTL is one hour and faking `Date.now()` would destabilize
1339
+ * unrelated runtime state.
1340
+ */
1341
+ expireIdempotencyKeyForTesting(prepDataDir, key) {
1342
+ const bucket = this.idempotencyKeyCache.get(resolve(prepDataDir));
1343
+ if (!bucket)
1344
+ return false;
1345
+ const entry = bucket.get(key);
1346
+ if (!entry)
1347
+ return false;
1348
+ entry.expiresAt = Date.now() - 1;
1349
+ return true;
1350
+ }
1351
+ listVerifyRuns(prepDataDir) {
1352
+ return newestFirst(listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))
1353
+ .flatMap((preparation) => this.listVerifyRunsForPreparation(prepDataDir, preparation.name)));
1354
+ }
1355
+ listVerifyRunsForPreparation(prepDataDir, preparationName) {
1356
+ return this.verifyRunCache.get(prepDataDir, preparationName, () => {
1357
+ const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparationName);
1358
+ return newestFirst(listJsonFiles(verifyRunsRoot(compiledPath))
1359
+ .map(readVerifyRunAt)
1360
+ .filter((run) => run !== null));
1361
+ }, (run) => run.run_id);
1362
+ }
1363
+ getVerifyRun(prepDataDir, runId) {
1364
+ const known = this.verifyRunCache.preparationFor(prepDataDir, runId);
1365
+ if (known) {
1366
+ const found = this.listVerifyRunsForPreparation(prepDataDir, known).find((run) => run.run_id === runId);
1367
+ if (found)
1368
+ return found;
1369
+ }
1370
+ return this.listVerifyRuns(prepDataDir).find((run) => run.run_id === runId) ?? null;
1588
1371
  }
1589
- async createTestRun(requestValue) {
1590
- const request = TestRunCreateRequestSchema.parse(requestValue);
1591
- const preparationConfig = this.resolvePreparationConfig(request.preparation);
1592
- const compiledPath = portableContextPath(this.rootPath, preparationConfig.name);
1372
+ async createVerifyRun(prepDataDir, requestValue) {
1373
+ const request = VerifyRunCreateRequestSchema.parse(requestValue);
1374
+ const preparationConfig = this.resolvePreparationConfig(prepDataDir, request.preparation);
1375
+ const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparationConfig.name);
1593
1376
  const compiledTarget = createCompiledTestTarget(compiledPath, preparationConfig.name, methodIdForSourcePreparationConfig(preparationConfig) ?? DEFAULT_METHOD_ID);
1594
- const runId = createRunId("test");
1377
+ const runId = createRunId("verify");
1595
1378
  const now = new Date().toISOString();
1596
- const initial = TestRunResourceSchema.parse({
1379
+ const initial = VerifyRunResourceSchema.parse({
1597
1380
  run_id: runId,
1598
1381
  status: "running",
1599
1382
  preparation: preparationConfig.name,
1600
- mode: request.mode,
1601
- source_path: this.rootPath,
1383
+ source_path: prepDataDir,
1602
1384
  portable_context_path: compiledTarget.eligible ? compiledPath : null,
1603
1385
  started_at: now,
1604
1386
  readiness_run: null,
1605
1387
  events: [],
1606
1388
  });
1607
- this.writeTestRun(compiledPath, initial);
1608
- void this.runTestInBackground(request, {
1389
+ this.writeVerifyRun(prepDataDir, compiledPath, initial);
1390
+ void this.runTestInBackground(prepDataDir, request, {
1609
1391
  runId,
1610
- sourcePath: this.rootPath,
1392
+ sourcePath: prepDataDir,
1611
1393
  compiledPath,
1612
1394
  preparationConfig,
1613
1395
  }, initial);
1614
1396
  return initial;
1615
1397
  }
1616
- async runCompileInBackground(request, context) {
1398
+ async runCompileInBackground(prepDataDir, request, context) {
1399
+ this.beginActiveRun();
1617
1400
  try {
1618
1401
  if (!this.handlers.createCompileRun) {
1619
1402
  throw new Error("No compile-run handler is configured for this local service.");
1620
1403
  }
1621
1404
  const result = LocalRunHandlerResultSchema.parse(await this.handlers.createCompileRun(request, context));
1622
- this.refreshCompileRunFromRuntime(context.compiledPath, context.runId);
1623
- await this.emitRuntimeDerivedEvents(context.compiledPath, context.runId);
1405
+ const wasCancelled = this.activeCompileRuns.get(context.runId)?.cancelled === true;
1406
+ if (wasCancelled) {
1407
+ // The run was cancelled while the handler was still running. The
1408
+ // cancellation path already wrote a `cancelled` record; just refresh
1409
+ // observability and skip emitting a second terminal event.
1410
+ this.refreshCompileRunFromRuntime(prepDataDir, context.compiledPath, context.runId);
1411
+ await this.emitRuntimeDerivedEvents(prepDataDir, context.compiledPath, context.runId);
1412
+ await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(prepDataDir, context.preparationConfig)));
1413
+ return;
1414
+ }
1415
+ this.refreshCompileRunFromRuntime(prepDataDir, context.compiledPath, context.runId);
1416
+ await this.emitRuntimeDerivedEvents(prepDataDir, context.compiledPath, context.runId);
1624
1417
  if (!result.ok) {
1625
- await this.recordCompileRunEvent(context.compiledPath, context.runId, {
1418
+ await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, {
1626
1419
  type: "run.failed",
1627
1420
  event_id: createRunEventId("event"),
1628
1421
  run_id: context.runId,
@@ -1631,7 +1424,7 @@ export class LocalServiceRuntime {
1631
1424
  });
1632
1425
  }
1633
1426
  else {
1634
- await this.recordCompileRunEvent(context.compiledPath, context.runId, {
1427
+ await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, {
1635
1428
  type: "run.completed",
1636
1429
  event_id: createRunEventId("event"),
1637
1430
  run_id: context.runId,
@@ -1639,30 +1432,35 @@ export class LocalServiceRuntime {
1639
1432
  summary: "Portable context ready.",
1640
1433
  });
1641
1434
  }
1642
- await this.recordCompileRunEvent(context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(context.preparationConfig)));
1435
+ await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(prepDataDir, context.preparationConfig)));
1643
1436
  }
1644
1437
  catch (error) {
1645
- await this.recordCompileRunEvent(context.compiledPath, context.runId, {
1438
+ await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, {
1646
1439
  type: "run.failed",
1647
1440
  event_id: createRunEventId("event"),
1648
1441
  run_id: context.runId,
1649
1442
  timestamp: createRunEventTimestamp(),
1650
1443
  error: error instanceof Error ? error.message : String(error),
1651
1444
  });
1652
- await this.recordCompileRunEvent(context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(context.preparationConfig)));
1445
+ await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(prepDataDir, context.preparationConfig)));
1446
+ }
1447
+ finally {
1448
+ this.activeCompileRuns.delete(context.runId);
1449
+ this.endActiveRun();
1653
1450
  }
1654
1451
  }
1655
- async runTestInBackground(request, context, initial) {
1452
+ async runTestInBackground(prepDataDir, request, context, initial) {
1453
+ this.beginActiveRun();
1656
1454
  try {
1657
- if (!this.handlers.createTestRun) {
1658
- throw new Error("No test-run handler is configured for this local service.");
1455
+ if (!this.handlers.createVerifyRun) {
1456
+ throw new Error("No verify-run handler is configured for this local service.");
1659
1457
  }
1660
- const result = LocalRunHandlerResultSchema.parse(await this.handlers.createTestRun(request, context));
1661
- const readinessRun = result.readiness_run ?? this.readLatestReadinessRun(context.preparationConfig.name);
1458
+ const result = LocalRunHandlerResultSchema.parse(await this.handlers.createVerifyRun(request, context));
1459
+ const readinessRun = result.readiness_run ?? this.readLatestReadinessRun(prepDataDir, context.preparationConfig.name);
1662
1460
  const resultEvent = readinessRun
1663
1461
  ? this.checksEvaluatedEvent(context.runId, readinessRun)
1664
1462
  : null;
1665
- const nextWithoutReadiness = TestRunResourceSchema.parse({
1463
+ const nextWithoutReadiness = VerifyRunResourceSchema.parse({
1666
1464
  ...initial,
1667
1465
  status: result.ok ? "succeeded" : "failed",
1668
1466
  finished_at: new Date().toISOString(),
@@ -1670,9 +1468,9 @@ export class LocalServiceRuntime {
1670
1468
  events: resultEvent ? [resultEvent] : [],
1671
1469
  ...(!result.ok ? { error: result.error ?? "Readiness check failed." } : {}),
1672
1470
  });
1673
- this.writeTestRun(context.compiledPath, nextWithoutReadiness);
1674
- const readiness = this.computePreparationReadiness(context.preparationConfig);
1675
- const next = TestRunResourceSchema.parse({
1471
+ this.writeVerifyRun(prepDataDir, context.compiledPath, nextWithoutReadiness);
1472
+ const readiness = this.computePreparationReadiness(prepDataDir, context.preparationConfig);
1473
+ const next = VerifyRunResourceSchema.parse({
1676
1474
  ...nextWithoutReadiness,
1677
1475
  readiness,
1678
1476
  events: [
@@ -1680,32 +1478,41 @@ export class LocalServiceRuntime {
1680
1478
  this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, readiness),
1681
1479
  ],
1682
1480
  });
1683
- this.writeTestRun(context.compiledPath, next);
1481
+ this.writeVerifyRun(prepDataDir, context.compiledPath, next);
1684
1482
  }
1685
1483
  catch (error) {
1686
- const failedWithoutReadiness = TestRunResourceSchema.parse({
1484
+ const failedWithoutReadiness = VerifyRunResourceSchema.parse({
1687
1485
  ...initial,
1688
1486
  status: "failed",
1689
1487
  finished_at: new Date().toISOString(),
1690
1488
  error: error instanceof Error ? error.message : String(error),
1691
1489
  });
1692
- this.writeTestRun(context.compiledPath, failedWithoutReadiness);
1693
- const readiness = this.computePreparationReadiness(context.preparationConfig);
1694
- const next = TestRunResourceSchema.parse({
1490
+ this.writeVerifyRun(prepDataDir, context.compiledPath, failedWithoutReadiness);
1491
+ const readiness = this.computePreparationReadiness(prepDataDir, context.preparationConfig);
1492
+ const next = VerifyRunResourceSchema.parse({
1695
1493
  ...failedWithoutReadiness,
1696
1494
  readiness,
1697
1495
  events: [this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, readiness)],
1698
1496
  });
1699
- this.writeTestRun(context.compiledPath, next);
1497
+ this.writeVerifyRun(prepDataDir, context.compiledPath, next);
1498
+ }
1499
+ finally {
1500
+ this.endActiveRun();
1700
1501
  }
1701
1502
  }
1702
- async runReadinessCheckDraftInBackground(request, runId) {
1503
+ async runReadinessCheckDraftInBackground(prepDataDir, request, runId) {
1504
+ this.beginActiveRun();
1505
+ return this.runReadinessCheckDraftInBackgroundInner(prepDataDir, request, runId).finally(() => {
1506
+ this.endActiveRun();
1507
+ });
1508
+ }
1509
+ async runReadinessCheckDraftInBackgroundInner(prepDataDir, request, runId) {
1703
1510
  try {
1704
1511
  if (!this.handlers.createReadinessCheckDraft) {
1705
1512
  throw new Error("No readiness-check-draft handler is configured for this local service.");
1706
1513
  }
1707
- const result = ReadinessCheckDraftResultSchema.parse(await this.handlers.createReadinessCheckDraft(request, this.jobRunContext(runId)));
1708
- this.appendJobRunEvent(runId, {
1514
+ const result = ReadinessCheckDraftResultSchema.parse(await this.handlers.createReadinessCheckDraft(request, this.jobRunContext(prepDataDir, runId)));
1515
+ this.appendJobRunEvent(prepDataDir, runId, {
1709
1516
  type: "step.completed",
1710
1517
  step_id: "agent-draft",
1711
1518
  message: `Drafted ${result.checks.length} readiness checks.`,
@@ -1713,7 +1520,7 @@ export class LocalServiceRuntime {
1713
1520
  checks: result.checks,
1714
1521
  },
1715
1522
  });
1716
- this.appendJobRunEvent(runId, {
1523
+ this.appendJobRunEvent(prepDataDir, runId, {
1717
1524
  type: "step.started",
1718
1525
  step_id: "normalize-checks",
1719
1526
  message: "Normalizing drafted readiness checks into saved check records.",
@@ -1721,7 +1528,7 @@ export class LocalServiceRuntime {
1721
1528
  checks: result.checks.length,
1722
1529
  },
1723
1530
  });
1724
- this.appendJobRunEvent(runId, {
1531
+ this.appendJobRunEvent(prepDataDir, runId, {
1725
1532
  type: "step.completed",
1726
1533
  step_id: "normalize-checks",
1727
1534
  message: `${result.checks.length} readiness checks ready for review.`,
@@ -1729,15 +1536,15 @@ export class LocalServiceRuntime {
1729
1536
  checks: result.checks.length,
1730
1537
  },
1731
1538
  });
1732
- this.setJobRunResult(runId, result);
1733
- this.appendJobRunEvent(runId, {
1539
+ this.setJobRunResult(prepDataDir, runId, result);
1540
+ this.appendJobRunEvent(prepDataDir, runId, {
1734
1541
  type: "job.completed",
1735
1542
  message: `Drafted ${result.checks.length} readiness checks.`,
1736
1543
  });
1737
1544
  }
1738
1545
  catch (error) {
1739
1546
  const message = error instanceof Error ? error.message : String(error);
1740
- this.appendJobRunEvent(runId, {
1547
+ this.appendJobRunEvent(prepDataDir, runId, {
1741
1548
  type: "step.failed",
1742
1549
  step_id: "agent-draft",
1743
1550
  message,
@@ -1745,20 +1552,26 @@ export class LocalServiceRuntime {
1745
1552
  error: message,
1746
1553
  },
1747
1554
  });
1748
- this.appendJobRunEvent(runId, {
1555
+ this.appendJobRunEvent(prepDataDir, runId, {
1749
1556
  type: "job.failed",
1750
1557
  message,
1751
1558
  });
1752
1559
  }
1753
1560
  }
1754
- async runMethodAuthoringInBackground(request, runId) {
1561
+ async runMethodAuthoringInBackground(prepDataDir, request, runId) {
1562
+ this.beginActiveRun();
1563
+ return this.runMethodAuthoringInBackgroundInner(prepDataDir, request, runId).finally(() => {
1564
+ this.endActiveRun();
1565
+ });
1566
+ }
1567
+ async runMethodAuthoringInBackgroundInner(prepDataDir, request, runId) {
1755
1568
  try {
1756
1569
  if (!this.handlers.createMethodAuthoringRun) {
1757
1570
  throw new Error("No Method-authoring handler is configured for this local service.");
1758
1571
  }
1759
- const result = MethodAuthoringResultSchema.parse(await this.handlers.createMethodAuthoringRun(request, this.jobRunContext(runId)));
1760
- this.setJobRunResult(runId, result);
1761
- this.appendJobRunEvent(runId, {
1572
+ const result = MethodAuthoringResultSchema.parse(await this.handlers.createMethodAuthoringRun(request, this.jobRunContext(prepDataDir, runId)));
1573
+ this.setJobRunResult(prepDataDir, runId, result);
1574
+ this.appendJobRunEvent(prepDataDir, runId, {
1762
1575
  type: result.status === "executor-failed" ? "step.failed" : "step.completed",
1763
1576
  step_id: "draft-package",
1764
1577
  message: result.summary,
@@ -1769,7 +1582,7 @@ export class LocalServiceRuntime {
1769
1582
  shell_path: result.shell_path,
1770
1583
  },
1771
1584
  });
1772
- this.appendJobRunEvent(runId, {
1585
+ this.appendJobRunEvent(prepDataDir, runId, {
1773
1586
  type: "step.started",
1774
1587
  step_id: "validate-package",
1775
1588
  message: "Validating Method package structure and stage contract.",
@@ -1778,7 +1591,7 @@ export class LocalServiceRuntime {
1778
1591
  },
1779
1592
  });
1780
1593
  if (result.status === "updated" || result.status === "no-change") {
1781
- this.appendJobRunEvent(runId, {
1594
+ this.appendJobRunEvent(prepDataDir, runId, {
1782
1595
  type: "step.completed",
1783
1596
  step_id: "validate-package",
1784
1597
  message: result.summary,
@@ -1787,13 +1600,13 @@ export class LocalServiceRuntime {
1787
1600
  validation: result.validation ?? null,
1788
1601
  },
1789
1602
  });
1790
- this.appendJobRunEvent(runId, {
1603
+ this.appendJobRunEvent(prepDataDir, runId, {
1791
1604
  type: "job.completed",
1792
1605
  message: result.summary,
1793
1606
  });
1794
1607
  }
1795
1608
  else {
1796
- this.appendJobRunEvent(runId, {
1609
+ this.appendJobRunEvent(prepDataDir, runId, {
1797
1610
  type: "step.failed",
1798
1611
  step_id: "validate-package",
1799
1612
  message: result.summary,
@@ -1802,7 +1615,7 @@ export class LocalServiceRuntime {
1802
1615
  validation: result.validation ?? null,
1803
1616
  },
1804
1617
  });
1805
- this.appendJobRunEvent(runId, {
1618
+ this.appendJobRunEvent(prepDataDir, runId, {
1806
1619
  type: "job.failed",
1807
1620
  message: result.summary,
1808
1621
  });
@@ -1810,7 +1623,7 @@ export class LocalServiceRuntime {
1810
1623
  }
1811
1624
  catch (error) {
1812
1625
  const message = error instanceof Error ? error.message : String(error);
1813
- this.appendJobRunEvent(runId, {
1626
+ this.appendJobRunEvent(prepDataDir, runId, {
1814
1627
  type: "step.failed",
1815
1628
  step_id: "draft-package",
1816
1629
  message,
@@ -1818,29 +1631,29 @@ export class LocalServiceRuntime {
1818
1631
  error: message,
1819
1632
  },
1820
1633
  });
1821
- this.appendJobRunEvent(runId, {
1634
+ this.appendJobRunEvent(prepDataDir, runId, {
1822
1635
  type: "job.failed",
1823
1636
  message,
1824
1637
  });
1825
1638
  }
1826
1639
  }
1827
- jobRunContext(runId) {
1640
+ jobRunContext(prepDataDir, runId) {
1828
1641
  return {
1829
1642
  runId,
1830
- sourcePath: this.rootPath,
1643
+ sourcePath: prepDataDir,
1831
1644
  emit: (event) => {
1832
- this.appendJobRunEvent(runId, event);
1645
+ this.appendJobRunEvent(prepDataDir, runId, event);
1833
1646
  },
1834
1647
  };
1835
1648
  }
1836
- defaultPreparationName() {
1837
- const preparation = listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath))[0];
1649
+ defaultPreparationName(prepDataDir) {
1650
+ const preparation = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))[0];
1838
1651
  if (!preparation) {
1839
1652
  throw new Error("No Preparation is saved in this control plane folder.");
1840
1653
  }
1841
1654
  return preparation.name;
1842
1655
  }
1843
- async planActionProposal(request) {
1656
+ async planActionProposal(prepDataDir, request) {
1844
1657
  if (!this.handlers.planActionProposal) {
1845
1658
  return ActionProposalPlanSchema.parse({
1846
1659
  action_type: "clarification",
@@ -1848,15 +1661,15 @@ export class LocalServiceRuntime {
1848
1661
  assistant_message: "No local action planner is configured for this Interf Workspace.",
1849
1662
  });
1850
1663
  }
1851
- const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath));
1664
+ const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir));
1852
1665
  let rawPlan;
1853
1666
  try {
1854
1667
  rawPlan = await this.handlers.planActionProposal(request, {
1855
- sourcePath: this.rootPath,
1668
+ sourcePath: prepDataDir,
1856
1669
  preparations,
1857
1670
  preparationHealth: preparations.map((preparation) => {
1858
1671
  const readinessChecks = preparation.checks?.length ?? 0;
1859
- const portableContextReady = hasCompiledTestTarget(this.rootPath, preparation);
1672
+ const portableContextReady = hasCompiledTestTarget(prepDataDir, preparation);
1860
1673
  return {
1861
1674
  name: preparation.name,
1862
1675
  readiness_checks: readinessChecks,
@@ -1869,8 +1682,8 @@ export class LocalServiceRuntime {
1869
1682
  : ["prepare"],
1870
1683
  };
1871
1684
  }),
1872
- sourceFolders: listSourceFolderChoices(this.rootPath),
1873
- recentProposals: this.listActionProposals().slice(0, 5),
1685
+ sourceFolders: listSourceFolderChoices(prepDataDir),
1686
+ recentProposals: this.listActionProposals(prepDataDir).slice(0, 5),
1874
1687
  });
1875
1688
  }
1876
1689
  catch {
@@ -1927,7 +1740,7 @@ export class LocalServiceRuntime {
1927
1740
  error: null,
1928
1741
  });
1929
1742
  }
1930
- async buildActionProposal(request) {
1743
+ async buildActionProposal(prepDataDir, request) {
1931
1744
  const structuredPreparationSetup = PreparationSetupActionValuesSchema.safeParse(request.values);
1932
1745
  const structuredMethodAuthoring = MethodAuthoringActionValuesSchema.safeParse(request.values);
1933
1746
  const structuredActionType = actionTypeFromValues(request.values);
@@ -1952,7 +1765,7 @@ export class LocalServiceRuntime {
1952
1765
  action_type: structuredPlanActionType,
1953
1766
  ...(request.preparation ? { preparation: request.preparation } : {}),
1954
1767
  })
1955
- : await this.planActionProposal(request);
1768
+ : await this.planActionProposal(prepDataDir, request);
1956
1769
  const actionType = structuredPlanActionType ?? plan.action_type;
1957
1770
  if (directServiceEndpointForAction(actionType)) {
1958
1771
  return this.directServiceActionClarification({
@@ -2003,13 +1816,13 @@ export class LocalServiceRuntime {
2003
1816
  const requestedPreparationName = plan.preparation ?? request.preparation ?? null;
2004
1817
  const fallbackPreparation = requestedPreparationName
2005
1818
  ? null
2006
- : listSourcePreparationConfigs(loadSourceFolderConfig(this.rootPath))[0] ?? null;
1819
+ : listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))[0] ?? null;
2007
1820
  const preparationConfig = requestedPreparationName
2008
- ? this.resolvePreparationConfig(requestedPreparationName)
1821
+ ? this.resolvePreparationConfig(prepDataDir, requestedPreparationName)
2009
1822
  : fallbackPreparation;
2010
1823
  const preparationPath = preparationConfig
2011
- ? resolveSourcePreparationPath(this.rootPath, preparationConfig)
2012
- : resolveConfiguredSourceFolderPath(this.rootPath) ?? this.rootPath;
1824
+ ? resolveSourcePreparationPath(prepDataDir, preparationConfig)
1825
+ : resolveConfiguredSourceFolderPath(prepDataDir) ?? prepDataDir;
2013
1826
  const requestedMethodId = stringValue(request.values, "method_id") ??
2014
1827
  stringValue(request.values, "method");
2015
1828
  const plannedMethodId = plan.method ??
@@ -2057,9 +1870,9 @@ export class LocalServiceRuntime {
2057
1870
  error: null,
2058
1871
  });
2059
1872
  }
2060
- const preparationConfig = this.resolvePreparationConfig(plan.preparation ?? request.preparation ?? this.defaultPreparationName());
1873
+ const preparationConfig = this.resolvePreparationConfig(prepDataDir, plan.preparation ?? request.preparation ?? this.defaultPreparationName(prepDataDir));
2061
1874
  const proposalActionType = ActionProposalTypeSchema.parse(actionType);
2062
- const preparationPath = resolveSourcePreparationPath(this.rootPath, preparationConfig);
1875
+ const preparationPath = resolveSourcePreparationPath(prepDataDir, preparationConfig);
2063
1876
  const requestedMethodId = stringValue(request.values, "method_id") ??
2064
1877
  stringValue(request.values, "method");
2065
1878
  const plannedMethodId = plan.method ??
@@ -2093,7 +1906,7 @@ export class LocalServiceRuntime {
2093
1906
  if (actionType === "test") {
2094
1907
  const requestedMode = testModeFromValues(proposalValues);
2095
1908
  const hasReadinessChecks = (preparationConfig.checks ?? []).length > 0;
2096
- const portableContextReady = hasCompiledTestTarget(this.rootPath, preparationConfig);
1909
+ const portableContextReady = hasCompiledTestTarget(prepDataDir, preparationConfig);
2097
1910
  if (!hasReadinessChecks) {
2098
1911
  return clarifyResolvedAction({
2099
1912
  title: `Add readiness checks for ${preparationConfig.name}`,
@@ -2101,7 +1914,7 @@ export class LocalServiceRuntime {
2101
1914
  assistantMessage: `Preparation "${preparationConfig.name}" does not have saved readiness checks yet. Ask me to draft readiness checks after the Source Folder is prepared, or add readiness guidance first.`,
2102
1915
  });
2103
1916
  }
2104
- if (!portableContextReady && requestedMode !== "raw") {
1917
+ if (!portableContextReady && requestedMode !== "source-files") {
2105
1918
  return clarifyResolvedAction({
2106
1919
  title: `Prepare ${preparationConfig.name} first`,
2107
1920
  summary: "Readiness checks need portable context unless you explicitly ask for a source-files-only baseline.",
@@ -2117,10 +1930,11 @@ export class LocalServiceRuntime {
2117
1930
  };
2118
1931
  }
2119
1932
  if (actionType === "test") {
2120
- const defaultMode = hasCompiledTestTarget(this.rootPath, preparationConfig) ? "both" : "raw";
1933
+ // 0.15 verify runs always judge against the compiled
1934
+ // portable context. The legacy `mode` field is gone from the
1935
+ // wire request shape.
2121
1936
  return {
2122
1937
  preparation: preparationConfig.name,
2123
- mode: testModeValue(proposalValues, defaultMode),
2124
1938
  };
2125
1939
  }
2126
1940
  if (actionType === "readiness-check-draft") {
@@ -2170,9 +1984,7 @@ export class LocalServiceRuntime {
2170
1984
  return "Ask the configured local executor to draft saved readiness checks.";
2171
1985
  return "Ask the configured local executor to create a reusable local Method.";
2172
1986
  })();
2173
- const previewValues = proposalActionType === "test"
2174
- ? { mode: actionRequest.mode }
2175
- : proposalValues;
1987
+ const previewValues = proposalValues;
2176
1988
  const commandPreview = plan.command_preview ?? actionCommandPreview(proposalActionType, preparationConfig.name, methodId, previewValues);
2177
1989
  return ActionProposalResourceSchema.parse({
2178
1990
  kind: "interf-action-proposal",
@@ -2197,26 +2009,26 @@ export class LocalServiceRuntime {
2197
2009
  error: null,
2198
2010
  });
2199
2011
  }
2200
- async submitActionProposal(proposal) {
2012
+ async submitActionProposal(prepDataDir, proposal) {
2201
2013
  if (proposal.action_type === "clarification") {
2202
2014
  throw new Error("Clarification proposals cannot be submitted.");
2203
2015
  }
2204
2016
  if (proposal.action_type === "compile") {
2205
- const resource = await this.createCompileRun(proposal.request);
2017
+ const resource = await this.createCompileRun(prepDataDir, proposal.request);
2206
2018
  return {
2207
2019
  runId: resource.run.run_id,
2208
2020
  runType: "compile-run",
2209
2021
  };
2210
2022
  }
2211
2023
  if (proposal.action_type === "test") {
2212
- const resource = await this.createTestRun(proposal.request);
2024
+ const resource = await this.createVerifyRun(prepDataDir, proposal.request);
2213
2025
  return {
2214
2026
  runId: resource.run_id,
2215
- runType: "test-run",
2027
+ runType: "verify-run",
2216
2028
  };
2217
2029
  }
2218
2030
  if (proposal.action_type === "readiness-check-draft") {
2219
- const job = await this.createReadinessCheckDraftRun(proposal.request);
2031
+ const job = await this.createReadinessCheckDraftRun(prepDataDir, proposal.request);
2220
2032
  return {
2221
2033
  runId: job.run_id,
2222
2034
  runType: "job-run",
@@ -2226,14 +2038,14 @@ export class LocalServiceRuntime {
2226
2038
  if (directEndpoint) {
2227
2039
  throw new Error(`Action "${proposal.action_type}" must be submitted directly to ${directEndpoint}.`);
2228
2040
  }
2229
- const job = await this.createMethodAuthoringRun(proposal.request, proposal.action_type === "method-improvement" ? "method-improvement" : "method-authoring");
2041
+ const job = await this.createMethodAuthoringRun(prepDataDir, proposal.request, proposal.action_type === "method-improvement" ? "method-improvement" : "method-authoring");
2230
2042
  return {
2231
2043
  runId: job.run_id,
2232
2044
  runType: "job-run",
2233
2045
  };
2234
2046
  }
2235
- resolvePreparationConfig(preparationName, overrides = {}) {
2236
- const preparation = findSourcePreparationConfig(loadSourceFolderConfig(this.rootPath), preparationName);
2047
+ resolvePreparationConfig(prepDataDir, preparationName, overrides = {}) {
2048
+ const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), preparationName);
2237
2049
  if (!preparation) {
2238
2050
  throw new Error(`Preparation "${preparationName}" is not saved in this control plane folder.`);
2239
2051
  }
@@ -2245,49 +2057,59 @@ export class LocalServiceRuntime {
2245
2057
  ...(typeof overrides.max_loops === "number" ? { max_loops: overrides.max_loops } : {}),
2246
2058
  };
2247
2059
  }
2248
- ensureCompiledForRun(preparationConfig) {
2060
+ ensureCompiledForRun(prepDataDir, preparationConfig) {
2249
2061
  const methodId = requireSelectedMethod(preparationConfig);
2250
- const compiledPath = ensurePortableContextScaffold(this.rootPath, preparationConfig.name, methodId);
2062
+ const compiledPath = ensurePortableContextScaffold(prepDataDir, preparationConfig.name, methodId);
2251
2063
  syncCompiledInterfConfigFromSourcePreparationConfig(compiledPath, preparationConfig);
2252
2064
  return compiledPath;
2253
2065
  }
2254
2066
  readCompileRun(compiledPath, runId) {
2255
2067
  return readCompileRunAt(compileRunPath(compiledPath, runId));
2256
2068
  }
2257
- writeCompileRun(compiledPath, run) {
2069
+ writeCompileRun(prepDataDir, compiledPath, run) {
2258
2070
  mkdirSync(compileRunsRoot(compiledPath), { recursive: true });
2259
2071
  writeJsonFile(compileRunPath(compiledPath, run.run_id), CompileRunSchema.parse(run));
2072
+ // Bust per-preparation list + readiness caches so the next read
2073
+ // reflects the write. We invalidate broadly (per-preparation, not
2074
+ // per-record) because list recompute cost is bounded by run count
2075
+ // and the simpler model avoids fan-out bugs.
2076
+ this.compileRunCache.invalidatePreparation(prepDataDir, run.preparation);
2077
+ this.readinessCache.invalidatePreparation(prepDataDir, run.preparation);
2078
+ }
2079
+ writeJobRun(prepDataDir, run) {
2080
+ mkdirSync(localJobsRoot(prepDataDir), { recursive: true });
2081
+ writeJsonFile(localJobPath(prepDataDir, run.run_id), LocalJobRunResourceSchema.parse(run));
2082
+ if (run.preparation) {
2083
+ // Some job runs (readiness-check drafts) flip readiness state.
2084
+ this.readinessCache.invalidatePreparation(prepDataDir, run.preparation);
2085
+ }
2260
2086
  }
2261
- writeJobRun(run) {
2262
- mkdirSync(localJobsRoot(this.rootPath), { recursive: true });
2263
- writeJsonFile(localJobPath(this.rootPath, run.run_id), LocalJobRunResourceSchema.parse(run));
2264
- }
2265
- writeActionProposal(proposal) {
2266
- mkdirSync(actionProposalsRoot(this.rootPath), { recursive: true });
2267
- writeJsonFile(actionProposalPath(this.rootPath, proposal.proposal_id), ActionProposalResourceSchema.parse(proposal));
2087
+ writeActionProposal(prepDataDir, proposal) {
2088
+ mkdirSync(actionProposalsRoot(prepDataDir), { recursive: true });
2089
+ writeJsonFile(actionProposalPath(prepDataDir, proposal.proposal_id), ActionProposalResourceSchema.parse(proposal));
2268
2090
  }
2269
- setJobRunResult(runId, result) {
2270
- const current = this.getJob(runId);
2091
+ setJobRunResult(prepDataDir, runId, result) {
2092
+ const current = this.getJob(prepDataDir, runId);
2271
2093
  if (!current)
2272
2094
  return;
2273
2095
  const normalizedResult = result && typeof result === "object" && !Array.isArray(result)
2274
2096
  ? result
2275
2097
  : { value: result };
2276
- this.writeJobRun(LocalJobRunResourceSchema.parse({
2098
+ this.writeJobRun(prepDataDir, LocalJobRunResourceSchema.parse({
2277
2099
  ...current,
2278
2100
  result: normalizedResult,
2279
2101
  }));
2280
2102
  }
2281
- async recordCompileRunEvent(compiledPath, runId, event) {
2103
+ async recordCompileRunEvent(prepDataDir, compiledPath, runId, event) {
2282
2104
  const current = this.readCompileRun(compiledPath, runId);
2283
2105
  if (!current)
2284
2106
  return;
2285
- this.writeCompileRun(compiledPath, applyEventToCompileRun(current, event));
2107
+ this.writeCompileRun(prepDataDir, compiledPath, applyEventToCompileRun(current, event));
2286
2108
  if (event.type === "stage.passed" || event.type === "stage.failed") {
2287
- this.refreshCompileRunFromRuntime(compiledPath, runId);
2109
+ this.refreshCompileRunFromRuntime(prepDataDir, compiledPath, runId);
2288
2110
  }
2289
2111
  }
2290
- refreshCompileRunFromRuntime(compiledPath, runId) {
2112
+ refreshCompileRunFromRuntime(prepDataDir, compiledPath, runId) {
2291
2113
  const current = this.readCompileRun(compiledPath, runId);
2292
2114
  if (!current)
2293
2115
  return;
@@ -2334,9 +2156,9 @@ export class LocalServiceRuntime {
2334
2156
  }),
2335
2157
  };
2336
2158
  next.latest_proof = [...next.stages].reverse().find((stage) => Boolean(stage.latest_proof))?.latest_proof ?? next.latest_proof;
2337
- this.writeCompileRun(compiledPath, next);
2159
+ this.writeCompileRun(prepDataDir, compiledPath, next);
2338
2160
  }
2339
- async emitRuntimeDerivedEvents(compiledPath, runId) {
2161
+ async emitRuntimeDerivedEvents(prepDataDir, compiledPath, runId) {
2340
2162
  const state = loadState(compiledPath);
2341
2163
  const run = this.readCompileRun(compiledPath, runId);
2342
2164
  if (!state?.stages || !run)
@@ -2347,7 +2169,7 @@ export class LocalServiceRuntime {
2347
2169
  continue;
2348
2170
  const artifacts = stageArtifactRefs(stage.stage_id, stageState.artifacts);
2349
2171
  for (const artifact of artifacts) {
2350
- await this.recordCompileRunEvent(compiledPath, runId, {
2172
+ await this.recordCompileRunEvent(prepDataDir, compiledPath, runId, {
2351
2173
  type: "artifact.written",
2352
2174
  event_id: createRunEventId("event"),
2353
2175
  run_id: runId,
@@ -2356,7 +2178,7 @@ export class LocalServiceRuntime {
2356
2178
  artifact,
2357
2179
  });
2358
2180
  }
2359
- await this.recordCompileRunEvent(compiledPath, runId, {
2181
+ await this.recordCompileRunEvent(prepDataDir, compiledPath, runId, {
2360
2182
  type: "proof.updated",
2361
2183
  event_id: createRunEventId("event"),
2362
2184
  run_id: runId,
@@ -2372,11 +2194,11 @@ export class LocalServiceRuntime {
2372
2194
  });
2373
2195
  }
2374
2196
  }
2375
- readLatestReadinessRun(preparationName) {
2376
- return readSavedReadinessCheckRun(this.rootPath, preparationName);
2197
+ readLatestReadinessRun(prepDataDir, preparationName) {
2198
+ return readSavedReadinessCheckRun(prepDataDir, preparationName);
2377
2199
  }
2378
2200
  checksEvaluatedEvent(runId, readinessRun) {
2379
- const target = readinessRun.compiled ?? readinessRun.raw;
2201
+ const target = readinessRun.compiled ?? readinessRun.source_files;
2380
2202
  return {
2381
2203
  type: "checks.evaluated",
2382
2204
  event_id: createRunEventId("event"),
@@ -2397,9 +2219,11 @@ export class LocalServiceRuntime {
2397
2219
  readiness,
2398
2220
  };
2399
2221
  }
2400
- writeTestRun(compiledPath, run) {
2401
- mkdirSync(testRunsRoot(compiledPath), { recursive: true });
2402
- writeJsonFile(testRunPath(compiledPath, run.run_id), TestRunResourceSchema.parse(run));
2222
+ writeVerifyRun(prepDataDir, compiledPath, run) {
2223
+ mkdirSync(verifyRunsRoot(compiledPath), { recursive: true });
2224
+ writeJsonFile(verifyRunPath(compiledPath, run.run_id), VerifyRunResourceSchema.parse(run));
2225
+ this.verifyRunCache.invalidatePreparation(prepDataDir, run.preparation);
2226
+ this.readinessCache.invalidatePreparation(prepDataDir, run.preparation);
2403
2227
  }
2404
2228
  }
2405
2229
  export function createLocalServiceRuntime(options) {