@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
|
@@ -299,7 +299,10 @@ function runUwf(
|
|
|
299
299
|
|
|
300
300
|
// ── Group 1: CLI argument validation ───────────────────────────────────────
|
|
301
301
|
|
|
302
|
-
|
|
302
|
+
// Phase 3 (#380): `uwf step ask` is disabled until Phase 4 once the Sumeru
|
|
303
|
+
// broker exposes session-fork APIs. The legacy spawn-agent path was removed,
|
|
304
|
+
// so these tests are skipped until the Phase 4 broker fork primitive lands.
|
|
305
|
+
describe.skip("uwf step ask - CLI argument validation", () => {
|
|
303
306
|
test("1.1 missing step-hash exits non-zero", async () => {
|
|
304
307
|
const { casDir } = await setupAskFixture();
|
|
305
308
|
const result = runUwf(["step", "ask"], casDir);
|
|
@@ -325,7 +328,7 @@ describe("uwf step ask - CLI argument validation", () => {
|
|
|
325
328
|
|
|
326
329
|
// ── Group 2: CAS validation errors ────────────────────────────────────────
|
|
327
330
|
|
|
328
|
-
describe("uwf step ask - CAS validation errors", () => {
|
|
331
|
+
describe.skip("uwf step ask - CAS validation errors", () => {
|
|
329
332
|
test("2.1 non-existent CAS hash exits non-zero with 'not found'", async () => {
|
|
330
333
|
const { casDir, mockAgentPath } = await setupAskFixture();
|
|
331
334
|
const result = runUwf(
|
|
@@ -360,7 +363,7 @@ describe("uwf step ask - CAS validation errors", () => {
|
|
|
360
363
|
|
|
361
364
|
// ── Group 3: Successful ask (core behavior) ───────────────────────────────
|
|
362
365
|
|
|
363
|
-
describe("uwf step ask - successful ask (core)", () => {
|
|
366
|
+
describe.skip("uwf step ask - successful ask (core)", () => {
|
|
364
367
|
test("3.1 stdout contains agent's response text", async () => {
|
|
365
368
|
const { casDir, stepHash, mockAgentPath } = await setupAskFixture();
|
|
366
369
|
const result = runUwf(
|
|
@@ -437,7 +440,7 @@ describe("uwf step ask - successful ask (core)", () => {
|
|
|
437
440
|
|
|
438
441
|
// ── Group 4: Fork cache semantics ─────────────────────────────────────────
|
|
439
442
|
|
|
440
|
-
describe("uwf step ask - fork cache", { timeout: 15_000 }, () => {
|
|
443
|
+
describe.skip("uwf step ask - fork cache", { timeout: 15_000 }, () => {
|
|
441
444
|
test("4.1 first ask creates a fork session and caches it", async () => {
|
|
442
445
|
const { casDir, stepHash, mockAgentPath, forkSessionCapturePath } = await setupAskFixture();
|
|
443
446
|
|
|
@@ -545,7 +548,7 @@ describe("uwf step ask - fork cache", { timeout: 15_000 }, () => {
|
|
|
545
548
|
|
|
546
549
|
// ── Group 5: Fallback (agent has no fork support) ─────────────────────────
|
|
547
550
|
|
|
548
|
-
describe("uwf step ask - fallback path", () => {
|
|
551
|
+
describe.skip("uwf step ask - fallback path", () => {
|
|
549
552
|
test("5.1 fallback agent (no fork support) still answers via stdout", async () => {
|
|
550
553
|
// Use a fallback agent that ONLY supports `ask` mode without ever being asked
|
|
551
554
|
// to fork. The CLI should detect missing fork support and inject context instead.
|
|
@@ -648,7 +651,7 @@ esac
|
|
|
648
651
|
|
|
649
652
|
// ── Group 6: Agent resolution ─────────────────────────────────────────────
|
|
650
653
|
|
|
651
|
-
describe("uwf step ask - agent resolution", () => {
|
|
654
|
+
describe.skip("uwf step ask - agent resolution", () => {
|
|
652
655
|
test("6.1 without --agent flag, agent is resolved from step's agent field", async () => {
|
|
653
656
|
// Step's agent field points at mockAgentPath by default.
|
|
654
657
|
const { casDir, stepHash, modeCapturePath, promptCapturePath } = await setupAskFixture();
|
|
@@ -169,7 +169,7 @@ describe("cmdStepShow JSON serialization", () => {
|
|
|
169
169
|
expect(jsonOutput).toContain("\\n");
|
|
170
170
|
|
|
171
171
|
const parsed = JSON.parse(jsonOutput);
|
|
172
|
-
expect(parsed.turns[0].toolCalls[0].args).toContain("\n");
|
|
172
|
+
expect(parsed.detail.turns[0].toolCalls[0].args).toContain("\n");
|
|
173
173
|
});
|
|
174
174
|
|
|
175
175
|
test("escapes tabs in tool call args", async () => {
|
|
@@ -239,7 +239,7 @@ describe("cmdStepShow JSON serialization", () => {
|
|
|
239
239
|
|
|
240
240
|
expect(() => JSON.parse(jsonOutput)).not.toThrow();
|
|
241
241
|
const parsed = JSON.parse(jsonOutput);
|
|
242
|
-
expect(parsed.turns).toBeDefined();
|
|
242
|
+
expect(parsed.detail.turns).toBeDefined();
|
|
243
243
|
});
|
|
244
244
|
|
|
245
245
|
test("handles Unicode control characters", async () => {
|
|
@@ -291,7 +291,7 @@ describe("cmdStepShow JSON serialization", () => {
|
|
|
291
291
|
|
|
292
292
|
expect(() => JSON.parse(jsonOutput)).not.toThrow();
|
|
293
293
|
const parsed = JSON.parse(jsonOutput);
|
|
294
|
-
expect(parsed.turns).toHaveLength(2);
|
|
294
|
+
expect(parsed.detail.turns).toHaveLength(2);
|
|
295
295
|
});
|
|
296
296
|
|
|
297
297
|
test("YAML output format is unaffected", async () => {
|
|
@@ -333,7 +333,7 @@ describe("cmdStepShow JSON serialization", () => {
|
|
|
333
333
|
|
|
334
334
|
expect(() => JSON.parse(jsonOutput)).not.toThrow();
|
|
335
335
|
const parsed = JSON.parse(jsonOutput);
|
|
336
|
-
expect(parsed.turns).toBeDefined();
|
|
336
|
+
expect(parsed.detail.turns).toBeDefined();
|
|
337
337
|
});
|
|
338
338
|
|
|
339
339
|
test("handles large step with multiple tool calls", async () => {
|
|
@@ -369,6 +369,6 @@ describe("cmdStepShow JSON serialization", () => {
|
|
|
369
369
|
expect(() => JSON.parse(jsonOutput)).not.toThrow();
|
|
370
370
|
|
|
371
371
|
const parsed = JSON.parse(jsonOutput);
|
|
372
|
-
expect(parsed.turns).toHaveLength(25);
|
|
372
|
+
expect(parsed.detail.turns).toHaveLength(25);
|
|
373
373
|
});
|
|
374
374
|
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { bootstrap, type Hash, type JSONSchema, putSchema } from "@ocas/core";
|
|
5
|
+
import { openStore } from "@ocas/fs";
|
|
6
|
+
import type { CasRef, StepNodePayload } from "@united-workforce/protocol";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
8
|
+
import { cmdStepShow } from "../commands/step.js";
|
|
9
|
+
import { writeEnvelope } from "../format.js";
|
|
10
|
+
import { toStepDetailPayload } from "../output-mappers.js";
|
|
11
|
+
import { registerUwfSchemas } from "../schemas.js";
|
|
12
|
+
import { createUwfStore } from "../store.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Issue #403 — regression guard for the `step show` **text** path.
|
|
16
|
+
*
|
|
17
|
+
* PR #394 added the `--- Content ---` turn block (plus `Usage` / `Turns`) to
|
|
18
|
+
* `STEP_DETAIL_TEMPLATE` and the `toStepDetailPayload` mapper flattens
|
|
19
|
+
* `detail.turns` into a top-level `turns` array — but no test asserted that the
|
|
20
|
+
* rendered text actually contains the turn bodies. A stale build (e.g. the
|
|
21
|
+
* published `protocol@0.4.0`) or an accidental retarget to `detail.turns` would
|
|
22
|
+
* go undetected.
|
|
23
|
+
*
|
|
24
|
+
* This exercises the full path
|
|
25
|
+
* cmdStepShow → toStepDetailPayload → writeEnvelope(text) → renderEnvelopeText
|
|
26
|
+
* and asserts the rendered text contains `--- Content ---`, each turn's content
|
|
27
|
+
* substring, and the `Turns N` line. The sibling JSON contract lives in
|
|
28
|
+
* `step-show-json.test.ts`; the protocol-level template invariant lives in
|
|
29
|
+
* `packages/protocol/src/__tests__/output-templates-step-detail.test.ts`.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const TURN_SCHEMA: JSONSchema = {
|
|
33
|
+
title: "test-turn",
|
|
34
|
+
type: "object",
|
|
35
|
+
required: ["index", "role", "content"],
|
|
36
|
+
properties: {
|
|
37
|
+
index: { type: "integer" },
|
|
38
|
+
role: { type: "string", enum: ["assistant", "tool"] },
|
|
39
|
+
content: { type: "string" },
|
|
40
|
+
toolCalls: {
|
|
41
|
+
anyOf: [
|
|
42
|
+
{
|
|
43
|
+
type: "array",
|
|
44
|
+
items: {
|
|
45
|
+
type: "object",
|
|
46
|
+
required: ["name", "args"],
|
|
47
|
+
properties: {
|
|
48
|
+
name: { type: "string" },
|
|
49
|
+
args: { type: "string" },
|
|
50
|
+
},
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{ type: "null" },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const DETAIL_SCHEMA: JSONSchema = {
|
|
62
|
+
title: "test-detail",
|
|
63
|
+
type: "object",
|
|
64
|
+
required: ["turns"],
|
|
65
|
+
properties: {
|
|
66
|
+
turns: {
|
|
67
|
+
type: "array",
|
|
68
|
+
items: { type: "string", format: "ocas_ref" },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
additionalProperties: false,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
type TestSetup = {
|
|
75
|
+
store: Awaited<ReturnType<typeof openStore>>;
|
|
76
|
+
schemas: Awaited<ReturnType<typeof registerUwfSchemas>>;
|
|
77
|
+
turnType: Hash;
|
|
78
|
+
detailType: Hash;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
async function setupTest(casDir: string): Promise<TestSetup> {
|
|
82
|
+
const store = await openStore(casDir);
|
|
83
|
+
await bootstrap(store);
|
|
84
|
+
const schemas = await registerUwfSchemas(store);
|
|
85
|
+
const [turnType, detailType] = await Promise.all([
|
|
86
|
+
putSchema(store, TURN_SCHEMA),
|
|
87
|
+
putSchema(store, DETAIL_SCHEMA),
|
|
88
|
+
]);
|
|
89
|
+
return { store, schemas, turnType, detailType };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function createTestStep(
|
|
93
|
+
setup: TestSetup,
|
|
94
|
+
turnPayloads: Array<{
|
|
95
|
+
index: number;
|
|
96
|
+
role: string;
|
|
97
|
+
content: string;
|
|
98
|
+
toolCalls: Array<{ name: string; args: string }> | null;
|
|
99
|
+
}>,
|
|
100
|
+
usage: StepNodePayload["usage"],
|
|
101
|
+
): Promise<CasRef> {
|
|
102
|
+
const { store, schemas, turnType, detailType } = setup;
|
|
103
|
+
|
|
104
|
+
const turnHashes: CasRef[] = [];
|
|
105
|
+
for (const payload of turnPayloads) {
|
|
106
|
+
turnHashes.push(await store.cas.put(turnType, payload));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const detailHash = await store.cas.put(detailType, { turns: turnHashes });
|
|
110
|
+
const startHash = await store.cas.put(schemas.startNode, {
|
|
111
|
+
workflow: "0000000000000" as CasRef,
|
|
112
|
+
prompt: "test prompt",
|
|
113
|
+
cwd: "/tmp",
|
|
114
|
+
});
|
|
115
|
+
const outputHash = await store.cas.put(schemas.text, { $status: "reviewed" });
|
|
116
|
+
|
|
117
|
+
const stepPayload: StepNodePayload = {
|
|
118
|
+
prev: null,
|
|
119
|
+
start: startHash,
|
|
120
|
+
role: "reviewer",
|
|
121
|
+
agent: "claude-code",
|
|
122
|
+
output: outputHash,
|
|
123
|
+
detail: detailHash,
|
|
124
|
+
edgePrompt: "",
|
|
125
|
+
startedAtMs: 1_000_000,
|
|
126
|
+
completedAtMs: 1_137_400,
|
|
127
|
+
assembledPrompt: null,
|
|
128
|
+
cwd: "/tmp",
|
|
129
|
+
usage,
|
|
130
|
+
previousAttempts: null,
|
|
131
|
+
};
|
|
132
|
+
return store.cas.put(schemas.stepNode, stepPayload);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Capture everything written to `process.stdout` while `fn` runs. */
|
|
136
|
+
async function captureStdout(fn: () => Promise<void>): Promise<string> {
|
|
137
|
+
const buf: string[] = [];
|
|
138
|
+
const spy = vi.spyOn(process.stdout, "write").mockImplementation(((
|
|
139
|
+
chunk: string | Uint8Array,
|
|
140
|
+
): boolean => {
|
|
141
|
+
buf.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
142
|
+
return true;
|
|
143
|
+
}) as typeof process.stdout.write);
|
|
144
|
+
try {
|
|
145
|
+
await fn();
|
|
146
|
+
} finally {
|
|
147
|
+
spy.mockRestore();
|
|
148
|
+
}
|
|
149
|
+
return buf.join("");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
describe("cmdStepShow text rendering (issue #403)", () => {
|
|
153
|
+
let testDir: string;
|
|
154
|
+
let casDir: string;
|
|
155
|
+
let originalEnv: string | undefined;
|
|
156
|
+
|
|
157
|
+
beforeEach(async () => {
|
|
158
|
+
testDir = await mkdtemp(join(tmpdir(), "uwf-step-show-text-"));
|
|
159
|
+
casDir = join(testDir, "cas");
|
|
160
|
+
await mkdir(casDir, { recursive: true });
|
|
161
|
+
originalEnv = process.env.OCAS_HOME;
|
|
162
|
+
process.env.OCAS_HOME = casDir;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
afterEach(async () => {
|
|
166
|
+
await rm(testDir, { recursive: true, force: true });
|
|
167
|
+
if (originalEnv === undefined) {
|
|
168
|
+
delete process.env.OCAS_HOME;
|
|
169
|
+
} else {
|
|
170
|
+
process.env.OCAS_HOME = originalEnv;
|
|
171
|
+
}
|
|
172
|
+
vi.restoreAllMocks();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("renders the --- Content --- block with each turn's role and content", async () => {
|
|
176
|
+
const setup = await setupTest(casDir);
|
|
177
|
+
const stepHash = await createTestStep(
|
|
178
|
+
setup,
|
|
179
|
+
[
|
|
180
|
+
{ index: 0, role: "assistant", content: "first turn body", toolCalls: null },
|
|
181
|
+
{ index: 1, role: "assistant", content: "second turn body", toolCalls: null },
|
|
182
|
+
],
|
|
183
|
+
{ turns: 9, inputTokens: 38612, outputTokens: 10584, duration: 137400 },
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const detail = await cmdStepShow(testDir, stepHash);
|
|
187
|
+
const uwf = await createUwfStore(testDir);
|
|
188
|
+
const out = await captureStdout(async () =>
|
|
189
|
+
writeEnvelope(toStepDetailPayload(stepHash, detail), "step-detail", {
|
|
190
|
+
format: "text",
|
|
191
|
+
store: uwf.store,
|
|
192
|
+
schemas: uwf.schemas,
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// Metadata header
|
|
197
|
+
expect(out).toContain(`Step ${stepHash}`);
|
|
198
|
+
expect(out).toContain("Role reviewer");
|
|
199
|
+
expect(out).toContain("Agent claude-code");
|
|
200
|
+
expect(out).toContain("Status reviewed");
|
|
201
|
+
expect(out).toContain("Duration 137.4s");
|
|
202
|
+
expect(out).toContain("Usage 38612 in / 10584 out / 9 turns");
|
|
203
|
+
|
|
204
|
+
// Turn-content block — the headline regression assertion
|
|
205
|
+
expect(out).toContain("Turns 2");
|
|
206
|
+
expect(out).toContain("--- Content ---");
|
|
207
|
+
expect(out).toContain("[assistant] first turn body");
|
|
208
|
+
expect(out).toContain("[assistant] second turn body");
|
|
209
|
+
|
|
210
|
+
// No JSON envelope leakage and a single trailing newline
|
|
211
|
+
expect(out).not.toContain('"type"');
|
|
212
|
+
expect(out).not.toContain("undefined");
|
|
213
|
+
expect(out.endsWith("\n")).toBe(true);
|
|
214
|
+
expect(out.endsWith("\n\n")).toBe(false);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("omits the Content block for a step with zero turns, without throwing", async () => {
|
|
218
|
+
const setup = await setupTest(casDir);
|
|
219
|
+
const stepHash = await createTestStep(setup, [], null);
|
|
220
|
+
|
|
221
|
+
const detail = await cmdStepShow(testDir, stepHash);
|
|
222
|
+
const uwf = await createUwfStore(testDir);
|
|
223
|
+
const out = await captureStdout(async () =>
|
|
224
|
+
writeEnvelope(toStepDetailPayload(stepHash, detail), "step-detail", {
|
|
225
|
+
format: "text",
|
|
226
|
+
store: uwf.store,
|
|
227
|
+
schemas: uwf.schemas,
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
expect(out).toContain("Role reviewer");
|
|
232
|
+
expect(out).not.toContain("--- Content ---");
|
|
233
|
+
expect(out).not.toMatch(/^Turns\s/m);
|
|
234
|
+
expect(out).not.toContain("undefined");
|
|
235
|
+
});
|
|
236
|
+
});
|