@united-workforce/cli 0.3.0 → 0.5.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 +45 -11
- package/dist/.build-fingerprint +1 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js +17 -7
- 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__/concurrency.test.d.ts +2 -0
- package/dist/__tests__/concurrency.test.d.ts.map +1 -0
- package/dist/__tests__/concurrency.test.js +196 -0
- package/dist/__tests__/concurrency.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 +43 -30
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
- package/dist/__tests__/format-text-default.test.d.ts +2 -0
- package/dist/__tests__/format-text-default.test.d.ts.map +1 -0
- package/dist/__tests__/format-text-default.test.js +43 -0
- package/dist/__tests__/format-text-default.test.js.map +1 -0
- package/dist/__tests__/format-text-registry.test.d.ts +2 -0
- package/dist/__tests__/format-text-registry.test.d.ts.map +1 -0
- package/dist/__tests__/format-text-registry.test.js +158 -0
- package/dist/__tests__/format-text-registry.test.js.map +1 -0
- 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__/log-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/log-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/log-text-renderer.test.js +265 -0
- package/dist/__tests__/log-text-renderer.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__/output-mapper-thread-list-startedat.test.d.ts +2 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts.map +1 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.js +102 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.js.map +1 -0
- package/dist/__tests__/output-mapper-workflow-add.test.d.ts +2 -0
- package/dist/__tests__/output-mapper-workflow-add.test.d.ts.map +1 -0
- package/dist/__tests__/output-mapper-workflow-add.test.js +22 -0
- package/dist/__tests__/output-mapper-workflow-add.test.js.map +1 -0
- 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 +273 -0
- package/dist/__tests__/pid-recycling.test.js.map +1 -0
- package/dist/__tests__/prompt.test.js +365 -2
- package/dist/__tests__/prompt.test.js.map +1 -1
- package/dist/__tests__/resolve-head-hash.test.js +12 -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 +27 -28
- 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 +507 -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 +28 -26
- package/dist/__tests__/store-unified-threads.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-status.test.js +25 -19
- package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.js +110 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.js.map +1 -0
- package/dist/__tests__/thread-list-filters.test.js +354 -17
- package/dist/__tests__/thread-list-filters.test.js.map +1 -1
- package/dist/__tests__/thread-list-template-ms-date.test.d.ts +2 -0
- package/dist/__tests__/thread-list-template-ms-date.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js +102 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts +2 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.js +157 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.js.map +1 -0
- 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 +422 -0
- package/dist/__tests__/thread-poke.test.js.map +1 -0
- package/dist/__tests__/thread-read-xml-tags.test.js +10 -9
- package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -1
- package/dist/__tests__/thread-resume.test.js +21 -15
- 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-start-cwd-cli.test.js +15 -3
- package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -1
- package/dist/__tests__/thread-stop-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/thread-stop-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/thread-stop-text-renderer.test.js +148 -0
- package/dist/__tests__/thread-stop-text-renderer.test.js.map +1 -0
- package/dist/__tests__/thread-suspend-step.test.js +13 -16
- 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-helpers.d.ts +7 -0
- package/dist/__tests__/thread-test-helpers.d.ts.map +1 -1
- package/dist/__tests__/thread-test-helpers.js +13 -0
- package/dist/__tests__/thread-test-helpers.js.map +1 -1
- package/dist/__tests__/thread.test.js +15 -13
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/__tests__/validate-semantic.test.js +105 -23
- 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 +286 -0
- package/dist/__tests__/workflow-list-recursive.test.js.map +1 -0
- package/dist/__tests__/workflow-resolution.test.js +46 -28
- 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 +213 -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 +707 -0
- package/dist/__tests__/workflow-validate.test.js.map +1 -0
- package/dist/__tests__/write-envelope.test.d.ts +2 -0
- package/dist/__tests__/write-envelope.test.d.ts.map +1 -0
- package/dist/__tests__/write-envelope.test.js +201 -0
- package/dist/__tests__/write-envelope.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 +120 -62
- 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 +17 -31
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +57 -31
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +12 -39
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +72 -303
- 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 +423 -142
- 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 +126 -6
- package/dist/commands/workflow.js.map +1 -1
- package/dist/concurrency/concurrency.d.ts +34 -0
- package/dist/concurrency/concurrency.d.ts.map +1 -0
- package/dist/concurrency/concurrency.js +216 -0
- package/dist/concurrency/concurrency.js.map +1 -0
- package/dist/concurrency/index.d.ts +3 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/index.js +2 -0
- package/dist/concurrency/index.js.map +1 -0
- package/dist/concurrency/types.d.ts +19 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency/types.js +2 -0
- package/dist/concurrency/types.js.map +1 -0
- package/dist/format.d.ts +69 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +198 -1
- package/dist/format.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/output-mappers.d.ts +122 -0
- package/dist/output-mappers.d.ts.map +1 -0
- package/dist/output-mappers.js +134 -0
- package/dist/output-mappers.js.map +1 -0
- package/dist/schemas.d.ts +6 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +34 -5
- 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/text-renderers.d.ts +30 -0
- package/dist/text-renderers.d.ts.map +1 -0
- package/dist/text-renderers.js +251 -0
- package/dist/text-renderers.js.map +1 -0
- package/dist/validate-semantic.d.ts.map +1 -1
- package/dist/validate-semantic.js +95 -61
- 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/examples/brainstorm.yaml +130 -0
- package/examples/debate.yaml +169 -0
- package/examples/socratic-questioning.yaml +112 -0
- package/package.json +9 -10
- package/src/__tests__/adapter-json-roundtrip.test.ts +16 -7
- 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__/concurrency.test.ts +266 -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 +65 -30
- 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__/format-text-default.test.ts +49 -0
- package/src/__tests__/format-text-registry.test.ts +173 -0
- package/src/__tests__/issue-180-workflow-ref-removed.test.ts +43 -0
- package/src/__tests__/log-text-renderer.test.ts +294 -0
- package/src/__tests__/moderator-evaluate.test.ts +9 -52
- package/src/__tests__/output-mapper-thread-list-startedat.test.ts +124 -0
- package/src/__tests__/output-mapper-workflow-add.test.ts +24 -0
- package/src/__tests__/pid-recycling.test.ts +329 -0
- package/src/__tests__/prompt.test.ts +443 -2
- package/src/__tests__/resolve-head-hash.test.ts +11 -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 +27 -31
- package/src/__tests__/step-ask.test.ts +677 -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 +30 -27
- package/src/__tests__/thread-cancel-status.test.ts +27 -20
- package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
- package/src/__tests__/thread-list-filters.test.ts +443 -17
- package/src/__tests__/thread-list-template-ms-date.test.ts +110 -0
- package/src/__tests__/thread-list-workflow-corrupt.test.ts +198 -0
- package/src/__tests__/thread-poke.test.ts +554 -0
- package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
- package/src/__tests__/thread-resume.test.ts +20 -15
- package/src/__tests__/thread-show-status.test.ts +17 -29
- package/src/__tests__/thread-start-cwd-cli.test.ts +15 -3
- package/src/__tests__/thread-stop-text-renderer.test.ts +168 -0
- package/src/__tests__/thread-suspend-step.test.ts +13 -16
- package/src/__tests__/thread-suspended-display.test.ts +10 -22
- package/src/__tests__/thread-test-helpers.ts +15 -1
- package/src/__tests__/thread.test.ts +14 -14
- package/src/__tests__/validate-semantic.test.ts +118 -33
- package/src/__tests__/workflow-list-recursive.test.ts +370 -0
- package/src/__tests__/workflow-resolution.test.ts +48 -29
- package/src/__tests__/workflow-show-resolution.test.ts +286 -0
- package/src/__tests__/workflow-validate.test.ts +828 -0
- package/src/__tests__/write-envelope.test.ts +257 -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 +184 -77
- package/src/commands/config.ts +16 -33
- package/src/commands/prompt.ts +57 -31
- package/src/commands/setup.ts +80 -358
- package/src/commands/step.ts +339 -12
- package/src/commands/thread.ts +511 -171
- package/src/commands/workflow.ts +155 -4
- package/src/concurrency/concurrency.ts +245 -0
- package/src/concurrency/index.ts +10 -0
- package/src/concurrency/types.ts +19 -0
- package/src/format.ts +282 -2
- 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/output-mappers.ts +254 -0
- package/src/schemas.ts +51 -5
- package/src/store.ts +86 -20
- package/src/text-renderers.ts +355 -0
- package/src/validate-semantic.ts +125 -73
- 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
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import type { CasRef, StartOutput, StepOutput } from "@united-workforce/protocol";
|
|
2
|
+
import { extractUlidTimestamp } from "@united-workforce/util";
|
|
3
|
+
import type { ThreadListItemWithStatus } from "./commands/thread.js";
|
|
4
|
+
import type {
|
|
5
|
+
WorkflowAddOutput,
|
|
6
|
+
WorkflowListEntry,
|
|
7
|
+
WorkflowShowOutput,
|
|
8
|
+
} from "./commands/workflow.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mappers that convert the existing rich command outputs into the
|
|
12
|
+
* schema-aligned payload shapes registered under `@uwf/output/*`.
|
|
13
|
+
*
|
|
14
|
+
* Each mapper returns plain payload data — no CAS refs, no JSON encoding,
|
|
15
|
+
* no I/O. The CLI calls one of these immediately before handing the payload
|
|
16
|
+
* to `writeEnvelope`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type ThreadStartPayload = {
|
|
20
|
+
threadId: string;
|
|
21
|
+
workflowHash: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function toThreadStartPayload(out: StartOutput): ThreadStartPayload {
|
|
25
|
+
return { threadId: out.thread, workflowHash: out.workflow };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ThreadStatusPayload = {
|
|
29
|
+
threadId: string;
|
|
30
|
+
workflowHash: string;
|
|
31
|
+
head: string | null;
|
|
32
|
+
status: string;
|
|
33
|
+
currentRole: string | null;
|
|
34
|
+
suspendedRole: string | null;
|
|
35
|
+
suspendMessage: string | null;
|
|
36
|
+
done: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function toThreadStatusPayload(out: StepOutput): ThreadStatusPayload {
|
|
40
|
+
return {
|
|
41
|
+
threadId: out.thread,
|
|
42
|
+
workflowHash: out.workflow,
|
|
43
|
+
head: out.head ?? null,
|
|
44
|
+
status: out.status,
|
|
45
|
+
currentRole: out.currentRole,
|
|
46
|
+
suspendedRole: out.suspendedRole,
|
|
47
|
+
suspendMessage: out.suspendMessage,
|
|
48
|
+
done: out.done,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ThreadListPayload = {
|
|
53
|
+
items: Array<{
|
|
54
|
+
threadId: string;
|
|
55
|
+
workflowHash: string;
|
|
56
|
+
workflowName: string | null;
|
|
57
|
+
status: string;
|
|
58
|
+
currentRole: string | null;
|
|
59
|
+
startedAt: number | null;
|
|
60
|
+
completedAt: number | null;
|
|
61
|
+
}>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function toThreadListPayload(items: ThreadListItemWithStatus[]): ThreadListPayload {
|
|
65
|
+
return {
|
|
66
|
+
items: items.map((it) => ({
|
|
67
|
+
threadId: it.thread,
|
|
68
|
+
workflowHash: it.workflow,
|
|
69
|
+
workflowName: it.workflowName,
|
|
70
|
+
status: it.status,
|
|
71
|
+
currentRole: it.currentRole,
|
|
72
|
+
startedAt: extractUlidTimestamp(it.thread),
|
|
73
|
+
completedAt: null,
|
|
74
|
+
})),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ThreadExecPayload = {
|
|
79
|
+
threadId: string;
|
|
80
|
+
workflowHash: string;
|
|
81
|
+
steps: Array<{
|
|
82
|
+
head: string;
|
|
83
|
+
status: string;
|
|
84
|
+
currentRole: string | null;
|
|
85
|
+
done: boolean;
|
|
86
|
+
role: string | null;
|
|
87
|
+
suspendedRole: string | null;
|
|
88
|
+
suspendMessage: string | null;
|
|
89
|
+
}>;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export function toThreadExecPayload(results: StepOutput[]): ThreadExecPayload {
|
|
93
|
+
const first = results[0];
|
|
94
|
+
return {
|
|
95
|
+
threadId: first?.thread ?? "",
|
|
96
|
+
workflowHash: first?.workflow ?? "",
|
|
97
|
+
steps: results.map((r) => ({
|
|
98
|
+
head: r.head,
|
|
99
|
+
status: r.status,
|
|
100
|
+
currentRole: r.currentRole,
|
|
101
|
+
done: r.done,
|
|
102
|
+
role: r.currentRole ?? r.suspendedRole ?? null,
|
|
103
|
+
suspendedRole: r.suspendedRole,
|
|
104
|
+
suspendMessage: r.suspendMessage,
|
|
105
|
+
})),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type StepDetailPayload = {
|
|
110
|
+
hash: string;
|
|
111
|
+
role: string;
|
|
112
|
+
agent: string;
|
|
113
|
+
status: string;
|
|
114
|
+
startedAtMs: number | null;
|
|
115
|
+
completedAtMs: number | null;
|
|
116
|
+
durationMs: number | null;
|
|
117
|
+
frontmatter: Record<string, unknown>;
|
|
118
|
+
turns: Array<{ role: string; content: string; timestamp: number | null }>;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export function toStepDetailPayload(stepHash: CasRef, raw: unknown): StepDetailPayload {
|
|
122
|
+
const r = (raw ?? {}) as Record<string, unknown>;
|
|
123
|
+
const turnsIn = Array.isArray(r.turns) ? (r.turns as unknown[]) : [];
|
|
124
|
+
const startedAtMs = numericOrNull(r.startedAtMs);
|
|
125
|
+
const completedAtMs = numericOrNull(r.completedAtMs);
|
|
126
|
+
const durationMs =
|
|
127
|
+
startedAtMs !== null && completedAtMs !== null && completedAtMs >= startedAtMs
|
|
128
|
+
? completedAtMs - startedAtMs
|
|
129
|
+
: null;
|
|
130
|
+
const frontmatter =
|
|
131
|
+
r.frontmatter !== null && typeof r.frontmatter === "object" && !Array.isArray(r.frontmatter)
|
|
132
|
+
? (r.frontmatter as Record<string, unknown>)
|
|
133
|
+
: {};
|
|
134
|
+
const status =
|
|
135
|
+
typeof frontmatter.$status === "string"
|
|
136
|
+
? (frontmatter.$status as string)
|
|
137
|
+
: typeof r.status === "string"
|
|
138
|
+
? (r.status as string)
|
|
139
|
+
: "";
|
|
140
|
+
return {
|
|
141
|
+
hash: stepHash,
|
|
142
|
+
role: typeof r.role === "string" ? r.role : "",
|
|
143
|
+
agent: typeof r.agent === "string" ? r.agent : "",
|
|
144
|
+
status,
|
|
145
|
+
startedAtMs,
|
|
146
|
+
completedAtMs,
|
|
147
|
+
durationMs,
|
|
148
|
+
frontmatter,
|
|
149
|
+
turns: turnsIn.map((t) => {
|
|
150
|
+
const o = (t ?? {}) as Record<string, unknown>;
|
|
151
|
+
return {
|
|
152
|
+
role: typeof o.role === "string" ? o.role : "",
|
|
153
|
+
content: typeof o.content === "string" ? o.content : "",
|
|
154
|
+
timestamp: numericOrNull(o.timestamp),
|
|
155
|
+
};
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function numericOrNull(v: unknown): number | null {
|
|
161
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type StepListPayload = {
|
|
165
|
+
threadId: string;
|
|
166
|
+
items: Array<{ hash: string; role: string; durationMs: number | null }>;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
type StepsLikeOutput = {
|
|
170
|
+
thread: string;
|
|
171
|
+
steps: Array<{
|
|
172
|
+
hash: CasRef;
|
|
173
|
+
role?: string;
|
|
174
|
+
durationMs?: number;
|
|
175
|
+
}>;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export function toStepListPayload(out: StepsLikeOutput): StepListPayload {
|
|
179
|
+
return {
|
|
180
|
+
threadId: out.thread,
|
|
181
|
+
items: out.steps
|
|
182
|
+
.filter((s) => typeof s.role === "string")
|
|
183
|
+
.map((s) => ({
|
|
184
|
+
hash: s.hash,
|
|
185
|
+
role: s.role ?? "",
|
|
186
|
+
durationMs: typeof s.durationMs === "number" ? s.durationMs : null,
|
|
187
|
+
})),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type WorkflowDetailPayload = {
|
|
192
|
+
name: string;
|
|
193
|
+
hash: string;
|
|
194
|
+
version: number;
|
|
195
|
+
description: string;
|
|
196
|
+
roles: Record<string, { description: string; goal: string }>;
|
|
197
|
+
graph: Record<string, Record<string, { role: string; prompt: string }>>;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export function toWorkflowDetailPayload(out: WorkflowShowOutput): WorkflowDetailPayload {
|
|
201
|
+
const roles: Record<string, { description: string; goal: string }> = {};
|
|
202
|
+
for (const [name, def] of Object.entries(out.payload.roles)) {
|
|
203
|
+
roles[name] = { description: def.description, goal: def.goal };
|
|
204
|
+
}
|
|
205
|
+
const graph: Record<string, Record<string, { role: string; prompt: string }>> = {};
|
|
206
|
+
for (const [from, transitions] of Object.entries(out.payload.graph)) {
|
|
207
|
+
const t: Record<string, { role: string; prompt: string }> = {};
|
|
208
|
+
for (const [status, target] of Object.entries(transitions)) {
|
|
209
|
+
t[status] = { role: target.role, prompt: target.prompt };
|
|
210
|
+
}
|
|
211
|
+
graph[from] = t;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
name: out.name ?? out.payload.name,
|
|
215
|
+
hash: out.hash,
|
|
216
|
+
version: out.payload.version,
|
|
217
|
+
description: out.payload.description,
|
|
218
|
+
roles,
|
|
219
|
+
graph,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export type WorkflowListPayload = {
|
|
224
|
+
items: Array<{ name: string; hash: string; source: string; description: string }>;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export function toWorkflowListPayload(entries: WorkflowListEntry[]): WorkflowListPayload {
|
|
228
|
+
return {
|
|
229
|
+
items: entries.map((e) => ({
|
|
230
|
+
name: e.name,
|
|
231
|
+
hash: e.hash,
|
|
232
|
+
source: e.origin === "local" ? ".workflows" : "registry",
|
|
233
|
+
description: "",
|
|
234
|
+
})),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export type WorkflowAddPayload = {
|
|
239
|
+
name: string;
|
|
240
|
+
hash: string;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export function toWorkflowAddPayload(out: WorkflowAddOutput): WorkflowAddPayload {
|
|
244
|
+
return { name: out.name, hash: out.hash };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export type ValidateResultPayload = {
|
|
248
|
+
valid: boolean;
|
|
249
|
+
errors: string[];
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export function toValidateResultPayload(errors: string[]): ValidateResultPayload {
|
|
253
|
+
return { valid: errors.length === 0, errors };
|
|
254
|
+
}
|
package/src/schemas.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { Hash, Store } from "@ocas/core";
|
|
2
|
-
import { putSchema } from "@ocas/core";
|
|
3
|
-
import {
|
|
2
|
+
import { bootstrap, putSchema } from "@ocas/core";
|
|
3
|
+
import {
|
|
4
|
+
ERROR_OUTPUT_SCHEMA,
|
|
5
|
+
OUTPUT_SCHEMAS,
|
|
6
|
+
OUTPUT_TEMPLATES,
|
|
7
|
+
type OutputSchemaName,
|
|
8
|
+
outputSchemaVarName,
|
|
9
|
+
START_NODE_SCHEMA,
|
|
10
|
+
STEP_NODE_SCHEMA,
|
|
11
|
+
SUSPEND_OUTPUT_SCHEMA,
|
|
12
|
+
WORKFLOW_SCHEMA,
|
|
13
|
+
} from "@united-workforce/protocol";
|
|
4
14
|
|
|
5
15
|
export const TEXT_SCHEMA = { type: "string" as const };
|
|
6
16
|
|
|
@@ -9,18 +19,54 @@ export type UwfSchemaHashes = {
|
|
|
9
19
|
startNode: Hash;
|
|
10
20
|
stepNode: Hash;
|
|
11
21
|
text: Hash;
|
|
22
|
+
errorOutput: Hash;
|
|
23
|
+
suspendOutput: Hash;
|
|
24
|
+
outputs: Record<OutputSchemaName, Hash>;
|
|
12
25
|
};
|
|
13
26
|
|
|
14
27
|
/**
|
|
15
|
-
* Register
|
|
28
|
+
* Register every uwf JSON Schema (workflow / start / step / error / suspend
|
|
29
|
+
* + the 9 CLI output envelopes) and the matching `text` Liquid templates.
|
|
16
30
|
* Idempotent: safe to call on every CLI invocation.
|
|
17
31
|
*/
|
|
18
32
|
export async function registerUwfSchemas(store: Store): Promise<UwfSchemaHashes> {
|
|
19
|
-
const [workflow, startNode, stepNode, text] = await Promise.all([
|
|
33
|
+
const [workflow, startNode, stepNode, text, errorOutput, suspendOutput] = await Promise.all([
|
|
20
34
|
putSchema(store, WORKFLOW_SCHEMA),
|
|
21
35
|
putSchema(store, START_NODE_SCHEMA),
|
|
22
36
|
putSchema(store, STEP_NODE_SCHEMA),
|
|
23
37
|
putSchema(store, TEXT_SCHEMA),
|
|
38
|
+
putSchema(store, ERROR_OUTPUT_SCHEMA),
|
|
39
|
+
putSchema(store, SUSPEND_OUTPUT_SCHEMA),
|
|
24
40
|
]);
|
|
25
|
-
|
|
41
|
+
const outputs = await registerOutputSchemas(store);
|
|
42
|
+
return { workflow, startNode, stepNode, text, errorOutput, suspendOutput, outputs };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Register the 9 CLI output schemas, bind `@uwf/output/<name>` to each, store
|
|
47
|
+
* each Liquid template as an `@ocas/string` CAS node, and bind
|
|
48
|
+
* `@ocas/template/text/<schemaHash>` to the template content hash.
|
|
49
|
+
*
|
|
50
|
+
* Idempotent: writes are content-addressed so repeat invocations no-op.
|
|
51
|
+
*/
|
|
52
|
+
async function registerOutputSchemas(store: Store): Promise<Record<OutputSchemaName, Hash>> {
|
|
53
|
+
const aliases = bootstrap(store);
|
|
54
|
+
const stringHash = aliases["@ocas/string"];
|
|
55
|
+
if (stringHash === undefined) {
|
|
56
|
+
throw new Error("@ocas/string schema not found in bootstrap result");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result = {} as Record<OutputSchemaName, Hash>;
|
|
60
|
+
const names = Object.keys(OUTPUT_SCHEMAS) as OutputSchemaName[];
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
const schemaHash = await putSchema(store, OUTPUT_SCHEMAS[name]);
|
|
63
|
+
store.var.set(outputSchemaVarName(name), schemaHash);
|
|
64
|
+
|
|
65
|
+
const template = OUTPUT_TEMPLATES[name];
|
|
66
|
+
const contentHash = store.cas.put(stringHash, template);
|
|
67
|
+
store.var.set(`@ocas/template/text/${schemaHash}`, contentHash);
|
|
68
|
+
|
|
69
|
+
result[name] = schemaHash;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
26
72
|
}
|
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 = {
|