@united-workforce/cli 0.2.1-rc.9 → 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
package/src/commands/workflow.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, resolve as resolvePath } from "node:path";
|
|
2
|
+
import { basename, dirname, isAbsolute, resolve as resolvePath } from "node:path";
|
|
3
3
|
|
|
4
4
|
import type { JSONSchema } from "@ocas/core";
|
|
5
5
|
import { putSchema, validate } from "@ocas/core";
|
|
@@ -12,11 +12,17 @@ import {
|
|
|
12
12
|
discoverProjectWorkflows,
|
|
13
13
|
findRegistryName,
|
|
14
14
|
loadWorkflowRegistry,
|
|
15
|
+
resolveProjectWorkflowFile,
|
|
15
16
|
resolveWorkflowHash,
|
|
16
17
|
saveWorkflowRegistry,
|
|
17
18
|
type UwfStore,
|
|
18
19
|
} from "../store.js";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
checkWorkflowFilenameConsistency,
|
|
22
|
+
isCasRef,
|
|
23
|
+
isMissingVersion,
|
|
24
|
+
parseWorkflowPayload,
|
|
25
|
+
} from "../validate.js";
|
|
20
26
|
import { validateWorkflow } from "../validate-semantic.js";
|
|
21
27
|
|
|
22
28
|
export type WorkflowOrigin = "local" | "global";
|
|
@@ -105,6 +111,7 @@ export async function materializeWorkflowPayload(
|
|
|
105
111
|
};
|
|
106
112
|
}
|
|
107
113
|
return {
|
|
114
|
+
version: raw.version,
|
|
108
115
|
name: raw.name,
|
|
109
116
|
description: raw.description,
|
|
110
117
|
roles,
|
|
@@ -112,6 +119,47 @@ export async function materializeWorkflowPayload(
|
|
|
112
119
|
};
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Validate a workflow YAML file without registering it.
|
|
124
|
+
*
|
|
125
|
+
* CI-friendly: does not touch CAS or the workflow registry. On success,
|
|
126
|
+
* returns silently (no stdout/stderr) and exits 0. On any error, writes a
|
|
127
|
+
* single message to stderr and exits 1.
|
|
128
|
+
*/
|
|
129
|
+
export async function cmdWorkflowValidate(filePath: string): Promise<void> {
|
|
130
|
+
let text: string;
|
|
131
|
+
try {
|
|
132
|
+
text = await readFile(filePath, "utf8");
|
|
133
|
+
} catch {
|
|
134
|
+
fail(`file not found: ${filePath}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let raw: unknown;
|
|
138
|
+
try {
|
|
139
|
+
raw = parse(text, {
|
|
140
|
+
customTags: [createIncludeTag(dirname(resolvePath(filePath)))],
|
|
141
|
+
}) as unknown;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
fail(`invalid YAML: ${e instanceof Error ? e.message : String(e)}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const payload = parseWorkflowPayload(raw);
|
|
147
|
+
if (payload === null) {
|
|
148
|
+
fail("invalid workflow YAML: expected WorkflowPayload shape");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const filenameError = checkWorkflowFilenameConsistency(filePath, payload);
|
|
152
|
+
if (filenameError !== null) {
|
|
153
|
+
fail(filenameError);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const semanticErrors = validateWorkflow(payload);
|
|
157
|
+
if (semanticErrors.length > 0) {
|
|
158
|
+
fail(`workflow validation failed:\n${semanticErrors.map((e) => ` - ${e}`).join("\n")}`);
|
|
159
|
+
}
|
|
160
|
+
// success: silent return
|
|
161
|
+
}
|
|
162
|
+
|
|
115
163
|
export async function cmdWorkflowAdd(
|
|
116
164
|
storageRoot: string,
|
|
117
165
|
filePath: string,
|
|
@@ -137,6 +185,12 @@ export async function cmdWorkflowAdd(
|
|
|
137
185
|
fail("invalid workflow YAML: expected WorkflowPayload shape");
|
|
138
186
|
}
|
|
139
187
|
|
|
188
|
+
if (isMissingVersion(raw)) {
|
|
189
|
+
process.stderr.write(
|
|
190
|
+
`warning: workflow YAML "${basename(filePath)}" is missing top-level \`version\` field; falling back to version 1. Add \`version: 1\` to silence this warning.\n`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
140
194
|
const filenameError = checkWorkflowFilenameConsistency(filePath, payload);
|
|
141
195
|
if (filenameError !== null) {
|
|
142
196
|
fail(filenameError);
|
|
@@ -161,13 +215,113 @@ export async function cmdWorkflowAdd(
|
|
|
161
215
|
return { name: materialized.name, hash };
|
|
162
216
|
}
|
|
163
217
|
|
|
218
|
+
// ── workflow show resolution helpers ──────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
function isFilePath(input: string): boolean {
|
|
221
|
+
return (
|
|
222
|
+
input.includes("/") || input.includes("\\") || input.endsWith(".yaml") || input.endsWith(".yml")
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function materializeLocalWorkflowForShow(uwf: UwfStore, filePath: string): Promise<CasRef> {
|
|
227
|
+
let text: string;
|
|
228
|
+
try {
|
|
229
|
+
text = await readFile(filePath, "utf8");
|
|
230
|
+
} catch {
|
|
231
|
+
fail(`project workflow file not found: ${filePath}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let raw: unknown;
|
|
235
|
+
try {
|
|
236
|
+
raw = parse(text, { customTags: [createIncludeTag(dirname(filePath))] }) as unknown;
|
|
237
|
+
} catch (e) {
|
|
238
|
+
fail(`invalid YAML in ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const payload = parseWorkflowPayload(raw);
|
|
242
|
+
if (payload === null) {
|
|
243
|
+
fail(`invalid workflow YAML in ${filePath}: expected WorkflowPayload shape`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const filenameError = checkWorkflowFilenameConsistency(filePath, payload);
|
|
247
|
+
if (filenameError !== null) {
|
|
248
|
+
fail(filenameError);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const semanticErrors = validateWorkflow(payload);
|
|
252
|
+
if (semanticErrors.length > 0) {
|
|
253
|
+
fail(`workflow validation failed:\n${semanticErrors.map((e) => ` - ${e}`).join("\n")}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const materialized = await materializeWorkflowPayload(uwf, payload);
|
|
257
|
+
const hash = await uwf.store.cas.put(uwf.schemas.workflow, materialized);
|
|
258
|
+
const stored = uwf.store.cas.get(hash);
|
|
259
|
+
if (stored === null || !validate(uwf.store, stored)) {
|
|
260
|
+
fail("stored local workflow failed schema validation");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return hash;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function resolveWorkflowCasRefForShow(
|
|
267
|
+
uwf: UwfStore,
|
|
268
|
+
workflowId: string,
|
|
269
|
+
projectRoot: string,
|
|
270
|
+
): Promise<CasRef> {
|
|
271
|
+
// Validate input
|
|
272
|
+
const trimmed = workflowId.trim();
|
|
273
|
+
if (trimmed === "") {
|
|
274
|
+
fail("workflow ID cannot be empty");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Strategy 1: Direct CAS hash
|
|
278
|
+
if (isCasRef(trimmed)) {
|
|
279
|
+
const node = uwf.store.cas.get(trimmed);
|
|
280
|
+
if (node === null) {
|
|
281
|
+
fail(`CAS node not found: ${trimmed}`);
|
|
282
|
+
}
|
|
283
|
+
if (node.type !== uwf.schemas.workflow) {
|
|
284
|
+
fail(`node ${trimmed} is not a Workflow (type ${node.type})`);
|
|
285
|
+
}
|
|
286
|
+
return trimmed;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Strategy 2: Explicit file path (relative or absolute)
|
|
290
|
+
if (isFilePath(trimmed)) {
|
|
291
|
+
const absolutePath = isAbsolute(trimmed) ? trimmed : resolvePath(projectRoot, trimmed);
|
|
292
|
+
return materializeLocalWorkflowForShow(uwf, absolutePath);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Strategy 3: Local discovery (reuses discoverProjectWorkflows from store.ts)
|
|
296
|
+
const localEntries = await discoverProjectWorkflows(projectRoot);
|
|
297
|
+
const localPath = resolveProjectWorkflowFile(localEntries, trimmed);
|
|
298
|
+
if (localPath !== null) {
|
|
299
|
+
return materializeLocalWorkflowForShow(uwf, localPath);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Strategy 4: Global registry fallback
|
|
303
|
+
const registry = loadWorkflowRegistry(uwf.varStore);
|
|
304
|
+
const hash = resolveWorkflowHash(registry, trimmed);
|
|
305
|
+
if (!isCasRef(hash)) {
|
|
306
|
+
fail(`workflow not found: ${trimmed}`);
|
|
307
|
+
}
|
|
308
|
+
const node = uwf.store.cas.get(hash);
|
|
309
|
+
if (node === null) {
|
|
310
|
+
fail(`CAS node not found: ${hash}`);
|
|
311
|
+
}
|
|
312
|
+
if (node.type !== uwf.schemas.workflow) {
|
|
313
|
+
fail(`node ${hash} is not a Workflow (type ${node.type})`);
|
|
314
|
+
}
|
|
315
|
+
return hash;
|
|
316
|
+
}
|
|
317
|
+
|
|
164
318
|
export async function cmdWorkflowShow(
|
|
165
319
|
storageRoot: string,
|
|
166
320
|
id: string,
|
|
321
|
+
projectRoot: string,
|
|
167
322
|
): Promise<WorkflowShowOutput> {
|
|
168
323
|
const uwf = await createUwfStore(storageRoot);
|
|
169
|
-
const
|
|
170
|
-
const hash = resolveWorkflowHash(registry, id);
|
|
324
|
+
const hash = await resolveWorkflowCasRefForShow(uwf, id, projectRoot);
|
|
171
325
|
|
|
172
326
|
const node = uwf.store.cas.get(hash);
|
|
173
327
|
if (node === null) {
|
|
@@ -178,6 +332,7 @@ export async function cmdWorkflowShow(
|
|
|
178
332
|
}
|
|
179
333
|
|
|
180
334
|
const payload = node.payload as WorkflowPayload;
|
|
335
|
+
const registry = loadWorkflowRegistry(uwf.varStore);
|
|
181
336
|
return {
|
|
182
337
|
hash,
|
|
183
338
|
name: findRegistryName(registry, hash),
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
2
|
import { evaluate } from "../evaluate.js";
|
|
3
|
-
import { isSuspendResult } from "../types.js";
|
|
4
3
|
|
|
5
4
|
describe("Edge prompt template variable resolution", () => {
|
|
6
5
|
test("returns error when rendered prompt is empty string", () => {
|
|
7
6
|
const graph = {
|
|
8
7
|
$START: {
|
|
9
|
-
new: { role: "classifier", prompt: "{{
|
|
8
|
+
new: { role: "classifier", prompt: "{{ userPrompt }}", location: null },
|
|
10
9
|
},
|
|
11
10
|
};
|
|
12
11
|
|
|
@@ -22,7 +21,7 @@ describe("Edge prompt template variable resolution", () => {
|
|
|
22
21
|
test("returns error when rendered prompt is whitespace-only", () => {
|
|
23
22
|
const graph = {
|
|
24
23
|
$START: {
|
|
25
|
-
new: { role: "classifier", prompt: " {{
|
|
24
|
+
new: { role: "classifier", prompt: " {{ userPrompt }} ", location: null },
|
|
26
25
|
},
|
|
27
26
|
};
|
|
28
27
|
|
|
@@ -38,7 +37,7 @@ describe("Edge prompt template variable resolution", () => {
|
|
|
38
37
|
test("succeeds when all template variables resolve to non-empty values", () => {
|
|
39
38
|
const graph = {
|
|
40
39
|
$START: {
|
|
41
|
-
new: { role: "classifier", prompt: "{{
|
|
40
|
+
new: { role: "classifier", prompt: "{{ userPrompt }}", location: null },
|
|
42
41
|
},
|
|
43
42
|
};
|
|
44
43
|
|
|
@@ -68,7 +67,7 @@ describe("Edge prompt template variable resolution", () => {
|
|
|
68
67
|
test("succeeds when prompt has mix of static text and unresolved variables", () => {
|
|
69
68
|
const graph = {
|
|
70
69
|
$START: {
|
|
71
|
-
new: { role: "classifier", prompt: "Please handle: {{
|
|
70
|
+
new: { role: "classifier", prompt: "Please handle: {{ userPrompt }}", location: null },
|
|
72
71
|
},
|
|
73
72
|
};
|
|
74
73
|
|
|
@@ -83,7 +82,7 @@ describe("Edge prompt template variable resolution", () => {
|
|
|
83
82
|
test("returns error when ALL variables missing and no static text remains", () => {
|
|
84
83
|
const graph = {
|
|
85
84
|
$START: {
|
|
86
|
-
new: { role: "classifier", prompt: "{{
|
|
85
|
+
new: { role: "classifier", prompt: "{{ a }}{{ b }}", location: null },
|
|
87
86
|
},
|
|
88
87
|
};
|
|
89
88
|
|
|
@@ -91,6 +90,24 @@ describe("Edge prompt template variable resolution", () => {
|
|
|
91
90
|
|
|
92
91
|
expect(result.ok).toBe(false);
|
|
93
92
|
});
|
|
93
|
+
|
|
94
|
+
test("does not HTML-escape characters like <, >, &", () => {
|
|
95
|
+
const graph = {
|
|
96
|
+
$START: {
|
|
97
|
+
new: { role: "classifier", prompt: "{{ content }}", location: null },
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const result = evaluate(graph, "$START", {
|
|
102
|
+
$status: "new",
|
|
103
|
+
content: "<div>Hello & welcome</div>",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(result.ok).toBe(true);
|
|
107
|
+
if (result.ok) {
|
|
108
|
+
expect(result.value.prompt).toBe("<div>Hello & welcome</div>");
|
|
109
|
+
}
|
|
110
|
+
});
|
|
94
111
|
});
|
|
95
112
|
|
|
96
113
|
describe("Moderator location resolution", () => {
|
|
@@ -108,7 +125,7 @@ describe("Moderator location resolution", () => {
|
|
|
108
125
|
const result = evaluate(graph, "planner", { $status: "ready" });
|
|
109
126
|
|
|
110
127
|
expect(result.ok).toBe(true);
|
|
111
|
-
if (result.ok
|
|
128
|
+
if (result.ok) {
|
|
112
129
|
expect(result.value.location).toBe(null);
|
|
113
130
|
}
|
|
114
131
|
});
|
|
@@ -127,18 +144,18 @@ describe("Moderator location resolution", () => {
|
|
|
127
144
|
const result = evaluate(graph, "planner", { $status: "ready" });
|
|
128
145
|
|
|
129
146
|
expect(result.ok).toBe(true);
|
|
130
|
-
if (result.ok
|
|
147
|
+
if (result.ok) {
|
|
131
148
|
expect(result.value.location).toBe("/static/path");
|
|
132
149
|
}
|
|
133
150
|
});
|
|
134
151
|
|
|
135
|
-
test("resolves
|
|
152
|
+
test("resolves liquid template location", () => {
|
|
136
153
|
const graph = {
|
|
137
154
|
planner: {
|
|
138
155
|
ready: {
|
|
139
156
|
role: "coder",
|
|
140
157
|
prompt: "Implement the code",
|
|
141
|
-
location: "{{
|
|
158
|
+
location: "{{ repoPath }}",
|
|
142
159
|
},
|
|
143
160
|
},
|
|
144
161
|
};
|
|
@@ -149,18 +166,18 @@ describe("Moderator location resolution", () => {
|
|
|
149
166
|
});
|
|
150
167
|
|
|
151
168
|
expect(result.ok).toBe(true);
|
|
152
|
-
if (result.ok
|
|
169
|
+
if (result.ok) {
|
|
153
170
|
expect(result.value.location).toBe("/home/user/repo");
|
|
154
171
|
}
|
|
155
172
|
});
|
|
156
173
|
|
|
157
|
-
test("resolves
|
|
174
|
+
test("resolves liquid template with multiple variables", () => {
|
|
158
175
|
const graph = {
|
|
159
176
|
planner: {
|
|
160
177
|
ready: {
|
|
161
178
|
role: "coder",
|
|
162
179
|
prompt: "Implement the code",
|
|
163
|
-
location: "{{
|
|
180
|
+
location: "{{ basePath }}/{{ projectName }}",
|
|
164
181
|
},
|
|
165
182
|
},
|
|
166
183
|
};
|
|
@@ -172,7 +189,7 @@ describe("Moderator location resolution", () => {
|
|
|
172
189
|
});
|
|
173
190
|
|
|
174
191
|
expect(result.ok).toBe(true);
|
|
175
|
-
if (result.ok
|
|
192
|
+
if (result.ok) {
|
|
176
193
|
expect(result.value.location).toBe("/home/user/myproject");
|
|
177
194
|
}
|
|
178
195
|
});
|
|
@@ -183,7 +200,7 @@ describe("Moderator location resolution", () => {
|
|
|
183
200
|
ready: {
|
|
184
201
|
role: "coder",
|
|
185
202
|
prompt: "Implement the code",
|
|
186
|
-
location: "{{
|
|
203
|
+
location: "{{ repoPath }}",
|
|
187
204
|
},
|
|
188
205
|
},
|
|
189
206
|
};
|
|
@@ -191,8 +208,8 @@ describe("Moderator location resolution", () => {
|
|
|
191
208
|
const result = evaluate(graph, "planner", { $status: "ready" });
|
|
192
209
|
|
|
193
210
|
expect(result.ok).toBe(true);
|
|
194
|
-
if (result.ok
|
|
195
|
-
//
|
|
211
|
+
if (result.ok) {
|
|
212
|
+
// LiquidJS renders missing variables as empty string
|
|
196
213
|
expect(result.value.location).toBe("");
|
|
197
214
|
}
|
|
198
215
|
});
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import type { Target } from "@united-workforce/protocol";
|
|
2
|
-
import
|
|
2
|
+
import { Liquid } from "liquidjs";
|
|
3
3
|
|
|
4
4
|
import type { EvaluateResult, Result } from "./types.js";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
mustache.escape = (text: string) => text;
|
|
8
|
-
|
|
9
|
-
const SUSPEND_ROLE = "$SUSPEND";
|
|
6
|
+
const engine = new Liquid();
|
|
10
7
|
|
|
11
8
|
type LastOutput = Record<string, unknown>;
|
|
12
9
|
|
|
@@ -44,7 +41,7 @@ export function evaluate(
|
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
try {
|
|
47
|
-
const prompt =
|
|
44
|
+
const prompt = engine.parseAndRenderSync(target.prompt, lastOutput);
|
|
48
45
|
if (prompt.trim() === "") {
|
|
49
46
|
return {
|
|
50
47
|
ok: false,
|
|
@@ -53,18 +50,9 @@ export function evaluate(
|
|
|
53
50
|
),
|
|
54
51
|
};
|
|
55
52
|
}
|
|
56
|
-
if (target.role === SUSPEND_ROLE) {
|
|
57
|
-
return {
|
|
58
|
-
ok: true,
|
|
59
|
-
value: {
|
|
60
|
-
action: "suspend",
|
|
61
|
-
suspendedRole: lastRole,
|
|
62
|
-
prompt,
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
53
|
|
|
67
|
-
const location =
|
|
54
|
+
const location =
|
|
55
|
+
target.location !== null ? engine.parseAndRenderSync(target.location, lastOutput) : null;
|
|
68
56
|
return { ok: true, value: { role: target.role, prompt, location } };
|
|
69
57
|
} catch (error) {
|
|
70
58
|
return {
|
package/src/moderator/index.ts
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
export { evaluate } from "./evaluate.js";
|
|
2
|
-
export type {
|
|
3
|
-
EvaluateResult,
|
|
4
|
-
EvaluateRouteResult,
|
|
5
|
-
EvaluateSuspendResult,
|
|
6
|
-
} from "./types.js";
|
|
7
|
-
export { isSuspendResult } from "./types.js";
|
|
2
|
+
export type { EvaluateResult, EvaluateRouteResult } from "./types.js";
|
package/src/moderator/types.ts
CHANGED
|
@@ -8,17 +8,9 @@ export type EvaluateRouteResult = {
|
|
|
8
8
|
location: string | null;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/** The result of moderator evaluation. */
|
|
20
|
-
export type EvaluateResult = EvaluateRouteResult | EvaluateSuspendResult;
|
|
21
|
-
|
|
22
|
-
export function isSuspendResult(result: EvaluateResult): result is EvaluateSuspendResult {
|
|
23
|
-
return "action" in result && result.action === "suspend";
|
|
24
|
-
}
|
|
11
|
+
/**
|
|
12
|
+
* The result of moderator evaluation. `$SUSPEND` is no longer a moderator
|
|
13
|
+
* concern — it is an engine-level reserved `$status` intercepted before the
|
|
14
|
+
* moderator runs.
|
|
15
|
+
*/
|
|
16
|
+
export type EvaluateResult = EvaluateRouteResult;
|
package/src/schemas.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Hash, Store } from "@ocas/core";
|
|
2
2
|
import { putSchema } from "@ocas/core";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ERROR_OUTPUT_SCHEMA,
|
|
5
|
+
START_NODE_SCHEMA,
|
|
6
|
+
STEP_NODE_SCHEMA,
|
|
7
|
+
SUSPEND_OUTPUT_SCHEMA,
|
|
8
|
+
WORKFLOW_SCHEMA,
|
|
9
|
+
} from "@united-workforce/protocol";
|
|
4
10
|
|
|
5
11
|
export const TEXT_SCHEMA = { type: "string" as const };
|
|
6
12
|
|
|
@@ -9,6 +15,8 @@ export type UwfSchemaHashes = {
|
|
|
9
15
|
startNode: Hash;
|
|
10
16
|
stepNode: Hash;
|
|
11
17
|
text: Hash;
|
|
18
|
+
errorOutput: Hash;
|
|
19
|
+
suspendOutput: Hash;
|
|
12
20
|
};
|
|
13
21
|
|
|
14
22
|
/**
|
|
@@ -16,11 +24,13 @@ export type UwfSchemaHashes = {
|
|
|
16
24
|
* Idempotent: safe to call on every CLI invocation.
|
|
17
25
|
*/
|
|
18
26
|
export async function registerUwfSchemas(store: Store): Promise<UwfSchemaHashes> {
|
|
19
|
-
const [workflow, startNode, stepNode, text] = await Promise.all([
|
|
27
|
+
const [workflow, startNode, stepNode, text, errorOutput, suspendOutput] = await Promise.all([
|
|
20
28
|
putSchema(store, WORKFLOW_SCHEMA),
|
|
21
29
|
putSchema(store, START_NODE_SCHEMA),
|
|
22
30
|
putSchema(store, STEP_NODE_SCHEMA),
|
|
23
31
|
putSchema(store, TEXT_SCHEMA),
|
|
32
|
+
putSchema(store, ERROR_OUTPUT_SCHEMA),
|
|
33
|
+
putSchema(store, SUSPEND_OUTPUT_SCHEMA),
|
|
24
34
|
]);
|
|
25
|
-
return { workflow, startNode, stepNode, text };
|
|
35
|
+
return { workflow, startNode, stepNode, text, errorOutput, suspendOutput };
|
|
26
36
|
}
|
package/src/store.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Dirent } from "node:fs";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { access, mkdir, readdir, readFile, rename } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
5
|
+
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
6
6
|
|
|
7
7
|
import { bootstrap, type Hash, type Store, type VarStore } from "@ocas/core";
|
|
8
8
|
import { createFsStore, createSqliteVarStore } from "@ocas/fs";
|
|
@@ -20,7 +20,7 @@ export const REGISTRY_VAR_PREFIX = "@uwf/registry/";
|
|
|
20
20
|
/** Variable name prefix for active thread entries (`@uwf/thread/<thread-id>`). */
|
|
21
21
|
export const THREAD_VAR_PREFIX = "@uwf/thread/";
|
|
22
22
|
|
|
23
|
-
/** A workflow entry discovered from the project-local .workflows/ directory. */
|
|
23
|
+
/** A workflow entry discovered from the project-local .workflows/ (primary) or .workflow/ (legacy) directory. */
|
|
24
24
|
export type ProjectWorkflowEntry = {
|
|
25
25
|
/** Workflow name (from YAML `name` field, equals filename stem). */
|
|
26
26
|
name: string;
|
|
@@ -82,16 +82,11 @@ async function scanWorkflowDir(dir: string): Promise<ProjectWorkflowEntry[]> {
|
|
|
82
82
|
return result;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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"));
|
|
85
|
+
/** Merge primary (.workflows/) and legacy (.workflow/) entries, primary wins on name collision. */
|
|
86
|
+
function mergeWorkflowEntries(
|
|
87
|
+
primary: ProjectWorkflowEntry[],
|
|
88
|
+
legacy: ProjectWorkflowEntry[],
|
|
89
|
+
): ProjectWorkflowEntry[] {
|
|
95
90
|
const seen = new Set(primary.map((e) => e.name));
|
|
96
91
|
const merged = [...primary];
|
|
97
92
|
for (const entry of legacy) {
|
|
@@ -102,6 +97,63 @@ export async function discoverProjectWorkflows(
|
|
|
102
97
|
return merged;
|
|
103
98
|
}
|
|
104
99
|
|
|
100
|
+
/** Check if a directory contains a .git marker (directory or file). */
|
|
101
|
+
async function hasGitMarker(dir: string): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
await access(join(dir, ".git"));
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Discover project-local workflows by walking from `startDir` up through parent
|
|
112
|
+
* directories. The nearest directory that contains a `.workflows/` or `.workflow/`
|
|
113
|
+
* directory wins — once a match is found, traversal stops (entries from more
|
|
114
|
+
* distant ancestors are NOT merged in).
|
|
115
|
+
*
|
|
116
|
+
* Within the winning directory:
|
|
117
|
+
* - `.workflows/` (preferred/primary) takes priority over `.workflow/` (legacy fallback).
|
|
118
|
+
* - If both exist in that directory, `.workflows/` entries win when names collide.
|
|
119
|
+
*
|
|
120
|
+
* This matches the resolution strategy of `findWorkflowInParents` used by
|
|
121
|
+
* `uwf thread start`, so `uwf workflow list` and `uwf thread start` agree on
|
|
122
|
+
* what's discoverable from any given subdirectory.
|
|
123
|
+
*
|
|
124
|
+
* Traversal stops at the first `.git` boundary (directory or file) or the
|
|
125
|
+
* filesystem root. Returns an empty array if no `.workflows/` or `.workflow/`
|
|
126
|
+
* directory exists within that range.
|
|
127
|
+
*/
|
|
128
|
+
export async function discoverProjectWorkflows(startDir: string): Promise<ProjectWorkflowEntry[]> {
|
|
129
|
+
let currentDir = resolvePath(startDir);
|
|
130
|
+
const root = resolvePath("/");
|
|
131
|
+
|
|
132
|
+
while (true) {
|
|
133
|
+
const primary = await scanWorkflowDir(join(currentDir, ".workflows"));
|
|
134
|
+
const legacy = await scanWorkflowDir(join(currentDir, ".workflow"));
|
|
135
|
+
|
|
136
|
+
if (primary.length > 0 || legacy.length > 0) {
|
|
137
|
+
return mergeWorkflowEntries(primary, legacy);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Stop at .git boundary (repo root)
|
|
141
|
+
if (await hasGitMarker(currentDir)) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Stop at filesystem root
|
|
146
|
+
if (currentDir === root) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const parentDir = dirname(currentDir);
|
|
150
|
+
if (parentDir === currentDir) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
currentDir = parentDir;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
105
157
|
/** Default filesystem root for uwf data (`~/.uwf`). */
|
|
106
158
|
export function getDefaultStorageRoot(): string {
|
|
107
159
|
return join(homedir(), ".uwf");
|
|
@@ -335,35 +387,35 @@ export function setThread(varStore: VarStore, threadId: ThreadId, entry: ThreadI
|
|
|
335
387
|
varStore.set(name, entry.head, { tags });
|
|
336
388
|
}
|
|
337
389
|
|
|
338
|
-
/** Load only active threads (status not in
|
|
390
|
+
/** Load only active threads (status not in end/cancelled). */
|
|
339
391
|
export function loadActiveThreads(varStore: VarStore): ThreadsIndex {
|
|
340
392
|
const all = loadAllThreads(varStore);
|
|
341
393
|
const active: ThreadsIndex = {};
|
|
342
394
|
for (const [threadId, entry] of Object.entries(all)) {
|
|
343
|
-
if (entry.status !== "
|
|
395
|
+
if (entry.status !== "end" && entry.status !== "cancelled") {
|
|
344
396
|
active[threadId as ThreadId] = entry;
|
|
345
397
|
}
|
|
346
398
|
}
|
|
347
399
|
return active;
|
|
348
400
|
}
|
|
349
401
|
|
|
350
|
-
/** Load only
|
|
402
|
+
/** Load only end/cancelled threads (history). */
|
|
351
403
|
export function loadHistoryThreads(varStore: VarStore): ThreadsIndex {
|
|
352
404
|
const all = loadAllThreads(varStore);
|
|
353
405
|
const history: ThreadsIndex = {};
|
|
354
406
|
for (const [threadId, entry] of Object.entries(all)) {
|
|
355
|
-
if (entry.status === "
|
|
407
|
+
if (entry.status === "end" || entry.status === "cancelled") {
|
|
356
408
|
history[threadId as ThreadId] = entry;
|
|
357
409
|
}
|
|
358
410
|
}
|
|
359
411
|
return history;
|
|
360
412
|
}
|
|
361
413
|
|
|
362
|
-
/** Complete a thread by marking it
|
|
414
|
+
/** Complete a thread by marking it end or cancelled. */
|
|
363
415
|
export function completeThread(
|
|
364
416
|
varStore: VarStore,
|
|
365
417
|
threadId: ThreadId,
|
|
366
|
-
reason: "
|
|
418
|
+
reason: "end" | "cancelled",
|
|
367
419
|
): void {
|
|
368
420
|
const entry = getThread(varStore, threadId);
|
|
369
421
|
if (entry === null) {
|
|
@@ -377,6 +429,20 @@ export function completeThread(
|
|
|
377
429
|
completedAt: Date.now(),
|
|
378
430
|
} as ThreadIndexEntry;
|
|
379
431
|
setThread(varStore, threadId, completed);
|
|
432
|
+
clearThreadFailedAttempts(varStore, threadId);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Remove all `@uwf/thread-failed/<threadId>/*` variables for a thread.
|
|
437
|
+
* Called on thread completion / cancellation so retry-lineage state does not
|
|
438
|
+
* leak into the variable store after the thread is archived.
|
|
439
|
+
*/
|
|
440
|
+
export function clearThreadFailedAttempts(varStore: VarStore, threadId: ThreadId): void {
|
|
441
|
+
const prefix = `@uwf/thread-failed/${threadId}/`;
|
|
442
|
+
const vars = varStore.list({ namePrefix: prefix });
|
|
443
|
+
for (const v of vars) {
|
|
444
|
+
varStore.remove(v.name);
|
|
445
|
+
}
|
|
380
446
|
}
|
|
381
447
|
|
|
382
448
|
type LegacyHistoryEntry = {
|
|
@@ -439,7 +505,7 @@ export async function migrateHistoryIfNeeded(
|
|
|
439
505
|
}
|
|
440
506
|
const entry = parseLegacyHistoryJsonlLine(trimmed);
|
|
441
507
|
if (entry !== null) {
|
|
442
|
-
const status = entry.reason === "cancelled" ? "cancelled" : "
|
|
508
|
+
const status = entry.reason === "cancelled" ? "cancelled" : "end";
|
|
443
509
|
const threadEntry: ThreadIndexEntry = {
|
|
444
510
|
head: entry.head,
|
|
445
511
|
status: status as ThreadIndexEntry["status"],
|
|
@@ -462,7 +528,7 @@ export function migrateHistoryVarsToThreadVars(varStore: VarStore): void {
|
|
|
462
528
|
for (const v of vars) {
|
|
463
529
|
const threadId = v.name.slice(LEGACY_HISTORY_VAR_PREFIX.length) as ThreadId;
|
|
464
530
|
const reason = v.tags.reason;
|
|
465
|
-
const status = reason === "cancelled" ? "cancelled" : "
|
|
531
|
+
const status = reason === "cancelled" ? "cancelled" : "end";
|
|
466
532
|
const completedAt = Number(v.tags.completedAt ?? Date.now());
|
|
467
533
|
|
|
468
534
|
const threadEntry: ThreadIndexEntry = {
|