@united-workforce/cli 0.4.0 → 0.5.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 +30 -3
- package/dist/.build-fingerprint +1 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js +16 -6
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
- package/dist/__tests__/concurrency.test.d.ts +2 -0
- package/dist/__tests__/concurrency.test.d.ts.map +1 -0
- package/dist/__tests__/concurrency.test.js +196 -0
- package/dist/__tests__/concurrency.test.js.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.js +23 -7
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
- package/dist/__tests__/format-text-default.test.d.ts +2 -0
- package/dist/__tests__/format-text-default.test.d.ts.map +1 -0
- package/dist/__tests__/format-text-default.test.js +43 -0
- package/dist/__tests__/format-text-default.test.js.map +1 -0
- package/dist/__tests__/format-text-registry.test.d.ts +2 -0
- package/dist/__tests__/format-text-registry.test.d.ts.map +1 -0
- package/dist/__tests__/format-text-registry.test.js +158 -0
- package/dist/__tests__/format-text-registry.test.js.map +1 -0
- package/dist/__tests__/log-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/log-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/log-text-renderer.test.js +265 -0
- package/dist/__tests__/log-text-renderer.test.js.map +1 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts +2 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts.map +1 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.js +102 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.js.map +1 -0
- package/dist/__tests__/output-mapper-workflow-add.test.d.ts +2 -0
- package/dist/__tests__/output-mapper-workflow-add.test.d.ts.map +1 -0
- package/dist/__tests__/output-mapper-workflow-add.test.js +22 -0
- package/dist/__tests__/output-mapper-workflow-add.test.js.map +1 -0
- package/dist/__tests__/pid-recycling.test.js +9 -7
- package/dist/__tests__/pid-recycling.test.js.map +1 -1
- package/dist/__tests__/prompt.test.js +46 -4
- package/dist/__tests__/prompt.test.js.map +1 -1
- package/dist/__tests__/resolve-head-hash.test.js +8 -0
- package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
- package/dist/__tests__/solve-issue-tea-worktree.test.js +3 -1
- package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
- package/dist/__tests__/step-ask.test.js +9 -1
- package/dist/__tests__/step-ask.test.js.map +1 -1
- package/dist/__tests__/store-unified-threads.test.js +19 -17
- package/dist/__tests__/store-unified-threads.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-status.test.js +19 -13
- package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.js +110 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.js.map +1 -0
- package/dist/__tests__/thread-list-filters.test.js +10 -8
- package/dist/__tests__/thread-list-filters.test.js.map +1 -1
- package/dist/__tests__/thread-list-template-ms-date.test.d.ts +2 -0
- package/dist/__tests__/thread-list-template-ms-date.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js +102 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts +2 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.js +157 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.js.map +1 -0
- package/dist/__tests__/thread-poke.test.js +11 -1
- package/dist/__tests__/thread-poke.test.js.map +1 -1
- package/dist/__tests__/thread-read-xml-tags.test.js +10 -9
- package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -1
- package/dist/__tests__/thread-resume.test.js +11 -1
- package/dist/__tests__/thread-resume.test.js.map +1 -1
- package/dist/__tests__/thread-start-cwd-cli.test.js +15 -3
- package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -1
- package/dist/__tests__/thread-stop-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/thread-stop-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/thread-stop-text-renderer.test.js +148 -0
- package/dist/__tests__/thread-stop-text-renderer.test.js.map +1 -0
- package/dist/__tests__/thread-suspend-step.test.js +5 -2
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
- package/dist/__tests__/thread-test-helpers.d.ts +7 -0
- package/dist/__tests__/thread-test-helpers.d.ts.map +1 -1
- package/dist/__tests__/thread-test-helpers.js +13 -0
- package/dist/__tests__/thread-test-helpers.js.map +1 -1
- package/dist/__tests__/thread.test.js +11 -9
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/__tests__/validate-semantic.test.js +56 -2
- package/dist/__tests__/validate-semantic.test.js.map +1 -1
- package/dist/__tests__/workflow-list-recursive.test.js +10 -7
- package/dist/__tests__/workflow-list-recursive.test.js.map +1 -1
- package/dist/__tests__/workflow-resolution.test.js +10 -7
- package/dist/__tests__/workflow-resolution.test.js.map +1 -1
- package/dist/__tests__/workflow-show-resolution.test.js +10 -7
- package/dist/__tests__/workflow-show-resolution.test.js.map +1 -1
- package/dist/__tests__/workflow-validate.test.js +75 -55
- package/dist/__tests__/workflow-validate.test.js.map +1 -1
- package/dist/__tests__/write-envelope.test.d.ts +2 -0
- package/dist/__tests__/write-envelope.test.d.ts.map +1 -0
- package/dist/__tests__/write-envelope.test.js +201 -0
- package/dist/__tests__/write-envelope.test.js.map +1 -0
- package/dist/cli.js +58 -35
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +12 -0
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +42 -29
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +9 -4
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +51 -7
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/thread.d.ts.map +1 -1
- package/dist/commands/thread.js +44 -2
- package/dist/commands/thread.js.map +1 -1
- package/dist/commands/workflow.d.ts +1 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +2 -6
- package/dist/commands/workflow.js.map +1 -1
- package/dist/concurrency/concurrency.d.ts +34 -0
- package/dist/concurrency/concurrency.d.ts.map +1 -0
- package/dist/concurrency/concurrency.js +216 -0
- package/dist/concurrency/concurrency.js.map +1 -0
- package/dist/concurrency/index.d.ts +3 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/index.js +2 -0
- package/dist/concurrency/index.js.map +1 -0
- package/dist/concurrency/types.d.ts +19 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency/types.js +2 -0
- package/dist/concurrency/types.js.map +1 -0
- package/dist/format.d.ts +69 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +198 -1
- package/dist/format.js.map +1 -1
- package/dist/output-mappers.d.ts +122 -0
- package/dist/output-mappers.d.ts.map +1 -0
- package/dist/output-mappers.js +134 -0
- package/dist/output-mappers.js.map +1 -0
- package/dist/schemas.d.ts +4 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +31 -4
- package/dist/schemas.js.map +1 -1
- package/dist/text-renderers.d.ts +30 -0
- package/dist/text-renderers.d.ts.map +1 -0
- package/dist/text-renderers.js +251 -0
- package/dist/text-renderers.js.map +1 -0
- package/dist/validate-semantic.d.ts.map +1 -1
- package/dist/validate-semantic.js +28 -11
- package/dist/validate-semantic.js.map +1 -1
- package/examples/brainstorm.yaml +130 -0
- package/examples/debate.yaml +169 -0
- package/examples/socratic-questioning.yaml +112 -0
- package/package.json +5 -4
- package/src/__tests__/adapter-json-roundtrip.test.ts +15 -6
- package/src/__tests__/concurrency.test.ts +266 -0
- package/src/__tests__/e2e-mock-agent.test.ts +45 -7
- package/src/__tests__/format-text-default.test.ts +49 -0
- package/src/__tests__/format-text-registry.test.ts +173 -0
- package/src/__tests__/log-text-renderer.test.ts +294 -0
- package/src/__tests__/output-mapper-thread-list-startedat.test.ts +124 -0
- package/src/__tests__/output-mapper-workflow-add.test.ts +24 -0
- package/src/__tests__/pid-recycling.test.ts +9 -8
- package/src/__tests__/prompt.test.ts +48 -4
- package/src/__tests__/resolve-head-hash.test.ts +7 -0
- package/src/__tests__/solve-issue-tea-worktree.test.ts +3 -1
- package/src/__tests__/step-ask.test.ts +8 -1
- package/src/__tests__/store-unified-threads.test.ts +21 -18
- package/src/__tests__/thread-cancel-status.test.ts +21 -14
- package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
- package/src/__tests__/thread-list-filters.test.ts +9 -9
- package/src/__tests__/thread-list-template-ms-date.test.ts +110 -0
- package/src/__tests__/thread-list-workflow-corrupt.test.ts +198 -0
- package/src/__tests__/thread-poke.test.ts +10 -1
- package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
- package/src/__tests__/thread-resume.test.ts +10 -1
- package/src/__tests__/thread-start-cwd-cli.test.ts +15 -3
- package/src/__tests__/thread-stop-text-renderer.test.ts +168 -0
- package/src/__tests__/thread-suspend-step.test.ts +5 -2
- package/src/__tests__/thread-test-helpers.ts +15 -1
- package/src/__tests__/thread.test.ts +10 -10
- package/src/__tests__/validate-semantic.test.ts +59 -2
- package/src/__tests__/workflow-list-recursive.test.ts +9 -9
- package/src/__tests__/workflow-resolution.test.ts +9 -8
- package/src/__tests__/workflow-show-resolution.test.ts +9 -8
- package/src/__tests__/workflow-validate.test.ts +78 -56
- package/src/__tests__/write-envelope.test.ts +257 -0
- package/src/cli.ts +92 -35
- package/src/commands/config.ts +11 -0
- package/src/commands/prompt.ts +42 -29
- package/src/commands/setup.ts +57 -7
- package/src/commands/thread.ts +48 -2
- package/src/commands/workflow.ts +3 -7
- package/src/concurrency/concurrency.ts +245 -0
- package/src/concurrency/index.ts +10 -0
- package/src/concurrency/types.ts +19 -0
- package/src/format.ts +282 -2
- package/src/output-mappers.ts +254 -0
- package/src/schemas.ts +39 -3
- package/src/text-renderers.ts +355 -0
- package/src/validate-semantic.ts +33 -12
|
@@ -28,12 +28,19 @@ const OUTPUT_SCHEMA = {
|
|
|
28
28
|
const THREAD_ID = "01POKESTEPTEST00000000" as ThreadId;
|
|
29
29
|
|
|
30
30
|
let tmpDir: string;
|
|
31
|
+
let savedOcasHome: string | undefined;
|
|
31
32
|
|
|
32
33
|
beforeEach(async () => {
|
|
34
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
33
35
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-poke-test-"));
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
afterEach(async () => {
|
|
39
|
+
if (savedOcasHome === undefined) {
|
|
40
|
+
delete process.env.OCAS_HOME;
|
|
41
|
+
} else {
|
|
42
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
43
|
+
}
|
|
37
44
|
await rm(tmpDir, { recursive: true, force: true });
|
|
38
45
|
});
|
|
39
46
|
|
|
@@ -271,8 +278,10 @@ function runUwf(
|
|
|
271
278
|
casDir: string,
|
|
272
279
|
): { stdout: string; stderr: string; status: number } {
|
|
273
280
|
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
|
|
281
|
+
// Tests parse stdout as bare JSON; default --format text would break that.
|
|
282
|
+
const formatArgs = args.includes("--format") ? args : ["--format", "raw-json", ...args];
|
|
274
283
|
try {
|
|
275
|
-
const stdout = execFileSync(process.execPath, [cliPath, ...
|
|
284
|
+
const stdout = execFileSync(process.execPath, [cliPath, ...formatArgs], {
|
|
276
285
|
encoding: "utf8",
|
|
277
286
|
stdio: ["ignore", "pipe", "pipe"],
|
|
278
287
|
env: {
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { bootstrap, putSchema, type Store } from "@ocas/core";
|
|
5
5
|
import type { CasRef, ThreadId } from "@united-workforce/protocol";
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
7
7
|
import { cmdThreadRead, THREAD_READ_DEFAULT_QUOTA } from "../commands/thread.js";
|
|
8
|
-
import
|
|
9
|
-
import { createUwfStore } from "../store.js";
|
|
10
|
-
import { seedThreads } from "./thread-test-helpers.js";
|
|
8
|
+
import { makeUwfStore, seedThreads } from "./thread-test-helpers.js";
|
|
11
9
|
|
|
12
10
|
// ── schemas used in tests ────────────────────────────────────────────────────
|
|
13
11
|
|
|
@@ -49,13 +47,6 @@ const DETAIL_SCHEMA = {
|
|
|
49
47
|
|
|
50
48
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
51
49
|
|
|
52
|
-
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
53
|
-
const casDir = join(storageRoot, "cas");
|
|
54
|
-
await mkdir(casDir, { recursive: true });
|
|
55
|
-
process.env.OCAS_HOME = casDir;
|
|
56
|
-
return createUwfStore(storageRoot);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
50
|
async function registerDetailSchemas(store: Store) {
|
|
60
51
|
await bootstrap(store);
|
|
61
52
|
const [turn, detail] = await Promise.all([
|
|
@@ -68,12 +59,19 @@ async function registerDetailSchemas(store: Store) {
|
|
|
68
59
|
// ── fixture ───────────────────────────────────────────────────────────────────
|
|
69
60
|
|
|
70
61
|
let tmpDir: string;
|
|
62
|
+
let savedOcasHome: string | undefined;
|
|
71
63
|
|
|
72
64
|
beforeEach(async () => {
|
|
65
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
73
66
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-test-"));
|
|
74
67
|
});
|
|
75
68
|
|
|
76
69
|
afterEach(async () => {
|
|
70
|
+
if (savedOcasHome === undefined) {
|
|
71
|
+
delete process.env.OCAS_HOME;
|
|
72
|
+
} else {
|
|
73
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
74
|
+
}
|
|
77
75
|
await rm(tmpDir, { recursive: true, force: true });
|
|
78
76
|
});
|
|
79
77
|
|
|
@@ -27,12 +27,19 @@ const SUSPEND_MESSAGE = "Please clarify: Which API?";
|
|
|
27
27
|
type MockAgentMode = "suspend" | "ok";
|
|
28
28
|
|
|
29
29
|
let tmpDir: string;
|
|
30
|
+
let savedOcasHome: string | undefined;
|
|
30
31
|
|
|
31
32
|
beforeEach(async () => {
|
|
33
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
32
34
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-resume-test-"));
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
afterEach(async () => {
|
|
38
|
+
if (savedOcasHome === undefined) {
|
|
39
|
+
delete process.env.OCAS_HOME;
|
|
40
|
+
} else {
|
|
41
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
42
|
+
}
|
|
36
43
|
await rm(tmpDir, { recursive: true, force: true });
|
|
37
44
|
});
|
|
38
45
|
|
|
@@ -184,8 +191,10 @@ function runUwf(
|
|
|
184
191
|
casDir: string,
|
|
185
192
|
): { stdout: string; stderr: string; status: number } {
|
|
186
193
|
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
|
|
194
|
+
// Tests parse stdout as bare JSON; default --format text would break that.
|
|
195
|
+
const formatArgs = args.includes("--format") ? args : ["--format", "raw-json", ...args];
|
|
187
196
|
try {
|
|
188
|
-
const stdout = execFileSync(process.execPath, [cliPath, ...
|
|
197
|
+
const stdout = execFileSync(process.execPath, [cliPath, ...formatArgs], {
|
|
189
198
|
encoding: "utf8",
|
|
190
199
|
stdio: ["ignore", "pipe", "pipe"],
|
|
191
200
|
env: {
|
|
@@ -150,16 +150,28 @@ graph:
|
|
|
150
150
|
// Verify CLI accepts --cwd option (no error thrown)
|
|
151
151
|
const output = execFileSync(
|
|
152
152
|
process.execPath,
|
|
153
|
-
[
|
|
153
|
+
[
|
|
154
|
+
uwfBin,
|
|
155
|
+
"--format",
|
|
156
|
+
"raw-json",
|
|
157
|
+
"thread",
|
|
158
|
+
"start",
|
|
159
|
+
"test-cwd-cli",
|
|
160
|
+
"-p",
|
|
161
|
+
"test prompt",
|
|
162
|
+
"--cwd",
|
|
163
|
+
testCwd,
|
|
164
|
+
],
|
|
154
165
|
{
|
|
155
166
|
env: { ...process.env, UWF_HOME: storageRoot, OCAS_HOME: casDir },
|
|
156
167
|
encoding: "utf8",
|
|
157
168
|
},
|
|
158
169
|
);
|
|
159
170
|
|
|
171
|
+
// raw-json envelope value for thread-start: { threadId, workflowHash }
|
|
160
172
|
const result = JSON.parse(output);
|
|
161
|
-
expect(result.
|
|
162
|
-
expect(result.
|
|
173
|
+
expect(result.threadId).toBeDefined();
|
|
174
|
+
expect(result.workflowHash).toBeDefined();
|
|
163
175
|
|
|
164
176
|
// The fact that we got here without throwing means CLI accepted the --cwd option
|
|
165
177
|
// The actual cwd functionality is tested by the other tests using cmdThreadStart directly
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { formatOutput, getTextRenderer, TEXT_RENDERERS } from "../format.js";
|
|
3
|
+
import { renderThreadStop } from "../text-renderers.js";
|
|
4
|
+
|
|
5
|
+
describe("thread stop — text renderer registration", () => {
|
|
6
|
+
test("TEXT_RENDERERS contains 'thread stop'", () => {
|
|
7
|
+
expect(getTextRenderer("thread stop")).toBeDefined();
|
|
8
|
+
expect(typeof getTextRenderer("thread stop")).toBe("function");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("TEXT_RENDERERS['thread stop'] is the same reference as renderThreadStop", () => {
|
|
12
|
+
expect(TEXT_RENDERERS["thread stop"]).toBe(renderThreadStop);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("renderThreadStop is exported from text-renderers.ts", () => {
|
|
16
|
+
expect(typeof renderThreadStop).toBe("function");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("renderThreadStop — output shape (stopped=true)", () => {
|
|
21
|
+
test("returns a string for full payload", () => {
|
|
22
|
+
const out = renderThreadStop({
|
|
23
|
+
thread: "01JTEST00000000000000STOP1",
|
|
24
|
+
stopped: true,
|
|
25
|
+
});
|
|
26
|
+
expect(typeof out).toBe("string");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("includes the stopped thread's ULID", () => {
|
|
30
|
+
const out = renderThreadStop({
|
|
31
|
+
thread: "01JTEST00000000000000STOP1",
|
|
32
|
+
stopped: true,
|
|
33
|
+
});
|
|
34
|
+
expect(out).toContain("01JTEST00000000000000STOP1");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("indicates stopped status (yes)", () => {
|
|
38
|
+
const out = renderThreadStop({
|
|
39
|
+
thread: "01JTEST00000000000000STOP1",
|
|
40
|
+
stopped: true,
|
|
41
|
+
});
|
|
42
|
+
const lower = out.toLowerCase();
|
|
43
|
+
const hasStopMarker = lower.includes("stopped") && lower.includes("yes");
|
|
44
|
+
expect(hasStopMarker).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("does NOT begin with '{' or '[' (not raw JSON)", () => {
|
|
48
|
+
const out = renderThreadStop({
|
|
49
|
+
thread: "01JTEST00000000000000STOP1",
|
|
50
|
+
stopped: true,
|
|
51
|
+
});
|
|
52
|
+
const trimmed = out.trimStart();
|
|
53
|
+
expect(trimmed.startsWith("{")).toBe(false);
|
|
54
|
+
expect(trimmed.startsWith("[")).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("does NOT contain literal 'undefined'", () => {
|
|
58
|
+
const out = renderThreadStop({
|
|
59
|
+
thread: "01JTEST00000000000000STOP1",
|
|
60
|
+
stopped: true,
|
|
61
|
+
});
|
|
62
|
+
expect(out).not.toContain("undefined");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("renderThreadStop — stopped=false variant", () => {
|
|
67
|
+
test("returns a string for stopped=false payload", () => {
|
|
68
|
+
const out = renderThreadStop({
|
|
69
|
+
thread: "01JTEST00000000000000STOP1",
|
|
70
|
+
stopped: false,
|
|
71
|
+
});
|
|
72
|
+
expect(typeof out).toBe("string");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("includes the thread's ULID even when stopped=false", () => {
|
|
76
|
+
const out = renderThreadStop({
|
|
77
|
+
thread: "01JTEST00000000000000STOP1",
|
|
78
|
+
stopped: false,
|
|
79
|
+
});
|
|
80
|
+
expect(out).toContain("01JTEST00000000000000STOP1");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("indicates not-stopped status (no)", () => {
|
|
84
|
+
const out = renderThreadStop({
|
|
85
|
+
thread: "01JTEST00000000000000STOP1",
|
|
86
|
+
stopped: false,
|
|
87
|
+
});
|
|
88
|
+
const lower = out.toLowerCase();
|
|
89
|
+
const hasNoMarker = lower.includes("stopped") && lower.includes("no");
|
|
90
|
+
expect(hasNoMarker).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("does NOT contain literal 'undefined' for stopped=false", () => {
|
|
94
|
+
const out = renderThreadStop({
|
|
95
|
+
thread: "01JTEST00000000000000STOP1",
|
|
96
|
+
stopped: false,
|
|
97
|
+
});
|
|
98
|
+
expect(out).not.toContain("undefined");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("renderThreadStop — partial / missing data", () => {
|
|
103
|
+
test("missing 'stopped' field — returns string, no throw, no 'undefined'", () => {
|
|
104
|
+
const out = renderThreadStop({ thread: "01JTEST00000000000000STOP1" });
|
|
105
|
+
expect(typeof out).toBe("string");
|
|
106
|
+
expect(out).not.toContain("undefined");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("missing 'thread' field — returns string, no throw, no 'undefined'", () => {
|
|
110
|
+
const out = renderThreadStop({ stopped: true });
|
|
111
|
+
expect(typeof out).toBe("string");
|
|
112
|
+
expect(out).not.toContain("undefined");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("empty object — returns string, no throw, no 'undefined'", () => {
|
|
116
|
+
const out = renderThreadStop({});
|
|
117
|
+
expect(typeof out).toBe("string");
|
|
118
|
+
expect(out).not.toContain("undefined");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("null payload — returns string, no throw", () => {
|
|
122
|
+
expect(() => renderThreadStop(null)).not.toThrow();
|
|
123
|
+
const out = renderThreadStop(null);
|
|
124
|
+
expect(typeof out).toBe("string");
|
|
125
|
+
expect(out).not.toContain("undefined");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("non-object payload (string) — returns string, no throw", () => {
|
|
129
|
+
expect(() => renderThreadStop("oops")).not.toThrow();
|
|
130
|
+
const out = renderThreadStop("oops");
|
|
131
|
+
expect(typeof out).toBe("string");
|
|
132
|
+
expect(out).not.toContain("undefined");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("formatOutput integration — thread stop", () => {
|
|
137
|
+
test("formatOutput(data, 'text', 'thread stop') uses renderer", () => {
|
|
138
|
+
const data = { thread: "01JTEST00000000000000STOP1", stopped: true };
|
|
139
|
+
const out = formatOutput(data, "text", "thread stop");
|
|
140
|
+
expect(typeof out).toBe("string");
|
|
141
|
+
expect(out).not.toContain("undefined");
|
|
142
|
+
expect(out.trimStart().startsWith("{")).toBe(false);
|
|
143
|
+
expect(out).toContain("01JTEST00000000000000STOP1");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("formatOutput(data, 'json', 'thread stop') still emits parseable JSON", () => {
|
|
147
|
+
const data = { thread: "01JTEST00000000000000STOP1", stopped: true };
|
|
148
|
+
const out = formatOutput(data, "json", "thread stop");
|
|
149
|
+
const parsed = JSON.parse(out);
|
|
150
|
+
expect(parsed).toEqual(data);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("formatOutput(data, 'yaml', 'thread stop') still emits YAML", () => {
|
|
154
|
+
const data = { thread: "01JTEST00000000000000STOP1", stopped: true };
|
|
155
|
+
const out = formatOutput(data, "yaml", "thread stop");
|
|
156
|
+
expect(typeof out).toBe("string");
|
|
157
|
+
expect(out).toContain("thread:");
|
|
158
|
+
expect(out).toContain("stopped:");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("formatOutput for stopped=false variant under text format", () => {
|
|
162
|
+
const data = { thread: "01JTEST00000000000000STOP1", stopped: false };
|
|
163
|
+
const out = formatOutput(data, "text", "thread stop");
|
|
164
|
+
expect(typeof out).toBe("string");
|
|
165
|
+
expect(out).not.toContain("undefined");
|
|
166
|
+
expect(out).toContain("01JTEST00000000000000STOP1");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -119,7 +119,7 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|
|
119
119
|
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
|
|
120
120
|
const stdout = execFileSync(
|
|
121
121
|
process.execPath,
|
|
122
|
-
[cliPath, "thread", "exec", threadId, "--agent", mockAgentPath],
|
|
122
|
+
[cliPath, "--format", "raw-json", "thread", "exec", threadId, "--agent", mockAgentPath],
|
|
123
123
|
{
|
|
124
124
|
encoding: "utf8",
|
|
125
125
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -133,7 +133,10 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|
|
133
133
|
},
|
|
134
134
|
);
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
// thread exec envelope value: { threadId, workflowHash, steps: [...] }
|
|
137
|
+
const envelope = JSON.parse(stdout.trim());
|
|
138
|
+
expect(envelope.steps).toHaveLength(1);
|
|
139
|
+
const cliOutput = envelope.steps[0];
|
|
137
140
|
expect(cliOutput.status).toBe("suspended");
|
|
138
141
|
expect(cliOutput.head).toBe(stepHash);
|
|
139
142
|
expect(cliOutput.suspendedRole).toBe("worker");
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import type { CasRef, ThreadId, ThreadIndexEntry } from "@united-workforce/protocol";
|
|
2
4
|
import { createThreadIndexEntry } from "@united-workforce/protocol";
|
|
3
|
-
import { createUwfStore, setThread } from "../store.js";
|
|
5
|
+
import { createUwfStore, setThread, type UwfStore } from "../store.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create an isolated UwfStore backed by a tmpdir.
|
|
9
|
+
* Sets process.env.OCAS_HOME so CAS resolves to the test's directory.
|
|
10
|
+
* Callers MUST save/restore OCAS_HOME in afterEach.
|
|
11
|
+
*/
|
|
12
|
+
export async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
13
|
+
const casDir = join(storageRoot, "cas");
|
|
14
|
+
await mkdir(casDir, { recursive: true });
|
|
15
|
+
process.env.OCAS_HOME = casDir;
|
|
16
|
+
return createUwfStore(storageRoot);
|
|
17
|
+
}
|
|
4
18
|
|
|
5
19
|
async function ensureHeadInCas(
|
|
6
20
|
uwf: Awaited<ReturnType<typeof createUwfStore>>,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { bootstrap, putSchema, type Store } from "@ocas/core";
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
THREAD_READ_DEFAULT_QUOTA,
|
|
12
12
|
} from "../commands/thread.js";
|
|
13
13
|
import type { UwfStore } from "../store.js";
|
|
14
|
-
import { completeThread,
|
|
15
|
-
import { seedThreads } from "./thread-test-helpers.js";
|
|
14
|
+
import { completeThread, setThread } from "../store.js";
|
|
15
|
+
import { makeUwfStore, seedThreads } from "./thread-test-helpers.js";
|
|
16
16
|
|
|
17
17
|
// ── schemas used in tests ────────────────────────────────────────────────────
|
|
18
18
|
|
|
@@ -54,13 +54,6 @@ const DETAIL_SCHEMA = {
|
|
|
54
54
|
|
|
55
55
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
56
56
|
|
|
57
|
-
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
58
|
-
const casDir = join(storageRoot, "cas");
|
|
59
|
-
await mkdir(casDir, { recursive: true });
|
|
60
|
-
process.env.OCAS_HOME = casDir;
|
|
61
|
-
return createUwfStore(storageRoot);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
57
|
async function registerDetailSchemas(store: Store) {
|
|
65
58
|
await bootstrap(store);
|
|
66
59
|
const [turn, detail] = await Promise.all([
|
|
@@ -73,12 +66,19 @@ async function registerDetailSchemas(store: Store) {
|
|
|
73
66
|
// ── fixture ───────────────────────────────────────────────────────────────────
|
|
74
67
|
|
|
75
68
|
let tmpDir: string;
|
|
69
|
+
let savedOcasHome: string | undefined;
|
|
76
70
|
|
|
77
71
|
beforeEach(async () => {
|
|
72
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
78
73
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-test-"));
|
|
79
74
|
});
|
|
80
75
|
|
|
81
76
|
afterEach(async () => {
|
|
77
|
+
if (savedOcasHome === undefined) {
|
|
78
|
+
delete process.env.OCAS_HOME;
|
|
79
|
+
} else {
|
|
80
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
81
|
+
}
|
|
82
82
|
await rm(tmpDir, { recursive: true, force: true });
|
|
83
83
|
});
|
|
84
84
|
|
|
@@ -402,13 +402,13 @@ describe("Suite 4: Template Variable Existence (LiquidJS strict-render)", () =>
|
|
|
402
402
|
expect(errors).toEqual([]);
|
|
403
403
|
});
|
|
404
404
|
|
|
405
|
-
test("4.4 $status
|
|
405
|
+
test("4.4 $status in template is rejected ($ prefix invalid in LiquidJS)", () => {
|
|
406
406
|
const wf = makeWorkflow();
|
|
407
407
|
wf.graph.writer = {
|
|
408
408
|
done: { role: "reviewer", prompt: "Status: {{ $status }}", location: null },
|
|
409
409
|
};
|
|
410
410
|
const errors = validateWorkflow(wf);
|
|
411
|
-
expect(errors).
|
|
411
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
412
412
|
});
|
|
413
413
|
});
|
|
414
414
|
|
|
@@ -521,3 +521,60 @@ describe("Suite 6: Multiple Errors Collection", () => {
|
|
|
521
521
|
expect(errors.length).toBeGreaterThanOrEqual(3);
|
|
522
522
|
});
|
|
523
523
|
});
|
|
524
|
+
|
|
525
|
+
describe("Suite 7: Reserved Frontmatter Properties", () => {
|
|
526
|
+
test("7.1 flat schema with _body property is rejected", () => {
|
|
527
|
+
const wf = makeWorkflow();
|
|
528
|
+
wf.roles.writer = {
|
|
529
|
+
...wf.roles.writer,
|
|
530
|
+
frontmatter: {
|
|
531
|
+
type: "object",
|
|
532
|
+
properties: {
|
|
533
|
+
$status: { const: "done" },
|
|
534
|
+
_body: { type: "string" },
|
|
535
|
+
},
|
|
536
|
+
required: ["$status"],
|
|
537
|
+
} as unknown as string,
|
|
538
|
+
};
|
|
539
|
+
wf.graph.writer = { done: { role: "reviewer", prompt: "ok", location: null } };
|
|
540
|
+
const errors = validateWorkflow(wf);
|
|
541
|
+
expect(errors.some((e) => e.includes("_body") && e.includes("reserved"))).toBe(true);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("7.2 oneOf schema with _body in a variant is rejected", () => {
|
|
545
|
+
const wf = makeWorkflow();
|
|
546
|
+
wf.roles.writer = {
|
|
547
|
+
...wf.roles.writer,
|
|
548
|
+
frontmatter: {
|
|
549
|
+
oneOf: [
|
|
550
|
+
{
|
|
551
|
+
properties: {
|
|
552
|
+
$status: { const: "done" },
|
|
553
|
+
_body: { type: "string" },
|
|
554
|
+
},
|
|
555
|
+
required: ["$status"],
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
properties: {
|
|
559
|
+
$status: { const: "failed" },
|
|
560
|
+
reason: { type: "string" },
|
|
561
|
+
},
|
|
562
|
+
required: ["$status", "reason"],
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
} as unknown as string,
|
|
566
|
+
};
|
|
567
|
+
wf.graph.writer = {
|
|
568
|
+
done: { role: "reviewer", prompt: "ok", location: null },
|
|
569
|
+
failed: { role: "$END", prompt: "failed", location: null },
|
|
570
|
+
};
|
|
571
|
+
const errors = validateWorkflow(wf);
|
|
572
|
+
expect(errors.some((e) => e.includes("_body") && e.includes("reserved"))).toBe(true);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test("7.3 schema without _body passes", () => {
|
|
576
|
+
const wf = makeWorkflow();
|
|
577
|
+
const errors = validateWorkflow(wf);
|
|
578
|
+
expect(errors.some((e) => e.includes("_body"))).toBe(false);
|
|
579
|
+
});
|
|
580
|
+
});
|
|
@@ -6,18 +6,11 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
|
6
6
|
import { stringify } from "yaml";
|
|
7
7
|
import { cmdThreadStart } from "../commands/thread.js";
|
|
8
8
|
import { cmdWorkflowList } from "../commands/workflow.js";
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
9
|
+
import { discoverProjectWorkflows } from "../store.js";
|
|
10
|
+
import { makeUwfStore } from "./thread-test-helpers.js";
|
|
11
11
|
|
|
12
12
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
13
13
|
|
|
14
|
-
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
15
|
-
const casDir = join(storageRoot, "cas");
|
|
16
|
-
await mkdir(casDir, { recursive: true });
|
|
17
|
-
process.env.OCAS_HOME = casDir;
|
|
18
|
-
return createUwfStore(storageRoot);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
14
|
function makeMinimalPayload(name: string, description: string): WorkflowPayload {
|
|
22
15
|
return {
|
|
23
16
|
version: 1,
|
|
@@ -62,8 +55,10 @@ async function createWorkflowYaml(name: string, version: string | null = null):
|
|
|
62
55
|
let tmpDir: string;
|
|
63
56
|
let storageRoot: string;
|
|
64
57
|
let projectRoot: string;
|
|
58
|
+
let savedOcasHome: string | undefined;
|
|
65
59
|
|
|
66
60
|
beforeEach(async () => {
|
|
61
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
67
62
|
tmpDir = await mkdtemp(join(tmpdir(), "uwf-wf-list-recursive-"));
|
|
68
63
|
storageRoot = join(tmpDir, "storage");
|
|
69
64
|
projectRoot = join(tmpDir, "project");
|
|
@@ -72,6 +67,11 @@ beforeEach(async () => {
|
|
|
72
67
|
});
|
|
73
68
|
|
|
74
69
|
afterEach(async () => {
|
|
70
|
+
if (savedOcasHome === undefined) {
|
|
71
|
+
delete process.env.OCAS_HOME;
|
|
72
|
+
} else {
|
|
73
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
74
|
+
}
|
|
75
75
|
await rm(tmpDir, { recursive: true, force: true });
|
|
76
76
|
});
|
|
77
77
|
|
|
@@ -6,17 +6,11 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
|
6
6
|
import { stringify } from "yaml";
|
|
7
7
|
import { cmdThreadStart } from "../commands/thread.js";
|
|
8
8
|
import type { UwfStore } from "../store.js";
|
|
9
|
-
import {
|
|
9
|
+
import { saveWorkflowRegistry } from "../store.js";
|
|
10
|
+
import { makeUwfStore } from "./thread-test-helpers.js";
|
|
10
11
|
|
|
11
12
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
12
13
|
|
|
13
|
-
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
14
|
-
const casDir = join(storageRoot, "cas");
|
|
15
|
-
await mkdir(casDir, { recursive: true });
|
|
16
|
-
process.env.OCAS_HOME = casDir;
|
|
17
|
-
return createUwfStore(storageRoot);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
function makeMinimalPayload(name: string, description: string): WorkflowPayload {
|
|
21
15
|
return {
|
|
22
16
|
version: 1,
|
|
@@ -67,8 +61,10 @@ async function createWorkflowYaml(name: string, version: string | null = null):
|
|
|
67
61
|
let tmpDir: string;
|
|
68
62
|
let storageRoot: string;
|
|
69
63
|
let projectRoot: string;
|
|
64
|
+
let savedOcasHome: string | undefined;
|
|
70
65
|
|
|
71
66
|
beforeEach(async () => {
|
|
67
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
72
68
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-wf-resolve-test-"));
|
|
73
69
|
storageRoot = join(tmpDir, "storage");
|
|
74
70
|
projectRoot = join(tmpDir, "project");
|
|
@@ -77,6 +73,11 @@ beforeEach(async () => {
|
|
|
77
73
|
});
|
|
78
74
|
|
|
79
75
|
afterEach(async () => {
|
|
76
|
+
if (savedOcasHome === undefined) {
|
|
77
|
+
delete process.env.OCAS_HOME;
|
|
78
|
+
} else {
|
|
79
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
80
|
+
}
|
|
80
81
|
await rm(tmpDir, { recursive: true, force: true });
|
|
81
82
|
});
|
|
82
83
|
|
|
@@ -6,17 +6,11 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
|
6
6
|
import { stringify } from "yaml";
|
|
7
7
|
import { cmdWorkflowShow } from "../commands/workflow.js";
|
|
8
8
|
import type { UwfStore } from "../store.js";
|
|
9
|
-
import {
|
|
9
|
+
import { saveWorkflowRegistry } from "../store.js";
|
|
10
|
+
import { makeUwfStore } from "./thread-test-helpers.js";
|
|
10
11
|
|
|
11
12
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
12
13
|
|
|
13
|
-
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
14
|
-
const casDir = join(storageRoot, "cas");
|
|
15
|
-
await mkdir(casDir, { recursive: true });
|
|
16
|
-
process.env.OCAS_HOME = casDir;
|
|
17
|
-
return createUwfStore(storageRoot);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
function makeMinimalPayload(name: string, description: string): WorkflowPayload {
|
|
21
15
|
return {
|
|
22
16
|
version: 1,
|
|
@@ -67,8 +61,10 @@ async function createWorkflowYaml(name: string, version: string | null = null):
|
|
|
67
61
|
let tmpDir: string;
|
|
68
62
|
let storageRoot: string;
|
|
69
63
|
let projectRoot: string;
|
|
64
|
+
let savedOcasHome: string | undefined;
|
|
70
65
|
|
|
71
66
|
beforeEach(async () => {
|
|
67
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
72
68
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-wf-show-test-"));
|
|
73
69
|
storageRoot = join(tmpDir, "storage");
|
|
74
70
|
projectRoot = join(tmpDir, "project");
|
|
@@ -77,6 +73,11 @@ beforeEach(async () => {
|
|
|
77
73
|
});
|
|
78
74
|
|
|
79
75
|
afterEach(async () => {
|
|
76
|
+
if (savedOcasHome === undefined) {
|
|
77
|
+
delete process.env.OCAS_HOME;
|
|
78
|
+
} else {
|
|
79
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
80
|
+
}
|
|
80
81
|
await rm(tmpDir, { recursive: true, force: true });
|
|
81
82
|
});
|
|
82
83
|
|