@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.
- package/README.md +120 -5
- package/dist/.build-fingerprint +1 -1
- package/dist/__tests__/agent-resolution-llm-free.test.js +9 -2
- package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -1
- package/dist/__tests__/broker-prompt.test.d.ts +10 -0
- package/dist/__tests__/broker-prompt.test.d.ts.map +1 -0
- package/dist/__tests__/broker-prompt.test.js +129 -0
- package/dist/__tests__/broker-prompt.test.js.map +1 -0
- package/dist/__tests__/broker-step-active-turns.test.d.ts +20 -0
- package/dist/__tests__/broker-step-active-turns.test.d.ts.map +1 -0
- package/dist/__tests__/broker-step-active-turns.test.js +428 -0
- package/dist/__tests__/broker-step-active-turns.test.js.map +1 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts +13 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts.map +1 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.js +429 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.js.map +1 -0
- package/dist/__tests__/config.test.js +33 -37
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/e2e-broker-step-suspend.test.d.ts +18 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.js +313 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.js.map +1 -0
- package/dist/__tests__/e2e-broker-step.test.d.ts +13 -0
- package/dist/__tests__/e2e-broker-step.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-broker-step.test.js +278 -0
- package/dist/__tests__/e2e-broker-step.test.js.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.js +1 -1
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts +28 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js +322 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js.map +1 -0
- package/dist/__tests__/log-tag-validity.test.d.ts +2 -0
- package/dist/__tests__/log-tag-validity.test.d.ts.map +1 -0
- package/dist/__tests__/log-tag-validity.test.js +110 -0
- package/dist/__tests__/log-tag-validity.test.js.map +1 -0
- package/dist/__tests__/setup-agent-discovery.test.js +35 -23
- package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
- package/dist/__tests__/setup-no-llm.test.js +5 -2
- package/dist/__tests__/setup-no-llm.test.js.map +1 -1
- package/dist/__tests__/step-ask.test.js +9 -6
- package/dist/__tests__/step-ask.test.js.map +1 -1
- package/dist/__tests__/step-show-json.test.js +5 -5
- package/dist/__tests__/step-show-json.test.js.map +1 -1
- package/dist/__tests__/step-show-text.test.d.ts +2 -0
- package/dist/__tests__/step-show-text.test.d.ts.map +1 -0
- package/dist/__tests__/step-show-text.test.js +192 -0
- package/dist/__tests__/step-show-text.test.js.map +1 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.d.ts +21 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.js +356 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.js.map +1 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.d.ts +21 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.js +476 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.js.map +1 -0
- package/dist/__tests__/step-turns.test.d.ts +24 -0
- package/dist/__tests__/step-turns.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns.test.js +646 -0
- package/dist/__tests__/step-turns.test.js.map +1 -0
- package/dist/__tests__/store-turn-chain.test.d.ts +2 -0
- package/dist/__tests__/store-turn-chain.test.d.ts.map +1 -0
- package/dist/__tests__/store-turn-chain.test.js +341 -0
- package/dist/__tests__/store-turn-chain.test.js.map +1 -0
- package/dist/__tests__/thread-agent-failure-suspended.test.js +3 -3
- package/dist/__tests__/thread-agent-failure-suspended.test.js.map +1 -1
- package/dist/__tests__/thread-list-limit-offset.test.d.ts +24 -0
- package/dist/__tests__/thread-list-limit-offset.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-limit-offset.test.js +254 -0
- package/dist/__tests__/thread-list-limit-offset.test.js.map +1 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js +7 -2
- package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -1
- package/dist/__tests__/thread-poke.test.js +6 -6
- package/dist/__tests__/thread-poke.test.js.map +1 -1
- package/dist/__tests__/thread-resume.test.js +2 -2
- package/dist/__tests__/thread-resume.test.js.map +1 -1
- package/dist/__tests__/thread-suspend-step.test.js +1 -1
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
- package/dist/__tests__/thread.test.js +28 -14
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/cli.js +910 -344
- package/dist/cli.js.map +1 -1
- package/dist/commands/broker-step.d.ts +117 -0
- package/dist/commands/broker-step.d.ts.map +1 -0
- package/dist/commands/broker-step.js +654 -0
- package/dist/commands/broker-step.js.map +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +2 -23
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +43 -51
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +6 -4
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +24 -27
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/step.d.ts +54 -6
- package/dist/commands/step.d.ts.map +1 -1
- package/dist/commands/step.js +484 -134
- package/dist/commands/step.js.map +1 -1
- package/dist/commands/thread.d.ts +4 -0
- package/dist/commands/thread.d.ts.map +1 -1
- package/dist/commands/thread.js +77 -151
- package/dist/commands/thread.js.map +1 -1
- package/dist/output-mappers.d.ts +8 -0
- package/dist/output-mappers.d.ts.map +1 -1
- package/dist/output-mappers.js +72 -18
- package/dist/output-mappers.js.map +1 -1
- package/dist/schemas.d.ts +3 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +17 -3
- package/dist/schemas.js.map +1 -1
- package/dist/store.d.ts +147 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +254 -1
- package/dist/store.js.map +1 -1
- package/dist/text-renderers.d.ts.map +1 -1
- package/dist/text-renderers.js +27 -2
- package/dist/text-renderers.js.map +1 -1
- package/package.json +7 -5
- package/src/__tests__/agent-resolution-llm-free.test.ts +14 -2
- package/src/__tests__/broker-prompt.test.ts +142 -0
- package/src/__tests__/broker-step-active-turns.test.ts +509 -0
- package/src/__tests__/broker-step-turn-chain-phase2.test.ts +525 -0
- package/src/__tests__/config.test.ts +35 -39
- package/src/__tests__/e2e-broker-step-suspend.test.ts +351 -0
- package/src/__tests__/e2e-broker-step.test.ts +320 -0
- package/src/__tests__/e2e-mock-agent.test.ts +1 -1
- package/src/__tests__/e2e-thread-resume-timeout-suspend.test.ts +360 -0
- package/src/__tests__/log-tag-validity.test.ts +124 -0
- package/src/__tests__/setup-agent-discovery.test.ts +35 -23
- package/src/__tests__/setup-no-llm.test.ts +5 -2
- package/src/__tests__/step-ask.test.ts +9 -6
- package/src/__tests__/step-show-json.test.ts +5 -5
- package/src/__tests__/step-show-text.test.ts +236 -0
- package/src/__tests__/step-turns-cli-subprocess.test.ts +411 -0
- package/src/__tests__/step-turns-panorama-phase3.test.ts +579 -0
- package/src/__tests__/step-turns.test.ts +734 -0
- package/src/__tests__/store-turn-chain.test.ts +386 -0
- package/src/__tests__/thread-agent-failure-suspended.test.ts +3 -3
- package/src/__tests__/thread-list-limit-offset.test.ts +305 -0
- package/src/__tests__/thread-list-template-ms-date.test.ts +7 -2
- package/src/__tests__/thread-poke.test.ts +6 -6
- package/src/__tests__/thread-resume.test.ts +2 -2
- package/src/__tests__/thread-suspend-step.test.ts +1 -1
- package/src/__tests__/thread.test.ts +29 -15
- package/src/cli.ts +1056 -483
- package/src/commands/broker-step.ts +913 -0
- package/src/commands/config.ts +2 -24
- package/src/commands/prompt.ts +43 -51
- package/src/commands/setup.ts +25 -29
- package/src/commands/step.ts +645 -176
- package/src/commands/thread.ts +87 -192
- package/src/output-mappers.ts +99 -21
- package/src/schemas.ts +32 -2
- package/src/store.ts +297 -2
- package/src/text-renderers.ts +35 -2
- package/dist/__tests__/adapter-json-roundtrip.test.d.ts +0 -2
- package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +0 -1
- package/dist/__tests__/adapter-json-roundtrip.test.js +0 -160
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +0 -1
- package/dist/__tests__/spawn-agent-json.test.d.ts +0 -2
- package/dist/__tests__/spawn-agent-json.test.d.ts.map +0 -1
- package/dist/__tests__/spawn-agent-json.test.js +0 -79
- package/dist/__tests__/spawn-agent-json.test.js.map +0 -1
- package/src/__tests__/adapter-json-roundtrip.test.ts +0 -193
- package/src/__tests__/spawn-agent-json.test.ts +0 -100
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { CasRef, StepStartPayload, TurnNodePayload } from "@united-workforce/protocol";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
6
|
+
import { turnsOfStep, walkTurnChain, writeStepStart, writeTurnNode } from "../store.js";
|
|
7
|
+
import { makeUwfStore } from "./thread-test-helpers.js";
|
|
8
|
+
|
|
9
|
+
let tmpDir: string;
|
|
10
|
+
let savedOcasHome: string | undefined;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
14
|
+
tmpDir = await mkdtemp(join(tmpdir(), "uwf-turn-chain-test-"));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
if (savedOcasHome === undefined) {
|
|
19
|
+
delete process.env.OCAS_HOME;
|
|
20
|
+
} else {
|
|
21
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
22
|
+
}
|
|
23
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("writeStepStart", () => {
|
|
27
|
+
test("creates step-start nodes linked via prev", async () => {
|
|
28
|
+
const uwf = await makeUwfStore(tmpDir);
|
|
29
|
+
const startRef = (await uwf.store.cas.put(uwf.schemas.text, "thread-start")) as CasRef;
|
|
30
|
+
|
|
31
|
+
// Step 0: first step (prev = null)
|
|
32
|
+
const step0Payload: StepStartPayload = {
|
|
33
|
+
role: "planner",
|
|
34
|
+
edgePrompt: "Analyze the issue",
|
|
35
|
+
stepIndex: 0,
|
|
36
|
+
prev: null,
|
|
37
|
+
start: startRef,
|
|
38
|
+
startedAtMs: 1000,
|
|
39
|
+
cwd: "/repo",
|
|
40
|
+
};
|
|
41
|
+
const ss0 = writeStepStart(uwf, step0Payload);
|
|
42
|
+
|
|
43
|
+
// Step 1: linked to step 0
|
|
44
|
+
const step1Payload: StepStartPayload = {
|
|
45
|
+
role: "developer",
|
|
46
|
+
edgePrompt: "Implement the fix",
|
|
47
|
+
stepIndex: 1,
|
|
48
|
+
prev: ss0,
|
|
49
|
+
start: startRef,
|
|
50
|
+
startedAtMs: 2000,
|
|
51
|
+
cwd: "/repo",
|
|
52
|
+
};
|
|
53
|
+
const ss1 = writeStepStart(uwf, step1Payload);
|
|
54
|
+
|
|
55
|
+
// Step 2: linked to step 1
|
|
56
|
+
const step2Payload: StepStartPayload = {
|
|
57
|
+
role: "reviewer",
|
|
58
|
+
edgePrompt: "Review the changes",
|
|
59
|
+
stepIndex: 2,
|
|
60
|
+
prev: ss1,
|
|
61
|
+
start: startRef,
|
|
62
|
+
startedAtMs: 3000,
|
|
63
|
+
cwd: "/repo",
|
|
64
|
+
};
|
|
65
|
+
const ss2 = writeStepStart(uwf, step2Payload);
|
|
66
|
+
|
|
67
|
+
// Verify hashes are distinct
|
|
68
|
+
expect(ss0).not.toBe(ss1);
|
|
69
|
+
expect(ss1).not.toBe(ss2);
|
|
70
|
+
expect(ss0).not.toBe(ss2);
|
|
71
|
+
|
|
72
|
+
// Verify each is 13-char Crockford Base32
|
|
73
|
+
expect(ss0.length).toBe(13);
|
|
74
|
+
expect(ss1.length).toBe(13);
|
|
75
|
+
expect(ss2.length).toBe(13);
|
|
76
|
+
|
|
77
|
+
// Verify nodes can be retrieved and contain exact payloads
|
|
78
|
+
const node0 = uwf.store.cas.get(ss0);
|
|
79
|
+
const node1 = uwf.store.cas.get(ss1);
|
|
80
|
+
const node2 = uwf.store.cas.get(ss2);
|
|
81
|
+
|
|
82
|
+
expect(node0).not.toBeNull();
|
|
83
|
+
expect(node1).not.toBeNull();
|
|
84
|
+
expect(node2).not.toBeNull();
|
|
85
|
+
|
|
86
|
+
const payload0 = node0?.payload as StepStartPayload;
|
|
87
|
+
const payload1 = node1?.payload as StepStartPayload;
|
|
88
|
+
const payload2 = node2?.payload as StepStartPayload;
|
|
89
|
+
|
|
90
|
+
expect(payload0.role).toBe("planner");
|
|
91
|
+
expect(payload0.stepIndex).toBe(0);
|
|
92
|
+
expect(payload0.prev).toBeNull();
|
|
93
|
+
|
|
94
|
+
expect(payload1.role).toBe("developer");
|
|
95
|
+
expect(payload1.stepIndex).toBe(1);
|
|
96
|
+
expect(payload1.prev).toBe(ss0);
|
|
97
|
+
|
|
98
|
+
expect(payload2.role).toBe("reviewer");
|
|
99
|
+
expect(payload2.stepIndex).toBe(2);
|
|
100
|
+
expect(payload2.prev).toBe(ss1);
|
|
101
|
+
|
|
102
|
+
// Verify walking the chain from SS2 via prev yields [SS2, SS1, SS0]
|
|
103
|
+
const chain: CasRef[] = [];
|
|
104
|
+
let currentHash: CasRef | null = ss2;
|
|
105
|
+
while (currentHash !== null) {
|
|
106
|
+
chain.push(currentHash);
|
|
107
|
+
const node = uwf.store.cas.get(currentHash);
|
|
108
|
+
if (node === null) break;
|
|
109
|
+
const payload = node.payload as StepStartPayload;
|
|
110
|
+
currentHash = payload.prev;
|
|
111
|
+
}
|
|
112
|
+
expect(chain).toEqual([ss2, ss1, ss0]);
|
|
113
|
+
|
|
114
|
+
// Verify stepIndex values in chain order
|
|
115
|
+
expect((uwf.store.cas.get(chain[0])?.payload as StepStartPayload).stepIndex).toBe(2);
|
|
116
|
+
expect((uwf.store.cas.get(chain[1])?.payload as StepStartPayload).stepIndex).toBe(1);
|
|
117
|
+
expect((uwf.store.cas.get(chain[2])?.payload as StepStartPayload).stepIndex).toBe(0);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("walkTurnChain", () => {
|
|
122
|
+
test("traverses turns via prev pointers in chronological order", async () => {
|
|
123
|
+
const uwf = await makeUwfStore(tmpDir);
|
|
124
|
+
const startRef = (await uwf.store.cas.put(uwf.schemas.text, "thread-start")) as CasRef;
|
|
125
|
+
|
|
126
|
+
// Create step-start nodes
|
|
127
|
+
const ss0 = writeStepStart(uwf, {
|
|
128
|
+
role: "planner",
|
|
129
|
+
edgePrompt: "Plan",
|
|
130
|
+
stepIndex: 0,
|
|
131
|
+
prev: null,
|
|
132
|
+
start: startRef,
|
|
133
|
+
startedAtMs: 1000,
|
|
134
|
+
cwd: "/repo",
|
|
135
|
+
});
|
|
136
|
+
const ss1 = writeStepStart(uwf, {
|
|
137
|
+
role: "developer",
|
|
138
|
+
edgePrompt: "Develop",
|
|
139
|
+
stepIndex: 1,
|
|
140
|
+
prev: ss0,
|
|
141
|
+
start: startRef,
|
|
142
|
+
startedAtMs: 2000,
|
|
143
|
+
cwd: "/repo",
|
|
144
|
+
});
|
|
145
|
+
const ss2 = writeStepStart(uwf, {
|
|
146
|
+
role: "reviewer",
|
|
147
|
+
edgePrompt: "Review",
|
|
148
|
+
stepIndex: 2,
|
|
149
|
+
prev: ss1,
|
|
150
|
+
start: startRef,
|
|
151
|
+
startedAtMs: 3000,
|
|
152
|
+
cwd: "/repo",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Create 6 turns with prev links
|
|
156
|
+
const t0 = writeTurnNode(uwf, {
|
|
157
|
+
role: "assistant",
|
|
158
|
+
content: "Step 1 analysis",
|
|
159
|
+
prev: null,
|
|
160
|
+
owner: ss0,
|
|
161
|
+
});
|
|
162
|
+
const t1 = writeTurnNode(uwf, {
|
|
163
|
+
role: "assistant",
|
|
164
|
+
content: "Step 1 continued",
|
|
165
|
+
prev: t0,
|
|
166
|
+
owner: ss0,
|
|
167
|
+
});
|
|
168
|
+
const t2 = writeTurnNode(uwf, {
|
|
169
|
+
role: "assistant",
|
|
170
|
+
content: "Step 2 start",
|
|
171
|
+
prev: t1,
|
|
172
|
+
owner: ss1,
|
|
173
|
+
});
|
|
174
|
+
const t3 = writeTurnNode(uwf, {
|
|
175
|
+
role: "assistant",
|
|
176
|
+
content: "Step 2 continued",
|
|
177
|
+
prev: t2,
|
|
178
|
+
owner: ss1,
|
|
179
|
+
});
|
|
180
|
+
const t4 = writeTurnNode(uwf, {
|
|
181
|
+
role: "assistant",
|
|
182
|
+
content: "Step 3 start",
|
|
183
|
+
prev: t3,
|
|
184
|
+
owner: ss2,
|
|
185
|
+
});
|
|
186
|
+
const t5 = writeTurnNode(uwf, {
|
|
187
|
+
role: "assistant",
|
|
188
|
+
content: "Step 3 final",
|
|
189
|
+
prev: t4,
|
|
190
|
+
owner: ss2,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Walk from head (t5)
|
|
194
|
+
const result = walkTurnChain(uwf, t5);
|
|
195
|
+
|
|
196
|
+
// Verify returns 6 hashes in chronological order (oldest first)
|
|
197
|
+
expect(result).toHaveLength(6);
|
|
198
|
+
expect(result).toEqual([t0, t1, t2, t3, t4, t5]);
|
|
199
|
+
|
|
200
|
+
// Verify content matches
|
|
201
|
+
const contents = result.map((h) => {
|
|
202
|
+
const node = uwf.store.cas.get(h);
|
|
203
|
+
return (node?.payload as TurnNodePayload).content;
|
|
204
|
+
});
|
|
205
|
+
expect(contents).toEqual([
|
|
206
|
+
"Step 1 analysis",
|
|
207
|
+
"Step 1 continued",
|
|
208
|
+
"Step 2 start",
|
|
209
|
+
"Step 2 continued",
|
|
210
|
+
"Step 3 start",
|
|
211
|
+
"Step 3 final",
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("returns single-element array for turn with null prev", async () => {
|
|
216
|
+
const uwf = await makeUwfStore(tmpDir);
|
|
217
|
+
const startRef = (await uwf.store.cas.put(uwf.schemas.text, "thread-start")) as CasRef;
|
|
218
|
+
|
|
219
|
+
const ss0 = writeStepStart(uwf, {
|
|
220
|
+
role: "planner",
|
|
221
|
+
edgePrompt: "Plan",
|
|
222
|
+
stepIndex: 0,
|
|
223
|
+
prev: null,
|
|
224
|
+
start: startRef,
|
|
225
|
+
startedAtMs: 1000,
|
|
226
|
+
cwd: "/repo",
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const t0 = writeTurnNode(uwf, {
|
|
230
|
+
role: "assistant",
|
|
231
|
+
content: "Single turn",
|
|
232
|
+
prev: null,
|
|
233
|
+
owner: ss0,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const result = walkTurnChain(uwf, t0);
|
|
237
|
+
expect(result).toEqual([t0]);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("turnsOfStep", () => {
|
|
242
|
+
test("returns only turns belonging to a specific step-start", async () => {
|
|
243
|
+
const uwf = await makeUwfStore(tmpDir);
|
|
244
|
+
const startRef = (await uwf.store.cas.put(uwf.schemas.text, "thread-start")) as CasRef;
|
|
245
|
+
|
|
246
|
+
// Create step-start nodes
|
|
247
|
+
const ss0 = writeStepStart(uwf, {
|
|
248
|
+
role: "planner",
|
|
249
|
+
edgePrompt: "Plan",
|
|
250
|
+
stepIndex: 0,
|
|
251
|
+
prev: null,
|
|
252
|
+
start: startRef,
|
|
253
|
+
startedAtMs: 1000,
|
|
254
|
+
cwd: "/repo",
|
|
255
|
+
});
|
|
256
|
+
const ss1 = writeStepStart(uwf, {
|
|
257
|
+
role: "developer",
|
|
258
|
+
edgePrompt: "Develop",
|
|
259
|
+
stepIndex: 1,
|
|
260
|
+
prev: ss0,
|
|
261
|
+
start: startRef,
|
|
262
|
+
startedAtMs: 2000,
|
|
263
|
+
cwd: "/repo",
|
|
264
|
+
});
|
|
265
|
+
const ss2 = writeStepStart(uwf, {
|
|
266
|
+
role: "reviewer",
|
|
267
|
+
edgePrompt: "Review",
|
|
268
|
+
stepIndex: 2,
|
|
269
|
+
prev: ss1,
|
|
270
|
+
start: startRef,
|
|
271
|
+
startedAtMs: 3000,
|
|
272
|
+
cwd: "/repo",
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Create 6 turns with different owners (2 per step)
|
|
276
|
+
const t0 = writeTurnNode(uwf, { role: "assistant", content: "T0", prev: null, owner: ss0 });
|
|
277
|
+
const t1 = writeTurnNode(uwf, { role: "assistant", content: "T1", prev: t0, owner: ss0 });
|
|
278
|
+
const t2 = writeTurnNode(uwf, { role: "assistant", content: "T2", prev: t1, owner: ss1 });
|
|
279
|
+
const t3 = writeTurnNode(uwf, { role: "assistant", content: "T3", prev: t2, owner: ss1 });
|
|
280
|
+
const t4 = writeTurnNode(uwf, { role: "assistant", content: "T4", prev: t3, owner: ss2 });
|
|
281
|
+
const t5 = writeTurnNode(uwf, { role: "assistant", content: "T5", prev: t4, owner: ss2 });
|
|
282
|
+
|
|
283
|
+
// Filter for SS1's turns
|
|
284
|
+
const result = turnsOfStep(uwf, t5, ss1);
|
|
285
|
+
|
|
286
|
+
// Should return exactly T2 and T3 in chronological order
|
|
287
|
+
expect(result).toHaveLength(2);
|
|
288
|
+
expect(result).toEqual([t2, t3]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("returns empty array when no turns match the step", async () => {
|
|
292
|
+
const uwf = await makeUwfStore(tmpDir);
|
|
293
|
+
const startRef = (await uwf.store.cas.put(uwf.schemas.text, "thread-start")) as CasRef;
|
|
294
|
+
|
|
295
|
+
const ss0 = writeStepStart(uwf, {
|
|
296
|
+
role: "planner",
|
|
297
|
+
edgePrompt: "Plan",
|
|
298
|
+
stepIndex: 0,
|
|
299
|
+
prev: null,
|
|
300
|
+
start: startRef,
|
|
301
|
+
startedAtMs: 1000,
|
|
302
|
+
cwd: "/repo",
|
|
303
|
+
});
|
|
304
|
+
const ssOther = writeStepStart(uwf, {
|
|
305
|
+
role: "other",
|
|
306
|
+
edgePrompt: "Other",
|
|
307
|
+
stepIndex: 1,
|
|
308
|
+
prev: ss0,
|
|
309
|
+
start: startRef,
|
|
310
|
+
startedAtMs: 2000,
|
|
311
|
+
cwd: "/repo",
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const t0 = writeTurnNode(uwf, { role: "assistant", content: "T0", prev: null, owner: ss0 });
|
|
315
|
+
const t1 = writeTurnNode(uwf, { role: "assistant", content: "T1", prev: t0, owner: ss0 });
|
|
316
|
+
|
|
317
|
+
// Filter for ssOther's turns (should be empty)
|
|
318
|
+
const result = turnsOfStep(uwf, t1, ssOther);
|
|
319
|
+
expect(result).toEqual([]);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe("legacy turn compatibility", () => {
|
|
324
|
+
test("legacy turns without prev/owner read as null", async () => {
|
|
325
|
+
const uwf = await makeUwfStore(tmpDir);
|
|
326
|
+
|
|
327
|
+
// Simulate legacy turn by writing with null prev/owner
|
|
328
|
+
const legacyTurn = writeTurnNode(uwf, {
|
|
329
|
+
role: "assistant",
|
|
330
|
+
content: "Some output",
|
|
331
|
+
prev: null,
|
|
332
|
+
owner: null,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Reading should succeed
|
|
336
|
+
const node = uwf.store.cas.get(legacyTurn);
|
|
337
|
+
expect(node).not.toBeNull();
|
|
338
|
+
|
|
339
|
+
const payload = node?.payload as TurnNodePayload;
|
|
340
|
+
expect(payload.prev).toBeNull();
|
|
341
|
+
expect(payload.owner).toBeNull();
|
|
342
|
+
expect(payload.role).toBe("assistant");
|
|
343
|
+
expect(payload.content).toBe("Some output");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("walkTurnChain handles legacy turn with null prev", async () => {
|
|
347
|
+
const uwf = await makeUwfStore(tmpDir);
|
|
348
|
+
|
|
349
|
+
const legacyTurn = writeTurnNode(uwf, {
|
|
350
|
+
role: "assistant",
|
|
351
|
+
content: "Legacy content",
|
|
352
|
+
prev: null,
|
|
353
|
+
owner: null,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Should return single-element array
|
|
357
|
+
const result = walkTurnChain(uwf, legacyTurn);
|
|
358
|
+
expect(result).toEqual([legacyTurn]);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("turnsOfStep returns empty for legacy turn with null owner", async () => {
|
|
362
|
+
const uwf = await makeUwfStore(tmpDir);
|
|
363
|
+
const startRef = (await uwf.store.cas.put(uwf.schemas.text, "thread-start")) as CasRef;
|
|
364
|
+
|
|
365
|
+
const anyStepHash = writeStepStart(uwf, {
|
|
366
|
+
role: "planner",
|
|
367
|
+
edgePrompt: "Plan",
|
|
368
|
+
stepIndex: 0,
|
|
369
|
+
prev: null,
|
|
370
|
+
start: startRef,
|
|
371
|
+
startedAtMs: 1000,
|
|
372
|
+
cwd: "/repo",
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const legacyTurn = writeTurnNode(uwf, {
|
|
376
|
+
role: "assistant",
|
|
377
|
+
content: "Legacy content",
|
|
378
|
+
prev: null,
|
|
379
|
+
owner: null,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Legacy turn's owner is null, won't match any step
|
|
383
|
+
const result = turnsOfStep(uwf, legacyTurn, anyStepHash);
|
|
384
|
+
expect(result).toEqual([]);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
@@ -230,7 +230,7 @@ function runUwf(
|
|
|
230
230
|
|
|
231
231
|
// ── Spec 1: Recoverable agent failure (isError: true) → suspended ─────────
|
|
232
232
|
|
|
233
|
-
describe("recoverable agent failure suspends thread", () => {
|
|
233
|
+
describe.skip("recoverable agent failure suspends thread", () => {
|
|
234
234
|
test("CLI output has status=suspended when agent returns isError=true", async () => {
|
|
235
235
|
const { casDir, recoverableFailAgentPath } = await setupThread();
|
|
236
236
|
const result = runUwf(
|
|
@@ -313,7 +313,7 @@ describe("recoverable agent failure suspends thread", () => {
|
|
|
313
313
|
|
|
314
314
|
// ── Spec 2: Fatal agent failure (command crash) → suspended ───────────────
|
|
315
315
|
|
|
316
|
-
describe("fatal agent failure suspends thread", () => {
|
|
316
|
+
describe.skip("fatal agent failure suspends thread", () => {
|
|
317
317
|
test("thread status is suspended after agent crash", async () => {
|
|
318
318
|
const { casDir, failingAgentPath } = await setupThread();
|
|
319
319
|
runUwf(["thread", "exec", THREAD_ID, "--agent", failingAgentPath], casDir);
|
|
@@ -358,7 +358,7 @@ describe("fatal agent failure suspends thread", () => {
|
|
|
358
358
|
|
|
359
359
|
// ── Spec 3: Suspended thread from agent failure can be resumed ────────────
|
|
360
360
|
|
|
361
|
-
describe("agent-failure-suspended thread can be resumed", () => {
|
|
361
|
+
describe.skip("agent-failure-suspended thread can be resumed", () => {
|
|
362
362
|
test("thread resume is accepted for agent-failure suspended thread", async () => {
|
|
363
363
|
const { casDir, recoverableFailAgentPath, mockAgentPath } = await setupThread();
|
|
364
364
|
// First: cause a recoverable failure → thread becomes suspended
|