@united-workforce/cli 0.1.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/LICENSE +21 -0
- package/README.md +221 -0
- package/dist/__tests__/adapter-json-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js +147 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +685 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/current-role.test.d.ts +2 -0
- package/dist/__tests__/current-role.test.d.ts.map +1 -0
- package/dist/__tests__/current-role.test.js +401 -0
- package/dist/__tests__/current-role.test.js.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.d.ts +2 -0
- package/dist/__tests__/e2e-mock-agent.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.js +401 -0
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -0
- package/dist/__tests__/include-tag.test.d.ts +2 -0
- package/dist/__tests__/include-tag.test.d.ts.map +1 -0
- package/dist/__tests__/include-tag.test.js +69 -0
- package/dist/__tests__/include-tag.test.js.map +1 -0
- package/dist/__tests__/log.test.d.ts +2 -0
- package/dist/__tests__/log.test.d.ts.map +1 -0
- package/dist/__tests__/log.test.js +161 -0
- package/dist/__tests__/log.test.js.map +1 -0
- package/dist/__tests__/moderator-evaluate.test.d.ts +2 -0
- package/dist/__tests__/moderator-evaluate.test.d.ts.map +1 -0
- package/dist/__tests__/moderator-evaluate.test.js +170 -0
- package/dist/__tests__/moderator-evaluate.test.js.map +1 -0
- package/dist/__tests__/preload.d.ts +3 -0
- package/dist/__tests__/preload.d.ts.map +1 -0
- package/dist/__tests__/preload.js +6 -0
- package/dist/__tests__/preload.js.map +1 -0
- package/dist/__tests__/prompt.test.d.ts +2 -0
- package/dist/__tests__/prompt.test.d.ts.map +1 -0
- package/dist/__tests__/prompt.test.js +111 -0
- package/dist/__tests__/prompt.test.js.map +1 -0
- package/dist/__tests__/resolve-head-hash.test.d.ts +2 -0
- package/dist/__tests__/resolve-head-hash.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-head-hash.test.js +66 -0
- package/dist/__tests__/resolve-head-hash.test.js.map +1 -0
- package/dist/__tests__/setup-agent-discovery.test.d.ts +2 -0
- package/dist/__tests__/setup-agent-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/setup-agent-discovery.test.js +119 -0
- package/dist/__tests__/setup-agent-discovery.test.js.map +1 -0
- package/dist/__tests__/setup-complexity.test.d.ts +2 -0
- package/dist/__tests__/setup-complexity.test.d.ts.map +1 -0
- package/dist/__tests__/setup-complexity.test.js +314 -0
- package/dist/__tests__/setup-complexity.test.js.map +1 -0
- package/dist/__tests__/setup-validate.test.d.ts +2 -0
- package/dist/__tests__/setup-validate.test.d.ts.map +1 -0
- package/dist/__tests__/setup-validate.test.js +108 -0
- package/dist/__tests__/setup-validate.test.js.map +1 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.d.ts +2 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.d.ts.map +1 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.js +107 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -0
- package/dist/__tests__/spawn-agent-json.test.d.ts +2 -0
- package/dist/__tests__/spawn-agent-json.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-agent-json.test.js +79 -0
- package/dist/__tests__/spawn-agent-json.test.js.map +1 -0
- package/dist/__tests__/step-read.test.d.ts +2 -0
- package/dist/__tests__/step-read.test.d.ts.map +1 -0
- package/dist/__tests__/step-read.test.js +561 -0
- package/dist/__tests__/step-read.test.js.map +1 -0
- package/dist/__tests__/step-show-json.test.d.ts +2 -0
- package/dist/__tests__/step-show-json.test.d.ts.map +1 -0
- package/dist/__tests__/step-show-json.test.js +311 -0
- package/dist/__tests__/step-show-json.test.js.map +1 -0
- package/dist/__tests__/step-timing.test.d.ts +2 -0
- package/dist/__tests__/step-timing.test.d.ts.map +1 -0
- package/dist/__tests__/step-timing.test.js +345 -0
- package/dist/__tests__/step-timing.test.js.map +1 -0
- package/dist/__tests__/store-global-cas.test.d.ts +2 -0
- package/dist/__tests__/store-global-cas.test.d.ts.map +1 -0
- package/dist/__tests__/store-global-cas.test.js +235 -0
- package/dist/__tests__/store-global-cas.test.js.map +1 -0
- package/dist/__tests__/store-storage-root.test.d.ts +2 -0
- package/dist/__tests__/store-storage-root.test.d.ts.map +1 -0
- package/dist/__tests__/store-storage-root.test.js +43 -0
- package/dist/__tests__/store-storage-root.test.js.map +1 -0
- package/dist/__tests__/store-unified-threads.test.d.ts +2 -0
- package/dist/__tests__/store-unified-threads.test.d.ts.map +1 -0
- package/dist/__tests__/store-unified-threads.test.js +189 -0
- package/dist/__tests__/store-unified-threads.test.js.map +1 -0
- package/dist/__tests__/thread-cancel-status.test.d.ts +2 -0
- package/dist/__tests__/thread-cancel-status.test.d.ts.map +1 -0
- package/dist/__tests__/thread-cancel-status.test.js +111 -0
- package/dist/__tests__/thread-cancel-status.test.js.map +1 -0
- package/dist/__tests__/thread-list-filters.test.d.ts +2 -0
- package/dist/__tests__/thread-list-filters.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-filters.test.js +442 -0
- package/dist/__tests__/thread-list-filters.test.js.map +1 -0
- package/dist/__tests__/thread-location.test.d.ts +2 -0
- package/dist/__tests__/thread-location.test.d.ts.map +1 -0
- package/dist/__tests__/thread-location.test.js +159 -0
- package/dist/__tests__/thread-location.test.js.map +1 -0
- package/dist/__tests__/thread-read-quota.test.d.ts +2 -0
- package/dist/__tests__/thread-read-quota.test.d.ts.map +1 -0
- package/dist/__tests__/thread-read-quota.test.js +546 -0
- package/dist/__tests__/thread-read-quota.test.js.map +1 -0
- package/dist/__tests__/thread-read-xml-tags.test.d.ts +2 -0
- package/dist/__tests__/thread-read-xml-tags.test.d.ts.map +1 -0
- package/dist/__tests__/thread-read-xml-tags.test.js +610 -0
- package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -0
- package/dist/__tests__/thread-resume.test.d.ts +2 -0
- package/dist/__tests__/thread-resume.test.d.ts.map +1 -0
- package/dist/__tests__/thread-resume.test.js +592 -0
- package/dist/__tests__/thread-resume.test.js.map +1 -0
- package/dist/__tests__/thread-show-status.test.d.ts +2 -0
- package/dist/__tests__/thread-show-status.test.d.ts.map +1 -0
- package/dist/__tests__/thread-show-status.test.js +267 -0
- package/dist/__tests__/thread-show-status.test.js.map +1 -0
- package/dist/__tests__/thread-start-cwd-cli.test.d.ts +2 -0
- package/dist/__tests__/thread-start-cwd-cli.test.d.ts.map +1 -0
- package/dist/__tests__/thread-start-cwd-cli.test.js +130 -0
- package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -0
- package/dist/__tests__/thread-step-count.test.d.ts +2 -0
- package/dist/__tests__/thread-step-count.test.d.ts.map +1 -0
- package/dist/__tests__/thread-step-count.test.js +55 -0
- package/dist/__tests__/thread-step-count.test.js.map +1 -0
- package/dist/__tests__/thread-suspend-step.test.d.ts +2 -0
- package/dist/__tests__/thread-suspend-step.test.d.ts.map +1 -0
- package/dist/__tests__/thread-suspend-step.test.js +155 -0
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -0
- package/dist/__tests__/thread-suspended-display.test.d.ts +2 -0
- package/dist/__tests__/thread-suspended-display.test.d.ts.map +1 -0
- package/dist/__tests__/thread-suspended-display.test.js +247 -0
- package/dist/__tests__/thread-suspended-display.test.js.map +1 -0
- package/dist/__tests__/thread-test-helpers.d.ts +4 -0
- package/dist/__tests__/thread-test-helpers.d.ts.map +1 -0
- package/dist/__tests__/thread-test-helpers.js +23 -0
- package/dist/__tests__/thread-test-helpers.js.map +1 -0
- package/dist/__tests__/thread.test.d.ts +2 -0
- package/dist/__tests__/thread.test.d.ts.map +1 -0
- package/dist/__tests__/thread.test.js +883 -0
- package/dist/__tests__/thread.test.js.map +1 -0
- package/dist/__tests__/validate-semantic.test.d.ts +2 -0
- package/dist/__tests__/validate-semantic.test.d.ts.map +1 -0
- package/dist/__tests__/validate-semantic.test.js +408 -0
- package/dist/__tests__/validate-semantic.test.js.map +1 -0
- package/dist/__tests__/workflow-resolution.test.d.ts +2 -0
- package/dist/__tests__/workflow-resolution.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-resolution.test.js +308 -0
- package/dist/__tests__/workflow-resolution.test.js.map +1 -0
- package/dist/background/background.d.ts +38 -0
- package/dist/background/background.d.ts.map +1 -0
- package/dist/background/background.js +123 -0
- package/dist/background/background.js.map +1 -0
- package/dist/background/index.d.ts +3 -0
- package/dist/background/index.d.ts.map +1 -0
- package/dist/background/index.js +2 -0
- package/dist/background/index.js.map +1 -0
- package/dist/background/types.d.ts +9 -0
- package/dist/background/types.d.ts.map +1 -0
- package/dist/background/types.js +2 -0
- package/dist/background/types.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +535 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.d.ts +41 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +252 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/log.d.ts +26 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +79 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/prompt.d.ts +6 -0
- package/dist/commands/prompt.d.ts.map +1 -0
- package/dist/commands/prompt.js +67 -0
- package/dist/commands/prompt.js.map +1 -0
- package/dist/commands/setup.d.ts +73 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +522 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/shared.d.ts +31 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +154 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/step.d.ts +18 -0
- package/dist/commands/step.d.ts.map +1 -0
- package/dist/commands/step.js +257 -0
- package/dist/commands/step.js.map +1 -0
- package/dist/commands/thread-time-parser.d.ts +6 -0
- package/dist/commands/thread-time-parser.d.ts.map +1 -0
- package/dist/commands/thread-time-parser.js +22 -0
- package/dist/commands/thread-time-parser.js.map +1 -0
- package/dist/commands/thread.d.ts +38 -0
- package/dist/commands/thread.d.ts.map +1 -0
- package/dist/commands/thread.js +1087 -0
- package/dist/commands/thread.js.map +1 -0
- package/dist/commands/workflow.d.ts +24 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +138 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +10 -0
- package/dist/format.js.map +1 -0
- package/dist/include.d.ts +12 -0
- package/dist/include.d.ts.map +1 -0
- package/dist/include.js +35 -0
- package/dist/include.js.map +1 -0
- package/dist/moderator/__tests__/evaluate.test.d.ts +2 -0
- package/dist/moderator/__tests__/evaluate.test.d.ts.map +1 -0
- package/dist/moderator/__tests__/evaluate.test.js +167 -0
- package/dist/moderator/__tests__/evaluate.test.js.map +1 -0
- package/dist/moderator/evaluate.d.ts +6 -0
- package/dist/moderator/evaluate.d.ts.map +1 -0
- package/dist/moderator/evaluate.js +65 -0
- package/dist/moderator/evaluate.js.map +1 -0
- package/dist/moderator/index.d.ts +4 -0
- package/dist/moderator/index.d.ts.map +1 -0
- package/dist/moderator/index.js +3 -0
- package/dist/moderator/index.js.map +1 -0
- package/dist/moderator/types.d.ts +25 -0
- package/dist/moderator/types.d.ts.map +1 -0
- package/dist/moderator/types.js +4 -0
- package/dist/moderator/types.js.map +1 -0
- package/dist/schemas.d.ts +16 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +17 -0
- package/dist/schemas.js.map +1 -0
- package/dist/store.d.ts +77 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +392 -0
- package/dist/store.js.map +1 -0
- package/dist/validate-semantic.d.ts +7 -0
- package/dist/validate-semantic.d.ts.map +1 -0
- package/dist/validate-semantic.js +263 -0
- package/dist/validate-semantic.js.map +1 -0
- package/dist/validate.d.ts +16 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +115 -0
- package/dist/validate.js.map +1 -0
- package/package.json +44 -0
- package/src/__tests__/adapter-json-roundtrip.test.ts +181 -0
- package/src/__tests__/config.test.ts +740 -0
- package/src/__tests__/current-role.test.ts +438 -0
- package/src/__tests__/e2e-mock-agent.test.ts +498 -0
- package/src/__tests__/fixtures/e2e-completed-resume.mock.yaml +15 -0
- package/src/__tests__/fixtures/e2e-count.mock.yaml +19 -0
- package/src/__tests__/fixtures/e2e-count.workflow.yaml +45 -0
- package/src/__tests__/fixtures/e2e-linear.mock.yaml +13 -0
- package/src/__tests__/fixtures/e2e-linear.workflow.yaml +32 -0
- package/src/__tests__/fixtures/e2e-loop.mock.yaml +25 -0
- package/src/__tests__/fixtures/e2e-loop.workflow.yaml +36 -0
- package/src/__tests__/fixtures/e2e-mismatch.mock.yaml +16 -0
- package/src/__tests__/fixtures/e2e-mustache.mock.yaml +15 -0
- package/src/__tests__/fixtures/e2e-mustache.workflow.yaml +34 -0
- package/src/__tests__/fixtures/e2e-suspend.mock.yaml +14 -0
- package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +24 -0
- package/src/__tests__/include-tag.test.ts +84 -0
- package/src/__tests__/log.test.ts +181 -0
- package/src/__tests__/moderator-evaluate.test.ts +186 -0
- package/src/__tests__/preload.ts +7 -0
- package/src/__tests__/prompt.test.ts +129 -0
- package/src/__tests__/resolve-head-hash.test.ts +86 -0
- package/src/__tests__/setup-agent-discovery.test.ts +167 -0
- package/src/__tests__/setup-complexity.test.ts +381 -0
- package/src/__tests__/setup-validate.test.ts +148 -0
- package/src/__tests__/solve-issue-tea-worktree.test.ts +144 -0
- package/src/__tests__/spawn-agent-json.test.ts +100 -0
- package/src/__tests__/step-read.test.ts +632 -0
- package/src/__tests__/step-show-json.test.ts +373 -0
- package/src/__tests__/step-timing.test.ts +392 -0
- package/src/__tests__/store-global-cas.test.ts +308 -0
- package/src/__tests__/store-storage-root.test.ts +49 -0
- package/src/__tests__/store-unified-threads.test.ts +235 -0
- package/src/__tests__/thread-cancel-status.test.ts +138 -0
- package/src/__tests__/thread-list-filters.test.ts +572 -0
- package/src/__tests__/thread-location.test.ts +186 -0
- package/src/__tests__/thread-read-quota.test.ts +613 -0
- package/src/__tests__/thread-read-xml-tags.test.ts +717 -0
- package/src/__tests__/thread-resume.test.ts +710 -0
- package/src/__tests__/thread-show-status.test.ts +317 -0
- package/src/__tests__/thread-start-cwd-cli.test.ts +164 -0
- package/src/__tests__/thread-step-count.test.ts +70 -0
- package/src/__tests__/thread-suspend-step.test.ts +181 -0
- package/src/__tests__/thread-suspended-display.test.ts +287 -0
- package/src/__tests__/thread-test-helpers.ts +37 -0
- package/src/__tests__/thread.test.ts +1025 -0
- package/src/__tests__/validate-semantic.test.ts +474 -0
- package/src/__tests__/workflow-resolution.test.ts +421 -0
- package/src/background/background.ts +147 -0
- package/src/background/index.ts +11 -0
- package/src/background/types.ts +9 -0
- package/src/cli.ts +692 -0
- package/src/commands/config.ts +304 -0
- package/src/commands/log.ts +116 -0
- package/src/commands/prompt.ts +81 -0
- package/src/commands/setup.ts +603 -0
- package/src/commands/shared.ts +227 -0
- package/src/commands/step.ts +343 -0
- package/src/commands/thread-time-parser.ts +23 -0
- package/src/commands/thread.ts +1575 -0
- package/src/commands/workflow.ts +213 -0
- package/src/format.ts +12 -0
- package/src/include.ts +37 -0
- package/src/moderator/__tests__/evaluate.test.ts +199 -0
- package/src/moderator/evaluate.ts +80 -0
- package/src/moderator/index.ts +7 -0
- package/src/moderator/types.ts +24 -0
- package/src/schemas.ts +26 -0
- package/src/store.ts +479 -0
- package/src/validate-semantic.ts +304 -0
- package/src/validate.ts +137 -0
|
@@ -0,0 +1,710 @@
|
|
|
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 { CasRef, StepNodePayload, ThreadId } from "@united-workforce/protocol";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
10
|
+
import { cmdThreadShow } from "../commands/thread.js";
|
|
11
|
+
import { registerUwfSchemas } from "../schemas.js";
|
|
12
|
+
import { seedThreads } from "./thread-test-helpers.js";
|
|
13
|
+
|
|
14
|
+
const OUTPUT_SCHEMA = {
|
|
15
|
+
type: "object" as const,
|
|
16
|
+
properties: {
|
|
17
|
+
$status: { type: "string" as const },
|
|
18
|
+
question: { type: "string" as const },
|
|
19
|
+
},
|
|
20
|
+
required: ["$status"],
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const THREAD_ID = "01RESUMESTEPTEST0000000" as ThreadId;
|
|
25
|
+
const SUSPEND_MESSAGE = "Please clarify: Which API?";
|
|
26
|
+
|
|
27
|
+
type MockAgentMode = "suspend" | "ok";
|
|
28
|
+
|
|
29
|
+
let tmpDir: string;
|
|
30
|
+
|
|
31
|
+
beforeEach(async () => {
|
|
32
|
+
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-resume-test-"));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
async function setupSuspendedThread(mode: MockAgentMode): Promise<{
|
|
40
|
+
casDir: string;
|
|
41
|
+
mockAgentPath: string;
|
|
42
|
+
promptCapturePath: string;
|
|
43
|
+
}> {
|
|
44
|
+
const casDir = join(tmpDir, "cas");
|
|
45
|
+
await mkdir(casDir, { recursive: true });
|
|
46
|
+
|
|
47
|
+
const store = await openStore(casDir);
|
|
48
|
+
const schemas = await registerUwfSchemas(store);
|
|
49
|
+
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
|
|
50
|
+
|
|
51
|
+
const workflowHash = await store.cas.put(schemas.workflow, {
|
|
52
|
+
name: "test-resume",
|
|
53
|
+
description: "resume command integration test",
|
|
54
|
+
roles: {
|
|
55
|
+
worker: {
|
|
56
|
+
description: "Worker role",
|
|
57
|
+
goal: "Work",
|
|
58
|
+
capabilities: [],
|
|
59
|
+
procedure: "work",
|
|
60
|
+
output: "result",
|
|
61
|
+
frontmatter: outputSchemaHash,
|
|
62
|
+
},
|
|
63
|
+
reviewer: {
|
|
64
|
+
description: "Reviewer role",
|
|
65
|
+
goal: "Review",
|
|
66
|
+
capabilities: [],
|
|
67
|
+
procedure: "review",
|
|
68
|
+
output: "result",
|
|
69
|
+
frontmatter: outputSchemaHash,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
graph: {
|
|
73
|
+
$START: { _: { role: "worker", prompt: "Start work", location: null } },
|
|
74
|
+
worker: {
|
|
75
|
+
needs_input: {
|
|
76
|
+
role: "$SUSPEND",
|
|
77
|
+
prompt: "Please clarify: {{{question}}}",
|
|
78
|
+
location: null,
|
|
79
|
+
},
|
|
80
|
+
ok: { role: "reviewer", prompt: "Review the work", location: null },
|
|
81
|
+
},
|
|
82
|
+
reviewer: { done: { role: "$END", prompt: "Done", location: null } },
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const startHash = await store.cas.put(schemas.startNode, {
|
|
87
|
+
workflow: workflowHash,
|
|
88
|
+
prompt: "Test resume task",
|
|
89
|
+
cwd: tmpDir,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
process.env.OCAS_HOME = casDir;
|
|
93
|
+
await seedThreads(tmpDir, { [THREAD_ID]: startHash });
|
|
94
|
+
|
|
95
|
+
const outputHash = await store.cas.put(outputSchemaHash, {
|
|
96
|
+
$status: "needs_input",
|
|
97
|
+
question: "Which API?",
|
|
98
|
+
});
|
|
99
|
+
const detailHash = await store.cas.put(schemas.text, "mock detail");
|
|
100
|
+
|
|
101
|
+
const startedAtMs = 1716600000000;
|
|
102
|
+
const completedAtMs = 1716600001500;
|
|
103
|
+
|
|
104
|
+
const stepHash = await store.cas.put(schemas.stepNode, {
|
|
105
|
+
start: startHash,
|
|
106
|
+
prev: null,
|
|
107
|
+
role: "worker",
|
|
108
|
+
output: outputHash,
|
|
109
|
+
detail: detailHash,
|
|
110
|
+
agent: "uwf-mock",
|
|
111
|
+
edgePrompt: "Start work",
|
|
112
|
+
startedAtMs,
|
|
113
|
+
completedAtMs,
|
|
114
|
+
cwd: tmpDir,
|
|
115
|
+
assembledPrompt: null,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await seedThreads(tmpDir, {
|
|
119
|
+
[THREAD_ID]: {
|
|
120
|
+
head: stepHash,
|
|
121
|
+
status: "suspended",
|
|
122
|
+
suspendedRole: "worker",
|
|
123
|
+
suspendMessage: SUSPEND_MESSAGE,
|
|
124
|
+
completedAt: null,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const promptCapturePath = join(tmpDir, "captured-prompt.txt");
|
|
129
|
+
const mockAgentPath = join(tmpDir, "mock-agent.sh");
|
|
130
|
+
|
|
131
|
+
const frontmatter =
|
|
132
|
+
mode === "suspend" ? { $status: "needs_input", question: "Which API?" } : { $status: "ok" };
|
|
133
|
+
|
|
134
|
+
const adapterJson = JSON.stringify({
|
|
135
|
+
stepHash: await store.cas.put(schemas.stepNode, {
|
|
136
|
+
start: startHash,
|
|
137
|
+
prev: stepHash,
|
|
138
|
+
role: "worker",
|
|
139
|
+
output: await store.cas.put(outputSchemaHash, frontmatter),
|
|
140
|
+
detail: detailHash,
|
|
141
|
+
agent: "uwf-mock",
|
|
142
|
+
edgePrompt: "resume prompt placeholder",
|
|
143
|
+
startedAtMs: completedAtMs + 1,
|
|
144
|
+
completedAtMs: completedAtMs + 2,
|
|
145
|
+
cwd: tmpDir,
|
|
146
|
+
assembledPrompt: null,
|
|
147
|
+
}),
|
|
148
|
+
detailHash,
|
|
149
|
+
role: "worker",
|
|
150
|
+
frontmatter,
|
|
151
|
+
body: "",
|
|
152
|
+
startedAtMs: completedAtMs + 1,
|
|
153
|
+
completedAtMs: completedAtMs + 2,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await writeFile(
|
|
157
|
+
mockAgentPath,
|
|
158
|
+
`#!/bin/sh
|
|
159
|
+
prompt=""
|
|
160
|
+
while [ $# -gt 0 ]; do
|
|
161
|
+
if [ "$1" = "--prompt" ]; then
|
|
162
|
+
prompt="$2"
|
|
163
|
+
shift 2
|
|
164
|
+
else
|
|
165
|
+
shift
|
|
166
|
+
fi
|
|
167
|
+
done
|
|
168
|
+
printf '%s' "$prompt" > '${promptCapturePath}'
|
|
169
|
+
echo '${adapterJson}'
|
|
170
|
+
`,
|
|
171
|
+
{ mode: 0o755 },
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const configPath = join(tmpDir, "config.yaml");
|
|
175
|
+
await writeFile(
|
|
176
|
+
configPath,
|
|
177
|
+
`defaultAgent: uwf-hermes\ndefaultModel: test-model\nagentOverrides: null\nagents: {}\nproviders: {}\nmodels: {}\n`,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return { casDir, mockAgentPath, promptCapturePath };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function runUwf(
|
|
184
|
+
args: string[],
|
|
185
|
+
casDir: string,
|
|
186
|
+
): { stdout: string; stderr: string; status: number } {
|
|
187
|
+
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
|
|
188
|
+
try {
|
|
189
|
+
const stdout = execFileSync(process.execPath, [cliPath, ...args], {
|
|
190
|
+
encoding: "utf8",
|
|
191
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
192
|
+
env: {
|
|
193
|
+
...process.env,
|
|
194
|
+
UWF_HOME: tmpDir,
|
|
195
|
+
OCAS_HOME: casDir,
|
|
196
|
+
},
|
|
197
|
+
cwd: tmpDir,
|
|
198
|
+
timeout: 30000,
|
|
199
|
+
});
|
|
200
|
+
return { stdout, stderr: "", status: 0 };
|
|
201
|
+
} catch (error) {
|
|
202
|
+
const err = error as NodeJS.ErrnoException & {
|
|
203
|
+
stdout?: string | Buffer;
|
|
204
|
+
stderr?: string | Buffer;
|
|
205
|
+
status?: number;
|
|
206
|
+
};
|
|
207
|
+
return {
|
|
208
|
+
stdout: typeof err.stdout === "string" ? err.stdout : (err.stdout?.toString("utf8") ?? ""),
|
|
209
|
+
stderr: typeof err.stderr === "string" ? err.stderr : (err.stderr?.toString("utf8") ?? ""),
|
|
210
|
+
status: err.status ?? 1,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
describe("uwf thread resume", () => {
|
|
216
|
+
test("resume non-suspended thread returns error", async () => {
|
|
217
|
+
const casDir = join(tmpDir, "cas");
|
|
218
|
+
await mkdir(casDir, { recursive: true });
|
|
219
|
+
const store = await openStore(casDir);
|
|
220
|
+
const schemas = await registerUwfSchemas(store);
|
|
221
|
+
|
|
222
|
+
const workflowHash = await store.cas.put(schemas.workflow, {
|
|
223
|
+
name: "idle-workflow",
|
|
224
|
+
description: "idle thread",
|
|
225
|
+
roles: {
|
|
226
|
+
worker: {
|
|
227
|
+
description: "Worker",
|
|
228
|
+
goal: "Work",
|
|
229
|
+
capabilities: [],
|
|
230
|
+
procedure: "work",
|
|
231
|
+
output: "result",
|
|
232
|
+
frontmatter: await putSchema(store, OUTPUT_SCHEMA),
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
graph: {
|
|
236
|
+
$START: { _: { role: "worker", prompt: "Start", location: null } },
|
|
237
|
+
worker: { done: { role: "$END", prompt: "Done", location: null } },
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const startHash = await store.cas.put(schemas.startNode, {
|
|
242
|
+
workflow: workflowHash,
|
|
243
|
+
prompt: "task",
|
|
244
|
+
cwd: tmpDir,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
process.env.OCAS_HOME = casDir;
|
|
248
|
+
await seedThreads(tmpDir, { [THREAD_ID]: startHash });
|
|
249
|
+
|
|
250
|
+
const result = runUwf(["thread", "resume", THREAD_ID], casDir);
|
|
251
|
+
expect(result.status).not.toBe(0);
|
|
252
|
+
expect(result.stderr).toContain("thread cannot be resumed");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("resume suspended thread executes step and becomes idle", async () => {
|
|
256
|
+
const originalCasDir = process.env.OCAS_HOME;
|
|
257
|
+
const { casDir, mockAgentPath } = await setupSuspendedThread("ok");
|
|
258
|
+
process.env.OCAS_HOME = casDir;
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const result = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
|
262
|
+
expect(result.status).toBe(0);
|
|
263
|
+
|
|
264
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
265
|
+
expect(cliOutput.status).toBe("idle");
|
|
266
|
+
expect(cliOutput.currentRole).toBe("reviewer");
|
|
267
|
+
expect(cliOutput.suspendedRole).toBeNull();
|
|
268
|
+
expect(cliOutput.suspendMessage).toBeNull();
|
|
269
|
+
expect(cliOutput.done).toBe(false);
|
|
270
|
+
|
|
271
|
+
const { createUwfStore, getThread } = await import("../store.js");
|
|
272
|
+
const uwf = await createUwfStore(tmpDir);
|
|
273
|
+
const entry = getThread(uwf.varStore, THREAD_ID);
|
|
274
|
+
expect(entry?.head).toBe(cliOutput.head);
|
|
275
|
+
expect(entry?.suspendedRole).toBeNull();
|
|
276
|
+
expect(entry?.suspendMessage).toBeNull();
|
|
277
|
+
|
|
278
|
+
const showResult = await cmdThreadShow(tmpDir, THREAD_ID);
|
|
279
|
+
expect(showResult.status).toBe("idle");
|
|
280
|
+
expect(showResult.suspendedRole).toBeNull();
|
|
281
|
+
expect(showResult.suspendMessage).toBeNull();
|
|
282
|
+
} finally {
|
|
283
|
+
if (originalCasDir === undefined) {
|
|
284
|
+
delete process.env.OCAS_HOME;
|
|
285
|
+
} else {
|
|
286
|
+
process.env.OCAS_HOME = originalCasDir;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("resume without -p uses suspend message as agent prompt", async () => {
|
|
292
|
+
const originalCasDir = process.env.OCAS_HOME;
|
|
293
|
+
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("ok");
|
|
294
|
+
process.env.OCAS_HOME = casDir;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const result = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
|
298
|
+
expect(result.status).toBe(0);
|
|
299
|
+
|
|
300
|
+
const capturedPrompt = await readFile(promptCapturePath, "utf8");
|
|
301
|
+
expect(capturedPrompt).toBe(SUSPEND_MESSAGE);
|
|
302
|
+
} finally {
|
|
303
|
+
if (originalCasDir === undefined) {
|
|
304
|
+
delete process.env.OCAS_HOME;
|
|
305
|
+
} else {
|
|
306
|
+
process.env.OCAS_HOME = originalCasDir;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("resume with -p appends supplementary info to agent prompt", async () => {
|
|
312
|
+
const originalCasDir = process.env.OCAS_HOME;
|
|
313
|
+
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("ok");
|
|
314
|
+
process.env.OCAS_HOME = casDir;
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const supplement = "Use the REST API.";
|
|
318
|
+
const result = runUwf(
|
|
319
|
+
["thread", "resume", THREAD_ID, "-p", supplement, "--agent", mockAgentPath],
|
|
320
|
+
casDir,
|
|
321
|
+
);
|
|
322
|
+
expect(result.status).toBe(0);
|
|
323
|
+
|
|
324
|
+
const capturedPrompt = await readFile(promptCapturePath, "utf8");
|
|
325
|
+
expect(capturedPrompt).toBe(`${SUSPEND_MESSAGE}\n\n${supplement}`);
|
|
326
|
+
} finally {
|
|
327
|
+
if (originalCasDir === undefined) {
|
|
328
|
+
delete process.env.OCAS_HOME;
|
|
329
|
+
} else {
|
|
330
|
+
process.env.OCAS_HOME = originalCasDir;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("multiple suspend/resume cycles", async () => {
|
|
336
|
+
const originalCasDir = process.env.OCAS_HOME;
|
|
337
|
+
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("suspend");
|
|
338
|
+
process.env.OCAS_HOME = casDir;
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const firstResult = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
|
342
|
+
expect(firstResult.status).toBe(0);
|
|
343
|
+
const firstResume = JSON.parse(firstResult.stdout.trim());
|
|
344
|
+
expect(firstResume.status).toBe("suspended");
|
|
345
|
+
expect(firstResume.suspendedRole).toBe("worker");
|
|
346
|
+
expect(firstResume.suspendMessage).toBe(SUSPEND_MESSAGE);
|
|
347
|
+
|
|
348
|
+
const { createUwfStore, getThread } = await import("../store.js");
|
|
349
|
+
const uwfAfterFirst = await createUwfStore(tmpDir);
|
|
350
|
+
expect(getThread(uwfAfterFirst.varStore, THREAD_ID)).toEqual({
|
|
351
|
+
head: firstResume.head,
|
|
352
|
+
status: "suspended",
|
|
353
|
+
suspendedRole: "worker",
|
|
354
|
+
suspendMessage: SUSPEND_MESSAGE,
|
|
355
|
+
completedAt: null,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const { mockAgentPath: okMockAgentPath } = await setupOkMockAgent(
|
|
359
|
+
casDir,
|
|
360
|
+
firstResume.head as CasRef,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const secondResult = runUwf(
|
|
364
|
+
["thread", "resume", THREAD_ID, "--agent", okMockAgentPath],
|
|
365
|
+
casDir,
|
|
366
|
+
);
|
|
367
|
+
expect(secondResult.status).toBe(0);
|
|
368
|
+
const secondResume = JSON.parse(secondResult.stdout.trim());
|
|
369
|
+
expect(secondResume.status).toBe("idle");
|
|
370
|
+
expect(secondResume.currentRole).toBe("reviewer");
|
|
371
|
+
expect(secondResume.suspendedRole).toBeNull();
|
|
372
|
+
expect(secondResume.suspendMessage).toBeNull();
|
|
373
|
+
|
|
374
|
+
const capturedPrompt = await readFile(promptCapturePath, "utf8");
|
|
375
|
+
expect(capturedPrompt).toBe(SUSPEND_MESSAGE);
|
|
376
|
+
} finally {
|
|
377
|
+
if (originalCasDir === undefined) {
|
|
378
|
+
delete process.env.OCAS_HOME;
|
|
379
|
+
} else {
|
|
380
|
+
process.env.OCAS_HOME = originalCasDir;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
async function setupOkMockAgent(
|
|
387
|
+
casDir: string,
|
|
388
|
+
prevHead: CasRef,
|
|
389
|
+
): Promise<{ mockAgentPath: string }> {
|
|
390
|
+
const store = await openStore(casDir);
|
|
391
|
+
const schemas = await registerUwfSchemas(store);
|
|
392
|
+
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
|
|
393
|
+
|
|
394
|
+
const prevNode = store.cas.get(prevHead);
|
|
395
|
+
if (prevNode === null || prevNode.type !== schemas.stepNode) {
|
|
396
|
+
throw new Error(`expected StepNode at ${prevHead}`);
|
|
397
|
+
}
|
|
398
|
+
const prevPayload = prevNode.payload as StepNodePayload;
|
|
399
|
+
|
|
400
|
+
const outputHash = await store.cas.put(outputSchemaHash, { $status: "ok" });
|
|
401
|
+
const detailHash = await store.cas.put(schemas.text, "ok detail");
|
|
402
|
+
const startedAtMs = Date.now();
|
|
403
|
+
const completedAtMs = startedAtMs + 1;
|
|
404
|
+
|
|
405
|
+
const stepHash = await store.cas.put(schemas.stepNode, {
|
|
406
|
+
start: prevPayload.start,
|
|
407
|
+
prev: prevHead,
|
|
408
|
+
role: "worker",
|
|
409
|
+
output: outputHash,
|
|
410
|
+
detail: detailHash,
|
|
411
|
+
agent: "uwf-mock",
|
|
412
|
+
edgePrompt: "resume",
|
|
413
|
+
startedAtMs,
|
|
414
|
+
completedAtMs,
|
|
415
|
+
cwd: tmpDir,
|
|
416
|
+
assembledPrompt: null,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const promptCapturePath = join(tmpDir, "captured-prompt.txt");
|
|
420
|
+
const mockAgentPath = join(tmpDir, "mock-agent-ok.sh");
|
|
421
|
+
const adapterJson = JSON.stringify({
|
|
422
|
+
stepHash,
|
|
423
|
+
detailHash,
|
|
424
|
+
role: "worker",
|
|
425
|
+
frontmatter: { $status: "ok" },
|
|
426
|
+
body: "",
|
|
427
|
+
startedAtMs,
|
|
428
|
+
completedAtMs,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
await writeFile(
|
|
432
|
+
mockAgentPath,
|
|
433
|
+
`#!/bin/sh
|
|
434
|
+
prompt=""
|
|
435
|
+
while [ $# -gt 0 ]; do
|
|
436
|
+
if [ "$1" = "--prompt" ]; then
|
|
437
|
+
prompt="$2"
|
|
438
|
+
shift 2
|
|
439
|
+
else
|
|
440
|
+
shift
|
|
441
|
+
fi
|
|
442
|
+
done
|
|
443
|
+
printf '%s' "$prompt" > '${promptCapturePath}'
|
|
444
|
+
echo '${adapterJson}'
|
|
445
|
+
`,
|
|
446
|
+
{ mode: 0o755 },
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
return { mockAgentPath };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
describe("uwf thread resume - completed threads", () => {
|
|
453
|
+
test("resume completed thread starts from $START role", async () => {
|
|
454
|
+
const casDir = join(tmpDir, "cas");
|
|
455
|
+
await mkdir(casDir, { recursive: true });
|
|
456
|
+
const store = await openStore(casDir);
|
|
457
|
+
const schemas = await registerUwfSchemas(store);
|
|
458
|
+
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
|
|
459
|
+
|
|
460
|
+
const workflowHash = await store.cas.put(schemas.workflow, {
|
|
461
|
+
name: "test-completed-resume",
|
|
462
|
+
description: "completed thread resume test",
|
|
463
|
+
roles: {
|
|
464
|
+
worker: {
|
|
465
|
+
description: "Worker role",
|
|
466
|
+
goal: "Work",
|
|
467
|
+
capabilities: [],
|
|
468
|
+
procedure: "work",
|
|
469
|
+
output: "result",
|
|
470
|
+
frontmatter: outputSchemaHash,
|
|
471
|
+
},
|
|
472
|
+
reviewer: {
|
|
473
|
+
description: "Reviewer role",
|
|
474
|
+
goal: "Review",
|
|
475
|
+
capabilities: [],
|
|
476
|
+
procedure: "review",
|
|
477
|
+
output: "result",
|
|
478
|
+
frontmatter: outputSchemaHash,
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
graph: {
|
|
482
|
+
$START: { _: { role: "worker", prompt: "Start work", location: null } },
|
|
483
|
+
worker: { done: { role: "reviewer", prompt: "Review the work", location: null } },
|
|
484
|
+
reviewer: { done: { role: "$END", prompt: "Done", location: null } },
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const startHash = await store.cas.put(schemas.startNode, {
|
|
489
|
+
workflow: workflowHash,
|
|
490
|
+
prompt: "Initial task",
|
|
491
|
+
cwd: tmpDir,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
process.env.OCAS_HOME = casDir;
|
|
495
|
+
|
|
496
|
+
const workerOutputHash = await store.cas.put(outputSchemaHash, { $status: "done" });
|
|
497
|
+
const reviewerOutputHash = await store.cas.put(outputSchemaHash, { $status: "done" });
|
|
498
|
+
const detailHash = await store.cas.put(schemas.text, "mock detail");
|
|
499
|
+
|
|
500
|
+
const workerStepHash = await store.cas.put(schemas.stepNode, {
|
|
501
|
+
start: startHash,
|
|
502
|
+
prev: null,
|
|
503
|
+
role: "worker",
|
|
504
|
+
output: workerOutputHash,
|
|
505
|
+
detail: detailHash,
|
|
506
|
+
agent: "uwf-mock",
|
|
507
|
+
edgePrompt: "Start work",
|
|
508
|
+
startedAtMs: 1716600000000,
|
|
509
|
+
completedAtMs: 1716600001000,
|
|
510
|
+
cwd: tmpDir,
|
|
511
|
+
assembledPrompt: null,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const reviewerStepHash = await store.cas.put(schemas.stepNode, {
|
|
515
|
+
start: startHash,
|
|
516
|
+
prev: workerStepHash,
|
|
517
|
+
role: "reviewer",
|
|
518
|
+
output: reviewerOutputHash,
|
|
519
|
+
detail: detailHash,
|
|
520
|
+
agent: "uwf-mock",
|
|
521
|
+
edgePrompt: "Review the work",
|
|
522
|
+
startedAtMs: 1716600001000,
|
|
523
|
+
completedAtMs: 1716600002000,
|
|
524
|
+
cwd: tmpDir,
|
|
525
|
+
assembledPrompt: null,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
await seedThreads(tmpDir, {
|
|
529
|
+
[THREAD_ID]: {
|
|
530
|
+
head: reviewerStepHash,
|
|
531
|
+
status: "completed",
|
|
532
|
+
suspendedRole: null,
|
|
533
|
+
suspendMessage: null,
|
|
534
|
+
completedAt: 1716600002000,
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Verify the status was actually set
|
|
539
|
+
const { createUwfStore, getThread } = await import("../store.js");
|
|
540
|
+
const verifyUwf = await createUwfStore(tmpDir);
|
|
541
|
+
const verifyEntry = getThread(verifyUwf.varStore, THREAD_ID);
|
|
542
|
+
console.log("Seeded entry status:", verifyEntry?.status);
|
|
543
|
+
console.log("Seeded entry:", JSON.stringify(verifyEntry, null, 2));
|
|
544
|
+
|
|
545
|
+
const promptCapturePath = join(tmpDir, "captured-prompt-completed.txt");
|
|
546
|
+
const mockAgentPath = join(tmpDir, "mock-agent-completed.sh");
|
|
547
|
+
|
|
548
|
+
const newWorkerStepHash = await store.cas.put(schemas.stepNode, {
|
|
549
|
+
start: startHash,
|
|
550
|
+
prev: reviewerStepHash,
|
|
551
|
+
role: "worker",
|
|
552
|
+
output: workerOutputHash,
|
|
553
|
+
detail: detailHash,
|
|
554
|
+
agent: "uwf-mock",
|
|
555
|
+
edgePrompt: "Start work",
|
|
556
|
+
startedAtMs: 1716600003000,
|
|
557
|
+
completedAtMs: 1716600004000,
|
|
558
|
+
cwd: tmpDir,
|
|
559
|
+
assembledPrompt: null,
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const adapterJson = JSON.stringify({
|
|
563
|
+
stepHash: newWorkerStepHash,
|
|
564
|
+
detailHash,
|
|
565
|
+
role: "worker",
|
|
566
|
+
frontmatter: { $status: "done" },
|
|
567
|
+
body: "",
|
|
568
|
+
startedAtMs: 1716600003000,
|
|
569
|
+
completedAtMs: 1716600004000,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
await writeFile(
|
|
573
|
+
mockAgentPath,
|
|
574
|
+
`#!/bin/sh
|
|
575
|
+
prompt=""
|
|
576
|
+
while [ $# -gt 0 ]; do
|
|
577
|
+
if [ "$1" = "--prompt" ]; then
|
|
578
|
+
prompt="$2"
|
|
579
|
+
shift 2
|
|
580
|
+
else
|
|
581
|
+
shift
|
|
582
|
+
fi
|
|
583
|
+
done
|
|
584
|
+
printf '%s' "$prompt" > '${promptCapturePath}'
|
|
585
|
+
echo '${adapterJson}'
|
|
586
|
+
`,
|
|
587
|
+
{ mode: 0o755 },
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
const configPath = join(tmpDir, "config.yaml");
|
|
591
|
+
await writeFile(
|
|
592
|
+
configPath,
|
|
593
|
+
`defaultAgent: uwf-hermes\ndefaultModel: test-model\nagentOverrides: null\nagents: {}\nproviders: {}\nmodels: {}\n`,
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
const result = runUwf(
|
|
597
|
+
["thread", "resume", THREAD_ID, "-p", "Additional context", "--agent", mockAgentPath],
|
|
598
|
+
casDir,
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
if (result.status !== 0) {
|
|
602
|
+
console.error("Command failed:", result.stderr);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
expect(result.status).toBe(0);
|
|
606
|
+
|
|
607
|
+
const cliOutput = JSON.parse(result.stdout.trim());
|
|
608
|
+
expect(cliOutput.status).toBe("idle");
|
|
609
|
+
expect(cliOutput.currentRole).toBe("reviewer");
|
|
610
|
+
expect(cliOutput.done).toBe(false);
|
|
611
|
+
|
|
612
|
+
const capturedPrompt = await readFile(promptCapturePath, "utf8");
|
|
613
|
+
expect(capturedPrompt).toContain("Previous run completed");
|
|
614
|
+
expect(capturedPrompt).toContain("Additional context");
|
|
615
|
+
|
|
616
|
+
const storeModule = await import("../store.js");
|
|
617
|
+
const uwf2 = await storeModule.createUwfStore(tmpDir);
|
|
618
|
+
const entry2 = storeModule.getThread(uwf2.varStore, THREAD_ID);
|
|
619
|
+
expect(entry2?.status).toBe("idle");
|
|
620
|
+
expect(entry2?.completedAt).toBeNull();
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("resume cancelled thread returns error", async () => {
|
|
624
|
+
const casDir = join(tmpDir, "cas");
|
|
625
|
+
await mkdir(casDir, { recursive: true });
|
|
626
|
+
const store = await openStore(casDir);
|
|
627
|
+
const schemas = await registerUwfSchemas(store);
|
|
628
|
+
|
|
629
|
+
const workflowHash = await store.cas.put(schemas.workflow, {
|
|
630
|
+
name: "cancelled-workflow",
|
|
631
|
+
description: "cancelled thread",
|
|
632
|
+
roles: {
|
|
633
|
+
worker: {
|
|
634
|
+
description: "Worker",
|
|
635
|
+
goal: "Work",
|
|
636
|
+
capabilities: [],
|
|
637
|
+
procedure: "work",
|
|
638
|
+
output: "result",
|
|
639
|
+
frontmatter: await putSchema(store, OUTPUT_SCHEMA),
|
|
640
|
+
},
|
|
641
|
+
},
|
|
642
|
+
graph: {
|
|
643
|
+
$START: { _: { role: "worker", prompt: "Start", location: null } },
|
|
644
|
+
worker: { done: { role: "$END", prompt: "Done", location: null } },
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const startHash = await store.cas.put(schemas.startNode, {
|
|
649
|
+
workflow: workflowHash,
|
|
650
|
+
prompt: "task",
|
|
651
|
+
cwd: tmpDir,
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
process.env.OCAS_HOME = casDir;
|
|
655
|
+
await seedThreads(tmpDir, {
|
|
656
|
+
[THREAD_ID]: {
|
|
657
|
+
head: startHash,
|
|
658
|
+
status: "cancelled",
|
|
659
|
+
suspendedRole: null,
|
|
660
|
+
suspendMessage: null,
|
|
661
|
+
completedAt: null,
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const result = runUwf(["thread", "resume", THREAD_ID], casDir);
|
|
666
|
+
expect(result.status).not.toBe(0);
|
|
667
|
+
expect(result.stderr).toContain("thread cannot be resumed");
|
|
668
|
+
expect(result.stderr).toContain("cancelled");
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("resume idle thread returns error", async () => {
|
|
672
|
+
const casDir = join(tmpDir, "cas");
|
|
673
|
+
await mkdir(casDir, { recursive: true });
|
|
674
|
+
const store = await openStore(casDir);
|
|
675
|
+
const schemas = await registerUwfSchemas(store);
|
|
676
|
+
|
|
677
|
+
const workflowHash = await store.cas.put(schemas.workflow, {
|
|
678
|
+
name: "idle-workflow",
|
|
679
|
+
description: "idle thread",
|
|
680
|
+
roles: {
|
|
681
|
+
worker: {
|
|
682
|
+
description: "Worker",
|
|
683
|
+
goal: "Work",
|
|
684
|
+
capabilities: [],
|
|
685
|
+
procedure: "work",
|
|
686
|
+
output: "result",
|
|
687
|
+
frontmatter: await putSchema(store, OUTPUT_SCHEMA),
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
graph: {
|
|
691
|
+
$START: { _: { role: "worker", prompt: "Start", location: null } },
|
|
692
|
+
worker: { done: { role: "$END", prompt: "Done", location: null } },
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
const startHash = await store.cas.put(schemas.startNode, {
|
|
697
|
+
workflow: workflowHash,
|
|
698
|
+
prompt: "task",
|
|
699
|
+
cwd: tmpDir,
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
process.env.OCAS_HOME = casDir;
|
|
703
|
+
await seedThreads(tmpDir, { [THREAD_ID]: startHash });
|
|
704
|
+
|
|
705
|
+
const result = runUwf(["thread", "resume", THREAD_ID], casDir);
|
|
706
|
+
expect(result.status).not.toBe(0);
|
|
707
|
+
expect(result.stderr).toContain("thread cannot be resumed");
|
|
708
|
+
expect(result.stderr).toContain("idle");
|
|
709
|
+
});
|
|
710
|
+
});
|