@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
@@ -0,0 +1,254 @@
1
+ import type { CasRef, StartOutput, StepOutput } from "@united-workforce/protocol";
2
+ import { extractUlidTimestamp } from "@united-workforce/util";
3
+ import type { ThreadListItemWithStatus } from "./commands/thread.js";
4
+ import type {
5
+ WorkflowAddOutput,
6
+ WorkflowListEntry,
7
+ WorkflowShowOutput,
8
+ } from "./commands/workflow.js";
9
+
10
+ /**
11
+ * Mappers that convert the existing rich command outputs into the
12
+ * schema-aligned payload shapes registered under `@uwf/output/*`.
13
+ *
14
+ * Each mapper returns plain payload data — no CAS refs, no JSON encoding,
15
+ * no I/O. The CLI calls one of these immediately before handing the payload
16
+ * to `writeEnvelope`.
17
+ */
18
+
19
+ export type ThreadStartPayload = {
20
+ threadId: string;
21
+ workflowHash: string;
22
+ };
23
+
24
+ export function toThreadStartPayload(out: StartOutput): ThreadStartPayload {
25
+ return { threadId: out.thread, workflowHash: out.workflow };
26
+ }
27
+
28
+ export type ThreadStatusPayload = {
29
+ threadId: string;
30
+ workflowHash: string;
31
+ head: string | null;
32
+ status: string;
33
+ currentRole: string | null;
34
+ suspendedRole: string | null;
35
+ suspendMessage: string | null;
36
+ done: boolean;
37
+ };
38
+
39
+ export function toThreadStatusPayload(out: StepOutput): ThreadStatusPayload {
40
+ return {
41
+ threadId: out.thread,
42
+ workflowHash: out.workflow,
43
+ head: out.head ?? null,
44
+ status: out.status,
45
+ currentRole: out.currentRole,
46
+ suspendedRole: out.suspendedRole,
47
+ suspendMessage: out.suspendMessage,
48
+ done: out.done,
49
+ };
50
+ }
51
+
52
+ export type ThreadListPayload = {
53
+ items: Array<{
54
+ threadId: string;
55
+ workflowHash: string;
56
+ workflowName: string | null;
57
+ status: string;
58
+ currentRole: string | null;
59
+ startedAt: number | null;
60
+ completedAt: number | null;
61
+ }>;
62
+ };
63
+
64
+ export function toThreadListPayload(items: ThreadListItemWithStatus[]): ThreadListPayload {
65
+ return {
66
+ items: items.map((it) => ({
67
+ threadId: it.thread,
68
+ workflowHash: it.workflow,
69
+ workflowName: it.workflowName,
70
+ status: it.status,
71
+ currentRole: it.currentRole,
72
+ startedAt: extractUlidTimestamp(it.thread),
73
+ completedAt: null,
74
+ })),
75
+ };
76
+ }
77
+
78
+ export type ThreadExecPayload = {
79
+ threadId: string;
80
+ workflowHash: string;
81
+ steps: Array<{
82
+ head: string;
83
+ status: string;
84
+ currentRole: string | null;
85
+ done: boolean;
86
+ role: string | null;
87
+ suspendedRole: string | null;
88
+ suspendMessage: string | null;
89
+ }>;
90
+ };
91
+
92
+ export function toThreadExecPayload(results: StepOutput[]): ThreadExecPayload {
93
+ const first = results[0];
94
+ return {
95
+ threadId: first?.thread ?? "",
96
+ workflowHash: first?.workflow ?? "",
97
+ steps: results.map((r) => ({
98
+ head: r.head,
99
+ status: r.status,
100
+ currentRole: r.currentRole,
101
+ done: r.done,
102
+ role: r.currentRole ?? r.suspendedRole ?? null,
103
+ suspendedRole: r.suspendedRole,
104
+ suspendMessage: r.suspendMessage,
105
+ })),
106
+ };
107
+ }
108
+
109
+ export type StepDetailPayload = {
110
+ hash: string;
111
+ role: string;
112
+ agent: string;
113
+ status: string;
114
+ startedAtMs: number | null;
115
+ completedAtMs: number | null;
116
+ durationMs: number | null;
117
+ frontmatter: Record<string, unknown>;
118
+ turns: Array<{ role: string; content: string; timestamp: number | null }>;
119
+ };
120
+
121
+ export function toStepDetailPayload(stepHash: CasRef, raw: unknown): StepDetailPayload {
122
+ const r = (raw ?? {}) as Record<string, unknown>;
123
+ const turnsIn = Array.isArray(r.turns) ? (r.turns as unknown[]) : [];
124
+ const startedAtMs = numericOrNull(r.startedAtMs);
125
+ const completedAtMs = numericOrNull(r.completedAtMs);
126
+ const durationMs =
127
+ startedAtMs !== null && completedAtMs !== null && completedAtMs >= startedAtMs
128
+ ? completedAtMs - startedAtMs
129
+ : null;
130
+ const frontmatter =
131
+ r.frontmatter !== null && typeof r.frontmatter === "object" && !Array.isArray(r.frontmatter)
132
+ ? (r.frontmatter as Record<string, unknown>)
133
+ : {};
134
+ const status =
135
+ typeof frontmatter.$status === "string"
136
+ ? (frontmatter.$status as string)
137
+ : typeof r.status === "string"
138
+ ? (r.status as string)
139
+ : "";
140
+ return {
141
+ hash: stepHash,
142
+ role: typeof r.role === "string" ? r.role : "",
143
+ agent: typeof r.agent === "string" ? r.agent : "",
144
+ status,
145
+ startedAtMs,
146
+ completedAtMs,
147
+ durationMs,
148
+ frontmatter,
149
+ turns: turnsIn.map((t) => {
150
+ const o = (t ?? {}) as Record<string, unknown>;
151
+ return {
152
+ role: typeof o.role === "string" ? o.role : "",
153
+ content: typeof o.content === "string" ? o.content : "",
154
+ timestamp: numericOrNull(o.timestamp),
155
+ };
156
+ }),
157
+ };
158
+ }
159
+
160
+ function numericOrNull(v: unknown): number | null {
161
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
162
+ }
163
+
164
+ export type StepListPayload = {
165
+ threadId: string;
166
+ items: Array<{ hash: string; role: string; durationMs: number | null }>;
167
+ };
168
+
169
+ type StepsLikeOutput = {
170
+ thread: string;
171
+ steps: Array<{
172
+ hash: CasRef;
173
+ role?: string;
174
+ durationMs?: number;
175
+ }>;
176
+ };
177
+
178
+ export function toStepListPayload(out: StepsLikeOutput): StepListPayload {
179
+ return {
180
+ threadId: out.thread,
181
+ items: out.steps
182
+ .filter((s) => typeof s.role === "string")
183
+ .map((s) => ({
184
+ hash: s.hash,
185
+ role: s.role ?? "",
186
+ durationMs: typeof s.durationMs === "number" ? s.durationMs : null,
187
+ })),
188
+ };
189
+ }
190
+
191
+ export type WorkflowDetailPayload = {
192
+ name: string;
193
+ hash: string;
194
+ version: number;
195
+ description: string;
196
+ roles: Record<string, { description: string; goal: string }>;
197
+ graph: Record<string, Record<string, { role: string; prompt: string }>>;
198
+ };
199
+
200
+ export function toWorkflowDetailPayload(out: WorkflowShowOutput): WorkflowDetailPayload {
201
+ const roles: Record<string, { description: string; goal: string }> = {};
202
+ for (const [name, def] of Object.entries(out.payload.roles)) {
203
+ roles[name] = { description: def.description, goal: def.goal };
204
+ }
205
+ const graph: Record<string, Record<string, { role: string; prompt: string }>> = {};
206
+ for (const [from, transitions] of Object.entries(out.payload.graph)) {
207
+ const t: Record<string, { role: string; prompt: string }> = {};
208
+ for (const [status, target] of Object.entries(transitions)) {
209
+ t[status] = { role: target.role, prompt: target.prompt };
210
+ }
211
+ graph[from] = t;
212
+ }
213
+ return {
214
+ name: out.name ?? out.payload.name,
215
+ hash: out.hash,
216
+ version: out.payload.version,
217
+ description: out.payload.description,
218
+ roles,
219
+ graph,
220
+ };
221
+ }
222
+
223
+ export type WorkflowListPayload = {
224
+ items: Array<{ name: string; hash: string; source: string; description: string }>;
225
+ };
226
+
227
+ export function toWorkflowListPayload(entries: WorkflowListEntry[]): WorkflowListPayload {
228
+ return {
229
+ items: entries.map((e) => ({
230
+ name: e.name,
231
+ hash: e.hash,
232
+ source: e.origin === "local" ? ".workflows" : "registry",
233
+ description: "",
234
+ })),
235
+ };
236
+ }
237
+
238
+ export type WorkflowAddPayload = {
239
+ name: string;
240
+ hash: string;
241
+ };
242
+
243
+ export function toWorkflowAddPayload(out: WorkflowAddOutput): WorkflowAddPayload {
244
+ return { name: out.name, hash: out.hash };
245
+ }
246
+
247
+ export type ValidateResultPayload = {
248
+ valid: boolean;
249
+ errors: string[];
250
+ };
251
+
252
+ export function toValidateResultPayload(errors: string[]): ValidateResultPayload {
253
+ return { valid: errors.length === 0, errors };
254
+ }
package/src/schemas.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  import type { Hash, Store } from "@ocas/core";
2
- import { putSchema } from "@ocas/core";
3
- import { START_NODE_SCHEMA, STEP_NODE_SCHEMA, WORKFLOW_SCHEMA } from "@united-workforce/protocol";
2
+ import { bootstrap, putSchema } from "@ocas/core";
3
+ import {
4
+ ERROR_OUTPUT_SCHEMA,
5
+ OUTPUT_SCHEMAS,
6
+ OUTPUT_TEMPLATES,
7
+ type OutputSchemaName,
8
+ outputSchemaVarName,
9
+ START_NODE_SCHEMA,
10
+ STEP_NODE_SCHEMA,
11
+ SUSPEND_OUTPUT_SCHEMA,
12
+ WORKFLOW_SCHEMA,
13
+ } from "@united-workforce/protocol";
4
14
 
