@united-workforce/cli 0.6.1 → 0.8.1

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 (167) hide show
  1. package/README.md +120 -5
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/__tests__/agent-resolution-llm-free.test.js +9 -2
  4. package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -1
  5. package/dist/__tests__/broker-prompt.test.d.ts +10 -0
  6. package/dist/__tests__/broker-prompt.test.d.ts.map +1 -0
  7. package/dist/__tests__/broker-prompt.test.js +129 -0
  8. package/dist/__tests__/broker-prompt.test.js.map +1 -0
  9. package/dist/__tests__/broker-step-active-turns.test.d.ts +20 -0
  10. package/dist/__tests__/broker-step-active-turns.test.d.ts.map +1 -0
  11. package/dist/__tests__/broker-step-active-turns.test.js +428 -0
  12. package/dist/__tests__/broker-step-active-turns.test.js.map +1 -0
  13. package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts +13 -0
  14. package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts.map +1 -0
  15. package/dist/__tests__/broker-step-turn-chain-phase2.test.js +429 -0
  16. package/dist/__tests__/broker-step-turn-chain-phase2.test.js.map +1 -0
  17. package/dist/__tests__/config.test.js +33 -37
  18. package/dist/__tests__/config.test.js.map +1 -1
  19. package/dist/__tests__/e2e-broker-step-suspend.test.d.ts +18 -0
  20. package/dist/__tests__/e2e-broker-step-suspend.test.d.ts.map +1 -0
  21. package/dist/__tests__/e2e-broker-step-suspend.test.js +313 -0
  22. package/dist/__tests__/e2e-broker-step-suspend.test.js.map +1 -0
  23. package/dist/__tests__/e2e-broker-step.test.d.ts +13 -0
  24. package/dist/__tests__/e2e-broker-step.test.d.ts.map +1 -0
  25. package/dist/__tests__/e2e-broker-step.test.js +278 -0
  26. package/dist/__tests__/e2e-broker-step.test.js.map +1 -0
  27. package/dist/__tests__/e2e-mock-agent.test.js +1 -1
  28. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
  29. package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts +28 -0
  30. package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts.map +1 -0
  31. package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js +322 -0
  32. package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js.map +1 -0
  33. package/dist/__tests__/log-tag-validity.test.d.ts +2 -0
  34. package/dist/__tests__/log-tag-validity.test.d.ts.map +1 -0
  35. package/dist/__tests__/log-tag-validity.test.js +110 -0
  36. package/dist/__tests__/log-tag-validity.test.js.map +1 -0
  37. package/dist/__tests__/setup-agent-discovery.test.js +35 -23
  38. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
  39. package/dist/__tests__/setup-no-llm.test.js +5 -2
  40. package/dist/__tests__/setup-no-llm.test.js.map +1 -1
  41. package/dist/__tests__/step-ask.test.js +9 -6
  42. package/dist/__tests__/step-ask.test.js.map +1 -1
  43. package/dist/__tests__/step-show-json.test.js +5 -5
  44. package/dist/__tests__/step-show-json.test.js.map +1 -1
  45. package/dist/__tests__/step-show-text.test.d.ts +2 -0
  46. package/dist/__tests__/step-show-text.test.d.ts.map +1 -0
  47. package/dist/__tests__/step-show-text.test.js +192 -0
  48. package/dist/__tests__/step-show-text.test.js.map +1 -0
  49. package/dist/__tests__/step-turns-cli-subprocess.test.d.ts +21 -0
  50. package/dist/__tests__/step-turns-cli-subprocess.test.d.ts.map +1 -0
  51. package/dist/__tests__/step-turns-cli-subprocess.test.js +356 -0
  52. package/dist/__tests__/step-turns-cli-subprocess.test.js.map +1 -0
  53. package/dist/__tests__/step-turns-panorama-phase3.test.d.ts +21 -0
  54. package/dist/__tests__/step-turns-panorama-phase3.test.d.ts.map +1 -0
  55. package/dist/__tests__/step-turns-panorama-phase3.test.js +476 -0
  56. package/dist/__tests__/step-turns-panorama-phase3.test.js.map +1 -0
  57. package/dist/__tests__/step-turns.test.d.ts +24 -0
  58. package/dist/__tests__/step-turns.test.d.ts.map +1 -0
  59. package/dist/__tests__/step-turns.test.js +646 -0
  60. package/dist/__tests__/step-turns.test.js.map +1 -0
  61. package/dist/__tests__/store-turn-chain.test.d.ts +2 -0
  62. package/dist/__tests__/store-turn-chain.test.d.ts.map +1 -0
  63. package/dist/__tests__/store-turn-chain.test.js +341 -0
  64. package/dist/__tests__/store-turn-chain.test.js.map +1 -0
  65. package/dist/__tests__/thread-agent-failure-suspended.test.js +3 -3
  66. package/dist/__tests__/thread-agent-failure-suspended.test.js.map +1 -1
  67. package/dist/__tests__/thread-list-limit-offset.test.d.ts +24 -0
  68. package/dist/__tests__/thread-list-limit-offset.test.d.ts.map +1 -0
  69. package/dist/__tests__/thread-list-limit-offset.test.js +254 -0
  70. package/dist/__tests__/thread-list-limit-offset.test.js.map +1 -0
  71. package/dist/__tests__/thread-list-template-ms-date.test.js +7 -2
  72. package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -1
  73. package/dist/__tests__/thread-poke.test.js +6 -6
  74. package/dist/__tests__/thread-poke.test.js.map +1 -1
  75. package/dist/__tests__/thread-resume.test.js +2 -2
  76. package/dist/__tests__/thread-resume.test.js.map +1 -1
  77. package/dist/__tests__/thread-suspend-step.test.js +1 -1
  78. package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
  79. package/dist/__tests__/thread.test.js +28 -14
  80. package/dist/__tests__/thread.test.js.map +1 -1
  81. package/dist/cli.js +910 -344
  82. package/dist/cli.js.map +1 -1
  83. package/dist/commands/broker-step.d.ts +117 -0
  84. package/dist/commands/broker-step.d.ts.map +1 -0
  85. package/dist/commands/broker-step.js +654 -0
  86. package/dist/commands/broker-step.js.map +1 -0
  87. package/dist/commands/config.d.ts.map +1 -1
  88. package/dist/commands/config.js +2 -23
  89. package/dist/commands/config.js.map +1 -1
  90. package/dist/commands/prompt.d.ts.map +1 -1
  91. package/dist/commands/prompt.js +43 -51
  92. package/dist/commands/prompt.js.map +1 -1
  93. package/dist/commands/setup.d.ts +6 -4
  94. package/dist/commands/setup.d.ts.map +1 -1
  95. package/dist/commands/setup.js +24 -27
  96. package/dist/commands/setup.js.map +1 -1
  97. package/dist/commands/step.d.ts +54 -6
  98. package/dist/commands/step.d.ts.map +1 -1
  99. package/dist/commands/step.js +484 -134
  100. package/dist/commands/step.js.map +1 -1
  101. package/dist/commands/thread.d.ts +4 -0
  102. package/dist/commands/thread.d.ts.map +1 -1
  103. package/dist/commands/thread.js +77 -151
  104. package/dist/commands/thread.js.map +1 -1
  105. package/dist/output-mappers.d.ts +8 -0
  106. package/dist/output-mappers.d.ts.map +1 -1
  107. package/dist/output-mappers.js +72 -18
  108. package/dist/output-mappers.js.map +1 -1
  109. package/dist/schemas.d.ts +3 -0
  110. package/dist/schemas.d.ts.map +1 -1
  111. package/dist/schemas.js +17 -3
  112. package/dist/schemas.js.map +1 -1
  113. package/dist/store.d.ts +147 -1
  114. package/dist/store.d.ts.map +1 -1
  115. package/dist/store.js +254 -1
  116. package/dist/store.js.map +1 -1
  117. package/dist/text-renderers.d.ts.map +1 -1
  118. package/dist/text-renderers.js +27 -2
  119. package/dist/text-renderers.js.map +1 -1
  120. package/package.json +7 -5
  121. package/src/__tests__/agent-resolution-llm-free.test.ts +14 -2
  122. package/src/__tests__/broker-prompt.test.ts +142 -0
  123. package/src/__tests__/broker-step-active-turns.test.ts +509 -0
  124. package/src/__tests__/broker-step-turn-chain-phase2.test.ts +525 -0
  125. package/src/__tests__/config.test.ts +35 -39
  126. package/src/__tests__/e2e-broker-step-suspend.test.ts +351 -0
  127. package/src/__tests__/e2e-broker-step.test.ts +320 -0
  128. package/src/__tests__/e2e-mock-agent.test.ts +1 -1
  129. package/src/__tests__/e2e-thread-resume-timeout-suspend.test.ts +360 -0
  130. package/src/__tests__/log-tag-validity.test.ts +124 -0
  131. package/src/__tests__/setup-agent-discovery.test.ts +35 -23
  132. package/src/__tests__/setup-no-llm.test.ts +5 -2
  133. package/src/__tests__/step-ask.test.ts +9 -6
  134. package/src/__tests__/step-show-json.test.ts +5 -5
  135. package/src/__tests__/step-show-text.test.ts +236 -0
  136. package/src/__tests__/step-turns-cli-subprocess.test.ts +411 -0
  137. package/src/__tests__/step-turns-panorama-phase3.test.ts +579 -0
  138. package/src/__tests__/step-turns.test.ts +734 -0
  139. package/src/__tests__/store-turn-chain.test.ts +386 -0
  140. package/src/__tests__/thread-agent-failure-suspended.test.ts +3 -3
  141. package/src/__tests__/thread-list-limit-offset.test.ts +305 -0
  142. package/src/__tests__/thread-list-template-ms-date.test.ts +7 -2
  143. package/src/__tests__/thread-poke.test.ts +6 -6
  144. package/src/__tests__/thread-resume.test.ts +2 -2
  145. package/src/__tests__/thread-suspend-step.test.ts +1 -1
  146. package/src/__tests__/thread.test.ts +29 -15
  147. package/src/cli.ts +1056 -483
  148. package/src/commands/broker-step.ts +913 -0
  149. package/src/commands/config.ts +2 -24
  150. package/src/commands/prompt.ts +43 -51
  151. package/src/commands/setup.ts +25 -29
  152. package/src/commands/step.ts +645 -176
  153. package/src/commands/thread.ts +87 -192
  154. package/src/output-mappers.ts +99 -21
  155. package/src/schemas.ts +32 -2
  156. package/src/store.ts +297 -2
  157. package/src/text-renderers.ts +35 -2
  158. package/dist/__tests__/adapter-json-roundtrip.test.d.ts +0 -2
  159. package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +0 -1
  160. package/dist/__tests__/adapter-json-roundtrip.test.js +0 -160
  161. package/dist/__tests__/adapter-json-roundtrip.test.js.map +0 -1
  162. package/dist/__tests__/spawn-agent-json.test.d.ts +0 -2
  163. package/dist/__tests__/spawn-agent-json.test.d.ts.map +0 -1
  164. package/dist/__tests__/spawn-agent-json.test.js +0 -79
  165. package/dist/__tests__/spawn-agent-json.test.js.map +0 -1
  166. package/src/__tests__/adapter-json-roundtrip.test.ts +0 -193
  167. package/src/__tests__/spawn-agent-json.test.ts +0 -100
