@united-workforce/cli 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -8
- package/dist/__tests__/adapter-json-roundtrip.test.js +1 -1
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
- package/dist/__tests__/agent-resolution-llm-free.test.d.ts +2 -0
- package/dist/__tests__/agent-resolution-llm-free.test.d.ts.map +1 -0
- package/dist/__tests__/agent-resolution-llm-free.test.js +30 -0
- package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -0
- package/dist/__tests__/build-step-entry.test.d.ts +2 -0
- package/dist/__tests__/build-step-entry.test.d.ts.map +1 -0
- package/dist/__tests__/build-step-entry.test.js +173 -0
- package/dist/__tests__/build-step-entry.test.js.map +1 -0
- package/dist/__tests__/clear-thread-failed-attempts.test.d.ts +2 -0
- package/dist/__tests__/clear-thread-failed-attempts.test.d.ts.map +1 -0
- package/dist/__tests__/clear-thread-failed-attempts.test.js +93 -0
- package/dist/__tests__/clear-thread-failed-attempts.test.js.map +1 -0
- package/dist/__tests__/config.test.js +26 -302
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/current-role.test.js +7 -6
- package/dist/__tests__/current-role.test.js.map +1 -1
- package/dist/__tests__/e2e-mock-agent.test.js +20 -23
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
- package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts +2 -0
- package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts.map +1 -0
- package/dist/__tests__/issue-180-workflow-ref-removed.test.js +40 -0
- package/dist/__tests__/issue-180-workflow-ref-removed.test.js.map +1 -0
- package/dist/__tests__/moderator-evaluate.test.js +9 -50
- package/dist/__tests__/moderator-evaluate.test.js.map +1 -1
- package/dist/__tests__/pid-recycling.test.d.ts +2 -0
- package/dist/__tests__/pid-recycling.test.d.ts.map +1 -0
- package/dist/__tests__/pid-recycling.test.js +271 -0
- package/dist/__tests__/pid-recycling.test.js.map +1 -0
- package/dist/__tests__/prompt.test.js +321 -0
- package/dist/__tests__/prompt.test.js.map +1 -1
- package/dist/__tests__/resolve-head-hash.test.js +4 -4
- package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
- package/dist/__tests__/setup-agent-discovery.test.js +21 -30
- package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
- package/dist/__tests__/setup-complexity.test.js +2 -168
- package/dist/__tests__/setup-complexity.test.js.map +1 -1
- package/dist/__tests__/setup-no-llm.test.d.ts +2 -0
- package/dist/__tests__/setup-no-llm.test.d.ts.map +1 -0
- package/dist/__tests__/setup-no-llm.test.js +52 -0
- package/dist/__tests__/setup-no-llm.test.js.map +1 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.js +24 -27
- package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
- package/dist/__tests__/step-ask.test.d.ts +2 -0
- package/dist/__tests__/step-ask.test.d.ts.map +1 -0
- package/dist/__tests__/step-ask.test.js +499 -0
- package/dist/__tests__/step-ask.test.js.map +1 -0
- package/dist/__tests__/step-show-json.test.js +1 -0
- package/dist/__tests__/step-show-json.test.js.map +1 -1
- package/dist/__tests__/step-timing.test.js +2 -0
- package/dist/__tests__/step-timing.test.js.map +1 -1
- package/dist/__tests__/store-global-cas.test.js +2 -2
- package/dist/__tests__/store-global-cas.test.js.map +1 -1
- package/dist/__tests__/store-unified-threads.test.js +9 -9
- package/dist/__tests__/store-unified-threads.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-status.test.js +6 -6
- package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
- package/dist/__tests__/thread-list-filters.test.js +344 -9
- package/dist/__tests__/thread-list-filters.test.js.map +1 -1
- package/dist/__tests__/thread-poke.test.d.ts +2 -0
- package/dist/__tests__/thread-poke.test.d.ts.map +1 -0
- package/dist/__tests__/thread-poke.test.js +412 -0
- package/dist/__tests__/thread-poke.test.js.map +1 -0
- package/dist/__tests__/thread-resume.test.js +10 -14
- package/dist/__tests__/thread-resume.test.js.map +1 -1
- package/dist/__tests__/thread-show-status.test.js +17 -28
- package/dist/__tests__/thread-show-status.test.js.map +1 -1
- package/dist/__tests__/thread-suspend-step.test.js +8 -14
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
- package/dist/__tests__/thread-suspended-display.test.js +10 -22
- package/dist/__tests__/thread-suspended-display.test.js.map +1 -1
- package/dist/__tests__/thread.test.js +4 -4
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/__tests__/validate-semantic.test.js +49 -21
- package/dist/__tests__/validate-semantic.test.js.map +1 -1
- package/dist/__tests__/workflow-list-recursive.test.d.ts +2 -0
- package/dist/__tests__/workflow-list-recursive.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-list-recursive.test.js +283 -0
- package/dist/__tests__/workflow-list-recursive.test.js.map +1 -0
- package/dist/__tests__/workflow-resolution.test.js +36 -21
- package/dist/__tests__/workflow-resolution.test.js.map +1 -1
- package/dist/__tests__/workflow-show-resolution.test.d.ts +2 -0
- package/dist/__tests__/workflow-show-resolution.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-show-resolution.test.js +210 -0
- package/dist/__tests__/workflow-show-resolution.test.js.map +1 -0
- package/dist/__tests__/workflow-validate.test.d.ts +2 -0
- package/dist/__tests__/workflow-validate.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-validate.test.js +687 -0
- package/dist/__tests__/workflow-validate.test.js.map +1 -0
- package/dist/background/background.d.ts +22 -1
- package/dist/background/background.d.ts.map +1 -1
- package/dist/background/background.js +83 -6
- package/dist/background/background.js.map +1 -1
- package/dist/background/index.d.ts +1 -1
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +1 -1
- package/dist/background/index.js.map +1 -1
- package/dist/background/types.d.ts +1 -0
- package/dist/background/types.d.ts.map +1 -1
- package/dist/cli.js +66 -31
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts +3 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +7 -33
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +15 -2
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +7 -39
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +27 -302
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/step.d.ts +44 -1
- package/dist/commands/step.d.ts.map +1 -1
- package/dist/commands/step.js +255 -11
- package/dist/commands/step.js.map +1 -1
- package/dist/commands/thread.d.ts +16 -3
- package/dist/commands/thread.d.ts.map +1 -1
- package/dist/commands/thread.js +379 -140
- package/dist/commands/thread.js.map +1 -1
- package/dist/commands/workflow.d.ts +9 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +130 -6
- package/dist/commands/workflow.js.map +1 -1
- package/dist/moderator/__tests__/evaluate.test.js +31 -17
- package/dist/moderator/__tests__/evaluate.test.js.map +1 -1
- package/dist/moderator/evaluate.d.ts.map +1 -1
- package/dist/moderator/evaluate.js +4 -16
- package/dist/moderator/evaluate.js.map +1 -1
- package/dist/moderator/index.d.ts +1 -2
- package/dist/moderator/index.d.ts.map +1 -1
- package/dist/moderator/index.js +0 -1
- package/dist/moderator/index.js.map +1 -1
- package/dist/moderator/types.d.ts +6 -10
- package/dist/moderator/types.d.ts.map +1 -1
- package/dist/moderator/types.js +1 -3
- package/dist/moderator/types.js.map +1 -1
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +5 -3
- package/dist/schemas.js.map +1 -1
- package/dist/store.d.ts +28 -9
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +75 -16
- package/dist/store.js.map +1 -1
- package/dist/validate-semantic.d.ts.map +1 -1
- package/dist/validate-semantic.js +83 -66
- package/dist/validate-semantic.js.map +1 -1
- package/dist/validate.d.ts +6 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +24 -0
- package/dist/validate.js.map +1 -1
- package/package.json +8 -10
- package/src/__tests__/adapter-json-roundtrip.test.ts +1 -1
- package/src/__tests__/agent-resolution-llm-free.test.ts +39 -0
- package/src/__tests__/build-step-entry.test.ts +203 -0
- package/src/__tests__/clear-thread-failed-attempts.test.ts +122 -0
- package/src/__tests__/config.test.ts +33 -321
- package/src/__tests__/current-role.test.ts +7 -6
- package/src/__tests__/e2e-mock-agent.test.ts +20 -23
- package/src/__tests__/fixtures/e2e-count.workflow.yaml +1 -0
- package/src/__tests__/fixtures/e2e-linear.workflow.yaml +1 -0
- package/src/__tests__/fixtures/{e2e-mustache.workflow.yaml → e2e-liquid.workflow.yaml} +3 -2
- package/src/__tests__/fixtures/e2e-loop.workflow.yaml +1 -0
- package/src/__tests__/fixtures/e2e-suspend.mock.yaml +2 -2
- package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +6 -10
- package/src/__tests__/issue-180-workflow-ref-removed.test.ts +43 -0
- package/src/__tests__/moderator-evaluate.test.ts +9 -52
- package/src/__tests__/pid-recycling.test.ts +328 -0
- package/src/__tests__/prompt.test.ts +397 -0
- package/src/__tests__/resolve-head-hash.test.ts +4 -4
- package/src/__tests__/setup-agent-discovery.test.ts +26 -51
- package/src/__tests__/setup-complexity.test.ts +1 -203
- package/src/__tests__/setup-no-llm.test.ts +68 -0
- package/src/__tests__/solve-issue-tea-worktree.test.ts +24 -30
- package/src/__tests__/step-ask.test.ts +670 -0
- package/src/__tests__/step-show-json.test.ts +1 -0
- package/src/__tests__/step-timing.test.ts +2 -0
- package/src/__tests__/store-global-cas.test.ts +2 -2
- package/src/__tests__/store-unified-threads.test.ts +9 -9
- package/src/__tests__/thread-cancel-status.test.ts +6 -6
- package/src/__tests__/thread-list-filters.test.ts +434 -8
- package/src/__tests__/thread-poke.test.ts +545 -0
- package/src/__tests__/thread-resume.test.ts +10 -14
- package/src/__tests__/thread-show-status.test.ts +17 -29
- package/src/__tests__/thread-suspend-step.test.ts +8 -14
- package/src/__tests__/thread-suspended-display.test.ts +10 -22
- package/src/__tests__/thread.test.ts +4 -4
- package/src/__tests__/validate-semantic.test.ts +59 -31
- package/src/__tests__/workflow-list-recursive.test.ts +370 -0
- package/src/__tests__/workflow-resolution.test.ts +39 -21
- package/src/__tests__/workflow-show-resolution.test.ts +285 -0
- package/src/__tests__/workflow-validate.test.ts +806 -0
- package/src/background/background.ts +88 -6
- package/src/background/index.ts +2 -0
- package/src/background/types.ts +1 -0
- package/src/cli.ts +97 -47
- package/src/commands/config.ts +7 -35
- package/src/commands/prompt.ts +15 -2
- package/src/commands/setup.ts +29 -357
- package/src/commands/step.ts +339 -12
- package/src/commands/thread.ts +463 -169
- package/src/commands/workflow.ts +159 -4
- package/src/moderator/__tests__/evaluate.test.ts +34 -17
- package/src/moderator/evaluate.ts +5 -17
- package/src/moderator/index.ts +1 -6
- package/src/moderator/types.ts +6 -14
- package/src/schemas.ts +13 -3
- package/src/store.ts +86 -20
- package/src/validate-semantic.ts +109 -78
- package/src/validate.ts +27 -0
- package/dist/__tests__/setup-validate.test.d.ts +0 -2
- package/dist/__tests__/setup-validate.test.d.ts.map +0 -1
- package/dist/__tests__/setup-validate.test.js +0 -108
- package/dist/__tests__/setup-validate.test.js.map +0 -1
- package/src/__tests__/setup-validate.test.ts +0 -148
- /package/src/__tests__/fixtures/{e2e-mustache.mock.yaml → e2e-liquid.mock.yaml} +0 -0
|
@@ -1,16 +1,7 @@
|
|
|
1
|
+
import { Liquid } from "liquidjs";
|
|
1
2
|
const RESERVED_NAMES = new Set(["$START", "$END", "$SUSPEND"]);
|
|
2
|
-
const PSEUDO_TARGETS = new Set(["$END"
|
|
3
|
-
|
|
4
|
-
function extractMustacheVars(prompt) {
|
|
5
|
-
const vars = [];
|
|
6
|
-
const re = /\{\{\{?([^}]+)\}\}\}?/g;
|
|
7
|
-
let m = re.exec(prompt);
|
|
8
|
-
while (m !== null) {
|
|
9
|
-
vars.push(m[1]);
|
|
10
|
-
m = re.exec(prompt);
|
|
11
|
-
}
|
|
12
|
-
return vars;
|
|
13
|
-
}
|
|
3
|
+
const PSEUDO_TARGETS = new Set(["$END"]);
|
|
4
|
+
const SUSPEND_TARGET = "$SUSPEND";
|
|
14
5
|
/** Check if a frontmatter schema is a oneOf (multi-exit) type. */
|
|
15
6
|
function isOneOfSchema(fm) {
|
|
16
7
|
if (typeof fm !== "object" || fm === null)
|
|
@@ -38,13 +29,6 @@ function getConstStatuses(fm) {
|
|
|
38
29
|
return [statusDef.const];
|
|
39
30
|
return [];
|
|
40
31
|
}
|
|
41
|
-
/** Get property names from a schema object. */
|
|
42
|
-
function getPropertyNames(schema) {
|
|
43
|
-
const props = schema.properties;
|
|
44
|
-
if (typeof props !== "object" || props === null)
|
|
45
|
-
return new Set();
|
|
46
|
-
return new Set(Object.keys(props));
|
|
47
|
-
}
|
|
48
32
|
/** Extract $status const values from oneOf variants. */
|
|
49
33
|
function getOneOfStatuses(variants) {
|
|
50
34
|
const statuses = [];
|
|
@@ -59,6 +43,66 @@ function getOneOfStatuses(variants) {
|
|
|
59
43
|
}
|
|
60
44
|
return statuses;
|
|
61
45
|
}
|
|
46
|
+
/** Generate mock data from schema property names for template rendering. */
|
|
47
|
+
function generateMockData(schema) {
|
|
48
|
+
const mock = {};
|
|
49
|
+
const props = schema.properties;
|
|
50
|
+
if (!props)
|
|
51
|
+
return mock;
|
|
52
|
+
for (const key of Object.keys(props)) {
|
|
53
|
+
mock[key] = `mock_${key}`;
|
|
54
|
+
}
|
|
55
|
+
return mock;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pre-process a template to replace `$`-prefixed variables (like `$status`)
|
|
59
|
+
* which are invalid in LiquidJS syntax but always valid at runtime.
|
|
60
|
+
* Replaces `{{ $varName }}` with a literal placeholder so the strict render
|
|
61
|
+
* does not reject them.
|
|
62
|
+
*/
|
|
63
|
+
function sanitizeReservedVars(template) {
|
|
64
|
+
return template.replace(/\{\{\s*\$\w+\s*\}\}/g, "RESERVED");
|
|
65
|
+
}
|
|
66
|
+
/** Extract variable name from a LiquidJS UndefinedVariableError message. */
|
|
67
|
+
function extractVarName(err) {
|
|
68
|
+
const msg = String(err);
|
|
69
|
+
const match = msg.match(/undefined variable: ([^,\s]+)/);
|
|
70
|
+
return match ? match[1] : "unknown";
|
|
71
|
+
}
|
|
72
|
+
/** Validate edge templates using LiquidJS strict-render for a multi-exit role. */
|
|
73
|
+
function validateMultiExitTemplates(roleName, graphEntry, variants, errors) {
|
|
74
|
+
const strictEngine = new Liquid({ strictVariables: true });
|
|
75
|
+
for (const [status, target] of Object.entries(graphEntry)) {
|
|
76
|
+
const variant = variants.find((v) => {
|
|
77
|
+
const props = v.properties;
|
|
78
|
+
return props?.$status?.const === status;
|
|
79
|
+
});
|
|
80
|
+
if (!variant)
|
|
81
|
+
continue;
|
|
82
|
+
const mockData = generateMockData(variant);
|
|
83
|
+
try {
|
|
84
|
+
strictEngine.parseAndRenderSync(sanitizeReservedVars(target.prompt), mockData);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const varName = extractVarName(err);
|
|
88
|
+
errors.push(`template variable "${varName}" not found in role "${roleName}" variant "${status}"`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Validate edge templates using LiquidJS strict-render for a flat schema. */
|
|
93
|
+
function validateFlatTemplates(roleName, graphEntry, fm, errors) {
|
|
94
|
+
const strictEngine = new Liquid({ strictVariables: true });
|
|
95
|
+
const mockData = generateMockData(fm);
|
|
96
|
+
for (const [status, target] of Object.entries(graphEntry)) {
|
|
97
|
+
try {
|
|
98
|
+
strictEngine.parseAndRenderSync(sanitizeReservedVars(target.prompt), mockData);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
const varName = extractVarName(err);
|
|
102
|
+
errors.push(`template variable "${varName}" in graph[${roleName}][${status}] not found in role "${roleName}" frontmatter`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
62
106
|
/** Check reserved names and role/graph reference integrity. */
|
|
63
107
|
function checkRoleReferences(payload, errors) {
|
|
64
108
|
const roleNames = new Set(Object.keys(payload.roles));
|
|
@@ -81,6 +125,20 @@ function checkRoleReferences(payload, errors) {
|
|
|
81
125
|
}
|
|
82
126
|
}
|
|
83
127
|
}
|
|
128
|
+
/** Validate each graph edge's target role, including the removed $SUSPEND target. */
|
|
129
|
+
function checkEdgeTargets(payload, roleNames, errors) {
|
|
130
|
+
for (const [node, statusMap] of Object.entries(payload.graph)) {
|
|
131
|
+
for (const [status, target] of Object.entries(statusMap)) {
|
|
132
|
+
if (target.role === SUSPEND_TARGET) {
|
|
133
|
+
errors.push(`edge ${node}→${status}: "${SUSPEND_TARGET}" is no longer a valid graph target. Emit $status: "${SUSPEND_TARGET}" from the "${node}" role output instead.`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!PSEUDO_TARGETS.has(target.role) && !roleNames.has(target.role)) {
|
|
137
|
+
errors.push(`edge ${node}→${status}: unknown target role "${target.role}"`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
84
142
|
/** Check $START/$END constraints, edge targets, and reachability. */
|
|
85
143
|
function checkGraphStructure(payload, errors) {
|
|
86
144
|
const roleNames = new Set(Object.keys(payload.roles));
|
|
@@ -97,16 +155,10 @@ function checkGraphStructure(payload, errors) {
|
|
|
97
155
|
if (graphNodes.has("$END")) {
|
|
98
156
|
errors.push("$END must not have outgoing edges");
|
|
99
157
|
}
|
|
100
|
-
if (graphNodes.has(
|
|
101
|
-
errors.push("$
|
|
102
|
-
}
|
|
103
|
-
for (const [node, statusMap] of Object.entries(payload.graph)) {
|
|
104
|
-
for (const [status, target] of Object.entries(statusMap)) {
|
|
105
|
-
if (!PSEUDO_TARGETS.has(target.role) && !roleNames.has(target.role)) {
|
|
106
|
-
errors.push(`edge ${node}→${status}: unknown target role "${target.role}"`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
158
|
+
if (graphNodes.has(SUSPEND_TARGET)) {
|
|
159
|
+
errors.push(`"${SUSPEND_TARGET}" is no longer a valid graph node — it is now an engine-level reserved $status. Emit $status: "${SUSPEND_TARGET}" from a role output instead.`);
|
|
109
160
|
}
|
|
161
|
+
checkEdgeTargets(payload, roleNames, errors);
|
|
110
162
|
checkReachability(roleNames, collectReachableRoles(payload.graph), errors);
|
|
111
163
|
}
|
|
112
164
|
/** BFS to collect all roles reachable from $START. */
|
|
@@ -179,27 +231,7 @@ function checkStatusEdges(roleName, graphKeys, statusSet, errors) {
|
|
|
179
231
|
errors.push(`role "${roleName}" graph is missing status keys: ${missingKeys.join(", ")}`);
|
|
180
232
|
}
|
|
181
233
|
}
|
|
182
|
-
/** Check
|
|
183
|
-
function checkMultiExitMustache(roleName, graphEntry, variants, errors) {
|
|
184
|
-
for (const [status, target] of Object.entries(graphEntry)) {
|
|
185
|
-
const vars = extractMustacheVars(target.prompt);
|
|
186
|
-
const variant = variants.find((v) => {
|
|
187
|
-
const props = v.properties;
|
|
188
|
-
return props?.$status?.const === status;
|
|
189
|
-
});
|
|
190
|
-
if (!variant)
|
|
191
|
-
continue;
|
|
192
|
-
const propNames = getPropertyNames(variant);
|
|
193
|
-
for (const v of vars) {
|
|
194
|
-
if (v === "$status")
|
|
195
|
-
continue;
|
|
196
|
-
if (!propNames.has(v)) {
|
|
197
|
-
errors.push(`prompt variable "${v}" not found in role "${roleName}" variant "${status}"`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
/** Check status-edge consistency and mustache for each role. */
|
|
234
|
+
/** Check status-edge consistency and template vars for each role. */
|
|
203
235
|
function checkRoleConsistency(payload, errors) {
|
|
204
236
|
for (const [roleName, role] of Object.entries(payload.roles)) {
|
|
205
237
|
if (RESERVED_NAMES.has(roleName))
|
|
@@ -214,33 +246,18 @@ function checkRoleConsistency(payload, errors) {
|
|
|
214
246
|
const statuses = getOneOfStatuses(variants);
|
|
215
247
|
checkOneOfDiscriminant(roleName, variants, statuses, errors);
|
|
216
248
|
checkStatusEdges(roleName, graphKeys, new Set(statuses), errors);
|
|
217
|
-
|
|
249
|
+
validateMultiExitTemplates(roleName, graphEntry, variants, errors);
|
|
218
250
|
}
|
|
219
251
|
else if (hasStatusConst(fm)) {
|
|
220
252
|
const statuses = getConstStatuses(fm);
|
|
221
253
|
checkStatusEdges(roleName, graphKeys, new Set(statuses), errors);
|
|
222
|
-
|
|
223
|
-
checkFlatMustache(roleName, graphEntry, fm, errors);
|
|
254
|
+
validateFlatTemplates(roleName, graphEntry, fm, errors);
|
|
224
255
|
}
|
|
225
256
|
else {
|
|
226
257
|
errors.push(`role "${roleName}" must define "$status" as const (or oneOf with const) in frontmatter`);
|
|
227
258
|
}
|
|
228
259
|
}
|
|
229
260
|
}
|
|
230
|
-
/** Check mustache vars in all edge prompts against flat schema properties. */
|
|
231
|
-
function checkFlatMustache(roleName, graphEntry, fm, errors) {
|
|
232
|
-
const propNames = getPropertyNames(fm);
|
|
233
|
-
for (const [status, target] of Object.entries(graphEntry)) {
|
|
234
|
-
const vars = extractMustacheVars(target.prompt);
|
|
235
|
-
for (const v of vars) {
|
|
236
|
-
if (v === "$status")
|
|
237
|
-
continue;
|
|
238
|
-
if (!propNames.has(v)) {
|
|
239
|
-
errors.push(`prompt variable "${v}" in graph[${roleName}][${status}] not found in role "${roleName}" frontmatter`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
261
|
/**
|
|
245
262
|
* Validate a parsed WorkflowPayload for semantic correctness.
|
|
246
263
|
* Returns an array of error messages. Empty array = valid.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-semantic.js","sourceRoot":"","sources":["../src/validate-semantic.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-semantic.js","sourceRoot":"","sources":["../src/validate-semantic.ts"],"names":[],"mappings":"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"}
|
package/dist/validate.d.ts
CHANGED
|
@@ -13,4 +13,10 @@ export declare function workflowNameFromPath(filePath: string): string;
|
|
|
13
13
|
export declare function checkWorkflowFilenameConsistency(filePath: string, payload: WorkflowPayload): string | null;
|
|
14
14
|
/** Validate YAML-parsed workflow document shape (outputSchema may be inline JSON Schema). */
|
|
15
15
|
export declare function parseWorkflowPayload(raw: unknown): WorkflowPayload | null;
|
|
16
|
+
/**
|
|
17
|
+
* Returns true when the parsed YAML document had no top-level `version` field.
|
|
18
|
+
* Used by `uwf workflow add` to emit a deprecation warning while still
|
|
19
|
+
* accepting legacy workflow YAML.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isMissingVersion(raw: unknown): boolean;
|
|
16
22
|
//# sourceMappingURL=validate.d.ts.map
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAK1E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,MAAM,CAEvD;AAkED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW7D;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,GACvB,MAAM,GAAG,IAAI,CAMf;AAED,6FAA6F;AAE7F,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CA0CzE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAKtD"}
|
package/dist/validate.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { basename, dirname } from "node:path";
|
|
2
|
+
import { CURRENT_WORKFLOW_VERSION } from "@united-workforce/protocol";
|
|
2
3
|
const CAS_REF_PATTERN = /^[0-9A-HJKMNP-TV-Z]{13}$/;
|
|
3
4
|
export function isCasRef(value) {
|
|
4
5
|
return CAS_REF_PATTERN.test(value);
|
|
@@ -92,11 +93,23 @@ export function parseWorkflowPayload(raw) {
|
|
|
92
93
|
if (typeof raw.name !== "string" || typeof raw.description !== "string") {
|
|
93
94
|
return null;
|
|
94
95
|
}
|
|
96
|
+
// version is optional in legacy YAML — falls back to CURRENT_WORKFLOW_VERSION.
|
|
97
|
+
// When present, it MUST be an integer (booleans, strings, floats are rejected).
|
|
98
|
+
if (raw.version !== undefined) {
|
|
99
|
+
if (typeof raw.version !== "number" ||
|
|
100
|
+
!Number.isInteger(raw.version) ||
|
|
101
|
+
typeof raw.version === "boolean") {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
95
105
|
if (!isStringRecord(raw.roles, isRoleDefinition) || !isGraph(raw.graph)) {
|
|
96
106
|
return null;
|
|
97
107
|
}
|
|
98
108
|
// Normalize location field: undefined → null
|
|
99
109
|
const normalized = { ...raw };
|
|
110
|
+
if (normalized.version === undefined || normalized.version === null) {
|
|
111
|
+
normalized.version = CURRENT_WORKFLOW_VERSION;
|
|
112
|
+
}
|
|
100
113
|
for (const roleName of Object.keys(normalized.graph)) {
|
|
101
114
|
const statusMap = normalized.graph[roleName];
|
|
102
115
|
if (statusMap !== undefined) {
|
|
@@ -112,4 +125,15 @@ export function parseWorkflowPayload(raw) {
|
|
|
112
125
|
}
|
|
113
126
|
return normalized;
|
|
114
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Returns true when the parsed YAML document had no top-level `version` field.
|
|
130
|
+
* Used by `uwf workflow add` to emit a deprecation warning while still
|
|
131
|
+
* accepting legacy workflow YAML.
|
|
132
|
+
*/
|
|
133
|
+
export function isMissingVersion(raw) {
|
|
134
|
+
if (!isRecord(raw)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return raw.version === undefined;
|
|
138
|
+
}
|
|
115
139
|
//# sourceMappingURL=validate.js.map
|
package/dist/validate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,MAAM,eAAe,GAAG,0BAA0B,CAAC;AAEnD,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,MAAM,aAAa,GACjB,QAAQ,CAAC,WAAW,CAAC;QACrB,CAAC,OAAO,WAAW,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;IACxC,MAAM,cAAc,GAClB,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IAClF,OAAO,CACL,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ;QACrC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAC9B,cAAc;QACd,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ;QACnC,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;QAChC,aAAa,CACd,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,gBAAgB,GACpB,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC;IAChG,OAAO,CACL,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAC9B,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;QAChC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAC1B,gBAAgB,CACjB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAc,EAAE,SAAqC;IAC3E,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,EAAE,EAAE;QAC9C,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE;YAC1D,6EAA6E;YAC7E,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;gBACnB,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QACjC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACnB,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YACrB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACnB,CAAC,CAAC,IAAI,CAAC;IACX,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,OAAO,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,CAC9C,QAAgB,EAChB,OAAwB;IAExB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,iCAAiC,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,QAAQ,6BAA6B,OAAO,CAAC,IAAI,iCAAiC,OAAO,CAAC,IAAI,gDAAgD,QAAQ,GAAG,CAAC;IACzO,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6FAA6F;AAC7F,0GAA0G;AAC1G,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,+EAA+E;IAC/E,gFAAgF;IAChF,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC9B,IACE,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;YAC/B,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;YAC9B,OAAO,GAAG,CAAC,OAAO,KAAK,SAAS,EAChC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6CAA6C;IAC7C,MAAM,UAAU,GAAG,EAAE,GAAG,GAAG,EAAqB,CAAC;IACjD,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,IAAI,UAAU,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACpE,UAAU,CAAC,OAAO,GAAG,wBAAwB,CAAC;IAChD,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACrD,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC5C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;gBACjC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACzB,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;wBAClC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC;oBACzB,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC;AACnC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@united-workforce/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"src",
|
|
6
6
|
"dist",
|
|
@@ -11,22 +11,20 @@
|
|
|
11
11
|
"uwf": "./dist/cli.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@ocas/core": "^0.
|
|
15
|
-
"@ocas/fs": "^0.
|
|
14
|
+
"@ocas/core": "^0.5.0",
|
|
15
|
+
"@ocas/fs": "^0.4.1",
|
|
16
16
|
"commander": "^14.0.3",
|
|
17
17
|
"dotenv": "^16.6.1",
|
|
18
|
-
"
|
|
18
|
+
"liquidjs": "^10.27.0",
|
|
19
19
|
"yaml": "^2.8.4",
|
|
20
|
-
"@united-workforce/protocol": "^0.
|
|
21
|
-
"@united-workforce/util": "^0.1.
|
|
22
|
-
"@united-workforce/util-agent": "^0.
|
|
20
|
+
"@united-workforce/protocol": "^0.2.0",
|
|
21
|
+
"@united-workforce/util": "^0.1.5",
|
|
22
|
+
"@united-workforce/util-agent": "^0.2.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@types/mustache": "^4.2.6"
|
|
29
|
-
},
|
|
27
|
+
"devDependencies": {},
|
|
30
28
|
"repository": {
|
|
31
29
|
"type": "git",
|
|
32
30
|
"url": "https://git.shazhou.work/shazhou/united-workforce.git",
|
|
@@ -119,7 +119,7 @@ describe("C1: adapter JSON round-trip integration", () => {
|
|
|
119
119
|
const configPath = join(tmpDir, "config.yaml");
|
|
120
120
|
await writeFile(
|
|
121
121
|
configPath,
|
|
122
|
-
`defaultAgent: uwf-hermes\
|
|
122
|
+
`defaultAgent: uwf-hermes\nagentOverrides: null\nagents:\n uwf-hermes:\n command: uwf-hermes\n`,
|
|
123
123
|
);
|
|
124
124
|
|
|
125
125
|
// 5. Run CLI with agent override pointing to our mock
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { loadWorkflowConfig } from "@united-workforce/util-agent";
|
|
5
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
6
|
+
|
|
7
|
+
describe("agent resolution works without LLM fields in config.yaml (issue #143)", () => {
|
|
8
|
+
let tempDir: string;
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("loadWorkflowConfig succeeds on a minimal engine-only config", async () => {
|
|
14
|
+
tempDir = mkdtempSync(join(tmpdir(), "uwf-engine-cfg-"));
|
|
15
|
+
writeFileSync(
|
|
16
|
+
join(tempDir, "config.yaml"),
|
|
17
|
+
"agents:\n hermes: { command: uwf-hermes, args: [] }\ndefaultAgent: hermes\n",
|
|
18
|
+
"utf8",
|
|
19
|
+
);
|
|
20
|
+
const cfg = await loadWorkflowConfig(tempDir);
|
|
21
|
+
expect(cfg.defaultAgent).toBe("hermes");
|
|
22
|
+
expect(cfg.agents.hermes).toBeDefined();
|
|
23
|
+
expect(cfg.agentOverrides).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("loadWorkflowConfig ignores legacy provider/model fields", async () => {
|
|
27
|
+
tempDir = mkdtempSync(join(tmpdir(), "uwf-engine-cfg-"));
|
|
28
|
+
writeFileSync(
|
|
29
|
+
join(tempDir, "config.yaml"),
|
|
30
|
+
"providers:\n openai: { baseUrl: x, apiKey: y }\nmodels:\n default: { provider: openai, name: gpt-4o }\ndefaultModel: default\nagents:\n hermes: { command: uwf-hermes, args: [] }\ndefaultAgent: hermes\n",
|
|
31
|
+
"utf8",
|
|
32
|
+
);
|
|
33
|
+
const cfg = (await loadWorkflowConfig(tempDir)) as Record<string, unknown>;
|
|
34
|
+
expect(cfg.defaultAgent).toBe("hermes");
|
|
35
|
+
expect(cfg.providers).toBeUndefined();
|
|
36
|
+
expect(cfg.models).toBeUndefined();
|
|
37
|
+
expect(cfg.defaultModel).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { CasRef, StepEntry, Usage } from "@united-workforce/protocol";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
6
|
+
import { buildStepEntry, sumStepEntryUsage } from "../commands/step.js";
|
|
7
|
+
import { createUwfStore, type UwfStore } from "../store.js";
|
|
8
|
+
|
|
9
|
+
let tmpDir: string;
|
|
10
|
+
let originalEnv: string | undefined;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
tmpDir = await mkdtemp(join(tmpdir(), "cli-build-step-entry-"));
|
|
14
|
+
originalEnv = process.env.OCAS_HOME;
|
|
15
|
+
process.env.OCAS_HOME = join(tmpDir, "cas");
|
|
16
|
+
await mkdir(process.env.OCAS_HOME, { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
21
|
+
if (originalEnv === undefined) {
|
|
22
|
+
delete process.env.OCAS_HOME;
|
|
23
|
+
} else {
|
|
24
|
+
process.env.OCAS_HOME = originalEnv;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
type PutStepOptions = {
|
|
29
|
+
startedAtMs: number;
|
|
30
|
+
completedAtMs: number;
|
|
31
|
+
usage: Usage | null;
|
|
32
|
+
previousAttempts: CasRef[] | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function setupStore(): Promise<{ uwf: UwfStore; startHash: CasRef }> {
|
|
36
|
+
const uwf = await createUwfStore(tmpDir);
|
|
37
|
+
const workflowHash = (await uwf.store.cas.put(uwf.schemas.workflow, {
|
|
38
|
+
name: "test-wf",
|
|
39
|
+
description: "desc",
|
|
40
|
+
roles: {},
|
|
41
|
+
graph: {},
|
|
42
|
+
})) as CasRef;
|
|
43
|
+
const startHash = (await uwf.store.cas.put(uwf.schemas.startNode, {
|
|
44
|
+
workflow: workflowHash,
|
|
45
|
+
prompt: "task",
|
|
46
|
+
cwd: "/tmp",
|
|
47
|
+
})) as CasRef;
|
|
48
|
+
return { uwf, startHash };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function putStep(uwf: UwfStore, startHash: CasRef, options: PutStepOptions): Promise<CasRef> {
|
|
52
|
+
const outputHash = (await uwf.store.cas.put(uwf.schemas.text, "output text")) as CasRef;
|
|
53
|
+
const detailHash = (await uwf.store.cas.put(uwf.schemas.text, "detail text")) as CasRef;
|
|
54
|
+
return (await uwf.store.cas.put(uwf.schemas.stepNode, {
|
|
55
|
+
start: startHash,
|
|
56
|
+
prev: null,
|
|
57
|
+
role: "planner",
|
|
58
|
+
output: outputHash,
|
|
59
|
+
detail: detailHash,
|
|
60
|
+
agent: "uwf-mock",
|
|
61
|
+
edgePrompt: "",
|
|
62
|
+
startedAtMs: options.startedAtMs,
|
|
63
|
+
completedAtMs: options.completedAtMs,
|
|
64
|
+
cwd: "/tmp",
|
|
65
|
+
assembledPrompt: null,
|
|
66
|
+
usage: options.usage,
|
|
67
|
+
previousAttempts: options.previousAttempts,
|
|
68
|
+
})) as CasRef;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const usage = (
|
|
72
|
+
turns: number,
|
|
73
|
+
inputTokens: number,
|
|
74
|
+
outputTokens: number,
|
|
75
|
+
duration: number,
|
|
76
|
+
): Usage => ({
|
|
77
|
+
turns,
|
|
78
|
+
inputTokens,
|
|
79
|
+
outputTokens,
|
|
80
|
+
duration,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("buildStepEntry", () => {
|
|
84
|
+
test("returns null for a non-StepNode hash", async () => {
|
|
85
|
+
const { uwf } = await setupStore();
|
|
86
|
+
const textHash = (await uwf.store.cas.put(uwf.schemas.text, "not a step")) as CasRef;
|
|
87
|
+
expect(buildStepEntry(uwf, textHash)).toBeNull();
|
|
88
|
+
expect(buildStepEntry(uwf, "MISSING000000" as CasRef)).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("builds an entry with no previousAttempts and computes durationMs", async () => {
|
|
92
|
+
const { uwf, startHash } = await setupStore();
|
|
93
|
+
const stepHash = await putStep(uwf, startHash, {
|
|
94
|
+
startedAtMs: 1_000,
|
|
95
|
+
completedAtMs: 4_500,
|
|
96
|
+
usage: usage(2, 100, 50, 3.5),
|
|
97
|
+
previousAttempts: null,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const entry = buildStepEntry(uwf, stepHash);
|
|
101
|
+
expect(entry).not.toBeNull();
|
|
102
|
+
expect(entry?.hash).toBe(stepHash);
|
|
103
|
+
expect(entry?.role).toBe("planner");
|
|
104
|
+
expect(entry?.durationMs).toBe(3_500);
|
|
105
|
+
expect(entry?.usage).toEqual(usage(2, 100, 50, 3.5));
|
|
106
|
+
expect(entry?.previousAttempts).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("recursively builds nested previousAttempts", async () => {
|
|
110
|
+
const { uwf, startHash } = await setupStore();
|
|
111
|
+
const first = await putStep(uwf, startHash, {
|
|
112
|
+
startedAtMs: 0,
|
|
113
|
+
completedAtMs: 100,
|
|
114
|
+
usage: usage(1, 10, 5, 0.1),
|
|
115
|
+
previousAttempts: null,
|
|
116
|
+
});
|
|
117
|
+
const second = await putStep(uwf, startHash, {
|
|
118
|
+
startedAtMs: 100,
|
|
119
|
+
completedAtMs: 300,
|
|
120
|
+
usage: usage(1, 20, 10, 0.2),
|
|
121
|
+
previousAttempts: [first],
|
|
122
|
+
});
|
|
123
|
+
const success = await putStep(uwf, startHash, {
|
|
124
|
+
startedAtMs: 300,
|
|
125
|
+
completedAtMs: 600,
|
|
126
|
+
usage: usage(3, 30, 15, 0.3),
|
|
127
|
+
previousAttempts: [second],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const entry = buildStepEntry(uwf, success);
|
|
131
|
+
expect(entry?.previousAttempts).toHaveLength(1);
|
|
132
|
+
const nested = entry?.previousAttempts?.[0];
|
|
133
|
+
expect(nested?.hash).toBe(second);
|
|
134
|
+
expect(nested?.previousAttempts).toHaveLength(1);
|
|
135
|
+
expect(nested?.previousAttempts?.[0]?.hash).toBe(first);
|
|
136
|
+
expect(nested?.previousAttempts?.[0]?.previousAttempts).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("skips previousAttempts refs that do not resolve to a StepNode", async () => {
|
|
140
|
+
const { uwf, startHash } = await setupStore();
|
|
141
|
+
const success = await putStep(uwf, startHash, {
|
|
142
|
+
startedAtMs: 0,
|
|
143
|
+
completedAtMs: 100,
|
|
144
|
+
usage: null,
|
|
145
|
+
previousAttempts: ["DEADBEEF00000" as CasRef],
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const entry = buildStepEntry(uwf, success);
|
|
149
|
+
expect(entry).not.toBeNull();
|
|
150
|
+
// The unresolvable ref is skipped, leaving no valid nested attempts.
|
|
151
|
+
expect(entry?.previousAttempts).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("sumStepEntryUsage", () => {
|
|
156
|
+
function entryWith(u: Usage | null, previousAttempts: StepEntry[] | null): StepEntry {
|
|
157
|
+
return {
|
|
158
|
+
hash: "STEP000000000" as CasRef,
|
|
159
|
+
role: "planner",
|
|
160
|
+
output: {},
|
|
161
|
+
detail: "DETAIL0000000" as CasRef,
|
|
162
|
+
agent: "uwf-mock",
|
|
163
|
+
timestamp: 0,
|
|
164
|
+
durationMs: 0,
|
|
165
|
+
usage: u,
|
|
166
|
+
previousAttempts,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
test("returns zeros when usage is null and there are no attempts", () => {
|
|
171
|
+
expect(sumStepEntryUsage(entryWith(null, null))).toEqual({
|
|
172
|
+
turns: 0,
|
|
173
|
+
inputTokens: 0,
|
|
174
|
+
outputTokens: 0,
|
|
175
|
+
duration: 0,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("aggregates usage across nested previousAttempts", () => {
|
|
180
|
+
const inner = entryWith(usage(1, 10, 5, 0.1), null);
|
|
181
|
+
const middle = entryWith(usage(2, 20, 10, 0.2), [inner]);
|
|
182
|
+
const root = entryWith(usage(3, 30, 15, 0.3), [middle]);
|
|
183
|
+
|
|
184
|
+
expect(sumStepEntryUsage(root)).toEqual({
|
|
185
|
+
turns: 6,
|
|
186
|
+
inputTokens: 60,
|
|
187
|
+
outputTokens: 30,
|
|
188
|
+
duration: expect.closeTo(0.6, 5),
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("treats null usage in nested attempts as zero", () => {
|
|
193
|
+
const inner = entryWith(null, null);
|
|
194
|
+
const root = entryWith(usage(2, 20, 10, 0.5), [inner]);
|
|
195
|
+
|
|
196
|
+
expect(sumStepEntryUsage(root)).toEqual({
|
|
197
|
+
turns: 2,
|
|
198
|
+
inputTokens: 20,
|
|
199
|
+
outputTokens: 10,
|
|
200
|
+
duration: 0.5,
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|