@united-workforce/cli 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +45 -11
  2. package/dist/.build-fingerprint +1 -0
  3. package/dist/__tests__/adapter-json-roundtrip.test.js +17 -7
  4. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
  5. package/dist/__tests__/agent-resolution-llm-free.test.d.ts +2 -0
  6. package/dist/__tests__/agent-resolution-llm-free.test.d.ts.map +1 -0
  7. package/dist/__tests__/agent-resolution-llm-free.test.js +30 -0
  8. package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -0
  9. package/dist/__tests__/build-step-entry.test.d.ts +2 -0
  10. package/dist/__tests__/build-step-entry.test.d.ts.map +1 -0
  11. package/dist/__tests__/build-step-entry.test.js +173 -0
  12. package/dist/__tests__/build-step-entry.test.js.map +1 -0
  13. package/dist/__tests__/clear-thread-failed-attempts.test.d.ts +2 -0
  14. package/dist/__tests__/clear-thread-failed-attempts.test.d.ts.map +1 -0
  15. package/dist/__tests__/clear-thread-failed-attempts.test.js +93 -0
  16. package/dist/__tests__/clear-thread-failed-attempts.test.js.map +1 -0
  17. package/dist/__tests__/concurrency.test.d.ts +2 -0
  18. package/dist/__tests__/concurrency.test.d.ts.map +1 -0
  19. package/dist/__tests__/concurrency.test.js +196 -0
  20. package/dist/__tests__/concurrency.test.js.map +1 -0
  21. package/dist/__tests__/config.test.js +26 -302
  22. package/dist/__tests__/config.test.js.map +1 -1
  23. package/dist/__tests__/current-role.test.js +7 -6
  24. package/dist/__tests__/current-role.test.js.map +1 -1
  25. package/dist/__tests__/e2e-mock-agent.test.js +43 -30
  26. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
  27. package/dist/__tests__/format-text-default.test.d.ts +2 -0
  28. package/dist/__tests__/format-text-default.test.d.ts.map +1 -0
  29. package/dist/__tests__/format-text-default.test.js +43 -0
  30. package/dist/__tests__/format-text-default.test.js.map +1 -0
  31. package/dist/__tests__/format-text-registry.test.d.ts +2 -0
  32. package/dist/__tests__/format-text-registry.test.d.ts.map +1 -0
  33. package/dist/__tests__/format-text-registry.test.js +158 -0
  34. package/dist/__tests__/format-text-registry.test.js.map +1 -0
  35. package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts +2 -0
  36. package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts.map +1 -0
  37. package/dist/__tests__/issue-180-workflow-ref-removed.test.js +40 -0
  38. package/dist/__tests__/issue-180-workflow-ref-removed.test.js.map +1 -0
  39. package/dist/__tests__/log-text-renderer.test.d.ts +2 -0
  40. package/dist/__tests__/log-text-renderer.test.d.ts.map +1 -0
  41. package/dist/__tests__/log-text-renderer.test.js +265 -0
  42. package/dist/__tests__/log-text-renderer.test.js.map +1 -0
  43. package/dist/__tests__/moderator-evaluate.test.js +9 -50
  44. package/dist/__tests__/moderator-evaluate.test.js.map +1 -1
  45. package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts +2 -0
  46. package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts.map +1 -0
  47. package/dist/__tests__/output-mapper-thread-list-startedat.test.js +102 -0
  48. package/dist/__tests__/output-mapper-thread-list-startedat.test.js.map +1 -0
  49. package/dist/__tests__/output-mapper-workflow-add.test.d.ts +2 -0
  50. package/dist/__tests__/output-mapper-workflow-add.test.d.ts.map +1 -0
  51. package/dist/__tests__/output-mapper-workflow-add.test.js +22 -0
  52. package/dist/__tests__/output-mapper-workflow-add.test.js.map +1 -0
  53. package/dist/__tests__/pid-recycling.test.d.ts +2 -0
  54. package/dist/__tests__/pid-recycling.test.d.ts.map +1 -0
  55. package/dist/__tests__/pid-recycling.test.js +273 -0
  56. package/dist/__tests__/pid-recycling.test.js.map +1 -0
  57. package/dist/__tests__/prompt.test.js +365 -2
  58. package/dist/__tests__/prompt.test.js.map +1 -1
  59. package/dist/__tests__/resolve-head-hash.test.js +12 -4
  60. package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
  61. package/dist/__tests__/setup-agent-discovery.test.js +21 -30
  62. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
  63. package/dist/__tests__/setup-complexity.test.js +2 -168
  64. package/dist/__tests__/setup-complexity.test.js.map +1 -1
  65. package/dist/__tests__/setup-no-llm.test.d.ts +2 -0
  66. package/dist/__tests__/setup-no-llm.test.d.ts.map +1 -0
  67. package/dist/__tests__/setup-no-llm.test.js +52 -0
  68. package/dist/__tests__/setup-no-llm.test.js.map +1 -0
  69. package/dist/__tests__/solve-issue-tea-worktree.test.js +27 -28
  70. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
  71. package/dist/__tests__/step-ask.test.d.ts +2 -0
  72. package/dist/__tests__/step-ask.test.d.ts.map +1 -0
  73. package/dist/__tests__/step-ask.test.js +507 -0
  74. package/dist/__tests__/step-ask.test.js.map +1 -0
  75. package/dist/__tests__/step-show-json.test.js +1 -0
  76. package/dist/__tests__/step-show-json.test.js.map +1 -1
  77. package/dist/__tests__/step-timing.test.js +2 -0
  78. package/dist/__tests__/step-timing.test.js.map +1 -1
  79. package/dist/__tests__/store-global-cas.test.js +2 -2
  80. package/dist/__tests__/store-global-cas.test.js.map +1 -1
  81. package/dist/__tests__/store-unified-threads.test.js +28 -26
  82. package/dist/__tests__/store-unified-threads.test.js.map +1 -1
  83. package/dist/__tests__/thread-cancel-status.test.js +25 -19
  84. package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
  85. package/dist/__tests__/thread-cancel-text-renderer.test.d.ts +2 -0
  86. package/dist/__tests__/thread-cancel-text-renderer.test.d.ts.map +1 -0
  87. package/dist/__tests__/thread-cancel-text-renderer.test.js +110 -0
  88. package/dist/__tests__/thread-cancel-text-renderer.test.js.map +1 -0
  89. package/dist/__tests__/thread-list-filters.test.js +354 -17
  90. package/dist/__tests__/thread-list-filters.test.js.map +1 -1
  91. package/dist/__tests__/thread-list-template-ms-date.test.d.ts +2 -0
  92. package/dist/__tests__/thread-list-template-ms-date.test.d.ts.map +1 -0
  93. package/dist/__tests__/thread-list-template-ms-date.test.js +102 -0
  94. package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -0
  95. package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts +2 -0
  96. package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts.map +1 -0
  97. package/dist/__tests__/thread-list-workflow-corrupt.test.js +157 -0
  98. package/dist/__tests__/thread-list-workflow-corrupt.test.js.map +1 -0
  99. package/dist/__tests__/thread-poke.test.d.ts +2 -0
  100. package/dist/__tests__/thread-poke.test.d.ts.map +1 -0
  101. package/dist/__tests__/thread-poke.test.js +422 -0
  102. package/dist/__tests__/thread-poke.test.js.map +1 -0
  103. package/dist/__tests__/thread-read-xml-tags.test.js +10 -9
  104. package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -1
  105. package/dist/__tests__/thread-resume.test.js +21 -15
  106. package/dist/__tests__/thread-resume.test.js.map +1 -1
  107. package/dist/__tests__/thread-show-status.test.js +17 -28
  108. package/dist/__tests__/thread-show-status.test.js.map +1 -1
  109. package/dist/__tests__/thread-start-cwd-cli.test.js +15 -3
  110. package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -1
  111. package/dist/__tests__/thread-stop-text-renderer.test.d.ts +2 -0
  112. package/dist/__tests__/thread-stop-text-renderer.test.d.ts.map +1 -0
  113. package/dist/__tests__/thread-stop-text-renderer.test.js +148 -0
  114. package/dist/__tests__/thread-stop-text-renderer.test.js.map +1 -0
  115. package/dist/__tests__/thread-suspend-step.test.js +13 -16
  116. package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
  117. package/dist/__tests__/thread-suspended-display.test.js +10 -22
  118. package/dist/__tests__/thread-suspended-display.test.js.map +1 -1
  119. package/dist/__tests__/thread-test-helpers.d.ts +7 -0
  120. package/dist/__tests__/thread-test-helpers.d.ts.map +1 -1
  121. package/dist/__tests__/thread-test-helpers.js +13 -0
  122. package/dist/__tests__/thread-test-helpers.js.map +1 -1
  123. package/dist/__tests__/thread.test.js +15 -13
  124. package/dist/__tests__/thread.test.js.map +1 -1
  125. package/dist/__tests__/validate-semantic.test.js +105 -23
  126. package/dist/__tests__/validate-semantic.test.js.map +1 -1
  127. package/dist/__tests__/workflow-list-recursive.test.d.ts +2 -0
  128. package/dist/__tests__/workflow-list-recursive.test.d.ts.map +1 -0
  129. package/dist/__tests__/workflow-list-recursive.test.js +286 -0
  130. package/dist/__tests__/workflow-list-recursive.test.js.map +1 -0
  131. package/dist/__tests__/workflow-resolution.test.js +46 -28
  132. package/dist/__tests__/workflow-resolution.test.js.map +1 -1
  133. package/dist/__tests__/workflow-show-resolution.test.d.ts +2 -0
  134. package/dist/__tests__/workflow-show-resolution.test.d.ts.map +1 -0
  135. package/dist/__tests__/workflow-show-resolution.test.js +213 -0
  136. package/dist/__tests__/workflow-show-resolution.test.js.map +1 -0
  137. package/dist/__tests__/workflow-validate.test.d.ts +2 -0
  138. package/dist/__tests__/workflow-validate.test.d.ts.map +1 -0
  139. package/dist/__tests__/workflow-validate.test.js +707 -0
  140. package/dist/__tests__/workflow-validate.test.js.map +1 -0
  141. package/dist/__tests__/write-envelope.test.d.ts +2 -0
  142. package/dist/__tests__/write-envelope.test.d.ts.map +1 -0
  143. package/dist/__tests__/write-envelope.test.js +201 -0
  144. package/dist/__tests__/write-envelope.test.js.map +1 -0
  145. package/dist/background/background.d.ts +22 -1
  146. package/dist/background/background.d.ts.map +1 -1
  147. package/dist/background/background.js +83 -6
  148. package/dist/background/background.js.map +1 -1
  149. package/dist/background/index.d.ts +1 -1
  150. package/dist/background/index.d.ts.map +1 -1
  151. package/dist/background/index.js +1 -1
  152. package/dist/background/index.js.map +1 -1
  153. package/dist/background/types.d.ts +1 -0
  154. package/dist/background/types.d.ts.map +1 -1
  155. package/dist/cli.js +120 -62
  156. package/dist/cli.js.map +1 -1
  157. package/dist/commands/config.d.ts +3 -1
  158. package/dist/commands/config.d.ts.map +1 -1
  159. package/dist/commands/config.js +17 -31
  160. package/dist/commands/config.js.map +1 -1
  161. package/dist/commands/prompt.d.ts.map +1 -1
  162. package/dist/commands/prompt.js +57 -31
  163. package/dist/commands/prompt.js.map +1 -1
  164. package/dist/commands/setup.d.ts +12 -39
  165. package/dist/commands/setup.d.ts.map +1 -1
  166. package/dist/commands/setup.js +72 -303
  167. package/dist/commands/setup.js.map +1 -1
  168. package/dist/commands/step.d.ts +44 -1
  169. package/dist/commands/step.d.ts.map +1 -1
  170. package/dist/commands/step.js +255 -11
  171. package/dist/commands/step.js.map +1 -1
  172. package/dist/commands/thread.d.ts +16 -3
  173. package/dist/commands/thread.d.ts.map +1 -1
  174. package/dist/commands/thread.js +423 -142
  175. package/dist/commands/thread.js.map +1 -1
  176. package/dist/commands/workflow.d.ts +9 -1
  177. package/dist/commands/workflow.d.ts.map +1 -1
  178. package/dist/commands/workflow.js +126 -6
  179. package/dist/commands/workflow.js.map +1 -1
  180. package/dist/concurrency/concurrency.d.ts +34 -0
  181. package/dist/concurrency/concurrency.d.ts.map +1 -0
  182. package/dist/concurrency/concurrency.js +216 -0
  183. package/dist/concurrency/concurrency.js.map +1 -0
  184. package/dist/concurrency/index.d.ts +3 -0
  185. package/dist/concurrency/index.d.ts.map +1 -0
  186. package/dist/concurrency/index.js +2 -0
  187. package/dist/concurrency/index.js.map +1 -0
  188. package/dist/concurrency/types.d.ts +19 -0
  189. package/dist/concurrency/types.d.ts.map +1 -0
  190. package/dist/concurrency/types.js +2 -0
  191. package/dist/concurrency/types.js.map +1 -0
  192. package/dist/format.d.ts +69 -2
  193. package/dist/format.d.ts.map +1 -1
  194. package/dist/format.js +198 -1
  195. package/dist/format.js.map +1 -1
  196. package/dist/moderator/__tests__/evaluate.test.js +31 -17
  197. package/dist/moderator/__tests__/evaluate.test.js.map +1 -1
  198. package/dist/moderator/evaluate.d.ts.map +1 -1
  199. package/dist/moderator/evaluate.js +4 -16
  200. package/dist/moderator/evaluate.js.map +1 -1
  201. package/dist/moderator/index.d.ts +1 -2
  202. package/dist/moderator/index.d.ts.map +1 -1
  203. package/dist/moderator/index.js +0 -1
  204. package/dist/moderator/index.js.map +1 -1
  205. package/dist/moderator/types.d.ts +6 -10
  206. package/dist/moderator/types.d.ts.map +1 -1
  207. package/dist/moderator/types.js +1 -3
  208. package/dist/moderator/types.js.map +1 -1
  209. package/dist/output-mappers.d.ts +122 -0
  210. package/dist/output-mappers.d.ts.map +1 -0
  211. package/dist/output-mappers.js +134 -0
  212. package/dist/output-mappers.js.map +1 -0
  213. package/dist/schemas.d.ts +6 -1
  214. package/dist/schemas.d.ts.map +1 -1
  215. package/dist/schemas.js +34 -5
  216. package/dist/schemas.js.map +1 -1
  217. package/dist/store.d.ts +28 -9
  218. package/dist/store.d.ts.map +1 -1
  219. package/dist/store.js +75 -16
  220. package/dist/store.js.map +1 -1
  221. package/dist/text-renderers.d.ts +30 -0
  222. package/dist/text-renderers.d.ts.map +1 -0
  223. package/dist/text-renderers.js +251 -0
  224. package/dist/text-renderers.js.map +1 -0
  225. package/dist/validate-semantic.d.ts.map +1 -1
  226. package/dist/validate-semantic.js +95 -61
  227. package/dist/validate-semantic.js.map +1 -1
  228. package/dist/validate.d.ts +6 -0
  229. package/dist/validate.d.ts.map +1 -1
  230. package/dist/validate.js +24 -0
  231. package/dist/validate.js.map +1 -1
  232. package/examples/brainstorm.yaml +130 -0
  233. package/examples/debate.yaml +169 -0
  234. package/examples/socratic-questioning.yaml +112 -0
  235. package/package.json +9 -10
  236. package/src/__tests__/adapter-json-roundtrip.test.ts +16 -7
  237. package/src/__tests__/agent-resolution-llm-free.test.ts +39 -0
  238. package/src/__tests__/build-step-entry.test.ts +203 -0
  239. package/src/__tests__/clear-thread-failed-attempts.test.ts +122 -0
  240. package/src/__tests__/concurrency.test.ts +266 -0
  241. package/src/__tests__/config.test.ts +33 -321
  242. package/src/__tests__/current-role.test.ts +7 -6
  243. package/src/__tests__/e2e-mock-agent.test.ts +65 -30
  244. package/src/__tests__/fixtures/e2e-count.workflow.yaml +1 -0
  245. package/src/__tests__/fixtures/e2e-linear.workflow.yaml +1 -0
  246. package/src/__tests__/fixtures/{e2e-mustache.workflow.yaml → e2e-liquid.workflow.yaml} +3 -2
  247. package/src/__tests__/fixtures/e2e-loop.workflow.yaml +1 -0
  248. package/src/__tests__/fixtures/e2e-suspend.mock.yaml +2 -2
  249. package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +6 -10
  250. package/src/__tests__/format-text-default.test.ts +49 -0
  251. package/src/__tests__/format-text-registry.test.ts +173 -0
  252. package/src/__tests__/issue-180-workflow-ref-removed.test.ts +43 -0
  253. package/src/__tests__/log-text-renderer.test.ts +294 -0
  254. package/src/__tests__/moderator-evaluate.test.ts +9 -52
  255. package/src/__tests__/output-mapper-thread-list-startedat.test.ts +124 -0
  256. package/src/__tests__/output-mapper-workflow-add.test.ts +24 -0
  257. package/src/__tests__/pid-recycling.test.ts +329 -0
  258. package/src/__tests__/prompt.test.ts +443 -2
  259. package/src/__tests__/resolve-head-hash.test.ts +11 -4
  260. package/src/__tests__/setup-agent-discovery.test.ts +26 -51
  261. package/src/__tests__/setup-complexity.test.ts +1 -203
  262. package/src/__tests__/setup-no-llm.test.ts +68 -0
  263. package/src/__tests__/solve-issue-tea-worktree.test.ts +27 -31
  264. package/src/__tests__/step-ask.test.ts +677 -0
  265. package/src/__tests__/step-show-json.test.ts +1 -0
  266. package/src/__tests__/step-timing.test.ts +2 -0
  267. package/src/__tests__/store-global-cas.test.ts +2 -2
  268. package/src/__tests__/store-unified-threads.test.ts +30 -27
  269. package/src/__tests__/thread-cancel-status.test.ts +27 -20
  270. package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
  271. package/src/__tests__/thread-list-filters.test.ts +443 -17
  272. package/src/__tests__/thread-list-template-ms-date.test.ts +110 -0
  273. package/src/__tests__/thread-list-workflow-corrupt.test.ts +198 -0
  274. package/src/__tests__/thread-poke.test.ts +554 -0
  275. package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
  276. package/src/__tests__/thread-resume.test.ts +20 -15
  277. package/src/__tests__/thread-show-status.test.ts +17 -29
  278. package/src/__tests__/thread-start-cwd-cli.test.ts +15 -3
  279. package/src/__tests__/thread-stop-text-renderer.test.ts +168 -0
  280. package/src/__tests__/thread-suspend-step.test.ts +13 -16
  281. package/src/__tests__/thread-suspended-display.test.ts +10 -22
  282. package/src/__tests__/thread-test-helpers.ts +15 -1
  283. package/src/__tests__/thread.test.ts +14 -14
  284. package/src/__tests__/validate-semantic.test.ts +118 -33
  285. package/src/__tests__/workflow-list-recursive.test.ts +370 -0
  286. package/src/__tests__/workflow-resolution.test.ts +48 -29
  287. package/src/__tests__/workflow-show-resolution.test.ts +286 -0
  288. package/src/__tests__/workflow-validate.test.ts +828 -0
  289. package/src/__tests__/write-envelope.test.ts +257 -0
  290. package/src/background/background.ts +88 -6
  291. package/src/background/index.ts +2 -0
  292. package/src/background/types.ts +1 -0
  293. package/src/cli.ts +184 -77
  294. package/src/commands/config.ts +16 -33
  295. package/src/commands/prompt.ts +57 -31
  296. package/src/commands/setup.ts +80 -358
  297. package/src/commands/step.ts +339 -12
  298. package/src/commands/thread.ts +511 -171
  299. package/src/commands/workflow.ts +155 -4
  300. package/src/concurrency/concurrency.ts +245 -0
  301. package/src/concurrency/index.ts +10 -0
  302. package/src/concurrency/types.ts +19 -0
  303. package/src/format.ts +282 -2
  304. package/src/moderator/__tests__/evaluate.test.ts +34 -17
  305. package/src/moderator/evaluate.ts +5 -17
  306. package/src/moderator/index.ts +1 -6
  307. package/src/moderator/types.ts +6 -14
  308. package/src/output-mappers.ts +254 -0
  309. package/src/schemas.ts +51 -5
  310. package/src/store.ts +86 -20
  311. package/src/text-renderers.ts +355 -0
  312. package/src/validate-semantic.ts +125 -73
  313. package/src/validate.ts +27 -0
  314. package/dist/__tests__/setup-validate.test.d.ts +0 -2
  315. package/dist/__tests__/setup-validate.test.d.ts.map +0 -1
  316. package/dist/__tests__/setup-validate.test.js +0 -108
  317. package/dist/__tests__/setup-validate.test.js.map +0 -1
  318. package/src/__tests__/setup-validate.test.ts +0 -148
  319. /package/src/__tests__/fixtures/{e2e-mustache.mock.yaml → e2e-liquid.mock.yaml} +0 -0
