@united-workforce/cli 0.2.1-rc.9 → 0.4.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 (219) hide show
  1. package/README.md +15 -8
  2. package/dist/__tests__/adapter-json-roundtrip.test.js +1 -1
  3. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
  4. package/dist/__tests__/agent-resolution-llm-free.test.d.ts +2 -0
  5. package/dist/__tests__/agent-resolution-llm-free.test.d.ts.map +1 -0
  6. package/dist/__tests__/agent-resolution-llm-free.test.js +30 -0
  7. package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -0
  8. package/dist/__tests__/build-step-entry.test.d.ts +2 -0
  9. package/dist/__tests__/build-step-entry.test.d.ts.map +1 -0
  10. package/dist/__tests__/build-step-entry.test.js +173 -0
  11. package/dist/__tests__/build-step-entry.test.js.map +1 -0
  12. package/dist/__tests__/clear-thread-failed-attempts.test.d.ts +2 -0
  13. package/dist/__tests__/clear-thread-failed-attempts.test.d.ts.map +1 -0
  14. package/dist/__tests__/clear-thread-failed-attempts.test.js +93 -0
  15. package/dist/__tests__/clear-thread-failed-attempts.test.js.map +1 -0
  16. package/dist/__tests__/config.test.js +26 -302
  17. package/dist/__tests__/config.test.js.map +1 -1
  18. package/dist/__tests__/current-role.test.js +7 -6
  19. package/dist/__tests__/current-role.test.js.map +1 -1
  20. package/dist/__tests__/e2e-mock-agent.test.js +20 -23
  21. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
  22. package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts +2 -0
  23. package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts.map +1 -0
  24. package/dist/__tests__/issue-180-workflow-ref-removed.test.js +40 -0
  25. package/dist/__tests__/issue-180-workflow-ref-removed.test.js.map +1 -0
  26. package/dist/__tests__/moderator-evaluate.test.js +9 -50
  27. package/dist/__tests__/moderator-evaluate.test.js.map +1 -1
  28. package/dist/__tests__/pid-recycling.test.d.ts +2 -0
  29. package/dist/__tests__/pid-recycling.test.d.ts.map +1 -0
  30. package/dist/__tests__/pid-recycling.test.js +271 -0
  31. package/dist/__tests__/pid-recycling.test.js.map +1 -0
  32. package/dist/__tests__/prompt.test.js +321 -0
  33. package/dist/__tests__/prompt.test.js.map +1 -1
  34. package/dist/__tests__/resolve-head-hash.test.js +4 -4
  35. package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
  36. package/dist/__tests__/setup-agent-discovery.test.js +21 -30
  37. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
  38. package/dist/__tests__/setup-complexity.test.js +2 -168
  39. package/dist/__tests__/setup-complexity.test.js.map +1 -1
  40. package/dist/__tests__/setup-no-llm.test.d.ts +2 -0
  41. package/dist/__tests__/setup-no-llm.test.d.ts.map +1 -0
  42. package/dist/__tests__/setup-no-llm.test.js +52 -0
  43. package/dist/__tests__/setup-no-llm.test.js.map +1 -0
  44. package/dist/__tests__/solve-issue-tea-worktree.test.js +24 -27
  45. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
  46. package/dist/__tests__/step-ask.test.d.ts +2 -0
  47. package/dist/__tests__/step-ask.test.d.ts.map +1 -0
  48. package/dist/__tests__/step-ask.test.js +499 -0
  49. package/dist/__tests__/step-ask.test.js.map +1 -0
  50. package/dist/__tests__/step-show-json.test.js +1 -0
  51. package/dist/__tests__/step-show-json.test.js.map +1 -1
  52. package/dist/__tests__/step-timing.test.js +2 -0
  53. package/dist/__tests__/step-timing.test.js.map +1 -1
  54. package/dist/__tests__/store-global-cas.test.js +2 -2
  55. package/dist/__tests__/store-global-cas.test.js.map +1 -1
  56. package/dist/__tests__/store-unified-threads.test.js +9 -9
  57. package/dist/__tests__/store-unified-threads.test.js.map +1 -1
  58. package/dist/__tests__/thread-cancel-status.test.js +6 -6
  59. package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
  60. package/dist/__tests__/thread-list-filters.test.js +344 -9
  61. package/dist/__tests__/thread-list-filters.test.js.map +1 -1
  62. package/dist/__tests__/thread-poke.test.d.ts +2 -0
  63. package/dist/__tests__/thread-poke.test.d.ts.map +1 -0
  64. package/dist/__tests__/thread-poke.test.js +412 -0
  65. package/dist/__tests__/thread-poke.test.js.map +1 -0
  66. package/dist/__tests__/thread-resume.test.js +10 -14
  67. package/dist/__tests__/thread-resume.test.js.map +1 -1
  68. package/dist/__tests__/thread-show-status.test.js +17 -28
  69. package/dist/__tests__/thread-show-status.test.js.map +1 -1
  70. package/dist/__tests__/thread-suspend-step.test.js +8 -14
  71. package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
  72. package/dist/__tests__/thread-suspended-display.test.js +10 -22
  73. package/dist/__tests__/thread-suspended-display.test.js.map +1 -1
  74. package/dist/__tests__/thread.test.js +4 -4
  75. package/dist/__tests__/thread.test.js.map +1 -1
  76. package/dist/__tests__/validate-semantic.test.js +49 -21
  77. package/dist/__tests__/validate-semantic.test.js.map +1 -1
  78. package/dist/__tests__/workflow-list-recursive.test.d.ts +2 -0
  79. package/dist/__tests__/workflow-list-recursive.test.d.ts.map +1 -0
  80. package/dist/__tests__/workflow-list-recursive.test.js +283 -0
  81. package/dist/__tests__/workflow-list-recursive.test.js.map +1 -0
  82. package/dist/__tests__/workflow-resolution.test.js +36 -21
  83. package/dist/__tests__/workflow-resolution.test.js.map +1 -1
  84. package/dist/__tests__/workflow-show-resolution.test.d.ts +2 -0
  85. package/dist/__tests__/workflow-show-resolution.test.d.ts.map +1 -0
  86. package/dist/__tests__/workflow-show-resolution.test.js +210 -0
  87. package/dist/__tests__/workflow-show-resolution.test.js.map +1 -0
  88. package/dist/__tests__/workflow-validate.test.d.ts +2 -0
  89. package/dist/__tests__/workflow-validate.test.d.ts.map +1 -0
  90. package/dist/__tests__/workflow-validate.test.js +687 -0
  91. package/dist/__tests__/workflow-validate.test.js.map +1 -0
  92. package/dist/background/background.d.ts +22 -1
  93. package/dist/background/background.d.ts.map +1 -1
  94. package/dist/background/background.js +83 -6
  95. package/dist/background/background.js.map +1 -1
  96. package/dist/background/index.d.ts +1 -1
  97. package/dist/background/index.d.ts.map +1 -1
  98. package/dist/background/index.js +1 -1
  99. package/dist/background/index.js.map +1 -1
  100. package/dist/background/types.d.ts +1 -0
  101. package/dist/background/types.d.ts.map +1 -1
  102. package/dist/cli.js +66 -31
  103. package/dist/cli.js.map +1 -1
  104. package/dist/commands/config.d.ts +3 -1
  105. package/dist/commands/config.d.ts.map +1 -1
  106. package/dist/commands/config.js +7 -33
  107. package/dist/commands/config.js.map +1 -1
  108. package/dist/commands/prompt.d.ts.map +1 -1
  109. package/dist/commands/prompt.js +15 -2
  110. package/dist/commands/prompt.js.map +1 -1
  111. package/dist/commands/setup.d.ts +7 -39
  112. package/dist/commands/setup.d.ts.map +1 -1
  113. package/dist/commands/setup.js +27 -302
  114. package/dist/commands/setup.js.map +1 -1
  115. package/dist/commands/step.d.ts +44 -1
  116. package/dist/commands/step.d.ts.map +1 -1
  117. package/dist/commands/step.js +255 -11
  118. package/dist/commands/step.js.map +1 -1
  119. package/dist/commands/thread.d.ts +16 -3
  120. package/dist/commands/thread.d.ts.map +1 -1
  121. package/dist/commands/thread.js +379 -140
  122. package/dist/commands/thread.js.map +1 -1
  123. package/dist/commands/workflow.d.ts +9 -1
  124. package/dist/commands/workflow.d.ts.map +1 -1
  125. package/dist/commands/workflow.js +130 -6
  126. package/dist/commands/workflow.js.map +1 -1
  127. package/dist/moderator/__tests__/evaluate.test.js +31 -17
  128. package/dist/moderator/__tests__/evaluate.test.js.map +1 -1
  129. package/dist/moderator/evaluate.d.ts.map +1 -1
  130. package/dist/moderator/evaluate.js +4 -16
  131. package/dist/moderator/evaluate.js.map +1 -1
  132. package/dist/moderator/index.d.ts +1 -2
  133. package/dist/moderator/index.d.ts.map +1 -1
  134. package/dist/moderator/index.js +0 -1
  135. package/dist/moderator/index.js.map +1 -1
  136. package/dist/moderator/types.d.ts +6 -10
  137. package/dist/moderator/types.d.ts.map +1 -1
  138. package/dist/moderator/types.js +1 -3
  139. package/dist/moderator/types.js.map +1 -1
  140. package/dist/schemas.d.ts +2 -0
  141. package/dist/schemas.d.ts.map +1 -1
  142. package/dist/schemas.js +5 -3
  143. package/dist/schemas.js.map +1 -1
  144. package/dist/store.d.ts +28 -9
  145. package/dist/store.d.ts.map +1 -1
  146. package/dist/store.js +75 -16
  147. package/dist/store.js.map +1 -1
  148. package/dist/validate-semantic.d.ts.map +1 -1
  149. package/dist/validate-semantic.js +83 -66
  150. package/dist/validate-semantic.js.map +1 -1
  151. package/dist/validate.d.ts +6 -0
  152. package/dist/validate.d.ts.map +1 -1
  153. package/dist/validate.js +24 -0
  154. package/dist/validate.js.map +1 -1
  155. package/package.json +8 -10
  156. package/src/__tests__/adapter-json-roundtrip.test.ts +1 -1
  157. package/src/__tests__/agent-resolution-llm-free.test.ts +39 -0
  158. package/src/__tests__/build-step-entry.test.ts +203 -0
  159. package/src/__tests__/clear-thread-failed-attempts.test.ts +122 -0
  160. package/src/__tests__/config.test.ts +33 -321
  161. package/src/__tests__/current-role.test.ts +7 -6
  162. package/src/__tests__/e2e-mock-agent.test.ts +20 -23
  163. package/src/__tests__/fixtures/e2e-count.workflow.yaml +1 -0
  164. package/src/__tests__/fixtures/e2e-linear.workflow.yaml +1 -0
  165. package/src/__tests__/fixtures/{e2e-mustache.workflow.yaml → e2e-liquid.workflow.yaml} +3 -2
  166. package/src/__tests__/fixtures/e2e-loop.workflow.yaml +1 -0
  167. package/src/__tests__/fixtures/e2e-suspend.mock.yaml +2 -2
  168. package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +6 -10
  169. package/src/__tests__/issue-180-workflow-ref-removed.test.ts +43 -0
  170. package/src/__tests__/moderator-evaluate.test.ts +9 -52
  171. package/src/__tests__/pid-recycling.test.ts +328 -0
  172. package/src/__tests__/prompt.test.ts +397 -0
  173. package/src/__tests__/resolve-head-hash.test.ts +4 -4
  174. package/src/__tests__/setup-agent-discovery.test.ts +26 -51
  175. package/src/__tests__/setup-complexity.test.ts +1 -203
  176. package/src/__tests__/setup-no-llm.test.ts +68 -0
  177. package/src/__tests__/solve-issue-tea-worktree.test.ts +24 -30
  178. package/src/__tests__/step-ask.test.ts +670 -0
  179. package/src/__tests__/step-show-json.test.ts +1 -0
  180. package/src/__tests__/step-timing.test.ts +2 -0
  181. package/src/__tests__/store-global-cas.test.ts +2 -2
  182. package/src/__tests__/store-unified-threads.test.ts +9 -9
  183. package/src/__tests__/thread-cancel-status.test.ts +6 -6
  184. package/src/__tests__/thread-list-filters.test.ts +434 -8
  185. package/src/__tests__/thread-poke.test.ts +545 -0
  186. package/src/__tests__/thread-resume.test.ts +10 -14
  187. package/src/__tests__/thread-show-status.test.ts +17 -29
  188. package/src/__tests__/thread-suspend-step.test.ts +8 -14
  189. package/src/__tests__/thread-suspended-display.test.ts +10 -22
  190. package/src/__tests__/thread.test.ts +4 -4
  191. package/src/__tests__/validate-semantic.test.ts +59 -31
  192. package/src/__tests__/workflow-list-recursive.test.ts +370 -0
  193. package/src/__tests__/workflow-resolution.test.ts +39 -21
  194. package/src/__tests__/workflow-show-resolution.test.ts +285 -0
  195. package/src/__tests__/workflow-validate.test.ts +806 -0
  196. package/src/background/background.ts +88 -6
  197. package/src/background/index.ts +2 -0
  198. package/src/background/types.ts +1 -0
  199. package/src/cli.ts +97 -47
  200. package/src/commands/config.ts +7 -35
  201. package/src/commands/prompt.ts +15 -2
  202. package/src/commands/setup.ts +29 -357
  203. package/src/commands/step.ts +339 -12
  204. package/src/commands/thread.ts +463 -169
  205. package/src/commands/workflow.ts +159 -4
  206. package/src/moderator/__tests__/evaluate.test.ts +34 -17
  207. package/src/moderator/evaluate.ts +5 -17
  208. package/src/moderator/index.ts +1 -6
  209. package/src/moderator/types.ts +6 -14
  210. package/src/schemas.ts +13 -3
  211. package/src/store.ts +86 -20
  212. package/src/validate-semantic.ts +109 -78
  213. package/src/validate.ts +27 -0
  214. package/dist/__tests__/setup-validate.test.d.ts +0 -2
  215. package/dist/__tests__/setup-validate.test.d.ts.map +0 -1
  216. package/dist/__tests__/setup-validate.test.js +0 -108
  217. package/dist/__tests__/setup-validate.test.js.map +0 -1
  218. package/src/__tests__/setup-validate.test.ts +0 -148
  219. /package/src/__tests__/fixtures/{e2e-mustache.mock.yaml → e2e-liquid.mock.yaml} +0 -0
