@davidorex/pi-workflows 0.2.0 → 0.3.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/CHANGELOG.md +7 -4
- package/README.md +12 -2
- package/agents/architecture-designer.agent.yaml +58 -0
- package/agents/architecture-inferrer.agent.yaml +60 -0
- package/agents/gap-identifier.agent.yaml +66 -0
- package/agents/handoff-writer.agent.yaml +64 -0
- package/agents/plan-creator.agent.yaml +63 -0
- package/agents/project-definer.agent.yaml +53 -0
- package/agents/project-inferrer.agent.yaml +63 -0
- package/agents/requirements-gatherer.agent.yaml +58 -0
- package/dist/agent-spec.d.ts +27 -0
- package/dist/agent-spec.d.ts.map +1 -0
- package/dist/agent-spec.js +112 -0
- package/dist/agent-spec.js.map +1 -0
- package/dist/checkpoint.d.ts +50 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +109 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/completion.d.ts +18 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +58 -0
- package/dist/completion.js.map +1 -0
- package/dist/dag.d.ts +56 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +178 -0
- package/dist/dag.js.map +1 -0
- package/dist/dispatch.d.ts +35 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/expression.d.ts +64 -0
- package/dist/expression.d.ts.map +1 -0
- package/dist/expression.js +294 -0
- package/dist/expression.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +42 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +59 -0
- package/dist/output.js.map +1 -0
- package/dist/state.d.ts +66 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +227 -0
- package/dist/state.js.map +1 -0
- package/dist/step-agent.d.ts +35 -0
- package/dist/step-agent.d.ts.map +1 -0
- package/dist/step-agent.js +171 -0
- package/dist/step-agent.js.map +1 -0
- package/dist/step-command.d.ts +21 -0
- package/dist/step-command.d.ts.map +1 -0
- package/dist/step-command.js +150 -0
- package/dist/step-command.js.map +1 -0
- package/dist/step-foreach.d.ts +33 -0
- package/dist/step-foreach.d.ts.map +1 -0
- package/dist/step-foreach.js +112 -0
- package/dist/step-foreach.js.map +1 -0
- package/dist/step-gate.d.ts +18 -0
- package/dist/step-gate.d.ts.map +1 -0
- package/dist/step-gate.js +137 -0
- package/dist/step-gate.js.map +1 -0
- package/dist/step-loop.d.ts +32 -0
- package/dist/step-loop.d.ts.map +1 -0
- package/dist/step-loop.js +278 -0
- package/dist/step-loop.js.map +1 -0
- package/dist/step-monitor.d.ts +56 -0
- package/dist/step-monitor.d.ts.map +1 -0
- package/dist/step-monitor.js +317 -0
- package/dist/step-monitor.js.map +1 -0
- package/dist/step-parallel.d.ts +43 -0
- package/dist/step-parallel.d.ts.map +1 -0
- package/dist/step-parallel.js +122 -0
- package/dist/step-parallel.js.map +1 -0
- package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
- package/dist/step-pause.d.ts.map +1 -0
- package/dist/step-pause.js +22 -0
- package/dist/step-pause.js.map +1 -0
- package/dist/step-shared.d.ts +63 -0
- package/dist/step-shared.d.ts.map +1 -0
- package/dist/step-shared.js +136 -0
- package/dist/step-shared.js.map +1 -0
- package/dist/step-transform.d.ts +11 -0
- package/dist/step-transform.d.ts.map +1 -0
- package/dist/step-transform.js +41 -0
- package/dist/step-transform.js.map +1 -0
- package/dist/template.d.ts +51 -0
- package/dist/template.d.ts.map +1 -0
- package/{src/template.ts → dist/template.js} +32 -38
- package/dist/template.js.map +1 -0
- package/dist/test-helpers.d.ts +17 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +46 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/tui.d.ts +47 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +143 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-discovery.d.ts +23 -0
- package/dist/workflow-discovery.d.ts.map +1 -0
- package/dist/workflow-discovery.js +103 -0
- package/dist/workflow-discovery.js.map +1 -0
- package/dist/workflow-executor.d.ts +50 -0
- package/dist/workflow-executor.d.ts.map +1 -0
- package/dist/workflow-executor.js +738 -0
- package/dist/workflow-executor.js.map +1 -0
- package/dist/workflow-sdk.d.ts +44 -0
- package/dist/workflow-sdk.d.ts.map +1 -0
- package/dist/workflow-sdk.js +457 -0
- package/dist/workflow-sdk.js.map +1 -0
- package/dist/workflow-spec.d.ts +33 -0
- package/dist/workflow-spec.d.ts.map +1 -0
- package/dist/workflow-spec.js +476 -0
- package/dist/workflow-spec.js.map +1 -0
- package/dist/workflows-dir.d.ts +3 -0
- package/dist/workflows-dir.d.ts.map +1 -0
- package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
- package/dist/workflows-dir.js.map +1 -0
- package/package.json +53 -38
- package/schemas/decomposition-specs.schema.json +48 -48
- package/schemas/execution-results.schema.json +48 -48
- package/schemas/investigation-findings.schema.json +43 -43
- package/schemas/pattern-analysis.schema.json +41 -41
- package/schemas/phase.schema.json +48 -0
- package/schemas/plan-breakdown.schema.json +20 -20
- package/schemas/quality-analysis.schema.json +33 -33
- package/schemas/research-findings.schema.json +42 -42
- package/schemas/structure-analysis.schema.json +40 -40
- package/schemas/synthesis.schema.json +31 -31
- package/schemas/verifier-output.schema.json +94 -94
- package/skill-narrative.md +107 -0
- package/skills/pi-workflows/SKILL.md +185 -0
- package/skills/pi-workflows/references/bundled-resources.md +87 -0
- package/templates/architecture-designer/task.md +117 -0
- package/templates/architecture-inferrer/task.md +61 -0
- package/templates/gap-identifier/task.md +103 -0
- package/templates/handoff-writer/task.md +91 -0
- package/templates/plan-creator/task.md +143 -0
- package/templates/project-definer/task.md +67 -0
- package/templates/project-inferrer/task.md +56 -0
- package/templates/requirements-gatherer/task.md +90 -0
- package/workflows/analyze-existing-project.workflow.yaml +94 -0
- package/workflows/create-handoff.workflow.yaml +66 -0
- package/workflows/create-phase.workflow.yaml +1 -1
- package/workflows/do-gap.workflow.yaml +4 -4
- package/workflows/fix-audit.workflow.yaml +1 -1
- package/workflows/gap-to-phase.workflow.yaml +1 -1
- package/workflows/init-new-project.workflow.yaml +81 -0
- package/workflows/plan-from-requirements.workflow.yaml +59 -0
- package/workflows/self-implement.workflow.yaml +3 -3
- package/workflows/typed-analysis.workflow.yaml +4 -4
- package/src/agent-spec.test.ts +0 -289
- package/src/agent-spec.ts +0 -122
- package/src/block-validation.test.ts +0 -350
- package/src/checkpoint.test.ts +0 -237
- package/src/checkpoint.ts +0 -140
- package/src/completion.test.ts +0 -143
- package/src/completion.ts +0 -68
- package/src/dag.test.ts +0 -350
- package/src/dag.ts +0 -219
- package/src/dispatch.test.ts +0 -475
- package/src/dispatch.ts +0 -353
- package/src/expression.test.ts +0 -353
- package/src/expression.ts +0 -332
- package/src/format.test.ts +0 -80
- package/src/format.ts +0 -41
- package/src/graduated-failure.test.ts +0 -556
- package/src/index.test.ts +0 -114
- package/src/index.ts +0 -516
- package/src/loop.test.ts +0 -551
- package/src/output.test.ts +0 -213
- package/src/output.ts +0 -70
- package/src/parallel-integration.test.ts +0 -175
- package/src/resume.test.ts +0 -192
- package/src/state.test.ts +0 -192
- package/src/state.ts +0 -254
- package/src/step-agent.test.ts +0 -327
- package/src/step-agent.ts +0 -178
- package/src/step-command.test.ts +0 -330
- package/src/step-command.ts +0 -132
- package/src/step-foreach.test.ts +0 -647
- package/src/step-foreach.ts +0 -148
- package/src/step-gate.test.ts +0 -185
- package/src/step-gate.ts +0 -116
- package/src/step-loop.test.ts +0 -626
- package/src/step-loop.ts +0 -323
- package/src/step-parallel.test.ts +0 -475
- package/src/step-parallel.ts +0 -168
- package/src/step-pause.test.ts +0 -30
- package/src/step-shared.test.ts +0 -355
- package/src/step-shared.ts +0 -157
- package/src/step-transform.test.ts +0 -155
- package/src/step-transform.ts +0 -47
- package/src/template-integration.test.ts +0 -72
- package/src/template.test.ts +0 -162
- package/src/test-helpers.ts +0 -51
- package/src/tui.test.ts +0 -355
- package/src/tui.ts +0 -182
- package/src/types.ts +0 -195
- package/src/verifier-schema.test.ts +0 -226
- package/src/workflow-discovery.test.ts +0 -105
- package/src/workflow-discovery.ts +0 -114
- package/src/workflow-executor.test.ts +0 -1532
- package/src/workflow-executor.ts +0 -810
- package/src/workflow-sdk.test.ts +0 -238
- package/src/workflow-sdk.ts +0 -262
- package/src/workflow-spec.test.ts +0 -576
- package/src/workflow-spec.ts +0 -471
package/src/dag.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DAG dependency analysis and execution planning for workflow steps.
|
|
3
|
-
* Extracts `${{ steps.X }}` references, builds a dependency graph,
|
|
4
|
-
* and produces layered execution plans for parallel dispatch.
|
|
5
|
-
*/
|
|
6
|
-
import type { WorkflowSpec, StepSpec } from "./types.ts";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A single layer in the execution plan.
|
|
10
|
-
* All steps in a layer are independent and can run concurrently.
|
|
11
|
-
*/
|
|
12
|
-
export interface ExecutionLayer {
|
|
13
|
-
steps: string[]; // step names in this layer
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* The full execution plan: an ordered list of layers.
|
|
18
|
-
* Steps in the same layer have no mutual dependencies.
|
|
19
|
-
* Steps in layer N depend only on steps in layers 0..N-1.
|
|
20
|
-
*/
|
|
21
|
-
export type ExecutionPlan = ExecutionLayer[];
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Extract all step dependencies from a workflow spec.
|
|
25
|
-
*
|
|
26
|
-
* Scans all expression-bearing fields in each step:
|
|
27
|
-
* - `input` values (recursive — expressions can be nested in objects/arrays)
|
|
28
|
-
* - `when` condition
|
|
29
|
-
* - `gate.check` (may contain ${{ }})
|
|
30
|
-
* - `transform.mapping` values (recursive)
|
|
31
|
-
* - `loop.attempts` (may be an expression)
|
|
32
|
-
*
|
|
33
|
-
* Does NOT descend into loop sub-steps — a loop step's internal steps
|
|
34
|
-
* are the loop's own concern, not part of the top-level DAG.
|
|
35
|
-
*
|
|
36
|
-
* Returns a map: stepName → Set of step names it depends on.
|
|
37
|
-
*/
|
|
38
|
-
export function extractDependencies(spec: WorkflowSpec): Map<string, Set<string>> {
|
|
39
|
-
const stepNames = new Set(Object.keys(spec.steps));
|
|
40
|
-
const deps = new Map<string, Set<string>>();
|
|
41
|
-
|
|
42
|
-
for (const [name, step] of Object.entries(spec.steps)) {
|
|
43
|
-
const stepDeps = new Set<string>();
|
|
44
|
-
|
|
45
|
-
// Collect all expression strings from this step
|
|
46
|
-
const expressions = collectExpressions(step);
|
|
47
|
-
|
|
48
|
-
// Parse each expression for `steps.<name>` references
|
|
49
|
-
for (const expr of expressions) {
|
|
50
|
-
const referenced = extractStepReferences(expr, stepNames);
|
|
51
|
-
for (const ref of referenced) {
|
|
52
|
-
if (ref !== name) { // no self-dependencies
|
|
53
|
-
stepDeps.add(ref);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
deps.set(name, stepDeps);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return deps;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Collect all raw expression strings from a step spec.
|
|
66
|
-
* Walks `input`, `when`, `gate.check`, `transform.mapping`, `loop.attempts`.
|
|
67
|
-
*/
|
|
68
|
-
function collectExpressions(step: StepSpec): string[] {
|
|
69
|
-
const exprs: string[] = [];
|
|
70
|
-
|
|
71
|
-
// input values (recursive)
|
|
72
|
-
if (step.input) {
|
|
73
|
-
collectExpressionsFromValue(step.input, exprs);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// when condition
|
|
77
|
-
if (step.when) {
|
|
78
|
-
exprs.push(step.when);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// gate check
|
|
82
|
-
if (step.gate) {
|
|
83
|
-
collectExpressionsFromValue(step.gate.check, exprs);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// transform mapping (recursive)
|
|
87
|
-
if (step.transform) {
|
|
88
|
-
collectExpressionsFromValue(step.transform.mapping, exprs);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// loop attempts expression
|
|
92
|
-
if (step.loop?.attempts) {
|
|
93
|
-
exprs.push(step.loop.attempts);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// forEach expression
|
|
97
|
-
if (step.forEach) {
|
|
98
|
-
collectExpressionsFromValue(step.forEach, exprs);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// command expression (may contain ${{ }} references)
|
|
102
|
-
if (step.command) {
|
|
103
|
-
collectExpressionsFromValue(step.command, exprs);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return exprs;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Recursively collect expression strings from a value.
|
|
111
|
-
* Walks objects, arrays, and strings looking for ${{ }} patterns.
|
|
112
|
-
*/
|
|
113
|
-
function collectExpressionsFromValue(value: unknown, exprs: string[]): void {
|
|
114
|
-
if (typeof value === "string") {
|
|
115
|
-
// Extract all ${{ ... }} patterns from the string
|
|
116
|
-
const regex = /\$\{\{([^}]+)\}\}/g;
|
|
117
|
-
let match: RegExpExecArray | null;
|
|
118
|
-
while ((match = regex.exec(value)) !== null) {
|
|
119
|
-
exprs.push(match[1].trim());
|
|
120
|
-
}
|
|
121
|
-
// Also handle bare expressions (no ${{ }} wrapper, e.g. in `when`)
|
|
122
|
-
if (!value.includes("${{") && value.includes("steps.")) {
|
|
123
|
-
exprs.push(value);
|
|
124
|
-
}
|
|
125
|
-
} else if (Array.isArray(value)) {
|
|
126
|
-
for (const item of value) {
|
|
127
|
-
collectExpressionsFromValue(item, exprs);
|
|
128
|
-
}
|
|
129
|
-
} else if (value !== null && typeof value === "object") {
|
|
130
|
-
for (const v of Object.values(value)) {
|
|
131
|
-
collectExpressionsFromValue(v, exprs);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Extract step name references from an expression string.
|
|
138
|
-
*
|
|
139
|
-
* Looks for patterns like:
|
|
140
|
-
* - `steps.diagnose.output`
|
|
141
|
-
* - `steps.diagnose.textOutput`
|
|
142
|
-
* - `steps.diagnose.status`
|
|
143
|
-
*
|
|
144
|
-
* Returns the set of step names referenced.
|
|
145
|
-
*/
|
|
146
|
-
function extractStepReferences(expr: string, validStepNames: Set<string>): Set<string> {
|
|
147
|
-
const refs = new Set<string>();
|
|
148
|
-
// Match `steps.<name>` — name is a word (alphanumeric + underscore + hyphen)
|
|
149
|
-
const regex = /steps\.([a-zA-Z_][\w-]*)/g;
|
|
150
|
-
let match: RegExpExecArray | null;
|
|
151
|
-
while ((match = regex.exec(expr)) !== null) {
|
|
152
|
-
const name = match[1];
|
|
153
|
-
if (validStepNames.has(name)) {
|
|
154
|
-
refs.add(name);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return refs;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Build an execution plan from a pre-computed dependency map.
|
|
162
|
-
* Performs topological sort, grouping independent steps into layers.
|
|
163
|
-
* Throws if the dependency graph contains a cycle.
|
|
164
|
-
*/
|
|
165
|
-
export function buildPlanFromDeps(
|
|
166
|
-
allSteps: string[],
|
|
167
|
-
deps: Map<string, Set<string>>,
|
|
168
|
-
): ExecutionPlan {
|
|
169
|
-
const plan: ExecutionPlan = [];
|
|
170
|
-
const placed = new Set<string>();
|
|
171
|
-
|
|
172
|
-
while (placed.size < allSteps.length) {
|
|
173
|
-
const layer: string[] = [];
|
|
174
|
-
|
|
175
|
-
for (const step of allSteps) {
|
|
176
|
-
if (placed.has(step)) continue;
|
|
177
|
-
|
|
178
|
-
const stepDeps = deps.get(step) ?? new Set();
|
|
179
|
-
if ([...stepDeps].every((d) => placed.has(d))) {
|
|
180
|
-
layer.push(step);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (layer.length === 0) {
|
|
185
|
-
const remaining = allSteps.filter((s) => !placed.has(s));
|
|
186
|
-
throw new Error(
|
|
187
|
-
`Dependency cycle detected among steps: ${remaining.join(", ")}`,
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
plan.push({ steps: layer });
|
|
192
|
-
for (const s of layer) {
|
|
193
|
-
placed.add(s);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return plan;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Build an execution plan from a workflow spec.
|
|
202
|
-
*
|
|
203
|
-
* Performs topological sort, grouping independent steps into layers.
|
|
204
|
-
* Steps within a layer can execute concurrently.
|
|
205
|
-
*
|
|
206
|
-
* Throws if the dependency graph contains a cycle.
|
|
207
|
-
*/
|
|
208
|
-
export function buildExecutionPlan(spec: WorkflowSpec): ExecutionPlan {
|
|
209
|
-
const deps = extractDependencies(spec);
|
|
210
|
-
return buildPlanFromDeps(Object.keys(spec.steps), deps);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Check if an execution plan is fully sequential
|
|
215
|
-
* (every layer has exactly one step).
|
|
216
|
-
*/
|
|
217
|
-
export function isSequential(plan: ExecutionPlan): boolean {
|
|
218
|
-
return plan.every((layer) => layer.steps.length === 1);
|
|
219
|
-
}
|
package/src/dispatch.test.ts
DELETED
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import { dispatch, buildArgs, extractText, extractToolArgsPreview } from "./dispatch.ts";
|
|
4
|
-
import type { StepSpec, AgentSpec, StepResult } from "./types.ts";
|
|
5
|
-
import fs from "node:fs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import os from "node:os";
|
|
8
|
-
|
|
9
|
-
// Skip integration tests unless RUN_INTEGRATION=1 and pi is available
|
|
10
|
-
let hasPi = false;
|
|
11
|
-
if (process.env.RUN_INTEGRATION === "1") {
|
|
12
|
-
try {
|
|
13
|
-
const { execSync } = await import("node:child_process");
|
|
14
|
-
execSync("pi --version", { stdio: "ignore" });
|
|
15
|
-
hasPi = true;
|
|
16
|
-
} catch {}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ── Unit tests: extractText ──
|
|
20
|
-
|
|
21
|
-
describe("extractText", () => {
|
|
22
|
-
it("extracts text from a content array with a text block", () => {
|
|
23
|
-
const content = [{ type: "text", text: "Hello world" }];
|
|
24
|
-
assert.strictEqual(extractText(content), "Hello world");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("returns empty string for empty array", () => {
|
|
28
|
-
assert.strictEqual(extractText([]), "");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("returns empty string for null/undefined", () => {
|
|
32
|
-
assert.strictEqual(extractText(null), "");
|
|
33
|
-
assert.strictEqual(extractText(undefined), "");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("returns empty string for non-array", () => {
|
|
37
|
-
assert.strictEqual(extractText("just a string"), "");
|
|
38
|
-
assert.strictEqual(extractText(42), "");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("returns first text block when multiple exist", () => {
|
|
42
|
-
const content = [
|
|
43
|
-
{ type: "text", text: "First" },
|
|
44
|
-
{ type: "text", text: "Second" },
|
|
45
|
-
];
|
|
46
|
-
assert.strictEqual(extractText(content), "First");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("skips non-text blocks", () => {
|
|
50
|
-
const content = [
|
|
51
|
-
{ type: "tool_use", name: "bash" },
|
|
52
|
-
{ type: "text", text: "After tool" },
|
|
53
|
-
];
|
|
54
|
-
assert.strictEqual(extractText(content), "After tool");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("returns empty string for array with no text blocks", () => {
|
|
58
|
-
const content = [
|
|
59
|
-
{ type: "tool_use", name: "bash" },
|
|
60
|
-
{ type: "image", source: {} },
|
|
61
|
-
];
|
|
62
|
-
assert.strictEqual(extractText(content), "");
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// ── Unit tests: extractToolArgsPreview ──
|
|
67
|
-
|
|
68
|
-
describe("extractToolArgsPreview", () => {
|
|
69
|
-
it("returns empty string for null/undefined", () => {
|
|
70
|
-
assert.strictEqual(extractToolArgsPreview(null), "");
|
|
71
|
-
assert.strictEqual(extractToolArgsPreview(undefined), "");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("returns empty string for non-object", () => {
|
|
75
|
-
assert.strictEqual(extractToolArgsPreview("string"), "");
|
|
76
|
-
assert.strictEqual(extractToolArgsPreview(42), "");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("extracts command arg", () => {
|
|
80
|
-
assert.strictEqual(extractToolArgsPreview({ command: "ls -la" }), "ls -la");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("extracts path arg", () => {
|
|
84
|
-
assert.strictEqual(extractToolArgsPreview({ path: "/foo/bar.ts" }), "/foo/bar.ts");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("extracts pattern arg", () => {
|
|
88
|
-
assert.strictEqual(extractToolArgsPreview({ pattern: "*.ts" }), "*.ts");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("extracts query arg", () => {
|
|
92
|
-
assert.strictEqual(extractToolArgsPreview({ query: "SELECT 1" }), "SELECT 1");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("extracts task arg", () => {
|
|
96
|
-
assert.strictEqual(extractToolArgsPreview({ task: "do something" }), "do something");
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("prefers command over other keys", () => {
|
|
100
|
-
assert.strictEqual(
|
|
101
|
-
extractToolArgsPreview({ command: "npm test", path: "/foo" }),
|
|
102
|
-
"npm test",
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("truncates long values to 60 chars", () => {
|
|
107
|
-
const longVal = "a".repeat(100);
|
|
108
|
-
const result = extractToolArgsPreview({ command: longVal });
|
|
109
|
-
assert.strictEqual(result.length, 60);
|
|
110
|
-
assert.ok(result.endsWith("..."));
|
|
111
|
-
assert.strictEqual(result, "a".repeat(57) + "...");
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("does not truncate values at exactly 60 chars", () => {
|
|
115
|
-
const val60 = "b".repeat(60);
|
|
116
|
-
assert.strictEqual(extractToolArgsPreview({ command: val60 }), val60);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("returns empty string when no recognized keys present", () => {
|
|
120
|
-
assert.strictEqual(extractToolArgsPreview({ foo: "bar", baz: 42 }), "");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("skips non-string values for recognized keys", () => {
|
|
124
|
-
assert.strictEqual(extractToolArgsPreview({ command: 123, path: true }), "");
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// ── Unit tests: buildArgs ──
|
|
129
|
-
|
|
130
|
-
describe("buildArgs", () => {
|
|
131
|
-
const baseOptions = {
|
|
132
|
-
cwd: "/tmp",
|
|
133
|
-
sessionLogDir: "/tmp/sessions",
|
|
134
|
-
stepName: "test-step",
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
it("produces minimal args for a bare step and agent", () => {
|
|
138
|
-
const step: StepSpec = { agent: "default" };
|
|
139
|
-
const agent: AgentSpec = { name: "default" };
|
|
140
|
-
const args = buildArgs(step, agent, "hello", baseOptions);
|
|
141
|
-
|
|
142
|
-
assert.deepStrictEqual(args.slice(0, 2), ["--mode", "json"]);
|
|
143
|
-
assert.ok(args.includes("--session-dir"));
|
|
144
|
-
assert.ok(args.includes("/tmp/sessions"));
|
|
145
|
-
// Ends with -p hello
|
|
146
|
-
assert.strictEqual(args[args.length - 2], "-p");
|
|
147
|
-
assert.strictEqual(args[args.length - 1], "hello");
|
|
148
|
-
// No model args
|
|
149
|
-
assert.ok(!args.includes("--models"));
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("includes model from agent spec", () => {
|
|
153
|
-
const step: StepSpec = { agent: "coder" };
|
|
154
|
-
const agent: AgentSpec = { name: "coder", model: "claude-sonnet-4-20250514" };
|
|
155
|
-
const args = buildArgs(step, agent, "do it", baseOptions);
|
|
156
|
-
|
|
157
|
-
const idx = args.indexOf("--models");
|
|
158
|
-
assert.ok(idx >= 0);
|
|
159
|
-
assert.strictEqual(args[idx + 1], "claude-sonnet-4-20250514:off");
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("step model overrides agent model", () => {
|
|
163
|
-
const step: StepSpec = { agent: "coder", model: "claude-opus-4-20250514" };
|
|
164
|
-
const agent: AgentSpec = { name: "coder", model: "claude-sonnet-4-20250514" };
|
|
165
|
-
const args = buildArgs(step, agent, "do it", baseOptions);
|
|
166
|
-
|
|
167
|
-
const idx = args.indexOf("--models");
|
|
168
|
-
assert.strictEqual(args[idx + 1], "claude-opus-4-20250514:off");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("appends thinking suffix to model", () => {
|
|
172
|
-
const step: StepSpec = { agent: "coder" };
|
|
173
|
-
const agent: AgentSpec = { name: "coder", model: "claude-sonnet-4-20250514", thinking: "16k" };
|
|
174
|
-
const args = buildArgs(step, agent, "do it", baseOptions);
|
|
175
|
-
|
|
176
|
-
const idx = args.indexOf("--models");
|
|
177
|
-
assert.strictEqual(args[idx + 1], "claude-sonnet-4-20250514:16k");
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("resolves model from modelConfig by role when agent has no model", () => {
|
|
181
|
-
const step: StepSpec = { agent: "coder" };
|
|
182
|
-
const agent: AgentSpec = { name: "coder", role: "action" };
|
|
183
|
-
const opts = { ...baseOptions, modelConfig: { default: "fallback-model", by_role: { action: "opus-4" } } };
|
|
184
|
-
const args = buildArgs(step, agent, "do it", opts);
|
|
185
|
-
|
|
186
|
-
const idx = args.indexOf("--models");
|
|
187
|
-
assert.ok(idx >= 0);
|
|
188
|
-
assert.strictEqual(args[idx + 1], "opus-4:off");
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("resolves model from modelConfig default when no role match", () => {
|
|
192
|
-
const step: StepSpec = { agent: "coder" };
|
|
193
|
-
const agent: AgentSpec = { name: "coder", role: "unknown-role" };
|
|
194
|
-
const opts = { ...baseOptions, modelConfig: { default: "fallback-model", by_role: { action: "opus-4" } } };
|
|
195
|
-
const args = buildArgs(step, agent, "do it", opts);
|
|
196
|
-
|
|
197
|
-
const idx = args.indexOf("--models");
|
|
198
|
-
assert.ok(idx >= 0);
|
|
199
|
-
assert.strictEqual(args[idx + 1], "fallback-model:off");
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("step model overrides modelConfig", () => {
|
|
203
|
-
const step: StepSpec = { agent: "coder", model: "step-override" };
|
|
204
|
-
const agent: AgentSpec = { name: "coder", role: "action" };
|
|
205
|
-
const opts = { ...baseOptions, modelConfig: { default: "fallback-model", by_role: { action: "opus-4" } } };
|
|
206
|
-
const args = buildArgs(step, agent, "do it", opts);
|
|
207
|
-
|
|
208
|
-
const idx = args.indexOf("--models");
|
|
209
|
-
assert.strictEqual(args[idx + 1], "step-override:off");
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("agent model overrides modelConfig", () => {
|
|
213
|
-
const step: StepSpec = { agent: "coder" };
|
|
214
|
-
const agent: AgentSpec = { name: "coder", model: "agent-model", role: "action" };
|
|
215
|
-
const opts = { ...baseOptions, modelConfig: { default: "fallback-model", by_role: { action: "opus-4" } } };
|
|
216
|
-
const args = buildArgs(step, agent, "do it", opts);
|
|
217
|
-
|
|
218
|
-
const idx = args.indexOf("--models");
|
|
219
|
-
assert.strictEqual(args[idx + 1], "agent-model:off");
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("handles builtin tools", () => {
|
|
223
|
-
const step: StepSpec = { agent: "coder" };
|
|
224
|
-
const agent: AgentSpec = { name: "coder", tools: ["bash", "read", "write"] };
|
|
225
|
-
const args = buildArgs(step, agent, "do it", baseOptions);
|
|
226
|
-
|
|
227
|
-
const idx = args.indexOf("--tools");
|
|
228
|
-
assert.ok(idx >= 0);
|
|
229
|
-
assert.strictEqual(args[idx + 1], "bash,read,write");
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it("handles extension tool paths", () => {
|
|
233
|
-
const step: StepSpec = { agent: "coder" };
|
|
234
|
-
const agent: AgentSpec = { name: "coder", tools: ["bash", "./ext/my-tool.ts", "/abs/tool.js"] };
|
|
235
|
-
const args = buildArgs(step, agent, "do it", baseOptions);
|
|
236
|
-
|
|
237
|
-
const toolsIdx = args.indexOf("--tools");
|
|
238
|
-
assert.ok(toolsIdx >= 0);
|
|
239
|
-
assert.strictEqual(args[toolsIdx + 1], "bash");
|
|
240
|
-
|
|
241
|
-
// Should have two --extension entries for the paths
|
|
242
|
-
const extIndices = args.reduce<number[]>((acc, a, i) => {
|
|
243
|
-
if (a === "--extension") acc.push(i);
|
|
244
|
-
return acc;
|
|
245
|
-
}, []);
|
|
246
|
-
assert.strictEqual(extIndices.length, 2);
|
|
247
|
-
assert.strictEqual(args[extIndices[0] + 1], "./ext/my-tool.ts");
|
|
248
|
-
assert.strictEqual(args[extIndices[1] + 1], "/abs/tool.js");
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it("handles extensions scoping", () => {
|
|
252
|
-
const step: StepSpec = { agent: "coder" };
|
|
253
|
-
const agent: AgentSpec = { name: "coder", extensions: ["./ext1.ts", "./ext2.ts"] };
|
|
254
|
-
const args = buildArgs(step, agent, "do it", baseOptions);
|
|
255
|
-
|
|
256
|
-
assert.ok(args.includes("--no-extensions"));
|
|
257
|
-
const extIndices = args.reduce<number[]>((acc, a, i) => {
|
|
258
|
-
if (a === "--extension") acc.push(i);
|
|
259
|
-
return acc;
|
|
260
|
-
}, []);
|
|
261
|
-
assert.strictEqual(extIndices.length, 2);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it("handles skills scoping", () => {
|
|
265
|
-
const step: StepSpec = { agent: "coder" };
|
|
266
|
-
const agent: AgentSpec = { name: "coder", skills: ["coding", "testing"] };
|
|
267
|
-
const args = buildArgs(step, agent, "do it", baseOptions);
|
|
268
|
-
|
|
269
|
-
assert.ok(args.includes("--no-skills"));
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// ── Integration tests: dispatch (require pi on PATH) ──
|
|
274
|
-
|
|
275
|
-
describe("dispatch", { skip: !hasPi ? "pi not available" : undefined }, () => {
|
|
276
|
-
it("runs a simple prompt and returns text output", async () => {
|
|
277
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wf-dispatch-"));
|
|
278
|
-
const sessDir = path.join(tmpDir, "sessions");
|
|
279
|
-
fs.mkdirSync(sessDir, { recursive: true });
|
|
280
|
-
|
|
281
|
-
const result = await dispatch(
|
|
282
|
-
{ agent: "default" }, // no specific agent — use pi defaults
|
|
283
|
-
{ name: "default" }, // minimal agent spec
|
|
284
|
-
"Respond with exactly: HELLO WORKFLOW",
|
|
285
|
-
{ cwd: process.cwd(), sessionLogDir: sessDir, stepName: "test" },
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
assert.strictEqual(result.status, "completed");
|
|
289
|
-
assert.ok(result.textOutput.includes("HELLO WORKFLOW"));
|
|
290
|
-
assert.ok(result.usage.turns >= 1);
|
|
291
|
-
assert.ok(result.durationMs > 0);
|
|
292
|
-
|
|
293
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
294
|
-
}, { timeout: 60000 });
|
|
295
|
-
|
|
296
|
-
it("returns failed status on bad prompt/agent", async () => {
|
|
297
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wf-dispatch-"));
|
|
298
|
-
const sessDir = path.join(tmpDir, "sessions");
|
|
299
|
-
fs.mkdirSync(sessDir, { recursive: true });
|
|
300
|
-
|
|
301
|
-
// --tools none should cause pi to fail or at least limit capabilities
|
|
302
|
-
const result = await dispatch(
|
|
303
|
-
{ agent: "nonexistent-agent-xyz" },
|
|
304
|
-
{ name: "nonexistent-agent-xyz" },
|
|
305
|
-
"This should fail",
|
|
306
|
-
{ cwd: process.cwd(), sessionLogDir: sessDir, stepName: "test" },
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
// Exact failure mode depends on pi behavior with unknown agents
|
|
310
|
-
// At minimum we should get a result back (not throw)
|
|
311
|
-
assert.ok(result.durationMs > 0);
|
|
312
|
-
|
|
313
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
314
|
-
}, { timeout: 30000 });
|
|
315
|
-
|
|
316
|
-
it("supports cancellation via AbortSignal", async () => {
|
|
317
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wf-dispatch-"));
|
|
318
|
-
const sessDir = path.join(tmpDir, "sessions");
|
|
319
|
-
fs.mkdirSync(sessDir, { recursive: true });
|
|
320
|
-
|
|
321
|
-
const controller = new AbortController();
|
|
322
|
-
|
|
323
|
-
// Cancel after 2 seconds
|
|
324
|
-
setTimeout(() => controller.abort(), 2000);
|
|
325
|
-
|
|
326
|
-
const result = await dispatch(
|
|
327
|
-
{ agent: "default" },
|
|
328
|
-
{ name: "default" },
|
|
329
|
-
"Write a very long essay about the history of computing. Make it at least 5000 words.",
|
|
330
|
-
{ cwd: process.cwd(), sessionLogDir: sessDir, stepName: "test", signal: controller.signal },
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
assert.strictEqual(result.status, "failed");
|
|
334
|
-
|
|
335
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
336
|
-
}, { timeout: 30000 });
|
|
337
|
-
|
|
338
|
-
it("fires onEvent callback for process events", async () => {
|
|
339
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wf-dispatch-"));
|
|
340
|
-
const sessDir = path.join(tmpDir, "sessions");
|
|
341
|
-
fs.mkdirSync(sessDir, { recursive: true });
|
|
342
|
-
|
|
343
|
-
const events: string[] = [];
|
|
344
|
-
|
|
345
|
-
const result = await dispatch(
|
|
346
|
-
{ agent: "default" },
|
|
347
|
-
{ name: "default" },
|
|
348
|
-
"Respond with exactly: TEST",
|
|
349
|
-
{
|
|
350
|
-
cwd: process.cwd(),
|
|
351
|
-
sessionLogDir: sessDir,
|
|
352
|
-
stepName: "test",
|
|
353
|
-
onEvent: (evt) => events.push(evt.type),
|
|
354
|
-
},
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
assert.ok(events.includes("agent_start"));
|
|
358
|
-
assert.ok(events.includes("message_end"));
|
|
359
|
-
|
|
360
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
361
|
-
}, { timeout: 60000 });
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
// ── Unit tests: StepResult truncation fields ──
|
|
365
|
-
|
|
366
|
-
describe("StepResult truncation fields", () => {
|
|
367
|
-
it("truncated and warnings are optional on StepResult", () => {
|
|
368
|
-
// Verify the type contract: a StepResult without truncated/warnings is valid
|
|
369
|
-
const result: StepResult = {
|
|
370
|
-
step: "test",
|
|
371
|
-
agent: "agent",
|
|
372
|
-
status: "completed",
|
|
373
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
|
|
374
|
-
durationMs: 0,
|
|
375
|
-
};
|
|
376
|
-
assert.strictEqual(result.truncated, undefined);
|
|
377
|
-
assert.strictEqual(result.warnings, undefined);
|
|
378
|
-
|
|
379
|
-
// A truncated result is also valid
|
|
380
|
-
const truncResult: StepResult = {
|
|
381
|
-
...result,
|
|
382
|
-
truncated: true,
|
|
383
|
-
warnings: ["Stdout exceeded 10MB limit"],
|
|
384
|
-
};
|
|
385
|
-
assert.strictEqual(truncResult.truncated, true);
|
|
386
|
-
assert.strictEqual(truncResult.warnings!.length, 1);
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
// ── Unit tests: truncation detection logic ──
|
|
391
|
-
|
|
392
|
-
describe("truncation detection logic", () => {
|
|
393
|
-
it("sets truncated flag when accumulated bytes exceed threshold", () => {
|
|
394
|
-
// Simulate the accumulation logic from dispatch's data handler
|
|
395
|
-
const MAX = 100; // small threshold for testing
|
|
396
|
-
let bufBytes = 0;
|
|
397
|
-
let stdoutTruncated = false;
|
|
398
|
-
const processedLines: string[] = [];
|
|
399
|
-
let buf = "";
|
|
400
|
-
|
|
401
|
-
function handleChunk(chunk: string) {
|
|
402
|
-
bufBytes += Buffer.byteLength(chunk);
|
|
403
|
-
if (bufBytes > MAX) {
|
|
404
|
-
if (!stdoutTruncated) {
|
|
405
|
-
stdoutTruncated = true;
|
|
406
|
-
if (buf.trim()) {
|
|
407
|
-
processedLines.push(buf.trim());
|
|
408
|
-
buf = "";
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
buf += chunk;
|
|
414
|
-
const lines = buf.split("\n");
|
|
415
|
-
buf = lines.pop() || "";
|
|
416
|
-
for (const line of lines) {
|
|
417
|
-
if (line.trim()) processedLines.push(line.trim());
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Send chunks that total over 100 bytes
|
|
422
|
-
handleChunk('{"type":"event1"}\n'); // ~18 bytes
|
|
423
|
-
handleChunk('{"type":"event2"}\n'); // ~36 total
|
|
424
|
-
handleChunk('{"type":"event3"}\n'); // ~54 total
|
|
425
|
-
handleChunk('{"type":"event4"}\n'); // ~72 total
|
|
426
|
-
handleChunk('{"type":"event5"}\n'); // ~90 total
|
|
427
|
-
handleChunk('{"type":"event6"}\n'); // ~108 total — exceeds 100
|
|
428
|
-
|
|
429
|
-
assert.strictEqual(stdoutTruncated, true);
|
|
430
|
-
// Events before the threshold should have been processed
|
|
431
|
-
assert.ok(processedLines.length >= 4);
|
|
432
|
-
assert.ok(processedLines.length < 6, "Post-threshold events should not be processed");
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
it("does not set truncated flag when under threshold", () => {
|
|
436
|
-
const MAX = 10000;
|
|
437
|
-
let bufBytes = 0;
|
|
438
|
-
let stdoutTruncated = false;
|
|
439
|
-
|
|
440
|
-
function handleChunk(chunk: string) {
|
|
441
|
-
bufBytes += Buffer.byteLength(chunk);
|
|
442
|
-
if (bufBytes > MAX) {
|
|
443
|
-
stdoutTruncated = true;
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
handleChunk('{"type":"small"}\n');
|
|
449
|
-
assert.strictEqual(stdoutTruncated, false);
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// ── Integration test: dispatch truncation contract ──
|
|
454
|
-
|
|
455
|
-
describe("dispatch truncation", { skip: !hasPi ? "pi not available" : undefined }, () => {
|
|
456
|
-
it("sets truncated flag when stdout exceeds buffer limit", async () => {
|
|
457
|
-
// A normal-sized response should NOT have truncated set.
|
|
458
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wf-dispatch-trunc-"));
|
|
459
|
-
const sessDir = path.join(tmpDir, "sessions");
|
|
460
|
-
fs.mkdirSync(sessDir, { recursive: true });
|
|
461
|
-
|
|
462
|
-
const result = await dispatch(
|
|
463
|
-
{ agent: "default" },
|
|
464
|
-
{ name: "default" },
|
|
465
|
-
"Respond with exactly: OK",
|
|
466
|
-
{ cwd: process.cwd(), sessionLogDir: sessDir, stepName: "test" },
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
assert.strictEqual(result.status, "completed");
|
|
470
|
-
assert.strictEqual(result.truncated, undefined, "Normal response should not be truncated");
|
|
471
|
-
assert.strictEqual(result.warnings, undefined, "Normal response should have no warnings");
|
|
472
|
-
|
|
473
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
474
|
-
}, { timeout: 60000 });
|
|
475
|
-
});
|