@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
package/src/store.ts
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import type { Dirent } from "node:fs";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { access, mkdir, readdir, readFile, rename } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
import { bootstrap, type Hash, type Store, type VarStore } from "@ocas/core";
|
|
8
|
+
import { createFsStore, createSqliteVarStore } from "@ocas/fs";
|
|
9
|
+
import type { CasRef, ThreadId, ThreadIndexEntry, ThreadsIndex } from "@united-workforce/protocol";
|
|
10
|
+
import { parseThreadsIndex } from "@united-workforce/protocol";
|
|
11
|
+
import { parse } from "yaml";
|
|
12
|
+
|
|
13
|
+
import { registerUwfSchemas, type UwfSchemaHashes } from "./schemas.js";
|
|
14
|
+
|
|
15
|
+
export type WorkflowRegistry = Record<string, CasRef>;
|
|
16
|
+
|
|
17
|
+
/** Variable name prefix for workflow registry entries (`@uwf/registry/<name>`). */
|
|
18
|
+
export const REGISTRY_VAR_PREFIX = "@uwf/registry/";
|
|
19
|
+
|
|
20
|
+
/** Variable name prefix for active thread entries (`@uwf/thread/<thread-id>`). */
|
|
21
|
+
export const THREAD_VAR_PREFIX = "@uwf/thread/";
|
|
22
|
+
|
|
23
|
+
/** A workflow entry discovered from the project-local .workflows/ directory. */
|
|
24
|
+
export type ProjectWorkflowEntry = {
|
|
25
|
+
/** Workflow name (from YAML `name` field, equals filename stem). */
|
|
26
|
+
name: string;
|
|
27
|
+
/** Absolute path to the YAML file. */
|
|
28
|
+
filePath: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Extract workflow name from a YAML filename (strip .yaml/.yml extension). */
|
|
32
|
+
function stemFromYaml(name: string): string {
|
|
33
|
+
if (name.endsWith(".yaml")) return name.slice(0, -5);
|
|
34
|
+
if (name.endsWith(".yml")) return name.slice(0, -4);
|
|
35
|
+
return name;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Check if a directory contains an index.yaml or index.yml workflow file. */
|
|
39
|
+
async function findIndexWorkflow(
|
|
40
|
+
dir: string,
|
|
41
|
+
dirName: string,
|
|
42
|
+
): Promise<ProjectWorkflowEntry | null> {
|
|
43
|
+
for (const indexName of ["index.yaml", "index.yml"]) {
|
|
44
|
+
const indexPath = join(dir, dirName, indexName);
|
|
45
|
+
try {
|
|
46
|
+
await access(indexPath);
|
|
47
|
+
return { name: dirName, filePath: indexPath };
|
|
48
|
+
} catch {
|
|
49
|
+
// not found, try next
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Scan a single directory for workflow entries (flat YAML files + folder/index.yaml).
|
|
57
|
+
* Returns discovered entries. Returns empty array if directory does not exist.
|
|
58
|
+
*/
|
|
59
|
+
async function scanWorkflowDir(dir: string): Promise<ProjectWorkflowEntry[]> {
|
|
60
|
+
let dirents: Dirent[];
|
|
61
|
+
try {
|
|
62
|
+
dirents = await readdir(dir, { withFileTypes: true });
|
|
63
|
+
} catch (e) {
|
|
64
|
+
const err = e as NodeJS.ErrnoException;
|
|
65
|
+
if (err.code === "ENOENT" || err.code === "ENOTDIR") {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
throw e;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result: ProjectWorkflowEntry[] = [];
|
|
72
|
+
for (const entry of dirents) {
|
|
73
|
+
if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
|
|
74
|
+
result.push({ name: stemFromYaml(entry.name), filePath: join(dir, entry.name) });
|
|
75
|
+
} else if (entry.isDirectory()) {
|
|
76
|
+
const found = await findIndexWorkflow(dir, entry.name);
|
|
77
|
+
if (found !== null) {
|
|
78
|
+
result.push(found);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Scan `<projectRoot>/.workflow/` (preferred) and `.workflows/` (legacy) for workflow entries.
|
|
87
|
+
* .workflow/ takes priority: if a name is found in both, .workflow/ wins.
|
|
88
|
+
* Returns an empty array if neither directory exists.
|
|
89
|
+
*/
|
|
90
|
+
export async function discoverProjectWorkflows(
|
|
91
|
+
projectRoot: string,
|
|
92
|
+
): Promise<ProjectWorkflowEntry[]> {
|
|
93
|
+
const primary = await scanWorkflowDir(join(projectRoot, ".workflow"));
|
|
94
|
+
const legacy = await scanWorkflowDir(join(projectRoot, ".workflows"));
|
|
95
|
+
const seen = new Set(primary.map((e) => e.name));
|
|
96
|
+
const merged = [...primary];
|
|
97
|
+
for (const entry of legacy) {
|
|
98
|
+
if (!seen.has(entry.name)) {
|
|
99
|
+
merged.push(entry);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return merged;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Default filesystem root for uwf data (`~/.uwf`). */
|
|
106
|
+
export function getDefaultStorageRoot(): string {
|
|
107
|
+
return join(homedir(), ".uwf");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve storage root.
|
|
112
|
+
* Priority: `UWF_HOME` → default.
|
|
113
|
+
*/
|
|
114
|
+
export function resolveStorageRoot(): string {
|
|
115
|
+
const primary = process.env.UWF_HOME;
|
|
116
|
+
if (primary !== undefined && primary !== "") {
|
|
117
|
+
return primary;
|
|
118
|
+
}
|
|
119
|
+
return getDefaultStorageRoot();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Deprecated: Use `getGlobalCasDir()` instead.
|
|
124
|
+
* Returns the old CAS directory for backward compatibility.
|
|
125
|
+
*/
|
|
126
|
+
export function getCasDir(storageRoot: string): string {
|
|
127
|
+
return join(storageRoot, "cas");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns the global CAS directory shared by all uwf and ocas tools.
|
|
132
|
+
* Priority: `OCAS_HOME` → default ~/.ocas
|
|
133
|
+
*/
|
|
134
|
+
export function getGlobalCasDir(): string {
|
|
135
|
+
const primary = process.env.OCAS_HOME;
|
|
136
|
+
if (primary !== undefined && primary !== "") {
|
|
137
|
+
return primary;
|
|
138
|
+
}
|
|
139
|
+
return join(homedir(), ".ocas");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function getRegistryPath(storageRoot: string): string {
|
|
143
|
+
return join(storageRoot, "workflows.yaml");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getThreadsPath(storageRoot: string): string {
|
|
147
|
+
return join(storageRoot, "threads.yaml");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type UwfStore = {
|
|
151
|
+
storageRoot: string;
|
|
152
|
+
store: Store;
|
|
153
|
+
schemas: UwfSchemaHashes;
|
|
154
|
+
varStore: VarStore;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export async function createUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
158
|
+
const casDir = getGlobalCasDir();
|
|
159
|
+
await mkdir(casDir, { recursive: true });
|
|
160
|
+
const cas = createFsStore(casDir);
|
|
161
|
+
const { var: varStore, tag } = createSqliteVarStore(join(casDir, "vars"), cas);
|
|
162
|
+
const store: Store = { cas, var: varStore, tag };
|
|
163
|
+
bootstrap(store);
|
|
164
|
+
const schemas = await registerUwfSchemas(store);
|
|
165
|
+
await migrateWorkflowRegistryIfNeeded(storageRoot, varStore);
|
|
166
|
+
await migrateThreadsIndexIfNeeded(storageRoot, varStore);
|
|
167
|
+
await migrateHistoryIfNeeded(storageRoot, varStore);
|
|
168
|
+
migrateHistoryVarsToThreadVars(varStore);
|
|
169
|
+
return { storageRoot, store, schemas, varStore };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function loadWorkflowRegistryFromYaml(storageRoot: string): Promise<WorkflowRegistry> {
|
|
173
|
+
const path = getRegistryPath(storageRoot);
|
|
174
|
+
const text = await readFile(path, "utf8");
|
|
175
|
+
const raw = parse(text) as unknown;
|
|
176
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
const registry: WorkflowRegistry = {};
|
|
180
|
+
for (const [name, hash] of Object.entries(raw as Record<string, unknown>)) {
|
|
181
|
+
if (typeof hash === "string") {
|
|
182
|
+
registry[name] = hash;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return registry;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** One-time migration: `~/.uwf/workflows.yaml` → `@uwf/registry/*` variables. */
|
|
189
|
+
export async function migrateWorkflowRegistryIfNeeded(
|
|
190
|
+
storageRoot: string,
|
|
191
|
+
varStore: VarStore,
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
const path = getRegistryPath(storageRoot);
|
|
194
|
+
if (!existsSync(path)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const registry = await loadWorkflowRegistryFromYaml(storageRoot);
|
|
199
|
+
for (const [name, hash] of Object.entries(registry)) {
|
|
200
|
+
saveWorkflowRegistry(varStore, name, hash);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await rename(path, `${path}.migrated`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function loadWorkflowRegistry(varStore: VarStore): WorkflowRegistry {
|
|
207
|
+
const vars = varStore.list({ namePrefix: REGISTRY_VAR_PREFIX });
|
|
208
|
+
const registry: WorkflowRegistry = {};
|
|
209
|
+
for (const v of vars) {
|
|
210
|
+
const name = v.name.slice(REGISTRY_VAR_PREFIX.length);
|
|
211
|
+
registry[name] = v.value;
|
|
212
|
+
}
|
|
213
|
+
return registry;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function saveWorkflowRegistry(varStore: VarStore, name: string, hash: CasRef): void {
|
|
217
|
+
varStore.set(`${REGISTRY_VAR_PREFIX}${name}`, hash);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function resolveWorkflowHash(registry: WorkflowRegistry, id: string): CasRef {
|
|
221
|
+
return registry[id] !== undefined ? registry[id] : id;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Resolve a workflow name to a project-local YAML file path.
|
|
226
|
+
* Returns null if the name is not found in the local entries.
|
|
227
|
+
*/
|
|
228
|
+
export function resolveProjectWorkflowFile(
|
|
229
|
+
localEntries: ProjectWorkflowEntry[],
|
|
230
|
+
name: string,
|
|
231
|
+
): string | null {
|
|
232
|
+
for (const entry of localEntries) {
|
|
233
|
+
if (entry.name === name) {
|
|
234
|
+
return entry.filePath;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function findRegistryName(registry: WorkflowRegistry, hash: Hash): string | null {
|
|
241
|
+
for (const [name, h] of Object.entries(registry)) {
|
|
242
|
+
if (h === hash) {
|
|
243
|
+
return name;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function loadThreadsIndexFromYaml(storageRoot: string): Promise<ThreadsIndex> {
|
|
250
|
+
const path = getThreadsPath(storageRoot);
|
|
251
|
+
try {
|
|
252
|
+
const text = await readFile(path, "utf8");
|
|
253
|
+
const raw = parse(text) as unknown;
|
|
254
|
+
return parseThreadsIndex(raw);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
const err = e as NodeJS.ErrnoException;
|
|
257
|
+
if (err.code === "ENOENT") {
|
|
258
|
+
return {};
|
|
259
|
+
}
|
|
260
|
+
throw e;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** One-time migration: `~/.uwf/threads.yaml` → `@uwf/thread/*` variables. */
|
|
265
|
+
export async function migrateThreadsIndexIfNeeded(
|
|
266
|
+
storageRoot: string,
|
|
267
|
+
varStore: VarStore,
|
|
268
|
+
): Promise<void> {
|
|
269
|
+
const path = getThreadsPath(storageRoot);
|
|
270
|
+
if (!existsSync(path)) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const index = await loadThreadsIndexFromYaml(storageRoot);
|
|
275
|
+
for (const [threadId, entry] of Object.entries(index)) {
|
|
276
|
+
setThread(varStore, threadId as ThreadId, entry);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await rename(path, `${path}.migrated`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function threadVarName(threadId: ThreadId): string {
|
|
283
|
+
return `${THREAD_VAR_PREFIX}${threadId}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function entryFromVariable(v: { value: string; tags: Record<string, string> }): ThreadIndexEntry {
|
|
287
|
+
return {
|
|
288
|
+
head: v.value as CasRef,
|
|
289
|
+
status: (v.tags.status ?? "idle") as ThreadIndexEntry["status"],
|
|
290
|
+
suspendedRole: v.tags.suspendedRole ?? null,
|
|
291
|
+
suspendMessage: v.tags.suspendMessage ?? null,
|
|
292
|
+
completedAt: v.tags.completedAt !== undefined ? Number(v.tags.completedAt) : null,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Load all active threads (equivalent to legacy `loadThreadsIndex`). */
|
|
297
|
+
export function loadAllThreads(varStore: VarStore): ThreadsIndex {
|
|
298
|
+
const vars = varStore.list({ namePrefix: THREAD_VAR_PREFIX });
|
|
299
|
+
const index: ThreadsIndex = {};
|
|
300
|
+
for (const v of vars) {
|
|
301
|
+
const threadId = v.name.slice(THREAD_VAR_PREFIX.length) as ThreadId;
|
|
302
|
+
index[threadId] = entryFromVariable(v);
|
|
303
|
+
}
|
|
304
|
+
return index;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Get a single active thread entry, or null if not found. */
|
|
308
|
+
export function getThread(varStore: VarStore, threadId: ThreadId): ThreadIndexEntry | null {
|
|
309
|
+
const vars = varStore.list({ exactName: threadVarName(threadId) });
|
|
310
|
+
const v = vars[0];
|
|
311
|
+
if (v === undefined) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
return entryFromVariable(v);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Set or update a single active thread entry. */
|
|
318
|
+
export function setThread(varStore: VarStore, threadId: ThreadId, entry: ThreadIndexEntry): void {
|
|
319
|
+
const name = threadVarName(threadId);
|
|
320
|
+
// Head CAS nodes may use different schemas (StartNode vs StepNode) — clear all variants first.
|
|
321
|
+
varStore.remove(name);
|
|
322
|
+
const tags: Record<string, string> = {};
|
|
323
|
+
if (entry.status !== "idle") {
|
|
324
|
+
tags.status = entry.status;
|
|
325
|
+
}
|
|
326
|
+
if (entry.suspendedRole !== null) {
|
|
327
|
+
tags.suspendedRole = entry.suspendedRole;
|
|
328
|
+
}
|
|
329
|
+
if (entry.suspendMessage !== null) {
|
|
330
|
+
tags.suspendMessage = entry.suspendMessage;
|
|
331
|
+
}
|
|
332
|
+
if (entry.completedAt !== null) {
|
|
333
|
+
tags.completedAt = String(entry.completedAt);
|
|
334
|
+
}
|
|
335
|
+
varStore.set(name, entry.head, { tags });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Load only active threads (status not in completed/cancelled). */
|
|
339
|
+
export function loadActiveThreads(varStore: VarStore): ThreadsIndex {
|
|
340
|
+
const all = loadAllThreads(varStore);
|
|
341
|
+
const active: ThreadsIndex = {};
|
|
342
|
+
for (const [threadId, entry] of Object.entries(all)) {
|
|
343
|
+
if (entry.status !== "completed" && entry.status !== "cancelled") {
|
|
344
|
+
active[threadId as ThreadId] = entry;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return active;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Load only completed/cancelled threads (history). */
|
|
351
|
+
export function loadHistoryThreads(varStore: VarStore): ThreadsIndex {
|
|
352
|
+
const all = loadAllThreads(varStore);
|
|
353
|
+
const history: ThreadsIndex = {};
|
|
354
|
+
for (const [threadId, entry] of Object.entries(all)) {
|
|
355
|
+
if (entry.status === "completed" || entry.status === "cancelled") {
|
|
356
|
+
history[threadId as ThreadId] = entry;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return history;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Complete a thread by marking it completed or cancelled. */
|
|
363
|
+
export function completeThread(
|
|
364
|
+
varStore: VarStore,
|
|
365
|
+
threadId: ThreadId,
|
|
366
|
+
reason: "completed" | "cancelled",
|
|
367
|
+
): void {
|
|
368
|
+
const entry = getThread(varStore, threadId);
|
|
369
|
+
if (entry === null) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const completed = {
|
|
373
|
+
head: entry.head,
|
|
374
|
+
status: reason,
|
|
375
|
+
suspendedRole: null,
|
|
376
|
+
suspendMessage: null,
|
|
377
|
+
completedAt: Date.now(),
|
|
378
|
+
} as ThreadIndexEntry;
|
|
379
|
+
setThread(varStore, threadId, completed);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
type LegacyHistoryEntry = {
|
|
383
|
+
thread: ThreadId;
|
|
384
|
+
workflow: CasRef;
|
|
385
|
+
head: CasRef;
|
|
386
|
+
completedAt: number;
|
|
387
|
+
reason: "completed" | "cancelled" | null;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
function parseLegacyHistoryJsonlLine(trimmed: string): LegacyHistoryEntry | null {
|
|
391
|
+
let raw: unknown;
|
|
392
|
+
try {
|
|
393
|
+
raw = JSON.parse(trimmed) as unknown;
|
|
394
|
+
} catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
const rec = raw as Record<string, unknown>;
|
|
401
|
+
const thread = rec.thread;
|
|
402
|
+
const workflow = rec.workflow;
|
|
403
|
+
const head = rec.head;
|
|
404
|
+
const completedAt = rec.completedAt;
|
|
405
|
+
if (
|
|
406
|
+
typeof thread === "string" &&
|
|
407
|
+
typeof workflow === "string" &&
|
|
408
|
+
typeof head === "string" &&
|
|
409
|
+
typeof completedAt === "number"
|
|
410
|
+
) {
|
|
411
|
+
const reason = rec.reason;
|
|
412
|
+
const parsedReason = reason === "completed" || reason === "cancelled" ? reason : null;
|
|
413
|
+
return {
|
|
414
|
+
thread: thread as ThreadId,
|
|
415
|
+
workflow,
|
|
416
|
+
head,
|
|
417
|
+
completedAt,
|
|
418
|
+
reason: parsedReason,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** One-time migration: `~/.uwf/history.jsonl` → `@uwf/thread/*` variables with status tags. */
|
|
425
|
+
export async function migrateHistoryIfNeeded(
|
|
426
|
+
storageRoot: string,
|
|
427
|
+
varStore: VarStore,
|
|
428
|
+
): Promise<void> {
|
|
429
|
+
const path = join(storageRoot, "history.jsonl");
|
|
430
|
+
if (!existsSync(path)) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const text = await readFile(path, "utf8");
|
|
435
|
+
for (const line of text.split("\n")) {
|
|
436
|
+
const trimmed = line.trim();
|
|
437
|
+
if (trimmed === "") {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const entry = parseLegacyHistoryJsonlLine(trimmed);
|
|
441
|
+
if (entry !== null) {
|
|
442
|
+
const status = entry.reason === "cancelled" ? "cancelled" : "completed";
|
|
443
|
+
const threadEntry: ThreadIndexEntry = {
|
|
444
|
+
head: entry.head,
|
|
445
|
+
status: status as ThreadIndexEntry["status"],
|
|
446
|
+
suspendedRole: null,
|
|
447
|
+
suspendMessage: null,
|
|
448
|
+
completedAt: entry.completedAt,
|
|
449
|
+
};
|
|
450
|
+
setThread(varStore, entry.thread, threadEntry);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
await rename(path, `${path}.migrated`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** Migrate `@uwf/history/*` variables to `@uwf/thread/*` with status tags. */
|
|
458
|
+
export function migrateHistoryVarsToThreadVars(varStore: VarStore): void {
|
|
459
|
+
const LEGACY_HISTORY_VAR_PREFIX = "@uwf/history/";
|
|
460
|
+
const vars = varStore.list({ namePrefix: LEGACY_HISTORY_VAR_PREFIX });
|
|
461
|
+
|
|
462
|
+
for (const v of vars) {
|
|
463
|
+
const threadId = v.name.slice(LEGACY_HISTORY_VAR_PREFIX.length) as ThreadId;
|
|
464
|
+
const reason = v.tags.reason;
|
|
465
|
+
const status = reason === "cancelled" ? "cancelled" : "completed";
|
|
466
|
+
const completedAt = Number(v.tags.completedAt ?? Date.now());
|
|
467
|
+
|
|
468
|
+
const threadEntry: ThreadIndexEntry = {
|
|
469
|
+
head: v.value as CasRef,
|
|
470
|
+
status: status as ThreadIndexEntry["status"],
|
|
471
|
+
suspendedRole: null,
|
|
472
|
+
suspendMessage: null,
|
|
473
|
+
completedAt,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
setThread(varStore, threadId, threadEntry);
|
|
477
|
+
varStore.remove(v.name);
|
|
478
|
+
}
|
|
479
|
+
}
|