5
15
  export const TEXT_SCHEMA = { type: "string" as const };
6
16
 
@@ -9,18 +19,54 @@ export type UwfSchemaHashes = {
9
19
  startNode: Hash;
10
20
  stepNode: Hash;
11
21
  text: Hash;
22
+ errorOutput: Hash;
23
+ suspendOutput: Hash;
24
+ outputs: Record<OutputSchemaName, Hash>;
12
25
  };
13
26
 
14
27
  /**
15
- * Register Workflow, StartNode, and StepNode JSON Schemas in the CAS store.
28
+ * Register every uwf JSON Schema (workflow / start / step / error / suspend
29
+ * + the 9 CLI output envelopes) and the matching `text` Liquid templates.
16
30
  * Idempotent: safe to call on every CLI invocation.
17
31
  */
18
32
  export async function registerUwfSchemas(store: Store): Promise<UwfSchemaHashes> {
19
- const [workflow, startNode, stepNode, text] = await Promise.all([
33
+ const [workflow, startNode, stepNode, text, errorOutput, suspendOutput] = await Promise.all([
20
34
  putSchema(store, WORKFLOW_SCHEMA),
21
35
  putSchema(store, START_NODE_SCHEMA),
22
36
  putSchema(store, STEP_NODE_SCHEMA),
23
37
  putSchema(store, TEXT_SCHEMA),
38
+ putSchema(store, ERROR_OUTPUT_SCHEMA),
39
+ putSchema(store, SUSPEND_OUTPUT_SCHEMA),
24
40
  ]);
25
- return { workflow, startNode, stepNode, text };
41
+ const outputs = await registerOutputSchemas(store);
42
+ return { workflow, startNode, stepNode, text, errorOutput, suspendOutput, outputs };
43
+ }
44
+
45
+ /**
46
+ * Register the 9 CLI output schemas, bind `@uwf/output/<name>` to each, store
47
+ * each Liquid template as an `@ocas/string` CAS node, and bind
48
+ * `@ocas/template/text/<schemaHash>` to the template content hash.
49
+ *
50
+ * Idempotent: writes are content-addressed so repeat invocations no-op.
51
+ */
52
+ async function registerOutputSchemas(store: Store): Promise<Record<OutputSchemaName, Hash>> {
53
+ const aliases = bootstrap(store);
54
+ const stringHash = aliases["@ocas/string"];
55
+ if (stringHash === undefined) {
56
+ throw new Error("@ocas/string schema not found in bootstrap result");
57
+ }
58
+
59
+ const result = {} as Record<OutputSchemaName, Hash>;
60
+ const names = Object.keys(OUTPUT_SCHEMAS) as OutputSchemaName[];
61
+ for (const name of names) {
62
+ const schemaHash = await putSchema(store, OUTPUT_SCHEMAS[name]);
63
+ store.var.set(outputSchemaVarName(name), schemaHash);
64
+
65
+ const template = OUTPUT_TEMPLATES[name];
66
+ const contentHash = store.cas.put(stringHash, template);
67
+ store.var.set(`@ocas/template/text/${schemaHash}`, contentHash);
68
+
69
+ result[name] = schemaHash;
70
+ }
71
+ return result;
26
72
  }
package/src/store.ts CHANGED
@@ -2,7 +2,7 @@ import type { Dirent } from "node:fs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { access, mkdir, readdir, readFile, rename } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
- import { join } from "node:path";
5
+ import { dirname, join, resolve as resolvePath } from "node:path";
6
6
 
7
7
  import { bootstrap, type Hash, type Store, type VarStore } from "@ocas/core";
8
8
  import { createFsStore, createSqliteVarStore } from "@ocas/fs";
@@ -20,7 +20,7 @@ export const REGISTRY_VAR_PREFIX = "@uwf/registry/";
20
20
  /** Variable name prefix for active thread entries (`@uwf/thread/<thread-id>`). */
21
21
  export const THREAD_VAR_PREFIX = "@uwf/thread/";
22
22
 
23
- /** A workflow entry discovered from the project-local .workflows/ directory. */
23
+ /** A workflow entry discovered from the project-local .workflows/ (primary) or .workflow/ (legacy) directory. */
24
24
  export type ProjectWorkflowEntry = {
25
25
  /** Workflow name (from YAML `name` field, equals filename stem). */
26
26
  name: string;
@@ -82,16 +82,11 @@ async function scanWorkflowDir(dir: string): Promise<ProjectWorkflowEntry[]> {
82
82
  return result;
83
83
  }
84
84
 
85
- /**
86
- * Scan `<projectRoot>/.workflow/` (preferred) and `.workflows/` (legacy) for workflow entries.
87
- * .workflow/ takes priority: if a name is found in both, .workflow/ wins.
88
- * Returns an empty array if neither directory exists.
89
- */
90
- export async function discoverProjectWorkflows(
91
- projectRoot: string,
92
- ): Promise<ProjectWorkflowEntry[]> {
93
- const primary = await scanWorkflowDir(join(projectRoot, ".workflow"));
94
- const legacy = await scanWorkflowDir(join(projectRoot, ".workflows"));
85
+ /** Merge primary (.workflows/) and legacy (.workflow/) entries, primary wins on name collision. */
86
+ function mergeWorkflowEntries(
87
+ primary: ProjectWorkflowEntry[],
88
+ legacy: ProjectWorkflowEntry[],
89
+ ): ProjectWorkflowEntry[] {
95
90
  const seen = new Set(primary.map((e) => e.name));
96
91
  const merged = [...primary];
97
92
  for (const entry of legacy) {
@@ -102,6 +97,63 @@ export async function discoverProjectWorkflows(
102
97
  return merged;
103
98
  }
104
99
 
100
+ /** Check if a directory contains a .git marker (directory or file). */
101
+ async function hasGitMarker(dir: string): Promise<boolean> {
102
+ try {
103
+ await access(join(dir, ".git"));
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Discover project-local workflows by walking from `startDir` up through parent
112
+ * directories. The nearest directory that contains a `.workflows/` or `.workflow/`
113
+ * directory wins — once a match is found, traversal stops (entries from more
114
+ * distant ancestors are NOT merged in).
115
+ *
116
+ * Within the winning directory:
117
+ * - `.workflows/` (preferred/primary) takes priority over `.workflow/` (legacy fallback).
118
+ * - If both exist in that directory, `.workflows/` entries win when names collide.
119
+ *
120
+ * This matches the resolution strategy of `findWorkflowInParents` used by
121
+ * `uwf thread start`, so `uwf workflow list` and `uwf thread start` agree on
122
+ * what's discoverable from any given subdirectory.
123
+ *
124
+ * Traversal stops at the first `.git` boundary (directory or file) or the
125
+ * filesystem root. Returns an empty array if no `.workflows/` or `.workflow/`
126
+ * directory exists within that range.
127
+ */
128
+ export async function discoverProjectWorkflows(startDir: string): Promise<ProjectWorkflowEntry[]> {
129
+ let currentDir = resolvePath(startDir);
130
+ const root = resolvePath("/");
131
+
132
+ while (true) {
133
+ const primary = await scanWorkflowDir(join(currentDir, ".workflows"));
134
+ const legacy = await scanWorkflowDir(join(currentDir, ".workflow"));
135
+
136
+ if (primary.length > 0 || legacy.length > 0) {
137
+ return mergeWorkflowEntries(primary, legacy);
138
+ }
139
+
140
+ // Stop at .git boundary (repo root)
141
+ if (await hasGitMarker(currentDir)) {
142
+ return [];
143
+ }
144
+
145
+ // Stop at filesystem root
146
+ if (currentDir === root) {
147
+ return [];
148
+ }
149
+ const parentDir = dirname(currentDir);
150
+ if (parentDir === currentDir) {
151
+ return [];
152
+ }
153
+ currentDir = parentDir;
154
+ }
155
+ }
156
+
105
157
  /** Default filesystem root for uwf data (`~/.uwf`). */
106
158
  export function getDefaultStorageRoot(): string {
107
159
  return join(homedir(), ".uwf");
@@ -335,35 +387,35 @@ export function setThread(varStore: VarStore, threadId: ThreadId, entry: ThreadI
335
387
  varStore.set(name, entry.head, { tags });
336
388
  }
337
389
 
338
- /** Load only active threads (status not in completed/cancelled). */
390
+ /** Load only active threads (status not in end/cancelled). */
339
391
  export function loadActiveThreads(varStore: VarStore): ThreadsIndex {
340
392
  const all = loadAllThreads(varStore);
341
393
  const active: ThreadsIndex = {};
342
394
  for (const [threadId, entry] of Object.entries(all)) {
343
- if (entry.status !== "completed" && entry.status !== "cancelled") {
395
+ if (entry.status !== "end" && entry.status !== "cancelled") {
344
396
  active[threadId as ThreadId] = entry;
345
397
  }
346
398
  }
347
399
  return active;
348
400
  }
349
401
 
350
- /** Load only completed/cancelled threads (history). */
402
+ /** Load only end/cancelled threads (history). */
351
403
  export function loadHistoryThreads(varStore: VarStore): ThreadsIndex {
352
404
  const all = loadAllThreads(varStore);
353
405
  const history: ThreadsIndex = {};
354
406
  for (const [threadId, entry] of Object.entries(all)) {
355
- if (entry.status === "completed" || entry.status === "cancelled") {
407
+ if (entry.status === "end" || entry.status === "cancelled") {
356
408
  history[threadId as ThreadId] = entry;
357
409
  }
358
410
  }
359
411
  return history;
360
412
  }
361
413
 
362
- /** Complete a thread by marking it completed or cancelled. */
414
+ /** Complete a thread by marking it end or cancelled. */
363
415
  export function completeThread(
364
416
  varStore: VarStore,
365
417
  threadId: ThreadId,
366
- reason: "completed" | "cancelled",
418
+ reason: "end" | "cancelled",
367
419
  ): void {
368
420
  const entry = getThread(varStore, threadId);
369
421
  if (entry === null) {
@@ -377,6 +429,20 @@ export function completeThread(
377
429
  completedAt: Date.now(),
378
430
  } as ThreadIndexEntry;
379
431
  setThread(varStore, threadId, completed);
432
+ clearThreadFailedAttempts(varStore, threadId);
433
+ }
434
+
435
+ /**
436
+ * Remove all `@uwf/thread-failed/<threadId>/*` variables for a thread.
437
+ * Called on thread completion / cancellation so retry-lineage state does not
438
+ * leak into the variable store after the thread is archived.
439
+ */
440
+ export function clearThreadFailedAttempts(varStore: VarStore, threadId: ThreadId): void {
441
+ const prefix = `@uwf/thread-failed/${threadId}/`;
442
+ const vars = varStore.list({ namePrefix: prefix });
443
+ for (const v of vars) {
444
+ varStore.remove(v.name);
445
+ }
380
446
  }
381
447
 
382
448
  type LegacyHistoryEntry = {
@@ -439,7 +505,7 @@ export async function migrateHistoryIfNeeded(
439
505
  }
440
506
  const entry = parseLegacyHistoryJsonlLine(trimmed);
441
507
  if (entry !== null) {
442
- const status = entry.reason === "cancelled" ? "cancelled" : "completed";
508
+ const status = entry.reason === "cancelled" ? "cancelled" : "end";
443
509
  const threadEntry: ThreadIndexEntry = {
444
510
  head: entry.head,
445
511
  status: status as ThreadIndexEntry["status"],
@@ -462,7 +528,7 @@ export function migrateHistoryVarsToThreadVars(varStore: VarStore): void {
462
528
  for (const v of vars) {
463
529
  const threadId = v.name.slice(LEGACY_HISTORY_VAR_PREFIX.length) as ThreadId;
464
530
  const reason = v.tags.reason;
465
- const status = reason === "cancelled" ? "cancelled" : "completed";
531
+ const status = reason === "cancelled" ? "cancelled" : "end";
466
532
  const completedAt = Number(v.tags.completedAt ?? Date.now());
467
533
 
468
534
  const threadEntry: ThreadIndexEntry = {