@united-workforce/cli 0.1.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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -0
  3. package/dist/__tests__/adapter-json-roundtrip.test.d.ts +2 -0
  4. package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +1 -0
  5. package/dist/__tests__/adapter-json-roundtrip.test.js +147 -0
  6. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -0
  7. package/dist/__tests__/config.test.d.ts +2 -0
  8. package/dist/__tests__/config.test.d.ts.map +1 -0
  9. package/dist/__tests__/config.test.js +685 -0
  10. package/dist/__tests__/config.test.js.map +1 -0
  11. package/dist/__tests__/current-role.test.d.ts +2 -0
  12. package/dist/__tests__/current-role.test.d.ts.map +1 -0
  13. package/dist/__tests__/current-role.test.js +401 -0
  14. package/dist/__tests__/current-role.test.js.map +1 -0
  15. package/dist/__tests__/e2e-mock-agent.test.d.ts +2 -0
  16. package/dist/__tests__/e2e-mock-agent.test.d.ts.map +1 -0
  17. package/dist/__tests__/e2e-mock-agent.test.js +401 -0
  18. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -0
  19. package/dist/__tests__/include-tag.test.d.ts +2 -0
  20. package/dist/__tests__/include-tag.test.d.ts.map +1 -0
  21. package/dist/__tests__/include-tag.test.js +69 -0
  22. package/dist/__tests__/include-tag.test.js.map +1 -0
  23. package/dist/__tests__/log.test.d.ts +2 -0
  24. package/dist/__tests__/log.test.d.ts.map +1 -0
  25. package/dist/__tests__/log.test.js +161 -0
  26. package/dist/__tests__/log.test.js.map +1 -0
  27. package/dist/__tests__/moderator-evaluate.test.d.ts +2 -0
  28. package/dist/__tests__/moderator-evaluate.test.d.ts.map +1 -0
  29. package/dist/__tests__/moderator-evaluate.test.js +170 -0
  30. package/dist/__tests__/moderator-evaluate.test.js.map +1 -0
  31. package/dist/__tests__/preload.d.ts +3 -0
  32. package/dist/__tests__/preload.d.ts.map +1 -0
  33. package/dist/__tests__/preload.js +6 -0
  34. package/dist/__tests__/preload.js.map +1 -0
  35. package/dist/__tests__/prompt.test.d.ts +2 -0
  36. package/dist/__tests__/prompt.test.d.ts.map +1 -0
  37. package/dist/__tests__/prompt.test.js +111 -0
  38. package/dist/__tests__/prompt.test.js.map +1 -0
  39. package/dist/__tests__/resolve-head-hash.test.d.ts +2 -0
  40. package/dist/__tests__/resolve-head-hash.test.d.ts.map +1 -0
  41. package/dist/__tests__/resolve-head-hash.test.js +66 -0
  42. package/dist/__tests__/resolve-head-hash.test.js.map +1 -0
  43. package/dist/__tests__/setup-agent-discovery.test.d.ts +2 -0
  44. package/dist/__tests__/setup-agent-discovery.test.d.ts.map +1 -0
  45. package/dist/__tests__/setup-agent-discovery.test.js +119 -0
  46. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -0
  47. package/dist/__tests__/setup-complexity.test.d.ts +2 -0
  48. package/dist/__tests__/setup-complexity.test.d.ts.map +1 -0
  49. package/dist/__tests__/setup-complexity.test.js +314 -0
  50. package/dist/__tests__/setup-complexity.test.js.map +1 -0
  51. package/dist/__tests__/setup-validate.test.d.ts +2 -0
  52. package/dist/__tests__/setup-validate.test.d.ts.map +1 -0
  53. package/dist/__tests__/setup-validate.test.js +108 -0
  54. package/dist/__tests__/setup-validate.test.js.map +1 -0
  55. package/dist/__tests__/solve-issue-tea-worktree.test.d.ts +2 -0
  56. package/dist/__tests__/solve-issue-tea-worktree.test.d.ts.map +1 -0
  57. package/dist/__tests__/solve-issue-tea-worktree.test.js +107 -0
  58. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -0
  59. package/dist/__tests__/spawn-agent-json.test.d.ts +2 -0
  60. package/dist/__tests__/spawn-agent-json.test.d.ts.map +1 -0
  61. package/dist/__tests__/spawn-agent-json.test.js +79 -0
  62. package/dist/__tests__/spawn-agent-json.test.js.map +1 -0
  63. package/dist/__tests__/step-read.test.d.ts +2 -0
  64. package/dist/__tests__/step-read.test.d.ts.map +1 -0
  65. package/dist/__tests__/step-read.test.js +561 -0
  66. package/dist/__tests__/step-read.test.js.map +1 -0
  67. package/dist/__tests__/step-show-json.test.d.ts +2 -0
  68. package/dist/__tests__/step-show-json.test.d.ts.map +1 -0
  69. package/dist/__tests__/step-show-json.test.js +311 -0
  70. package/dist/__tests__/step-show-json.test.js.map +1 -0
  71. package/dist/__tests__/step-timing.test.d.ts +2 -0
  72. package/dist/__tests__/step-timing.test.d.ts.map +1 -0
  73. package/dist/__tests__/step-timing.test.js +345 -0
  74. package/dist/__tests__/step-timing.test.js.map +1 -0
  75. package/dist/__tests__/store-global-cas.test.d.ts +2 -0
  76. package/dist/__tests__/store-global-cas.test.d.ts.map +1 -0
  77. package/dist/__tests__/store-global-cas.test.js +235 -0
  78. package/dist/__tests__/store-global-cas.test.js.map +1 -0
  79. package/dist/__tests__/store-storage-root.test.d.ts +2 -0
  80. package/dist/__tests__/store-storage-root.test.d.ts.map +1 -0
  81. package/dist/__tests__/store-storage-root.test.js +43 -0
  82. package/dist/__tests__/store-storage-root.test.js.map +1 -0
  83. package/dist/__tests__/store-unified-threads.test.d.ts +2 -0
  84. package/dist/__tests__/store-unified-threads.test.d.ts.map +1 -0
  85. package/dist/__tests__/store-unified-threads.test.js +189 -0
  86. package/dist/__tests__/store-unified-threads.test.js.map +1 -0
  87. package/dist/__tests__/thread-cancel-status.test.d.ts +2 -0
  88. package/dist/__tests__/thread-cancel-status.test.d.ts.map +1 -0
  89. package/dist/__tests__/thread-cancel-status.test.js +111 -0
  90. package/dist/__tests__/thread-cancel-status.test.js.map +1 -0
  91. package/dist/__tests__/thread-list-filters.test.d.ts +2 -0
  92. package/dist/__tests__/thread-list-filters.test.d.ts.map +1 -0
  93. package/dist/__tests__/thread-list-filters.test.js +442 -0
  94. package/dist/__tests__/thread-list-filters.test.js.map +1 -0
  95. package/dist/__tests__/thread-location.test.d.ts +2 -0
  96. package/dist/__tests__/thread-location.test.d.ts.map +1 -0
  97. package/dist/__tests__/thread-location.test.js +159 -0
  98. package/dist/__tests__/thread-location.test.js.map +1 -0
  99. package/dist/__tests__/thread-read-quota.test.d.ts +2 -0
  100. package/dist/__tests__/thread-read-quota.test.d.ts.map +1 -0
  101. package/dist/__tests__/thread-read-quota.test.js +546 -0
  102. package/dist/__tests__/thread-read-quota.test.js.map +1 -0
  103. package/dist/__tests__/thread-read-xml-tags.test.d.ts +2 -0
  104. package/dist/__tests__/thread-read-xml-tags.test.d.ts.map +1 -0
  105. package/dist/__tests__/thread-read-xml-tags.test.js +610 -0
  106. package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -0
  107. package/dist/__tests__/thread-resume.test.d.ts +2 -0
  108. package/dist/__tests__/thread-resume.test.d.ts.map +1 -0
  109. package/dist/__tests__/thread-resume.test.js +592 -0
  110. package/dist/__tests__/thread-resume.test.js.map +1 -0
  111. package/dist/__tests__/thread-show-status.test.d.ts +2 -0
  112. package/dist/__tests__/thread-show-status.test.d.ts.map +1 -0
  113. package/dist/__tests__/thread-show-status.test.js +267 -0
  114. package/dist/__tests__/thread-show-status.test.js.map +1 -0
  115. package/dist/__tests__/thread-start-cwd-cli.test.d.ts +2 -0
  116. package/dist/__tests__/thread-start-cwd-cli.test.d.ts.map +1 -0
  117. package/dist/__tests__/thread-start-cwd-cli.test.js +130 -0
  118. package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -0
  119. package/dist/__tests__/thread-step-count.test.d.ts +2 -0
  120. package/dist/__tests__/thread-step-count.test.d.ts.map +1 -0
  121. package/dist/__tests__/thread-step-count.test.js +55 -0
  122. package/dist/__tests__/thread-step-count.test.js.map +1 -0
  123. package/dist/__tests__/thread-suspend-step.test.d.ts +2 -0
  124. package/dist/__tests__/thread-suspend-step.test.d.ts.map +1 -0
  125. package/dist/__tests__/thread-suspend-step.test.js +155 -0
  126. package/dist/__tests__/thread-suspend-step.test.js.map +1 -0
  127. package/dist/__tests__/thread-suspended-display.test.d.ts +2 -0
  128. package/dist/__tests__/thread-suspended-display.test.d.ts.map +1 -0
  129. package/dist/__tests__/thread-suspended-display.test.js +247 -0
  130. package/dist/__tests__/thread-suspended-display.test.js.map +1 -0
  131. package/dist/__tests__/thread-test-helpers.d.ts +4 -0
  132. package/dist/__tests__/thread-test-helpers.d.ts.map +1 -0
  133. package/dist/__tests__/thread-test-helpers.js +23 -0
  134. package/dist/__tests__/thread-test-helpers.js.map +1 -0
  135. package/dist/__tests__/thread.test.d.ts +2 -0
  136. package/dist/__tests__/thread.test.d.ts.map +1 -0
  137. package/dist/__tests__/thread.test.js +883 -0
  138. package/dist/__tests__/thread.test.js.map +1 -0
  139. package/dist/__tests__/validate-semantic.test.d.ts +2 -0
  140. package/dist/__tests__/validate-semantic.test.d.ts.map +1 -0
  141. package/dist/__tests__/validate-semantic.test.js +408 -0
  142. package/dist/__tests__/validate-semantic.test.js.map +1 -0
  143. package/dist/__tests__/workflow-resolution.test.d.ts +2 -0
  144. package/dist/__tests__/workflow-resolution.test.d.ts.map +1 -0
  145. package/dist/__tests__/workflow-resolution.test.js +308 -0
  146. package/dist/__tests__/workflow-resolution.test.js.map +1 -0
  147. package/dist/background/background.d.ts +38 -0
  148. package/dist/background/background.d.ts.map +1 -0
  149. package/dist/background/background.js +123 -0
  150. package/dist/background/background.js.map +1 -0
  151. package/dist/background/index.d.ts +3 -0
  152. package/dist/background/index.d.ts.map +1 -0
  153. package/dist/background/index.js +2 -0
  154. package/dist/background/index.js.map +1 -0
  155. package/dist/background/types.d.ts +9 -0
  156. package/dist/background/types.d.ts.map +1 -0
  157. package/dist/background/types.js +2 -0
  158. package/dist/background/types.js.map +1 -0
  159. package/dist/cli.d.ts +3 -0
  160. package/dist/cli.d.ts.map +1 -0
  161. package/dist/cli.js +535 -0
  162. package/dist/cli.js.map +1 -0
  163. package/dist/commands/config.d.ts +41 -0
  164. package/dist/commands/config.d.ts.map +1 -0
  165. package/dist/commands/config.js +252 -0
  166. package/dist/commands/config.js.map +1 -0
  167. package/dist/commands/log.d.ts +26 -0
  168. package/dist/commands/log.d.ts.map +1 -0
  169. package/dist/commands/log.js +79 -0
  170. package/dist/commands/log.js.map +1 -0
  171. package/dist/commands/prompt.d.ts +6 -0
  172. package/dist/commands/prompt.d.ts.map +1 -0
  173. package/dist/commands/prompt.js +67 -0
  174. package/dist/commands/prompt.js.map +1 -0
  175. package/dist/commands/setup.d.ts +73 -0
  176. package/dist/commands/setup.d.ts.map +1 -0
  177. package/dist/commands/setup.js +522 -0
  178. package/dist/commands/setup.js.map +1 -0
  179. package/dist/commands/shared.d.ts +31 -0
  180. package/dist/commands/shared.d.ts.map +1 -0
  181. package/dist/commands/shared.js +154 -0
  182. package/dist/commands/shared.js.map +1 -0
  183. package/dist/commands/step.d.ts +18 -0
  184. package/dist/commands/step.d.ts.map +1 -0
  185. package/dist/commands/step.js +257 -0
  186. package/dist/commands/step.js.map +1 -0
  187. package/dist/commands/thread-time-parser.d.ts +6 -0
  188. package/dist/commands/thread-time-parser.d.ts.map +1 -0
  189. package/dist/commands/thread-time-parser.js +22 -0
  190. package/dist/commands/thread-time-parser.js.map +1 -0
  191. package/dist/commands/thread.d.ts +38 -0
  192. package/dist/commands/thread.d.ts.map +1 -0
  193. package/dist/commands/thread.js +1087 -0
  194. package/dist/commands/thread.js.map +1 -0
  195. package/dist/commands/workflow.d.ts +24 -0
  196. package/dist/commands/workflow.d.ts.map +1 -0
  197. package/dist/commands/workflow.js +138 -0
  198. package/dist/commands/workflow.js.map +1 -0
  199. package/dist/format.d.ts +3 -0
  200. package/dist/format.d.ts.map +1 -0
  201. package/dist/format.js +10 -0
  202. package/dist/format.js.map +1 -0
  203. package/dist/include.d.ts +12 -0
  204. package/dist/include.d.ts.map +1 -0
  205. package/dist/include.js +35 -0
  206. package/dist/include.js.map +1 -0
  207. package/dist/moderator/__tests__/evaluate.test.d.ts +2 -0
  208. package/dist/moderator/__tests__/evaluate.test.d.ts.map +1 -0
  209. package/dist/moderator/__tests__/evaluate.test.js +167 -0
  210. package/dist/moderator/__tests__/evaluate.test.js.map +1 -0
  211. package/dist/moderator/evaluate.d.ts +6 -0
  212. package/dist/moderator/evaluate.d.ts.map +1 -0
  213. package/dist/moderator/evaluate.js +65 -0
  214. package/dist/moderator/evaluate.js.map +1 -0
  215. package/dist/moderator/index.d.ts +4 -0
  216. package/dist/moderator/index.d.ts.map +1 -0
  217. package/dist/moderator/index.js +3 -0
  218. package/dist/moderator/index.js.map +1 -0
  219. package/dist/moderator/types.d.ts +25 -0
  220. package/dist/moderator/types.d.ts.map +1 -0
  221. package/dist/moderator/types.js +4 -0
  222. package/dist/moderator/types.js.map +1 -0
  223. package/dist/schemas.d.ts +16 -0
  224. package/dist/schemas.d.ts.map +1 -0
  225. package/dist/schemas.js +17 -0
  226. package/dist/schemas.js.map +1 -0
  227. package/dist/store.d.ts +77 -0
  228. package/dist/store.d.ts.map +1 -0
  229. package/dist/store.js +392 -0
  230. package/dist/store.js.map +1 -0
  231. package/dist/validate-semantic.d.ts +7 -0
  232. package/dist/validate-semantic.d.ts.map +1 -0
  233. package/dist/validate-semantic.js +263 -0
  234. package/dist/validate-semantic.js.map +1 -0
  235. package/dist/validate.d.ts +16 -0
  236. package/dist/validate.d.ts.map +1 -0
  237. package/dist/validate.js +115 -0
  238. package/dist/validate.js.map +1 -0
  239. package/package.json +44 -0
  240. package/src/__tests__/adapter-json-roundtrip.test.ts +181 -0
  241. package/src/__tests__/config.test.ts +740 -0
  242. package/src/__tests__/current-role.test.ts +438 -0
  243. package/src/__tests__/e2e-mock-agent.test.ts +498 -0
  244. package/src/__tests__/fixtures/e2e-completed-resume.mock.yaml +15 -0
  245. package/src/__tests__/fixtures/e2e-count.mock.yaml +19 -0
  246. package/src/__tests__/fixtures/e2e-count.workflow.yaml +45 -0
  247. package/src/__tests__/fixtures/e2e-linear.mock.yaml +13 -0
  248. package/src/__tests__/fixtures/e2e-linear.workflow.yaml +32 -0
  249. package/src/__tests__/fixtures/e2e-loop.mock.yaml +25 -0
  250. package/src/__tests__/fixtures/e2e-loop.workflow.yaml +36 -0
  251. package/src/__tests__/fixtures/e2e-mismatch.mock.yaml +16 -0
  252. package/src/__tests__/fixtures/e2e-mustache.mock.yaml +15 -0
  253. package/src/__tests__/fixtures/e2e-mustache.workflow.yaml +34 -0
  254. package/src/__tests__/fixtures/e2e-suspend.mock.yaml +14 -0
  255. package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +24 -0
  256. package/src/__tests__/include-tag.test.ts +84 -0
  257. package/src/__tests__/log.test.ts +181 -0
  258. package/src/__tests__/moderator-evaluate.test.ts +186 -0
  259. package/src/__tests__/preload.ts +7 -0
  260. package/src/__tests__/prompt.test.ts +129 -0
  261. package/src/__tests__/resolve-head-hash.test.ts +86 -0
  262. package/src/__tests__/setup-agent-discovery.test.ts +167 -0
  263. package/src/__tests__/setup-complexity.test.ts +381 -0
  264. package/src/__tests__/setup-validate.test.ts +148 -0
  265. package/src/__tests__/solve-issue-tea-worktree.test.ts +144 -0
  266. package/src/__tests__/spawn-agent-json.test.ts +100 -0
  267. package/src/__tests__/step-read.test.ts +632 -0
  268. package/src/__tests__/step-show-json.test.ts +373 -0
  269. package/src/__tests__/step-timing.test.ts +392 -0
  270. package/src/__tests__/store-global-cas.test.ts +308 -0
  271. package/src/__tests__/store-storage-root.test.ts +49 -0
  272. package/src/__tests__/store-unified-threads.test.ts +235 -0
  273. package/src/__tests__/thread-cancel-status.test.ts +138 -0
  274. package/src/__tests__/thread-list-filters.test.ts +572 -0
  275. package/src/__tests__/thread-location.test.ts +186 -0
  276. package/src/__tests__/thread-read-quota.test.ts +613 -0
  277. package/src/__tests__/thread-read-xml-tags.test.ts +717 -0
  278. package/src/__tests__/thread-resume.test.ts +710 -0
  279. package/src/__tests__/thread-show-status.test.ts +317 -0
  280. package/src/__tests__/thread-start-cwd-cli.test.ts +164 -0
  281. package/src/__tests__/thread-step-count.test.ts +70 -0
  282. package/src/__tests__/thread-suspend-step.test.ts +181 -0
  283. package/src/__tests__/thread-suspended-display.test.ts +287 -0
  284. package/src/__tests__/thread-test-helpers.ts +37 -0
  285. package/src/__tests__/thread.test.ts +1025 -0
  286. package/src/__tests__/validate-semantic.test.ts +474 -0
  287. package/src/__tests__/workflow-resolution.test.ts +421 -0
  288. package/src/background/background.ts +147 -0
  289. package/src/background/index.ts +11 -0
  290. package/src/background/types.ts +9 -0
  291. package/src/cli.ts +692 -0
  292. package/src/commands/config.ts +304 -0
  293. package/src/commands/log.ts +116 -0
  294. package/src/commands/prompt.ts +81 -0
  295. package/src/commands/setup.ts +603 -0
  296. package/src/commands/shared.ts +227 -0
  297. package/src/commands/step.ts +343 -0
  298. package/src/commands/thread-time-parser.ts +23 -0
  299. package/src/commands/thread.ts +1575 -0
  300. package/src/commands/workflow.ts +213 -0
  301. package/src/format.ts +12 -0
  302. package/src/include.ts +37 -0
  303. package/src/moderator/__tests__/evaluate.test.ts +199 -0
  304. package/src/moderator/evaluate.ts +80 -0
  305. package/src/moderator/index.ts +7 -0
  306. package/src/moderator/types.ts +24 -0
  307. package/src/schemas.ts +26 -0
  308. package/src/store.ts +479 -0
  309. package/src/validate-semantic.ts +304 -0
  310. package/src/validate.ts +137 -0
