@united-workforce/cli 0.6.1 → 0.8.1
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 +120 -5
- package/dist/.build-fingerprint +1 -1
- package/dist/__tests__/agent-resolution-llm-free.test.js +9 -2
- package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -1
- package/dist/__tests__/broker-prompt.test.d.ts +10 -0
- package/dist/__tests__/broker-prompt.test.d.ts.map +1 -0
- package/dist/__tests__/broker-prompt.test.js +129 -0
- package/dist/__tests__/broker-prompt.test.js.map +1 -0
- package/dist/__tests__/broker-step-active-turns.test.d.ts +20 -0
- package/dist/__tests__/broker-step-active-turns.test.d.ts.map +1 -0
- package/dist/__tests__/broker-step-active-turns.test.js +428 -0
- package/dist/__tests__/broker-step-active-turns.test.js.map +1 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts +13 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts.map +1 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.js +429 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.js.map +1 -0
- package/dist/__tests__/config.test.js +33 -37
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/e2e-broker-step-suspend.test.d.ts +18 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.js +313 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.js.map +1 -0
- package/dist/__tests__/e2e-broker-step.test.d.ts +13 -0
- package/dist/__tests__/e2e-broker-step.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-broker-step.test.js +278 -0
- package/dist/__tests__/e2e-broker-step.test.js.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.js +1 -1
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts +28 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js +322 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js.map +1 -0
- package/dist/__tests__/log-tag-validity.test.d.ts +2 -0
- package/dist/__tests__/log-tag-validity.test.d.ts.map +1 -0
- package/dist/__tests__/log-tag-validity.test.js +110 -0
- package/dist/__tests__/log-tag-validity.test.js.map +1 -0
- package/dist/__tests__/setup-agent-discovery.test.js +35 -23
- package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
- package/dist/__tests__/setup-no-llm.test.js +5 -2
- package/dist/__tests__/setup-no-llm.test.js.map +1 -1
- package/dist/__tests__/step-ask.test.js +9 -6
- package/dist/__tests__/step-ask.test.js.map +1 -1
- package/dist/__tests__/step-show-json.test.js +5 -5
- package/dist/__tests__/step-show-json.test.js.map +1 -1
- package/dist/__tests__/step-show-text.test.d.ts +2 -0
- package/dist/__tests__/step-show-text.test.d.ts.map +1 -0
- package/dist/__tests__/step-show-text.test.js +192 -0
- package/dist/__tests__/step-show-text.test.js.map +1 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.d.ts +21 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.js +356 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.js.map +1 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.d.ts +21 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.js +476 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.js.map +1 -0
- package/dist/__tests__/step-turns.test.d.ts +24 -0
- package/dist/__tests__/step-turns.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns.test.js +646 -0
- package/dist/__tests__/step-turns.test.js.map +1 -0
- package/dist/__tests__/store-turn-chain.test.d.ts +2 -0
- package/dist/__tests__/store-turn-chain.test.d.ts.map +1 -0
- package/dist/__tests__/store-turn-chain.test.js +341 -0
- package/dist/__tests__/store-turn-chain.test.js.map +1 -0
- package/dist/__tests__/thread-agent-failure-suspended.test.js +3 -3
- package/dist/__tests__/thread-agent-failure-suspended.test.js.map +1 -1
- package/dist/__tests__/thread-list-limit-offset.test.d.ts +24 -0
- package/dist/__tests__/thread-list-limit-offset.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-limit-offset.test.js +254 -0
- package/dist/__tests__/thread-list-limit-offset.test.js.map +1 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js +7 -2
- package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -1
- package/dist/__tests__/thread-poke.test.js +6 -6
- package/dist/__tests__/thread-poke.test.js.map +1 -1
- package/dist/__tests__/thread-resume.test.js +2 -2
- package/dist/__tests__/thread-resume.test.js.map +1 -1
- package/dist/__tests__/thread-suspend-step.test.js +1 -1
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
- package/dist/__tests__/thread.test.js +28 -14
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/cli.js +910 -344
- package/dist/cli.js.map +1 -1
- package/dist/commands/broker-step.d.ts +117 -0
- package/dist/commands/broker-step.d.ts.map +1 -0
- package/dist/commands/broker-step.js +654 -0
- package/dist/commands/broker-step.js.map +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +2 -23
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +43 -51
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +6 -4
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +24 -27
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/step.d.ts +54 -6
- package/dist/commands/step.d.ts.map +1 -1
- package/dist/commands/step.js +484 -134
- package/dist/commands/step.js.map +1 -1
- package/dist/commands/thread.d.ts +4 -0
- package/dist/commands/thread.d.ts.map +1 -1
- package/dist/commands/thread.js +77 -151
- package/dist/commands/thread.js.map +1 -1
- package/dist/output-mappers.d.ts +8 -0
- package/dist/output-mappers.d.ts.map +1 -1
- package/dist/output-mappers.js +72 -18
- package/dist/output-mappers.js.map +1 -1
- package/dist/schemas.d.ts +3 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +17 -3
- package/dist/schemas.js.map +1 -1
- package/dist/store.d.ts +147 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +254 -1
- package/dist/store.js.map +1 -1
- package/dist/text-renderers.d.ts.map +1 -1
- package/dist/text-renderers.js +27 -2
- package/dist/text-renderers.js.map +1 -1
- package/package.json +7 -5
- package/src/__tests__/agent-resolution-llm-free.test.ts +14 -2
- package/src/__tests__/broker-prompt.test.ts +142 -0
- package/src/__tests__/broker-step-active-turns.test.ts +509 -0
- package/src/__tests__/broker-step-turn-chain-phase2.test.ts +525 -0
- package/src/__tests__/config.test.ts +35 -39
- package/src/__tests__/e2e-broker-step-suspend.test.ts +351 -0
- package/src/__tests__/e2e-broker-step.test.ts +320 -0
- package/src/__tests__/e2e-mock-agent.test.ts +1 -1
- package/src/__tests__/e2e-thread-resume-timeout-suspend.test.ts +360 -0
- package/src/__tests__/log-tag-validity.test.ts +124 -0
- package/src/__tests__/setup-agent-discovery.test.ts +35 -23
- package/src/__tests__/setup-no-llm.test.ts +5 -2
- package/src/__tests__/step-ask.test.ts +9 -6
- package/src/__tests__/step-show-json.test.ts +5 -5
- package/src/__tests__/step-show-text.test.ts +236 -0
- package/src/__tests__/step-turns-cli-subprocess.test.ts +411 -0
- package/src/__tests__/step-turns-panorama-phase3.test.ts +579 -0
- package/src/__tests__/step-turns.test.ts +734 -0
- package/src/__tests__/store-turn-chain.test.ts +386 -0
- package/src/__tests__/thread-agent-failure-suspended.test.ts +3 -3
- package/src/__tests__/thread-list-limit-offset.test.ts +305 -0
- package/src/__tests__/thread-list-template-ms-date.test.ts +7 -2
- package/src/__tests__/thread-poke.test.ts +6 -6
- package/src/__tests__/thread-resume.test.ts +2 -2
- package/src/__tests__/thread-suspend-step.test.ts +1 -1
- package/src/__tests__/thread.test.ts +29 -15
- package/src/cli.ts +1056 -483
- package/src/commands/broker-step.ts +913 -0
- package/src/commands/config.ts +2 -24
- package/src/commands/prompt.ts +43 -51
- package/src/commands/setup.ts +25 -29
- package/src/commands/step.ts +645 -176
- package/src/commands/thread.ts +87 -192
- package/src/output-mappers.ts +99 -21
- package/src/schemas.ts +32 -2
- package/src/store.ts +297 -2
- package/src/text-renderers.ts +35 -2
- package/dist/__tests__/adapter-json-roundtrip.test.d.ts +0 -2
- package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +0 -1
- package/dist/__tests__/adapter-json-roundtrip.test.js +0 -160
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +0 -1
- package/dist/__tests__/spawn-agent-json.test.d.ts +0 -2
- package/dist/__tests__/spawn-agent-json.test.d.ts.map +0 -1
- package/dist/__tests__/spawn-agent-json.test.js +0 -79
- package/dist/__tests__/spawn-agent-json.test.js.map +0 -1
- package/src/__tests__/adapter-json-roundtrip.test.ts +0 -193
- package/src/__tests__/spawn-agent-json.test.ts +0 -100
package/src/store.ts
CHANGED
|
@@ -4,9 +4,16 @@ import { access, mkdir, readdir, readFile, rename } from "node:fs/promises";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
6
6
|
|
|
7
|
-
import { bootstrap, type Hash, type Store, type VarStore } from "@ocas/core";
|
|
7
|
+
import { bootstrap, type Hash, putSchema, type Store, type VarStore } from "@ocas/core";
|
|
8
8
|
import { createFsStore, createSqliteVarStore } from "@ocas/fs";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
CasRef,
|
|
11
|
+
StepStartPayload,
|
|
12
|
+
ThreadId,
|
|
13
|
+
ThreadIndexEntry,
|
|
14
|
+
ThreadsIndex,
|
|
15
|
+
TurnNodePayload,
|
|
16
|
+
} from "@united-workforce/protocol";
|
|
10
17
|
import { parseThreadsIndex } from "@united-workforce/protocol";
|
|
11
18
|
import { parse } from "yaml";
|
|
12
19
|
|
|
@@ -20,6 +27,232 @@ export const REGISTRY_VAR_PREFIX = "@uwf/registry/";
|
|
|
20
27
|
/** Variable name prefix for active thread entries (`@uwf/thread/<thread-id>`). */
|
|
21
28
|
export const THREAD_VAR_PREFIX = "@uwf/thread/";
|
|
22
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Variable name prefix for the in-flight turn list of a running step
|
|
32
|
+
* (`@uwf/active-turns/<thread-id>/<role>`). Phase 2 of the realtime-turns RFC
|
|
33
|
+
* (#398): broker-step appends each assistant turn hash here as it arrives, so
|
|
34
|
+
* an independent process can observe a step's progress mid-flight. The var is a
|
|
35
|
+
* mutable head pointer at an immutable CAS array node; it is cleared at the
|
|
36
|
+
* start of each step and deleted once the turns are solidified into the
|
|
37
|
+
* step's immutable `detail.turns`.
|
|
38
|
+
*
|
|
39
|
+
* @deprecated Phase 2 (#419) — role-keyed vars replaced by thread-keyed:
|
|
40
|
+
* - `@uwf/active-step/<threadId>` — current in-flight step-start hash
|
|
41
|
+
* - `@uwf/active-turn-head/<threadId>` — head of the turn chain
|
|
42
|
+
*/
|
|
43
|
+
export const ACTIVE_TURNS_VAR_PREFIX = "@uwf/active-turns/";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Schema for the active-turns list node: a bare ordered array of turn-hash
|
|
47
|
+
* `ocas_ref`s. Because an ocas variable is keyed by `(name, schema)` where the
|
|
48
|
+
* schema is the pointed-at node's `type`, this schema must be stable so that
|
|
49
|
+
* re-pointing the var (append), removing it (clear/solidify), and listing it by
|
|
50
|
+
* exact name all address the same variable. The hash is content-addressed and
|
|
51
|
+
* therefore identical across processes.
|
|
52
|
+
*
|
|
53
|
+
* @deprecated Phase 2 (#419) — role-keyed vars replaced by thread-keyed vars.
|
|
54
|
+
*/
|
|
55
|
+
export const ACTIVE_TURNS_LIST_SCHEMA = {
|
|
56
|
+
title: "uwf-active-turns",
|
|
57
|
+
type: "array" as const,
|
|
58
|
+
items: { type: "string" as const, format: "ocas_ref" },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build the active-turns variable name for a `(threadId, role)` pair.
|
|
63
|
+
*
|
|
64
|
+
* @deprecated Phase 2 (#419) — role-keyed vars replaced by thread-keyed vars.
|
|
65
|
+
*/
|
|
66
|
+
export function activeTurnsVarName(threadId: ThreadId, role: string): string {
|
|
67
|
+
return `${ACTIVE_TURNS_VAR_PREFIX}${threadId}/${role}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Register (idempotently) and return the CAS schema hash for the active-turns
|
|
72
|
+
* list node. Used both as the array node's type and — implicitly — as the
|
|
73
|
+
* variable's schema key.
|
|
74
|
+
*
|
|
75
|
+
* @deprecated Phase 2 (#419) — role-keyed vars replaced by thread-keyed vars.
|
|
76
|
+
*/
|
|
77
|
+
export function activeTurnsListSchemaHash(store: Store): Hash {
|
|
78
|
+
return putSchema(store, ACTIVE_TURNS_LIST_SCHEMA);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Read the ordered turn-hash list currently pointed at by
|
|
83
|
+
* `@uwf/active-turns/<threadId>/<role>`. Returns `[]` when the var does not
|
|
84
|
+
* exist (no turns appended yet, or already solidified/cleared).
|
|
85
|
+
*
|
|
86
|
+
* @deprecated Phase 2 (#419) — use `turnsOfStep()` with thread-keyed vars instead.
|
|
87
|
+
*/
|
|
88
|
+
export function readActiveTurns(store: Store, threadId: ThreadId, role: string): CasRef[] {
|
|
89
|
+
const name = activeTurnsVarName(threadId, role);
|
|
90
|
+
const vars = store.var.list({ exactName: name });
|
|
91
|
+
const v = vars[0];
|
|
92
|
+
if (v === undefined) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const node = store.cas.get(v.value as CasRef);
|
|
96
|
+
if (node === null || !Array.isArray(node.payload)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return node.payload as CasRef[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Discover every in-flight role for a thread by scanning the
|
|
104
|
+
* `@uwf/active-turns/<threadId>/` var namespace. Returns each role whose active
|
|
105
|
+
* var currently holds a **non-empty** ordered turn-hash list, in var-creation
|
|
106
|
+
* order (a chronological proxy for "which in-flight step started first"). Used by
|
|
107
|
+
* `step turns` to append the running step(s) to the whole-chain panorama — the
|
|
108
|
+
* in-flight step has no settled StepNode hash, so it is found via its
|
|
109
|
+
* `(threadId, role)` active var rather than the chain.
|
|
110
|
+
*
|
|
111
|
+
* @deprecated Phase 2 (#419) — use `getActiveStep()` with thread-keyed vars instead.
|
|
112
|
+
*/
|
|
113
|
+
export function readActiveTurnRoles(
|
|
114
|
+
store: Store,
|
|
115
|
+
threadId: ThreadId,
|
|
116
|
+
): { role: string; turns: CasRef[] }[] {
|
|
117
|
+
const prefix = `${ACTIVE_TURNS_VAR_PREFIX}${threadId}/`;
|
|
118
|
+
const vars = store.var.list({ namePrefix: prefix });
|
|
119
|
+
const sorted = [...vars].sort((a, b) => a.created - b.created);
|
|
120
|
+
const result: { role: string; turns: CasRef[] }[] = [];
|
|
121
|
+
for (const v of sorted) {
|
|
122
|
+
const role = v.name.slice(prefix.length);
|
|
123
|
+
if (role === "" || role.includes("/")) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const node = store.cas.get(v.value as CasRef);
|
|
127
|
+
if (node === null || !Array.isArray(node.payload)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const turns = node.payload as CasRef[];
|
|
131
|
+
if (turns.length === 0) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
result.push({ role, turns });
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Append a turn hash to `@uwf/active-turns/<threadId>/<role>` (read-modify-write
|
|
141
|
+
* on the array node, then re-point the var). The var is a single mutable
|
|
142
|
+
* pointer re-pointed on each append — not one var per turn. Returns the full
|
|
143
|
+
* updated list.
|
|
144
|
+
*
|
|
145
|
+
* @deprecated Phase 2 (#419) — turns now written directly with prev+owner chain.
|
|
146
|
+
*/
|
|
147
|
+
export function appendActiveTurn(
|
|
148
|
+
store: Store,
|
|
149
|
+
threadId: ThreadId,
|
|
150
|
+
role: string,
|
|
151
|
+
turnHash: CasRef,
|
|
152
|
+
): CasRef[] {
|
|
153
|
+
const name = activeTurnsVarName(threadId, role);
|
|
154
|
+
const current = readActiveTurns(store, threadId, role);
|
|
155
|
+
const next = [...current, turnHash];
|
|
156
|
+
const schemaHash = activeTurnsListSchemaHash(store);
|
|
157
|
+
const listHash = store.cas.put(schemaHash, next) as CasRef;
|
|
158
|
+
store.var.set(name, listHash);
|
|
159
|
+
return next;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Clear (delete) the `@uwf/active-turns/<threadId>/<role>` pointer. Removing a
|
|
164
|
+
* missing var is a no-op, so this is safe to call at the start of a clean run.
|
|
165
|
+
* Targets the exact `(threadId, role)` var only — concurrent active vars for
|
|
166
|
+
* other roles/threads are untouched.
|
|
167
|
+
*
|
|
168
|
+
* @deprecated Phase 2 (#419) — turns now written directly with prev+owner chain.
|
|
169
|
+
*/
|
|
170
|
+
export function clearActiveTurns(store: Store, threadId: ThreadId, role: string): void {
|
|
171
|
+
const name = activeTurnsVarName(threadId, role);
|
|
172
|
+
store.var.remove(name);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Thread-keyed Active Vars (Phase 2 #419) ──────────────────────────
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Variable name prefix for in-flight step (`@uwf/active-step/<thread-id>`).
|
|
179
|
+
* Phase 2 (#419): points to the current step-start hash while a step is in-flight.
|
|
180
|
+
* Cleared when the step completes (step-complete written).
|
|
181
|
+
*/
|
|
182
|
+
export const ACTIVE_STEP_VAR_PREFIX = "@uwf/active-step/";
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Variable name prefix for the turn chain head (`@uwf/active-turn-head/<thread-id>`).
|
|
186
|
+
* Phase 2 (#419): points to the most recent turn hash. Updated as each turn arrives.
|
|
187
|
+
* Remains after step completion (turns are immutable).
|
|
188
|
+
*/
|
|
189
|
+
export const ACTIVE_TURN_HEAD_VAR_PREFIX = "@uwf/active-turn-head/";
|
|
190
|
+
|
|
191
|
+
/** Build the active-step variable name for a thread. */
|
|
192
|
+
export function activeStepVarName(threadId: ThreadId): string {
|
|
193
|
+
return `${ACTIVE_STEP_VAR_PREFIX}${threadId}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Build the active-turn-head variable name for a thread. */
|
|
197
|
+
export function activeTurnHeadVarName(threadId: ThreadId): string {
|
|
198
|
+
return `${ACTIVE_TURN_HEAD_VAR_PREFIX}${threadId}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get the current in-flight step-start hash for a thread, or null if no step
|
|
203
|
+
* is in-flight. Phase 2 (#419): thread-keyed, not role-keyed.
|
|
204
|
+
*/
|
|
205
|
+
export function getActiveStep(store: Store, threadId: ThreadId): CasRef | null {
|
|
206
|
+
const name = activeStepVarName(threadId);
|
|
207
|
+
const vars = store.var.list({ exactName: name });
|
|
208
|
+
const v = vars[0];
|
|
209
|
+
if (v === undefined) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return v.value as CasRef;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Set the in-flight step-start hash for a thread. Called at step start.
|
|
217
|
+
* Phase 2 (#419): thread-keyed, not role-keyed.
|
|
218
|
+
*/
|
|
219
|
+
export function setActiveStep(store: Store, threadId: ThreadId, stepStartHash: CasRef): void {
|
|
220
|
+
const name = activeStepVarName(threadId);
|
|
221
|
+
store.var.set(name, stepStartHash);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Clear the in-flight step-start for a thread. Called at step completion.
|
|
226
|
+
* Phase 2 (#419): thread-keyed, not role-keyed.
|
|
227
|
+
*/
|
|
228
|
+
export function clearActiveStep(store: Store, threadId: ThreadId): void {
|
|
229
|
+
const name = activeStepVarName(threadId);
|
|
230
|
+
store.var.remove(name);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the current turn chain head hash for a thread, or null if no turns exist.
|
|
235
|
+
* Phase 2 (#419): thread-keyed, not role-keyed.
|
|
236
|
+
*/
|
|
237
|
+
export function getActiveTurnHead(store: Store, threadId: ThreadId): CasRef | null {
|
|
238
|
+
const name = activeTurnHeadVarName(threadId);
|
|
239
|
+
const vars = store.var.list({ exactName: name });
|
|
240
|
+
const v = vars[0];
|
|
241
|
+
if (v === undefined) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
return v.value as CasRef;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Set the turn chain head hash for a thread. Called after each turn is written.
|
|
249
|
+
* Phase 2 (#419): thread-keyed, not role-keyed.
|
|
250
|
+
*/
|
|
251
|
+
export function setActiveTurnHead(store: Store, threadId: ThreadId, turnHash: CasRef): void {
|
|
252
|
+
const name = activeTurnHeadVarName(threadId);
|
|
253
|
+
store.var.set(name, turnHash);
|
|
254
|
+
}
|
|
255
|
+
|
|
23
256
|
/** A workflow entry discovered from the project-local .workflows/ (primary) or .workflow/ (legacy) directory. */
|
|
24
257
|
export type ProjectWorkflowEntry = {
|
|
25
258
|
/** Workflow name (from YAML `name` field, equals filename stem). */
|
|
@@ -567,3 +800,65 @@ export function migrateHistoryVarsToThreadVars(varStore: VarStore): void {
|
|
|
567
800
|
varStore.remove(v.name);
|
|
568
801
|
}
|
|
569
802
|
}
|
|
803
|
+
|
|
804
|
+
// ── Turn Chain Functions (Phase 1) ─────────────────────────────────
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Write a step-start node to CAS. Returns the CAS hash.
|
|
808
|
+
*/
|
|
809
|
+
export function writeStepStart(uwfStore: UwfStore, payload: StepStartPayload): CasRef {
|
|
810
|
+
const hash = uwfStore.store.cas.put(uwfStore.schemas.stepStart, payload);
|
|
811
|
+
return hash as CasRef;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Write a turn node to CAS. Returns the CAS hash.
|
|
816
|
+
*/
|
|
817
|
+
export function writeTurnNode(uwfStore: UwfStore, payload: TurnNodePayload): CasRef {
|
|
818
|
+
const hash = uwfStore.store.cas.put(uwfStore.schemas.turnNode, payload);
|
|
819
|
+
return hash as CasRef;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Walk the turn chain from head back to the first turn via `prev` pointers.
|
|
824
|
+
* Returns turns in chronological order (oldest first).
|
|
825
|
+
*/
|
|
826
|
+
export function walkTurnChain(uwfStore: UwfStore, headHash: CasRef): CasRef[] {
|
|
827
|
+
const chain: CasRef[] = [];
|
|
828
|
+
let currentHash: CasRef | null = headHash;
|
|
829
|
+
|
|
830
|
+
while (currentHash !== null) {
|
|
831
|
+
chain.push(currentHash);
|
|
832
|
+
const node = uwfStore.store.cas.get(currentHash);
|
|
833
|
+
if (node === null) {
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
const payload = node.payload as TurnNodePayload | { prev?: CasRef | null };
|
|
837
|
+
currentHash = payload.prev ?? null;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Reverse to get chronological order (oldest first)
|
|
841
|
+
return chain.reverse();
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Get turns belonging to a specific step-start by filtering the turn chain
|
|
846
|
+
* via the `owner` field. Returns turns in chronological order (oldest first).
|
|
847
|
+
*/
|
|
848
|
+
export function turnsOfStep(uwfStore: UwfStore, headHash: CasRef, stepStartHash: CasRef): CasRef[] {
|
|
849
|
+
const allTurns = walkTurnChain(uwfStore, headHash);
|
|
850
|
+
const result: CasRef[] = [];
|
|
851
|
+
|
|
852
|
+
for (const turnHash of allTurns) {
|
|
853
|
+
const node = uwfStore.store.cas.get(turnHash);
|
|
854
|
+
if (node === null) {
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const payload = node.payload as TurnNodePayload | { owner?: CasRef | null };
|
|
858
|
+
if (payload.owner === stepStartHash) {
|
|
859
|
+
result.push(turnHash);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
return result;
|
|
864
|
+
}
|
package/src/text-renderers.ts
CHANGED
|
@@ -81,6 +81,13 @@ type ThreadStopPayload = {
|
|
|
81
81
|
stopped: boolean;
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
+
type StepDetailUsage = {
|
|
85
|
+
turns: number;
|
|
86
|
+
inputTokens: number;
|
|
87
|
+
outputTokens: number;
|
|
88
|
+
duration: number;
|
|
89
|
+
};
|
|
90
|
+
|
|
84
91
|
type StepDetailPayload = {
|
|
85
92
|
hash: string;
|
|
86
93
|
role: string;
|
|
@@ -89,8 +96,10 @@ type StepDetailPayload = {
|
|
|
89
96
|
startedAtMs: number | null;
|
|
90
97
|
completedAtMs: number | null;
|
|
91
98
|
durationMs: number | null;
|
|
99
|
+
usage: StepDetailUsage | null;
|
|
92
100
|
frontmatter: Record<string, unknown>;
|
|
93
101
|
turns: Array<{ role: string; content: string; timestamp: number | null }>;
|
|
102
|
+
detail: Record<string, unknown> | null;
|
|
94
103
|
};
|
|
95
104
|
|
|
96
105
|
function asObject(data: unknown): Record<string, unknown> {
|
|
@@ -223,15 +232,39 @@ export function renderStepList(data: unknown): string {
|
|
|
223
232
|
return lines.join("\n");
|
|
224
233
|
}
|
|
225
234
|
|
|
235
|
+
function renderUsageLine(usage: unknown): string | null {
|
|
236
|
+
if (usage === null || usage === undefined || typeof usage !== "object") return null;
|
|
237
|
+
const u = usage as StepDetailUsage;
|
|
238
|
+
const inputTokens = typeof u.inputTokens === "number" ? u.inputTokens : 0;
|
|
239
|
+
const outputTokens = typeof u.outputTokens === "number" ? u.outputTokens : 0;
|
|
240
|
+
const turns = typeof u.turns === "number" ? u.turns : 0;
|
|
241
|
+
return `Usage ${inputTokens} in / ${outputTokens} out / ${turns} turns`;
|
|
242
|
+
}
|
|
243
|
+
|
|
226
244
|
export function renderStepShow(data: unknown): string {
|
|
227
245
|
const p = asObject(data) as Partial<StepDetailPayload>;
|
|
228
|
-
|
|
246
|
+
const lines: string[] = [
|
|
229
247
|
`Step ${asString(p.hash)}`,
|
|
230
248
|
`Role ${asString(p.role)}`,
|
|
231
249
|
`Agent ${asString(p.agent)}`,
|
|
232
250
|
`Status ${asString(p.status)}`,
|
|
233
251
|
`Duration ${formatDuration(p.durationMs)}`,
|
|
234
|
-
]
|
|
252
|
+
];
|
|
253
|
+
const usageLine = renderUsageLine(p.usage);
|
|
254
|
+
if (usageLine !== null) lines.push(usageLine);
|
|
255
|
+
const turnsArr = Array.isArray(p.turns) ? p.turns : [];
|
|
256
|
+
if (turnsArr.length > 0) {
|
|
257
|
+
lines.push(`Turns ${turnsArr.length}`);
|
|
258
|
+
lines.push("");
|
|
259
|
+
lines.push("--- Content ---");
|
|
260
|
+
for (const turn of turnsArr) {
|
|
261
|
+
const t = asObject(turn);
|
|
262
|
+
const role = asString(t.role, "assistant");
|
|
263
|
+
const content = typeof t.content === "string" ? t.content : "";
|
|
264
|
+
lines.push(`[${role}] ${content}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return lines.join("\n");
|
|
235
268
|
}
|
|
236
269
|
|
|
237
270
|
export function renderThreadCancel(data: unknown): string {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-json-roundtrip.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/adapter-json-roundtrip.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { putSchema } from "@ocas/core";
|
|
7
|
-
import { openStore } from "@ocas/fs";
|
|
8
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
9
|
-
import { registerUwfSchemas } from "../schemas.js";
|
|
10
|
-
import { seedThreads } from "./thread-test-helpers.js";
|
|
11
|
-
// ── schemas ──────────────────────────────────────────────────────────────────
|
|
12
|
-
const OUTPUT_SCHEMA = {
|
|
13
|
-
type: "object",
|
|
14
|
-
properties: {
|
|
15
|
-
$status: { type: "string", enum: ["done", "failed"] },
|
|
16
|
-
result: { type: "string" },
|
|
17
|
-
},
|
|
18
|
-
required: ["$status"],
|
|
19
|
-
additionalProperties: false,
|
|
20
|
-
};
|
|
21
|
-
// ── fixture ──────────────────────────────────────────────────────────────────
|
|
22
|
-
let tmpDir;
|
|
23
|
-
let savedOcasHome;
|
|
24
|
-
beforeEach(async () => {
|
|
25
|
-
savedOcasHome = process.env.OCAS_HOME;
|
|
26
|
-
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-roundtrip-test-"));
|
|
27
|
-
});
|
|
28
|
-
afterEach(async () => {
|
|
29
|
-
if (savedOcasHome === undefined) {
|
|
30
|
-
delete process.env.OCAS_HOME;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
process.env.OCAS_HOME = savedOcasHome;
|
|
34
|
-
}
|
|
35
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
36
|
-
});
|
|
37
|
-
describe("C1: adapter JSON round-trip integration", () => {
|
|
38
|
-
test("mock agent outputs JSON, CLI parses it and updates thread head in CAS", async () => {
|
|
39
|
-
// 1. Set up CAS store with workflow, start node, and output schema
|
|
40
|
-
const casDir = join(tmpDir, "cas");
|
|
41
|
-
await mkdir(casDir, { recursive: true });
|
|
42
|
-
const store = await openStore(casDir);
|
|
43
|
-
const schemas = await registerUwfSchemas(store);
|
|
44
|
-
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
|
|
45
|
-
const workflowHash = await store.cas.put(schemas.workflow, {
|
|
46
|
-
name: "test-roundtrip",
|
|
47
|
-
description: "roundtrip integration test",
|
|
48
|
-
roles: {
|
|
49
|
-
worker: {
|
|
50
|
-
description: "Worker role",
|
|
51
|
-
goal: "Do work",
|
|
52
|
-
capabilities: [],
|
|
53
|
-
procedure: "work",
|
|
54
|
-
output: "result",
|
|
55
|
-
frontmatter: outputSchemaHash,
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
graph: {
|
|
59
|
-
$START: {
|
|
60
|
-
new: { role: "worker", prompt: "Do the work", location: null },
|
|
61
|
-
resume: { role: "worker", prompt: "Resume the work", location: null },
|
|
62
|
-
},
|
|
63
|
-
worker: { done: { role: "$END", prompt: "completed", location: null } },
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
const startHash = await store.cas.put(schemas.startNode, {
|
|
67
|
-
workflow: workflowHash,
|
|
68
|
-
prompt: "Test round-trip task",
|
|
69
|
-
});
|
|
70
|
-
process.env.OCAS_HOME = casDir;
|
|
71
|
-
const threadId = "01ROUNDTRIPTEST0000000000";
|
|
72
|
-
await seedThreads(tmpDir, { [threadId]: startHash });
|
|
73
|
-
// 2. Pre-create CAS nodes that the mock agent would produce
|
|
74
|
-
const outputHash = await store.cas.put(outputSchemaHash, {
|
|
75
|
-
$status: "done",
|
|
76
|
-
result: "test-ok",
|
|
77
|
-
});
|
|
78
|
-
// Use text schema for detail (simple placeholder)
|
|
79
|
-
const detailHash = await store.cas.put(schemas.text, "mock detail");
|
|
80
|
-
const startedAtMs = 1716600000000;
|
|
81
|
-
const completedAtMs = 1716600001500;
|
|
82
|
-
const stepHash = await store.cas.put(schemas.stepNode, {
|
|
83
|
-
start: startHash,
|
|
84
|
-
prev: null,
|
|
85
|
-
role: "worker",
|
|
86
|
-
output: outputHash,
|
|
87
|
-
detail: detailHash,
|
|
88
|
-
agent: "uwf-mock",
|
|
89
|
-
edgePrompt: "Do the work",
|
|
90
|
-
startedAtMs,
|
|
91
|
-
completedAtMs,
|
|
92
|
-
cwd: tmpDir,
|
|
93
|
-
});
|
|
94
|
-
// 3. Create a minimal mock agent shell script that just outputs JSON
|
|
95
|
-
// The step node is already in CAS — the agent just needs to print the JSON line
|
|
96
|
-
const mockAgentPath = join(tmpDir, "mock-agent.sh");
|
|
97
|
-
const adapterJson = JSON.stringify({
|
|
98
|
-
stepHash,
|
|
99
|
-
detailHash,
|
|
100
|
-
role: "worker",
|
|
101
|
-
frontmatter: { $status: "done", result: "test-ok" },
|
|
102
|
-
body: "",
|
|
103
|
-
startedAtMs,
|
|
104
|
-
completedAtMs,
|
|
105
|
-
});
|
|
106
|
-
await writeFile(mockAgentPath, `#!/bin/sh\necho '${adapterJson}'\n`, { mode: 0o755 });
|
|
107
|
-
// 4. Write config.yaml
|
|
108
|
-
const configPath = join(tmpDir, "config.yaml");
|
|
109
|
-
await writeFile(configPath, `defaultAgent: uwf-hermes\nagentOverrides: null\nagents:\n uwf-hermes:\n command: uwf-hermes\n`);
|
|
110
|
-
// 5. Run CLI with agent override pointing to our mock
|
|
111
|
-
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
|
|
112
|
-
let stdout;
|
|
113
|
-
let stderr;
|
|
114
|
-
let exitCode;
|
|
115
|
-
try {
|
|
116
|
-
stdout = execFileSync(process.execPath, [cliPath, "--format", "raw-json", "thread", "exec", threadId, "--agent", mockAgentPath], {
|
|
117
|
-
encoding: "utf8",
|
|
118
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
119
|
-
env: {
|
|
120
|
-
...process.env,
|
|
121
|
-
UWF_HOME: tmpDir,
|
|
122
|
-
OCAS_HOME: casDir,
|
|
123
|
-
},
|
|
124
|
-
cwd: tmpDir,
|
|
125
|
-
timeout: 30000,
|
|
126
|
-
});
|
|
127
|
-
stderr = "";
|
|
128
|
-
exitCode = 0;
|
|
129
|
-
}
|
|
130
|
-
catch (e) {
|
|
131
|
-
const err = e;
|
|
132
|
-
stdout = err.stdout ?? "";
|
|
133
|
-
stderr = err.stderr ?? "";
|
|
134
|
-
exitCode = err.status ?? 1;
|
|
135
|
-
}
|
|
136
|
-
// 6. Verify
|
|
137
|
-
if (exitCode !== 0) {
|
|
138
|
-
throw new Error(`CLI exited with code ${exitCode}\nstdout: ${stdout}\nstderr: ${stderr}`);
|
|
139
|
-
}
|
|
140
|
-
// Parse CLI output (raw-json envelope value: { threadId, workflowHash, steps: [...] })
|
|
141
|
-
const cliOutput = JSON.parse(stdout.trim());
|
|
142
|
-
expect(cliOutput).toHaveProperty("threadId", threadId);
|
|
143
|
-
expect(cliOutput.steps).toHaveLength(1);
|
|
144
|
-
const firstStep = cliOutput.steps[0];
|
|
145
|
-
expect(firstStep).toHaveProperty("head", stepHash);
|
|
146
|
-
expect(firstStep.head).toMatch(/^[0-9A-HJ-NP-TV-Z]{13}$/);
|
|
147
|
-
// Verify the CAS step node exists and has correct metadata
|
|
148
|
-
const storeAfter = await openStore(casDir);
|
|
149
|
-
const stepNode = storeAfter.cas.get(firstStep.head);
|
|
150
|
-
expect(stepNode).not.toBeNull();
|
|
151
|
-
const payload = stepNode.payload;
|
|
152
|
-
expect(payload.role).toBe("worker");
|
|
153
|
-
expect(payload.agent).toBe("uwf-mock");
|
|
154
|
-
expect(payload.startedAtMs).toBe(1716600000000);
|
|
155
|
-
expect(payload.completedAtMs).toBe(1716600001500);
|
|
156
|
-
expect(payload.output).toBe(outputHash);
|
|
157
|
-
expect(payload.detail).toBe(detailHash);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
//# sourceMappingURL=adapter-json-roundtrip.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-json-roundtrip.test.js","sourceRoot":"","sources":["../../src/__tests__/adapter-json-roundtrip.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-agent-json.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/spawn-agent-json.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
-
/**
|
|
3
|
-
* B-group tests: validate JSON parsing logic used by spawnAgent.
|
|
4
|
-
*
|
|
5
|
-
* We test the parsing logic inline since spawnAgent is a private function.
|
|
6
|
-
* These tests verify the contract: last line of stdout must be valid JSON
|
|
7
|
-
* with a valid stepHash CasRef.
|
|
8
|
-
*/
|
|
9
|
-
const CASREF_PATTERN = /^[0-9A-HJ-NP-TV-Z]{13}$/;
|
|
10
|
-
function isCasRef(s) {
|
|
11
|
-
return CASREF_PATTERN.test(s);
|
|
12
|
-
}
|
|
13
|
-
function parseAgentStdout(stdout) {
|
|
14
|
-
const line = stdout.trim().split("\n").pop()?.trim() ?? "";
|
|
15
|
-
let parsed;
|
|
16
|
-
try {
|
|
17
|
-
parsed = JSON.parse(line);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
throw new Error(`agent stdout last line is not valid JSON: ${line || "(empty)"}`);
|
|
21
|
-
}
|
|
22
|
-
const obj = parsed;
|
|
23
|
-
if (typeof obj !== "object" ||
|
|
24
|
-
obj === null ||
|
|
25
|
-
typeof obj.stepHash !== "string" ||
|
|
26
|
-
!isCasRef(obj.stepHash)) {
|
|
27
|
-
throw new Error(`agent stdout JSON missing valid stepHash: ${line}`);
|
|
28
|
-
}
|
|
29
|
-
return obj;
|
|
30
|
-
}
|
|
31
|
-
const VALID_OUTPUT = {
|
|
32
|
-
stepHash: "0123456789ABC",
|
|
33
|
-
detailHash: "DEFGH12345678",
|
|
34
|
-
role: "planner",
|
|
35
|
-
frontmatter: { $status: "ready", plan: "somehash" },
|
|
36
|
-
body: "Plan body",
|
|
37
|
-
startedAtMs: 1000,
|
|
38
|
-
completedAtMs: 2000,
|
|
39
|
-
};
|
|
40
|
-
describe("spawnAgent JSON parsing", () => {
|
|
41
|
-
test("B1. parses valid JSON from agent stdout", () => {
|
|
42
|
-
const stdout = `${JSON.stringify(VALID_OUTPUT)}\n`;
|
|
43
|
-
const result = parseAgentStdout(stdout);
|
|
44
|
-
expect(result.stepHash).toBe("0123456789ABC");
|
|
45
|
-
expect(result.detailHash).toBe("DEFGH12345678");
|
|
46
|
-
expect(result.role).toBe("planner");
|
|
47
|
-
expect(result.frontmatter).toEqual({ $status: "ready", plan: "somehash" });
|
|
48
|
-
expect(result.body).toBe("Plan body");
|
|
49
|
-
expect(result.startedAtMs).toBe(1000);
|
|
50
|
-
expect(result.completedAtMs).toBe(2000);
|
|
51
|
-
});
|
|
52
|
-
test("B2. extracts stepHash for head pointer", () => {
|
|
53
|
-
const stdout = `${JSON.stringify(VALID_OUTPUT)}\n`;
|
|
54
|
-
const result = parseAgentStdout(stdout);
|
|
55
|
-
expect(result.stepHash).toBe("0123456789ABC");
|
|
56
|
-
expect(isCasRef(result.stepHash)).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
test("B3. handles debug lines before JSON", () => {
|
|
59
|
-
const debugLines = "[debug] loading context...\n[debug] running agent...\n";
|
|
60
|
-
const stdout = `${debugLines + JSON.stringify(VALID_OUTPUT)}\n`;
|
|
61
|
-
const result = parseAgentStdout(stdout);
|
|
62
|
-
expect(result.stepHash).toBe("0123456789ABC");
|
|
63
|
-
});
|
|
64
|
-
test("B4. rejects non-JSON last line", () => {
|
|
65
|
-
const stdout = "not-json-at-all\n";
|
|
66
|
-
expect(() => parseAgentStdout(stdout)).toThrow("not valid JSON");
|
|
67
|
-
});
|
|
68
|
-
test("B5. rejects JSON missing stepHash", () => {
|
|
69
|
-
const incomplete = { detailHash: "DEFGH12345678", role: "planner" };
|
|
70
|
-
const stdout = `${JSON.stringify(incomplete)}\n`;
|
|
71
|
-
expect(() => parseAgentStdout(stdout)).toThrow("missing valid stepHash");
|
|
72
|
-
});
|
|
73
|
-
test("B6. rejects JSON with invalid stepHash", () => {
|
|
74
|
-
const bad = { ...VALID_OUTPUT, stepHash: "not-a-hash" };
|
|
75
|
-
const stdout = `${JSON.stringify(bad)}\n`;
|
|
76
|
-
expect(() => parseAgentStdout(stdout)).toThrow("missing valid stepHash");
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
//# sourceMappingURL=spawn-agent-json.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-agent-json.test.js","sourceRoot":"","sources":["../../src/__tests__/spawn-agent-json.test.ts"],"names":[],"mappings":"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"}
|