@united-workforce/cli 0.3.0 → 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
@@ -12,28 +12,26 @@ import { parse } from "yaml";
12
12
  */
13
13
  describe("solve-issue workflow: Gitea API PR creation", () => {
14
14
  // Navigate up from packages/cli/src/__tests__ to repo root
15
- const workflowPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "..", ".workflows", "solve-issue.yaml");
16
- test("committer procedure should use curl API instead of tea pr create", async () => {
15
+ const workflowPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "..", "examples", "solve-issue.yaml");
16
+ test("committer procedure should create PR via tea pr create", async () => {
17
17
  const yamlContent = await readFile(workflowPath, "utf-8");
18
18
  const workflow = parse(yamlContent);
19
19
  expect(workflow.roles.committer).toBeDefined();
20
20
  const committerProcedure = workflow.roles.committer?.procedure;
21
21
  expect(committerProcedure).toBeDefined();
22
- // Verify the procedure uses curl API, not tea pr create
23
- expect(committerProcedure).toContain("curl");
24
- expect(committerProcedure).toContain("api/v1/repos");
25
- expect(committerProcedure).toContain("/pulls");
26
- // Verify it explicitly warns against tea pr create
27
- expect(committerProcedure).toMatch(/do NOT use.*tea pr create/i);
22
+ // Verify the procedure uses tea pr create for PR creation
23
+ expect(committerProcedure).toContain("tea pr create");
24
+ expect(committerProcedure).toContain("git push");
25
+ expect(committerProcedure).toContain("Fixes #N");
28
26
  });
29
- test("committer procedure should reference repoRemote from task prompt", async () => {
27
+ test("committer procedure should extract owner/repo from git remote", async () => {
30
28
  const yamlContent = await readFile(workflowPath, "utf-8");
31
29
  const workflow = parse(yamlContent);
32
30
  const committerProcedure = workflow.roles.committer?.procedure;
33
31
  expect(committerProcedure).toBeDefined();
34
- // Verify the procedure mentions repoRemote is provided in task prompt
35
- expect(committerProcedure).toMatch(/repo remote.*provided.*task prompt/i);
36
- expect(committerProcedure).toMatch(/owner\/repo/i);
32
+ // Verify the procedure extracts owner/repo from remote
33
+ expect(committerProcedure).toContain("git remote get-url origin");
34
+ expect(committerProcedure).toContain("hook_failed");
37
35
  });
38
36
  test("committer procedure should include error handling for curl failures", async () => {
39
37
  const yamlContent = await readFile(workflowPath, "utf-8");
@@ -72,36 +70,35 @@ describe("solve-issue workflow: Gitea API PR creation", () => {
72
70
  expect(committedVariant).toBeDefined();
73
71
  expect(committedVariant.required).toContain("$status");
74
72
  });
75
- test("developer procedure should include mandatory verification step", async () => {
73
+ test("developer procedure should include worktree setup", async () => {
76
74
  const yamlContent = await readFile(workflowPath, "utf-8");
77
75
  const workflow = parse(yamlContent);
78
76
  const developerProcedure = workflow.roles.developer?.procedure;
79
77
  expect(developerProcedure).toBeDefined();
80
- // Verify the procedure includes mandatory verification step
81
- expect(developerProcedure).toContain("MANDATORY VERIFICATION");
82
- expect(developerProcedure).toContain("git branch --show-current");
83
- expect(developerProcedure).toContain("git status");
84
- expect(developerProcedure).toMatch(/ls -la|verify.*exist/i);
78
+ // Verify the procedure includes worktree setup
79
+ expect(developerProcedure).toContain("IMPORTANT");
80
+ expect(developerProcedure).toContain("git worktree add");
81
+ expect(developerProcedure).toContain("pnpm install");
85
82
  });
86
- test("reviewer procedure should enforce worktree path verification", async () => {
83
+ test("reviewer procedure should verify branch and run checks", async () => {
87
84
  const yamlContent = await readFile(workflowPath, "utf-8");
88
85
  const workflow = parse(yamlContent);
89
86
  const reviewerProcedure = workflow.roles.reviewer?.procedure;
90
87
  expect(reviewerProcedure).toBeDefined();
91
- // Verify the procedure includes critical enforcement
92
- expect(reviewerProcedure).toContain("CRITICAL");
93
- expect(reviewerProcedure).toMatch(/cd.*pwd/);
94
- expect(reviewerProcedure).toContain("Do NOT report results without running the actual commands");
88
+ // Verify the procedure includes branch verification and build checks
89
+ expect(reviewerProcedure).toContain("git branch --show-current");
90
+ expect(reviewerProcedure).toContain("pnpm run build");
91
+ expect(reviewerProcedure).toContain("pnpm run check");
95
92
  });
96
- test("developer procedure should include test debugging escalation", async () => {
93
+ test("developer procedure should include changeset and failure handling", async () => {
97
94
  const yamlContent = await readFile(workflowPath, "utf-8");
98
95
  const workflow = parse(yamlContent);
99
96
  const developerProcedure = workflow.roles.developer?.procedure;
100
97
  expect(developerProcedure).toBeDefined();
101
- // Verify the procedure includes test failure guidance
102
- expect(developerProcedure).toMatch(/tests fail.*first run/i);
103
- expect(developerProcedure).toMatch(/3 test cycles|after 3 attempts/i);
98
+ // Verify the procedure includes changeset requirement and failure path
99
+ expect(developerProcedure).toContain(".changeset/");
104
100
  expect(developerProcedure).toContain("$status=failed");
101
+ expect(developerProcedure).toContain("pnpm test");
105
102
  });
106
103
  });
107
104
  //# sourceMappingURL=solve-issue-tea-worktree.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"solve-issue-tea-worktree.test.js","sourceRoot":"","sources":["../../src/__tests__/solve-issue-tea-worktree.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"solve-issue-tea-worktree.test.js","sourceRoot":"","sources":["../../src/__tests__/solve-issue-tea-worktree.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=step-ask.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-ask.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/step-ask.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,499 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdir, mkdtemp, readFile, 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 { bootstrap, putSchema } from "@ocas/core";
7
+ import { openStore } from "@ocas/fs";
8
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
9
+ import { registerUwfSchemas } from "../schemas.js";
10
+ import { seedThreads } from "./thread-test-helpers.js";
11
+ const OUTPUT_SCHEMA = {
12
+ type: "object",
13
+ properties: {
14
+ $status: { type: "string" },
15
+ note: { type: "string" },
16
+ },
17
+ required: ["$status"],
18
+ additionalProperties: false,
19
+ };
20
+ const DETAIL_SCHEMA = {
21
+ title: "ask-detail",
22
+ type: "object",
23
+ required: ["sessionId", "model", "duration", "turnCount", "turns"],
24
+ properties: {
25
+ sessionId: { type: "string" },
26
+ model: { type: "string" },
27
+ duration: { type: "integer" },
28
+ turnCount: { type: "integer" },
29
+ turns: {
30
+ type: "array",
31
+ items: { type: "string", format: "ocas_ref" },
32
+ },
33
+ },
34
+ additionalProperties: false,
35
+ };
36
+ const THREAD_ID = "01ASKSTEPTEST000000000";
37
+ const STEP_SESSION_ID = "ses-original-step-001";
38
+ let tmpDir;
39
+ beforeEach(async () => {
40
+ tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-ask-test-"));
41
+ });
42
+ afterEach(async () => {
43
+ await rm(tmpDir, { recursive: true, force: true });
44
+ });
45
+ async function setupAskFixture(opts = {}) {
46
+ const cfg = {
47
+ threadStatus: opts.threadStatus ?? "idle",
48
+ withDetail: opts.withDetail ?? true,
49
+ stepAgentNameOverride: opts.stepAgentNameOverride ?? null,
50
+ preCachedForkSessionId: opts.preCachedForkSessionId ?? null,
51
+ };
52
+ const casDir = join(tmpDir, "cas");
53
+ await mkdir(casDir, { recursive: true });
54
+ const store = await openStore(casDir);
55
+ await bootstrap(store);
56
+ const schemas = await registerUwfSchemas(store);
57
+ const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
58
+ const detailSchemaHash = await putSchema(store, DETAIL_SCHEMA);
59
+ const workflowHash = await store.cas.put(schemas.workflow, {
60
+ name: "test-ask",
61
+ description: "ask command integration test",
62
+ roles: {
63
+ worker: {
64
+ description: "Worker",
65
+ goal: "Work",
66
+ capabilities: [],
67
+ procedure: "work",
68
+ output: "result",
69
+ frontmatter: outputSchemaHash,
70
+ },
71
+ },
72
+ graph: {
73
+ $START: {
74
+ new: { role: "worker", prompt: "Start work", location: null },
75
+ },
76
+ worker: { ok: { role: "$END", prompt: "done", location: null } },
77
+ },
78
+ });
79
+ const startHash = await store.cas.put(schemas.startNode, {
80
+ workflow: workflowHash,
81
+ prompt: "Test ask task",
82
+ cwd: tmpDir,
83
+ });
84
+ // Set OCAS_HOME so seedThreads + in-test createUwfStore calls resolve to this CAS dir.
85
+ process.env.OCAS_HOME = casDir;
86
+ // Capture file paths
87
+ const promptCapturePath = join(tmpDir, "captured-prompt.txt");
88
+ const modeCapturePath = join(tmpDir, "captured-mode.txt");
89
+ const forkSessionCapturePath = join(tmpDir, "captured-fork-session.txt");
90
+ const askSessionCapturePath = join(tmpDir, "captured-ask-session.txt");
91
+ const envCapturePath = join(tmpDir, "captured-env.txt");
92
+ const mockAgentPath = join(tmpDir, "mock-agent.sh");
93
+ const failingAgentPath = join(tmpDir, "failing-agent.sh");
94
+ // Build a detail node with sessionId so step ask can extract it
95
+ let detailHash = null;
96
+ if (cfg.withDetail) {
97
+ const turnHash = await store.cas.put(detailSchemaHash, {
98
+ sessionId: STEP_SESSION_ID,
99
+ model: "test-model",
100
+ duration: 1000,
101
+ turnCount: 0,
102
+ turns: [],
103
+ });
104
+ detailHash = turnHash;
105
+ }
106
+ // Build the StepNode at thread head
107
+ const outputHash = await store.cas.put(outputSchemaHash, { $status: "ok" });
108
+ const stepHash = await store.cas.put(schemas.stepNode, {
109
+ start: startHash,
110
+ prev: null,
111
+ role: "worker",
112
+ output: outputHash,
113
+ detail: detailHash,
114
+ agent: cfg.stepAgentNameOverride ?? mockAgentPath,
115
+ edgePrompt: "Start work",
116
+ startedAtMs: 1716600000000,
117
+ completedAtMs: 1716600001000,
118
+ cwd: tmpDir,
119
+ assembledPrompt: null,
120
+ usage: null,
121
+ });
122
+ // Seed thread index entry
123
+ await seedThreads(tmpDir, {
124
+ [THREAD_ID]: {
125
+ head: stepHash,
126
+ status: cfg.threadStatus,
127
+ suspendedRole: null,
128
+ suspendMessage: null,
129
+ completedAt: cfg.threadStatus === "end" ? 1716600001000 : null,
130
+ },
131
+ });
132
+ // Pre-seed the ask session cache so reuse tests have something to find.
133
+ if (cfg.preCachedForkSessionId !== null) {
134
+ const cachePath = join(tmpDir, "cache", "mock-sessions.json");
135
+ await mkdir(dirname(cachePath), { recursive: true });
136
+ await writeFile(cachePath, `${JSON.stringify({ [`${stepHash}:ask`]: cfg.preCachedForkSessionId }, null, 2)}\n`, "utf8");
137
+ }
138
+ // Mock agent: dispatches based on `--mode` (ask|fork|run) and captures inputs.
139
+ // - --mode ask --session <id> --prompt <text>: writes to ask capture; echoes a fixed answer to stdout
140
+ // - --mode fork --session <id>: writes to fork capture; prints "forked-from-<id>" sessionId on stdout
141
+ // - default (uwf-* style invocation): captures and echoes adapter JSON (not used in this suite)
142
+ await writeFile(mockAgentPath, `#!/bin/sh
143
+ mode=""
144
+ prompt=""
145
+ session=""
146
+ detail=""
147
+ while [ $# -gt 0 ]; do
148
+ case "$1" in
149
+ --mode) mode="$2"; shift 2 ;;
150
+ --prompt) prompt="$2"; shift 2 ;;
151
+ --session) session="$2"; shift 2 ;;
152
+ --detail) detail="$2"; shift 2 ;;
153
+ *) shift ;;
154
+ esac
155
+ done
156
+ printf '%s' "$mode" > '${modeCapturePath}'
157
+ printf '%s' "$prompt" > '${promptCapturePath}'
158
+ printf 'OCAS_HOME=%s\\n' "$OCAS_HOME" > '${envCapturePath}'
159
+ case "$mode" in
160
+ fork)
161
+ printf '%s' "$session" > '${forkSessionCapturePath}'
162
+ new_id="forked-from-$session"
163
+ printf '%s\\n' "$new_id"
164
+ ;;
165
+ ask)
166
+ printf '%s' "$session" > '${askSessionCapturePath}'
167
+ # Print a deterministic answer that the cmdStepAsk path will hand back.
168
+ printf 'MOCK_ANSWER prompt=%s session=%s detail=%s\\n' "$prompt" "$session" "$detail"
169
+ ;;
170
+ *)
171
+ echo "{\\"stepHash\\":\\"unused\\"}"
172
+ ;;
173
+ esac
174
+ `, { mode: 0o755 });
175
+ await writeFile(failingAgentPath, `#!/bin/sh
176
+ echo "boom" >&2
177
+ exit 7
178
+ `, { mode: 0o755 });
179
+ // Minimal config so loadWorkflowConfig succeeds.
180
+ const configPath = join(tmpDir, "config.yaml");
181
+ await writeFile(configPath, `defaultAgent: uwf-hermes\nagentOverrides: null\nagents:\n uwf-hermes:\n command: uwf-hermes\n`);
182
+ return {
183
+ casDir,
184
+ stepHash,
185
+ startHash,
186
+ workflowHash,
187
+ detailHash,
188
+ mockAgentPath,
189
+ failingAgentPath,
190
+ promptCapturePath,
191
+ modeCapturePath,
192
+ forkSessionCapturePath,
193
+ askSessionCapturePath,
194
+ envCapturePath,
195
+ };
196
+ }
197
+ function runUwf(args, casDir) {
198
+ const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
199
+ try {
200
+ const stdout = execFileSync(process.execPath, [cliPath, ...args], {
201
+ encoding: "utf8",
202
+ stdio: ["ignore", "pipe", "pipe"],
203
+ env: {
204
+ ...process.env,
205
+ UWF_HOME: tmpDir,
206
+ OCAS_HOME: casDir,
207
+ },
208
+ cwd: tmpDir,
209
+ timeout: 30000,
210
+ });
211
+ return { stdout, stderr: "", status: 0 };
212
+ }
213
+ catch (error) {
214
+ const err = error;
215
+ return {
216
+ stdout: typeof err.stdout === "string" ? err.stdout : (err.stdout?.toString("utf8") ?? ""),
217
+ stderr: typeof err.stderr === "string" ? err.stderr : (err.stderr?.toString("utf8") ?? ""),
218
+ status: err.status ?? 1,
219
+ };
220
+ }
221
+ }
222
+ // ── Group 1: CLI argument validation ───────────────────────────────────────
223
+ describe("uwf step ask - CLI argument validation", () => {
224
+ test("1.1 missing step-hash exits non-zero", async () => {
225
+ const { casDir } = await setupAskFixture();
226
+ const result = runUwf(["step", "ask"], casDir);
227
+ expect(result.status).not.toBe(0);
228
+ });
229
+ test("1.2 missing -p flag exits non-zero", async () => {
230
+ const { casDir, stepHash } = await setupAskFixture();
231
+ const result = runUwf(["step", "ask", stepHash], casDir);
232
+ expect(result.status).not.toBe(0);
233
+ expect(result.stderr.toLowerCase()).toMatch(/required|missing|prompt/);
234
+ });
235
+ test("1.3 step-hash and -p accepted as valid invocation", async () => {
236
+ const { casDir, stepHash, mockAgentPath } = await setupAskFixture();
237
+ const result = runUwf(["step", "ask", stepHash, "-p", "why?", "--agent", mockAgentPath], casDir);
238
+ expect(result.status).toBe(0);
239
+ });
240
+ });
241
+ // ── Group 2: CAS validation errors ────────────────────────────────────────
242
+ describe("uwf step ask - CAS validation errors", () => {
243
+ test("2.1 non-existent CAS hash exits non-zero with 'not found'", async () => {
244
+ const { casDir, mockAgentPath } = await setupAskFixture();
245
+ const result = runUwf(["step", "ask", "0000000000000", "-p", "why?", "--agent", mockAgentPath], casDir);
246
+ expect(result.status).not.toBe(0);
247
+ expect(result.stderr.toLowerCase()).toContain("not found");
248
+ });
249
+ test("2.2 hash that is not a StepNode exits non-zero", async () => {
250
+ const { casDir, startHash, mockAgentPath } = await setupAskFixture();
251
+ // Use the StartNode hash — it exists but is not a StepNode
252
+ const result = runUwf(["step", "ask", startHash, "-p", "why?", "--agent", mockAgentPath], casDir);
253
+ expect(result.status).not.toBe(0);
254
+ expect(result.stderr.toLowerCase()).toContain("not a stepnode");
255
+ });
256
+ test("2.3 step with no detail ref exits non-zero", async () => {
257
+ const { casDir, stepHash, mockAgentPath } = await setupAskFixture({ withDetail: false });
258
+ const result = runUwf(["step", "ask", stepHash, "-p", "why?", "--agent", mockAgentPath], casDir);
259
+ expect(result.status).not.toBe(0);
260
+ expect(result.stderr.toLowerCase()).toMatch(/no detail|detail.*missing|missing.*detail/);
261
+ });
262
+ });
263
+ // ── Group 3: Successful ask (core behavior) ───────────────────────────────
264
+ describe("uwf step ask - successful ask (core)", () => {
265
+ test("3.1 stdout contains agent's response text", async () => {
266
+ const { casDir, stepHash, mockAgentPath } = await setupAskFixture();
267
+ const result = runUwf(["step", "ask", stepHash, "-p", "why tar not zip?", "--agent", mockAgentPath], casDir);
268
+ expect(result.status).toBe(0);
269
+ expect(result.stdout).toContain("MOCK_ANSWER");
270
+ expect(result.stdout).toContain("why tar not zip?");
271
+ });
272
+ test("3.2 thread index entry (head, status) is identical before and after ask", async () => {
273
+ const { casDir, stepHash, mockAgentPath } = await setupAskFixture();
274
+ // Before ask: snapshot the thread state
275
+ const { createUwfStore, getThread } = await import("../store.js");
276
+ const before = await createUwfStore(tmpDir);
277
+ const beforeEntry = getThread(before.varStore, THREAD_ID);
278
+ expect(beforeEntry).not.toBeNull();
279
+ const result = runUwf(["step", "ask", stepHash, "-p", "anything", "--agent", mockAgentPath], casDir);
280
+ expect(result.status).toBe(0);
281
+ // After ask: thread state should be unchanged
282
+ const after = await createUwfStore(tmpDir);
283
+ const afterEntry = getThread(after.varStore, THREAD_ID);
284
+ expect(afterEntry).not.toBeNull();
285
+ expect(afterEntry?.head).toBe(beforeEntry?.head);
286
+ expect(afterEntry?.status).toBe(beforeEntry?.status);
287
+ });
288
+ test("3.3 no new StepNode is written to CAS (step count unchanged)", async () => {
289
+ const { casDir, stepHash, mockAgentPath } = await setupAskFixture();
290
+ // Count StepNodes before
291
+ const { createUwfStore } = await import("../store.js");
292
+ const before = await createUwfStore(tmpDir);
293
+ const stepSchemaHash = before.schemas.stepNode;
294
+ function countStepNodes(uwfStore) {
295
+ const candidates = [stepHash];
296
+ let count = 0;
297
+ for (const h of candidates) {
298
+ const node = uwfStore.store.cas.get(h);
299
+ if (node !== null && node.type === stepSchemaHash)
300
+ count++;
301
+ }
302
+ return count;
303
+ }
304
+ const beforeCount = countStepNodes(before);
305
+ expect(beforeCount).toBe(1);
306
+ const result = runUwf(["step", "ask", stepHash, "-p", "anything", "--agent", mockAgentPath], casDir);
307
+ expect(result.status).toBe(0);
308
+ // After ask: still only the seeded StepNode exists at head; no new step appended.
309
+ const after = await createUwfStore(tmpDir);
310
+ const headNode = after.store.cas.get(stepHash);
311
+ expect(headNode).not.toBeNull();
312
+ expect(headNode?.type).toBe(after.schemas.stepNode);
313
+ // Confirm thread head still points to the original step hash
314
+ const { getThread } = await import("../store.js");
315
+ const entry = getThread(after.varStore, THREAD_ID);
316
+ expect(entry?.head).toBe(stepHash);
317
+ });
318
+ });
319
+ // ── Group 4: Fork cache semantics ─────────────────────────────────────────
320
+ describe("uwf step ask - fork cache", { timeout: 15_000 }, () => {
321
+ test("4.1 first ask creates a fork session and caches it", async () => {
322
+ const { casDir, stepHash, mockAgentPath, forkSessionCapturePath } = await setupAskFixture();
323
+ const result = runUwf(["step", "ask", stepHash, "-p", "first ask", "--agent", mockAgentPath], casDir);
324
+ expect(result.status).toBe(0);
325
+ // The mock agent in fork mode receives the source session id
326
+ const forkArg = await readFile(forkSessionCapturePath, "utf8");
327
+ expect(forkArg).toBe(STEP_SESSION_ID);
328
+ // Cache file should now contain the ask key
329
+ const cachePath = join(tmpDir, "cache", "mock-sessions.json");
330
+ const raw = await readFile(cachePath, "utf8");
331
+ const parsed = JSON.parse(raw);
332
+ expect(parsed[`${stepHash}:ask`]).toBeDefined();
333
+ expect(parsed[`${stepHash}:ask`]).toBe(`forked-from-${STEP_SESSION_ID}`);
334
+ });
335
+ test("4.2 second ask on same step reuses the cached fork session", async () => {
336
+ const cachedFork = "ses-already-forked-once";
337
+ const { casDir, stepHash, mockAgentPath, modeCapturePath, askSessionCapturePath } = await setupAskFixture({ preCachedForkSessionId: cachedFork });
338
+ const result = runUwf(["step", "ask", stepHash, "-p", "second ask", "--agent", mockAgentPath], casDir);
339
+ expect(result.status).toBe(0);
340
+ // The mock agent must have been invoked in `ask` mode (no fork performed).
341
+ const mode = await readFile(modeCapturePath, "utf8");
342
+ expect(mode).toBe("ask");
343
+ // The ask invocation should have received the cached fork session id.
344
+ const askArg = await readFile(askSessionCapturePath, "utf8");
345
+ expect(askArg).toBe(cachedFork);
346
+ });
347
+ test("4.3 different step hash creates an independent fork", async () => {
348
+ // Run a first ask on the base step → caches forkA
349
+ const { casDir, stepHash, mockAgentPath } = await setupAskFixture();
350
+ const r1 = runUwf(["step", "ask", stepHash, "-p", "ask on step A", "--agent", mockAgentPath], casDir);
351
+ expect(r1.status).toBe(0);
352
+ // Build a second StepNode (different hash) with a different sessionId so
353
+ // its detail-derived ask session is independent of the first.
354
+ const { createUwfStore } = await import("../store.js");
355
+ const uwf = await createUwfStore(tmpDir);
356
+ const detailSchemaHash = await putSchema(uwf.store, DETAIL_SCHEMA);
357
+ const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
358
+ const otherDetailHash = await uwf.store.cas.put(detailSchemaHash, {
359
+ sessionId: "ses-original-step-002",
360
+ model: "test-model",
361
+ duration: 1000,
362
+ turnCount: 0,
363
+ turns: [],
364
+ });
365
+ const otherOutputHash = await uwf.store.cas.put(outputSchemaHash, {
366
+ $status: "ok",
367
+ note: "alt",
368
+ });
369
+ // Reuse the same start ref the first step points to so the new step is a valid sibling.
370
+ const head = uwf.store.cas.get(stepHash);
371
+ const startRefFromHead = (head?.payload).start;
372
+ const properOtherStep = await uwf.store.cas.put(uwf.schemas.stepNode, {
373
+ start: startRefFromHead,
374
+ prev: null,
375
+ role: "worker",
376
+ output: otherOutputHash,
377
+ detail: otherDetailHash,
378
+ agent: mockAgentPath,
379
+ edgePrompt: "Start work",
380
+ startedAtMs: 1716600002000,
381
+ completedAtMs: 1716600003000,
382
+ cwd: tmpDir,
383
+ assembledPrompt: null,
384
+ usage: null,
385
+ });
386
+ // sanity check we constructed a separate hash
387
+ expect(properOtherStep).not.toBe(stepHash);
388
+ const r2 = runUwf(["step", "ask", properOtherStep, "-p", "ask on step B", "--agent", mockAgentPath], casDir);
389
+ expect(r2.status).toBe(0);
390
+ const cachePath = join(tmpDir, "cache", "mock-sessions.json");
391
+ const raw = await readFile(cachePath, "utf8");
392
+ const parsed = JSON.parse(raw);
393
+ expect(parsed[`${stepHash}:ask`]).toBeDefined();
394
+ expect(parsed[`${properOtherStep}:ask`]).toBeDefined();
395
+ expect(parsed[`${stepHash}:ask`]).not.toBe(parsed[`${properOtherStep}:ask`]);
396
+ });
397
+ });
398
+ // ── Group 5: Fallback (agent has no fork support) ─────────────────────────
399
+ describe("uwf step ask - fallback path", () => {
400
+ test("5.1 fallback agent (no fork support) still answers via stdout", async () => {
401
+ // Use a fallback agent that ONLY supports `ask` mode without ever being asked
402
+ // to fork. The CLI should detect missing fork support and inject context instead.
403
+ const { casDir, stepHash, mockAgentPath } = await setupAskFixture();
404
+ // Create a fallback agent script that fails with non-zero exit on "fork" mode.
405
+ // Fallback path must NOT call mode=fork; it should call mode=ask directly.
406
+ const fallbackPath = join(tmpDir, "fallback-agent.sh");
407
+ const promptCapture = join(tmpDir, "fallback-prompt.txt");
408
+ const sessionCapture = join(tmpDir, "fallback-session.txt");
409
+ const modeCapture = join(tmpDir, "fallback-mode.txt");
410
+ await writeFile(fallbackPath, `#!/bin/sh
411
+ mode=""
412
+ prompt=""
413
+ session=""
414
+ detail=""
415
+ while [ $# -gt 0 ]; do
416
+ case "$1" in
417
+ --mode) mode="$2"; shift 2 ;;
418
+ --prompt) prompt="$2"; shift 2 ;;
419
+ --session) session="$2"; shift 2 ;;
420
+ --detail) detail="$2"; shift 2 ;;
421
+ *) shift ;;
422
+ esac
423
+ done
424
+ printf '%s' "$mode" > '${modeCapture}'
425
+ printf '%s' "$prompt" > '${promptCapture}'
426
+ printf '%s' "$session" > '${sessionCapture}'
427
+ case "$mode" in
428
+ fork) echo "fork not supported" >&2; exit 99 ;;
429
+ ask) printf 'FALLBACK_ANSWER for: %s (detail=%s)\\n' "$prompt" "$detail" ;;
430
+ *) echo "unknown" >&2; exit 1 ;;
431
+ esac
432
+ `, { mode: 0o755 });
433
+ const result = runUwf(["step", "ask", stepHash, "-p", "explain context", "--agent", fallbackPath, "--no-fork"], casDir);
434
+ expect(result.status).toBe(0);
435
+ expect(result.stdout).toContain("FALLBACK_ANSWER");
436
+ expect(result.stdout).toContain("explain context");
437
+ // The fallback agent should be invoked in `ask` mode, with NO session id
438
+ // (since no fork happened). The detail ref must be passed for context injection.
439
+ const mode = await readFile(modeCapture, "utf8");
440
+ expect(mode).toBe("ask");
441
+ const session = await readFile(sessionCapture, "utf8");
442
+ expect(session).toBe("");
443
+ // Make sure mockAgentPath's mock never ran.
444
+ void mockAgentPath;
445
+ });
446
+ test("5.2 fallback ask still does NOT mutate thread state", async () => {
447
+ const { casDir, stepHash } = await setupAskFixture();
448
+ const fallbackPath = join(tmpDir, "fallback-agent.sh");
449
+ await writeFile(fallbackPath, `#!/bin/sh
450
+ mode=""
451
+ prompt=""
452
+ while [ $# -gt 0 ]; do
453
+ case "$1" in
454
+ --mode) mode="$2"; shift 2 ;;
455
+ --prompt) prompt="$2"; shift 2 ;;
456
+ *) shift ;;
457
+ esac
458
+ done
459
+ case "$mode" in
460
+ fork) echo "fork not supported" >&2; exit 99 ;;
461
+ ask) printf 'OK %s\\n' "$prompt" ;;
462
+ *) exit 1 ;;
463
+ esac
464
+ `, { mode: 0o755 });
465
+ const { createUwfStore, getThread } = await import("../store.js");
466
+ const before = await createUwfStore(tmpDir);
467
+ const beforeEntry = getThread(before.varStore, THREAD_ID);
468
+ const result = runUwf(["step", "ask", stepHash, "-p", "any", "--agent", fallbackPath, "--no-fork"], casDir);
469
+ expect(result.status).toBe(0);
470
+ const after = await createUwfStore(tmpDir);
471
+ const afterEntry = getThread(after.varStore, THREAD_ID);
472
+ expect(afterEntry?.head).toBe(beforeEntry?.head);
473
+ expect(afterEntry?.status).toBe(beforeEntry?.status);
474
+ });
475
+ });
476
+ // ── Group 6: Agent resolution ─────────────────────────────────────────────
477
+ describe("uwf step ask - agent resolution", () => {
478
+ test("6.1 without --agent flag, agent is resolved from step's agent field", async () => {
479
+ // Step's agent field points at mockAgentPath by default.
480
+ const { casDir, stepHash, modeCapturePath, promptCapturePath } = await setupAskFixture();
481
+ const result = runUwf(["step", "ask", stepHash, "-p", "explain"], casDir);
482
+ expect(result.status).toBe(0);
483
+ // The mockAgentPath must have been invoked in ask mode with the user prompt.
484
+ const mode = await readFile(modeCapturePath, "utf8");
485
+ expect(mode).toBe("ask");
486
+ const captured = await readFile(promptCapturePath, "utf8");
487
+ expect(captured).toBe("explain");
488
+ });
489
+ test("6.2 --agent override beats step's recorded agent", async () => {
490
+ // Record a non-existent agent in step.agent. Provide a working one via --agent.
491
+ const { casDir, stepHash, mockAgentPath } = await setupAskFixture({
492
+ stepAgentNameOverride: "uwf-does-not-exist",
493
+ });
494
+ const result = runUwf(["step", "ask", stepHash, "-p", "explain", "--agent", mockAgentPath], casDir);
495
+ expect(result.status).toBe(0);
496
+ expect(result.stdout).toContain("MOCK_ANSWER");
497
+ });
498
+ });
499
+ //# sourceMappingURL=step-ask.test.js.map