@@ -0,0 +1,317 @@
1
+ import { mkdir, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { putSchema } from "@ocas/core";
5
+ import type { CasRef, ThreadId } from "@united-workforce/protocol";
6
+ import { describe, expect, test } from "vitest";
7
+ import { createMarker, deleteMarker } from "../background/index.js";
8
+ import { cmdThreadShow, cmdThreadStart } from "../commands/thread.js";
9
+ import { completeThread, createUwfStore, loadAllThreads, setThread } from "../store.js";
10
+
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
+ const TEST_WORKFLOW_YAML = `
20
+ name: test-status
21
+ description: Test workflow for status field
22
+ roles:
23
+ planner:
24
+ description: Plans the work
25
+ goal: Plan implementation
26
+ capabilities: ["planning"]
27
+ procedure: Plan
28
+ output: |
29
+ $status: "ready"
30
+ frontmatter:
31
+ type: object
32
+ required: ["$status"]
33
+ properties:
34
+ $status: { type: string, enum: ["ready"] }
35
+ graph:
36
+ $START:
37
+ _:
38
+ role: planner
39
+ prompt: "Plan the work"
40
+ location: null
41
+ planner:
42
+ ready:
43
+ role: $END
44
+ prompt: "Done"
45
+ location: null
46
+ `;
47
+
48
+ const SUSPEND_WORKFLOW_YAML = `
49
+ name: test-suspend-status
50
+ description: Test workflow for suspended status
51
+ roles:
52
+ worker:
53
+ description: Worker role
54
+ goal: Work
55
+ capabilities: ["coding"]
56
+ procedure: Work
57
+ output: |
58
+ $status: "needs_input"
59
+ question: "Which API?"
60
+ frontmatter:
61
+ oneOf:
62
+ - type: object
63
+ required: ["$status", "question"]
64
+ properties:
65
+ $status: { const: "needs_input" }
66
+ question: { type: string }
67
+ graph:
68
+ $START:
69
+ _:
70
+ role: worker
71
+ prompt: "Start work"
72
+ location: null
73
+ worker:
74
+ needs_input:
75
+ role: $SUSPEND
76
+ prompt: "Please clarify: {{{question}}}"
77
+ location: null
78
+ `;
79
+
80
+ async function insertStepNode(
81
+ storageRoot: string,
82
+ threadId: ThreadId,
83
+ role: string,
84
+ outputPayload: Record<string, unknown>,
85
+ ): Promise<void> {
86
+ const uwf = await createUwfStore(storageRoot);
87
+ const index = loadAllThreads(uwf.varStore);
88
+ const headEntry = index[threadId];
89
+ if (headEntry === undefined) throw new Error(`thread ${threadId} not in index`);
90
+ const head = headEntry.head;
91
+
92
+ const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
93
+ const outputHash = await uwf.store.cas.put(outputSchemaHash, outputPayload);
94
+ const detailHash = await uwf.store.cas.put(uwf.schemas.text, "detail-placeholder");
95
+
96
+ const headNode = uwf.store.cas.get(head);
97
+ if (headNode === null) throw new Error(`head ${head} not found`);
98
+ const isStart = headNode.type === uwf.schemas.startNode;
99
+ const startHash = isStart ? head : (headNode.payload as { start: CasRef }).start;
100
+
101
+ const stepHash = (await uwf.store.cas.put(uwf.schemas.stepNode, {
102
+ start: startHash,
103
+ prev: isStart ? null : head,
104
+ role,
105
+ output: outputHash,
106
+ detail: detailHash,
107
+ agent: "uwf-test",
108
+ edgePrompt: "edge",
109
+ startedAtMs: Date.now(),
110
+ completedAtMs: Date.now() + 1,
111
+ cwd: "/tmp",
112
+ assembledPrompt: null,
113
+ })) as CasRef;
114
+
115
+ setThread(uwf.varStore, threadId, {
116
+ head: stepHash,
117
+ status: "idle",
118
+ suspendedRole: null,
119
+ suspendMessage: null,
120
+ completedAt: null,
121
+ });
122
+ }
123
+
124
+ describe("thread show status field", () => {
125
+ let tmpDir: string;
126
+ let storageRoot: string;
127
+
128
+ async function setupTestEnv() {
129
+ tmpDir = join(tmpdir(), `uwf-test-status-${Date.now()}`);
130
+ storageRoot = join(tmpDir, "storage");
131
+ await mkdir(storageRoot, { recursive: true });
132
+ }
133
+
134
+ async function teardown() {
135
+ if (tmpDir) {
136
+ await rm(tmpDir, { recursive: true, force: true });
137
+ }
138
+ }
139
+
140
+ test("active idle thread shows status 'idle'", async () => {
141
+ await setupTestEnv();
142
+
143
+ const workflowPath = join(tmpDir, "test-status.yaml");
144
+ await writeFile(workflowPath, TEST_WORKFLOW_YAML, "utf8");
145
+
146
+ // Create a thread
147
+ const startResult = await cmdThreadStart(storageRoot, workflowPath, "test prompt", tmpDir);
148
+ const threadId = startResult.thread as ThreadId;
149
+
150
+ // Show the thread (should be idle)
151
+ const result = await cmdThreadShow(storageRoot, threadId);
152
+
153
+ expect(result.status).toBe("idle");
154
+ expect(result.done).toBe(false);
155
+ expect(result.background).toBe(null);
156
+ expect(result.thread).toBe(threadId);
157
+
158
+ await teardown();
159
+ });
160
+
161
+ test("active running thread shows status 'running'", async () => {
162
+ await setupTestEnv();
163
+
164
+ const workflowPath = join(tmpDir, "test-status.yaml");
165
+ await writeFile(workflowPath, TEST_WORKFLOW_YAML, "utf8");
166
+
167
+ // Create a thread
168
+ const startResult = await cmdThreadStart(storageRoot, workflowPath, "test prompt", tmpDir);
169
+ const threadId = startResult.thread as ThreadId;
170
+ const workflow = startResult.workflow;
171
+
172
+ // Create a running marker
173
+ await createMarker(storageRoot, {
174
+ thread: threadId,
175
+ workflow,
176
+ pid: process.pid,
177
+ startedAt: Date.now(),
178
+ });
179
+
180
+ try {
181
+ const result = await cmdThreadShow(storageRoot, threadId);
182
+
183
+ expect(result.status).toBe("running");
184
+ expect(result.done).toBe(false);
185
+ expect(result.background).toBe(null);
186
+ expect(result.thread).toBe(threadId);
187
+ } finally {
188
+ // Cleanup: delete marker
189
+ await deleteMarker(storageRoot, threadId);
190
+ await teardown();
191
+ }
192
+ });
193
+
194
+ test("completed thread shows status 'completed'", async () => {
195
+ await setupTestEnv();
196
+
197
+ const workflowPath = join(tmpDir, "test-status.yaml");
198
+ await writeFile(workflowPath, TEST_WORKFLOW_YAML, "utf8");
199
+
200
+ // Create a thread
201
+ const startResult = await cmdThreadStart(storageRoot, workflowPath, "test prompt", tmpDir);
202
+ const threadId = startResult.thread as ThreadId;
203
+ const _workflow = startResult.workflow;
204
+
205
+ // Get the head hash before moving to history
206
+ const uwfForIndex = await createUwfStore(storageRoot);
207
+ const index = loadAllThreads(uwfForIndex.varStore);
208
+ const head = index[threadId]!.head;
209
+ if (!head) throw new Error("Thread not found in index");
210
+
211
+ completeThread(uwfForIndex.varStore, threadId, "completed");
212
+
213
+ const result = await cmdThreadShow(storageRoot, threadId);
214
+
215
+ expect(result.status).toBe("completed");
216
+ expect(result.done).toBe(true);
217
+ expect(result.background).toBe(null);
218
+ expect(result.thread).toBe(threadId);
219
+
220
+ await teardown();
221
+ });
222
+
223
+ test("cancelled thread shows status 'cancelled'", async () => {
224
+ await setupTestEnv();
225
+
226
+ const workflowPath = join(tmpDir, "test-status.yaml");
227
+ await writeFile(workflowPath, TEST_WORKFLOW_YAML, "utf8");
228
+
229
+ // Create a thread
230
+ const startResult = await cmdThreadStart(storageRoot, workflowPath, "test prompt", tmpDir);
231
+ const threadId = startResult.thread as ThreadId;
232
+ const _workflow = startResult.workflow;
233
+
234
+ // Get the head hash before moving to history
235
+ const uwfForIndex = await createUwfStore(storageRoot);
236
+ const index = loadAllThreads(uwfForIndex.varStore);
237
+ const head = index[threadId]!.head;
238
+ if (!head) throw new Error("Thread not found in index");
239
+
240
+ completeThread(uwfForIndex.varStore, threadId, "cancelled");
241
+
242
+ const result = await cmdThreadShow(storageRoot, threadId);
243
+
244
+ expect(result.status).toBe("cancelled");
245
+ expect(result.done).toBe(true);
246
+ expect(result.background).toBe(null);
247
+ expect(result.thread).toBe(threadId);
248
+
249
+ await teardown();
250
+ });
251
+
252
+ test("legacy completed thread without reason shows status 'completed'", async () => {
253
+ await setupTestEnv();
254
+
255
+ const workflowPath = join(tmpDir, "test-status.yaml");
256
+ await writeFile(workflowPath, TEST_WORKFLOW_YAML, "utf8");
257
+
258
+ // Create a thread
259
+ const startResult = await cmdThreadStart(storageRoot, workflowPath, "test prompt", tmpDir);
260
+ const threadId = startResult.thread as ThreadId;
261
+ const _workflow = startResult.workflow;
262
+
263
+ // Get the head hash before moving to history
264
+ const uwfForIndex = await createUwfStore(storageRoot);
265
+ const index = loadAllThreads(uwfForIndex.varStore);
266
+ const head = index[threadId]!.head;
267
+ if (!head) throw new Error("Thread not found in index");
268
+
269
+ completeThread(uwfForIndex.varStore, threadId, "completed");
270
+
271
+ const result = await cmdThreadShow(storageRoot, threadId);
272
+
273
+ expect(result.status).toBe("completed");
274
+ expect(result.done).toBe(true);
275
+ expect(result.background).toBe(null);
276
+
277
+ await teardown();
278
+ });
279
+
280
+ test("active suspended thread shows status 'suspended'", async () => {
281
+ await setupTestEnv();
282
+ const casDir = join(tmpDir, "cas");
283
+ await mkdir(casDir, { recursive: true });
284
+ const originalCasDir = process.env.OCAS_HOME;
285
+ process.env.OCAS_HOME = casDir;
286
+
287
+ try {
288
+ const workflowPath = join(tmpDir, "test-suspend-status.yaml");
289
+ await writeFile(workflowPath, SUSPEND_WORKFLOW_YAML, "utf8");
290
+
291
+ const startResult = await cmdThreadStart(storageRoot, workflowPath, "test prompt", tmpDir);
292
+ const threadId = startResult.thread as ThreadId;
293
+
294
+ await insertStepNode(storageRoot, threadId, "worker", {
295
+ $status: "needs_input",
296
+ question: "Which API?",
297
+ });
298
+
299
+ const result = await cmdThreadShow(storageRoot, threadId);
300
+
301
+ expect(result.status).toBe("suspended");
302
+ expect(result.done).toBe(false);
303
+ expect(result.currentRole).toBe(null);
304
+ expect(result.suspendedRole).toBe("worker");
305
+ expect(result.suspendMessage).toBe("Please clarify: Which API?");
306
+ expect(result.background).toBe(null);
307
+ expect(result.thread).toBe(threadId);
308
+ } finally {
309
+ if (originalCasDir === undefined) {
310
+ delete process.env.OCAS_HOME;
311
+ } else {
312
+ process.env.OCAS_HOME = originalCasDir;
313
+ }
314
+ await teardown();
315
+ }
316
+ });
317
+ });
@@ -0,0 +1,164 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdir, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import type { CasRef, StartNodePayload, ThreadId } from "@united-workforce/protocol";
7
+ import { describe, expect, test } from "vitest";
8
+ import { cmdThreadStart } from "../commands/thread.js";
9
+ import { createUwfStore, getThread } from "../store.js";
10
+
11
+ describe("thread start --cwd CLI option", () => {
12
+ let tmpDir: string;
13
+ let storageRoot: string;
14
+ let casDir: string;
15
+ let originalEnv: string | undefined;
16
+
17
+ async function setupTestEnv() {
18
+ tmpDir = join(tmpdir(), `uwf-test-cwd-cli-${Date.now()}`);
19
+ storageRoot = join(tmpDir, "storage");
20
+ casDir = join(tmpDir, "cas");
21
+ await mkdir(storageRoot, { recursive: true });
22
+ await mkdir(casDir, { recursive: true });
23
+
24
+ // Set OCAS_HOME for this test
25
+ originalEnv = process.env.OCAS_HOME;
26
+ process.env.OCAS_HOME = casDir;
27
+ }
28
+
29
+ async function teardown() {
30
+ if (tmpDir) {
31
+ await rm(tmpDir, { recursive: true, force: true });
32
+ }
33
+ // Restore original environment
34
+ if (originalEnv === undefined) {
35
+ delete process.env.OCAS_HOME;
36
+ } else {
37
+ process.env.OCAS_HOME = originalEnv;
38
+ }
39
+ }
40
+
41
+ async function createTestWorkflow(): Promise<string> {
42
+ const workflowYaml = `
43
+ name: test-cwd-cli
44
+ description: Test workflow for CLI cwd option
45
+ roles:
46
+ planner:
47
+ description: Plans the work
48
+ goal: Plan implementation
49
+ capabilities: ["planning"]
50
+ procedure: Plan
51
+ output: |
52
+ $status: "ready"
53
+ frontmatter:
54
+ type: object
55
+ required: ["$status"]
56
+ properties:
57
+ $status: { type: string, enum: ["ready"] }
58
+ graph:
59
+ $START:
60
+ _:
61
+ role: planner
62
+ prompt: "Plan the work"
63
+ location: null
64
+ planner:
65
+ ready:
66
+ role: $END
67
+ prompt: "Done"
68
+ location: null
69
+ `;
70
+
71
+ const workflowPath = join(tmpDir, "test-cwd-cli.yaml");
72
+ await writeFile(workflowPath, workflowYaml, "utf8");
73
+ return workflowPath;
74
+ }
75
+
76
+ async function getStartNodeCwd(threadId: string): Promise<string> {
77
+ const uwf = await createUwfStore(storageRoot);
78
+ const entry = getThread(uwf.varStore, threadId as ThreadId);
79
+ const headHash = entry!.head;
80
+ expect(headHash).toBeDefined();
81
+
82
+ const startNode = uwf.store.cas.get(headHash as CasRef);
83
+ expect(startNode).not.toBe(null);
84
+ expect(startNode?.type).toBe(uwf.schemas.startNode);
85
+
86
+ const startPayload = startNode?.payload as StartNodePayload;
87
+ return startPayload.cwd;
88
+ }
89
+
90
+ test("thread start with custom cwd via cmdThreadStart", async () => {
91
+ await setupTestEnv();
92
+
93
+ const workflowPath = await createTestWorkflow();
94
+ const testCwd = "/test/custom/path";
95
+
96
+ const result = await cmdThreadStart(storageRoot, workflowPath, "test prompt", tmpDir, testCwd);
97
+
98
+ expect(result.thread).toBeDefined();
99
+ const actualCwd = await getStartNodeCwd(result.thread);
100
+ expect(actualCwd).toBe(testCwd);
101
+
102
+ await teardown();
103
+ });
104
+
105
+ test("thread start without cwd defaults to process.cwd()", async () => {
106
+ await setupTestEnv();
107
+
108
+ const workflowPath = await createTestWorkflow();
109
+
110
+ // Call without cwd parameter (it defaults to process.cwd())
111
+ const result = await cmdThreadStart(storageRoot, workflowPath, "test prompt", tmpDir);
112
+
113
+ expect(result.thread).toBeDefined();
114
+ const actualCwd = await getStartNodeCwd(result.thread);
115
+ expect(actualCwd).toBe(process.cwd());
116
+
117
+ await teardown();
118
+ });
119
+
120
+ test("thread start with relative path fails", async () => {
121
+ await setupTestEnv();
122
+
123
+ const workflowPath = await createTestWorkflow();
124
+
125
+ await expect(
126
+ cmdThreadStart(storageRoot, workflowPath, "test", tmpDir, "relative/path"),
127
+ ).rejects.toThrow();
128
+
129
+ await teardown();
130
+ });
131
+
132
+ test("CLI accepts --cwd option without error", async () => {
133
+ await setupTestEnv();
134
+
135
+ const workflowPath = await createTestWorkflow();
136
+ const testCwd = "/test/cli/path";
137
+ const pkgRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
138
+ const uwfBin = join(pkgRoot, "dist", "cli.js");
139
+
140
+ // Register the workflow
141
+ execFileSync(process.execPath, [uwfBin, "workflow", "add", workflowPath], {
142
+ env: { ...process.env, UWF_HOME: storageRoot, OCAS_HOME: casDir },
143
+ encoding: "utf8",
144
+ });
145
+
146
+ // Verify CLI accepts --cwd option (no error thrown)
147
+ const output = execFileSync(
148
+ process.execPath,
149
+ [uwfBin, "thread", "start", "test-cwd-cli", "-p", "test prompt", "--cwd", testCwd],
150
+ {
151
+ env: { ...process.env, UWF_HOME: storageRoot, OCAS_HOME: casDir },
152
+ encoding: "utf8",
153
+ },
154
+ );
155
+
156
+ const result = JSON.parse(output);
157
+ expect(result.thread).toBeDefined();
158
+ expect(result.workflow).toBeDefined();
159
+
160
+ // The fact that we got here without throwing means CLI accepted the --cwd option
161
+ // The actual cwd functionality is tested by the other tests using cmdThreadStart directly
162
+ await teardown();
163
+ });
164
+ });
@@ -0,0 +1,70 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, test } from "vitest";
5
+ import { validateCount } from "../commands/thread.js";
6
+
7
+ const CLI_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
8
+
9
+ function runCli(args: string[]): {
10
+ stdout: string;
11
+ stderr: string;
12
+ exitCode: number;
13
+ } {
14
+ try {
15
+ const stdout = execFileSync("node", [CLI_PATH, ...args], {
16
+ encoding: "utf8",
17
+ env: { ...process.env, UWF_HOME: "/tmp/uwf-test-nonexistent" },
18
+ stdio: ["ignore", "pipe", "pipe"],
19
+ });
20
+ return { stdout, stderr: "", exitCode: 0 };
21
+ } catch (e: unknown) {
22
+ const err = e as NodeJS.ErrnoException & {
23
+ stdout?: string;
24
+ stderr?: string;
25
+ status?: number;
26
+ };
27
+ return {
28
+ stdout: err.stdout ?? "",
29
+ stderr: err.stderr ?? "",
30
+ exitCode: err.status ?? 1,
31
+ };
32
+ }
33
+ }
34
+
35
+ describe("thread exec --count CLI parsing", { timeout: 30_000 }, () => {
36
+ test("--help shows -c/--count option", () => {
37
+ const result = runCli(["thread", "exec", "--help"]);
38
+ const combined = result.stdout + result.stderr;
39
+ expect(combined).toContain("--count");
40
+ expect(combined).toContain("-c");
41
+ });
42
+
43
+ test("description says 'one or more steps'", () => {
44
+ const result = runCli(["thread", "exec", "--help"]);
45
+ const combined = result.stdout + result.stderr;
46
+ expect(combined).toContain("one or more steps");
47
+ });
48
+ });
49
+
50
+ describe("validateCount", () => {
51
+ test("count=0 throws validation error", () => {
52
+ expect(() => validateCount(0)).toThrow("positive integer");
53
+ });
54
+
55
+ test("negative count throws validation error", () => {
56
+ expect(() => validateCount(-1)).toThrow("positive integer");
57
+ });
58
+
59
+ test("non-integer count throws validation error", () => {
60
+ expect(() => validateCount(1.5)).toThrow("positive integer");
61
+ });
62
+
63
+ test("count=1 passes validation", () => {
64
+ expect(() => validateCount(1)).not.toThrow();
65
+ });
66
+
67
+ test("count=3 passes validation", () => {
68
+ expect(() => validateCount(3)).not.toThrow();
69
+ });
70
+ });