@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,181 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdir, mkdtemp, 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
|
+
let tmpDir: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-suspend-step-test-"));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|
35
|
+
test("thread exec records suspend step in CAS and suspend metadata in threads.yaml", async () => {
|
|
36
|
+
const casDir = join(tmpDir, "cas");
|
|
37
|
+
await mkdir(casDir, { recursive: true });
|
|
38
|
+
const originalCasDir = process.env.OCAS_HOME;
|
|
39
|
+
process.env.OCAS_HOME = casDir;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const store = await openStore(casDir);
|
|
43
|
+
const schemas = await registerUwfSchemas(store);
|
|
44
|
+
|
|
45
|
+
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
|
|
46
|
+
|
|
47
|
+
const workflowHash = await store.cas.put(schemas.workflow, {
|
|
48
|
+
name: "test-suspend-step",
|
|
49
|
+
description: "suspend step integration test",
|
|
50
|
+
roles: {
|
|
51
|
+
worker: {
|
|
52
|
+
description: "Worker role",
|
|
53
|
+
goal: "Work",
|
|
54
|
+
capabilities: [],
|
|
55
|
+
procedure: "work",
|
|
56
|
+
output: "result",
|
|
57
|
+
frontmatter: outputSchemaHash,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
graph: {
|
|
61
|
+
$START: { _: { role: "worker", prompt: "Start work", location: null } },
|
|
62
|
+
worker: {
|
|
63
|
+
needs_input: {
|
|
64
|
+
role: "$SUSPEND",
|
|
65
|
+
prompt: "Please clarify: {{{question}}}",
|
|
66
|
+
location: null,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const startHash = await store.cas.put(schemas.startNode, {
|
|
73
|
+
workflow: workflowHash,
|
|
74
|
+
prompt: "Test suspend task",
|
|
75
|
+
cwd: tmpDir,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const threadId = "01SUSPENDSTEPTEST0000000" as ThreadId;
|
|
79
|
+
await seedThreads(tmpDir, { [threadId]: startHash });
|
|
80
|
+
|
|
81
|
+
const outputHash = await store.cas.put(outputSchemaHash, {
|
|
82
|
+
$status: "needs_input",
|
|
83
|
+
question: "Which API?",
|
|
84
|
+
});
|
|
85
|
+
const detailHash = await store.cas.put(schemas.text, "mock detail");
|
|
86
|
+
|
|
87
|
+
const startedAtMs = 1716600000000;
|
|
88
|
+
const completedAtMs = 1716600001500;
|
|
89
|
+
|
|
90
|
+
const stepHash = await store.cas.put(schemas.stepNode, {
|
|
91
|
+
start: startHash,
|
|
92
|
+
prev: null,
|
|
93
|
+
role: "worker",
|
|
94
|
+
output: outputHash,
|
|
95
|
+
detail: detailHash,
|
|
96
|
+
agent: "uwf-mock",
|
|
97
|
+
edgePrompt: "Start work",
|
|
98
|
+
startedAtMs,
|
|
99
|
+
completedAtMs,
|
|
100
|
+
cwd: tmpDir,
|
|
101
|
+
assembledPrompt: null,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const mockAgentPath = join(tmpDir, "mock-agent.sh");
|
|
105
|
+
const adapterJson = JSON.stringify({
|
|
106
|
+
stepHash,
|
|
107
|
+
detailHash,
|
|
108
|
+
role: "worker",
|
|
109
|
+
frontmatter: { $status: "needs_input", question: "Which API?" },
|
|
110
|
+
body: "",
|
|
111
|
+
startedAtMs,
|
|
112
|
+
completedAtMs,
|
|
113
|
+
});
|
|
114
|
+
await writeFile(mockAgentPath, `#!/bin/sh\necho '${adapterJson}'\n`, { mode: 0o755 });
|
|
115
|
+
|
|
116
|
+
const configPath = join(tmpDir, "config.yaml");
|
|
117
|
+
await writeFile(
|
|
118
|
+
configPath,
|
|
119
|
+
`defaultAgent: uwf-hermes\ndefaultModel: test-model\nagentOverrides: null\nagents: {}\nproviders: {}\nmodels: {}\n`,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
|
|
123
|
+
const stdout = execFileSync(
|
|
124
|
+
process.execPath,
|
|
125
|
+
[cliPath, "thread", "exec", threadId, "--agent", mockAgentPath],
|
|
126
|
+
{
|
|
127
|
+
encoding: "utf8",
|
|
128
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
129
|
+
env: {
|
|
130
|
+
...process.env,
|
|
131
|
+
UWF_HOME: tmpDir,
|
|
132
|
+
OCAS_HOME: casDir,
|
|
133
|
+
},
|
|
134
|
+
cwd: tmpDir,
|
|
135
|
+
timeout: 30000,
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const cliOutput = JSON.parse(stdout.trim());
|
|
140
|
+
expect(cliOutput.status).toBe("suspended");
|
|
141
|
+
expect(cliOutput.head).toBe(stepHash);
|
|
142
|
+
expect(cliOutput.suspendedRole).toBe("worker");
|
|
143
|
+
expect(cliOutput.suspendMessage).toBe("Please clarify: Which API?");
|
|
144
|
+
|
|
145
|
+
const storeAfter = await openStore(casDir);
|
|
146
|
+
const stepNode = storeAfter.cas.get(cliOutput.head as CasRef);
|
|
147
|
+
expect(stepNode).not.toBeNull();
|
|
148
|
+
const payload = stepNode!.payload as StepNodePayload;
|
|
149
|
+
expect(payload.role).toBe("worker");
|
|
150
|
+
expect(payload.output).toBe(outputHash);
|
|
151
|
+
|
|
152
|
+
const outputNode = storeAfter.cas.get(outputHash);
|
|
153
|
+
expect(outputNode?.payload).toEqual({
|
|
154
|
+
$status: "needs_input",
|
|
155
|
+
question: "Which API?",
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const { createUwfStore, getThread } = await import("../store.js");
|
|
159
|
+
const uwf = await createUwfStore(tmpDir);
|
|
160
|
+
const threadEntry = getThread(uwf.varStore, threadId);
|
|
161
|
+
expect(threadEntry).toEqual({
|
|
162
|
+
head: stepHash,
|
|
163
|
+
status: "suspended",
|
|
164
|
+
suspendedRole: "worker",
|
|
165
|
+
suspendMessage: "Please clarify: Which API?",
|
|
166
|
+
completedAt: null,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const showResult = await cmdThreadShow(tmpDir, threadId);
|
|
170
|
+
expect(showResult.status).toBe("suspended");
|
|
171
|
+
expect(showResult.suspendMessage).toBe("Please clarify: Which API?");
|
|
172
|
+
expect(showResult.suspendedRole).toBe("worker");
|
|
173
|
+
} finally {
|
|
174
|
+
if (originalCasDir === undefined) {
|
|
175
|
+
delete process.env.OCAS_HOME;
|
|
176
|
+
} else {
|
|
177
|
+
process.env.OCAS_HOME = originalCasDir;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { putSchema } from "@ocas/core";
|
|
5
|
+
import type { ThreadId } from "@united-workforce/protocol";
|
|
6
|
+
import { createThreadIndexEntry, markThreadSuspended } from "@united-workforce/protocol";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
8
|
+
import { cmdThreadList, cmdThreadShow } from "../commands/thread.js";
|
|
9
|
+
import { createUwfStore } from "../store.js";
|
|
10
|
+
import { seedThreads } from "./thread-test-helpers.js";
|
|
11
|
+
|
|
12
|
+
const OUTPUT_SCHEMA = {
|
|
13
|
+
type: "object" as const,
|
|
14
|
+
properties: {
|
|
15
|
+
$status: { type: "string" as const },
|
|
16
|
+
question: { type: "string" as const },
|
|
17
|
+
},
|
|
18
|
+
required: ["$status"],
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let tmpDir: string;
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-suspended-display-test-"));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("suspended thread display", () => {
|
|
33
|
+
test("thread list shows [suspended] marker for suspended threads", async () => {
|
|
34
|
+
const casDir = join(tmpDir, "cas");
|
|
35
|
+
await mkdir(casDir, { recursive: true });
|
|
36
|
+
const originalCasDir = process.env.OCAS_HOME;
|
|
37
|
+
process.env.OCAS_HOME = casDir;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const uwf = await createUwfStore(tmpDir);
|
|
41
|
+
const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
|
|
42
|
+
|
|
43
|
+
// Create test workflow with suspend capability
|
|
44
|
+
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
|
|
45
|
+
name: "test-suspend-display",
|
|
46
|
+
description: "test suspended display",
|
|
47
|
+
roles: {
|
|
48
|
+
worker: {
|
|
49
|
+
description: "Worker role",
|
|
50
|
+
goal: "Work and potentially suspend",
|
|
51
|
+
capabilities: [],
|
|
52
|
+
procedure: "work",
|
|
53
|
+
output: "result",
|
|
54
|
+
frontmatter: outputSchemaHash,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
graph: {
|
|
58
|
+
$START: { _: { role: "worker", prompt: "Start work", location: null } },
|
|
59
|
+
worker: {
|
|
60
|
+
needs_input: {
|
|
61
|
+
role: "$SUSPEND",
|
|
62
|
+
prompt: "Please provide more details: {{{question}}}",
|
|
63
|
+
location: null,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
|
|
70
|
+
workflow: workflowHash,
|
|
71
|
+
prompt: "Test task requiring input",
|
|
72
|
+
cwd: tmpDir,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Create suspended thread
|
|
76
|
+
const suspendedThreadId = "01SUSPENDEDTHREAD0000000" as ThreadId;
|
|
77
|
+
const outputHash = await uwf.store.cas.put(outputSchemaHash, {
|
|
78
|
+
$status: "needs_input",
|
|
79
|
+
question: "What is the target API?",
|
|
80
|
+
});
|
|
81
|
+
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "mock detail");
|
|
82
|
+
|
|
83
|
+
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
|
|
84
|
+
start: startHash,
|
|
85
|
+
prev: null,
|
|
86
|
+
role: "worker",
|
|
87
|
+
output: outputHash,
|
|
88
|
+
detail: detailHash,
|
|
89
|
+
agent: "uwf-mock",
|
|
90
|
+
edgePrompt: "Start work",
|
|
91
|
+
startedAtMs: 1716600000000,
|
|
92
|
+
completedAtMs: 1716600001500,
|
|
93
|
+
cwd: tmpDir,
|
|
94
|
+
assembledPrompt: null,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Create suspended thread entry in threads.yaml
|
|
98
|
+
const suspendedEntry = markThreadSuspended(
|
|
99
|
+
createThreadIndexEntry(stepHash),
|
|
100
|
+
"worker",
|
|
101
|
+
"Please provide more details: What is the target API?",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Create normal (idle) thread
|
|
105
|
+
const idleThreadId = "01IDLETHREAD00000000000" as ThreadId;
|
|
106
|
+
const idleStartHash = await uwf.store.cas.put(uwf.schemas.startNode, {
|
|
107
|
+
workflow: workflowHash,
|
|
108
|
+
prompt: "Normal task",
|
|
109
|
+
cwd: tmpDir,
|
|
110
|
+
});
|
|
111
|
+
const idleEntry = createThreadIndexEntry(idleStartHash);
|
|
112
|
+
|
|
113
|
+
await seedThreads(tmpDir, {
|
|
114
|
+
[suspendedThreadId]: suspendedEntry,
|
|
115
|
+
[idleThreadId]: idleEntry,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Test thread list
|
|
119
|
+
const listResult = await cmdThreadList(tmpDir, null, null, null, null, null);
|
|
120
|
+
|
|
121
|
+
// Find the suspended and idle threads in results
|
|
122
|
+
const suspendedItem = listResult.find((item) => item.thread === suspendedThreadId);
|
|
123
|
+
const idleItem = listResult.find((item) => item.thread === idleThreadId);
|
|
124
|
+
|
|
125
|
+
expect(suspendedItem).toBeDefined();
|
|
126
|
+
expect(suspendedItem!.status).toBe("suspended");
|
|
127
|
+
expect(suspendedItem!.statusDisplay).toBe("suspended [suspended]");
|
|
128
|
+
|
|
129
|
+
expect(idleItem).toBeDefined();
|
|
130
|
+
expect(idleItem!.status).toBe("idle");
|
|
131
|
+
expect(idleItem!.statusDisplay).toBe("idle");
|
|
132
|
+
} finally {
|
|
133
|
+
if (originalCasDir === undefined) {
|
|
134
|
+
delete process.env.OCAS_HOME;
|
|
135
|
+
} else {
|
|
136
|
+
process.env.OCAS_HOME = originalCasDir;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("thread show displays suspend info and resume hint", async () => {
|
|
142
|
+
const casDir = join(tmpDir, "cas");
|
|
143
|
+
await mkdir(casDir, { recursive: true });
|
|
144
|
+
const originalCasDir = process.env.OCAS_HOME;
|
|
145
|
+
process.env.OCAS_HOME = casDir;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const uwf = await createUwfStore(tmpDir);
|
|
149
|
+
const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
|
|
150
|
+
|
|
151
|
+
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
|
|
152
|
+
name: "test-suspend-show",
|
|
153
|
+
description: "test suspended show",
|
|
154
|
+
roles: {
|
|
155
|
+
worker: {
|
|
156
|
+
description: "Worker role",
|
|
157
|
+
goal: "Work and potentially suspend",
|
|
158
|
+
capabilities: [],
|
|
159
|
+
procedure: "work",
|
|
160
|
+
output: "result",
|
|
161
|
+
frontmatter: outputSchemaHash,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
graph: {
|
|
165
|
+
$START: { _: { role: "worker", prompt: "Start work", location: null } },
|
|
166
|
+
worker: {
|
|
167
|
+
needs_input: {
|
|
168
|
+
role: "$SUSPEND",
|
|
169
|
+
prompt: "Need clarification: {{{question}}}",
|
|
170
|
+
location: null,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
|
|
177
|
+
workflow: workflowHash,
|
|
178
|
+
prompt: "Test task",
|
|
179
|
+
cwd: tmpDir,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const threadId = "01SUSPENDSHOW000000000" as ThreadId;
|
|
183
|
+
const outputHash = await uwf.store.cas.put(outputSchemaHash, {
|
|
184
|
+
$status: "needs_input",
|
|
185
|
+
question: "Which database to use?",
|
|
186
|
+
});
|
|
187
|
+
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "mock detail");
|
|
188
|
+
|
|
189
|
+
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
|
|
190
|
+
start: startHash,
|
|
191
|
+
prev: null,
|
|
192
|
+
role: "worker",
|
|
193
|
+
output: outputHash,
|
|
194
|
+
detail: detailHash,
|
|
195
|
+
agent: "uwf-mock",
|
|
196
|
+
edgePrompt: "Start work",
|
|
197
|
+
startedAtMs: 1716600000000,
|
|
198
|
+
completedAtMs: 1716600001500,
|
|
199
|
+
cwd: tmpDir,
|
|
200
|
+
assembledPrompt: null,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const suspendedEntry = markThreadSuspended(
|
|
204
|
+
createThreadIndexEntry(stepHash),
|
|
205
|
+
"worker",
|
|
206
|
+
"Need clarification: Which database to use?",
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
await seedThreads(tmpDir, { [threadId]: suspendedEntry });
|
|
210
|
+
|
|
211
|
+
// Test thread show
|
|
212
|
+
const showResult = await cmdThreadShow(tmpDir, threadId);
|
|
213
|
+
|
|
214
|
+
expect(showResult.status).toBe("suspended");
|
|
215
|
+
expect(showResult.suspendedRole).toBe("worker");
|
|
216
|
+
expect(showResult.suspendMessage).toBe("Need clarification: Which database to use?");
|
|
217
|
+
expect(showResult.hint).toBe(
|
|
218
|
+
`Thread is suspended. Resume with: uwf thread resume ${threadId}`,
|
|
219
|
+
);
|
|
220
|
+
} finally {
|
|
221
|
+
if (originalCasDir === undefined) {
|
|
222
|
+
delete process.env.OCAS_HOME;
|
|
223
|
+
} else {
|
|
224
|
+
process.env.OCAS_HOME = originalCasDir;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("non-suspended threads do not show suspend markers or hints", async () => {
|
|
230
|
+
const casDir = join(tmpDir, "cas");
|
|
231
|
+
await mkdir(casDir, { recursive: true });
|
|
232
|
+
const originalCasDir = process.env.OCAS_HOME;
|
|
233
|
+
process.env.OCAS_HOME = casDir;
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const uwf = await createUwfStore(tmpDir);
|
|
237
|
+
|
|
238
|
+
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
|
|
239
|
+
name: "test-normal",
|
|
240
|
+
description: "test normal thread",
|
|
241
|
+
roles: {
|
|
242
|
+
worker: {
|
|
243
|
+
description: "Worker role",
|
|
244
|
+
goal: "Work normally",
|
|
245
|
+
capabilities: [],
|
|
246
|
+
procedure: "work",
|
|
247
|
+
output: "result",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
graph: {
|
|
251
|
+
$START: { _: { role: "worker", prompt: "Start work", location: null } },
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
|
|
256
|
+
workflow: workflowHash,
|
|
257
|
+
prompt: "Normal task",
|
|
258
|
+
cwd: tmpDir,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const threadId = "01NORMALTHREAD000000000" as ThreadId;
|
|
262
|
+
await seedThreads(tmpDir, { [threadId]: createThreadIndexEntry(startHash) });
|
|
263
|
+
|
|
264
|
+
// Test thread show
|
|
265
|
+
const showResult = await cmdThreadShow(tmpDir, threadId);
|
|
266
|
+
|
|
267
|
+
expect(showResult.status).toBe("idle");
|
|
268
|
+
expect(showResult.suspendedRole).toBeNull();
|
|
269
|
+
expect(showResult.suspendMessage).toBeNull();
|
|
270
|
+
expect(showResult.hint).toBeNull();
|
|
271
|
+
|
|
272
|
+
// Test thread list
|
|
273
|
+
const listResult = await cmdThreadList(tmpDir, null, null, null, null, null);
|
|
274
|
+
const threadItem = listResult.find((item) => item.thread === threadId);
|
|
275
|
+
|
|
276
|
+
expect(threadItem).toBeDefined();
|
|
277
|
+
expect(threadItem!.status).toBe("idle");
|
|
278
|
+
expect(threadItem!.statusDisplay).toBe("idle");
|
|
279
|
+
} finally {
|
|
280
|
+
if (originalCasDir === undefined) {
|
|
281
|
+
delete process.env.OCAS_HOME;
|
|
282
|
+
} else {
|
|
283
|
+
process.env.OCAS_HOME = originalCasDir;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CasRef, ThreadId, ThreadIndexEntry } from "@united-workforce/protocol";
|
|
2
|
+
import { createThreadIndexEntry } from "@united-workforce/protocol";
|
|
3
|
+
import { createUwfStore, setThread } from "../store.js";
|
|
4
|
+
|
|
5
|
+
async function ensureHeadInCas(
|
|
6
|
+
uwf: Awaited<ReturnType<typeof createUwfStore>>,
|
|
7
|
+
head: CasRef,
|
|
8
|
+
threadId: ThreadId,
|
|
9
|
+
): Promise<CasRef> {
|
|
10
|
+
if (uwf.store.cas.get(head) !== null) {
|
|
11
|
+
return head;
|
|
12
|
+
}
|
|
13
|
+
return (await uwf.store.cas.put(uwf.schemas.text, `thread-head:${threadId}:${head}`)) as CasRef;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function seedThread(
|
|
17
|
+
storageRoot: string,
|
|
18
|
+
threadId: ThreadId,
|
|
19
|
+
entry: ThreadIndexEntry | CasRef,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const uwf = await createUwfStore(storageRoot);
|
|
22
|
+
const normalized = typeof entry === "string" ? createThreadIndexEntry(entry) : entry;
|
|
23
|
+
const head = await ensureHeadInCas(uwf, normalized.head, threadId);
|
|
24
|
+
setThread(uwf.varStore, threadId, { ...normalized, head });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function seedThreads(
|
|
28
|
+
storageRoot: string,
|
|
29
|
+
entries: Record<ThreadId, ThreadIndexEntry | CasRef>,
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
const uwf = await createUwfStore(storageRoot);
|
|
32
|
+
for (const [threadId, entry] of Object.entries(entries)) {
|
|
33
|
+
const normalized = typeof entry === "string" ? createThreadIndexEntry(entry as CasRef) : entry;
|
|
34
|
+
const head = await ensureHeadInCas(uwf, normalized.head, threadId as ThreadId);
|
|
35
|
+
setThread(uwf.varStore, threadId as ThreadId, { ...normalized, head });
|
|
36
|
+
}
|
|
37
|
+
}
|