@@ -4,7 +4,7 @@ import { join } from "node:path";
4
4
  import { putSchema } from "@ocas/core";
5
5
  import type { CasRef, ThreadId } from "@united-workforce/protocol";
6
6
  import { describe, expect, test } from "vitest";
7
- import { createMarker, deleteMarker } from "../background/index.js";
7
+ import { createMarker, deleteMarker, getProcessStartTime } from "../background/index.js";
8
8
  import { cmdThreadList, cmdThreadShow, cmdThreadStart } from "../commands/thread.js";
9
9
  import { completeThread, createUwfStore, loadActiveThreads, setThread } from "../store.js";
10
10
 
@@ -307,10 +307,10 @@ describe("currentRole field", () => {
307
307
 
308
308
  const uwfForIndex = await createUwfStore(storageRoot);
309
309
  loadActiveThreads(uwfForIndex.varStore)[tid]!.head;
310
- completeThread(uwfForIndex.varStore, tid, "completed");
310
+ completeThread(uwfForIndex.varStore, tid, "end");
311
311
 
312
312
  const result = await cmdThreadShow(storageRoot, tid);
313
- expect(result.status).toBe("completed");
313
+ expect(result.status).toBe("end");
314
314
  expect(result.currentRole).toBe(null);
315
315
  } finally {
316
316
  await teardown();
@@ -352,6 +352,7 @@ describe("currentRole field", () => {
352
352
  workflow,
353
353
  pid: process.pid,
354
354
  startedAt: Date.now(),
355
+ processStartTime: getProcessStartTime(process.pid),
355
356
  });
356
357
 
357
358
  try {
@@ -378,13 +379,13 @@ describe("currentRole field", () => {
378
379
  const idleId = idle.thread as ThreadId;
379
380
 
380
381
  // completed thread
381
- const comp = await cmdThreadStart(storageRoot, wf, "completed", tmpDir);
382
+ const comp = await cmdThreadStart(storageRoot, wf, "end", tmpDir);
382
383
  const compId = comp.thread as ThreadId;
383
384
  const uwfForIndex = await createUwfStore(storageRoot);
384
385
  const _compHead = loadActiveThreads(uwfForIndex.varStore)[compId]!.head;
385
- completeThread(uwfForIndex.varStore, compId, "completed");
386
+ completeThread(uwfForIndex.varStore, compId, "end");
386
387
 
387
- const list = await cmdThreadList(storageRoot, null, null, null, 0, 100);
388
+ const list = await cmdThreadList(storageRoot, null, null, null, 0, 100, true);
388
389
 
389
390
  const idleItem = list.find((i) => i.thread === idleId);
390
391
  expect(idleItem).toBeDefined();
@@ -78,9 +78,6 @@ afterEach(async () => {
78
78
  async function writeMockConfig(mockDataFixture: string): Promise<void> {
79
79
  const config = {
80
80
  defaultAgent: "mock",
81
- defaultModel: "test",
82
- providers: {},
83
- models: {},
84
81
  agentOverrides: null,
85
82
  agents: {
86
83
  mock: {
@@ -187,7 +184,7 @@ function getStatus(store: Awaited<ReturnType<typeof openStore>>, outputRef: CasR
187
184
 
188
185
  // ── scenarios ─────────────────────────────────────────────────────────────────
189
186
 
190
- describe("E2E mock-agent: full uwf pipeline", () => {
187
+ describe("E2E mock-agent: full uwf pipeline", { timeout: 15_000 }, () => {
191
188
  test("1. linear workflow runs planner then worker and reaches $END", async () => {
192
189
  await writeMockConfig("e2e-linear.mock.yaml");
193
190
  const workflowHash = await addWorkflow("e2e-linear.workflow.yaml", "test-linear");
@@ -209,7 +206,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
209
206
  // Step 2 → worker → $END (thread archived to history).
210
207
  const step2 = execStep(threadId);
211
208
  expect(step2.done).toBe(true);
212
- expect(step2.status).toBe("completed");
209
+ expect(step2.status).toBe("end");
213
210
  expect(step2.currentRole).toBeNull();
214
211
 
215
212
  // Verify CAS chain integrity: start → step1 → step2.
@@ -237,11 +234,11 @@ describe("E2E mock-agent: full uwf pipeline", () => {
237
234
  const startNode = store.cas.get(startHash as CasRef);
238
235
  expect((startNode!.payload as StartNodePayload).workflow).toBe(workflowHash);
239
236
 
240
- // Thread is completed: status changed to "completed", head updated.
237
+ // Thread is completed: status changed to "end", head updated.
241
238
  const uwf = await createUwfStore(uwfHome);
242
239
  const finalEntry = getThread(uwf.varStore, threadId);
243
240
  expect(finalEntry).not.toBeNull();
244
- expect(finalEntry!.status).toBe("completed");
241
+ expect(finalEntry!.status).toBe("end");
245
242
  expect(finalEntry!.head).toBe(step2.head);
246
243
  });
247
244
 
@@ -270,7 +267,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
270
267
 
271
268
  const s4 = execStep(threadId);
272
269
  expect(s4.done).toBe(true);
273
- expect(s4.status).toBe("completed");
270
+ expect(s4.status).toBe("end");
274
271
 
275
272
  // Verify the chain order and roles.
276
273
  const store = await openStore(casDir);
@@ -302,7 +299,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
302
299
  const uwf = await createUwfStore(uwfHome);
303
300
  const finalEntry = getThread(uwf.varStore, threadId);
304
301
  expect(finalEntry).not.toBeNull();
305
- expect(finalEntry!.status).toBe("completed");
302
+ expect(finalEntry!.status).toBe("end");
306
303
  });
307
304
 
308
305
  test("3. role mismatch in mock data makes the agent exit with an error", {
@@ -329,7 +326,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
329
326
  const uwf = await createUwfStore(uwfHome);
330
327
  const entry = getThread(uwf.varStore, threadId);
331
328
  expect(entry).not.toBeNull();
332
- expect(entry!.status).not.toBe("completed");
329
+ expect(entry!.status).not.toBe("end");
333
330
  expect(entry!.head).toBe(step1.head);
334
331
  });
335
332
 
@@ -361,7 +358,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
361
358
  const resume = runResume(threadId, "Here are the requirements");
362
359
  expect(resume.exitCode).toBe(0);
363
360
  const resumeOut = JSON.parse(resume.stdout.trim()) as StepOutputJson;
364
- expect(resumeOut.status).toBe("completed");
361
+ expect(resumeOut.status).toBe("end");
365
362
  expect(resumeOut.done).toBe(true);
366
363
  expect(resumeOut.currentRole).toBeNull();
367
364
  expect(resumeOut.suspendedRole).toBeNull();
@@ -373,12 +370,12 @@ describe("E2E mock-agent: full uwf pipeline", () => {
373
370
  expect(s1.role).toBe("planner");
374
371
  expect(s2.role).toBe("planner");
375
372
  expect(s2.prev).toBe(step1.head);
376
- expect(getStatus(store, s1.output)).toBe("insufficient_info");
373
+ expect(getStatus(store, s1.output)).toBe("$SUSPEND");
377
374
  expect(getStatus(store, s2.output)).toBe("ready");
378
375
 
379
376
  const finalEntry = getThread((await createUwfStore(uwfHome)).varStore, threadId);
380
377
  expect(finalEntry).not.toBeNull();
381
- expect(finalEntry!.status).toBe("completed");
378
+ expect(finalEntry!.status).toBe("end");
382
379
  expect(finalEntry!.head).toBe(resumeOut.head);
383
380
  });
384
381
 
@@ -404,7 +401,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
404
401
  expect(results[0].currentRole).toBe("developer");
405
402
  expect(results[1].status).toBe("idle");
406
403
  expect(results[1].currentRole).toBe("reviewer");
407
- expect(results[2].status).toBe("completed");
404
+ expect(results[2].status).toBe("end");
408
405
  expect(results[2].done).toBe(true);
409
406
 
410
407
  // Verify the CAS chain holds 3 step nodes in the correct order.
@@ -420,15 +417,15 @@ describe("E2E mock-agent: full uwf pipeline", () => {
420
417
 
421
418
  const finalEntry = getThread((await createUwfStore(uwfHome)).varStore, threadId);
422
419
  expect(finalEntry).not.toBeNull();
423
- expect(finalEntry!.status).toBe("completed");
420
+ expect(finalEntry!.status).toBe("end");
424
421
  expect(finalEntry!.head).toBe(results[2].head);
425
422
  });
426
423
 
427
- test("6. mustache edge prompt renders planner variables into the worker step", {
424
+ test("6. Liquid edge prompt renders planner variables into the worker step", {
428
425
  timeout: 30_000,
429
426
  }, async () => {
430
- await writeMockConfig("e2e-mustache.mock.yaml");
431
- const workflowHash = await addWorkflow("e2e-mustache.workflow.yaml", "test-mustache");
427
+ await writeMockConfig("e2e-liquid.mock.yaml");
428
+ const workflowHash = await addWorkflow("e2e-liquid.workflow.yaml", "test-liquid");
432
429
 
433
430
  const start = await cmdThreadStart(uwfHome, workflowHash, "Plan the task", uwfHome, tmpDir);
434
431
  const threadId = start.thread;
@@ -441,13 +438,13 @@ describe("E2E mock-agent: full uwf pipeline", () => {
441
438
  // Step 2 → worker; the moderator renders the templated edge prompt before spawning it.
442
439
  const step2 = execStep(threadId);
443
440
  expect(step2.done).toBe(true);
444
- expect(step2.status).toBe("completed");
441
+ expect(step2.status).toBe("end");
445
442
 
446
443
  const store = await openStore(casDir);
447
444
  const plannerStep = getStepNode(store, step1.head);
448
445
  expect(getStatus(store, plannerStep.output)).toBe("ready");
449
446
 
450
- // The worker step's edgePrompt is the mustache-rendered template.
447
+ // The worker step's edgePrompt is the Liquid-rendered template.
451
448
  const workerStep = getStepNode(store, step2.head);
452
449
  expect(workerStep.role).toBe("worker");
453
450
  expect(workerStep.edgePrompt).toContain("fix/42-auth");
@@ -469,12 +466,12 @@ describe("E2E mock-agent: full uwf pipeline", () => {
469
466
  // Step 1: planner outputs ready → $END → thread completed.
470
467
  const step1 = execStep(threadId);
471
468
  expect(step1.done).toBe(true);
472
- expect(step1.status).toBe("completed");
469
+ expect(step1.status).toBe("end");
473
470
 
474
471
  const uwf1 = await createUwfStore(uwfHome);
475
472
  const entry1 = getThread(uwf1.varStore, threadId);
476
473
  expect(entry1).not.toBeNull();
477
- expect(entry1!.status).toBe("completed");
474
+ expect(entry1!.status).toBe("end");
478
475
 
479
476
  // Resume the completed thread — should re-evaluate $START → planner.
480
477
  const resumeResult = runResume(threadId, "Additional context for round 2");
@@ -484,7 +481,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
484
481
  const uwf2 = await createUwfStore(uwfHome);
485
482
  const entry2 = getThread(uwf2.varStore, threadId);
486
483
  expect(entry2).not.toBeNull();
487
- expect(entry2!.status).toBe("completed");
484
+ expect(entry2!.status).toBe("end");
488
485
  // Head should have advanced (not the same as step1).
489
486
  expect(entry2!.head).not.toBe(step1.head);
490
487
 
@@ -1,3 +1,4 @@
1
+ version: 1
1
2
  name: test-count
2
3
  description: 3-step linear pipeline (analyst -> developer -> reviewer -> $END)
3
4
  roles:
@@ -1,3 +1,4 @@
1
+ version: 1
1
2
  name: test-linear
2
3
  description: Simple 2-step linear test (planner -> worker -> $END)
3
4
  roles:
@@ -1,4 +1,5 @@
1
- name: test-mustache
1
+ version: 1
2
+ name: test-liquid
2
3
  description: Planner emits template variables consumed by the worker edge prompt
3
4
  roles:
4
5
  planner:
@@ -30,6 +31,6 @@ graph:
30
31
  new: { role: planner, prompt: 'Plan the task' }
31
32
  resume: { role: planner, prompt: 'Review the previous run output and continue the work.' }
32
33
  planner:
33
- ready: { role: worker, prompt: 'Work on branch {{{branch}}} in {{{repoPath}}}' }
34
+ ready: { role: worker, prompt: 'Work on branch {{ branch }} in {{ repoPath }}' }
34
35
  worker:
35
36
  done: { role: '$END', prompt: 'Complete' }
@@ -1,3 +1,4 @@
1
+ version: 1
1
2
  name: test-loop
2
3
  description: Branching test where the reviewer can reject and loop back to the developer
3
4
  roles:
@@ -2,8 +2,8 @@ steps:
2
2
  - role: planner
3
3
  output: |
4
4
  ---
5
- $status: insufficient_info
6
- reason: missing requirements
5
+ $status: "$SUSPEND"
6
+ reason: 'Need more info: missing requirements'
7
7
  ---
8
8
  I need more information before I can plan this.
9
9
  - role: planner
@@ -1,3 +1,4 @@
1
+ version: 1
1
2
  name: test-suspend
2
3
  description: Planner can suspend for more info or finish when ready
3
4
  roles:
@@ -6,20 +7,15 @@ roles:
6
7
  goal: Analyze the task
7
8
  capabilities: []
8
9
  procedure: Analyze the task and decide if more info is needed
9
- output: Set $status to insufficient_info (with reason) or ready
10
+ output: Set $status to ready when done, or emit $status "$SUSPEND" (with reason) to pause for more info
10
11
  frontmatter:
11
- oneOf:
12
- - properties:
13
- $status: { const: insufficient_info }
14
- reason: { type: string }
15
- required: [$status, reason]
16
- - properties:
17
- $status: { const: ready }
18
- required: [$status]
12
+ type: object
13
+ required: [$status]
14
+ properties:
15
+ $status: { const: ready }
19
16
  graph:
20
17
  $START:
21
18
  new: { role: planner, prompt: 'Analyze the task' }
22
19
  resume: { role: planner, prompt: 'Review the previous run output and continue the work.' }
23
20
  planner:
24
- insufficient_info: { role: '$SUSPEND', prompt: 'Need more info: {{{reason}}}' }
25
21
  ready: { role: '$END', prompt: 'Done' }
@@ -0,0 +1,43 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { describe, expect, test } from "vitest";
4
+
5
+ const THREAD_TS_PATH = fileURLToPath(new URL("../commands/thread.ts", import.meta.url));
6
+
7
+ describe("issue #180 — _workflowRef ghost parameter cleanup", () => {
8
+ test("thread.ts no longer references the dead _workflowRef parameter", async () => {
9
+ const source = await readFile(THREAD_TS_PATH, "utf8");
10
+ expect(source).not.toContain("_workflowRef");
11
+ });
12
+
13
+ test("resolveActiveThreadStatus is declared with exactly 4 parameters", async () => {
14
+ const source = await readFile(THREAD_TS_PATH, "utf8");
15
+ const declMatch = source.match(/async function resolveActiveThreadStatus\s*\(([\s\S]*?)\)\s*:/);
16
+ expect(declMatch).not.toBeNull();
17
+ const paramList = (declMatch as RegExpMatchArray)[1];
18
+ const params = paramList
19
+ .split(",")
20
+ .map((p) => p.trim())
21
+ .filter((p) => p.length > 0);
22
+ expect(params).toHaveLength(4);
23
+ });
24
+
25
+ test("every call site of resolveActiveThreadStatus passes exactly 4 args", async () => {
26
+ const source = await readFile(THREAD_TS_PATH, "utf8");
27
+ // Capture call-site arg lists. Excludes the function declaration: it's
28
+ // preceded by `function ` rather than parens-following-name.
29
+ const callRe = /(?<!function\s)resolveActiveThreadStatus\s*\(([^)]*)\)/g;
30
+ const callSites: string[] = [];
31
+ for (const match of source.matchAll(callRe)) {
32
+ callSites.push(match[1]);
33
+ }
34
+ expect(callSites.length).toBe(3);
35
+ for (const args of callSites) {
36
+ const argCount = args
37
+ .split(",")
38
+ .map((a) => a.trim())
39
+ .filter((a) => a.length > 0).length;
40
+ expect(argCount).toBe(4);
41
+ }
42
+ });
43
+ });
@@ -13,14 +13,14 @@ const solveIssueGraph: WorkflowPayload["graph"] = {
13
13
  },
14
14
  },
15
15
  planner: {
16
- planned: { role: "developer", prompt: "Implement the plan: {{plan}}", location: null },
16
+ planned: { role: "developer", prompt: "Implement the plan: {{ plan }}", location: null },
17
17
  },
18
18
  developer: {
19
- implemented: { role: "reviewer", prompt: "Review the changes: {{summary}}", location: null },
19
+ implemented: { role: "reviewer", prompt: "Review the changes: {{ summary }}", location: null },
20
20
  },
21
21
  reviewer: {
22
22
  approved: { role: "$END", prompt: "Done.", location: null },
23
- rejected: { role: "developer", prompt: "Fix: {{comments}}", location: null },
23
+ rejected: { role: "developer", prompt: "Fix: {{ comments }}", location: null },
24
24
  },
25
25
  };
26
26
 
@@ -68,49 +68,6 @@ describe("evaluate", () => {
68
68
  });
69
69
  });
70
70
 
71
- test("status-based routing (needs input → $SUSPEND)", () => {
72
- const graph: Record<string, Record<string, Target>> = {
73
- ...solveIssueGraph,
74
- reviewer: {
75
- ...solveIssueGraph.reviewer,
76
- needs_input: { role: "$SUSPEND", prompt: "Waiting for user input.", location: null },
77
- },
78
- };
79
- const result = evaluate(graph, "reviewer", { $status: "needs_input" });
80
- expect(result).toEqual({
81
- ok: true,
82
- value: {
83
- action: "suspend",
84
- suspendedRole: "reviewer",
85
- prompt: "Waiting for user input.",
86
- },
87
- });
88
- });
89
-
90
- test("$SUSPEND prompt template renders mustache variables", () => {
91
- const graph: Record<string, Record<string, Target>> = {
92
- reviewer: {
93
- needs_input: {
94
- role: "$SUSPEND",
95
- prompt: "Please clarify: {{{question}}}",
96
- location: null,
97
- },
98
- },
99
- };
100
- const result = evaluate(graph, "reviewer", {
101
- $status: "needs_input",
102
- question: "Which API endpoint?",
103
- });
104
- expect(result).toEqual({
105
- ok: true,
106
- value: {
107
- action: "suspend",
108
- suspendedRole: "reviewer",
109
- prompt: "Please clarify: Which API endpoint?",
110
- },
111
- });
112
- });
113
-
114
71
  test("missing role in graph → error", () => {
115
72
  const result = evaluate(solveIssueGraph, "unknown-role", { $status: "new" });
116
73
  expect(result.ok).toBe(false);
@@ -127,7 +84,7 @@ describe("evaluate", () => {
127
84
  }
128
85
  });
129
86
 
130
- test("mustache template rendering with simple fields", () => {
87
+ test("liquid template rendering with simple fields", () => {
131
88
  const result = evaluate(solveIssueGraph, "planner", {
132
89
  $status: "planned",
133
90
  plan: "Add auth middleware",
@@ -142,7 +99,7 @@ describe("evaluate", () => {
142
99
  });
143
100
  });
144
101
 
145
- test("mustache does not HTML-escape prompt content", () => {
102
+ test("liquid does not HTML-escape prompt content", () => {
146
103
  const result = evaluate(solveIssueGraph, "reviewer", {
147
104
  $status: "rejected",
148
105
  comments: 'use <T> & "Result<T, E>" types',
@@ -153,10 +110,10 @@ describe("evaluate", () => {
153
110
  });
154
111
  });
155
112
 
156
- test("triple mustache also works for unescaped output", () => {
113
+ test("liquid renders HTML content without escaping", () => {
157
114
  const graph: Record<string, Record<string, Target>> = {
158
115
  reviewer: {
159
- rejected: { role: "developer", prompt: "Fix: {{{comments}}}", location: null },
116
+ rejected: { role: "developer", prompt: "Fix: {{ comments }}", location: null },
160
117
  },
161
118
  };
162
119
  const result = evaluate(graph, "reviewer", {
@@ -181,12 +138,12 @@ describe("evaluate", () => {
181
138
  }
182
139
  });
183
140
 
184
- test("mustache template with nested object paths", () => {
141
+ test("liquid template with nested object paths", () => {
185
142
  const graph: Record<string, Record<string, Target>> = {
186
143
  reviewer: {
187
144
  rejected: {
188
145
  role: "developer",
189
- prompt: "Address: {{review.comments}}",
146
+ prompt: "Address: {{ review.comments }}",
190
147
  location: null,
191
148
  },
192
149
  },