@united-workforce/cli 0.3.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 +45 -11
- package/dist/.build-fingerprint +1 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js +17 -7
- 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__/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__/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 +43 -30
- 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__/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__/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__/moderator-evaluate.test.js +9 -50
- package/dist/__tests__/moderator-evaluate.test.js.map +1 -1
- 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.d.ts +2 -0
- package/dist/__tests__/pid-recycling.test.d.ts.map +1 -0
- package/dist/__tests__/pid-recycling.test.js +273 -0
- package/dist/__tests__/pid-recycling.test.js.map +1 -0
- package/dist/__tests__/prompt.test.js +365 -2
- package/dist/__tests__/prompt.test.js.map +1 -1
- package/dist/__tests__/resolve-head-hash.test.js +12 -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 +27 -28
- 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 +507 -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 +28 -26
- package/dist/__tests__/store-unified-threads.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-status.test.js +25 -19
- 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 +354 -17
- 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.d.ts +2 -0
- package/dist/__tests__/thread-poke.test.d.ts.map +1 -0
- package/dist/__tests__/thread-poke.test.js +422 -0
- package/dist/__tests__/thread-poke.test.js.map +1 -0
- 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 +21 -15
- 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-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 +13 -16
- 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-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 +15 -13
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/__tests__/validate-semantic.test.js +105 -23
- 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 +286 -0
- package/dist/__tests__/workflow-list-recursive.test.js.map +1 -0
- package/dist/__tests__/workflow-resolution.test.js +46 -28
- 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 +213 -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 +707 -0
- package/dist/__tests__/workflow-validate.test.js.map +1 -0
- 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/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 +120 -62
- 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 +17 -31
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +57 -31
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +12 -39
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +72 -303
- 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 +423 -142
- 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 +126 -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/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/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 +6 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +34 -5
- 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/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 +95 -61
- 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/examples/brainstorm.yaml +130 -0
- package/examples/debate.yaml +169 -0
- package/examples/socratic-questioning.yaml +112 -0
- package/package.json +9 -10
- package/src/__tests__/adapter-json-roundtrip.test.ts +16 -7
- 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__/concurrency.test.ts +266 -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 +65 -30
- 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__/format-text-default.test.ts +49 -0
- package/src/__tests__/format-text-registry.test.ts +173 -0
- package/src/__tests__/issue-180-workflow-ref-removed.test.ts +43 -0
- package/src/__tests__/log-text-renderer.test.ts +294 -0
- package/src/__tests__/moderator-evaluate.test.ts +9 -52
- 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 +329 -0
- package/src/__tests__/prompt.test.ts +443 -2
- package/src/__tests__/resolve-head-hash.test.ts +11 -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 +27 -31
- package/src/__tests__/step-ask.test.ts +677 -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 +30 -27
- package/src/__tests__/thread-cancel-status.test.ts +27 -20
- package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
- package/src/__tests__/thread-list-filters.test.ts +443 -17
- 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 +554 -0
- package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
- package/src/__tests__/thread-resume.test.ts +20 -15
- package/src/__tests__/thread-show-status.test.ts +17 -29
- 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 +13 -16
- package/src/__tests__/thread-suspended-display.test.ts +10 -22
- package/src/__tests__/thread-test-helpers.ts +15 -1
- package/src/__tests__/thread.test.ts +14 -14
- package/src/__tests__/validate-semantic.test.ts +118 -33
- package/src/__tests__/workflow-list-recursive.test.ts +370 -0
- package/src/__tests__/workflow-resolution.test.ts +48 -29
- package/src/__tests__/workflow-show-resolution.test.ts +286 -0
- package/src/__tests__/workflow-validate.test.ts +828 -0
- package/src/__tests__/write-envelope.test.ts +257 -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 +184 -77
- package/src/commands/config.ts +16 -33
- package/src/commands/prompt.ts +57 -31
- package/src/commands/setup.ts +80 -358
- package/src/commands/step.ts +339 -12
- package/src/commands/thread.ts +511 -171
- package/src/commands/workflow.ts +155 -4
- 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/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/output-mappers.ts +254 -0
- package/src/schemas.ts +51 -5
- package/src/store.ts +86 -20
- package/src/text-renderers.ts +355 -0
- package/src/validate-semantic.ts +125 -73
- 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
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { putSchema } from "@ocas/core";
|
|
7
|
+
import { openStore } from "@ocas/fs";
|
|
8
|
+
import type {
|
|
9
|
+
CasRef,
|
|
10
|
+
StepNodePayload,
|
|
11
|
+
ThreadId,
|
|
12
|
+
ThreadIndexEntry,
|
|
13
|
+
} from "@united-workforce/protocol";
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
15
|
+
import { registerUwfSchemas } from "../schemas.js";
|
|
16
|
+
import { seedThreads } from "./thread-test-helpers.js";
|
|
17
|
+
|
|
18
|
+
const OUTPUT_SCHEMA = {
|
|
19
|
+
type: "object" as const,
|
|
20
|
+
properties: {
|
|
21
|
+
$status: { type: "string" as const },
|
|
22
|
+
note: { type: "string" as const },
|
|
23
|
+
},
|
|
24
|
+
required: ["$status"],
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const THREAD_ID = "01POKESTEPTEST00000000" as ThreadId;
|
|
29
|
+
|
|
30
|
+
let tmpDir: string;
|
|
31
|
+
let savedOcasHome: string | undefined;
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
35
|
+
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-poke-test-"));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
if (savedOcasHome === undefined) {
|
|
40
|
+
delete process.env.OCAS_HOME;
|
|
41
|
+
} else {
|
|
42
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
43
|
+
}
|
|
44
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
type SetupResult = {
|
|
48
|
+
casDir: string;
|
|
49
|
+
oldStepHash: CasRef;
|
|
50
|
+
oldStepPrev: CasRef | null;
|
|
51
|
+
oldStepCompletedAtMs: number;
|
|
52
|
+
startHash: CasRef;
|
|
53
|
+
workflowHash: CasRef;
|
|
54
|
+
mockAgentPath: string;
|
|
55
|
+
failingAgentPath: string;
|
|
56
|
+
promptCapturePath: string;
|
|
57
|
+
envCapturePath: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type SetupOpts = {
|
|
61
|
+
threadStatus: ThreadIndexEntry["status"];
|
|
62
|
+
multipleSteps: boolean;
|
|
63
|
+
newCompletedAtMs: number;
|
|
64
|
+
newStatus: string;
|
|
65
|
+
// The agent name to record in the head StepNode.agent field. Defaults to mockAgentPath.
|
|
66
|
+
stepAgentNameOverride: string | null;
|
|
67
|
+
// Whether to seed an actual head StepNode (false → only StartNode is the head).
|
|
68
|
+
withHeadStep: boolean;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
async function setupThread(opts: Partial<SetupOpts> = {}): Promise<SetupResult> {
|
|
72
|
+
const cfg: SetupOpts = {
|
|
73
|
+
threadStatus: opts.threadStatus ?? "idle",
|
|
74
|
+
multipleSteps: opts.multipleSteps ?? false,
|
|
75
|
+
newCompletedAtMs: opts.newCompletedAtMs ?? 1716600005000,
|
|
76
|
+
newStatus: opts.newStatus ?? "ok",
|
|
77
|
+
stepAgentNameOverride: opts.stepAgentNameOverride ?? null,
|
|
78
|
+
withHeadStep: opts.withHeadStep ?? true,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const casDir = join(tmpDir, "cas");
|
|
82
|
+
await mkdir(casDir, { recursive: true });
|
|
83
|
+
|
|
84
|
+
const store = await openStore(casDir);
|
|
85
|
+
const schemas = await registerUwfSchemas(store);
|
|
86
|
+
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
|
|
87
|
+
|
|
88
|
+
const workflowHash = await store.cas.put(schemas.workflow, {
|
|
89
|
+
name: "test-poke",
|
|
90
|
+
description: "poke command integration test",
|
|
91
|
+
roles: {
|
|
92
|
+
worker: {
|
|
93
|
+
description: "Worker role",
|
|
94
|
+
goal: "Work",
|
|
95
|
+
capabilities: [],
|
|
96
|
+
procedure: "work",
|
|
97
|
+
output: "result",
|
|
98
|
+
frontmatter: outputSchemaHash,
|
|
99
|
+
},
|
|
100
|
+
reviewer: {
|
|
101
|
+
description: "Reviewer role",
|
|
102
|
+
goal: "Review",
|
|
103
|
+
capabilities: [],
|
|
104
|
+
procedure: "review",
|
|
105
|
+
output: "result",
|
|
106
|
+
frontmatter: outputSchemaHash,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
graph: {
|
|
110
|
+
$START: {
|
|
111
|
+
new: { role: "worker", prompt: "Start work", location: null },
|
|
112
|
+
resume: { role: "worker", prompt: "Resume the work", location: null },
|
|
113
|
+
},
|
|
114
|
+
worker: {
|
|
115
|
+
ok: { role: "reviewer", prompt: "Review the work", location: null },
|
|
116
|
+
},
|
|
117
|
+
reviewer: { done: { role: "$END", prompt: "Done", location: null } },
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const startHash = await store.cas.put(schemas.startNode, {
|
|
122
|
+
workflow: workflowHash,
|
|
123
|
+
prompt: "Test poke task",
|
|
124
|
+
cwd: tmpDir,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
process.env.OCAS_HOME = casDir;
|
|
128
|
+
|
|
129
|
+
// Paths for mock agent and capture files (set early so we can use mockAgentPath as the recorded agent name)
|
|
130
|
+
const promptCapturePath = join(tmpDir, "captured-prompt.txt");
|
|
131
|
+
const envCapturePath = join(tmpDir, "captured-env.txt");
|
|
132
|
+
const mockAgentPath = join(tmpDir, "mock-agent.sh");
|
|
133
|
+
const failingAgentPath = join(tmpDir, "failing-agent.sh");
|
|
134
|
+
|
|
135
|
+
// Build head StepNode chain
|
|
136
|
+
let oldStepPrev: CasRef | null = null;
|
|
137
|
+
if (cfg.multipleSteps) {
|
|
138
|
+
// First step: prev=null
|
|
139
|
+
const firstOutputHash = await store.cas.put(outputSchemaHash, { $status: "ok" });
|
|
140
|
+
const firstDetailHash = await store.cas.put(schemas.text, "first detail");
|
|
141
|
+
const firstStepHash = await store.cas.put(schemas.stepNode, {
|
|
142
|
+
start: startHash,
|
|
143
|
+
prev: null,
|
|
144
|
+
role: "worker",
|
|
145
|
+
output: firstOutputHash,
|
|
146
|
+
detail: firstDetailHash,
|
|
147
|
+
agent: cfg.stepAgentNameOverride ?? mockAgentPath,
|
|
148
|
+
edgePrompt: "Start work",
|
|
149
|
+
startedAtMs: 1716600000000,
|
|
150
|
+
completedAtMs: 1716600001000,
|
|
151
|
+
cwd: tmpDir,
|
|
152
|
+
assembledPrompt: null,
|
|
153
|
+
usage: null,
|
|
154
|
+
});
|
|
155
|
+
oldStepPrev = firstStepHash;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let oldStepHash: CasRef = startHash;
|
|
159
|
+
const oldStepCompletedAtMs = 1716600002000;
|
|
160
|
+
if (cfg.withHeadStep) {
|
|
161
|
+
const outputHash = await store.cas.put(outputSchemaHash, { $status: "ok" });
|
|
162
|
+
const detailHash = await store.cas.put(schemas.text, "head step detail");
|
|
163
|
+
oldStepHash = await store.cas.put(schemas.stepNode, {
|
|
164
|
+
start: startHash,
|
|
165
|
+
prev: oldStepPrev,
|
|
166
|
+
role: "worker",
|
|
167
|
+
output: outputHash,
|
|
168
|
+
detail: detailHash,
|
|
169
|
+
agent: cfg.stepAgentNameOverride ?? mockAgentPath,
|
|
170
|
+
edgePrompt: "Start work",
|
|
171
|
+
startedAtMs: 1716600001500,
|
|
172
|
+
completedAtMs: oldStepCompletedAtMs,
|
|
173
|
+
cwd: tmpDir,
|
|
174
|
+
assembledPrompt: null,
|
|
175
|
+
usage: null,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Seed thread index entry. For "running" we let the test create the marker separately.
|
|
180
|
+
await seedThreads(tmpDir, {
|
|
181
|
+
[THREAD_ID]: {
|
|
182
|
+
head: oldStepHash,
|
|
183
|
+
status: cfg.threadStatus,
|
|
184
|
+
suspendedRole: cfg.threadStatus === "suspended" ? "worker" : null,
|
|
185
|
+
suspendMessage: cfg.threadStatus === "suspended" ? "Please clarify" : null,
|
|
186
|
+
completedAt:
|
|
187
|
+
cfg.threadStatus === "end" || cfg.threadStatus === "cancelled"
|
|
188
|
+
? oldStepCompletedAtMs
|
|
189
|
+
: null,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Mock agent always emits a stepNode keyed off the current thread head (which we
|
|
194
|
+
// observe through OCAS_HOME). The script writes prompt/env captures and then prints
|
|
195
|
+
// an adapter JSON that references a pre-built stepHash.
|
|
196
|
+
// We pre-build the agent's stepHash with prev=oldStepHash (normal append behaviour).
|
|
197
|
+
const newOutputHash = await store.cas.put(outputSchemaHash, {
|
|
198
|
+
$status: cfg.newStatus,
|
|
199
|
+
note: "poked output",
|
|
200
|
+
});
|
|
201
|
+
const newDetailHash = await store.cas.put(schemas.text, "poked detail");
|
|
202
|
+
const agentStepHash = await store.cas.put(schemas.stepNode, {
|
|
203
|
+
start: startHash,
|
|
204
|
+
prev: cfg.withHeadStep ? oldStepHash : null,
|
|
205
|
+
role: "worker",
|
|
206
|
+
output: newOutputHash,
|
|
207
|
+
detail: newDetailHash,
|
|
208
|
+
agent: "mock-agent-output",
|
|
209
|
+
edgePrompt: "poke prompt placeholder",
|
|
210
|
+
startedAtMs: cfg.newCompletedAtMs - 100,
|
|
211
|
+
completedAtMs: cfg.newCompletedAtMs,
|
|
212
|
+
cwd: tmpDir,
|
|
213
|
+
assembledPrompt: null,
|
|
214
|
+
usage: null,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const adapterJson = JSON.stringify({
|
|
218
|
+
stepHash: agentStepHash,
|
|
219
|
+
detailHash: newDetailHash,
|
|
220
|
+
role: "worker",
|
|
221
|
+
frontmatter: { $status: cfg.newStatus, note: "poked output" },
|
|
222
|
+
body: "",
|
|
223
|
+
startedAtMs: cfg.newCompletedAtMs - 100,
|
|
224
|
+
completedAtMs: cfg.newCompletedAtMs,
|
|
225
|
+
usage: null,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await writeFile(
|
|
229
|
+
mockAgentPath,
|
|
230
|
+
`#!/bin/sh
|
|
231
|
+
prompt=""
|
|
232
|
+
while [ $# -gt 0 ]; do
|
|
233
|
+
if [ "$1" = "--prompt" ]; then
|
|
234
|
+
prompt="$2"
|
|
235
|
+
shift 2
|
|
236
|
+
else
|
|
237
|
+
shift
|
|
238
|
+
fi
|
|
239
|
+
done
|
|
240
|
+
printf '%s' "$prompt" > '${promptCapturePath}'
|
|
241
|
+
printf 'OCAS_HOME=%s\\n' "$OCAS_HOME" > '${envCapturePath}'
|
|
242
|
+
echo '${adapterJson}'
|
|
243
|
+
`,
|
|
244
|
+
{ mode: 0o755 },
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
await writeFile(
|
|
248
|
+
failingAgentPath,
|
|
249
|
+
`#!/bin/sh
|
|
250
|
+
echo "boom" >&2
|
|
251
|
+
exit 7
|
|
252
|
+
`,
|
|
253
|
+
{ mode: 0o755 },
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const configPath = join(tmpDir, "config.yaml");
|
|
257
|
+
await writeFile(
|
|
258
|
+
configPath,
|
|
259
|
+
`defaultAgent: uwf-hermes\nagentOverrides: null\nagents:\n uwf-hermes:\n command: uwf-hermes\n`,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
casDir,
|
|
264
|
+
oldStepHash,
|
|
265
|
+
oldStepPrev,
|
|
266
|
+
oldStepCompletedAtMs,
|
|
267
|
+
startHash,
|
|
268
|
+
workflowHash,
|
|
269
|
+
mockAgentPath,
|
|
270
|
+
failingAgentPath,
|
|
271
|
+
promptCapturePath,
|
|
272
|
+
envCapturePath,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function runUwf(
|
|
277
|
+
args: string[],
|
|
278
|
+
casDir: string,
|
|
279
|
+
): { stdout: string; stderr: string; status: number } {
|
|
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];
|
|
283
|
+
try {
|
|
284
|
+
const stdout = execFileSync(process.execPath, [cliPath, ...formatArgs], {
|
|
285
|
+
encoding: "utf8",
|
|
286
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
287
|
+
env: {
|
|
288
|
+
...process.env,
|
|
289
|
+
UWF_HOME: tmpDir,
|
|
290
|
+
OCAS_HOME: casDir,
|
|
291
|
+
},
|
|
292
|
+
cwd: tmpDir,
|
|
293
|
+
timeout: 30000,
|
|
294
|
+
});
|
|
295
|
+
return { stdout, stderr: "", status: 0 };
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const err = error as NodeJS.ErrnoException & {
|
|
298
|
+
stdout?: string | Buffer;
|
|
299
|
+
stderr?: string | Buffer;
|
|
300
|
+
status?: number;
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
stdout: typeof err.stdout === "string" ? err.stdout : (err.stdout?.toString("utf8") ?? ""),
|
|
304
|
+
stderr: typeof err.stderr === "string" ? err.stderr : (err.stderr?.toString("utf8") ?? ""),
|
|
305
|
+
status: err.status ?? 1,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── Group 1: CLI argument validation ───────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
describe("uwf thread poke - CLI argument validation", () => {
|
|
313
|
+
test("1.1 missing -p flag exits non-zero", async () => {
|
|
314
|
+
const { casDir } = await setupThread();
|
|
315
|
+
const result = runUwf(["thread", "poke", THREAD_ID], casDir);
|
|
316
|
+
expect(result.status).not.toBe(0);
|
|
317
|
+
expect(result.stderr.toLowerCase()).toMatch(/required|missing|prompt/);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("1.2 -p without --agent succeeds", async () => {
|
|
321
|
+
const { casDir } = await setupThread();
|
|
322
|
+
const result = runUwf(["thread", "poke", THREAD_ID, "-p", "do it again"], casDir);
|
|
323
|
+
expect(result.status).toBe(0);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("1.3 -p with --agent succeeds", async () => {
|
|
327
|
+
const { casDir, mockAgentPath } = await setupThread();
|
|
328
|
+
const result = runUwf(
|
|
329
|
+
["thread", "poke", THREAD_ID, "-p", "do it again", "--agent", mockAgentPath],
|
|
330
|
+
casDir,
|
|
331
|
+
);
|
|
332
|
+
expect(result.status).toBe(0);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// ── Group 2: Guard errors ──────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
describe("uwf thread poke - guard errors", () => {
|
|
339
|
+
test("2.1 thread not found", async () => {
|
|
340
|
+
const { casDir } = await setupThread();
|
|
341
|
+
const result = runUwf(["thread", "poke", "01NOSUCHTHREAD0000000A", "-p", "prompt"], casDir);
|
|
342
|
+
expect(result.status).not.toBe(0);
|
|
343
|
+
expect(result.stderr.toLowerCase()).toMatch(/not found|not active/);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("2.2 thread running rejects poke", async () => {
|
|
347
|
+
const { casDir, workflowHash } = await setupThread();
|
|
348
|
+
// Create background marker to simulate running
|
|
349
|
+
const { createMarker, getProcessStartTime } = await import("../background/index.js");
|
|
350
|
+
await createMarker(tmpDir, {
|
|
351
|
+
thread: THREAD_ID,
|
|
352
|
+
workflow: workflowHash,
|
|
353
|
+
pid: process.pid,
|
|
354
|
+
startedAt: Date.now(),
|
|
355
|
+
processStartTime: getProcessStartTime(process.pid),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const result = runUwf(["thread", "poke", THREAD_ID, "-p", "prompt"], casDir);
|
|
359
|
+
expect(result.status).not.toBe(0);
|
|
360
|
+
expect(result.stderr.toLowerCase()).toContain("already executing");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("2.3 completed thread rejects poke", async () => {
|
|
364
|
+
const { casDir } = await setupThread({ threadStatus: "end" });
|
|
365
|
+
const result = runUwf(["thread", "poke", THREAD_ID, "-p", "prompt"], casDir);
|
|
366
|
+
expect(result.status).not.toBe(0);
|
|
367
|
+
expect(result.stderr.toLowerCase()).toMatch(/cannot be poked|end/);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("2.4 cancelled thread rejects poke", async () => {
|
|
371
|
+
const { casDir } = await setupThread({ threadStatus: "cancelled" });
|
|
372
|
+
const result = runUwf(["thread", "poke", THREAD_ID, "-p", "prompt"], casDir);
|
|
373
|
+
expect(result.status).not.toBe(0);
|
|
374
|
+
expect(result.stderr.toLowerCase()).toMatch(/cannot be poked|cancelled/);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("2.5 thread head is StartNode (no StepNode) rejects poke", async () => {
|
|
378
|
+
const { casDir } = await setupThread({ withHeadStep: false });
|
|
379
|
+
const result = runUwf(["thread", "poke", THREAD_ID, "-p", "prompt"], casDir);
|
|
380
|
+
expect(result.status).not.toBe(0);
|
|
381
|
+
expect(result.stderr.toLowerCase()).toMatch(/no step|cannot be poked/);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// ── Group 3: Success happy path ────────────────────────────────────────────
|
|
386
|
+
|
|
387
|
+
describe("uwf thread poke - success", () => {
|
|
388
|
+
test("3.1, 3.4 idle thread → new head differs from old, thread index updated", async () => {
|
|
389
|
+
const { casDir, oldStepHash, mockAgentPath } = await setupThread();
|
|
390
|
+
const result = runUwf(
|
|
391
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", mockAgentPath],
|
|
392
|
+
casDir,
|
|
393
|
+
);
|
|
394
|
+
expect(result.status).toBe(0);
|
|
395
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
396
|
+
expect(cliOutput.head).not.toBe(oldStepHash);
|
|
397
|
+
|
|
398
|
+
const { createUwfStore, getThread } = await import("../store.js");
|
|
399
|
+
const uwf = await createUwfStore(tmpDir);
|
|
400
|
+
const entry = getThread(uwf.varStore, THREAD_ID);
|
|
401
|
+
expect(entry?.head).toBe(cliOutput.head);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("3.2 new step's prev equals old head's prev (replace, not append)", async () => {
|
|
405
|
+
const { casDir, oldStepPrev, mockAgentPath } = await setupThread({ multipleSteps: true });
|
|
406
|
+
const result = runUwf(
|
|
407
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", mockAgentPath],
|
|
408
|
+
casDir,
|
|
409
|
+
);
|
|
410
|
+
expect(result.status).toBe(0);
|
|
411
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
412
|
+
|
|
413
|
+
const { createUwfStore } = await import("../store.js");
|
|
414
|
+
const uwf = await createUwfStore(tmpDir);
|
|
415
|
+
const node = uwf.store.cas.get(cliOutput.head as CasRef);
|
|
416
|
+
expect(node).not.toBeNull();
|
|
417
|
+
expect(node?.type).toBe(uwf.schemas.stepNode);
|
|
418
|
+
const payload = node?.payload as StepNodePayload;
|
|
419
|
+
expect(payload.prev).toBe(oldStepPrev);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("3.2b new step's prev is null when old head was the first step", async () => {
|
|
423
|
+
// multipleSteps:false means oldHead.prev = null
|
|
424
|
+
const { casDir, mockAgentPath } = await setupThread({ multipleSteps: false });
|
|
425
|
+
const result = runUwf(
|
|
426
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", mockAgentPath],
|
|
427
|
+
casDir,
|
|
428
|
+
);
|
|
429
|
+
expect(result.status).toBe(0);
|
|
430
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
431
|
+
|
|
432
|
+
const { createUwfStore } = await import("../store.js");
|
|
433
|
+
const uwf = await createUwfStore(tmpDir);
|
|
434
|
+
const node = uwf.store.cas.get(cliOutput.head as CasRef);
|
|
435
|
+
const payload = node?.payload as StepNodePayload;
|
|
436
|
+
expect(payload.prev).toBeNull();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("3.3 new step's completedAtMs is later than old", async () => {
|
|
440
|
+
const { casDir, oldStepCompletedAtMs, mockAgentPath } = await setupThread();
|
|
441
|
+
const result = runUwf(
|
|
442
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", mockAgentPath],
|
|
443
|
+
casDir,
|
|
444
|
+
);
|
|
445
|
+
expect(result.status).toBe(0);
|
|
446
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
447
|
+
|
|
448
|
+
const { createUwfStore } = await import("../store.js");
|
|
449
|
+
const uwf = await createUwfStore(tmpDir);
|
|
450
|
+
const node = uwf.store.cas.get(cliOutput.head as CasRef);
|
|
451
|
+
const payload = node?.payload as StepNodePayload;
|
|
452
|
+
expect(payload.completedAtMs).toBeGreaterThan(oldStepCompletedAtMs);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("3.5 status remains idle after poke (no completion/suspend)", async () => {
|
|
456
|
+
const { casDir, mockAgentPath } = await setupThread();
|
|
457
|
+
const result = runUwf(
|
|
458
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", mockAgentPath],
|
|
459
|
+
casDir,
|
|
460
|
+
);
|
|
461
|
+
expect(result.status).toBe(0);
|
|
462
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
463
|
+
expect(cliOutput.status).toBe("idle");
|
|
464
|
+
expect(cliOutput.done).toBe(false);
|
|
465
|
+
expect(cliOutput.suspendedRole).toBeNull();
|
|
466
|
+
expect(cliOutput.suspendMessage).toBeNull();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("3.6 currentRole unchanged after poke (no moderator re-route)", async () => {
|
|
470
|
+
// Before poke: idle thread with worker step having $status=ok → moderator would route to reviewer.
|
|
471
|
+
// After poke (mock returns same $status=ok), moderator routing remains the same.
|
|
472
|
+
const { casDir, mockAgentPath } = await setupThread();
|
|
473
|
+
const result = runUwf(
|
|
474
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", mockAgentPath],
|
|
475
|
+
casDir,
|
|
476
|
+
);
|
|
477
|
+
expect(result.status).toBe(0);
|
|
478
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
479
|
+
expect(cliOutput.currentRole).toBe("reviewer");
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// ── Group 4: Agent resolution ──────────────────────────────────────────────
|
|
484
|
+
|
|
485
|
+
describe("uwf thread poke - agent resolution", () => {
|
|
486
|
+
test("4.1 without --agent, agent command read from head step's agent field", async () => {
|
|
487
|
+
// Head step's agent field points at mockAgentPath (default in setupThread)
|
|
488
|
+
const { casDir, promptCapturePath } = await setupThread();
|
|
489
|
+
const result = runUwf(["thread", "poke", THREAD_ID, "-p", "redo"], casDir);
|
|
490
|
+
expect(result.status).toBe(0);
|
|
491
|
+
const captured = await readFile(promptCapturePath, "utf8");
|
|
492
|
+
expect(captured).toBe("redo");
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test("4.2 with --agent, explicit override is used", async () => {
|
|
496
|
+
// Head step records "uwf-mock" (which is not a real binary). Override with mockAgentPath.
|
|
497
|
+
const { casDir, mockAgentPath } = await setupThread({ stepAgentNameOverride: "uwf-mock" });
|
|
498
|
+
const result = runUwf(
|
|
499
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", mockAgentPath],
|
|
500
|
+
casDir,
|
|
501
|
+
);
|
|
502
|
+
expect(result.status).toBe(0);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// ── Group 5: Prompt passthrough ────────────────────────────────────────────
|
|
507
|
+
|
|
508
|
+
describe("uwf thread poke - prompt passthrough", () => {
|
|
509
|
+
test("5.1 -p value is passed to agent as --prompt", async () => {
|
|
510
|
+
const { casDir, mockAgentPath, promptCapturePath } = await setupThread();
|
|
511
|
+
const supplement = "Use the REST API instead.";
|
|
512
|
+
const result = runUwf(
|
|
513
|
+
["thread", "poke", THREAD_ID, "-p", supplement, "--agent", mockAgentPath],
|
|
514
|
+
casDir,
|
|
515
|
+
);
|
|
516
|
+
expect(result.status).toBe(0);
|
|
517
|
+
const captured = await readFile(promptCapturePath, "utf8");
|
|
518
|
+
expect(captured).toBe(supplement);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// ── Group 6: Edge cases ────────────────────────────────────────────────────
|
|
523
|
+
|
|
524
|
+
describe("uwf thread poke - edge cases", () => {
|
|
525
|
+
test("6.1 poke succeeds on suspended thread", async () => {
|
|
526
|
+
const { casDir, oldStepHash, mockAgentPath } = await setupThread({
|
|
527
|
+
threadStatus: "suspended",
|
|
528
|
+
});
|
|
529
|
+
const result = runUwf(
|
|
530
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", mockAgentPath],
|
|
531
|
+
casDir,
|
|
532
|
+
);
|
|
533
|
+
expect(result.status).toBe(0);
|
|
534
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
535
|
+
expect(cliOutput.head).not.toBe(oldStepHash);
|
|
536
|
+
expect(cliOutput.status).toBe("idle");
|
|
537
|
+
expect(cliOutput.suspendedRole).toBeNull();
|
|
538
|
+
expect(cliOutput.suspendMessage).toBeNull();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("6.2 agent failure leaves thread head unchanged", async () => {
|
|
542
|
+
const { casDir, oldStepHash, failingAgentPath } = await setupThread();
|
|
543
|
+
const result = runUwf(
|
|
544
|
+
["thread", "poke", THREAD_ID, "-p", "redo", "--agent", failingAgentPath],
|
|
545
|
+
casDir,
|
|
546
|
+
);
|
|
547
|
+
expect(result.status).not.toBe(0);
|
|
548
|
+
|
|
549
|
+
const { createUwfStore, getThread } = await import("../store.js");
|
|
550
|
+
const uwf = await createUwfStore(tmpDir);
|
|
551
|
+
const entry = getThread(uwf.varStore, THREAD_ID);
|
|
552
|
+
expect(entry?.head).toBe(oldStepHash);
|
|
553
|
+
});
|
|
554
|
+
});
|
|
@@ -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
|
|