@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.
- package/README.md +15 -8
- package/dist/__tests__/adapter-json-roundtrip.test.js +1 -1
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
- package/dist/__tests__/agent-resolution-llm-free.test.d.ts +2 -0
- package/dist/__tests__/agent-resolution-llm-free.test.d.ts.map +1 -0
- package/dist/__tests__/agent-resolution-llm-free.test.js +30 -0
- package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -0
- package/dist/__tests__/build-step-entry.test.d.ts +2 -0
- package/dist/__tests__/build-step-entry.test.d.ts.map +1 -0
- package/dist/__tests__/build-step-entry.test.js +173 -0
- package/dist/__tests__/build-step-entry.test.js.map +1 -0
- package/dist/__tests__/clear-thread-failed-attempts.test.d.ts +2 -0
- package/dist/__tests__/clear-thread-failed-attempts.test.d.ts.map +1 -0
- package/dist/__tests__/clear-thread-failed-attempts.test.js +93 -0
- package/dist/__tests__/clear-thread-failed-attempts.test.js.map +1 -0
- package/dist/__tests__/config.test.js +26 -302
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/current-role.test.js +7 -6
- package/dist/__tests__/current-role.test.js.map +1 -1
- package/dist/__tests__/e2e-mock-agent.test.js +20 -23
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
- package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts +2 -0
- package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts.map +1 -0
- package/dist/__tests__/issue-180-workflow-ref-removed.test.js +40 -0
- package/dist/__tests__/issue-180-workflow-ref-removed.test.js.map +1 -0
- package/dist/__tests__/moderator-evaluate.test.js +9 -50
- package/dist/__tests__/moderator-evaluate.test.js.map +1 -1
- package/dist/__tests__/pid-recycling.test.d.ts +2 -0
- package/dist/__tests__/pid-recycling.test.d.ts.map +1 -0
- package/dist/__tests__/pid-recycling.test.js +271 -0
- package/dist/__tests__/pid-recycling.test.js.map +1 -0
- package/dist/__tests__/prompt.test.js +321 -0
- package/dist/__tests__/prompt.test.js.map +1 -1
- package/dist/__tests__/resolve-head-hash.test.js +4 -4
- package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
- package/dist/__tests__/setup-agent-discovery.test.js +21 -30
- package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
- package/dist/__tests__/setup-complexity.test.js +2 -168
- package/dist/__tests__/setup-complexity.test.js.map +1 -1
- package/dist/__tests__/setup-no-llm.test.d.ts +2 -0
- package/dist/__tests__/setup-no-llm.test.d.ts.map +1 -0
- package/dist/__tests__/setup-no-llm.test.js +52 -0
- package/dist/__tests__/setup-no-llm.test.js.map +1 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.js +24 -27
- package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
- package/dist/__tests__/step-ask.test.d.ts +2 -0
- package/dist/__tests__/step-ask.test.d.ts.map +1 -0
- package/dist/__tests__/step-ask.test.js +499 -0
- package/dist/__tests__/step-ask.test.js.map +1 -0
- package/dist/__tests__/step-show-json.test.js +1 -0
- package/dist/__tests__/step-show-json.test.js.map +1 -1
- package/dist/__tests__/step-timing.test.js +2 -0
- package/dist/__tests__/step-timing.test.js.map +1 -1
- package/dist/__tests__/store-global-cas.test.js +2 -2
- package/dist/__tests__/store-global-cas.test.js.map +1 -1
- package/dist/__tests__/store-unified-threads.test.js +9 -9
- package/dist/__tests__/store-unified-threads.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-status.test.js +6 -6
- package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
- package/dist/__tests__/thread-list-filters.test.js +344 -9
- package/dist/__tests__/thread-list-filters.test.js.map +1 -1
- package/dist/__tests__/thread-poke.test.d.ts +2 -0
- package/dist/__tests__/thread-poke.test.d.ts.map +1 -0
- package/dist/__tests__/thread-poke.test.js +412 -0
- package/dist/__tests__/thread-poke.test.js.map +1 -0
- package/dist/__tests__/thread-resume.test.js +10 -14
- package/dist/__tests__/thread-resume.test.js.map +1 -1
- package/dist/__tests__/thread-show-status.test.js +17 -28
- package/dist/__tests__/thread-show-status.test.js.map +1 -1
- package/dist/__tests__/thread-suspend-step.test.js +8 -14
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
- package/dist/__tests__/thread-suspended-display.test.js +10 -22
- package/dist/__tests__/thread-suspended-display.test.js.map +1 -1
- package/dist/__tests__/thread.test.js +4 -4
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/__tests__/validate-semantic.test.js +49 -21
- package/dist/__tests__/validate-semantic.test.js.map +1 -1
- package/dist/__tests__/workflow-list-recursive.test.d.ts +2 -0
- package/dist/__tests__/workflow-list-recursive.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-list-recursive.test.js +283 -0
- package/dist/__tests__/workflow-list-recursive.test.js.map +1 -0
- package/dist/__tests__/workflow-resolution.test.js +36 -21
- package/dist/__tests__/workflow-resolution.test.js.map +1 -1
- package/dist/__tests__/workflow-show-resolution.test.d.ts +2 -0
- package/dist/__tests__/workflow-show-resolution.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-show-resolution.test.js +210 -0
- package/dist/__tests__/workflow-show-resolution.test.js.map +1 -0
- package/dist/__tests__/workflow-validate.test.d.ts +2 -0
- package/dist/__tests__/workflow-validate.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-validate.test.js +687 -0
- package/dist/__tests__/workflow-validate.test.js.map +1 -0
- package/dist/background/background.d.ts +22 -1
- package/dist/background/background.d.ts.map +1 -1
- package/dist/background/background.js +83 -6
- package/dist/background/background.js.map +1 -1
- package/dist/background/index.d.ts +1 -1
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +1 -1
- package/dist/background/index.js.map +1 -1
- package/dist/background/types.d.ts +1 -0
- package/dist/background/types.d.ts.map +1 -1
- package/dist/cli.js +66 -31
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts +3 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +7 -33
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +15 -2
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +7 -39
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +27 -302
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/step.d.ts +44 -1
- package/dist/commands/step.d.ts.map +1 -1
- package/dist/commands/step.js +255 -11
- package/dist/commands/step.js.map +1 -1
- package/dist/commands/thread.d.ts +16 -3
- package/dist/commands/thread.d.ts.map +1 -1
- package/dist/commands/thread.js +379 -140
- package/dist/commands/thread.js.map +1 -1
- package/dist/commands/workflow.d.ts +9 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +130 -6
- package/dist/commands/workflow.js.map +1 -1
- package/dist/moderator/__tests__/evaluate.test.js +31 -17
- package/dist/moderator/__tests__/evaluate.test.js.map +1 -1
- package/dist/moderator/evaluate.d.ts.map +1 -1
- package/dist/moderator/evaluate.js +4 -16
- package/dist/moderator/evaluate.js.map +1 -1
- package/dist/moderator/index.d.ts +1 -2
- package/dist/moderator/index.d.ts.map +1 -1
- package/dist/moderator/index.js +0 -1
- package/dist/moderator/index.js.map +1 -1
- package/dist/moderator/types.d.ts +6 -10
- package/dist/moderator/types.d.ts.map +1 -1
- package/dist/moderator/types.js +1 -3
- package/dist/moderator/types.js.map +1 -1
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +5 -3
- package/dist/schemas.js.map +1 -1
- package/dist/store.d.ts +28 -9
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +75 -16
- package/dist/store.js.map +1 -1
- package/dist/validate-semantic.d.ts.map +1 -1
- package/dist/validate-semantic.js +83 -66
- package/dist/validate-semantic.js.map +1 -1
- package/dist/validate.d.ts +6 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +24 -0
- package/dist/validate.js.map +1 -1
- package/package.json +8 -10
- package/src/__tests__/adapter-json-roundtrip.test.ts +1 -1
- package/src/__tests__/agent-resolution-llm-free.test.ts +39 -0
- package/src/__tests__/build-step-entry.test.ts +203 -0
- package/src/__tests__/clear-thread-failed-attempts.test.ts +122 -0
- package/src/__tests__/config.test.ts +33 -321
- package/src/__tests__/current-role.test.ts +7 -6
- package/src/__tests__/e2e-mock-agent.test.ts +20 -23
- package/src/__tests__/fixtures/e2e-count.workflow.yaml +1 -0
- package/src/__tests__/fixtures/e2e-linear.workflow.yaml +1 -0
- package/src/__tests__/fixtures/{e2e-mustache.workflow.yaml → e2e-liquid.workflow.yaml} +3 -2
- package/src/__tests__/fixtures/e2e-loop.workflow.yaml +1 -0
- package/src/__tests__/fixtures/e2e-suspend.mock.yaml +2 -2
- package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +6 -10
- package/src/__tests__/issue-180-workflow-ref-removed.test.ts +43 -0
- package/src/__tests__/moderator-evaluate.test.ts +9 -52
- package/src/__tests__/pid-recycling.test.ts +328 -0
- package/src/__tests__/prompt.test.ts +397 -0
- package/src/__tests__/resolve-head-hash.test.ts +4 -4
- package/src/__tests__/setup-agent-discovery.test.ts +26 -51
- package/src/__tests__/setup-complexity.test.ts +1 -203
- package/src/__tests__/setup-no-llm.test.ts +68 -0
- package/src/__tests__/solve-issue-tea-worktree.test.ts +24 -30
- package/src/__tests__/step-ask.test.ts +670 -0
- package/src/__tests__/step-show-json.test.ts +1 -0
- package/src/__tests__/step-timing.test.ts +2 -0
- package/src/__tests__/store-global-cas.test.ts +2 -2
- package/src/__tests__/store-unified-threads.test.ts +9 -9
- package/src/__tests__/thread-cancel-status.test.ts +6 -6
- package/src/__tests__/thread-list-filters.test.ts +434 -8
- package/src/__tests__/thread-poke.test.ts +545 -0
- package/src/__tests__/thread-resume.test.ts +10 -14
- package/src/__tests__/thread-show-status.test.ts +17 -29
- package/src/__tests__/thread-suspend-step.test.ts +8 -14
- package/src/__tests__/thread-suspended-display.test.ts +10 -22
- package/src/__tests__/thread.test.ts +4 -4
- package/src/__tests__/validate-semantic.test.ts +59 -31
- package/src/__tests__/workflow-list-recursive.test.ts +370 -0
- package/src/__tests__/workflow-resolution.test.ts +39 -21
- package/src/__tests__/workflow-show-resolution.test.ts +285 -0
- package/src/__tests__/workflow-validate.test.ts +806 -0
- package/src/background/background.ts +88 -6
- package/src/background/index.ts +2 -0
- package/src/background/types.ts +1 -0
- package/src/cli.ts +97 -47
- package/src/commands/config.ts +7 -35
- package/src/commands/prompt.ts +15 -2
- package/src/commands/setup.ts +29 -357
- package/src/commands/step.ts +339 -12
- package/src/commands/thread.ts +463 -169
- package/src/commands/workflow.ts +159 -4
- package/src/moderator/__tests__/evaluate.test.ts +34 -17
- package/src/moderator/evaluate.ts +5 -17
- package/src/moderator/index.ts +1 -6
- package/src/moderator/types.ts +6 -14
- package/src/schemas.ts +13 -3
- package/src/store.ts +86 -20
- package/src/validate-semantic.ts +109 -78
- package/src/validate.ts +27 -0
- package/dist/__tests__/setup-validate.test.d.ts +0 -2
- package/dist/__tests__/setup-validate.test.d.ts.map +0 -1
- package/dist/__tests__/setup-validate.test.js +0 -108
- package/dist/__tests__/setup-validate.test.js.map +0 -1
- package/src/__tests__/setup-validate.test.ts +0 -148
- /package/src/__tests__/fixtures/{e2e-mustache.mock.yaml → e2e-liquid.mock.yaml} +0 -0
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { putSchema } from "@ocas/core";
|
|
5
4
|
import type { CasRef, ThreadId } from "@united-workforce/protocol";
|
|
6
5
|
import { describe, expect, test } from "vitest";
|
|
7
|
-
import { createMarker, deleteMarker } from "../background/index.js";
|
|
6
|
+
import { createMarker, deleteMarker, getProcessStartTime } from "../background/index.js";
|
|
8
7
|
import { cmdThreadShow, cmdThreadStart } from "../commands/thread.js";
|
|
9
8
|
import { completeThread, createUwfStore, loadAllThreads, setThread } from "../store.js";
|
|
10
9
|
|
|
11
|
-
const OUTPUT_SCHEMA = {
|
|
12
|
-
type: "object" as const,
|
|
13
|
-
properties: {
|
|
14
|
-
$status: { type: "string" as const },
|
|
15
|
-
question: { type: "string" as const },
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
10
|
const TEST_WORKFLOW_YAML = `
|
|
20
11
|
name: test-status
|
|
21
12
|
description: Test workflow for status field
|
|
@@ -59,15 +50,12 @@ roles:
|
|
|
59
50
|
capabilities: ["coding"]
|
|
60
51
|
procedure: Work
|
|
61
52
|
output: |
|
|
62
|
-
$status: "
|
|
63
|
-
question: "Which API?"
|
|
53
|
+
$status: "done"
|
|
64
54
|
frontmatter:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
$status: { const: "needs_input" }
|
|
70
|
-
question: { type: string }
|
|
55
|
+
type: object
|
|
56
|
+
required: ["$status"]
|
|
57
|
+
properties:
|
|
58
|
+
$status: { const: "done" }
|
|
71
59
|
graph:
|
|
72
60
|
$START:
|
|
73
61
|
new:
|
|
@@ -79,9 +67,9 @@ graph:
|
|
|
79
67
|
prompt: "Resume work"
|
|
80
68
|
location: null
|
|
81
69
|
worker:
|
|
82
|
-
|
|
83
|
-
role: $
|
|
84
|
-
prompt: "
|
|
70
|
+
done:
|
|
71
|
+
role: $END
|
|
72
|
+
prompt: "Done"
|
|
85
73
|
location: null
|
|
86
74
|
`;
|
|
87
75
|
|
|
@@ -97,8 +85,7 @@ async function insertStepNode(
|
|
|
97
85
|
if (headEntry === undefined) throw new Error(`thread ${threadId} not in index`);
|
|
98
86
|
const head = headEntry.head;
|
|
99
87
|
|
|
100
|
-
const
|
|
101
|
-
const outputHash = await uwf.store.cas.put(outputSchemaHash, outputPayload);
|
|
88
|
+
const outputHash = await uwf.store.cas.put(uwf.schemas.suspendOutput, outputPayload);
|
|
102
89
|
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "detail-placeholder");
|
|
103
90
|
|
|
104
91
|
const headNode = uwf.store.cas.get(head);
|
|
@@ -183,6 +170,7 @@ describe("thread show status field", () => {
|
|
|
183
170
|
workflow,
|
|
184
171
|
pid: process.pid,
|
|
185
172
|
startedAt: Date.now(),
|
|
173
|
+
processStartTime: getProcessStartTime(process.pid),
|
|
186
174
|
});
|
|
187
175
|
|
|
188
176
|
try {
|
|
@@ -216,11 +204,11 @@ describe("thread show status field", () => {
|
|
|
216
204
|
const head = index[threadId]!.head;
|
|
217
205
|
if (!head) throw new Error("Thread not found in index");
|
|
218
206
|
|
|
219
|
-
completeThread(uwfForIndex.varStore, threadId, "
|
|
207
|
+
completeThread(uwfForIndex.varStore, threadId, "end");
|
|
220
208
|
|
|
221
209
|
const result = await cmdThreadShow(storageRoot, threadId);
|
|
222
210
|
|
|
223
|
-
expect(result.status).toBe("
|
|
211
|
+
expect(result.status).toBe("end");
|
|
224
212
|
expect(result.done).toBe(true);
|
|
225
213
|
expect(result.background).toBe(null);
|
|
226
214
|
expect(result.thread).toBe(threadId);
|
|
@@ -274,11 +262,11 @@ describe("thread show status field", () => {
|
|
|
274
262
|
const head = index[threadId]!.head;
|
|
275
263
|
if (!head) throw new Error("Thread not found in index");
|
|
276
264
|
|
|
277
|
-
completeThread(uwfForIndex.varStore, threadId, "
|
|
265
|
+
completeThread(uwfForIndex.varStore, threadId, "end");
|
|
278
266
|
|
|
279
267
|
const result = await cmdThreadShow(storageRoot, threadId);
|
|
280
268
|
|
|
281
|
-
expect(result.status).toBe("
|
|
269
|
+
expect(result.status).toBe("end");
|
|
282
270
|
expect(result.done).toBe(true);
|
|
283
271
|
expect(result.background).toBe(null);
|
|
284
272
|
|
|
@@ -300,8 +288,8 @@ describe("thread show status field", () => {
|
|
|
300
288
|
const threadId = startResult.thread as ThreadId;
|
|
301
289
|
|
|
302
290
|
await insertStepNode(storageRoot, threadId, "worker", {
|
|
303
|
-
$status: "
|
|
304
|
-
|
|
291
|
+
$status: "$SUSPEND",
|
|
292
|
+
reason: "Please clarify: Which API?",
|
|
305
293
|
});
|
|
306
294
|
|
|
307
295
|
const result = await cmdThreadShow(storageRoot, threadId);
|
|
@@ -62,13 +62,7 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|
|
62
62
|
new: { role: "worker", prompt: "Start work", location: null },
|
|
63
63
|
resume: { role: "worker", prompt: "Resume work", location: null },
|
|
64
64
|
},
|
|
65
|
-
worker: {
|
|
66
|
-
needs_input: {
|
|
67
|
-
role: "$SUSPEND",
|
|
68
|
-
prompt: "Please clarify: {{{question}}}",
|
|
69
|
-
location: null,
|
|
70
|
-
},
|
|
71
|
-
},
|
|
65
|
+
worker: {},
|
|
72
66
|
},
|
|
73
67
|
});
|
|
74
68
|
|
|
@@ -81,9 +75,9 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|
|
81
75
|
const threadId = "01SUSPENDSTEPTEST0000000" as ThreadId;
|
|
82
76
|
await seedThreads(tmpDir, { [threadId]: startHash });
|
|
83
77
|
|
|
84
|
-
const outputHash = await store.cas.put(
|
|
85
|
-
$status: "
|
|
86
|
-
|
|
78
|
+
const outputHash = await store.cas.put(schemas.suspendOutput, {
|
|
79
|
+
$status: "$SUSPEND",
|
|
80
|
+
reason: "Please clarify: Which API?",
|
|
87
81
|
});
|
|
88
82
|
const detailHash = await store.cas.put(schemas.text, "mock detail");
|
|
89
83
|
|
|
@@ -109,7 +103,7 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|
|
109
103
|
stepHash,
|
|
110
104
|
detailHash,
|
|
111
105
|
role: "worker",
|
|
112
|
-
frontmatter: { $status: "
|
|
106
|
+
frontmatter: { $status: "$SUSPEND", reason: "Please clarify: Which API?" },
|
|
113
107
|
body: "",
|
|
114
108
|
startedAtMs,
|
|
115
109
|
completedAtMs,
|
|
@@ -119,7 +113,7 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|
|
119
113
|
const configPath = join(tmpDir, "config.yaml");
|
|
120
114
|
await writeFile(
|
|
121
115
|
configPath,
|
|
122
|
-
`defaultAgent: uwf-hermes\
|
|
116
|
+
`defaultAgent: uwf-hermes\nagentOverrides: null\nagents:\n uwf-hermes:\n command: uwf-hermes\n`,
|
|
123
117
|
);
|
|
124
118
|
|
|
125
119
|
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
|
|
@@ -154,8 +148,8 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|
|
154
148
|
|
|
155
149
|
const outputNode = storeAfter.cas.get(outputHash);
|
|
156
150
|
expect(outputNode?.payload).toEqual({
|
|
157
|
-
$status: "
|
|
158
|
-
|
|
151
|
+
$status: "$SUSPEND",
|
|
152
|
+
reason: "Please clarify: Which API?",
|
|
159
153
|
});
|
|
160
154
|
|
|
161
155
|
const { createUwfStore, getThread } = await import("../store.js");
|
|
@@ -59,13 +59,7 @@ describe("suspended thread display", () => {
|
|
|
59
59
|
new: { role: "worker", prompt: "Start work", location: null },
|
|
60
60
|
resume: { role: "worker", prompt: "Resume work", location: null },
|
|
61
61
|
},
|
|
62
|
-
worker: {
|
|
63
|
-
needs_input: {
|
|
64
|
-
role: "$SUSPEND",
|
|
65
|
-
prompt: "Please provide more details: {{{question}}}",
|
|
66
|
-
location: null,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
62
|
+
worker: {},
|
|
69
63
|
},
|
|
70
64
|
});
|
|
71
65
|
|
|
@@ -77,9 +71,9 @@ describe("suspended thread display", () => {
|
|
|
77
71
|
|
|
78
72
|
// Create suspended thread
|
|
79
73
|
const suspendedThreadId = "01SUSPENDEDTHREAD0000000" as ThreadId;
|
|
80
|
-
const outputHash = await uwf.store.cas.put(
|
|
81
|
-
$status: "
|
|
82
|
-
|
|
74
|
+
const outputHash = await uwf.store.cas.put(uwf.schemas.suspendOutput, {
|
|
75
|
+
$status: "$SUSPEND",
|
|
76
|
+
reason: "Please provide more details: What is the target API?",
|
|
83
77
|
});
|
|
84
78
|
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "mock detail");
|
|
85
79
|
|
|
@@ -118,8 +112,8 @@ describe("suspended thread display", () => {
|
|
|
118
112
|
[idleThreadId]: idleEntry,
|
|
119
113
|
});
|
|
120
114
|
|
|
121
|
-
// Test thread list
|
|
122
|
-
const listResult = await cmdThreadList(tmpDir, null, null, null, null, null);
|
|
115
|
+
// Test thread list — pass showAll=true to include suspended threads
|
|
116
|
+
const listResult = await cmdThreadList(tmpDir, null, null, null, null, null, true);
|
|
123
117
|
|
|
124
118
|
// Find the suspended and idle threads in results
|
|
125
119
|
const suspendedItem = listResult.find((item) => item.thread === suspendedThreadId);
|
|
@@ -169,13 +163,7 @@ describe("suspended thread display", () => {
|
|
|
169
163
|
new: { role: "worker", prompt: "Start work", location: null },
|
|
170
164
|
resume: { role: "worker", prompt: "Resume work", location: null },
|
|
171
165
|
},
|
|
172
|
-
worker: {
|
|
173
|
-
needs_input: {
|
|
174
|
-
role: "$SUSPEND",
|
|
175
|
-
prompt: "Need clarification: {{{question}}}",
|
|
176
|
-
location: null,
|
|
177
|
-
},
|
|
178
|
-
},
|
|
166
|
+
worker: {},
|
|
179
167
|
},
|
|
180
168
|
});
|
|
181
169
|
|
|
@@ -186,9 +174,9 @@ describe("suspended thread display", () => {
|
|
|
186
174
|
});
|
|
187
175
|
|
|
188
176
|
const threadId = "01SUSPENDSHOW000000000" as ThreadId;
|
|
189
|
-
const outputHash = await uwf.store.cas.put(
|
|
190
|
-
$status: "
|
|
191
|
-
|
|
177
|
+
const outputHash = await uwf.store.cas.put(uwf.schemas.suspendOutput, {
|
|
178
|
+
$status: "$SUSPEND",
|
|
179
|
+
reason: "Need clarification: Which database to use?",
|
|
192
180
|
});
|
|
193
181
|
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "mock detail");
|
|
194
182
|
|
|
@@ -752,7 +752,7 @@ describe("cmdStepList with completed threads", () => {
|
|
|
752
752
|
suspendMessage: null,
|
|
753
753
|
completedAt: null,
|
|
754
754
|
});
|
|
755
|
-
completeThread(uwf.varStore, threadId, "
|
|
755
|
+
completeThread(uwf.varStore, threadId, "end");
|
|
756
756
|
|
|
757
757
|
const result = await cmdStepList(tmpDir, threadId);
|
|
758
758
|
|
|
@@ -881,7 +881,7 @@ describe("cmdStepShow with completed threads", () => {
|
|
|
881
881
|
suspendMessage: null,
|
|
882
882
|
completedAt: null,
|
|
883
883
|
});
|
|
884
|
-
completeThread(uwf.varStore, threadId, "
|
|
884
|
+
completeThread(uwf.varStore, threadId, "end");
|
|
885
885
|
|
|
886
886
|
const result = await cmdStepShow(tmpDir, stepHash);
|
|
887
887
|
|
|
@@ -944,7 +944,7 @@ describe("cmdThreadRead with completed threads", () => {
|
|
|
944
944
|
suspendMessage: null,
|
|
945
945
|
completedAt: null,
|
|
946
946
|
});
|
|
947
|
-
completeThread(uwf.varStore, threadId, "
|
|
947
|
+
completeThread(uwf.varStore, threadId, "end");
|
|
948
948
|
|
|
949
949
|
const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, false);
|
|
950
950
|
|
|
@@ -1007,7 +1007,7 @@ describe("cmdThreadRead with completed threads", () => {
|
|
|
1007
1007
|
suspendMessage: null,
|
|
1008
1008
|
completedAt: null,
|
|
1009
1009
|
});
|
|
1010
|
-
completeThread(uwf.varStore, threadId, "
|
|
1010
|
+
completeThread(uwf.varStore, threadId, "end");
|
|
1011
1011
|
|
|
1012
1012
|
const markdown = await cmdThreadRead(
|
|
1013
1013
|
tmpDir,
|
|
@@ -5,6 +5,7 @@ import { validateWorkflow } from "../validate-semantic.js";
|
|
|
5
5
|
/** Build a valid two-role workflow that passes all checks. */
|
|
6
6
|
function makeWorkflow(overrides?: Partial<WorkflowPayload>): WorkflowPayload {
|
|
7
7
|
const base: WorkflowPayload = {
|
|
8
|
+
version: 1,
|
|
8
9
|
name: "test-workflow",
|
|
9
10
|
description: "A test workflow",
|
|
10
11
|
roles: {
|
|
@@ -55,10 +56,10 @@ function makeWorkflow(overrides?: Partial<WorkflowPayload>): WorkflowPayload {
|
|
|
55
56
|
new: { role: "writer", prompt: "Begin writing", location: null },
|
|
56
57
|
resume: { role: "writer", prompt: "Review previous output and continue", location: null },
|
|
57
58
|
},
|
|
58
|
-
writer: { done: { role: "reviewer", prompt: "Review this: {{
|
|
59
|
+
writer: { done: { role: "reviewer", prompt: "Review this: {{ plan }}", location: null } },
|
|
59
60
|
reviewer: {
|
|
60
|
-
approved: { role: "$END", prompt: "Done: {{
|
|
61
|
-
rejected: { role: "writer", prompt: "Fix: {{
|
|
61
|
+
approved: { role: "$END", prompt: "Done: {{ summary }}", location: null },
|
|
62
|
+
rejected: { role: "writer", prompt: "Fix: {{ reason }}", location: null },
|
|
62
63
|
},
|
|
63
64
|
},
|
|
64
65
|
};
|
|
@@ -208,8 +209,6 @@ describe("Suite 2: Graph Structure", () => {
|
|
|
208
209
|
|
|
209
210
|
describe("Suite 3: Status-Edge Consistency", () => {
|
|
210
211
|
test("3.1 user role using _ graph key is treated as an unknown status", () => {
|
|
211
|
-
// "_" is no longer special-cased — it's just a status key that does not
|
|
212
|
-
// match the role's $status enum, so it surfaces as extra/missing keys.
|
|
213
212
|
const wf = makeWorkflow();
|
|
214
213
|
wf.graph.writer = { _: { role: "reviewer", prompt: "Review", location: null } };
|
|
215
214
|
const errors = validateWorkflow(wf);
|
|
@@ -288,7 +287,7 @@ describe("Suite 3b: Enum-Based $status is Rejected", () => {
|
|
|
288
287
|
};
|
|
289
288
|
wf.graph.reviewer = {
|
|
290
289
|
approved: { role: "$END", prompt: "Done", location: null },
|
|
291
|
-
rejected: { role: "writer", prompt: "Fix: {{
|
|
290
|
+
rejected: { role: "writer", prompt: "Fix: {{ comments }}", location: null },
|
|
292
291
|
};
|
|
293
292
|
const errors = validateWorkflow(wf);
|
|
294
293
|
expect(errors.some((e) => e.includes("must define") && e.includes("const"))).toBe(true);
|
|
@@ -307,7 +306,9 @@ describe("Suite 3b: Enum-Based $status is Rejected", () => {
|
|
|
307
306
|
required: ["$status", "plan"],
|
|
308
307
|
} as unknown as string,
|
|
309
308
|
};
|
|
310
|
-
wf.graph.writer = {
|
|
309
|
+
wf.graph.writer = {
|
|
310
|
+
ready: { role: "reviewer", prompt: "Review: {{ plan }}", location: null },
|
|
311
|
+
};
|
|
311
312
|
const errors = validateWorkflow(wf);
|
|
312
313
|
expect(errors.some((e) => e.includes("must define") && e.includes("const"))).toBe(true);
|
|
313
314
|
});
|
|
@@ -352,7 +353,7 @@ describe("Suite 3c: Const-Based Flat Schema", () => {
|
|
|
352
353
|
expect(errors.some((e) => e.includes("extra status keys") && e.includes("extra"))).toBe(true);
|
|
353
354
|
});
|
|
354
355
|
|
|
355
|
-
test("3c.3 flat schema with const $status validates
|
|
356
|
+
test("3c.3 flat schema with const $status validates template vars", () => {
|
|
356
357
|
const wf = makeWorkflow();
|
|
357
358
|
wf.roles.writer = {
|
|
358
359
|
...wf.roles.writer,
|
|
@@ -366,46 +367,36 @@ describe("Suite 3c: Const-Based Flat Schema", () => {
|
|
|
366
367
|
} as unknown as string,
|
|
367
368
|
};
|
|
368
369
|
wf.graph.writer = {
|
|
369
|
-
done: { role: "reviewer", prompt: "Review: {{
|
|
370
|
+
done: { role: "reviewer", prompt: "Review: {{ nonexistent }}", location: null },
|
|
370
371
|
};
|
|
371
372
|
const errors = validateWorkflow(wf);
|
|
372
|
-
expect(
|
|
373
|
-
errors.some(
|
|
374
|
-
(e) => e.includes('prompt variable "nonexistent"') && e.includes('role "writer"'),
|
|
375
|
-
),
|
|
376
|
-
).toBe(true);
|
|
373
|
+
expect(errors.some((e) => e.includes("nonexistent") && e.includes('role "writer"'))).toBe(true);
|
|
377
374
|
});
|
|
378
375
|
});
|
|
379
376
|
|
|
380
|
-
describe("Suite 4:
|
|
381
|
-
test("4.1 prompt references nonexistent variable (
|
|
377
|
+
describe("Suite 4: Template Variable Existence (LiquidJS strict-render)", () => {
|
|
378
|
+
test("4.1 prompt references nonexistent variable (flat schema)", () => {
|
|
382
379
|
const wf = makeWorkflow();
|
|
383
380
|
wf.graph.writer = {
|
|
384
|
-
done: { role: "reviewer", prompt: "Review: {{
|
|
381
|
+
done: { role: "reviewer", prompt: "Review: {{ branch }}", location: null },
|
|
385
382
|
};
|
|
386
383
|
const errors = validateWorkflow(wf);
|
|
387
|
-
expect(
|
|
388
|
-
errors.some(
|
|
389
|
-
(e) => e.includes('prompt variable "branch"') && e.includes('role "writer" frontmatter'),
|
|
390
|
-
),
|
|
391
|
-
).toBe(true);
|
|
384
|
+
expect(errors.some((e) => e.includes("branch") && e.includes('role "writer"'))).toBe(true);
|
|
392
385
|
});
|
|
393
386
|
|
|
394
387
|
test("4.2 prompt references nonexistent variable (multi-exit)", () => {
|
|
395
388
|
const wf = makeWorkflow();
|
|
396
389
|
wf.graph.reviewer = {
|
|
397
|
-
approved: { role: "$END", prompt: "Done: {{
|
|
398
|
-
rejected: { role: "writer", prompt: "Fix: {{
|
|
390
|
+
approved: { role: "$END", prompt: "Done: {{ branch }}", location: null },
|
|
391
|
+
rejected: { role: "writer", prompt: "Fix: {{ reason }}", location: null },
|
|
399
392
|
};
|
|
400
393
|
const errors = validateWorkflow(wf);
|
|
401
394
|
expect(
|
|
402
|
-
errors.some((e) =>
|
|
403
|
-
e.includes('prompt variable "branch" not found in role "reviewer" variant "approved"'),
|
|
404
|
-
),
|
|
395
|
+
errors.some((e) => e.includes("branch") && e.includes("reviewer") && e.includes("approved")),
|
|
405
396
|
).toBe(true);
|
|
406
397
|
});
|
|
407
398
|
|
|
408
|
-
test("4.3 valid
|
|
399
|
+
test("4.3 valid template variables pass", () => {
|
|
409
400
|
const wf = makeWorkflow();
|
|
410
401
|
const errors = validateWorkflow(wf);
|
|
411
402
|
expect(errors).toEqual([]);
|
|
@@ -413,7 +404,9 @@ describe("Suite 4: Mustache Template Variable Existence", () => {
|
|
|
413
404
|
|
|
414
405
|
test("4.4 $status variable is always valid", () => {
|
|
415
406
|
const wf = makeWorkflow();
|
|
416
|
-
wf.graph.writer = {
|
|
407
|
+
wf.graph.writer = {
|
|
408
|
+
done: { role: "reviewer", prompt: "Status: {{ $status }}", location: null },
|
|
409
|
+
};
|
|
417
410
|
const errors = validateWorkflow(wf);
|
|
418
411
|
expect(errors).toEqual([]);
|
|
419
412
|
});
|
|
@@ -469,6 +462,41 @@ describe("Suite 5: oneOf Discriminant Validity", () => {
|
|
|
469
462
|
});
|
|
470
463
|
});
|
|
471
464
|
|
|
465
|
+
describe("Suite 7: $SUSPEND is no longer a valid graph target", () => {
|
|
466
|
+
test("7.1 edge targeting $SUSPEND is rejected with a migration hint", () => {
|
|
467
|
+
const wf = makeWorkflow();
|
|
468
|
+
wf.graph.writer = {
|
|
469
|
+
done: { role: "$SUSPEND", prompt: "Need more info", location: null },
|
|
470
|
+
};
|
|
471
|
+
const errors = validateWorkflow(wf);
|
|
472
|
+
expect(
|
|
473
|
+
errors.some(
|
|
474
|
+
(e) =>
|
|
475
|
+
e.includes("$SUSPEND") &&
|
|
476
|
+
e.includes("no longer a valid graph target") &&
|
|
477
|
+
e.includes('Emit $status: "$SUSPEND"'),
|
|
478
|
+
),
|
|
479
|
+
).toBe(true);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test("7.2 $SUSPEND as a graph node is rejected", () => {
|
|
483
|
+
const wf = makeWorkflow();
|
|
484
|
+
(wf.graph as Record<string, unknown>).$SUSPEND = {
|
|
485
|
+
done: { role: "$END", prompt: "done", location: null },
|
|
486
|
+
};
|
|
487
|
+
const errors = validateWorkflow(wf);
|
|
488
|
+
expect(
|
|
489
|
+
errors.some((e) => e.includes("$SUSPEND") && e.includes("no longer a valid graph node")),
|
|
490
|
+
).toBe(true);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test("7.3 a role emitting $SUSPEND from its output (not the graph) passes", () => {
|
|
494
|
+
const wf = makeWorkflow();
|
|
495
|
+
const errors = validateWorkflow(wf);
|
|
496
|
+
expect(errors.some((e) => e.includes("$SUSPEND"))).toBe(false);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
472
500
|
describe("Suite 6: Multiple Errors Collection", () => {
|
|
473
501
|
test("6.1 multiple errors collected", () => {
|
|
474
502
|
const wf = makeWorkflow();
|
|
@@ -487,8 +515,8 @@ describe("Suite 6: Multiple Errors Collection", () => {
|
|
|
487
515
|
};
|
|
488
516
|
// unknown graph reference
|
|
489
517
|
wf.graph.nonexistent = { done: { role: "$END", prompt: "done", location: null } };
|
|
490
|
-
// bad
|
|
491
|
-
wf.graph.writer = { done: { role: "reviewer", prompt: "{{
|
|
518
|
+
// bad template var
|
|
519
|
+
wf.graph.writer = { done: { role: "reviewer", prompt: "{{ badvar }}", location: null } };
|
|
492
520
|
const errors = validateWorkflow(wf);
|
|
493
521
|
expect(errors.length).toBeGreaterThanOrEqual(3);
|
|
494
522
|
});
|