@@ -27,12 +27,19 @@ const SUSPEND_MESSAGE = "Please clarify: Which API?";
27
27
  type MockAgentMode = "suspend" | "ok";
28
28
 
29
29
  let tmpDir: string;
30
+ let savedOcasHome: string | undefined;
30
31
 
31
32
  beforeEach(async () => {
33
+ savedOcasHome = process.env.OCAS_HOME;
32
34
  tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-resume-test-"));
33
35
  });
34
36
 
35
37
  afterEach(async () => {
38
+ if (savedOcasHome === undefined) {
39
+ delete process.env.OCAS_HOME;
40
+ } else {
41
+ process.env.OCAS_HOME = savedOcasHome;
42
+ }
36
43
  await rm(tmpDir, { recursive: true, force: true });
37
44
  });
38
45
 
@@ -75,11 +82,6 @@ async function setupSuspendedThread(mode: MockAgentMode): Promise<{
75
82
  resume: { role: "worker", prompt: "Resume the work", location: null },
76
83
  },
77
84
  worker: {
78
- needs_input: {
79
- role: "$SUSPEND",
80
- prompt: "Please clarify: {{{question}}}",
81
- location: null,
82
- },
83
85
  ok: { role: "reviewer", prompt: "Review the work", location: null },
84
86
  },
