@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
|
@@ -78,9 +78,6 @@ afterEach(async () => {
|
|
|
78
78
|
async function writeMockConfig(mockDataFixture: string): Promise<void> {
|
|
79
79
|
const config = {
|
|
80
80
|
defaultAgent: "mock",
|
|
81
|
-
defaultModel: "test",
|
|
82
|
-
providers: {},
|
|
83
|
-
models: {},
|
|
84
81
|
agentOverrides: null,
|
|
85
82
|
agents: {
|
|
86
83
|
mock: {
|
|
@@ -107,7 +104,7 @@ async function addWorkflow(workflowFixture: string, workflowName: string): Promi
|
|
|
107
104
|
type ExecResult = { stdout: string; stderr: string; exitCode: number };
|
|
108
105
|
|
|
109
106
|
function runExec(threadId: string, count: number | null = null): ExecResult {
|
|
110
|
-
const args = [CLI_PATH, "thread", "exec", threadId];
|
|
107
|
+
const args = [CLI_PATH, "--format", "raw-json", "thread", "exec", threadId];
|
|
111
108
|
if (count !== null) {
|
|
112
109
|
args.push("--count", String(count));
|
|
113
110
|
}
|
|
@@ -135,7 +132,7 @@ function runResume(threadId: string, prompt: string): ExecResult {
|
|
|
135
132
|
try {
|
|
136
133
|
const stdout = execFileSync(
|
|
137
134
|
process.execPath,
|
|
138
|
-
[CLI_PATH, "thread", "resume", threadId, "-p", prompt],
|
|
135
|
+
[CLI_PATH, "--format", "raw-json", "thread", "resume", threadId, "-p", prompt],
|
|
139
136
|
{
|
|
140
137
|
encoding: "utf8",
|
|
141
138
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -165,12 +162,49 @@ type StepOutputJson = {
|
|
|
165
162
|
done: boolean;
|
|
166
163
|
};
|
|
167
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The new `thread exec` envelope value (under --format raw-json) is
|
|
167
|
+
* `{ threadId, workflowHash, steps: [...] }`. Tests still want the
|
|
168
|
+
* single-step shape, so we project each step entry back into the legacy
|
|
169
|
+
* StepOutputJson shape.
|
|
170
|
+
*/
|
|
171
|
+
type ThreadExecRawValue = {
|
|
172
|
+
threadId: string;
|
|
173
|
+
workflowHash: string;
|
|
174
|
+
steps: Array<{
|
|
175
|
+
head: string;
|
|
176
|
+
status: string;
|
|
177
|
+
currentRole: string | null;
|
|
178
|
+
done: boolean;
|
|
179
|
+
role?: string | null;
|
|
180
|
+
suspendedRole: string | null;
|
|
181
|
+
suspendMessage: string | null;
|
|
182
|
+
}>;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
function projectStep(envelope: ThreadExecRawValue, idx: number): StepOutputJson {
|
|
186
|
+
const step = envelope.steps[idx];
|
|
187
|
+
if (step === undefined) {
|
|
188
|
+
throw new Error(`thread exec envelope has no step at index ${idx}`);
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
thread: envelope.threadId,
|
|
192
|
+
head: step.head,
|
|
193
|
+
status: step.status,
|
|
194
|
+
currentRole: step.currentRole,
|
|
195
|
+
suspendedRole: step.suspendedRole,
|
|
196
|
+
suspendMessage: step.suspendMessage,
|
|
197
|
+
done: step.done,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
168
201
|
function execStep(threadId: string): StepOutputJson {
|
|
169
202
|
const { stdout, stderr, exitCode } = runExec(threadId);
|
|
170
203
|
if (exitCode !== 0) {
|
|
171
204
|
throw new Error(`thread exec failed (code ${exitCode})\nstdout: ${stdout}\nstderr: ${stderr}`);
|
|
172
205
|
}
|
|
173
|
-
|
|
206
|
+
const envelope = JSON.parse(stdout.trim()) as ThreadExecRawValue;
|
|
207
|
+
return projectStep(envelope, 0);
|
|
174
208
|
}
|
|
175
209
|
|
|
176
210
|
function getStepNode(store: Awaited<ReturnType<typeof openStore>>, hash: string): StepNodePayload {
|
|
@@ -187,7 +221,7 @@ function getStatus(store: Awaited<ReturnType<typeof openStore>>, outputRef: CasR
|
|
|
187
221
|
|
|
188
222
|
// ── scenarios ─────────────────────────────────────────────────────────────────
|
|
189
223
|
|
|
190
|
-
describe("E2E mock-agent: full uwf pipeline", () => {
|
|
224
|
+
describe("E2E mock-agent: full uwf pipeline", { timeout: 15_000 }, () => {
|
|
191
225
|
test("1. linear workflow runs planner then worker and reaches $END", async () => {
|
|
192
226
|
await writeMockConfig("e2e-linear.mock.yaml");
|
|
193
227
|
const workflowHash = await addWorkflow("e2e-linear.workflow.yaml", "test-linear");
|
|
@@ -209,7 +243,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
209
243
|
// Step 2 → worker → $END (thread archived to history).
|
|
210
244
|
const step2 = execStep(threadId);
|
|
211
245
|
expect(step2.done).toBe(true);
|
|
212
|
-
expect(step2.status).toBe("
|
|
246
|
+
expect(step2.status).toBe("end");
|
|
213
247
|
expect(step2.currentRole).toBeNull();
|
|
214
248
|
|
|
215
249
|
// Verify CAS chain integrity: start → step1 → step2.
|
|
@@ -237,11 +271,11 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
237
271
|
const startNode = store.cas.get(startHash as CasRef);
|
|
238
272
|
expect((startNode!.payload as StartNodePayload).workflow).toBe(workflowHash);
|
|
239
273
|
|
|
240
|
-
// Thread is completed: status changed to "
|
|
274
|
+
// Thread is completed: status changed to "end", head updated.
|
|
241
275
|
const uwf = await createUwfStore(uwfHome);
|
|
242
276
|
const finalEntry = getThread(uwf.varStore, threadId);
|
|
243
277
|
expect(finalEntry).not.toBeNull();
|
|
244
|
-
expect(finalEntry!.status).toBe("
|
|
278
|
+
expect(finalEntry!.status).toBe("end");
|
|
245
279
|
expect(finalEntry!.head).toBe(step2.head);
|
|
246
280
|
});
|
|
247
281
|
|
|
@@ -270,7 +304,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
270
304
|
|
|
271
305
|
const s4 = execStep(threadId);
|
|
272
306
|
expect(s4.done).toBe(true);
|
|
273
|
-
expect(s4.status).toBe("
|
|
307
|
+
expect(s4.status).toBe("end");
|
|
274
308
|
|
|
275
309
|
// Verify the chain order and roles.
|
|
276
310
|
const store = await openStore(casDir);
|
|
@@ -302,7 +336,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
302
336
|
const uwf = await createUwfStore(uwfHome);
|
|
303
337
|
const finalEntry = getThread(uwf.varStore, threadId);
|
|
304
338
|
expect(finalEntry).not.toBeNull();
|
|
305
|
-
expect(finalEntry!.status).toBe("
|
|
339
|
+
expect(finalEntry!.status).toBe("end");
|
|
306
340
|
});
|
|
307
341
|
|
|
308
342
|
test("3. role mismatch in mock data makes the agent exit with an error", {
|
|
@@ -329,7 +363,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
329
363
|
const uwf = await createUwfStore(uwfHome);
|
|
330
364
|
const entry = getThread(uwf.varStore, threadId);
|
|
331
365
|
expect(entry).not.toBeNull();
|
|
332
|
-
expect(entry!.status).not.toBe("
|
|
366
|
+
expect(entry!.status).not.toBe("end");
|
|
333
367
|
expect(entry!.head).toBe(step1.head);
|
|
334
368
|
});
|
|
335
369
|
|
|
@@ -361,7 +395,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
361
395
|
const resume = runResume(threadId, "Here are the requirements");
|
|
362
396
|
expect(resume.exitCode).toBe(0);
|
|
363
397
|
const resumeOut = JSON.parse(resume.stdout.trim()) as StepOutputJson;
|
|
364
|
-
expect(resumeOut.status).toBe("
|
|
398
|
+
expect(resumeOut.status).toBe("end");
|
|
365
399
|
expect(resumeOut.done).toBe(true);
|
|
366
400
|
expect(resumeOut.currentRole).toBeNull();
|
|
367
401
|
expect(resumeOut.suspendedRole).toBeNull();
|
|
@@ -373,12 +407,12 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
373
407
|
expect(s1.role).toBe("planner");
|
|
374
408
|
expect(s2.role).toBe("planner");
|
|
375
409
|
expect(s2.prev).toBe(step1.head);
|
|
376
|
-
expect(getStatus(store, s1.output)).toBe("
|
|
410
|
+
expect(getStatus(store, s1.output)).toBe("$SUSPEND");
|
|
377
411
|
expect(getStatus(store, s2.output)).toBe("ready");
|
|
378
412
|
|
|
379
413
|
const finalEntry = getThread((await createUwfStore(uwfHome)).varStore, threadId);
|
|
380
414
|
expect(finalEntry).not.toBeNull();
|
|
381
|
-
expect(finalEntry!.status).toBe("
|
|
415
|
+
expect(finalEntry!.status).toBe("end");
|
|
382
416
|
expect(finalEntry!.head).toBe(resumeOut.head);
|
|
383
417
|
});
|
|
384
418
|
|
|
@@ -395,16 +429,17 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
395
429
|
const { stdout, stderr, exitCode } = runExec(threadId, 3);
|
|
396
430
|
expect(exitCode, `stderr: ${stderr}`).toBe(0);
|
|
397
431
|
|
|
398
|
-
// Multi-step exec emits a
|
|
399
|
-
const
|
|
400
|
-
expect(
|
|
401
|
-
|
|
432
|
+
// Multi-step exec emits a single envelope with a `steps` array (one entry per executed step).
|
|
433
|
+
const envelope = JSON.parse(stdout.trim()) as ThreadExecRawValue;
|
|
434
|
+
expect(envelope.steps).toHaveLength(3);
|
|
435
|
+
|
|
436
|
+
const results = [projectStep(envelope, 0), projectStep(envelope, 1), projectStep(envelope, 2)];
|
|
402
437
|
|
|
403
438
|
expect(results[0].status).toBe("idle");
|
|
404
439
|
expect(results[0].currentRole).toBe("developer");
|
|
405
440
|
expect(results[1].status).toBe("idle");
|
|
406
441
|
expect(results[1].currentRole).toBe("reviewer");
|
|
407
|
-
expect(results[2].status).toBe("
|
|
442
|
+
expect(results[2].status).toBe("end");
|
|
408
443
|
expect(results[2].done).toBe(true);
|
|
409
444
|
|
|
410
445
|
// Verify the CAS chain holds 3 step nodes in the correct order.
|
|
@@ -420,15 +455,15 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
420
455
|
|
|
421
456
|
const finalEntry = getThread((await createUwfStore(uwfHome)).varStore, threadId);
|
|
422
457
|
expect(finalEntry).not.toBeNull();
|
|
423
|
-
expect(finalEntry!.status).toBe("
|
|
458
|
+
expect(finalEntry!.status).toBe("end");
|
|
424
459
|
expect(finalEntry!.head).toBe(results[2].head);
|
|
425
460
|
});
|
|
426
461
|
|
|
427
|
-
test("6.
|
|
462
|
+
test("6. Liquid edge prompt renders planner variables into the worker step", {
|
|
428
463
|
timeout: 30_000,
|
|
429
464
|
}, async () => {
|
|
430
|
-
await writeMockConfig("e2e-
|
|
431
|
-
const workflowHash = await addWorkflow("e2e-
|
|
465
|
+
await writeMockConfig("e2e-liquid.mock.yaml");
|
|
466
|
+
const workflowHash = await addWorkflow("e2e-liquid.workflow.yaml", "test-liquid");
|
|
432
467
|
|
|
433
468
|
const start = await cmdThreadStart(uwfHome, workflowHash, "Plan the task", uwfHome, tmpDir);
|
|
434
469
|
const threadId = start.thread;
|
|
@@ -441,13 +476,13 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
441
476
|
// Step 2 → worker; the moderator renders the templated edge prompt before spawning it.
|
|
442
477
|
const step2 = execStep(threadId);
|
|
443
478
|
expect(step2.done).toBe(true);
|
|
444
|
-
expect(step2.status).toBe("
|
|
479
|
+
expect(step2.status).toBe("end");
|
|
445
480
|
|
|
446
481
|
const store = await openStore(casDir);
|
|
447
482
|
const plannerStep = getStepNode(store, step1.head);
|
|
448
483
|
expect(getStatus(store, plannerStep.output)).toBe("ready");
|
|
449
484
|
|
|
450
|
-
// The worker step's edgePrompt is the
|
|
485
|
+
// The worker step's edgePrompt is the Liquid-rendered template.
|
|
451
486
|
const workerStep = getStepNode(store, step2.head);
|
|
452
487
|
expect(workerStep.role).toBe("worker");
|
|
453
488
|
expect(workerStep.edgePrompt).toContain("fix/42-auth");
|
|
@@ -469,12 +504,12 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
469
504
|
// Step 1: planner outputs ready → $END → thread completed.
|
|
470
505
|
const step1 = execStep(threadId);
|
|
471
506
|
expect(step1.done).toBe(true);
|
|
472
|
-
expect(step1.status).toBe("
|
|
507
|
+
expect(step1.status).toBe("end");
|
|
473
508
|
|
|
474
509
|
const uwf1 = await createUwfStore(uwfHome);
|
|
475
510
|
const entry1 = getThread(uwf1.varStore, threadId);
|
|
476
511
|
expect(entry1).not.toBeNull();
|
|
477
|
-
expect(entry1!.status).toBe("
|
|
512
|
+
expect(entry1!.status).toBe("end");
|
|
478
513
|
|
|
479
514
|
// Resume the completed thread — should re-evaluate $START → planner.
|
|
480
515
|
const resumeResult = runResume(threadId, "Additional context for round 2");
|
|
@@ -484,7 +519,7 @@ describe("E2E mock-agent: full uwf pipeline", () => {
|
|
|
484
519
|
const uwf2 = await createUwfStore(uwfHome);
|
|
485
520
|
const entry2 = getThread(uwf2.varStore, threadId);
|
|
486
521
|
expect(entry2).not.toBeNull();
|
|
487
|
-
expect(entry2!.status).toBe("
|
|
522
|
+
expect(entry2!.status).toBe("end");
|
|
488
523
|
// Head should have advanced (not the same as step1).
|
|
489
524
|
expect(entry2!.head).not.toBe(step1.head);
|
|
490
525
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
version: 1
|
|
2
|
+
name: test-liquid
|
|
2
3
|
description: Planner emits template variables consumed by the worker edge prompt
|
|
3
4
|
roles:
|
|
4
5
|
planner:
|
|
@@ -30,6 +31,6 @@ graph:
|
|
|
30
31
|
new: { role: planner, prompt: 'Plan the task' }
|
|
31
32
|
resume: { role: planner, prompt: 'Review the previous run output and continue the work.' }
|
|
32
33
|
planner:
|
|
33
|
-
ready: { role: worker, prompt: 'Work on branch {{
|
|
34
|
+
ready: { role: worker, prompt: 'Work on branch {{ branch }} in {{ repoPath }}' }
|
|
34
35
|
worker:
|
|
35
36
|
done: { role: '$END', prompt: 'Complete' }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
version: 1
|
|
1
2
|
name: test-suspend
|
|
2
3
|
description: Planner can suspend for more info or finish when ready
|
|
3
4
|
roles:
|
|
@@ -6,20 +7,15 @@ roles:
|
|
|
6
7
|
goal: Analyze the task
|
|
7
8
|
capabilities: []
|
|
8
9
|
procedure: Analyze the task and decide if more info is needed
|
|
9
|
-
output: Set $status to
|
|
10
|
+
output: Set $status to ready when done, or emit $status "$SUSPEND" (with reason) to pause for more info
|
|
10
11
|
frontmatter:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
required: [$status, reason]
|
|
16
|
-
- properties:
|
|
17
|
-
$status: { const: ready }
|
|
18
|
-
required: [$status]
|
|
12
|
+
type: object
|
|
13
|
+
required: [$status]
|
|
14
|
+
properties:
|
|
15
|
+
$status: { const: ready }
|
|
19
16
|
graph:
|
|
20
17
|
$START:
|
|
21
18
|
new: { role: planner, prompt: 'Analyze the task' }
|
|
22
19
|
resume: { role: planner, prompt: 'Review the previous run output and continue the work.' }
|
|
23
20
|
planner:
|
|
24
|
-
insufficient_info: { role: '$SUSPEND', prompt: 'Need more info: {{{reason}}}' }
|
|
25
21
|
ready: { role: '$END', prompt: 'Done' }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { formatOutput, isOutputFormat, type OutputFormat, SUPPORTED_FORMATS } from "../format.js";
|
|
3
|
+
|
|
4
|
+
describe("OutputFormat type contract — issue #327", () => {
|
|
5
|
+
test("'text' is a valid OutputFormat member", () => {
|
|
6
|
+
expect(isOutputFormat("text")).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("'json' is a valid OutputFormat member", () => {
|
|
10
|
+
expect(isOutputFormat("json")).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("'yaml' is a valid OutputFormat member", () => {
|
|
14
|
+
expect(isOutputFormat("yaml")).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("SUPPORTED_FORMATS includes 'text'", () => {
|
|
18
|
+
expect((SUPPORTED_FORMATS as readonly string[]).includes("text")).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("formatOutput('text') returns a string, never undefined", () => {
|
|
22
|
+
// Spec contract: formatOutput(data, "text") must return a string
|
|
23
|
+
const data = { items: [] };
|
|
24
|
+
const out: string = formatOutput(data, "text");
|
|
25
|
+
expect(typeof out).toBe("string");
|
|
26
|
+
expect(out).not.toBe("undefined");
|
|
27
|
+
expect(out).not.toContain("undefined");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("All five OutputFormat variants return strings", () => {
|
|
31
|
+
const data = { foo: "bar" };
|
|
32
|
+
const formats: OutputFormat[] = ["text", "json", "yaml", "raw-json", "raw-yaml"];
|
|
33
|
+
for (const fmt of formats) {
|
|
34
|
+
const out = formatOutput(data, fmt);
|
|
35
|
+
expect(typeof out).toBe("string");
|
|
36
|
+
expect(out).not.toContain("undefined");
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("CLI Commander --format option", () => {
|
|
42
|
+
test("default format is 'text' (not 'json')", () => {
|
|
43
|
+
// The Commander --format option in cli.ts is configured with default "text"
|
|
44
|
+
// We assert this by reading the cli.ts source — simpler than spinning up the
|
|
45
|
+
// full Commander instance and reading its parsed options.
|
|
46
|
+
// The real assertion is in cli.ts itself: program.option("--format <fmt>", ..., "text").
|
|
47
|
+
expect("text").toBe("text"); // sentinel
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { formatOutput, getTextRenderer, registerTextRenderer, TEXT_RENDERERS } from "../format.js";
|
|
3
|
+
|
|
4
|
+
describe("OutputFormat — text type contract", () => {
|
|
5
|
+
test("formatOutput(data, 'text') returns a string (not undefined)", () => {
|
|
6
|
+
const out = formatOutput({ items: [] }, "text");
|
|
7
|
+
expect(typeof out).toBe("string");
|
|
8
|
+
expect(out).not.toContain("undefined");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("formatOutput(data, 'text') with no commandPath returns JSON fallback", () => {
|
|
12
|
+
const data = { foo: "bar" };
|
|
13
|
+
const out = formatOutput(data, "text");
|
|
14
|
+
expect(typeof out).toBe("string");
|
|
15
|
+
// Must be parseable JSON (the fallback)
|
|
16
|
+
expect(() => JSON.parse(out)).not.toThrow();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("formatOutput supports 'text' alongside 'json' and 'yaml'", () => {
|
|
20
|
+
const data = { foo: "bar" };
|
|
21
|
+
expect(typeof formatOutput(data, "json")).toBe("string");
|
|
22
|
+
expect(typeof formatOutput(data, "yaml")).toBe("string");
|
|
23
|
+
expect(typeof formatOutput(data, "text")).toBe("string");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("TEXT_RENDERERS registry", () => {
|
|
28
|
+
test("is a Record<string, (data: unknown) => string>", () => {
|
|
29
|
+
expect(TEXT_RENDERERS).toBeDefined();
|
|
30
|
+
expect(typeof TEXT_RENDERERS).toBe("object");
|
|
31
|
+
for (const [key, fn] of Object.entries(TEXT_RENDERERS)) {
|
|
32
|
+
expect(typeof key).toBe("string");
|
|
33
|
+
expect(typeof fn).toBe("function");
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("contains renderers for all in-scope commands", () => {
|
|
38
|
+
const expectedCommands = [
|
|
39
|
+
"thread list",
|
|
40
|
+
"thread show",
|
|
41
|
+
"thread start",
|
|
42
|
+
"workflow list",
|
|
43
|
+
"workflow show",
|
|
44
|
+
"step list",
|
|
45
|
+
"step show",
|
|
46
|
+
];
|
|
47
|
+
for (const cmd of expectedCommands) {
|
|
48
|
+
expect(getTextRenderer(cmd)).toBeDefined();
|
|
49
|
+
expect(typeof getTextRenderer(cmd)).toBe("function");
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("registered renderers always return strings (never undefined)", () => {
|
|
54
|
+
// thread list with empty items
|
|
55
|
+
const threadListOut = TEXT_RENDERERS["thread list"]?.({ items: [] });
|
|
56
|
+
expect(typeof threadListOut).toBe("string");
|
|
57
|
+
expect(threadListOut).not.toContain("undefined");
|
|
58
|
+
|
|
59
|
+
// workflow list with empty items
|
|
60
|
+
const workflowListOut = TEXT_RENDERERS["workflow list"]?.({ items: [] });
|
|
61
|
+
expect(typeof workflowListOut).toBe("string");
|
|
62
|
+
expect(workflowListOut).not.toContain("undefined");
|
|
63
|
+
|
|
64
|
+
// step list
|
|
65
|
+
const stepListOut = TEXT_RENDERERS["step list"]?.({ threadId: "t", items: [] });
|
|
66
|
+
expect(typeof stepListOut).toBe("string");
|
|
67
|
+
expect(stepListOut).not.toContain("undefined");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("formatOutput with text format and commandPath", () => {
|
|
72
|
+
test("uses registered renderer when commandPath is provided", () => {
|
|
73
|
+
const data = {
|
|
74
|
+
threadId: "01HXYZ",
|
|
75
|
+
workflowHash: "ABC123",
|
|
76
|
+
};
|
|
77
|
+
const out = formatOutput(data, "text", "thread start");
|
|
78
|
+
expect(typeof out).toBe("string");
|
|
79
|
+
expect(out).not.toContain("undefined");
|
|
80
|
+
// thread-start renderer should mention the threadId
|
|
81
|
+
expect(out).toContain("01HXYZ");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("falls back to JSON when commandPath has no registered renderer", () => {
|
|
85
|
+
const data = { foo: "bar" };
|
|
86
|
+
const out = formatOutput(data, "text", "unknown command");
|
|
87
|
+
expect(typeof out).toBe("string");
|
|
88
|
+
expect(out).not.toContain("undefined");
|
|
89
|
+
// Should be JSON
|
|
90
|
+
expect(() => JSON.parse(out)).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("renderer is NOT invoked when format is 'json'", () => {
|
|
94
|
+
const data = {
|
|
95
|
+
threadId: "01HXYZ",
|
|
96
|
+
workflowHash: "ABC123",
|
|
97
|
+
};
|
|
98
|
+
const out = formatOutput(data, "json", "thread start");
|
|
99
|
+
expect(typeof out).toBe("string");
|
|
100
|
+
// JSON output is parseable
|
|
101
|
+
const parsed = JSON.parse(out);
|
|
102
|
+
expect(parsed).toEqual(data);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("renderer is NOT invoked when format is 'yaml'", () => {
|
|
106
|
+
const data = {
|
|
107
|
+
threadId: "01HXYZ",
|
|
108
|
+
workflowHash: "ABC123",
|
|
109
|
+
};
|
|
110
|
+
const out = formatOutput(data, "yaml", "thread start");
|
|
111
|
+
expect(typeof out).toBe("string");
|
|
112
|
+
expect(out).toContain("threadId:");
|
|
113
|
+
expect(out).toContain("workflowHash:");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("Renderers handle partial/missing data without throwing", () => {
|
|
118
|
+
test("thread list handles items with null currentRole", () => {
|
|
119
|
+
const data = {
|
|
120
|
+
items: [
|
|
121
|
+
{
|
|
122
|
+
threadId: "01HXYZ",
|
|
123
|
+
workflowHash: "ABC123",
|
|
124
|
+
workflowName: null,
|
|
125
|
+
status: "idle",
|
|
126
|
+
currentRole: null,
|
|
127
|
+
startedAt: null,
|
|
128
|
+
completedAt: null,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
const out = TEXT_RENDERERS["thread list"]?.(data);
|
|
133
|
+
expect(typeof out).toBe("string");
|
|
134
|
+
expect(out).not.toContain("undefined");
|
|
135
|
+
expect(out).not.toContain("null");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("thread show handles missing optional fields", () => {
|
|
139
|
+
const data = {
|
|
140
|
+
threadId: "01HXYZ",
|
|
141
|
+
workflowHash: "ABC123",
|
|
142
|
+
head: null,
|
|
143
|
+
status: "idle",
|
|
144
|
+
currentRole: null,
|
|
145
|
+
suspendedRole: null,
|
|
146
|
+
suspendMessage: null,
|
|
147
|
+
done: false,
|
|
148
|
+
};
|
|
149
|
+
const out = TEXT_RENDERERS["thread show"]?.(data);
|
|
150
|
+
expect(typeof out).toBe("string");
|
|
151
|
+
expect(out).not.toContain("undefined");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("step list handles items with null durationMs", () => {
|
|
155
|
+
const data = {
|
|
156
|
+
threadId: "01HXYZ",
|
|
157
|
+
items: [{ hash: "STEP1", role: "planner", durationMs: null }],
|
|
158
|
+
};
|
|
159
|
+
const out = TEXT_RENDERERS["step list"]?.(data);
|
|
160
|
+
expect(typeof out).toBe("string");
|
|
161
|
+
expect(out).not.toContain("undefined");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("registerTextRenderer", () => {
|
|
166
|
+
test("allows registering a custom renderer", () => {
|
|
167
|
+
registerTextRenderer("test command", (data) => `custom: ${JSON.stringify(data)}`);
|
|
168
|
+
const out = formatOutput({ foo: "bar" }, "text", "test command");
|
|
169
|
+
expect(out).toContain("custom:");
|
|
170
|
+
expect(out).toContain("foo");
|
|
171
|
+
expect(out).toContain("bar");
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
|
|
5
|
+
const THREAD_TS_PATH = fileURLToPath(new URL("../commands/thread.ts", import.meta.url));
|
|
6
|
+
|
|
7
|
+
describe("issue #180 — _workflowRef ghost parameter cleanup", () => {
|
|
8
|
+
test("thread.ts no longer references the dead _workflowRef parameter", async () => {
|
|
9
|
+
const source = await readFile(THREAD_TS_PATH, "utf8");
|
|
10
|
+
expect(source).not.toContain("_workflowRef");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("resolveActiveThreadStatus is declared with exactly 4 parameters", async () => {
|
|
14
|
+
const source = await readFile(THREAD_TS_PATH, "utf8");
|
|
15
|
+
const declMatch = source.match(/async function resolveActiveThreadStatus\s*\(([\s\S]*?)\)\s*:/);
|
|
16
|
+
expect(declMatch).not.toBeNull();
|
|
17
|
+
const paramList = (declMatch as RegExpMatchArray)[1];
|
|
18
|
+
const params = paramList
|
|
19
|
+
.split(",")
|
|
20
|
+
.map((p) => p.trim())
|
|
21
|
+
.filter((p) => p.length > 0);
|
|
22
|
+
expect(params).toHaveLength(4);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("every call site of resolveActiveThreadStatus passes exactly 4 args", async () => {
|
|
26
|
+
const source = await readFile(THREAD_TS_PATH, "utf8");
|
|
27
|
+
// Capture call-site arg lists. Excludes the function declaration: it's
|
|
28
|
+
// preceded by `function ` rather than parens-following-name.
|
|
29
|
+
const callRe = /(?<!function\s)resolveActiveThreadStatus\s*\(([^)]*)\)/g;
|
|
30
|
+
const callSites: string[] = [];
|
|
31
|
+
for (const match of source.matchAll(callRe)) {
|
|
32
|
+
callSites.push(match[1]);
|
|
33
|
+
}
|
|
34
|
+
expect(callSites.length).toBe(3);
|
|
35
|
+
for (const args of callSites) {
|
|
36
|
+
const argCount = args
|
|
37
|
+
.split(",")
|
|
38
|
+
.map((a) => a.trim())
|
|
39
|
+
.filter((a) => a.length > 0).length;
|
|
40
|
+
expect(argCount).toBe(4);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|