@@ -0,0 +1,429 @@
1
+ /**
2
+ * Phase 2 (#419) — Turn chain with prev+owner fields and thread-keyed active vars.
3
+ *
4
+ * Covers the spec acceptance scenarios:
5
+ * 1. onTurn writes each turn with prev pointer and owner reference
6
+ * 2. Step-start/step-complete dual node lifecycle
7
+ * 3. Same role multi-round ownership (#412 regression test)
8
+ * 4. Thread-keyed active vars (not role-keyed)
9
+ * 5. Crash recovery isolation (new attempt gets new step-start)
10
+ * 6. Detail node has no turns array (turns self-contained via chain)
11
+ */
12
+ import { mkdtemp, rm } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { putSchema } from "@ocas/core";
16
+ import { createProcessLogger } from "@united-workforce/util";
17
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
18
+ import { executeBrokerStep } from "../commands/broker-step.js";
19
+ import { ACTIVE_TURNS_VAR_PREFIX, activeStepVarName, activeTurnHeadVarName, createUwfStore, getActiveStep, getActiveTurnHead, turnsOfStep, walkTurnChain, writeStepStart, writeTurnNode, } from "../store.js";
20
+ // ── SSE plumbing ─────────────────────────────────────────────────────────────
21
+ function sseFrame(id, event, data) {
22
+ return `id: ${id}\nevent: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
23
+ }
24
+ function turnFrame(id, index, content) {
25
+ return sseFrame(id, "turn", {
26
+ type: "@sumeru/turn",
27
+ value: { index, role: "assistant", content, timestamp: "", toolCalls: null },
28
+ });
29
+ }
30
+ function doneFrame(id, turnCount) {
31
+ return sseFrame(id, "done", {
32
+ type: "@sumeru/summary",
33
+ value: { turnCount, tokens: { in: 9, out: 4 }, durationMs: 42 },
34
+ });
35
+ }
36
+ function delay(ms) {
37
+ return new Promise((resolve) => setTimeout(resolve, ms));
38
+ }
39
+ const PER_TURN_MS = 40;
40
+ function buildPacedSseResponse(frames) {
41
+ const encoder = new TextEncoder();
42
+ let cancelled = false;
43
+ const stream = new ReadableStream({
44
+ start(controller) {
45
+ void (async () => {
46
+ try {
47
+ for (const frame of frames) {
48
+ if (cancelled)
49
+ return;
50
+ controller.enqueue(encoder.encode(frame));
51
+ await delay(PER_TURN_MS);
52
+ }
53
+ if (!cancelled)
54
+ controller.close();
55
+ }
56
+ catch {
57
+ // Consumer closed/cancelled the stream first
58
+ }
59
+ })();
60
+ },
61
+ cancel() {
62
+ cancelled = true;
63
+ },
64
+ });
65
+ return new Response(stream, {
66
+ status: 200,
67
+ headers: { "Content-Type": "text/event-stream; charset=utf-8" },
68
+ });
69
+ }
70
+ function buildJsonResponse(status, body) {
71
+ return new Response(JSON.stringify(body), {
72
+ status,
73
+ headers: { "Content-Type": "application/json" },
74
+ });
75
+ }
76
+ // ── Fixture ──────────────────────────────────────────────────────────────────
77
+ const ROLE_OUTPUT_SCHEMA = {
78
+ title: "coder-output",
79
+ type: "object",
80
+ required: ["$status"],
81
+ properties: {
82
+ $status: { type: "string", enum: ["done", "failed"] },
83
+ summary: { type: "string" },
84
+ },
85
+ additionalProperties: false,
86
+ };
87
+ const FINAL_TURN = `---
88
+ $status: done
89
+ summary: shipped
90
+ ---
91
+ the final answer`;
92
+ const HOST = "http://127.0.0.1:7900";
93
+ const GATEWAY = "coder-gw";
94
+ const ALIAS = "coder-agent";
95
+ const SESSION_ID = "ses_turn_chain_phase2";
96
+ const THREAD_ID = "06FDTURNCHAINPHASE2TEST01";
97
+ const ROLE = "coder";
98
+ function buildConfig() {
99
+ return {
100
+ agents: { [ALIAS]: { host: HOST, gateway: GATEWAY } },
101
+ defaultAgent: ALIAS,
102
+ agentOverrides: null,
103
+ };
104
+ }
105
+ async function buildWorkflow(uwf) {
106
+ const frontmatterHash = (await putSchema(uwf.store, ROLE_OUTPUT_SCHEMA));
107
+ const workflow = {
108
+ version: 1,
109
+ name: "turn-chain-wf",
110
+ description: "phase2 turn chain",
111
+ roles: {
112
+ [ROLE]: {
113
+ description: "writes code",
114
+ goal: "produce a change",
115
+ capabilities: [],
116
+ procedure: "do the work",
117
+ output: "frontmatter+body",
118
+ frontmatter: frontmatterHash,
119
+ },
120
+ },
121
+ graph: {
122
+ [ROLE]: {
123
+ done: { role: "$END", prompt: "", location: null },
124
+ },
125
+ },
126
+ };
127
+ const startHash = (await uwf.store.cas.put(uwf.schemas.startNode, {
128
+ workflow: await uwf.store.cas.put(uwf.schemas.workflow, workflow),
129
+ prompt: "task",
130
+ cwd: "/tmp/work",
131
+ }));
132
+ return { workflow, startHash };
133
+ }
134
+ function resolveFetchUrl(input) {
135
+ if (typeof input === "string")
136
+ return input;
137
+ if (input instanceof URL)
138
+ return input.href;
139
+ return input.url;
140
+ }
141
+ function runStep(uwf, workflow, startHash, tmpDir, prevHash = null) {
142
+ return executeBrokerStep({
143
+ storageRoot: tmpDir,
144
+ uwf,
145
+ config: buildConfig(),
146
+ workflow,
147
+ threadId: THREAD_ID,
148
+ role: ROLE,
149
+ edgePrompt: "go",
150
+ effectiveCwd: "/tmp/work",
151
+ startHash,
152
+ prevHash,
153
+ agentOverride: null,
154
+ previousAttempts: null,
155
+ plog: createProcessLogger({
156
+ storageRoot: tmpDir,
157
+ context: { thread: THREAD_ID, workflow: "turn-chain-wf" },
158
+ }),
159
+ });
160
+ }
161
+ // ── Tests ────────────────────────────────────────────────────────────────────
162
+ describe("turn chain Phase 2 (#419)", () => {
163
+ let tmpDir;
164
+ let casDir;
165
+ let savedOcasHome;
166
+ beforeEach(async () => {
167
+ savedOcasHome = process.env.OCAS_HOME;
168
+ tmpDir = await mkdtemp(join(tmpdir(), "turn-chain-phase2-"));
169
+ casDir = join(tmpDir, "cas");
170
+ process.env.OCAS_HOME = casDir;
171
+ vi.stubGlobal("fetch", async (input, _init) => {
172
+ const url = resolveFetchUrl(input);
173
+ if (url.endsWith(`/gateways/${GATEWAY}/sessions`)) {
174
+ return buildJsonResponse(201, {
175
+ type: "@sumeru/session",
176
+ value: { id: SESSION_ID, gateway: GATEWAY },
177
+ });
178
+ }
179
+ if (url.endsWith(`/sessions/${SESSION_ID}/messages`)) {
180
+ return buildPacedSseResponse([
181
+ turnFrame(1, 0, "First analysis"),
182
+ turnFrame(2, 1, "Continued work"),
183
+ turnFrame(3, 2, FINAL_TURN),
184
+ doneFrame(4, 3),
185
+ ]);
186
+ }
187
+ return buildJsonResponse(500, { error: "unexpected url", url });
188
+ });
189
+ });
190
+ afterEach(async () => {
191
+ vi.unstubAllGlobals();
192
+ if (savedOcasHome === undefined)
193
+ delete process.env.OCAS_HOME;
194
+ else
195
+ process.env.OCAS_HOME = savedOcasHome;
196
+ await rm(tmpDir, { recursive: true, force: true });
197
+ });
198
+ test("onTurn writes each turn with prev pointer and owner reference", async () => {
199
+ const uwf = await createUwfStore(tmpDir);
200
+ const { workflow, startHash } = await buildWorkflow(uwf);
201
+ const result = await runStep(uwf, workflow, startHash, tmpDir);
202
+ expect(result.isError).toBe(false);
203
+ // Get the turn chain head
204
+ const turnHead = getActiveTurnHead(uwf.store, THREAD_ID);
205
+ expect(turnHead).not.toBeNull();
206
+ // Walk the turn chain
207
+ const turnChain = walkTurnChain(uwf, turnHead);
208
+ expect(turnChain).toHaveLength(3);
209
+ // Verify each turn has correct prev and owner
210
+ const turn0 = uwf.store.cas.get(turnChain[0])?.payload;
211
+ const turn1 = uwf.store.cas.get(turnChain[1])?.payload;
212
+ const turn2 = uwf.store.cas.get(turnChain[2])?.payload;
213
+ // Turn 0: first turn, prev is null
214
+ expect(turn0.prev).toBeNull();
215
+ expect(turn0.owner).not.toBeNull();
216
+ expect(turn0.content).toBe("First analysis");
217
+ // Turn 1: prev points to turn 0
218
+ expect(turn1.prev).toBe(turnChain[0]);
219
+ expect(turn1.owner).toBe(turn0.owner);
220
+ expect(turn1.content).toBe("Continued work");
221
+ // Turn 2: prev points to turn 1
222
+ expect(turn2.prev).toBe(turnChain[1]);
223
+ expect(turn2.owner).toBe(turn0.owner);
224
+ expect(turn2.content).toBe(FINAL_TURN);
225
+ // All turns have same owner (the step-start)
226
+ expect(turn0.owner).toBe(turn1.owner);
227
+ expect(turn1.owner).toBe(turn2.owner);
228
+ });
229
+ test("step-start is written at entry and active-step var is set", async () => {
230
+ const uwf = await createUwfStore(tmpDir);
231
+ const { workflow, startHash } = await buildWorkflow(uwf);
232
+ // Capture active-step during execution
233
+ let activeStepDuringExec = null;
234
+ const originalFetch = globalThis.fetch;
235
+ vi.stubGlobal("fetch", async (input, init) => {
236
+ const url = resolveFetchUrl(input);
237
+ if (url.endsWith(`/sessions/${SESSION_ID}/messages`)) {
238
+ // Sample active-step while broker is in flight
239
+ activeStepDuringExec = getActiveStep(uwf.store, THREAD_ID);
240
+ }
241
+ return originalFetch(input, init);
242
+ });
243
+ const result = await runStep(uwf, workflow, startHash, tmpDir);
244
+ expect(result.isError).toBe(false);
245
+ // active-step was set during execution
246
+ expect(activeStepDuringExec).not.toBeNull();
247
+ // active-step is cleared after completion
248
+ const activeStepAfter = getActiveStep(uwf.store, THREAD_ID);
249
+ expect(activeStepAfter).toBeNull();
250
+ // Verify step-start node exists and has correct structure
251
+ const stepStartNode = uwf.store.cas.get(activeStepDuringExec);
252
+ expect(stepStartNode).not.toBeNull();
253
+ const stepStartPayload = stepStartNode?.payload;
254
+ expect(stepStartPayload.role).toBe(ROLE);
255
+ expect(stepStartPayload.edgePrompt).toBe("go");
256
+ expect(stepStartPayload.stepIndex).toBe(0);
257
+ expect(stepStartPayload.prev).toBeNull();
258
+ expect(stepStartPayload.start).toBe(startHash);
259
+ });
260
+ test("detail node has no turns array (turns self-contained via chain)", async () => {
261
+ const uwf = await createUwfStore(tmpDir);
262
+ const { workflow, startHash } = await buildWorkflow(uwf);
263
+ const result = await runStep(uwf, workflow, startHash, tmpDir);
264
+ expect(result.isError).toBe(false);
265
+ // Get detail node
266
+ const detailNode = uwf.store.cas.get(result.detailHash);
267
+ expect(detailNode).not.toBeNull();
268
+ const detail = detailNode?.payload;
269
+ // Detail should have sessionId, duration, turnCount but NOT turns array
270
+ expect(detail.sessionId).toBe(SESSION_ID);
271
+ expect(typeof detail.duration).toBe("number");
272
+ expect(detail.turnCount).toBe(3);
273
+ expect(detail.turns).toBeUndefined(); // No turns array in Phase 2
274
+ });
275
+ test("thread-keyed active vars exist, role-keyed do not", async () => {
276
+ const uwf = await createUwfStore(tmpDir);
277
+ const { workflow, startHash } = await buildWorkflow(uwf);
278
+ await runStep(uwf, workflow, startHash, tmpDir);
279
+ // Thread-keyed turn head exists
280
+ const turnHeadVars = uwf.varStore.list({
281
+ exactName: activeTurnHeadVarName(THREAD_ID),
282
+ });
283
+ expect(turnHeadVars.length).toBe(1);
284
+ // Active-step var is cleared (step completed)
285
+ const activeStepVars = uwf.varStore.list({
286
+ exactName: activeStepVarName(THREAD_ID),
287
+ });
288
+ expect(activeStepVars.length).toBe(0);
289
+ // Role-keyed var is also cleared (backward compat cleanup)
290
+ const roleKeyedVars = uwf.varStore.list({
291
+ namePrefix: `${ACTIVE_TURNS_VAR_PREFIX}${THREAD_ID}/`,
292
+ });
293
+ expect(roleKeyedVars.length).toBe(0);
294
+ });
295
+ test("turnsOfStep filters turns by owner", async () => {
296
+ const uwf = await createUwfStore(tmpDir);
297
+ const { workflow, startHash } = await buildWorkflow(uwf);
298
+ const result = await runStep(uwf, workflow, startHash, tmpDir);
299
+ expect(result.isError).toBe(false);
300
+ const turnHead = getActiveTurnHead(uwf.store, THREAD_ID);
301
+ expect(turnHead).not.toBeNull();
302
+ // Get the step-start from the first turn's owner
303
+ const firstTurn = uwf.store.cas.get(walkTurnChain(uwf, turnHead)[0])
304
+ ?.payload;
305
+ const stepStartHash = firstTurn.owner;
306
+ // turnsOfStep should return all 3 turns for this step
307
+ const stepTurns = turnsOfStep(uwf, turnHead, stepStartHash);
308
+ expect(stepTurns).toHaveLength(3);
309
+ // A different step-start should return no turns
310
+ const otherStepStart = writeStepStart(uwf, {
311
+ role: "other",
312
+ edgePrompt: "other",
313
+ stepIndex: 1,
314
+ prev: stepStartHash,
315
+ start: startHash,
316
+ startedAtMs: Date.now(),
317
+ cwd: "/tmp",
318
+ });
319
+ const otherTurns = turnsOfStep(uwf, turnHead, otherStepStart);
320
+ expect(otherTurns).toHaveLength(0);
321
+ });
322
+ });
323
+ describe("turn chain unit tests", () => {
324
+ let tmpDir;
325
+ let savedOcasHome;
326
+ beforeEach(async () => {
327
+ savedOcasHome = process.env.OCAS_HOME;
328
+ tmpDir = await mkdtemp(join(tmpdir(), "turn-chain-unit-"));
329
+ process.env.OCAS_HOME = join(tmpDir, "cas");
330
+ });
331
+ afterEach(async () => {
332
+ if (savedOcasHome === undefined)
333
+ delete process.env.OCAS_HOME;
334
+ else
335
+ process.env.OCAS_HOME = savedOcasHome;
336
+ await rm(tmpDir, { recursive: true, force: true });
337
+ });
338
+ test("same role multi-round: turns have correct owner (#412 regression)", async () => {
339
+ const uwf = await createUwfStore(tmpDir);
340
+ const startRef = (await uwf.store.cas.put(uwf.schemas.text, "thread-start"));
341
+ // Round 1: developer
342
+ const ss_dev1 = writeStepStart(uwf, {
343
+ role: "developer",
344
+ edgePrompt: "Implement",
345
+ stepIndex: 0,
346
+ prev: null,
347
+ start: startRef,
348
+ startedAtMs: 1000,
349
+ cwd: "/repo",
350
+ });
351
+ const t1 = writeTurnNode(uwf, { role: "assistant", content: "T1", prev: null, owner: ss_dev1 });
352
+ const t2 = writeTurnNode(uwf, { role: "assistant", content: "T2", prev: t1, owner: ss_dev1 });
353
+ // Reviewer
354
+ const ss_rev = writeStepStart(uwf, {
355
+ role: "reviewer",
356
+ edgePrompt: "Review",
357
+ stepIndex: 1,
358
+ prev: ss_dev1,
359
+ start: startRef,
360
+ startedAtMs: 2000,
361
+ cwd: "/repo",
362
+ });
363
+ const t3 = writeTurnNode(uwf, { role: "assistant", content: "T3", prev: t2, owner: ss_rev });
364
+ const t4 = writeTurnNode(uwf, { role: "assistant", content: "T4", prev: t3, owner: ss_rev });
365
+ // Round 2: developer again (same role, different step-start)
366
+ const ss_dev2 = writeStepStart(uwf, {
367
+ role: "developer",
368
+ edgePrompt: "Fix issues",
369
+ stepIndex: 2,
370
+ prev: ss_rev,
371
+ start: startRef,
372
+ startedAtMs: 3000,
373
+ cwd: "/repo",
374
+ });
375
+ const t5 = writeTurnNode(uwf, { role: "assistant", content: "T5", prev: t4, owner: ss_dev2 });
376
+ // Verify ownership
377
+ expect((uwf.store.cas.get(t1)?.payload).owner).toBe(ss_dev1);
378
+ expect((uwf.store.cas.get(t2)?.payload).owner).toBe(ss_dev1);
379
+ expect((uwf.store.cas.get(t3)?.payload).owner).toBe(ss_rev);
380
+ expect((uwf.store.cas.get(t4)?.payload).owner).toBe(ss_rev);
381
+ expect((uwf.store.cas.get(t5)?.payload).owner).toBe(ss_dev2);
382
+ // turnsOfStep correctly filters by owner
383
+ expect(turnsOfStep(uwf, t5, ss_dev1)).toEqual([t1, t2]);
384
+ expect(turnsOfStep(uwf, t5, ss_rev)).toEqual([t3, t4]);
385
+ expect(turnsOfStep(uwf, t5, ss_dev2)).toEqual([t5]);
386
+ // Step-start chain is correct
387
+ expect((uwf.store.cas.get(ss_dev2)?.payload).prev).toBe(ss_rev);
388
+ expect((uwf.store.cas.get(ss_rev)?.payload).prev).toBe(ss_dev1);
389
+ expect((uwf.store.cas.get(ss_dev1)?.payload).prev).toBeNull();
390
+ });
391
+ test("crash recovery: new attempt gets new step-start, old turns orphaned", async () => {
392
+ const uwf = await createUwfStore(tmpDir);
393
+ const startRef = (await uwf.store.cas.put(uwf.schemas.text, "thread-start"));
394
+ // Attempt 1 (crashed): step-start SS1 with 2 turns
395
+ const ss1 = writeStepStart(uwf, {
396
+ role: "developer",
397
+ edgePrompt: "Implement",
398
+ stepIndex: 0,
399
+ prev: null,
400
+ start: startRef,
401
+ startedAtMs: 1000,
402
+ cwd: "/repo",
403
+ });
404
+ const t1 = writeTurnNode(uwf, { role: "assistant", content: "Old T1", prev: null, owner: ss1 });
405
+ const t2 = writeTurnNode(uwf, { role: "assistant", content: "Old T2", prev: t1, owner: ss1 });
406
+ // Attempt 2 (recovery): new step-start SS2
407
+ const ss2 = writeStepStart(uwf, {
408
+ role: "developer",
409
+ edgePrompt: "Implement",
410
+ stepIndex: 0,
411
+ prev: null, // Same prev as SS1 (recovery starts fresh)
412
+ start: startRef,
413
+ startedAtMs: 2000,
414
+ cwd: "/repo",
415
+ });
416
+ // New turns link to global chain (prev=t2) but have different owner
417
+ const t3 = writeTurnNode(uwf, { role: "assistant", content: "New T3", prev: t2, owner: ss2 });
418
+ const t4 = writeTurnNode(uwf, { role: "assistant", content: "New T4", prev: t3, owner: ss2 });
419
+ // SS1 and SS2 have different hashes
420
+ expect(ss1).not.toBe(ss2);
421
+ // Old attempt's turns belong to SS1
422
+ expect(turnsOfStep(uwf, t4, ss1)).toEqual([t1, t2]);
423
+ // New attempt's turns belong to SS2
424
+ expect(turnsOfStep(uwf, t4, ss2)).toEqual([t3, t4]);
425
+ // Walking the full chain shows all 4 turns
426
+ expect(walkTurnChain(uwf, t4)).toEqual([t1, t2, t3, t4]);
427
+ });
428
+ });
429
+ //# sourceMappingURL=broker-step-turn-chain-phase2.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker-step-turn-chain-phase2.test.js","sourceRoot":"","sources":["../../src/__tests__/broker-step-turn-chain-phase2.test.ts"],"names":[],"mappings":"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"}
@@ -10,18 +10,15 @@ describe("config command", () => {
10
10
  writeFileSync(configPath, content, "utf8");
11
11
  return configPath;
12
12
  }
13
- // Sample test config — engine-only (no providers/models/defaultModel/modelOverrides)
13
+ // Sample test config — engine-only (no providers/models/defaultModel/modelOverrides).
14
+ // Phase 3 (#380) replaced the legacy {command, args} agent shape with {host, gateway}.
14
15
  const sampleConfig = `agents:
15
16
  hermes:
16
- command: uwf-hermes
17
- args:
18
- - --provider
19
- - dashscope
17
+ host: http://127.0.0.1:7900
18
+ gateway: hermes
20
19
  claude-code:
21
- command: claude-code
22
- args:
23
- - --profile
24
- - work
20
+ host: http://127.0.0.1:7901
21
+ gateway: claude-code
25
22
  defaultAgent: hermes
26
23
  `;
27
24
  describe("helper functions", () => {
@@ -29,7 +26,7 @@ defaultAgent: hermes
29
26
  test("splits dot notation correctly", () => {
30
27
  expect(parseDotPath("a.b.c")).toEqual(["a", "b", "c"]);
31
28
  expect(parseDotPath("defaultAgent")).toEqual(["defaultAgent"]);
32
- expect(parseDotPath("agents.hermes.command")).toEqual(["agents", "hermes", "command"]);
29
+ expect(parseDotPath("agents.hermes.host")).toEqual(["agents", "hermes", "host"]);
33
30
  });
34
31
  });
35
32
  describe("getNestedValue", () => {
@@ -67,7 +64,7 @@ defaultAgent: hermes
67
64
  describe("maskApiKeys", () => {
68
65
  test("returns deep clone (no mutation) — engine config has no apiKey to mask", () => {
69
66
  const config = {
70
- agents: { hermes: { command: "uwf-hermes", args: [] } },
67
+ agents: { hermes: { host: "http://127.0.0.1:7900", gateway: "hermes" } },
71
68
  defaultAgent: "hermes",
72
69
  };
73
70
  const masked = maskApiKeys(config);
@@ -134,12 +131,12 @@ defaultAgent: hermes
134
131
  rmSync(tempDir, { recursive: true, force: true });
135
132
  }
136
133
  });
137
- test("retrieves array value (agents.hermes.args)", async () => {
134
+ test("retrieves nested string value (agents.hermes.host)", async () => {
138
135
  const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
139
136
  try {
140
137
  createTestConfig(tempDir, sampleConfig);
141
- const result = await cmdConfigGet(tempDir, "agents.hermes.args");
142
- expect(result).toEqual(["--provider", "dashscope"]);
138
+ const result = await cmdConfigGet(tempDir, "agents.hermes.host");
139
+ expect(result).toBe("http://127.0.0.1:7900");
143
140
  }
144
141
  finally {
145
142
  rmSync(tempDir, { recursive: true, force: true });
@@ -189,18 +186,17 @@ defaultAgent: hermes
189
186
  rmSync(tempDir, { recursive: true, force: true });
190
187
  }
191
188
  });
192
- test("sets array value for args key with valid JSON array", async () => {
189
+ test("sets nested string value (agents.hermes.host)", async () => {
193
190
  const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
194
191
  try {
195
192
  createTestConfig(tempDir, sampleConfig);
196
- const newArgs = '["--new", "--flags"]';
197
- const result = await cmdConfigSet(tempDir, "agents.hermes.args", newArgs);
193
+ const result = await cmdConfigSet(tempDir, "agents.hermes.host", "http://10.0.0.1:7900");
198
194
  expect(result).toEqual({
199
- key: "agents.hermes.args",
200
- value: ["--new", "--flags"],
195
+ key: "agents.hermes.host",
196
+ value: "http://10.0.0.1:7900",
201
197
  });
202
- const updated = await cmdConfigGet(tempDir, "agents.hermes.args");
203
- expect(updated).toEqual(["--new", "--flags"]);
198
+ const updated = await cmdConfigGet(tempDir, "agents.hermes.host");
199
+ expect(updated).toBe("http://10.0.0.1:7900");
204
200
  }
205
201
  finally {
206
202
  rmSync(tempDir, { recursive: true, force: true });
@@ -211,8 +207,8 @@ defaultAgent: hermes
211
207
  try {
212
208
  createTestConfig(tempDir, sampleConfig);
213
209
  await cmdConfigSet(tempDir, "defaultAgent", "claude-code");
214
- const cmd = await cmdConfigGet(tempDir, "agents.hermes.command");
215
- expect(cmd).toBe("uwf-hermes");
210
+ const host = await cmdConfigGet(tempDir, "agents.hermes.host");
211
+ expect(host).toBe("http://127.0.0.1:7900");
216
212
  }
217
213
  finally {
218
214
  rmSync(tempDir, { recursive: true, force: true });
@@ -241,27 +237,27 @@ defaultAgent: hermes
241
237
  rmSync(tempDir, { recursive: true, force: true });
242
238
  }
243
239
  });
244
- test("throws error when array value is invalid JSON for args key", async () => {
240
+ test("throws error when value for unknown nested field is invalid", async () => {
245
241
  const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
246
242
  try {
247
243
  createTestConfig(tempDir, sampleConfig);
248
- await expect(cmdConfigSet(tempDir, "agents.hermes.args", "[invalid json")).rejects.toThrow();
244
+ await expect(cmdConfigSet(tempDir, "agents.hermes.args", "[invalid json")).rejects.toThrow(/Unknown field/);
249
245
  }
250
246
  finally {
251
247
  rmSync(tempDir, { recursive: true, force: true });
252
248
  }
253
249
  });
254
- test("sets agent command (agents.claude-code.command)", async () => {
250
+ test("sets agent gateway (agents.claude-code.gateway)", async () => {
255
251
  const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
256
252
  try {
257
253
  createTestConfig(tempDir, sampleConfig);
258
- const result = await cmdConfigSet(tempDir, "agents.claude-code.command", "new-command");
254
+ const result = await cmdConfigSet(tempDir, "agents.claude-code.gateway", "new-gateway");
259
255
  expect(result).toEqual({
260
- key: "agents.claude-code.command",
261
- value: "new-command",
256
+ key: "agents.claude-code.gateway",
257
+ value: "new-gateway",
262
258
  });
263
- const updated = await cmdConfigGet(tempDir, "agents.claude-code.command");
264
- expect(updated).toBe("new-command");
259
+ const updated = await cmdConfigGet(tempDir, "agents.claude-code.gateway");
260
+ expect(updated).toBe("new-gateway");
265
261
  }
266
262
  finally {
267
263
  rmSync(tempDir, { recursive: true, force: true });
@@ -355,12 +351,12 @@ defaultAgent: hermes
355
351
  const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
356
352
  try {
357
353
  createTestConfig(tempDir, sampleConfig);
358
- await cmdConfigSet(tempDir, "agents.hermes.command", "uwf-hermes");
359
- await cmdConfigSet(tempDir, "agents.hermes.args", '["--flag"]');
360
- const command = await cmdConfigGet(tempDir, "agents.hermes.command");
361
- const args = await cmdConfigGet(tempDir, "agents.hermes.args");
362
- expect(command).toBe("uwf-hermes");
363
- expect(args).toEqual(["--flag"]);
354
+ await cmdConfigSet(tempDir, "agents.hermes.host", "http://example:7900");
355
+ await cmdConfigSet(tempDir, "agents.hermes.gateway", "hermes-gw");
356
+ const host = await cmdConfigGet(tempDir, "agents.hermes.host");
357
+ const gateway = await cmdConfigGet(tempDir, "agents.hermes.gateway");
358
+ expect(host).toBe("http://example:7900");
359
+ expect(gateway).toBe("hermes-gw");
364
360
  }
365
361
  finally {
366
362
  rmSync(tempDir, { recursive: true, force: true });