85
87
  reviewer: { done: { role: "$END", prompt: "Done", location: null } },
@@ -95,9 +97,9 @@ async function setupSuspendedThread(mode: MockAgentMode): Promise<{
95
97
  process.env.OCAS_HOME = casDir;
96
98
  await seedThreads(tmpDir, { [THREAD_ID]: startHash });
97
99
 
98
- const outputHash = await store.cas.put(outputSchemaHash, {
99
- $status: "needs_input",
100
- question: "Which API?",
100
+ const outputHash = await store.cas.put(schemas.suspendOutput, {
101
+ $status: "$SUSPEND",
102
+ reason: SUSPEND_MESSAGE,
101
103
  });
102
104
  const detailHash = await store.cas.put(schemas.text, "mock detail");
103
105
 
@@ -132,14 +134,15 @@ async function setupSuspendedThread(mode: MockAgentMode): Promise<{
132
134
  const mockAgentPath = join(tmpDir, "mock-agent.sh");
133
135
 
134
136
  const frontmatter =
135
- mode === "suspend" ? { $status: "needs_input", question: "Which API?" } : { $status: "ok" };
137
+ mode === "suspend" ? { $status: "$SUSPEND", reason: SUSPEND_MESSAGE } : { $status: "ok" };
138
+ const frontmatterSchema = mode === "suspend" ? schemas.suspendOutput : outputSchemaHash;
136
139
 
137
140
  const adapterJson = JSON.stringify({
138
141
  stepHash: await store.cas.put(schemas.stepNode, {
139
142
  start: startHash,
140
143
  prev: stepHash,
141
144
  role: "worker",
142
- output: await store.cas.put(outputSchemaHash, frontmatter),
145
+ output: await store.cas.put(frontmatterSchema, frontmatter),
143
146
  detail: detailHash,
144
147
  agent: "uwf-mock",
145
148
  edgePrompt: "resume prompt placeholder",
@@ -177,7 +180,7 @@ echo '${adapterJson}'
177
180
  const configPath = join(tmpDir, "config.yaml");
178
181
  await writeFile(
179
182
  configPath,
180
- `defaultAgent: uwf-hermes\ndefaultModel: test-model\nagentOverrides: null\nagents: {}\nproviders: {}\nmodels: {}\n`,
183
+ `defaultAgent: uwf-hermes\nagentOverrides: null\nagents:\n uwf-hermes:\n command: uwf-hermes\n`,
181
184
  );
182
185
 
183
186
  return { casDir, mockAgentPath, promptCapturePath };
@@ -188,8 +191,10 @@ function runUwf(
188
191
  casDir: string,
189
192
  ): { stdout: string; stderr: string; status: number } {
190
193
  const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
194
+ // Tests parse stdout as bare JSON; default --format text would break that.
195
+ const formatArgs = args.includes("--format") ? args : ["--format", "raw-json", ...args];
191
196
  try {
192
- const stdout = execFileSync(process.execPath, [cliPath, ...args], {
197
+ const stdout = execFileSync(process.execPath, [cliPath, ...formatArgs], {
193
198
  encoding: "utf8",
194
199
  stdio: ["ignore", "pipe", "pipe"],
195
200
  env: {
@@ -338,7 +343,7 @@ describe("uwf thread resume", () => {
338
343
  }
339
344
  });
340
345
 
341
- test("multiple suspend/resume cycles", async () => {
346
+ test("multiple suspend/resume cycles", { timeout: 15_000 }, async () => {
342
347
  const originalCasDir = process.env.OCAS_HOME;
343
348
  const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("suspend");
344
349
  process.env.OCAS_HOME = casDir;
@@ -537,7 +542,7 @@ describe("uwf thread resume - completed threads", () => {
537
542
  await seedThreads(tmpDir, {
538
543
  [THREAD_ID]: {
539
544
  head: reviewerStepHash,
540
- status: "completed",
545
+ status: "end",
541
546
  suspendedRole: null,
542
547
  suspendMessage: null,
543
548
  completedAt: 1716600002000,
@@ -599,7 +604,7 @@ echo '${adapterJson}'
599
604
  const configPath = join(tmpDir, "config.yaml");
600
605
  await writeFile(
601
606
  configPath,
602
- `defaultAgent: uwf-hermes\ndefaultModel: test-model\nagentOverrides: null\nagents: {}\nproviders: {}\nmodels: {}\n`,
607
+ `defaultAgent: uwf-hermes\nagentOverrides: null\nagents:\n uwf-hermes:\n command: uwf-hermes\n`,
603
608
  );
604
609
 
605
610
  const result = runUwf(
@@ -1,21 +1,12 @@
1
1
  import { mkdir, rm, writeFile } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { putSchema } from "@ocas/core";
5
4
  import type { CasRef, ThreadId } from "@united-workforce/protocol";
6
5
  import { describe, expect, test } from "vitest";
7
- import { createMarker, deleteMarker } from "../background/index.js";
6
+ import { createMarker, deleteMarker, getProcessStartTime } from "../background/index.js";
8
7
  import { cmdThreadShow, cmdThreadStart } from "../commands/thread.js";
9
8
  import { completeThread, createUwfStore, loadAllThreads, setThread } from "../store.js";
10
9
 
11
- const OUTPUT_SCHEMA = {
12
- type: "object" as const,
13
- properties: {
14
- $status: { type: "string" as const },
15
- question: { type: "string" as const },
16
- },
17
- };
18
-
19
10
  const TEST_WORKFLOW_YAML = `
20
11
  name: test-status
21
12
  description: Test workflow for status field
@@ -59,15 +50,12 @@ roles:
59
50
  capabilities: ["coding"]
60
51
  procedure: Work
61
52
  output: |
62
- $status: "needs_input"
63
- question: "Which API?"
53
+ $status: "done"
64
54
  frontmatter:
65
- oneOf:
66
- - type: object
67
- required: ["$status", "question"]
68
- properties:
69
- $status: { const: "needs_input" }
70
- question: { type: string }
55
+ type: object
56
+ required: ["$status"]
57
+ properties:
58
+ $status: { const: "done" }
71
59
  graph:
72
60
  $START:
73
61
  new:
@@ -79,9 +67,9 @@ graph:
79
67
  prompt: "Resume work"
80
68
  location: null
81
69
  worker:
82
- needs_input:
83
- role: $SUSPEND
84
- prompt: "Please clarify: {{{question}}}"
70
+ done:
71
+ role: $END
72
+ prompt: "Done"
85
73
  location: null
86
74
  `;
87
75
 
@@ -97,8 +85,7 @@ async function insertStepNode(
97
85
  if (headEntry === undefined) throw new Error(`thread ${threadId} not in index`);
98
86
  const head = headEntry.head;
99
87
 
100
- const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
101
- const outputHash = await uwf.store.cas.put(outputSchemaHash, outputPayload);
88
+ const outputHash = await uwf.store.cas.put(uwf.schemas.suspendOutput, outputPayload);
102
89
  const detailHash = await uwf.store.cas.put(uwf.schemas.text, "detail-placeholder");
103
90
 
104
91
  const headNode = uwf.store.cas.get(head);
@@ -183,6 +170,7 @@ describe("thread show status field", () => {
183
170
  workflow,
184
171
  pid: process.pid,
185
172
  startedAt: Date.now(),
173
+ processStartTime: getProcessStartTime(process.pid),
186
174
  });
187
175
 
188
176
  try {
@@ -216,11 +204,11 @@ describe("thread show status field", () => {
216
204
  const head = index[threadId]!.head;
217
205
  if (!head) throw new Error("Thread not found in index");
218
206
 
219
- completeThread(uwfForIndex.varStore, threadId, "completed");
207
+ completeThread(uwfForIndex.varStore, threadId, "end");
220
208
 
221
209
  const result = await cmdThreadShow(storageRoot, threadId);
222
210
 
223
- expect(result.status).toBe("completed");
211
+ expect(result.status).toBe("end");
224
212
  expect(result.done).toBe(true);
225
213
  expect(result.background).toBe(null);
226
214
  expect(result.thread).toBe(threadId);
@@ -274,11 +262,11 @@ describe("thread show status field", () => {
274
262
  const head = index[threadId]!.head;
275
263
  if (!head) throw new Error("Thread not found in index");
276
264
 
277
- completeThread(uwfForIndex.varStore, threadId, "completed");
265
+ completeThread(uwfForIndex.varStore, threadId, "end");
278
266
 
279
267
  const result = await cmdThreadShow(storageRoot, threadId);
280
268
 
281
- expect(result.status).toBe("completed");
269
+ expect(result.status).toBe("end");
282
270
  expect(result.done).toBe(true);
283
271
  expect(result.background).toBe(null);
284
272
 
@@ -300,8 +288,8 @@ describe("thread show status field", () => {
300
288
  const threadId = startResult.thread as ThreadId;
301
289
 
302
290
  await insertStepNode(storageRoot, threadId, "worker", {
303
- $status: "needs_input",
304
- question: "Which API?",
291
+ $status: "$SUSPEND",
292
+ reason: "Please clarify: Which API?",
305
293
  });
306
294
 
307
295
  const result = await cmdThreadShow(storageRoot, threadId);
@@ -150,16 +150,28 @@ graph:
150
150
  // Verify CLI accepts --cwd option (no error thrown)
151
151
  const output = execFileSync(
152
152
  process.execPath,
153
- [uwfBin, "thread", "start", "test-cwd-cli", "-p", "test prompt", "--cwd", testCwd],
153
+ [
154
+ uwfBin,
155
+ "--format",
156
+ "raw-json",
157
+ "thread",
158
+ "start",
159
+ "test-cwd-cli",
160
+ "-p",
161
+ "test prompt",
162
+ "--cwd",
163
+ testCwd,
164
+ ],
154
165
  {
155
166
  env: { ...process.env, UWF_HOME: storageRoot, OCAS_HOME: casDir },
156
167
  encoding: "utf8",
157
168
  },
158
169
  );
159
170
 
171
+ // raw-json envelope value for thread-start: { threadId, workflowHash }
160
172
  const result = JSON.parse(output);
161
- expect(result.thread).toBeDefined();
162
- expect(result.workflow).toBeDefined();
173
+ expect(result.threadId).toBeDefined();
174
+ expect(result.workflowHash).toBeDefined();
163
175
 
164
176
  // The fact that we got here without throwing means CLI accepted the --cwd option
165
177
  // The actual cwd functionality is tested by the other tests using cmdThreadStart directly
@@ -0,0 +1,168 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { formatOutput, getTextRenderer, TEXT_RENDERERS } from "../format.js";
3
+ import { renderThreadStop } from "../text-renderers.js";
4
+
5
+ describe("thread stop — text renderer registration", () => {
6
+ test("TEXT_RENDERERS contains 'thread stop'", () => {
7
+ expect(getTextRenderer("thread stop")).toBeDefined();
8
+ expect(typeof getTextRenderer("thread stop")).toBe("function");
9
+ });
10
+
11
+ test("TEXT_RENDERERS['thread stop'] is the same reference as renderThreadStop", () => {
12
+ expect(TEXT_RENDERERS["thread stop"]).toBe(renderThreadStop);
13
+ });
14
+
15
+ test("renderThreadStop is exported from text-renderers.ts", () => {
16
+ expect(typeof renderThreadStop).toBe("function");
17
+ });
18
+ });
19
+
20
+ describe("renderThreadStop — output shape (stopped=true)", () => {
21
+ test("returns a string for full payload", () => {
22
+ const out = renderThreadStop({
23
+ thread: "01JTEST00000000000000STOP1",
24
+ stopped: true,
25
+ });
26
+ expect(typeof out).toBe("string");
27
+ });
28
+
29
+ test("includes the stopped thread's ULID", () => {
30
+ const out = renderThreadStop({
31
+ thread: "01JTEST00000000000000STOP1",
32
+ stopped: true,
33
+ });
34
+ expect(out).toContain("01JTEST00000000000000STOP1");
35
+ });
36
+
37
+ test("indicates stopped status (yes)", () => {
38
+ const out = renderThreadStop({
39
+ thread: "01JTEST00000000000000STOP1",
40
+ stopped: true,
41
+ });
42
+ const lower = out.toLowerCase();
43
+ const hasStopMarker = lower.includes("stopped") && lower.includes("yes");
44
+ expect(hasStopMarker).toBe(true);
45
+ });
46
+
47
+ test("does NOT begin with '{' or '[' (not raw JSON)", () => {
48
+ const out = renderThreadStop({
49
+ thread: "01JTEST00000000000000STOP1",
50
+ stopped: true,
51
+ });
52
+ const trimmed = out.trimStart();
53
+ expect(trimmed.startsWith("{")).toBe(false);
54
+ expect(trimmed.startsWith("[")).toBe(false);
55
+ });
56
+
57
+ test("does NOT contain literal 'undefined'", () => {
58
+ const out = renderThreadStop({
59
+ thread: "01JTEST00000000000000STOP1",
60
+ stopped: true,
61
+ });
62
+ expect(out).not.toContain("undefined");
63
+ });
64
+ });
65
+
66
+ describe("renderThreadStop — stopped=false variant", () => {
67
+ test("returns a string for stopped=false payload", () => {
68
+ const out = renderThreadStop({
69
+ thread: "01JTEST00000000000000STOP1",
70
+ stopped: false,
71
+ });
72
+ expect(typeof out).toBe("string");
73
+ });
74
+
75
+ test("includes the thread's ULID even when stopped=false", () => {
76
+ const out = renderThreadStop({
77
+ thread: "01JTEST00000000000000STOP1",
78
+ stopped: false,
79
+ });
80
+ expect(out).toContain("01JTEST00000000000000STOP1");
81
+ });
82
+
83
+ test("indicates not-stopped status (no)", () => {
84
+ const out = renderThreadStop({
85
+ thread: "01JTEST00000000000000STOP1",
86
+ stopped: false,
87
+ });
88
+ const lower = out.toLowerCase();
89
+ const hasNoMarker = lower.includes("stopped") && lower.includes("no");
90
+ expect(hasNoMarker).toBe(true);
91
+ });
92
+
93
+ test("does NOT contain literal 'undefined' for stopped=false", () => {
94
+ const out = renderThreadStop({
95
+ thread: "01JTEST00000000000000STOP1",
96
+ stopped: false,
97
+ });
98
+ expect(out).not.toContain("undefined");
99
+ });
100
+ });
101
+
102
+ describe("renderThreadStop — partial / missing data", () => {
103
+ test("missing 'stopped' field — returns string, no throw, no 'undefined'", () => {
104
+ const out = renderThreadStop({ thread: "01JTEST00000000000000STOP1" });
105
+ expect(typeof out).toBe("string");
106
+ expect(out).not.toContain("undefined");
107
+ });
108
+
109
+ test("missing 'thread' field — returns string, no throw, no 'undefined'", () => {
110
+ const out = renderThreadStop({ stopped: true });
111
+ expect(typeof out).toBe("string");
112
+ expect(out).not.toContain("undefined");
113
+ });
114
+
115
+ test("empty object — returns string, no throw, no 'undefined'", () => {
116
+ const out = renderThreadStop({});
117
+ expect(typeof out).toBe("string");
118
+ expect(out).not.toContain("undefined");
119
+ });
120
+
121
+ test("null payload — returns string, no throw", () => {
122
+ expect(() => renderThreadStop(null)).not.toThrow();
123
+ const out = renderThreadStop(null);
124
+ expect(typeof out).toBe("string");
125
+ expect(out).not.toContain("undefined");
126
+ });
127
+
128
+ test("non-object payload (string) — returns string, no throw", () => {
129
+ expect(() => renderThreadStop("oops")).not.toThrow();
130
+ const out = renderThreadStop("oops");
131
+ expect(typeof out).toBe("string");
132
+ expect(out).not.toContain("undefined");
133
+ });
134
+ });
135
+
136
+ describe("formatOutput integration — thread stop", () => {
137
+ test("formatOutput(data, 'text', 'thread stop') uses renderer", () => {
138
+ const data = { thread: "01JTEST00000000000000STOP1", stopped: true };
139
+ const out = formatOutput(data, "text", "thread stop");
140
+ expect(typeof out).toBe("string");
141
+ expect(out).not.toContain("undefined");
142
+ expect(out.trimStart().startsWith("{")).toBe(false);
143
+ expect(out).toContain("01JTEST00000000000000STOP1");
144
+ });
145
+
146
+ test("formatOutput(data, 'json', 'thread stop') still emits parseable JSON", () => {
147
+ const data = { thread: "01JTEST00000000000000STOP1", stopped: true };
148
+ const out = formatOutput(data, "json", "thread stop");
149
+ const parsed = JSON.parse(out);
150
+ expect(parsed).toEqual(data);
151
+ });
152
+
153
+ test("formatOutput(data, 'yaml', 'thread stop') still emits YAML", () => {
154
+ const data = { thread: "01JTEST00000000000000STOP1", stopped: true };
155
+ const out = formatOutput(data, "yaml", "thread stop");
156
+ expect(typeof out).toBe("string");
157
+ expect(out).toContain("thread:");
158
+ expect(out).toContain("stopped:");
159
+ });
160
+
161
+ test("formatOutput for stopped=false variant under text format", () => {
162
+ const data = { thread: "01JTEST00000000000000STOP1", stopped: false };
163
+ const out = formatOutput(data, "text", "thread stop");
164
+ expect(typeof out).toBe("string");
165
+ expect(out).not.toContain("undefined");
166
+ expect(out).toContain("01JTEST00000000000000STOP1");
167
+ });
168
+ });
@@ -62,13 +62,7 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
62
62
  new: { role: "worker", prompt: "Start work", location: null },
63
63
  resume: { role: "worker", prompt: "Resume work", location: null },
64
64
  },
65
- worker: {
66
- needs_input: {
67
- role: "$SUSPEND",
68
- prompt: "Please clarify: {{{question}}}",
69
- location: null,
70
- },
71
- },
65
+ worker: {},
72
66
  },
73
67
  });
74
68
 
@@ -81,9 +75,9 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
81
75
  const threadId = "01SUSPENDSTEPTEST0000000" as ThreadId;
82
76
  await seedThreads(tmpDir, { [threadId]: startHash });
83
77
 
84
- const outputHash = await store.cas.put(outputSchemaHash, {
85
- $status: "needs_input",
86
- question: "Which API?",
78
+ const outputHash = await store.cas.put(schemas.suspendOutput, {
79
+ $status: "$SUSPEND",
80
+ reason: "Please clarify: Which API?",
87
81
  });
88
82
  const detailHash = await store.cas.put(schemas.text, "mock detail");
89
83
 
@@ -109,7 +103,7 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
109
103
  stepHash,
110
104
  detailHash,
111
105
  role: "worker",
112
- frontmatter: { $status: "needs_input", question: "Which API?" },
106
+ frontmatter: { $status: "$SUSPEND", reason: "Please clarify: Which API?" },
113
107
  body: "",
114
108
  startedAtMs,
115
109
  completedAtMs,
@@ -119,13 +113,13 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
119
113
  const configPath = join(tmpDir, "config.yaml");
120
114
  await writeFile(
121
115
  configPath,
122
- `defaultAgent: uwf-hermes\ndefaultModel: test-model\nagentOverrides: null\nagents: {}\nproviders: {}\nmodels: {}\n`,
116
+ `defaultAgent: uwf-hermes\nagentOverrides: null\nagents:\n uwf-hermes:\n command: uwf-hermes\n`,
123
117
  );
124
118
 
125
119
  const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
126
120
  const stdout = execFileSync(
127
121
  process.execPath,
128
- [cliPath, "thread", "exec", threadId, "--agent", mockAgentPath],
122
+ [cliPath, "--format", "raw-json", "thread", "exec", threadId, "--agent", mockAgentPath],
129
123
  {
130
124
  encoding: "utf8",
131
125
  stdio: ["ignore", "pipe", "pipe"],
@@ -139,7 +133,10 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
139
133
  },
140
134
  );
141
135
 
142
- const cliOutput = JSON.parse(stdout.trim());
136
+ // thread exec envelope value: { threadId, workflowHash, steps: [...] }
137
+ const envelope = JSON.parse(stdout.trim());
138
+ expect(envelope.steps).toHaveLength(1);
139
+ const cliOutput = envelope.steps[0];
143
140
  expect(cliOutput.status).toBe("suspended");
144
141
  expect(cliOutput.head).toBe(stepHash);
145
142
  expect(cliOutput.suspendedRole).toBe("worker");
@@ -154,8 +151,8 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
154
151
 
155
152
  const outputNode = storeAfter.cas.get(outputHash);
156
153
  expect(outputNode?.payload).toEqual({
157
- $status: "needs_input",
158
- question: "Which API?",
154
+ $status: "$SUSPEND",
155
+ reason: "Please clarify: Which API?",
159
156
  });
160
157
 
161
158
  const { createUwfStore, getThread } = await import("../store.js");
@@ -59,13 +59,7 @@ describe("suspended thread display", () => {
59
59
  new: { role: "worker", prompt: "Start work", location: null },
60
60
  resume: { role: "worker", prompt: "Resume work", location: null },
61
61
  },
62
- worker: {
63
- needs_input: {
64
- role: "$SUSPEND",
65
- prompt: "Please provide more details: {{{question}}}",
66
- location: null,
67
- },
68
- },
62
+ worker: {},
69
63
  },
70
64
  });
71
65
 
@@ -77,9 +71,9 @@ describe("suspended thread display", () => {
77
71
 
78
72
  // Create suspended thread
79
73
  const suspendedThreadId = "01SUSPENDEDTHREAD0000000" as ThreadId;
80
- const outputHash = await uwf.store.cas.put(outputSchemaHash, {
81
- $status: "needs_input",
82
- question: "What is the target API?",
74
+ const outputHash = await uwf.store.cas.put(uwf.schemas.suspendOutput, {
75
+ $status: "$SUSPEND",
76
+ reason: "Please provide more details: What is the target API?",
83
77
  });
84
78
  const detailHash = await uwf.store.cas.put(uwf.schemas.text, "mock detail");
85
79
 
@@ -118,8 +112,8 @@ describe("suspended thread display", () => {
118
112
  [idleThreadId]: idleEntry,
119
113
  });
120
114
 
121
- // Test thread list
122
- const listResult = await cmdThreadList(tmpDir, null, null, null, null, null);
115
+ // Test thread list — pass showAll=true to include suspended threads
116
+ const listResult = await cmdThreadList(tmpDir, null, null, null, null, null, true);
123
117
 
124
118
  // Find the suspended and idle threads in results
125
119
  const suspendedItem = listResult.find((item) => item.thread === suspendedThreadId);
@@ -169,13 +163,7 @@ describe("suspended thread display", () => {
169
163
  new: { role: "worker", prompt: "Start work", location: null },
170
164
  resume: { role: "worker", prompt: "Resume work", location: null },
171
165
  },
172
- worker: {
173
- needs_input: {
174
- role: "$SUSPEND",
175
- prompt: "Need clarification: {{{question}}}",
176
- location: null,
177
- },
178
- },
166
+ worker: {},
179
167
  },
180
168
  });
181
169
 
@@ -186,9 +174,9 @@ describe("suspended thread display", () => {
186
174
  });
187
175
 
188
176
  const threadId = "01SUSPENDSHOW000000000" as ThreadId;
189
- const outputHash = await uwf.store.cas.put(outputSchemaHash, {
190
- $status: "needs_input",
191
- question: "Which database to use?",
177
+ const outputHash = await uwf.store.cas.put(uwf.schemas.suspendOutput, {
178
+ $status: "$SUSPEND",
179
+ reason: "Need clarification: Which database to use?",
192
180
  });
193
181
  const detailHash = await uwf.store.cas.put(uwf.schemas.text, "mock detail");
194
182
 
@@ -1,6 +1,20 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { join } from "node:path";
1
3
  import type { CasRef, ThreadId, ThreadIndexEntry } from "@united-workforce/protocol";
2
4
  import { createThreadIndexEntry } from "@united-workforce/protocol";
3
- import { createUwfStore, setThread } from "../store.js";
5
+ import { createUwfStore, setThread, type UwfStore } from "../store.js";
6
+
7
+ /**
8
+ * Create an isolated UwfStore backed by a tmpdir.
9
+ * Sets process.env.OCAS_HOME so CAS resolves to the test's directory.
10
+ * Callers MUST save/restore OCAS_HOME in afterEach.
11
+ */
12
+ export async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
13
+ const casDir = join(storageRoot, "cas");
14
+ await mkdir(casDir, { recursive: true });
15
+ process.env.OCAS_HOME = casDir;
16
+ return createUwfStore(storageRoot);
17
+ }
4
18
 
5
19
  async function ensureHeadInCas(
6
20
  uwf: Awaited<ReturnType<typeof createUwfStore>>,
@@ -1,4 +1,4 @@
1
- import { mkdir, mkdtemp, rm } from "node:fs/promises";
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { bootstrap, putSchema, type Store } from "@ocas/core";
@@ -11,8 +11,8 @@ import {
11
11
  THREAD_READ_DEFAULT_QUOTA,
12
12
  } from "../commands/thread.js";
13
13
  import type { UwfStore } from "../store.js";
14
- import { completeThread, createUwfStore, setThread } from "../store.js";
15
- import { seedThreads } from "./thread-test-helpers.js";
14
+ import { completeThread, setThread } from "../store.js";
15
+ import { makeUwfStore, seedThreads } from "./thread-test-helpers.js";
16
16
 
17
17
  // ── schemas used in tests ────────────────────────────────────────────────────
18
18
 
@@ -54,13 +54,6 @@ const DETAIL_SCHEMA = {
54
54
 
55
55
  // ── helpers ───────────────────────────────────────────────────────────────────
56
56
 
57
- async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
58
- const casDir = join(storageRoot, "cas");
59
- await mkdir(casDir, { recursive: true });
60
- process.env.OCAS_HOME = casDir;
61
- return createUwfStore(storageRoot);
62
- }
63
-
64
57
  async function registerDetailSchemas(store: Store) {
65
58
  await bootstrap(store);
66
59
  const [turn, detail] = await Promise.all([
@@ -73,12 +66,19 @@ async function registerDetailSchemas(store: Store) {
73
66
  // ── fixture ───────────────────────────────────────────────────────────────────
74
67
 
75
68
  let tmpDir: string;
69
+ let savedOcasHome: string | undefined;
76
70
 
77
71
  beforeEach(async () => {
72
+ savedOcasHome = process.env.OCAS_HOME;
78
73
  tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-test-"));
79
74
  });
80
75
 
81
76
  afterEach(async () => {
77
+ if (savedOcasHome === undefined) {
78
+ delete process.env.OCAS_HOME;
79
+ } else {
80
+ process.env.OCAS_HOME = savedOcasHome;
81
+ }
82
82
  await rm(tmpDir, { recursive: true, force: true });
83
83
  });
84
84
 
@@ -752,7 +752,7 @@ describe("cmdStepList with completed threads", () => {
752
752
  suspendMessage: null,
753
753
  completedAt: null,
754
754
  });
755
- completeThread(uwf.varStore, threadId, "completed");
755
+ completeThread(uwf.varStore, threadId, "end");
756
756
 
757
757
  const result = await cmdStepList(tmpDir, threadId);
758
758
 
@@ -881,7 +881,7 @@ describe("cmdStepShow with completed threads", () => {
881
881
  suspendMessage: null,
882
882
  completedAt: null,
883
883
  });
884
- completeThread(uwf.varStore, threadId, "completed");
884
+ completeThread(uwf.varStore, threadId, "end");
885
885
 
886
886
  const result = await cmdStepShow(tmpDir, stepHash);
887
887
 
@@ -944,7 +944,7 @@ describe("cmdThreadRead with completed threads", () => {
944
944
  suspendMessage: null,
945
945
  completedAt: null,
946
946
  });
947
- completeThread(uwf.varStore, threadId, "completed");
947
+ completeThread(uwf.varStore, threadId, "end");
948
948
 
949
949
  const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, false);
950
950
 
@@ -1007,7 +1007,7 @@ describe("cmdThreadRead with completed threads", () => {
1007
1007
  suspendMessage: null,
1008
1008
  completedAt: null,
1009
1009
  });
1010
- completeThread(uwf.varStore, threadId, "completed");
1010
+ completeThread(uwf.varStore, threadId, "end");
1011
1011
 
1012
1012
  const markdown = await cmdThreadRead(
1013
1013
  tmpDir,