@trailstep/core 0.1.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/LICENSE +176 -0
- package/README.md +15 -0
- package/dist/index.d.ts +629 -0
- package/dist/index.js +4654 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4654 @@
|
|
|
1
|
+
// src/contracts/failures/failure.ts
|
|
2
|
+
var TrailStepFailureError = class extends Error {
|
|
3
|
+
failure;
|
|
4
|
+
constructor(failure) {
|
|
5
|
+
super(failure.message);
|
|
6
|
+
this.name = "TrailStepFailureError";
|
|
7
|
+
this.failure = failure;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function validationFailure(message, details) {
|
|
11
|
+
return {
|
|
12
|
+
code: "validation_failed",
|
|
13
|
+
message,
|
|
14
|
+
...details === void 0 ? {} : { details }
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/agent-targeting/parse-trailstep-config/expand-agent-refs.ts
|
|
19
|
+
function expandAgentRefs(options) {
|
|
20
|
+
const expandedAgents = {};
|
|
21
|
+
const memo = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const name of Object.keys(options.agents)) {
|
|
23
|
+
expandedAgents[name] = expandTopLevelAgent({
|
|
24
|
+
name,
|
|
25
|
+
agents: options.agents,
|
|
26
|
+
memo,
|
|
27
|
+
stack: [],
|
|
28
|
+
rootRefPath: void 0
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const expandedWorkflows = options.workflows === void 0 ? void 0 : Object.fromEntries(
|
|
32
|
+
Object.entries(options.workflows).map(([workflowId, workflow]) => {
|
|
33
|
+
const workflowAgents = workflow.agents === void 0 ? void 0 : Object.fromEntries(
|
|
34
|
+
Object.entries(workflow.agents).map(([roleName, targets]) => [
|
|
35
|
+
roleName,
|
|
36
|
+
expandTargetArray({
|
|
37
|
+
path: `workflows.${workflowId}.agents.${roleName}`,
|
|
38
|
+
targets,
|
|
39
|
+
agents: options.agents,
|
|
40
|
+
memo,
|
|
41
|
+
stack: [],
|
|
42
|
+
rootRefPath: void 0
|
|
43
|
+
})
|
|
44
|
+
])
|
|
45
|
+
);
|
|
46
|
+
return [
|
|
47
|
+
workflowId,
|
|
48
|
+
{
|
|
49
|
+
...workflowAgents === void 0 ? {} : { agents: workflowAgents },
|
|
50
|
+
...workflow.settings === void 0 ? {} : { settings: workflow.settings }
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
return { agents: expandedAgents, workflows: expandedWorkflows };
|
|
56
|
+
}
|
|
57
|
+
function expandTopLevelAgent(options) {
|
|
58
|
+
const memoized = options.memo.get(options.name);
|
|
59
|
+
if (memoized !== void 0) {
|
|
60
|
+
return memoized;
|
|
61
|
+
}
|
|
62
|
+
const targets = options.agents[options.name];
|
|
63
|
+
if (targets === void 0) {
|
|
64
|
+
throwUnknownRef(`agents.${options.name}`, options.name);
|
|
65
|
+
}
|
|
66
|
+
const expanded = expandTargetArray({
|
|
67
|
+
path: `agents.${options.name}`,
|
|
68
|
+
targets,
|
|
69
|
+
agents: options.agents,
|
|
70
|
+
memo: options.memo,
|
|
71
|
+
stack: [...options.stack, options.name],
|
|
72
|
+
rootRefPath: options.rootRefPath
|
|
73
|
+
});
|
|
74
|
+
options.memo.set(options.name, expanded);
|
|
75
|
+
return expanded;
|
|
76
|
+
}
|
|
77
|
+
function expandTargetArray(options) {
|
|
78
|
+
return options.targets.flatMap((target, index) => {
|
|
79
|
+
if ("provider" in target) {
|
|
80
|
+
return [target];
|
|
81
|
+
}
|
|
82
|
+
const refPath = `${options.path}[${index}].ref`;
|
|
83
|
+
const referencedTargets = options.agents[target.ref];
|
|
84
|
+
if (referencedTargets === void 0) {
|
|
85
|
+
throwUnknownRef(refPath, target.ref);
|
|
86
|
+
}
|
|
87
|
+
const existingRefIndex = options.stack.indexOf(target.ref);
|
|
88
|
+
if (existingRefIndex !== -1) {
|
|
89
|
+
throwCycle(options.rootRefPath ?? refPath, [
|
|
90
|
+
...options.stack.slice(existingRefIndex),
|
|
91
|
+
target.ref
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
const memoized = options.memo.get(target.ref);
|
|
95
|
+
if (memoized !== void 0) {
|
|
96
|
+
return memoized;
|
|
97
|
+
}
|
|
98
|
+
const expanded = expandTargetArray({
|
|
99
|
+
path: `agents.${target.ref}`,
|
|
100
|
+
targets: referencedTargets,
|
|
101
|
+
agents: options.agents,
|
|
102
|
+
memo: options.memo,
|
|
103
|
+
stack: [...options.stack, target.ref],
|
|
104
|
+
rootRefPath: options.rootRefPath ?? refPath
|
|
105
|
+
});
|
|
106
|
+
options.memo.set(target.ref, expanded);
|
|
107
|
+
return expanded;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function throwUnknownRef(path, ref) {
|
|
111
|
+
throw new TrailStepFailureError({
|
|
112
|
+
code: "agent_ref_unknown",
|
|
113
|
+
message: "One or more agent refs reference an unknown top-level agent.",
|
|
114
|
+
details: { diagnostics: [`${path} references unknown agent '${ref}'.`] }
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function throwCycle(path, cycle) {
|
|
118
|
+
throw new TrailStepFailureError({
|
|
119
|
+
code: "agent_ref_cycle",
|
|
120
|
+
message: "One or more agent refs create a cycle.",
|
|
121
|
+
details: { diagnostics: [`${path} creates an agent ref cycle: ${cycle.join(" -> ")}.`] }
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/agent-targeting/parse-trailstep-config/parse-utils.ts
|
|
126
|
+
function throwValidationFailure(diagnostics) {
|
|
127
|
+
throw new TrailStepFailureError(
|
|
128
|
+
validationFailure("Invalid .trailstep/config.json.", { diagnostics })
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
function isRecord(value) {
|
|
132
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
|
+
}
|
|
134
|
+
function parseOptionalStringArray(path, value, diagnostics) {
|
|
135
|
+
if (value === void 0) {
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
139
|
+
diagnostics.push(`${path} must be an array of strings when present.`);
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
function parseOptionalStringRecord(path, value, diagnostics) {
|
|
145
|
+
if (value === void 0) {
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
if (!isRecord(value)) {
|
|
149
|
+
diagnostics.push(`${path} must be an object when present.`);
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
const env = {};
|
|
153
|
+
for (const [key, envValue] of Object.entries(value)) {
|
|
154
|
+
if (typeof envValue !== "string") {
|
|
155
|
+
diagnostics.push(`${path}.${key} must be a string.`);
|
|
156
|
+
} else {
|
|
157
|
+
env[key] = envValue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return env;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/agent-targeting/parse-trailstep-config/parse-agent-targets.ts
|
|
164
|
+
var THINKING_LEVELS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]);
|
|
165
|
+
function parseTargetArray(path, value, diagnostics) {
|
|
166
|
+
if (!Array.isArray(value)) {
|
|
167
|
+
diagnostics.push(`${path} must be an array.`);
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
return value.flatMap((target, index) => {
|
|
171
|
+
const targetPath = `${path}[${index}]`;
|
|
172
|
+
if (!isRecord(target)) {
|
|
173
|
+
diagnostics.push(`${targetPath} must be an object.`);
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
if (target.ref !== void 0) {
|
|
177
|
+
if (typeof target.ref !== "string" || target.ref.length === 0) {
|
|
178
|
+
diagnostics.push(`${targetPath}.ref must be a non-empty string when present.`);
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return [{ ref: target.ref }];
|
|
182
|
+
}
|
|
183
|
+
if (typeof target.provider !== "string" || target.provider.length === 0) {
|
|
184
|
+
diagnostics.push(`${targetPath}.provider must be a non-empty string.`);
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
if (target.model !== void 0 && typeof target.model !== "string") {
|
|
188
|
+
diagnostics.push(`${targetPath}.model must be a string when present.`);
|
|
189
|
+
}
|
|
190
|
+
if (target.thinking !== void 0 && !THINKING_LEVELS.has(target.thinking)) {
|
|
191
|
+
diagnostics.push(
|
|
192
|
+
`${targetPath}.thinking must be one of low, medium, high, xhigh, or max when present.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const args = parseOptionalStringArray(`${targetPath}.args`, target.args, diagnostics);
|
|
196
|
+
return [
|
|
197
|
+
{
|
|
198
|
+
provider: target.provider,
|
|
199
|
+
...typeof target.model === "string" ? { model: target.model } : {},
|
|
200
|
+
...typeof target.thinking === "string" && THINKING_LEVELS.has(target.thinking) ? { thinking: target.thinking } : {},
|
|
201
|
+
...args === void 0 ? {} : { args }
|
|
202
|
+
}
|
|
203
|
+
];
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/agent-targeting/parse-trailstep-config/parse-agent-mappings.ts
|
|
208
|
+
function parseAgentMappings(path, value, diagnostics) {
|
|
209
|
+
const mappings = {};
|
|
210
|
+
if (!isRecord(value)) {
|
|
211
|
+
diagnostics.push(`${path} must be an object.`);
|
|
212
|
+
return mappings;
|
|
213
|
+
}
|
|
214
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
215
|
+
mappings[name] = parseTargetArray(`${path}.${name}`, entry, diagnostics);
|
|
216
|
+
}
|
|
217
|
+
return mappings;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/agent-targeting/parse-trailstep-config/parse-custom-providers.ts
|
|
221
|
+
function parseCustomProviders(value, diagnostics) {
|
|
222
|
+
const customProviders = {};
|
|
223
|
+
if (!isRecord(value)) {
|
|
224
|
+
diagnostics.push("customProviders must be an object.");
|
|
225
|
+
return customProviders;
|
|
226
|
+
}
|
|
227
|
+
for (const [name, providerConfig] of Object.entries(value)) {
|
|
228
|
+
if (!isRecord(providerConfig)) {
|
|
229
|
+
diagnostics.push(`customProviders.${name} must be an object.`);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (typeof providerConfig.binary !== "string" || providerConfig.binary.length === 0) {
|
|
233
|
+
diagnostics.push(`customProviders.${name}.binary must be a non-empty string.`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const args = parseOptionalStringArray(
|
|
237
|
+
`customProviders.${name}.args`,
|
|
238
|
+
providerConfig.args,
|
|
239
|
+
diagnostics
|
|
240
|
+
);
|
|
241
|
+
const interactiveArgs = parseOptionalStringArray(
|
|
242
|
+
`customProviders.${name}.interactiveArgs`,
|
|
243
|
+
providerConfig.interactiveArgs,
|
|
244
|
+
diagnostics
|
|
245
|
+
);
|
|
246
|
+
const env = parseOptionalStringRecord(
|
|
247
|
+
`customProviders.${name}.env`,
|
|
248
|
+
providerConfig.env,
|
|
249
|
+
diagnostics
|
|
250
|
+
);
|
|
251
|
+
if (providerConfig.cwd !== void 0 && typeof providerConfig.cwd !== "string") {
|
|
252
|
+
diagnostics.push(`customProviders.${name}.cwd must be a string when present.`);
|
|
253
|
+
}
|
|
254
|
+
customProviders[name] = {
|
|
255
|
+
binary: providerConfig.binary,
|
|
256
|
+
...args === void 0 ? {} : { args },
|
|
257
|
+
...interactiveArgs === void 0 ? {} : { interactiveArgs },
|
|
258
|
+
...typeof providerConfig.cwd === "string" ? { cwd: providerConfig.cwd } : {},
|
|
259
|
+
...env === void 0 ? {} : { env }
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return customProviders;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/agent-targeting/parse-trailstep-config/parse-settings.ts
|
|
266
|
+
function parseSettings(path, value, diagnostics) {
|
|
267
|
+
if (value === void 0) {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
if (!isRecord(value)) {
|
|
271
|
+
diagnostics.push(`${path} must be an object when present.`);
|
|
272
|
+
return void 0;
|
|
273
|
+
}
|
|
274
|
+
const settings = { ...value };
|
|
275
|
+
if (value.retry !== void 0) {
|
|
276
|
+
if (!isRecord(value.retry)) {
|
|
277
|
+
diagnostics.push(`${path}.retry must be an object when present.`);
|
|
278
|
+
delete settings.retry;
|
|
279
|
+
} else if (value.retry.maxAttempts === void 0) {
|
|
280
|
+
delete settings.retry;
|
|
281
|
+
} else {
|
|
282
|
+
settings.retry = { maxAttempts: value.retry.maxAttempts };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (value.timeout !== void 0 && typeof value.timeout !== "number") {
|
|
286
|
+
diagnostics.push(`${path}.timeout must be a number when present.`);
|
|
287
|
+
delete settings.timeout;
|
|
288
|
+
}
|
|
289
|
+
return settings;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/agent-targeting/parse-trailstep-config/parse-workflow-agent-mappings.ts
|
|
293
|
+
function parseWorkflows(value, diagnostics) {
|
|
294
|
+
if (value === void 0) {
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
297
|
+
const workflows = {};
|
|
298
|
+
if (!isRecord(value)) {
|
|
299
|
+
diagnostics.push("workflows must be an object when present.");
|
|
300
|
+
return workflows;
|
|
301
|
+
}
|
|
302
|
+
for (const [workflowId, workflow] of Object.entries(value)) {
|
|
303
|
+
if (!isRecord(workflow)) {
|
|
304
|
+
diagnostics.push(`workflows.${workflowId} must be an object.`);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const agents = workflow.agents === void 0 ? void 0 : parseAgentMappings(`workflows.${workflowId}.agents`, workflow.agents, diagnostics);
|
|
308
|
+
const settings = parseSettings(
|
|
309
|
+
`workflows.${workflowId}.settings`,
|
|
310
|
+
workflow.settings,
|
|
311
|
+
diagnostics
|
|
312
|
+
);
|
|
313
|
+
workflows[workflowId] = {
|
|
314
|
+
...agents === void 0 ? {} : { agents },
|
|
315
|
+
...settings === void 0 ? {} : { settings }
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return workflows;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/known-cli-providers/providers/claude/claude-provider.ts
|
|
322
|
+
import { spawn } from "child_process";
|
|
323
|
+
import { writeFile } from "fs/promises";
|
|
324
|
+
import { performance } from "perf_hooks";
|
|
325
|
+
|
|
326
|
+
// src/known-cli-providers/envelopes/envelope.ts
|
|
327
|
+
function extractEnvelopeMetadata(rawStdout, options) {
|
|
328
|
+
const metadata = { harnessDurationMs: options.harnessDurationMs };
|
|
329
|
+
const envelope = parseEnvelopeObject(rawStdout);
|
|
330
|
+
if (envelope) {
|
|
331
|
+
const usage = camelCaseUsage(envelope.usage);
|
|
332
|
+
if (usage) {
|
|
333
|
+
metadata.usage = usage;
|
|
334
|
+
}
|
|
335
|
+
if (typeof envelope.total_cost_usd === "number") {
|
|
336
|
+
metadata.costUsd = envelope.total_cost_usd;
|
|
337
|
+
}
|
|
338
|
+
if (typeof envelope.duration_ms === "number") {
|
|
339
|
+
metadata.durationMs = envelope.duration_ms;
|
|
340
|
+
}
|
|
341
|
+
if (typeof envelope.num_turns === "number") {
|
|
342
|
+
metadata.turns = envelope.num_turns;
|
|
343
|
+
}
|
|
344
|
+
if (typeof envelope.session_id === "string") {
|
|
345
|
+
metadata.sessionId = envelope.session_id;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return metadata;
|
|
349
|
+
}
|
|
350
|
+
function extractEnvelopeOutput(rawStdout, options) {
|
|
351
|
+
const text = rawStdout.trim();
|
|
352
|
+
if (!text) {
|
|
353
|
+
throw new Error("Provider process returned empty stdout.");
|
|
354
|
+
}
|
|
355
|
+
const parsedEnvelope = parseJson(text);
|
|
356
|
+
if (isPlainObject(parsedEnvelope)) {
|
|
357
|
+
if (parsedEnvelope.is_error === true) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Provider reported an error: ${String(parsedEnvelope[options.resultField] ?? text)}`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
const resolved = resolveFieldValue(parsedEnvelope[options.resultField]);
|
|
363
|
+
if (resolved) {
|
|
364
|
+
return resolved;
|
|
365
|
+
}
|
|
366
|
+
if (typeof parsedEnvelope.content === "string") {
|
|
367
|
+
return parseObjectFromText(parsedEnvelope.content);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const streamResult = parseLastJsonLineResult(text, options.resultField);
|
|
371
|
+
if (streamResult) {
|
|
372
|
+
return streamResult;
|
|
373
|
+
}
|
|
374
|
+
return parseObjectFromText(text);
|
|
375
|
+
}
|
|
376
|
+
function extractEnvelopeText(rawStdout, options) {
|
|
377
|
+
const text = rawStdout.trim();
|
|
378
|
+
if (!text) {
|
|
379
|
+
throw new Error("Provider process returned empty stdout.");
|
|
380
|
+
}
|
|
381
|
+
const parsedEnvelope = parseJson(text);
|
|
382
|
+
if (isPlainObject(parsedEnvelope)) {
|
|
383
|
+
if (parsedEnvelope.is_error === true) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Provider reported an error: ${String(parsedEnvelope[options.resultField] ?? text)}`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const resolved = resolveFieldText(parsedEnvelope[options.resultField]);
|
|
389
|
+
if (resolved !== void 0) {
|
|
390
|
+
return resolved;
|
|
391
|
+
}
|
|
392
|
+
if (typeof parsedEnvelope.content === "string") {
|
|
393
|
+
return parsedEnvelope.content;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const streamResult = parseLastJsonLineText(text, options.resultField);
|
|
397
|
+
if (streamResult !== void 0) {
|
|
398
|
+
return streamResult;
|
|
399
|
+
}
|
|
400
|
+
return text;
|
|
401
|
+
}
|
|
402
|
+
function parseLastJsonLineText(text, resultField) {
|
|
403
|
+
const lines = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
404
|
+
for (const line of [...lines].reverse()) {
|
|
405
|
+
const parsed = parseJson(line);
|
|
406
|
+
if (!isPlainObject(parsed)) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const resolved = resolveFieldText(parsed[resultField]);
|
|
410
|
+
if (resolved !== void 0) {
|
|
411
|
+
return resolved;
|
|
412
|
+
}
|
|
413
|
+
if (typeof parsed.content === "string") {
|
|
414
|
+
return parsed.content;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return void 0;
|
|
418
|
+
}
|
|
419
|
+
function resolveFieldText(fieldValue) {
|
|
420
|
+
if (typeof fieldValue === "string") {
|
|
421
|
+
return fieldValue;
|
|
422
|
+
}
|
|
423
|
+
if (isPlainObject(fieldValue)) {
|
|
424
|
+
return extractTextFromContentBlocks(fieldValue);
|
|
425
|
+
}
|
|
426
|
+
return void 0;
|
|
427
|
+
}
|
|
428
|
+
function parseLastJsonLineResult(text, resultField) {
|
|
429
|
+
const lines = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
430
|
+
for (const line of [...lines].reverse()) {
|
|
431
|
+
const parsed = parseJson(line);
|
|
432
|
+
if (!isPlainObject(parsed)) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const resolved = resolveFieldValue(parsed[resultField]);
|
|
436
|
+
if (resolved) {
|
|
437
|
+
return resolved;
|
|
438
|
+
}
|
|
439
|
+
if (typeof parsed.content === "string") {
|
|
440
|
+
return parseObjectFromText(parsed.content);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
445
|
+
function resolveFieldValue(fieldValue) {
|
|
446
|
+
if (typeof fieldValue === "string") {
|
|
447
|
+
return parseObjectFromText(fieldValue);
|
|
448
|
+
}
|
|
449
|
+
if (isPlainObject(fieldValue)) {
|
|
450
|
+
const blockText = extractTextFromContentBlocks(fieldValue);
|
|
451
|
+
if (blockText !== void 0) {
|
|
452
|
+
return parseObjectFromText(blockText);
|
|
453
|
+
}
|
|
454
|
+
return fieldValue;
|
|
455
|
+
}
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
function extractTextFromContentBlocks(value) {
|
|
459
|
+
const content = value.content;
|
|
460
|
+
if (!Array.isArray(content)) {
|
|
461
|
+
return void 0;
|
|
462
|
+
}
|
|
463
|
+
const textParts = content.filter(isPlainObject).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text);
|
|
464
|
+
if (textParts.length === 0) {
|
|
465
|
+
return void 0;
|
|
466
|
+
}
|
|
467
|
+
return textParts.join("");
|
|
468
|
+
}
|
|
469
|
+
function parseObjectFromText(text) {
|
|
470
|
+
const parsed = parseJson(text.trim());
|
|
471
|
+
if (isPlainObject(parsed)) {
|
|
472
|
+
return parsed;
|
|
473
|
+
}
|
|
474
|
+
const firstBrace = text.indexOf("{");
|
|
475
|
+
const lastBrace = text.lastIndexOf("}");
|
|
476
|
+
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
477
|
+
throw new Error(`Provider response did not contain a JSON object: ${text}`);
|
|
478
|
+
}
|
|
479
|
+
const extracted = parseJson(text.slice(firstBrace, lastBrace + 1));
|
|
480
|
+
if (!isPlainObject(extracted)) {
|
|
481
|
+
throw new Error("Provider response JSON was not an object.");
|
|
482
|
+
}
|
|
483
|
+
return extracted;
|
|
484
|
+
}
|
|
485
|
+
function parseEnvelopeObject(rawStdout) {
|
|
486
|
+
const text = rawStdout.trim();
|
|
487
|
+
const parsed = parseJson(text);
|
|
488
|
+
if (isPlainObject(parsed)) {
|
|
489
|
+
return parsed;
|
|
490
|
+
}
|
|
491
|
+
const lines = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
492
|
+
for (const line of [...lines].reverse()) {
|
|
493
|
+
const parsedLine = parseJson(line);
|
|
494
|
+
if (isPlainObject(parsedLine)) {
|
|
495
|
+
return parsedLine;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return void 0;
|
|
499
|
+
}
|
|
500
|
+
function camelCaseUsage(value) {
|
|
501
|
+
if (!isPlainObject(value)) {
|
|
502
|
+
return void 0;
|
|
503
|
+
}
|
|
504
|
+
const usage = {};
|
|
505
|
+
for (const [key, fieldValue] of Object.entries(value)) {
|
|
506
|
+
if (typeof fieldValue === "number") {
|
|
507
|
+
usage[snakeToCamel(key)] = fieldValue;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
511
|
+
}
|
|
512
|
+
function snakeToCamel(value) {
|
|
513
|
+
return value.replace(/_([a-z])/gu, (_match, letter) => letter.toUpperCase());
|
|
514
|
+
}
|
|
515
|
+
function parseJson(text) {
|
|
516
|
+
try {
|
|
517
|
+
return JSON.parse(text);
|
|
518
|
+
} catch {
|
|
519
|
+
return void 0;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function isPlainObject(value) {
|
|
523
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/known-cli-providers/providers/prompt-file-reference.ts
|
|
527
|
+
function promptFileReference(promptFile) {
|
|
528
|
+
return `@${promptFile}`;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/known-cli-providers/providers/claude/claude-provider.ts
|
|
532
|
+
var CLAUDE_BINARY = "claude";
|
|
533
|
+
async function runWorking(request, runner = spawnClaudeCapturingStdout) {
|
|
534
|
+
const args = buildClaudeWorkingArgs(request);
|
|
535
|
+
let result;
|
|
536
|
+
const startedAt = performance.now();
|
|
537
|
+
try {
|
|
538
|
+
result = await runner({
|
|
539
|
+
command: CLAUDE_BINARY,
|
|
540
|
+
args,
|
|
541
|
+
cwd: request.cwd,
|
|
542
|
+
signal: request.signal
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
throw new TrailStepFailureError({
|
|
546
|
+
code: "agent_provider_spawn_error",
|
|
547
|
+
message: "claude provider process could not be started.",
|
|
548
|
+
details: { cause: error instanceof Error ? error.message : String(error) }
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const harnessDurationMs = Math.max(0, Math.round(performance.now() - startedAt));
|
|
552
|
+
if (result.exitCode !== 0) {
|
|
553
|
+
throw new TrailStepFailureError({
|
|
554
|
+
code: "agent_provider_failed",
|
|
555
|
+
message: `claude provider process exited with code ${result.exitCode}.`,
|
|
556
|
+
details: { exitCode: result.exitCode }
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
await writeCapturedOutput({
|
|
560
|
+
stdout: result.stdout,
|
|
561
|
+
outputFile: request.outputFile,
|
|
562
|
+
usageFile: request.usageFile,
|
|
563
|
+
captureMode: request.captureMode,
|
|
564
|
+
harnessDurationMs,
|
|
565
|
+
context: "working"
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function extractFailureContext(rawStdout) {
|
|
569
|
+
const metadata = extractEnvelopeMetadata(rawStdout, { harnessDurationMs: 0 });
|
|
570
|
+
let rawResultText;
|
|
571
|
+
try {
|
|
572
|
+
rawResultText = extractEnvelopeText(rawStdout, { resultField: "result" });
|
|
573
|
+
} catch {
|
|
574
|
+
rawResultText = void 0;
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
...metadata.sessionId === void 0 ? {} : { sessionId: metadata.sessionId },
|
|
578
|
+
...rawResultText === void 0 ? {} : { rawResultText }
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
async function writeCapturedOutput(options) {
|
|
582
|
+
const label = options.context === "repair" ? "repair " : "";
|
|
583
|
+
if (options.captureMode === "raw-text") {
|
|
584
|
+
let text;
|
|
585
|
+
try {
|
|
586
|
+
text = extractEnvelopeText(options.stdout, { resultField: "result" });
|
|
587
|
+
} catch (error) {
|
|
588
|
+
throw new TrailStepFailureError({
|
|
589
|
+
code: "agent_provider_output_invalid",
|
|
590
|
+
message: `claude provider ${label}stdout did not contain a usable result.`,
|
|
591
|
+
details: {
|
|
592
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
593
|
+
...extractFailureContext(options.stdout)
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
await writeFile(options.outputFile, text, "utf8");
|
|
598
|
+
} else {
|
|
599
|
+
let output;
|
|
600
|
+
try {
|
|
601
|
+
output = extractEnvelopeOutput(options.stdout, { resultField: "result" });
|
|
602
|
+
} catch (error) {
|
|
603
|
+
throw new TrailStepFailureError({
|
|
604
|
+
code: "agent_provider_output_invalid",
|
|
605
|
+
message: `claude provider ${label}stdout did not contain a usable JSON result.`,
|
|
606
|
+
details: {
|
|
607
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
608
|
+
...extractFailureContext(options.stdout)
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
await writeFile(options.outputFile, `${JSON.stringify(output, null, 2)}
|
|
613
|
+
`, "utf8");
|
|
614
|
+
}
|
|
615
|
+
if (options.usageFile) {
|
|
616
|
+
const metadata = extractEnvelopeMetadata(options.stdout, {
|
|
617
|
+
harnessDurationMs: options.harnessDurationMs
|
|
618
|
+
});
|
|
619
|
+
await writeFile(options.usageFile, `${JSON.stringify(metadata, null, 2)}
|
|
620
|
+
`, "utf8");
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function repairOutput(request, runner = spawnClaudeCapturingStdout) {
|
|
624
|
+
const prompt = buildClaudeRepairPrompt(request);
|
|
625
|
+
const args = buildClaudeRepairArgs(request);
|
|
626
|
+
let result;
|
|
627
|
+
const startedAt = performance.now();
|
|
628
|
+
try {
|
|
629
|
+
result = await runner({
|
|
630
|
+
command: CLAUDE_BINARY,
|
|
631
|
+
args,
|
|
632
|
+
cwd: request.cwd,
|
|
633
|
+
stdin: prompt,
|
|
634
|
+
signal: request.signal
|
|
635
|
+
});
|
|
636
|
+
} catch (error) {
|
|
637
|
+
throw new TrailStepFailureError({
|
|
638
|
+
code: "agent_provider_spawn_error",
|
|
639
|
+
message: "claude provider repair process could not be started.",
|
|
640
|
+
details: { cause: error instanceof Error ? error.message : String(error) }
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
const harnessDurationMs = Math.max(0, Math.round(performance.now() - startedAt));
|
|
644
|
+
if (result.exitCode !== 0) {
|
|
645
|
+
throw new TrailStepFailureError({
|
|
646
|
+
code: "agent_provider_failed",
|
|
647
|
+
message: `claude provider repair process exited with code ${result.exitCode}.`,
|
|
648
|
+
details: { exitCode: result.exitCode }
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
await writeCapturedOutput({
|
|
652
|
+
stdout: result.stdout,
|
|
653
|
+
outputFile: request.outputFile,
|
|
654
|
+
usageFile: request.usageFile,
|
|
655
|
+
captureMode: request.captureMode,
|
|
656
|
+
harnessDurationMs,
|
|
657
|
+
context: "repair"
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
function buildClaudeRepairArgs(request) {
|
|
661
|
+
const args = [
|
|
662
|
+
"--resume",
|
|
663
|
+
request.sessionId,
|
|
664
|
+
"-p",
|
|
665
|
+
"--output-format",
|
|
666
|
+
"json",
|
|
667
|
+
"--dangerously-skip-permissions"
|
|
668
|
+
];
|
|
669
|
+
if (request.model) {
|
|
670
|
+
args.push("--model", request.model);
|
|
671
|
+
}
|
|
672
|
+
if (request.thinking) {
|
|
673
|
+
args.push("--effort", request.thinking);
|
|
674
|
+
}
|
|
675
|
+
return args;
|
|
676
|
+
}
|
|
677
|
+
function buildClaudeRepairPrompt(request) {
|
|
678
|
+
if (request.captureMode === "raw-text") {
|
|
679
|
+
return [
|
|
680
|
+
"# TrailStep output repair",
|
|
681
|
+
"",
|
|
682
|
+
"Your previous final answer in this session could not be used as-is.",
|
|
683
|
+
"Do not redo the task or make any further changes - the work is already done.",
|
|
684
|
+
"Reply with the document content only, as your entire final answer: no JSON wrapper, no surrounding commentary, no markdown fences unless they are literally part of the document content itself.",
|
|
685
|
+
"",
|
|
686
|
+
"## Your previous final answer",
|
|
687
|
+
"",
|
|
688
|
+
request.rawResultText,
|
|
689
|
+
""
|
|
690
|
+
].join("\n");
|
|
691
|
+
}
|
|
692
|
+
return [
|
|
693
|
+
"# TrailStep output repair",
|
|
694
|
+
"",
|
|
695
|
+
"Your previous final answer in this session was not valid JSON matching the required schema.",
|
|
696
|
+
"Do not redo the task, re-read files, or make any further changes - the work is already done.",
|
|
697
|
+
"Reply with exactly one JSON object as your entire final answer: no prose, no markdown fences, no multiple JSON values.",
|
|
698
|
+
"",
|
|
699
|
+
"The JSON object must match this output schema:",
|
|
700
|
+
"",
|
|
701
|
+
"```json",
|
|
702
|
+
JSON.stringify(request.outputSchema, null, 2),
|
|
703
|
+
"```",
|
|
704
|
+
"",
|
|
705
|
+
"## Your previous final answer",
|
|
706
|
+
"",
|
|
707
|
+
request.rawResultText,
|
|
708
|
+
""
|
|
709
|
+
].join("\n");
|
|
710
|
+
}
|
|
711
|
+
function buildClaudeWorkingArgs(request) {
|
|
712
|
+
const args = [
|
|
713
|
+
"-p",
|
|
714
|
+
promptFileReference(request.promptFile),
|
|
715
|
+
"--output-format",
|
|
716
|
+
"json",
|
|
717
|
+
"--dangerously-skip-permissions"
|
|
718
|
+
];
|
|
719
|
+
if (request.model) {
|
|
720
|
+
args.push("--model", request.model);
|
|
721
|
+
}
|
|
722
|
+
if (request.thinking) {
|
|
723
|
+
args.push("--effort", request.thinking);
|
|
724
|
+
}
|
|
725
|
+
return args;
|
|
726
|
+
}
|
|
727
|
+
async function runInteractive(request, runner = spawnClaudeInteractive) {
|
|
728
|
+
const args = [];
|
|
729
|
+
if (request.model) {
|
|
730
|
+
args.push("--model", request.model);
|
|
731
|
+
}
|
|
732
|
+
if (request.permissionMode !== "prompt") {
|
|
733
|
+
args.push("--dangerously-skip-permissions");
|
|
734
|
+
}
|
|
735
|
+
if (!request.systemPromptFile) {
|
|
736
|
+
throw new TrailStepFailureError({
|
|
737
|
+
code: "agent_provider_invalid_request",
|
|
738
|
+
message: "claude provider's interactive mode requires systemPromptFile."
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
args.push("--append-system-prompt-file", request.systemPromptFile);
|
|
742
|
+
return await runner({
|
|
743
|
+
command: CLAUDE_BINARY,
|
|
744
|
+
args,
|
|
745
|
+
cwd: request.cwd,
|
|
746
|
+
shell: false,
|
|
747
|
+
stdio: "inherit",
|
|
748
|
+
env: request.env,
|
|
749
|
+
signal: request.signal
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
var spawnClaudeCapturingStdout = async ({
|
|
753
|
+
command,
|
|
754
|
+
args,
|
|
755
|
+
cwd,
|
|
756
|
+
stdin,
|
|
757
|
+
signal
|
|
758
|
+
}) => {
|
|
759
|
+
return await new Promise((resolve2, reject) => {
|
|
760
|
+
const hasStdin = stdin !== void 0;
|
|
761
|
+
const child = spawn(command, args, {
|
|
762
|
+
cwd,
|
|
763
|
+
shell: false,
|
|
764
|
+
stdio: [hasStdin ? "pipe" : "ignore", "pipe", "inherit"],
|
|
765
|
+
detached: process.platform !== "win32"
|
|
766
|
+
});
|
|
767
|
+
let stdout = "";
|
|
768
|
+
child.stdout?.on("data", (chunk) => {
|
|
769
|
+
stdout += chunk.toString();
|
|
770
|
+
});
|
|
771
|
+
child.stdin?.on("error", reject);
|
|
772
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree(child), { once: true });
|
|
773
|
+
child.on("error", reject);
|
|
774
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1, stdout }));
|
|
775
|
+
if (hasStdin) {
|
|
776
|
+
child.stdin?.end(stdin);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
};
|
|
780
|
+
var spawnClaudeInteractive = async ({
|
|
781
|
+
command,
|
|
782
|
+
args,
|
|
783
|
+
cwd,
|
|
784
|
+
env,
|
|
785
|
+
signal
|
|
786
|
+
}) => {
|
|
787
|
+
return await new Promise((resolve2, reject) => {
|
|
788
|
+
const child = spawn(command, args, {
|
|
789
|
+
cwd,
|
|
790
|
+
env,
|
|
791
|
+
signal,
|
|
792
|
+
detached: process.platform !== "win32",
|
|
793
|
+
shell: false,
|
|
794
|
+
stdio: "inherit"
|
|
795
|
+
});
|
|
796
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree(child), { once: true });
|
|
797
|
+
child.on("error", reject);
|
|
798
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1 }));
|
|
799
|
+
});
|
|
800
|
+
};
|
|
801
|
+
function terminateChildProcessTree(child) {
|
|
802
|
+
if (!child.pid) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
if (process.platform === "win32") {
|
|
806
|
+
spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
|
|
807
|
+
"error",
|
|
808
|
+
() => {
|
|
809
|
+
child.kill();
|
|
810
|
+
}
|
|
811
|
+
);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
try {
|
|
815
|
+
process.kill(-child.pid, "SIGTERM");
|
|
816
|
+
} catch {
|
|
817
|
+
child.kill();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
var claudeProvider = {
|
|
821
|
+
id: "claude",
|
|
822
|
+
runWorking,
|
|
823
|
+
repairOutput,
|
|
824
|
+
runInteractive
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// src/known-cli-providers/providers/codex/codex-provider.ts
|
|
828
|
+
import { spawn as spawn2 } from "child_process";
|
|
829
|
+
var CODEX_BINARY = "codex";
|
|
830
|
+
var SUPPORTED_CODEX_THINKING = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh"]);
|
|
831
|
+
async function runWorking2(request, runner = spawnCodexInheritingStdio) {
|
|
832
|
+
const args = buildCodexWorkingArgs(request);
|
|
833
|
+
let result;
|
|
834
|
+
try {
|
|
835
|
+
result = await runner({
|
|
836
|
+
command: CODEX_BINARY,
|
|
837
|
+
args,
|
|
838
|
+
cwd: request.cwd,
|
|
839
|
+
signal: request.signal
|
|
840
|
+
});
|
|
841
|
+
} catch (error) {
|
|
842
|
+
throw new TrailStepFailureError({
|
|
843
|
+
code: "agent_provider_spawn_error",
|
|
844
|
+
message: "codex provider process could not be started.",
|
|
845
|
+
details: { cause: error instanceof Error ? error.message : String(error) }
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
if (result.exitCode !== 0) {
|
|
849
|
+
throw new TrailStepFailureError({
|
|
850
|
+
code: "agent_provider_failed",
|
|
851
|
+
message: `codex provider process exited with code ${result.exitCode}.`,
|
|
852
|
+
details: { exitCode: result.exitCode }
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function buildCodexWorkingArgs(request) {
|
|
857
|
+
const args = ["exec", "--dangerously-bypass-approvals-and-sandbox"];
|
|
858
|
+
if (request.model) {
|
|
859
|
+
args.push("-m", request.model);
|
|
860
|
+
}
|
|
861
|
+
if (request.thinking) {
|
|
862
|
+
if (!SUPPORTED_CODEX_THINKING.has(request.thinking)) {
|
|
863
|
+
throw new TrailStepFailureError({
|
|
864
|
+
code: "agent_provider_thinking_unsupported",
|
|
865
|
+
message: `codex provider does not support thinking level '${request.thinking}'. Codex only supports low|medium|high|xhigh (no "max" tier).`,
|
|
866
|
+
details: { thinking: request.thinking }
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
args.push("-c", `model_reasoning_effort="${request.thinking}"`);
|
|
870
|
+
}
|
|
871
|
+
args.push("-o", request.outputFile);
|
|
872
|
+
args.push(promptFileReference(request.promptFile));
|
|
873
|
+
return args;
|
|
874
|
+
}
|
|
875
|
+
async function runInteractive2(request, runner = spawnCodexInteractive) {
|
|
876
|
+
const args = [];
|
|
877
|
+
if (request.model) {
|
|
878
|
+
args.push("--model", request.model);
|
|
879
|
+
}
|
|
880
|
+
args.push(
|
|
881
|
+
request.systemPromptFile ? promptFileReference(request.systemPromptFile) : request.prompt
|
|
882
|
+
);
|
|
883
|
+
return await runner({
|
|
884
|
+
command: CODEX_BINARY,
|
|
885
|
+
args,
|
|
886
|
+
cwd: request.cwd,
|
|
887
|
+
shell: false,
|
|
888
|
+
stdio: "inherit",
|
|
889
|
+
env: request.env,
|
|
890
|
+
signal: request.signal
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
var spawnCodexInheritingStdio = async ({ command, args, cwd, signal }) => {
|
|
894
|
+
return await new Promise((resolve2, reject) => {
|
|
895
|
+
const child = spawn2(command, args, {
|
|
896
|
+
cwd,
|
|
897
|
+
shell: false,
|
|
898
|
+
stdio: "inherit",
|
|
899
|
+
detached: process.platform !== "win32"
|
|
900
|
+
});
|
|
901
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree2(child), { once: true });
|
|
902
|
+
child.on("error", reject);
|
|
903
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1, stdout: "" }));
|
|
904
|
+
});
|
|
905
|
+
};
|
|
906
|
+
var spawnCodexInteractive = async ({
|
|
907
|
+
command,
|
|
908
|
+
args,
|
|
909
|
+
cwd,
|
|
910
|
+
env,
|
|
911
|
+
signal
|
|
912
|
+
}) => {
|
|
913
|
+
return await new Promise((resolve2, reject) => {
|
|
914
|
+
const child = spawn2(command, args, {
|
|
915
|
+
cwd,
|
|
916
|
+
env,
|
|
917
|
+
signal,
|
|
918
|
+
detached: process.platform !== "win32",
|
|
919
|
+
shell: false,
|
|
920
|
+
stdio: "inherit"
|
|
921
|
+
});
|
|
922
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree2(child), { once: true });
|
|
923
|
+
child.on("error", reject);
|
|
924
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1 }));
|
|
925
|
+
});
|
|
926
|
+
};
|
|
927
|
+
function terminateChildProcessTree2(child) {
|
|
928
|
+
if (!child.pid) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (process.platform === "win32") {
|
|
932
|
+
spawn2("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
|
|
933
|
+
"error",
|
|
934
|
+
() => {
|
|
935
|
+
child.kill();
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
try {
|
|
941
|
+
process.kill(-child.pid, "SIGTERM");
|
|
942
|
+
} catch {
|
|
943
|
+
child.kill();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
var codexProvider = {
|
|
947
|
+
id: "codex",
|
|
948
|
+
runWorking: runWorking2,
|
|
949
|
+
runInteractive: runInteractive2
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
// src/known-cli-providers/providers/gemini/gemini-provider.ts
|
|
953
|
+
import { spawn as spawn3 } from "child_process";
|
|
954
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
955
|
+
var GEMINI_BINARY = "gemini";
|
|
956
|
+
var GEMINI_RESULT_FIELD = "response";
|
|
957
|
+
async function runWorking3(request, runner = spawnGeminiCapturingStdout) {
|
|
958
|
+
const args = buildGeminiWorkingArgs(request);
|
|
959
|
+
let result;
|
|
960
|
+
try {
|
|
961
|
+
result = await runner({
|
|
962
|
+
command: GEMINI_BINARY,
|
|
963
|
+
args,
|
|
964
|
+
cwd: request.cwd,
|
|
965
|
+
signal: request.signal
|
|
966
|
+
});
|
|
967
|
+
} catch (error) {
|
|
968
|
+
throw new TrailStepFailureError({
|
|
969
|
+
code: "agent_provider_spawn_error",
|
|
970
|
+
message: "gemini provider process could not be started.",
|
|
971
|
+
details: { cause: error instanceof Error ? error.message : String(error) }
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
if (result.exitCode !== 0) {
|
|
975
|
+
throw new TrailStepFailureError({
|
|
976
|
+
code: "agent_provider_failed",
|
|
977
|
+
message: `gemini provider process exited with code ${result.exitCode}.`,
|
|
978
|
+
details: { exitCode: result.exitCode }
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
if (request.captureMode === "raw-text") {
|
|
982
|
+
let text;
|
|
983
|
+
try {
|
|
984
|
+
text = extractEnvelopeText(result.stdout, { resultField: GEMINI_RESULT_FIELD });
|
|
985
|
+
} catch (error) {
|
|
986
|
+
throw new TrailStepFailureError({
|
|
987
|
+
code: "agent_provider_output_invalid",
|
|
988
|
+
message: "gemini provider stdout did not contain a usable result.",
|
|
989
|
+
details: { cause: error instanceof Error ? error.message : String(error) }
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
await writeFile2(request.outputFile, text, "utf8");
|
|
993
|
+
} else {
|
|
994
|
+
let output;
|
|
995
|
+
try {
|
|
996
|
+
output = extractEnvelopeOutput(result.stdout, { resultField: GEMINI_RESULT_FIELD });
|
|
997
|
+
} catch (error) {
|
|
998
|
+
throw new TrailStepFailureError({
|
|
999
|
+
code: "agent_provider_output_invalid",
|
|
1000
|
+
message: "gemini provider stdout did not contain a usable JSON result.",
|
|
1001
|
+
details: { cause: error instanceof Error ? error.message : String(error) }
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
await writeFile2(request.outputFile, `${JSON.stringify(output, null, 2)}
|
|
1005
|
+
`, "utf8");
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function buildGeminiWorkingArgs(request) {
|
|
1009
|
+
const args = ["-p", promptFileReference(request.promptFile), "--yolo"];
|
|
1010
|
+
if (request.model) {
|
|
1011
|
+
args.push("-m", request.model);
|
|
1012
|
+
}
|
|
1013
|
+
args.push("--output-format", "json");
|
|
1014
|
+
return args;
|
|
1015
|
+
}
|
|
1016
|
+
async function runInteractive3(request, runner = spawnGeminiInteractive) {
|
|
1017
|
+
const args = [];
|
|
1018
|
+
if (request.model) {
|
|
1019
|
+
args.push("-m", request.model);
|
|
1020
|
+
}
|
|
1021
|
+
args.push(
|
|
1022
|
+
request.systemPromptFile ? promptFileReference(request.systemPromptFile) : request.prompt
|
|
1023
|
+
);
|
|
1024
|
+
return await runner({
|
|
1025
|
+
command: GEMINI_BINARY,
|
|
1026
|
+
args,
|
|
1027
|
+
cwd: request.cwd,
|
|
1028
|
+
shell: false,
|
|
1029
|
+
stdio: "inherit",
|
|
1030
|
+
env: request.env,
|
|
1031
|
+
signal: request.signal
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
var spawnGeminiCapturingStdout = async ({
|
|
1035
|
+
command,
|
|
1036
|
+
args,
|
|
1037
|
+
cwd,
|
|
1038
|
+
signal
|
|
1039
|
+
}) => {
|
|
1040
|
+
return await new Promise((resolve2, reject) => {
|
|
1041
|
+
const child = spawn3(command, args, {
|
|
1042
|
+
cwd,
|
|
1043
|
+
shell: false,
|
|
1044
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
1045
|
+
detached: process.platform !== "win32"
|
|
1046
|
+
});
|
|
1047
|
+
let stdout = "";
|
|
1048
|
+
child.stdout?.on("data", (chunk) => {
|
|
1049
|
+
stdout += chunk.toString();
|
|
1050
|
+
});
|
|
1051
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree3(child), { once: true });
|
|
1052
|
+
child.on("error", reject);
|
|
1053
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1, stdout }));
|
|
1054
|
+
});
|
|
1055
|
+
};
|
|
1056
|
+
var spawnGeminiInteractive = async ({
|
|
1057
|
+
command,
|
|
1058
|
+
args,
|
|
1059
|
+
cwd,
|
|
1060
|
+
env,
|
|
1061
|
+
signal
|
|
1062
|
+
}) => {
|
|
1063
|
+
return await new Promise((resolve2, reject) => {
|
|
1064
|
+
const child = spawn3(command, args, {
|
|
1065
|
+
cwd,
|
|
1066
|
+
env,
|
|
1067
|
+
signal,
|
|
1068
|
+
detached: process.platform !== "win32",
|
|
1069
|
+
shell: false,
|
|
1070
|
+
stdio: "inherit"
|
|
1071
|
+
});
|
|
1072
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree3(child), { once: true });
|
|
1073
|
+
child.on("error", reject);
|
|
1074
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1 }));
|
|
1075
|
+
});
|
|
1076
|
+
};
|
|
1077
|
+
function terminateChildProcessTree3(child) {
|
|
1078
|
+
if (!child.pid) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (process.platform === "win32") {
|
|
1082
|
+
spawn3("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
|
|
1083
|
+
"error",
|
|
1084
|
+
() => {
|
|
1085
|
+
child.kill();
|
|
1086
|
+
}
|
|
1087
|
+
);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
process.kill(-child.pid, "SIGTERM");
|
|
1092
|
+
} catch {
|
|
1093
|
+
child.kill();
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
var geminiProvider = {
|
|
1097
|
+
id: "gemini",
|
|
1098
|
+
runWorking: runWorking3,
|
|
1099
|
+
runInteractive: runInteractive3
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// src/known-cli-providers/providers/pi/pi-provider.ts
|
|
1103
|
+
import { spawn as spawn4 } from "child_process";
|
|
1104
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
1105
|
+
import { StringDecoder } from "string_decoder";
|
|
1106
|
+
|
|
1107
|
+
// src/known-cli-providers/process/resolve-cli-command.ts
|
|
1108
|
+
import { constants } from "fs";
|
|
1109
|
+
import { access, readFile } from "fs/promises";
|
|
1110
|
+
import { dirname, extname, join } from "path";
|
|
1111
|
+
var WINDOWS_EXECUTABLE_EXTENSIONS = [".cmd", ".exe", ".bat", ""];
|
|
1112
|
+
async function resolveCliCommandForSpawn(request, options = {}) {
|
|
1113
|
+
if ((options.platform ?? process.platform) !== "win32") {
|
|
1114
|
+
return request;
|
|
1115
|
+
}
|
|
1116
|
+
const commandPath = await findWindowsCommandPath(request.command, request.env ?? process.env);
|
|
1117
|
+
if (!commandPath) {
|
|
1118
|
+
return request;
|
|
1119
|
+
}
|
|
1120
|
+
if (isWindowsCommandShim(commandPath)) {
|
|
1121
|
+
const nodeScript = await readNpmCmdShimEntrypoint(commandPath);
|
|
1122
|
+
if (nodeScript) {
|
|
1123
|
+
return {
|
|
1124
|
+
command: options.execPath ?? process.execPath,
|
|
1125
|
+
args: [nodeScript, ...request.args]
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
command: "cmd.exe",
|
|
1130
|
+
args: ["/d", "/s", "/c", commandPath, ...request.args]
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
return { command: commandPath, args: request.args };
|
|
1134
|
+
}
|
|
1135
|
+
async function findWindowsCommandPath(command, env) {
|
|
1136
|
+
const commandCandidates = windowsCommandCandidates(command);
|
|
1137
|
+
if (hasPathSeparator(command)) {
|
|
1138
|
+
return await firstExistingPath(commandCandidates);
|
|
1139
|
+
}
|
|
1140
|
+
const pathValue = readCaseInsensitiveEnv(env, "PATH");
|
|
1141
|
+
if (!pathValue) {
|
|
1142
|
+
return await firstExistingPath(commandCandidates);
|
|
1143
|
+
}
|
|
1144
|
+
for (const directory of pathValue.split(windowsPathDelimiter()).filter(Boolean)) {
|
|
1145
|
+
const found = await firstExistingPath(
|
|
1146
|
+
commandCandidates.map((candidate) => join(directory, candidate))
|
|
1147
|
+
);
|
|
1148
|
+
if (found) {
|
|
1149
|
+
return found;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return void 0;
|
|
1153
|
+
}
|
|
1154
|
+
function windowsCommandCandidates(command) {
|
|
1155
|
+
if (extname(command)) {
|
|
1156
|
+
return [command];
|
|
1157
|
+
}
|
|
1158
|
+
return WINDOWS_EXECUTABLE_EXTENSIONS.map((extension) => `${command}${extension}`);
|
|
1159
|
+
}
|
|
1160
|
+
async function firstExistingPath(paths) {
|
|
1161
|
+
for (const path of paths) {
|
|
1162
|
+
try {
|
|
1163
|
+
await access(path, constants.X_OK);
|
|
1164
|
+
return path;
|
|
1165
|
+
} catch {
|
|
1166
|
+
try {
|
|
1167
|
+
await access(path, constants.F_OK);
|
|
1168
|
+
return path;
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return void 0;
|
|
1174
|
+
}
|
|
1175
|
+
async function readNpmCmdShimEntrypoint(commandPath) {
|
|
1176
|
+
let shim;
|
|
1177
|
+
try {
|
|
1178
|
+
shim = await readFile(commandPath, "utf8");
|
|
1179
|
+
} catch {
|
|
1180
|
+
return void 0;
|
|
1181
|
+
}
|
|
1182
|
+
const match = shim.match(/"%dp0%\\?([^"]+?\.js)"\s+%\*/i);
|
|
1183
|
+
if (!match?.[1]) {
|
|
1184
|
+
return void 0;
|
|
1185
|
+
}
|
|
1186
|
+
return join(dirname(commandPath), match[1].replace(/\\/g, "/"));
|
|
1187
|
+
}
|
|
1188
|
+
function isWindowsCommandShim(path) {
|
|
1189
|
+
const extension = extname(path).toLowerCase();
|
|
1190
|
+
return extension === ".cmd" || extension === ".bat";
|
|
1191
|
+
}
|
|
1192
|
+
function hasPathSeparator(command) {
|
|
1193
|
+
return command.includes("/") || command.includes("\\");
|
|
1194
|
+
}
|
|
1195
|
+
function readCaseInsensitiveEnv(env, key) {
|
|
1196
|
+
return Object.entries(env).find(([name]) => name.toLowerCase() === key.toLowerCase())?.[1];
|
|
1197
|
+
}
|
|
1198
|
+
function windowsPathDelimiter() {
|
|
1199
|
+
return ";";
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// src/known-cli-providers/providers/pi/pi-provider.ts
|
|
1203
|
+
var PI_BINARY = "pi";
|
|
1204
|
+
var PI_RESULT_FIELD = "message";
|
|
1205
|
+
var PI_STDOUT_FALLBACK_TAIL_CHARS = 128e3;
|
|
1206
|
+
async function runWorking4(request, runner = spawnPiCapturingStdout) {
|
|
1207
|
+
const args = buildPiWorkingArgs(request);
|
|
1208
|
+
let result;
|
|
1209
|
+
try {
|
|
1210
|
+
result = await runner({ command: PI_BINARY, args, cwd: request.cwd, signal: request.signal });
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
throw new TrailStepFailureError({
|
|
1213
|
+
code: "agent_provider_spawn_error",
|
|
1214
|
+
message: "pi provider process could not be started.",
|
|
1215
|
+
details: { cause: error instanceof Error ? error.message : String(error) }
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
if (request.captureMode === "raw-text") {
|
|
1219
|
+
let text;
|
|
1220
|
+
try {
|
|
1221
|
+
text = extractEnvelopeText(result.stdout, { resultField: PI_RESULT_FIELD });
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
throwPiOutputFailure({ exitCode: result.exitCode, error, json: false });
|
|
1224
|
+
}
|
|
1225
|
+
await writeFile3(request.outputFile, text, "utf8");
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
let output;
|
|
1229
|
+
try {
|
|
1230
|
+
output = extractEnvelopeOutput(result.stdout, { resultField: PI_RESULT_FIELD });
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
throwPiOutputFailure({ exitCode: result.exitCode, error, json: true });
|
|
1233
|
+
}
|
|
1234
|
+
await writeFile3(request.outputFile, `${JSON.stringify(output, null, 2)}
|
|
1235
|
+
`, "utf8");
|
|
1236
|
+
}
|
|
1237
|
+
function throwPiOutputFailure(options) {
|
|
1238
|
+
if (options.exitCode !== 0) {
|
|
1239
|
+
throw new TrailStepFailureError({
|
|
1240
|
+
code: "agent_provider_failed",
|
|
1241
|
+
message: `pi provider process exited with code ${options.exitCode}.`,
|
|
1242
|
+
details: { exitCode: options.exitCode }
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
throw new TrailStepFailureError({
|
|
1246
|
+
code: "agent_provider_output_invalid",
|
|
1247
|
+
message: options.json ? "pi provider stdout did not contain a usable JSON result." : "pi provider stdout did not contain a usable result.",
|
|
1248
|
+
details: {
|
|
1249
|
+
cause: options.error instanceof Error ? options.error.message : String(options.error)
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
function buildPiWorkingArgs(request) {
|
|
1254
|
+
const args = ["-p"];
|
|
1255
|
+
if (request.model) {
|
|
1256
|
+
args.push("--model", request.model);
|
|
1257
|
+
}
|
|
1258
|
+
if (request.thinking) {
|
|
1259
|
+
args.push("--thinking", request.thinking);
|
|
1260
|
+
}
|
|
1261
|
+
args.push(promptFileReference(request.promptFile));
|
|
1262
|
+
args.push("--mode", "json");
|
|
1263
|
+
return args;
|
|
1264
|
+
}
|
|
1265
|
+
async function runInteractive4(request, runner = spawnPiInteractive) {
|
|
1266
|
+
const args = [];
|
|
1267
|
+
if (request.model) {
|
|
1268
|
+
args.push("--model", request.model);
|
|
1269
|
+
}
|
|
1270
|
+
args.push(
|
|
1271
|
+
request.systemPromptFile ? promptFileReference(request.systemPromptFile) : request.prompt
|
|
1272
|
+
);
|
|
1273
|
+
return await runner({
|
|
1274
|
+
command: PI_BINARY,
|
|
1275
|
+
args,
|
|
1276
|
+
cwd: request.cwd,
|
|
1277
|
+
shell: false,
|
|
1278
|
+
stdio: "inherit",
|
|
1279
|
+
env: request.env,
|
|
1280
|
+
signal: request.signal
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
var spawnPiCapturingStdout = async ({ command, args, cwd, signal }) => {
|
|
1284
|
+
const executable = await resolveCliCommandForSpawn({ command, args });
|
|
1285
|
+
return await new Promise((resolve2, reject) => {
|
|
1286
|
+
const child = spawn4(executable.command, executable.args, {
|
|
1287
|
+
cwd,
|
|
1288
|
+
shell: false,
|
|
1289
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
1290
|
+
detached: process.platform !== "win32"
|
|
1291
|
+
});
|
|
1292
|
+
const stdout = createPiJsonStreamStdoutCollector();
|
|
1293
|
+
child.stdout?.on("data", (chunk) => {
|
|
1294
|
+
stdout.append(chunk);
|
|
1295
|
+
});
|
|
1296
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree4(child), { once: true });
|
|
1297
|
+
child.on("error", reject);
|
|
1298
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1, stdout: stdout.finish() }));
|
|
1299
|
+
});
|
|
1300
|
+
};
|
|
1301
|
+
function createPiJsonStreamStdoutCollector() {
|
|
1302
|
+
const decoder = new StringDecoder("utf8");
|
|
1303
|
+
let pendingLine = "";
|
|
1304
|
+
let latestResultLine;
|
|
1305
|
+
let fallbackTail = "";
|
|
1306
|
+
function appendText(text) {
|
|
1307
|
+
fallbackTail = trimFallbackTail(`${fallbackTail}${text}`);
|
|
1308
|
+
pendingLine += text;
|
|
1309
|
+
while (true) {
|
|
1310
|
+
const newlineIndex = pendingLine.indexOf("\n");
|
|
1311
|
+
if (newlineIndex === -1) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
const line = pendingLine.slice(0, newlineIndex).replace(/\r$/u, "");
|
|
1315
|
+
rememberLine(line);
|
|
1316
|
+
pendingLine = pendingLine.slice(newlineIndex + 1);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
append(chunk) {
|
|
1321
|
+
appendText(decoder.write(chunk));
|
|
1322
|
+
},
|
|
1323
|
+
finish() {
|
|
1324
|
+
appendText(decoder.end());
|
|
1325
|
+
rememberLine(pendingLine);
|
|
1326
|
+
pendingLine = "";
|
|
1327
|
+
return latestResultLine ?? fallbackTail;
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
function rememberLine(line) {
|
|
1331
|
+
const trimmed = line.trim();
|
|
1332
|
+
if (trimmed && isPiResultCandidateLine(trimmed)) {
|
|
1333
|
+
latestResultLine = trimmed;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
function trimFallbackTail(value) {
|
|
1338
|
+
if (value.length <= PI_STDOUT_FALLBACK_TAIL_CHARS) {
|
|
1339
|
+
return value;
|
|
1340
|
+
}
|
|
1341
|
+
return value.slice(-PI_STDOUT_FALLBACK_TAIL_CHARS);
|
|
1342
|
+
}
|
|
1343
|
+
function isPiResultCandidateLine(line) {
|
|
1344
|
+
return line.includes(`"${PI_RESULT_FIELD}"`);
|
|
1345
|
+
}
|
|
1346
|
+
var spawnPiInteractive = async ({
|
|
1347
|
+
command,
|
|
1348
|
+
args,
|
|
1349
|
+
cwd,
|
|
1350
|
+
env,
|
|
1351
|
+
signal
|
|
1352
|
+
}) => {
|
|
1353
|
+
const executable = await resolveCliCommandForSpawn({ command, args, env });
|
|
1354
|
+
return await new Promise((resolve2, reject) => {
|
|
1355
|
+
const child = spawn4(executable.command, executable.args, {
|
|
1356
|
+
cwd,
|
|
1357
|
+
env,
|
|
1358
|
+
signal,
|
|
1359
|
+
detached: process.platform !== "win32",
|
|
1360
|
+
shell: false,
|
|
1361
|
+
stdio: "inherit"
|
|
1362
|
+
});
|
|
1363
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree4(child), { once: true });
|
|
1364
|
+
child.on("error", reject);
|
|
1365
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1 }));
|
|
1366
|
+
});
|
|
1367
|
+
};
|
|
1368
|
+
function terminateChildProcessTree4(child) {
|
|
1369
|
+
if (!child.pid) {
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (process.platform === "win32") {
|
|
1373
|
+
spawn4("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
|
|
1374
|
+
"error",
|
|
1375
|
+
() => {
|
|
1376
|
+
child.kill();
|
|
1377
|
+
}
|
|
1378
|
+
);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
try {
|
|
1382
|
+
process.kill(-child.pid, "SIGTERM");
|
|
1383
|
+
} catch {
|
|
1384
|
+
child.kill();
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
var piProvider = {
|
|
1388
|
+
id: "pi",
|
|
1389
|
+
runWorking: runWorking4,
|
|
1390
|
+
runInteractive: runInteractive4
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
// src/known-cli-providers/registry/provider-registry.ts
|
|
1394
|
+
var providerRegistry = {
|
|
1395
|
+
claude: claudeProvider,
|
|
1396
|
+
codex: codexProvider,
|
|
1397
|
+
pi: piProvider,
|
|
1398
|
+
gemini: geminiProvider
|
|
1399
|
+
};
|
|
1400
|
+
function isProviderRegistryKey(provider) {
|
|
1401
|
+
return Object.hasOwn(providerRegistry, provider);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// src/agent-targeting/parse-trailstep-config/validate-provider-references.ts
|
|
1405
|
+
function validateProviderReferences(options) {
|
|
1406
|
+
const providerViolations = [];
|
|
1407
|
+
validateAgentMappings("agents", options.agents, options.providerNames, providerViolations);
|
|
1408
|
+
if (options.workflows !== void 0) {
|
|
1409
|
+
for (const [workflowId, workflow] of Object.entries(options.workflows)) {
|
|
1410
|
+
validateAgentMappings(
|
|
1411
|
+
`workflows.${workflowId}.agents`,
|
|
1412
|
+
workflow.agents,
|
|
1413
|
+
options.providerNames,
|
|
1414
|
+
providerViolations
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
if (providerViolations.length > 0) {
|
|
1419
|
+
throw new TrailStepFailureError({
|
|
1420
|
+
code: "agent_provider_unknown",
|
|
1421
|
+
message: "One or more agent targets reference a provider that is neither a built-in provider nor a declared customProviders entry.",
|
|
1422
|
+
details: { diagnostics: providerViolations }
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
function validateAgentMappings(path, mappings, providerNames, diagnostics) {
|
|
1427
|
+
if (mappings === void 0) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
for (const [name, targets] of Object.entries(mappings)) {
|
|
1431
|
+
validateTargetReferences(`${path}.${name}`, targets, providerNames, diagnostics);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function validateTargetReferences(path, targets, providerNames, diagnostics) {
|
|
1435
|
+
for (const [index, target] of targets.entries()) {
|
|
1436
|
+
if (!("provider" in target)) {
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (!providerNames.has(target.provider) && !isProviderRegistryKey(target.provider)) {
|
|
1440
|
+
diagnostics.push(
|
|
1441
|
+
`${path}[${index}].provider references unknown provider '${target.provider}'. Declare it under customProviders or use a built-in provider.`
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// src/agent-targeting/parse-trailstep-config/parse-trailstep-config.ts
|
|
1448
|
+
var parsedConfigs = /* @__PURE__ */ new WeakSet();
|
|
1449
|
+
function isParsedTrailStepConfig(value) {
|
|
1450
|
+
return isRecord(value) && parsedConfigs.has(value);
|
|
1451
|
+
}
|
|
1452
|
+
function parseTrailStepConfig(value) {
|
|
1453
|
+
const diagnostics = [];
|
|
1454
|
+
if (!isRecord(value)) {
|
|
1455
|
+
throwValidationFailure(["config must be an object."]);
|
|
1456
|
+
}
|
|
1457
|
+
if (value.version !== 1) {
|
|
1458
|
+
diagnostics.push("version must be 1.");
|
|
1459
|
+
}
|
|
1460
|
+
const customProviders = parseCustomProviders(value.customProviders, diagnostics);
|
|
1461
|
+
const providerNames = new Set(Object.keys(customProviders));
|
|
1462
|
+
const agents = parseAgentMappings("agents", value.agents, diagnostics);
|
|
1463
|
+
const settings = parseSettings("settings", value.settings, diagnostics);
|
|
1464
|
+
const workflows = parseWorkflows(value.workflows, diagnostics);
|
|
1465
|
+
if (diagnostics.length > 0) {
|
|
1466
|
+
throwValidationFailure(diagnostics);
|
|
1467
|
+
}
|
|
1468
|
+
validateProviderReferences({ agents, workflows, providerNames });
|
|
1469
|
+
const expanded = expandAgentRefs({ agents, workflows });
|
|
1470
|
+
const config = {
|
|
1471
|
+
version: 1,
|
|
1472
|
+
customProviders,
|
|
1473
|
+
agents: expanded.agents,
|
|
1474
|
+
...settings === void 0 ? {} : { settings },
|
|
1475
|
+
...expanded.workflows === void 0 ? {} : { workflows: expanded.workflows }
|
|
1476
|
+
};
|
|
1477
|
+
parsedConfigs.add(config);
|
|
1478
|
+
return config;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// src/agent-targeting/resolve-agent-targets/resolve-agent-targets.ts
|
|
1482
|
+
function resolveAgentTargets(options) {
|
|
1483
|
+
const workflowMappings = options.config.workflows?.[options.workflowId]?.agents;
|
|
1484
|
+
const targetLists = [
|
|
1485
|
+
workflowMappings?.[options.roleName],
|
|
1486
|
+
options.config.agents[options.roleSize],
|
|
1487
|
+
options.config.agents.default
|
|
1488
|
+
];
|
|
1489
|
+
const targets = targetLists.flatMap(
|
|
1490
|
+
(targets2) => targets2 && targets2.length > 0 ? [...targets2] : []
|
|
1491
|
+
);
|
|
1492
|
+
if (targets.length > 0) {
|
|
1493
|
+
return targets;
|
|
1494
|
+
}
|
|
1495
|
+
throw new TrailStepFailureError({
|
|
1496
|
+
code: "agent_targets_unavailable",
|
|
1497
|
+
message: `No agent targets found for role ${options.roleName} with size ${options.roleSize} in workflow ${options.workflowId}.`,
|
|
1498
|
+
details: {
|
|
1499
|
+
roleName: options.roleName,
|
|
1500
|
+
roleSize: options.roleSize,
|
|
1501
|
+
workflowId: options.workflowId
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// src/runtime/artifacts/run-storage.ts
|
|
1507
|
+
import { appendFile, mkdir, readFile as readFile2, writeFile as writeFile4 } from "fs/promises";
|
|
1508
|
+
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
1509
|
+
function defaultRunsRoot(cwd) {
|
|
1510
|
+
return join2(cwd, ".trailstep", "runs");
|
|
1511
|
+
}
|
|
1512
|
+
async function createRunDirectory(options) {
|
|
1513
|
+
const runsRoot = options.runsRoot ?? defaultRunsRoot(options.cwd);
|
|
1514
|
+
await mkdir(runsRoot, { recursive: true });
|
|
1515
|
+
await ensureTrailStepGitignoreForRunsRoot(runsRoot);
|
|
1516
|
+
for (let suffix = 1; ; suffix += 1) {
|
|
1517
|
+
const runId = suffix === 1 ? options.runName : `${options.runName}-${suffix}`;
|
|
1518
|
+
const runDir = join2(runsRoot, runId);
|
|
1519
|
+
try {
|
|
1520
|
+
await mkdir(runDir);
|
|
1521
|
+
return { runId, runDir };
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
throw error;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
async function appendEvent(runDir, event) {
|
|
1531
|
+
await appendFile(join2(runDir, "events.jsonl"), `${JSON.stringify(event)}
|
|
1532
|
+
`, "utf8");
|
|
1533
|
+
}
|
|
1534
|
+
async function readRunEvents(runDir) {
|
|
1535
|
+
let contents;
|
|
1536
|
+
try {
|
|
1537
|
+
contents = await readFile2(join2(runDir, "events.jsonl"), "utf8");
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
if (isNodeError(error)) {
|
|
1540
|
+
throw new TrailStepFailureError({
|
|
1541
|
+
code: "resume_target_not_found",
|
|
1542
|
+
message: `Resume target not found or unreadable: ${join2(runDir, "events.jsonl")}.`,
|
|
1543
|
+
details: { runDir, causeCode: error.code }
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
throw error;
|
|
1547
|
+
}
|
|
1548
|
+
const rawLines = contents.split("\n");
|
|
1549
|
+
const lines = rawLines.filter((line, index) => line.length > 0 || index < rawLines.length - 1);
|
|
1550
|
+
const events = [];
|
|
1551
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1552
|
+
const line = lines[index];
|
|
1553
|
+
if (!line) {
|
|
1554
|
+
continue;
|
|
1555
|
+
}
|
|
1556
|
+
try {
|
|
1557
|
+
events.push(JSON.parse(line));
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
const isTrailingLine = index === lines.length - 1;
|
|
1560
|
+
if (isTrailingLine) {
|
|
1561
|
+
return events;
|
|
1562
|
+
}
|
|
1563
|
+
throw error;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return events;
|
|
1567
|
+
}
|
|
1568
|
+
async function writeDocumentArtifact(dir, filename, content) {
|
|
1569
|
+
await mkdir(dir, { recursive: true });
|
|
1570
|
+
const documentPath = join2(dir, filename);
|
|
1571
|
+
await writeFile4(documentPath, content, "utf8");
|
|
1572
|
+
return documentPath;
|
|
1573
|
+
}
|
|
1574
|
+
async function readRunState(runDir) {
|
|
1575
|
+
try {
|
|
1576
|
+
const contents = await readFile2(join2(runDir, "state.json"), "utf8");
|
|
1577
|
+
const parsed = JSON.parse(contents);
|
|
1578
|
+
if (!isRunState(parsed)) {
|
|
1579
|
+
throw new Error("run state must be a JSON object");
|
|
1580
|
+
}
|
|
1581
|
+
return parsed;
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1584
|
+
return {};
|
|
1585
|
+
}
|
|
1586
|
+
throw error;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
async function writeRunState(runDir, state2) {
|
|
1590
|
+
await writeFile4(join2(runDir, "state.json"), `${JSON.stringify(state2, null, 2)}
|
|
1591
|
+
`, "utf8");
|
|
1592
|
+
}
|
|
1593
|
+
function isNodeError(error) {
|
|
1594
|
+
return error instanceof Error && "code" in error;
|
|
1595
|
+
}
|
|
1596
|
+
function isRunState(value) {
|
|
1597
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
1598
|
+
}
|
|
1599
|
+
async function ensureTrailStepGitignoreForRunsRoot(runsRoot) {
|
|
1600
|
+
const trailstepRoot = dirname2(runsRoot);
|
|
1601
|
+
if (basename(trailstepRoot) !== ".trailstep") {
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
await ensureTrailStepGitignore(trailstepRoot);
|
|
1605
|
+
}
|
|
1606
|
+
async function ensureTrailStepGitignore(trailstepRoot) {
|
|
1607
|
+
try {
|
|
1608
|
+
await writeFile4(join2(trailstepRoot, ".gitignore"), "*\n!.gitignore\n", { flag: "wx" });
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
throw error;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// src/runtime/run-context/run-context-storage.ts
|
|
1618
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1619
|
+
var RUN_CONTEXT_STORAGE_KEY = /* @__PURE__ */ Symbol.for("trailstep.core.runContextStorage");
|
|
1620
|
+
var globalScope = globalThis;
|
|
1621
|
+
function getOrCreateRunContextStorage() {
|
|
1622
|
+
const existing = globalScope[RUN_CONTEXT_STORAGE_KEY];
|
|
1623
|
+
if (existing) {
|
|
1624
|
+
return existing;
|
|
1625
|
+
}
|
|
1626
|
+
const created = new AsyncLocalStorage();
|
|
1627
|
+
globalScope[RUN_CONTEXT_STORAGE_KEY] = created;
|
|
1628
|
+
return created;
|
|
1629
|
+
}
|
|
1630
|
+
var runContextStorage = getOrCreateRunContextStorage();
|
|
1631
|
+
function currentRunContext() {
|
|
1632
|
+
const runContext = runContextStorage.getStore();
|
|
1633
|
+
if (!runContext) {
|
|
1634
|
+
throw new Error("state.* called outside an active TrailStep run.");
|
|
1635
|
+
}
|
|
1636
|
+
return runContext;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// src/authoring/document/document.ts
|
|
1640
|
+
var Document = class _Document {
|
|
1641
|
+
constructor(content, path) {
|
|
1642
|
+
this.content = content;
|
|
1643
|
+
this.path = path;
|
|
1644
|
+
}
|
|
1645
|
+
content;
|
|
1646
|
+
path;
|
|
1647
|
+
static captureMode = "raw-text";
|
|
1648
|
+
static jsonSchema = {
|
|
1649
|
+
type: "string",
|
|
1650
|
+
description: "Document content, captured as raw text."
|
|
1651
|
+
};
|
|
1652
|
+
static validate(value) {
|
|
1653
|
+
return isDocumentLike(value);
|
|
1654
|
+
}
|
|
1655
|
+
static diagnostics(value) {
|
|
1656
|
+
if (isDocumentLike(value)) {
|
|
1657
|
+
return [];
|
|
1658
|
+
}
|
|
1659
|
+
return [
|
|
1660
|
+
{ path: "/", message: "must be a document (an object with string content and path fields)" }
|
|
1661
|
+
];
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Deliberately duck-types instead of checking `instanceof Document`: on
|
|
1665
|
+
* resume, a completed step's recorded output is replayed from
|
|
1666
|
+
* `events.jsonl` via `JSON.parse`, producing a plain deserialized object —
|
|
1667
|
+
* never a live `Document` instance — before `assert` is called on it. An
|
|
1668
|
+
* `instanceof` check would fail every such replay. `assert` reconstructs a
|
|
1669
|
+
* genuine `Document` from any content/path-shaped value, so every
|
|
1670
|
+
* consumer downstream — a live capture or a replayed value alike — always
|
|
1671
|
+
* receives a real `Document` instance with working prototype methods.
|
|
1672
|
+
*/
|
|
1673
|
+
static assert(value, label = "value") {
|
|
1674
|
+
const valueDiagnostics = _Document.diagnostics(value);
|
|
1675
|
+
if (valueDiagnostics.length > 0 || !isDocumentLike(value)) {
|
|
1676
|
+
throw new TrailStepFailureError(
|
|
1677
|
+
validationFailure(
|
|
1678
|
+
`${label} failed schema validation: ${formatDiagnostics(valueDiagnostics)}`,
|
|
1679
|
+
{
|
|
1680
|
+
diagnostics: valueDiagnostics
|
|
1681
|
+
}
|
|
1682
|
+
)
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
return new _Document(value.content, value.path);
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
async function document(content) {
|
|
1689
|
+
const { currentStep } = currentRunContext();
|
|
1690
|
+
if (!currentStep) {
|
|
1691
|
+
throw new Error(
|
|
1692
|
+
"document(...) must be called from within a step's `.do()` callback; no active step context was found."
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
const index = currentStep.nextDocumentIndex();
|
|
1696
|
+
const path = await writeDocumentArtifact(currentStep.dir, `document-${index}.md`, content);
|
|
1697
|
+
return new Document(content, path);
|
|
1698
|
+
}
|
|
1699
|
+
function isDocumentLike(value) {
|
|
1700
|
+
return typeof value === "object" && value !== null && typeof value.content === "string" && typeof value.path === "string";
|
|
1701
|
+
}
|
|
1702
|
+
function formatDiagnostics(diagnostics) {
|
|
1703
|
+
return diagnostics.map((diagnostic) => `${diagnostic.path} ${diagnostic.message}`).join("; ");
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// src/authoring/prompt-composition/prompt-composition.ts
|
|
1707
|
+
import { readFileSync } from "fs";
|
|
1708
|
+
import { join as join3 } from "path";
|
|
1709
|
+
function loadFragments(dir, files) {
|
|
1710
|
+
const fragments = {};
|
|
1711
|
+
for (const key of Object.keys(files)) {
|
|
1712
|
+
fragments[key] = readFileSync(join3(dir, files[key]), "utf8").trimEnd();
|
|
1713
|
+
}
|
|
1714
|
+
return fragments;
|
|
1715
|
+
}
|
|
1716
|
+
function section(title, body) {
|
|
1717
|
+
return body ? `## ${title}
|
|
1718
|
+
|
|
1719
|
+
${body.trimEnd()}` : void 0;
|
|
1720
|
+
}
|
|
1721
|
+
function promptSections(...parts) {
|
|
1722
|
+
return parts.filter((part) => Boolean(part)).join("\n\n");
|
|
1723
|
+
}
|
|
1724
|
+
function list(items) {
|
|
1725
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// src/authoring/prompt-template/prompt-template.ts
|
|
1729
|
+
function promptTemplate(path) {
|
|
1730
|
+
return { kind: "promptTemplate", path };
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// src/authoring/shape/json-schema.ts
|
|
1734
|
+
import { Ajv } from "ajv/dist/ajv.js";
|
|
1735
|
+
var ajv = new Ajv({ allErrors: true });
|
|
1736
|
+
function normalizeShape(shapeInput) {
|
|
1737
|
+
if (isSchema(shapeInput)) {
|
|
1738
|
+
return shapeInput;
|
|
1739
|
+
}
|
|
1740
|
+
return shape(shapeInput);
|
|
1741
|
+
}
|
|
1742
|
+
function shape(shapeObject) {
|
|
1743
|
+
return jsonSchema({
|
|
1744
|
+
type: "object",
|
|
1745
|
+
properties: Object.fromEntries(
|
|
1746
|
+
Object.entries(shapeObject).map(([key, type]) => [key, { type }])
|
|
1747
|
+
),
|
|
1748
|
+
required: Object.keys(shapeObject),
|
|
1749
|
+
additionalProperties: false
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
function jsonSchema(schema) {
|
|
1753
|
+
const validateWithAjv = ajv.compile(schema);
|
|
1754
|
+
return {
|
|
1755
|
+
validate(value) {
|
|
1756
|
+
return validateWithAjv(value);
|
|
1757
|
+
},
|
|
1758
|
+
diagnostics(value) {
|
|
1759
|
+
if (!isPlainObject2(value)) {
|
|
1760
|
+
return [{ path: "/", message: "must be a plain object" }];
|
|
1761
|
+
}
|
|
1762
|
+
if (validateWithAjv(value)) {
|
|
1763
|
+
return [];
|
|
1764
|
+
}
|
|
1765
|
+
return diagnosticsFromAjvErrors(validateWithAjv.errors);
|
|
1766
|
+
},
|
|
1767
|
+
assert(value, label = "value") {
|
|
1768
|
+
const diagnostics = this.diagnostics(value);
|
|
1769
|
+
if (diagnostics.length > 0) {
|
|
1770
|
+
throw new TrailStepFailureError(
|
|
1771
|
+
validationFailure(
|
|
1772
|
+
`${label} failed schema validation: ${formatDiagnostics2(diagnostics)}`,
|
|
1773
|
+
{ diagnostics }
|
|
1774
|
+
)
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
return value;
|
|
1778
|
+
},
|
|
1779
|
+
jsonSchema: schema
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
function isSchema(value) {
|
|
1783
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") {
|
|
1784
|
+
return false;
|
|
1785
|
+
}
|
|
1786
|
+
const candidate = value;
|
|
1787
|
+
return typeof candidate.validate === "function" && typeof candidate.diagnostics === "function" && typeof candidate.assert === "function";
|
|
1788
|
+
}
|
|
1789
|
+
function isPlainObject2(value) {
|
|
1790
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
1791
|
+
}
|
|
1792
|
+
function diagnosticsFromAjvErrors(errors) {
|
|
1793
|
+
if (!errors || errors.length === 0) {
|
|
1794
|
+
return [{ path: "/", message: "is invalid" }];
|
|
1795
|
+
}
|
|
1796
|
+
return errors.map((error) => ({
|
|
1797
|
+
path: error.instancePath || "/",
|
|
1798
|
+
message: error.message ?? "is invalid"
|
|
1799
|
+
}));
|
|
1800
|
+
}
|
|
1801
|
+
function formatDiagnostics2(diagnostics) {
|
|
1802
|
+
return diagnostics.map((diagnostic) => `${diagnostic.path} ${diagnostic.message}`).join("; ");
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// src/authoring/state/state.ts
|
|
1806
|
+
var state = {
|
|
1807
|
+
async get(key) {
|
|
1808
|
+
return currentRunContext().state.get(key);
|
|
1809
|
+
},
|
|
1810
|
+
async set(key, value) {
|
|
1811
|
+
await currentRunContext().state.set(key, value);
|
|
1812
|
+
},
|
|
1813
|
+
get id() {
|
|
1814
|
+
return currentRunContext().id;
|
|
1815
|
+
},
|
|
1816
|
+
get name() {
|
|
1817
|
+
return currentRunContext().name;
|
|
1818
|
+
},
|
|
1819
|
+
get path() {
|
|
1820
|
+
return currentRunContext().path;
|
|
1821
|
+
},
|
|
1822
|
+
get cwd() {
|
|
1823
|
+
return currentRunContext().cwd;
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
|
|
1827
|
+
// src/authoring/step/step-node.ts
|
|
1828
|
+
function step(config) {
|
|
1829
|
+
const buildFactory = (prompt, promptOptions, onOutput, onError) => {
|
|
1830
|
+
const factory = ((input) => ({
|
|
1831
|
+
kind: "step",
|
|
1832
|
+
config: {
|
|
1833
|
+
...config,
|
|
1834
|
+
...promptOptions,
|
|
1835
|
+
prompt,
|
|
1836
|
+
input: input ?? {}
|
|
1837
|
+
},
|
|
1838
|
+
onOutput,
|
|
1839
|
+
onError
|
|
1840
|
+
}));
|
|
1841
|
+
factory.catch = (nextOnError) => buildFactory(prompt, promptOptions, onOutput, nextOnError);
|
|
1842
|
+
return factory;
|
|
1843
|
+
};
|
|
1844
|
+
return {
|
|
1845
|
+
prompt(source, options) {
|
|
1846
|
+
if (hasRetryOption(options)) {
|
|
1847
|
+
throw new TypeError("Retry config belongs on step(...), not .prompt(...) options.");
|
|
1848
|
+
}
|
|
1849
|
+
if (hasTimeoutOption(options)) {
|
|
1850
|
+
throw new TypeError("Timeout config belongs on step(...), not .prompt(...) options.");
|
|
1851
|
+
}
|
|
1852
|
+
return {
|
|
1853
|
+
do: (onOutput) => buildFactory(source, options, onOutput)
|
|
1854
|
+
};
|
|
1855
|
+
},
|
|
1856
|
+
do(onOutput) {
|
|
1857
|
+
return buildFactory(void 0, void 0, onOutput);
|
|
1858
|
+
}
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
function done(output) {
|
|
1862
|
+
return {
|
|
1863
|
+
kind: "done",
|
|
1864
|
+
output: output ?? {}
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
function fail(failure) {
|
|
1868
|
+
return {
|
|
1869
|
+
kind: "fail",
|
|
1870
|
+
failure
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function isStepNode(value) {
|
|
1874
|
+
return isPlainObject3(value) && value.kind === "step";
|
|
1875
|
+
}
|
|
1876
|
+
function isDoneNode(value) {
|
|
1877
|
+
return isPlainObject3(value) && value.kind === "done";
|
|
1878
|
+
}
|
|
1879
|
+
function isFailNode(value) {
|
|
1880
|
+
return isPlainObject3(value) && value.kind === "fail";
|
|
1881
|
+
}
|
|
1882
|
+
function hasRetryOption(value) {
|
|
1883
|
+
return isPlainObject3(value) && "retry" in value;
|
|
1884
|
+
}
|
|
1885
|
+
function hasTimeoutOption(value) {
|
|
1886
|
+
return isPlainObject3(value) && "timeout" in value;
|
|
1887
|
+
}
|
|
1888
|
+
function isPlainObject3(value) {
|
|
1889
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// src/runtime/sub-prompts/run-sub-prompt.ts
|
|
1893
|
+
import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
|
|
1894
|
+
|
|
1895
|
+
// src/agent-execution/adapter-agent/run-adapter-agent-step/run-adapter-agent-step.ts
|
|
1896
|
+
var PROVIDER_NEUTRAL_ADAPTER_KEY = "custom";
|
|
1897
|
+
var PROVIDER_NEUTRAL_MODEL = "configured-agent";
|
|
1898
|
+
async function runAgentStep(options) {
|
|
1899
|
+
const model = resolveAgentRequirements(options.step.requirements);
|
|
1900
|
+
const adapter = resolveAgentAdapter(options.step.adapter);
|
|
1901
|
+
let submittedOutput;
|
|
1902
|
+
const tools = [
|
|
1903
|
+
createSubmitOutputTool(options.step.output, async (output) => {
|
|
1904
|
+
options.step.output.assert(output, `agent step ${options.step.id} submit_output`);
|
|
1905
|
+
submittedOutput = output;
|
|
1906
|
+
await options.onToolCall?.({
|
|
1907
|
+
name: "submit_output",
|
|
1908
|
+
input: output,
|
|
1909
|
+
output: { accepted: true }
|
|
1910
|
+
});
|
|
1911
|
+
})
|
|
1912
|
+
];
|
|
1913
|
+
const prompt = renderAgentPrompt(options.step.prompt, options.input);
|
|
1914
|
+
await adapter({
|
|
1915
|
+
messages: [{ role: "user", content: prompt }],
|
|
1916
|
+
tools,
|
|
1917
|
+
requirements: options.step.requirements,
|
|
1918
|
+
model,
|
|
1919
|
+
step: options.step,
|
|
1920
|
+
input: options.input
|
|
1921
|
+
});
|
|
1922
|
+
if (submittedOutput === void 0) {
|
|
1923
|
+
throw new TrailStepFailureError({
|
|
1924
|
+
code: "agent_output_missing",
|
|
1925
|
+
message: `Agent step ${options.step.id} did not call submit_output.`
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
return submittedOutput;
|
|
1929
|
+
}
|
|
1930
|
+
function renderAgentPrompt(prompt, input) {
|
|
1931
|
+
return typeof prompt === "function" ? prompt({ input }) : prompt;
|
|
1932
|
+
}
|
|
1933
|
+
function resolveAgentRequirements(requirements) {
|
|
1934
|
+
return {
|
|
1935
|
+
adapterKey: PROVIDER_NEUTRAL_ADAPTER_KEY,
|
|
1936
|
+
model: requirements.name ?? requirements.size ?? PROVIDER_NEUTRAL_MODEL
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function resolveAgentAdapter(selection) {
|
|
1940
|
+
if (typeof selection === "function") {
|
|
1941
|
+
return selection;
|
|
1942
|
+
}
|
|
1943
|
+
if (typeof selection === "object" && selection !== null) {
|
|
1944
|
+
return (request) => selection.runAgentStep(request);
|
|
1945
|
+
}
|
|
1946
|
+
throw new TrailStepFailureError({
|
|
1947
|
+
code: "agent_adapter_unavailable",
|
|
1948
|
+
message: "Agent steps that do not use a configured command runner require a custom adapter function or adapter object."
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
function createSubmitOutputTool(schema, onOutput) {
|
|
1952
|
+
return {
|
|
1953
|
+
name: "submit_output",
|
|
1954
|
+
description: "Submit the typed output for this TrailStep agent step.",
|
|
1955
|
+
schema,
|
|
1956
|
+
async call(input) {
|
|
1957
|
+
await onOutput(input);
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// src/agent-execution/dispatch-agent-step/dispatch-agent-step.ts
|
|
1963
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1964
|
+
import { resolve } from "path";
|
|
1965
|
+
|
|
1966
|
+
// src/runtime/artifacts/step-artifacts.ts
|
|
1967
|
+
import { join as join4 } from "path";
|
|
1968
|
+
function resolveStepArtifactPaths(options) {
|
|
1969
|
+
const artifactStepId = `${String(options.stepIndex).padStart(4, "0")}-${sanitizeStepId(
|
|
1970
|
+
options.stepId
|
|
1971
|
+
)}`;
|
|
1972
|
+
const runRelativeStepDir = `steps/${artifactStepId}`;
|
|
1973
|
+
const runRelativeSessionDescriptionFile = `${runRelativeStepDir}/session-description.md`;
|
|
1974
|
+
const stepDir = join4(options.runDir, "steps", artifactStepId);
|
|
1975
|
+
return {
|
|
1976
|
+
artifactStepId,
|
|
1977
|
+
stepDir,
|
|
1978
|
+
promptFile: join4(stepDir, "prompt.txt"),
|
|
1979
|
+
outputFile: join4(stepDir, "output.json"),
|
|
1980
|
+
usageFile: join4(stepDir, "usage.json"),
|
|
1981
|
+
interactiveFile: join4(stepDir, "interactive.json"),
|
|
1982
|
+
sessionDescriptionFile: join4(stepDir, "session-description.md"),
|
|
1983
|
+
runRelativeStepDir,
|
|
1984
|
+
runRelativeSessionDescriptionFile
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
function sanitizeStepId(stepId) {
|
|
1988
|
+
return stepId.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "step";
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
// src/runtime/events/create-run-event.ts
|
|
1992
|
+
import { randomUUID } from "crypto";
|
|
1993
|
+
function createEvent(options) {
|
|
1994
|
+
return {
|
|
1995
|
+
id: `evt_${randomUUID()}`,
|
|
1996
|
+
runId: options.runId,
|
|
1997
|
+
workflowId: options.workflowId,
|
|
1998
|
+
stepId: options.stepId,
|
|
1999
|
+
type: options.type,
|
|
2000
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2001
|
+
schemaVersion: "v0",
|
|
2002
|
+
payload: options.payload ?? {}
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
// src/agent-execution/interactive-agent/run-interactive-agent-command.ts
|
|
2007
|
+
import { spawn as spawn5 } from "child_process";
|
|
2008
|
+
import { mkdir as mkdir2, writeFile as writeFile5 } from "fs/promises";
|
|
2009
|
+
import { dirname as dirname3 } from "path";
|
|
2010
|
+
|
|
2011
|
+
// src/runtime/interactive-session/interactive-session-protocol.ts
|
|
2012
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
2013
|
+
import { setTimeout as delay } from "timers/promises";
|
|
2014
|
+
async function waitForInteractiveCompletion(interactiveFile, signal) {
|
|
2015
|
+
while (!signal.aborted) {
|
|
2016
|
+
if (await isInteractiveCompleted(interactiveFile)) {
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
await delay(100, void 0, { signal }).catch(() => void 0);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
async function isInteractiveCompleted(interactiveFile) {
|
|
2023
|
+
try {
|
|
2024
|
+
const protocol = await readPlainJsonFile(interactiveFile, {
|
|
2025
|
+
readCode: "interactive_session_invalid",
|
|
2026
|
+
invalidCode: "interactive_session_invalid",
|
|
2027
|
+
message: "Interactive session protocol file is invalid."
|
|
2028
|
+
});
|
|
2029
|
+
return protocol.status === "completed" || protocol.status === "cancelled";
|
|
2030
|
+
} catch {
|
|
2031
|
+
return false;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
async function readCompletedInteractiveOutput(options) {
|
|
2035
|
+
const protocol = await readPlainJsonFile(options.interactiveFile, {
|
|
2036
|
+
readCode: "interactive_session_invalid",
|
|
2037
|
+
invalidCode: "interactive_session_invalid",
|
|
2038
|
+
message: `Interactive agent step ${options.stepId} has an invalid interactive.json file.`
|
|
2039
|
+
});
|
|
2040
|
+
if (protocol.status === "cancelled") {
|
|
2041
|
+
throw new TrailStepFailureError({
|
|
2042
|
+
code: "interactive_session_cancelled",
|
|
2043
|
+
message: `Interactive agent step ${options.stepId} was cancelled.`,
|
|
2044
|
+
details: {
|
|
2045
|
+
status: protocol.status,
|
|
2046
|
+
...typeof protocol.reason === "string" ? { reason: protocol.reason } : {}
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
if (protocol.status !== "completed") {
|
|
2051
|
+
throw new TrailStepFailureError({
|
|
2052
|
+
code: "interactive_session_incomplete",
|
|
2053
|
+
message: `Interactive agent step ${options.stepId} did not complete the file-based protocol.`,
|
|
2054
|
+
details: { status: protocol.status }
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
const outputFile = requireProtocolString(protocol.outputFile, "outputFile", options.stepId);
|
|
2058
|
+
const output = await readPlainJsonFile(outputFile, {
|
|
2059
|
+
readCode: "interactive_output_missing",
|
|
2060
|
+
invalidCode: "interactive_output_invalid",
|
|
2061
|
+
message: `Interactive agent step ${options.stepId} completed without a valid output.json file.`
|
|
2062
|
+
});
|
|
2063
|
+
const diagnostics = options.outputSchema.diagnostics(output);
|
|
2064
|
+
if (diagnostics.length > 0) {
|
|
2065
|
+
throw new TrailStepFailureError({
|
|
2066
|
+
code: "interactive_output_invalid",
|
|
2067
|
+
message: `Interactive agent step ${options.stepId} output.json failed schema validation: ${formatDiagnostics3(diagnostics)}`,
|
|
2068
|
+
details: { diagnostics }
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
return output;
|
|
2072
|
+
}
|
|
2073
|
+
async function readPlainJsonFile(path, failure) {
|
|
2074
|
+
let contents;
|
|
2075
|
+
try {
|
|
2076
|
+
contents = await readFile3(path, "utf8");
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
throw new TrailStepFailureError({
|
|
2079
|
+
code: failure.readCode,
|
|
2080
|
+
message: failure.message,
|
|
2081
|
+
details: { path, cause: error instanceof Error ? error.message : String(error) }
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
let parsed;
|
|
2085
|
+
try {
|
|
2086
|
+
parsed = JSON.parse(contents);
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
throw new TrailStepFailureError({
|
|
2089
|
+
code: failure.invalidCode,
|
|
2090
|
+
message: failure.message,
|
|
2091
|
+
details: { path, cause: error instanceof Error ? error.message : String(error) }
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2095
|
+
throw new TrailStepFailureError({
|
|
2096
|
+
code: failure.invalidCode,
|
|
2097
|
+
message: `${failure.message} Expected a plain JSON object.`,
|
|
2098
|
+
details: { path }
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
return parsed;
|
|
2102
|
+
}
|
|
2103
|
+
function requireProtocolString(value, field, stepId) {
|
|
2104
|
+
if (typeof value === "string" && value.length > 0) {
|
|
2105
|
+
return value;
|
|
2106
|
+
}
|
|
2107
|
+
throw new TrailStepFailureError({
|
|
2108
|
+
code: "interactive_session_invalid",
|
|
2109
|
+
message: `Interactive agent step ${stepId} interactive.json is missing ${field}.`
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
function formatDiagnostics3(diagnostics) {
|
|
2113
|
+
return diagnostics.map((diagnostic) => `${diagnostic.path} ${diagnostic.message}`).join("; ");
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
// src/agent-execution/interactive-agent/run-interactive-agent-command.ts
|
|
2117
|
+
async function runInteractiveAgentCommand(options) {
|
|
2118
|
+
const [target] = resolveAgentTargets({
|
|
2119
|
+
config: options.config,
|
|
2120
|
+
workflowId: options.workflowId,
|
|
2121
|
+
roleName: options.roleName,
|
|
2122
|
+
roleSize: options.role.size
|
|
2123
|
+
});
|
|
2124
|
+
if (!target) {
|
|
2125
|
+
throw new TrailStepFailureError({
|
|
2126
|
+
code: "agent_targets_unavailable",
|
|
2127
|
+
message: `No interactive agent targets found for role ${options.roleName} with size ${options.role.size} in workflow ${options.workflowId}.`
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
return await runInteractiveAgentTarget({ ...options, target });
|
|
2131
|
+
}
|
|
2132
|
+
async function runInteractiveAgentTarget(options) {
|
|
2133
|
+
const files = options.artifactPaths;
|
|
2134
|
+
await prepareInteractiveArtifacts({
|
|
2135
|
+
files,
|
|
2136
|
+
runDir: options.runDir,
|
|
2137
|
+
stepId: options.stepId,
|
|
2138
|
+
outputSchema: options.outputSchema,
|
|
2139
|
+
outputMode: options.outputMode
|
|
2140
|
+
});
|
|
2141
|
+
const prompt = buildInteractivePrompt({
|
|
2142
|
+
renderedPrompt: options.renderedPrompt,
|
|
2143
|
+
files,
|
|
2144
|
+
outputSchema: options.outputSchema,
|
|
2145
|
+
outputMode: options.outputMode
|
|
2146
|
+
});
|
|
2147
|
+
const env = { ...definedProcessEnv(), TRAILSTEP_INTERACTIVE_FILE: files.interactiveFile };
|
|
2148
|
+
const abortController = new AbortController();
|
|
2149
|
+
options.signal?.addEventListener("abort", () => abortController.abort(), { once: true });
|
|
2150
|
+
const provider = providerRegistry[options.target.provider];
|
|
2151
|
+
if (provider) {
|
|
2152
|
+
return await runProcessUntilExitOrCompletion({
|
|
2153
|
+
stepId: options.stepId,
|
|
2154
|
+
target: options.target.provider,
|
|
2155
|
+
interactiveFile: files.interactiveFile,
|
|
2156
|
+
abortController,
|
|
2157
|
+
readOutput: async () => await readCompletedOutput(options),
|
|
2158
|
+
runProcess: async () => {
|
|
2159
|
+
await writeFile5(files.promptFile, prompt, "utf8");
|
|
2160
|
+
return await provider.runInteractive(
|
|
2161
|
+
{
|
|
2162
|
+
prompt,
|
|
2163
|
+
systemPromptFile: files.promptFile,
|
|
2164
|
+
cwd: files.stepDir,
|
|
2165
|
+
env,
|
|
2166
|
+
signal: abortController.signal,
|
|
2167
|
+
...options.target.model === void 0 ? {} : { model: options.target.model },
|
|
2168
|
+
...options.target.permissionMode === void 0 ? {} : { permissionMode: options.target.permissionMode }
|
|
2169
|
+
},
|
|
2170
|
+
options.runner
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
const agentConfig = options.config.customProviders[options.target.provider];
|
|
2176
|
+
if (!agentConfig) {
|
|
2177
|
+
throw new TrailStepFailureError({
|
|
2178
|
+
code: "agent_provider_unavailable",
|
|
2179
|
+
message: `Interactive agent target '${options.target.provider}' does not reference a configured custom agent.`,
|
|
2180
|
+
details: { provider: options.target.provider }
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
if (!agentConfig.interactiveArgs) {
|
|
2184
|
+
throw new TrailStepFailureError({
|
|
2185
|
+
code: "agent_provider_interactive_unsupported",
|
|
2186
|
+
message: `Custom provider '${options.target.provider}' cannot run interactive agent steps because customProviders.${options.target.provider}.interactiveArgs is not declared.`,
|
|
2187
|
+
details: { provider: options.target.provider }
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
const args = await substitutePromptPlaceholders({
|
|
2191
|
+
argv: options.target.args ?? agentConfig.interactiveArgs,
|
|
2192
|
+
prompt,
|
|
2193
|
+
promptFile: files.promptFile,
|
|
2194
|
+
model: options.target.model
|
|
2195
|
+
});
|
|
2196
|
+
return await runProcessUntilExitOrCompletion({
|
|
2197
|
+
stepId: options.stepId,
|
|
2198
|
+
target: options.target.provider,
|
|
2199
|
+
interactiveFile: files.interactiveFile,
|
|
2200
|
+
abortController,
|
|
2201
|
+
readOutput: async () => await readCompletedOutput(options),
|
|
2202
|
+
mapSpawnError: (error) => new TrailStepFailureError({
|
|
2203
|
+
code: "interactive_command_spawn_error",
|
|
2204
|
+
message: `Interactive agent step ${options.stepId} could not start target '${options.target.provider}'.`,
|
|
2205
|
+
details: {
|
|
2206
|
+
target: options.target.provider,
|
|
2207
|
+
...options.target.model === void 0 ? {} : { model: options.target.model },
|
|
2208
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2209
|
+
}
|
|
2210
|
+
}),
|
|
2211
|
+
runProcess: async () => await (options.runner ?? spawnInteractiveProcess)({
|
|
2212
|
+
command: agentConfig.binary,
|
|
2213
|
+
args,
|
|
2214
|
+
cwd: files.stepDir,
|
|
2215
|
+
shell: false,
|
|
2216
|
+
stdio: "inherit",
|
|
2217
|
+
env,
|
|
2218
|
+
signal: abortController.signal
|
|
2219
|
+
})
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
function definedProcessEnv() {
|
|
2223
|
+
return Object.fromEntries(
|
|
2224
|
+
Object.entries(process.env).filter(
|
|
2225
|
+
(entry) => entry[1] !== void 0
|
|
2226
|
+
)
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
async function runProcessUntilExitOrCompletion(options) {
|
|
2230
|
+
const processPromise = options.runProcess();
|
|
2231
|
+
void processPromise.catch(() => void 0);
|
|
2232
|
+
const watcherAbortController = new AbortController();
|
|
2233
|
+
const winner = await Promise.race([
|
|
2234
|
+
processPromise.then(
|
|
2235
|
+
(result) => ({ type: "process", result }),
|
|
2236
|
+
(error) => ({ type: "processError", error })
|
|
2237
|
+
),
|
|
2238
|
+
waitForInteractiveCompletion(options.interactiveFile, watcherAbortController.signal).then(
|
|
2239
|
+
() => ({ type: "completed" })
|
|
2240
|
+
)
|
|
2241
|
+
]);
|
|
2242
|
+
watcherAbortController.abort();
|
|
2243
|
+
if (winner.type === "processError") {
|
|
2244
|
+
throw options.mapSpawnError?.(winner.error) ?? winner.error;
|
|
2245
|
+
}
|
|
2246
|
+
if (winner.type === "process") {
|
|
2247
|
+
if (await isInteractiveCompleted(options.interactiveFile)) {
|
|
2248
|
+
return { exitCode: 0, output: await options.readOutput() };
|
|
2249
|
+
}
|
|
2250
|
+
if (winner.result.exitCode !== 0) {
|
|
2251
|
+
throw new TrailStepFailureError({
|
|
2252
|
+
code: "interactive_session_failed",
|
|
2253
|
+
message: `Interactive agent step ${options.stepId} exited with code ${winner.result.exitCode}.`,
|
|
2254
|
+
details: { exitCode: winner.result.exitCode, target: options.target }
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
return { exitCode: winner.result.exitCode, output: await options.readOutput() };
|
|
2258
|
+
}
|
|
2259
|
+
options.abortController.abort();
|
|
2260
|
+
return { exitCode: 0, output: await options.readOutput() };
|
|
2261
|
+
}
|
|
2262
|
+
async function prepareInteractiveArtifacts(options) {
|
|
2263
|
+
await mkdir2(options.files.stepDir, { recursive: true });
|
|
2264
|
+
await writeFile5(
|
|
2265
|
+
options.files.interactiveFile,
|
|
2266
|
+
`${JSON.stringify(
|
|
2267
|
+
{
|
|
2268
|
+
status: "active",
|
|
2269
|
+
stepId: options.stepId,
|
|
2270
|
+
artifactStepId: options.files.artifactStepId,
|
|
2271
|
+
outputMode: options.outputMode,
|
|
2272
|
+
runDir: options.runDir,
|
|
2273
|
+
stepDir: options.files.stepDir,
|
|
2274
|
+
promptFile: options.files.promptFile,
|
|
2275
|
+
outputFile: options.files.outputFile,
|
|
2276
|
+
interactiveFile: options.files.interactiveFile,
|
|
2277
|
+
...options.outputMode === "session-file" ? {
|
|
2278
|
+
sessionDescriptionFile: options.files.sessionDescriptionFile,
|
|
2279
|
+
runRelativeSessionDescriptionFile: options.files.runRelativeSessionDescriptionFile
|
|
2280
|
+
} : {},
|
|
2281
|
+
runRelativeStepDir: options.files.runRelativeStepDir,
|
|
2282
|
+
outputSchema: options.outputSchema.jsonSchema
|
|
2283
|
+
},
|
|
2284
|
+
null,
|
|
2285
|
+
2
|
|
2286
|
+
)}
|
|
2287
|
+
`,
|
|
2288
|
+
"utf8"
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
function buildInteractivePrompt(options) {
|
|
2292
|
+
const preamble = options.outputMode === "session-file" ? [
|
|
2293
|
+
"You are running inside a TrailStep interactive step.",
|
|
2294
|
+
"Write a dense session description to session-description.md in the current directory.",
|
|
2295
|
+
"preserve as much usable context as possible.",
|
|
2296
|
+
"describe the conversation rather than aggressively summarize it.",
|
|
2297
|
+
"include decisions, rejected options, tradeoffs, constraints, side comments, terminology, open questions, assumptions, file paths, commands, APIs, package names, examples, preferences, reasoning, and abandoned options.",
|
|
2298
|
+
"The goal is context preservation, not polish.",
|
|
2299
|
+
"Do not omit low-importance details merely because they seem minor.",
|
|
2300
|
+
"When complete, run: trailstep continue --session-file session-description.md"
|
|
2301
|
+
] : [
|
|
2302
|
+
"You are running inside a TrailStep interactive step.",
|
|
2303
|
+
"Submit a JSON object matching this schema:",
|
|
2304
|
+
JSON.stringify(options.outputSchema.jsonSchema, null, 2),
|
|
2305
|
+
"When complete, run one of:",
|
|
2306
|
+
" trailstep continue --json-file output.json",
|
|
2307
|
+
` trailstep continue --json '${JSON.stringify(exampleJsonObject(options.outputSchema.jsonSchema))}'`,
|
|
2308
|
+
"If validation fails, update the JSON so it matches the schema and run trailstep continue again."
|
|
2309
|
+
];
|
|
2310
|
+
return [...preamble, "", "## Original prompt", options.renderedPrompt].join("\n");
|
|
2311
|
+
}
|
|
2312
|
+
async function readCompletedOutput(options) {
|
|
2313
|
+
return await readCompletedInteractiveOutput({
|
|
2314
|
+
stepId: options.stepId,
|
|
2315
|
+
interactiveFile: options.artifactPaths.interactiveFile,
|
|
2316
|
+
outputSchema: options.outputSchema
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
function exampleJsonObject(schema) {
|
|
2320
|
+
const properties = typeof schema.properties === "object" && schema.properties !== null && !Array.isArray(schema.properties) ? schema.properties : {};
|
|
2321
|
+
return Object.fromEntries(
|
|
2322
|
+
Object.entries(properties).map(([key, property]) => {
|
|
2323
|
+
const type = typeof property === "object" && property !== null && "type" in property ? property.type : void 0;
|
|
2324
|
+
if (type === "boolean") {
|
|
2325
|
+
return [key, true];
|
|
2326
|
+
}
|
|
2327
|
+
if (type === "number") {
|
|
2328
|
+
return [key, 1];
|
|
2329
|
+
}
|
|
2330
|
+
return [key, "string"];
|
|
2331
|
+
})
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
async function substitutePromptPlaceholders(options) {
|
|
2335
|
+
let needsPromptFile = false;
|
|
2336
|
+
const substituted = options.argv.map((arg) => {
|
|
2337
|
+
if (arg === "{{prompt}}") {
|
|
2338
|
+
return options.prompt;
|
|
2339
|
+
}
|
|
2340
|
+
if (arg === "{{promptFile}}") {
|
|
2341
|
+
needsPromptFile = true;
|
|
2342
|
+
return options.promptFile;
|
|
2343
|
+
}
|
|
2344
|
+
if (arg === "{{model}}") {
|
|
2345
|
+
return options.model ?? "";
|
|
2346
|
+
}
|
|
2347
|
+
if (arg.includes("{{prompt}}") || arg.includes("{{promptFile}}") || arg.includes("{{model}}")) {
|
|
2348
|
+
throw new TrailStepFailureError({
|
|
2349
|
+
code: "interactive_command_invalid",
|
|
2350
|
+
message: "Interactive prompt placeholders must be whole argv values."
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
return arg;
|
|
2354
|
+
});
|
|
2355
|
+
if (needsPromptFile) {
|
|
2356
|
+
await mkdir2(dirname3(options.promptFile), { recursive: true });
|
|
2357
|
+
await writeFile5(options.promptFile, options.prompt, "utf8");
|
|
2358
|
+
}
|
|
2359
|
+
return substituted;
|
|
2360
|
+
}
|
|
2361
|
+
var spawnInteractiveProcess = async ({
|
|
2362
|
+
command,
|
|
2363
|
+
args,
|
|
2364
|
+
cwd,
|
|
2365
|
+
env,
|
|
2366
|
+
signal
|
|
2367
|
+
}) => {
|
|
2368
|
+
return await new Promise((resolve2, reject) => {
|
|
2369
|
+
const child = spawn5(command, args, {
|
|
2370
|
+
cwd,
|
|
2371
|
+
env,
|
|
2372
|
+
signal,
|
|
2373
|
+
detached: process.platform !== "win32",
|
|
2374
|
+
shell: false,
|
|
2375
|
+
stdio: "inherit"
|
|
2376
|
+
});
|
|
2377
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree5(child), { once: true });
|
|
2378
|
+
child.on("error", reject);
|
|
2379
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1 }));
|
|
2380
|
+
});
|
|
2381
|
+
};
|
|
2382
|
+
function terminateChildProcessTree5(child) {
|
|
2383
|
+
if (!child.pid) {
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
if (process.platform === "win32") {
|
|
2387
|
+
spawn5("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
|
|
2388
|
+
"error",
|
|
2389
|
+
() => {
|
|
2390
|
+
child.kill();
|
|
2391
|
+
}
|
|
2392
|
+
);
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
try {
|
|
2396
|
+
process.kill(-child.pid, "SIGTERM");
|
|
2397
|
+
} catch {
|
|
2398
|
+
child.kill();
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// src/agent-execution/working-agent/run-working-agent-command.ts
|
|
2403
|
+
import { mkdir as mkdir3, writeFile as writeFile7 } from "fs/promises";
|
|
2404
|
+
|
|
2405
|
+
// src/agent-execution/working-agent/artifacts/resolve-step-agent-files.ts
|
|
2406
|
+
import { join as join5 } from "path";
|
|
2407
|
+
function resolveStepAgentFiles(options) {
|
|
2408
|
+
const artifactPaths = resolveStepArtifactPaths(options);
|
|
2409
|
+
return {
|
|
2410
|
+
stepDir: artifactPaths.stepDir,
|
|
2411
|
+
promptFile: join5(artifactPaths.stepDir, "prompt.md"),
|
|
2412
|
+
outputFile: artifactPaths.outputFile,
|
|
2413
|
+
usageFile: artifactPaths.usageFile
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// src/agent-execution/working-agent/prompts/build-working-agent-prompt.ts
|
|
2418
|
+
function buildWorkingAgentPrompt(options) {
|
|
2419
|
+
if (options.captureMode === "raw-text") {
|
|
2420
|
+
return [
|
|
2421
|
+
"# TrailStep working-agent task",
|
|
2422
|
+
"",
|
|
2423
|
+
"Run the task described below and write the document content to the output file.",
|
|
2424
|
+
"Print the document content directly as your entire response \u2014 no JSON wrapper, no surrounding commentary, no markdown fences unless they are literally part of the document content itself.",
|
|
2425
|
+
"",
|
|
2426
|
+
`Output file: ${options.outputFile}`,
|
|
2427
|
+
"",
|
|
2428
|
+
"## Original prompt",
|
|
2429
|
+
"",
|
|
2430
|
+
options.prompt,
|
|
2431
|
+
""
|
|
2432
|
+
].join("\n");
|
|
2433
|
+
}
|
|
2434
|
+
return [
|
|
2435
|
+
"# TrailStep working-agent task",
|
|
2436
|
+
"",
|
|
2437
|
+
"Run the task described below and write exactly one JSON object to the output file.",
|
|
2438
|
+
"Do not write prose, markdown fences, or multiple JSON values to the output file.",
|
|
2439
|
+
"",
|
|
2440
|
+
`Output file: ${options.outputFile}`,
|
|
2441
|
+
"",
|
|
2442
|
+
"The JSON object must match this output schema:",
|
|
2443
|
+
"",
|
|
2444
|
+
"```json",
|
|
2445
|
+
JSON.stringify(options.outputSchema, null, 2),
|
|
2446
|
+
"```",
|
|
2447
|
+
"",
|
|
2448
|
+
"## Original prompt",
|
|
2449
|
+
"",
|
|
2450
|
+
options.prompt,
|
|
2451
|
+
""
|
|
2452
|
+
].join("\n");
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
// src/agent-execution/working-agent/targets/run-working-agent-target-attempt.ts
|
|
2456
|
+
import { rm } from "fs/promises";
|
|
2457
|
+
|
|
2458
|
+
// src/agent-execution/working-agent/output/read-working-agent-output.ts
|
|
2459
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
2460
|
+
async function readWorkingAgentOutput(options) {
|
|
2461
|
+
let raw;
|
|
2462
|
+
try {
|
|
2463
|
+
raw = await readFile4(options.outputFile, "utf8");
|
|
2464
|
+
} catch (error) {
|
|
2465
|
+
throw new TrailStepFailureError({
|
|
2466
|
+
code: "agent_output_unreadable",
|
|
2467
|
+
message: `Working agent step ${options.stepId} output.json could not be read.`,
|
|
2468
|
+
details: error instanceof Error ? { path: options.outputFile, cause: error.message } : { path: options.outputFile }
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
if (options.step.output.captureMode === "raw-text") {
|
|
2472
|
+
const capturedDocument = await document(raw);
|
|
2473
|
+
return options.step.output.assert(capturedDocument, `step ${options.stepId} output`);
|
|
2474
|
+
}
|
|
2475
|
+
let parsed;
|
|
2476
|
+
try {
|
|
2477
|
+
parsed = JSON.parse(raw);
|
|
2478
|
+
} catch (error) {
|
|
2479
|
+
throw new TrailStepFailureError({
|
|
2480
|
+
code: "agent_output_invalid_json",
|
|
2481
|
+
message: `Working agent step ${options.stepId} output.json must contain one JSON object.`,
|
|
2482
|
+
details: error instanceof Error ? { path: options.outputFile, cause: error.message } : { path: options.outputFile }
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2486
|
+
throw new TrailStepFailureError({
|
|
2487
|
+
code: "agent_output_invalid_json",
|
|
2488
|
+
message: `Working agent step ${options.stepId} output.json must contain one JSON object.`,
|
|
2489
|
+
details: { path: options.outputFile }
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
return options.step.output.assert(parsed, `step ${options.stepId} output`);
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// src/agent-execution/working-agent/targets/custom-provider/build-working-agent-args.ts
|
|
2496
|
+
function buildWorkingAgentArgs(options) {
|
|
2497
|
+
if (!options.argv) {
|
|
2498
|
+
return [
|
|
2499
|
+
"--prompt-file",
|
|
2500
|
+
options.promptFile,
|
|
2501
|
+
"--output-file",
|
|
2502
|
+
options.outputFile,
|
|
2503
|
+
...options.model ? ["--model", options.model] : []
|
|
2504
|
+
];
|
|
2505
|
+
}
|
|
2506
|
+
return options.argv.map((arg) => {
|
|
2507
|
+
switch (arg) {
|
|
2508
|
+
case "{{promptFile}}":
|
|
2509
|
+
return options.promptFile;
|
|
2510
|
+
case "{{outputFile}}":
|
|
2511
|
+
return options.outputFile;
|
|
2512
|
+
case "{{model}}":
|
|
2513
|
+
return options.model ?? "";
|
|
2514
|
+
default:
|
|
2515
|
+
if (arg.includes("{{promptFile}}") || arg.includes("{{outputFile}}") || arg.includes("{{model}}")) {
|
|
2516
|
+
throw new TrailStepFailureError({
|
|
2517
|
+
code: "agent_provider_invalid",
|
|
2518
|
+
message: "Working agent command placeholders must be whole argv values."
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
return arg;
|
|
2522
|
+
}
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
// src/agent-execution/working-agent/targets/custom-provider/spawn-working-agent-process.ts
|
|
2527
|
+
import { spawn as spawn6 } from "child_process";
|
|
2528
|
+
var spawnWorkingAgentProcess = async ({
|
|
2529
|
+
command,
|
|
2530
|
+
args,
|
|
2531
|
+
cwd,
|
|
2532
|
+
signal
|
|
2533
|
+
}) => {
|
|
2534
|
+
return await new Promise((resolve2, reject) => {
|
|
2535
|
+
const child = spawn6(command, args, {
|
|
2536
|
+
cwd,
|
|
2537
|
+
shell: false,
|
|
2538
|
+
stdio: "inherit",
|
|
2539
|
+
detached: process.platform !== "win32"
|
|
2540
|
+
});
|
|
2541
|
+
signal?.addEventListener("abort", () => terminateChildProcessTree6(child), { once: true });
|
|
2542
|
+
child.on("error", reject);
|
|
2543
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1 }));
|
|
2544
|
+
});
|
|
2545
|
+
};
|
|
2546
|
+
function terminateChildProcessTree6(child) {
|
|
2547
|
+
if (!child.pid) {
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
if (process.platform === "win32") {
|
|
2551
|
+
spawn6("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
|
|
2552
|
+
"error",
|
|
2553
|
+
() => {
|
|
2554
|
+
child.kill();
|
|
2555
|
+
}
|
|
2556
|
+
);
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
try {
|
|
2560
|
+
process.kill(-child.pid, "SIGTERM");
|
|
2561
|
+
} catch {
|
|
2562
|
+
child.kill();
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
// src/agent-execution/working-agent/targets/custom-provider/run-custom-working-provider.ts
|
|
2567
|
+
async function runCustomWorkingProvider(options) {
|
|
2568
|
+
const agentConfig = options.config.customProviders[options.target.provider];
|
|
2569
|
+
if (!agentConfig) {
|
|
2570
|
+
throw new TrailStepFailureError({
|
|
2571
|
+
code: "agent_provider_unavailable",
|
|
2572
|
+
message: `Working agent target '${options.target.provider}' does not reference a configured custom agent.`,
|
|
2573
|
+
details: { provider: options.target.provider }
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
const args = buildWorkingAgentArgs({
|
|
2577
|
+
argv: options.target.args ?? agentConfig.args,
|
|
2578
|
+
promptFile: options.files.promptFile,
|
|
2579
|
+
outputFile: options.files.outputFile,
|
|
2580
|
+
model: options.target.model
|
|
2581
|
+
});
|
|
2582
|
+
let result;
|
|
2583
|
+
try {
|
|
2584
|
+
result = await (options.runner ?? spawnWorkingAgentProcess)({
|
|
2585
|
+
command: agentConfig.binary,
|
|
2586
|
+
args,
|
|
2587
|
+
cwd: options.cwd,
|
|
2588
|
+
shell: false,
|
|
2589
|
+
stdio: "inherit",
|
|
2590
|
+
promptFile: options.files.promptFile,
|
|
2591
|
+
outputFile: options.files.outputFile,
|
|
2592
|
+
...options.target.model === void 0 ? {} : { model: options.target.model },
|
|
2593
|
+
signal: options.signal
|
|
2594
|
+
});
|
|
2595
|
+
} catch (error) {
|
|
2596
|
+
throw new TrailStepFailureError({
|
|
2597
|
+
code: "agent_provider_spawn_error",
|
|
2598
|
+
message: `Working agent step ${options.step.id} could not start target '${options.target.provider}'.`,
|
|
2599
|
+
details: {
|
|
2600
|
+
target: options.target.provider,
|
|
2601
|
+
...options.target.model === void 0 ? {} : { model: options.target.model },
|
|
2602
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
if (result.exitCode !== 0) {
|
|
2607
|
+
throw new TrailStepFailureError({
|
|
2608
|
+
code: "agent_provider_failed",
|
|
2609
|
+
message: `Working agent step ${options.step.id} target '${options.target.provider}' exited with code ${result.exitCode}.`,
|
|
2610
|
+
details: {
|
|
2611
|
+
exitCode: result.exitCode,
|
|
2612
|
+
provider: agentConfig.binary,
|
|
2613
|
+
target: options.target.provider,
|
|
2614
|
+
...options.target.model === void 0 ? {} : { model: options.target.model }
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
return readWorkingAgentOutput({
|
|
2619
|
+
stepId: options.step.id,
|
|
2620
|
+
outputFile: options.files.outputFile,
|
|
2621
|
+
step: options.step
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
// src/agent-execution/working-agent/targets/registry-provider/run-registry-working-provider.ts
|
|
2626
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
2627
|
+
|
|
2628
|
+
// src/agent-execution/working-agent/prompts/build-provider-working-prompt.ts
|
|
2629
|
+
function buildProviderWorkingPrompt(options) {
|
|
2630
|
+
if (options.captureMode === "raw-text") {
|
|
2631
|
+
return [
|
|
2632
|
+
"# TrailStep working-agent task",
|
|
2633
|
+
"",
|
|
2634
|
+
"Print the document content directly as your entire response \u2014 no JSON wrapper, no surrounding commentary, no markdown fences unless they are literally part of the document content itself.",
|
|
2635
|
+
"Do not write output to a file.",
|
|
2636
|
+
"",
|
|
2637
|
+
"## Original prompt",
|
|
2638
|
+
"",
|
|
2639
|
+
options.prompt,
|
|
2640
|
+
""
|
|
2641
|
+
].join("\n");
|
|
2642
|
+
}
|
|
2643
|
+
return [
|
|
2644
|
+
"# TrailStep working-agent task",
|
|
2645
|
+
"",
|
|
2646
|
+
"Respond with exactly one JSON object as your entire final answer, and nothing else.",
|
|
2647
|
+
"Do not write output to a file. Do not include prose, markdown fences, or multiple JSON values in your final answer - only the JSON object itself. This instruction applies to your literal final message, not just the work you do to get there.",
|
|
2648
|
+
"",
|
|
2649
|
+
"The JSON object must match this output schema:",
|
|
2650
|
+
"",
|
|
2651
|
+
"```json",
|
|
2652
|
+
JSON.stringify(options.outputSchema, null, 2),
|
|
2653
|
+
"```",
|
|
2654
|
+
"",
|
|
2655
|
+
"## Original prompt",
|
|
2656
|
+
"",
|
|
2657
|
+
options.prompt,
|
|
2658
|
+
""
|
|
2659
|
+
].join("\n");
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
// src/agent-execution/working-agent/targets/registry-provider/repair-provider-output.ts
|
|
2663
|
+
var MAX_OUTPUT_REPAIR_ATTEMPTS = 1;
|
|
2664
|
+
async function attemptProviderOutputRepair(options) {
|
|
2665
|
+
if (!options.provider.repairOutput) {
|
|
2666
|
+
return false;
|
|
2667
|
+
}
|
|
2668
|
+
const repairable = extractRepairableFailure(options.error);
|
|
2669
|
+
if (!repairable || MAX_OUTPUT_REPAIR_ATTEMPTS < 1) {
|
|
2670
|
+
return false;
|
|
2671
|
+
}
|
|
2672
|
+
try {
|
|
2673
|
+
await options.provider.repairOutput(
|
|
2674
|
+
{
|
|
2675
|
+
sessionId: repairable.sessionId,
|
|
2676
|
+
rawResultText: repairable.rawResultText ?? "",
|
|
2677
|
+
outputFile: options.files.outputFile,
|
|
2678
|
+
usageFile: options.files.usageFile,
|
|
2679
|
+
cwd: options.cwd,
|
|
2680
|
+
...options.model === void 0 ? {} : { model: options.model },
|
|
2681
|
+
...options.thinking === void 0 ? {} : { thinking: options.thinking },
|
|
2682
|
+
outputSchema: options.outputSchema,
|
|
2683
|
+
captureMode: options.captureMode,
|
|
2684
|
+
signal: options.signal
|
|
2685
|
+
},
|
|
2686
|
+
options.providerWorkingRunner
|
|
2687
|
+
);
|
|
2688
|
+
return true;
|
|
2689
|
+
} catch {
|
|
2690
|
+
return false;
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
function extractRepairableFailure(error) {
|
|
2694
|
+
if (!(error instanceof TrailStepFailureError)) {
|
|
2695
|
+
return void 0;
|
|
2696
|
+
}
|
|
2697
|
+
if (error.failure.code !== "agent_provider_output_invalid") {
|
|
2698
|
+
return void 0;
|
|
2699
|
+
}
|
|
2700
|
+
const details = error.failure.details;
|
|
2701
|
+
if (typeof details !== "object" || details === null) {
|
|
2702
|
+
return void 0;
|
|
2703
|
+
}
|
|
2704
|
+
const sessionId = details.sessionId;
|
|
2705
|
+
if (typeof sessionId !== "string") {
|
|
2706
|
+
return void 0;
|
|
2707
|
+
}
|
|
2708
|
+
const rawResultText = details.rawResultText;
|
|
2709
|
+
return {
|
|
2710
|
+
sessionId,
|
|
2711
|
+
...typeof rawResultText === "string" ? { rawResultText } : {}
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
// src/agent-execution/working-agent/targets/registry-provider/run-registry-working-provider.ts
|
|
2716
|
+
async function runRegistryWorkingProvider(options) {
|
|
2717
|
+
await writeFile6(
|
|
2718
|
+
options.files.promptFile,
|
|
2719
|
+
buildProviderWorkingPrompt({
|
|
2720
|
+
prompt: options.renderedPrompt,
|
|
2721
|
+
outputSchema: options.step.output.jsonSchema,
|
|
2722
|
+
captureMode: options.step.output.captureMode
|
|
2723
|
+
}),
|
|
2724
|
+
"utf8"
|
|
2725
|
+
);
|
|
2726
|
+
const thinking = resolveWorkingThinking(options.target, options.role);
|
|
2727
|
+
try {
|
|
2728
|
+
await options.provider.runWorking(
|
|
2729
|
+
{
|
|
2730
|
+
promptFile: options.files.promptFile,
|
|
2731
|
+
outputFile: options.files.outputFile,
|
|
2732
|
+
usageFile: options.files.usageFile,
|
|
2733
|
+
cwd: options.cwd,
|
|
2734
|
+
...options.target.model === void 0 ? {} : { model: options.target.model },
|
|
2735
|
+
...thinking === void 0 ? {} : { thinking },
|
|
2736
|
+
captureMode: options.step.output.captureMode,
|
|
2737
|
+
signal: options.signal
|
|
2738
|
+
},
|
|
2739
|
+
options.providerWorkingRunner
|
|
2740
|
+
);
|
|
2741
|
+
} catch (error) {
|
|
2742
|
+
const repaired = await attemptProviderOutputRepair({
|
|
2743
|
+
provider: options.provider,
|
|
2744
|
+
error,
|
|
2745
|
+
model: options.target.model,
|
|
2746
|
+
thinking,
|
|
2747
|
+
outputSchema: options.step.output.jsonSchema,
|
|
2748
|
+
captureMode: options.step.output.captureMode,
|
|
2749
|
+
files: options.files,
|
|
2750
|
+
cwd: options.cwd,
|
|
2751
|
+
providerWorkingRunner: options.providerWorkingRunner,
|
|
2752
|
+
signal: options.signal
|
|
2753
|
+
});
|
|
2754
|
+
if (!repaired) {
|
|
2755
|
+
throw error;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
return readWorkingAgentOutput({
|
|
2759
|
+
stepId: options.step.id,
|
|
2760
|
+
outputFile: options.files.outputFile,
|
|
2761
|
+
step: options.step
|
|
2762
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
function resolveWorkingThinking(target, role) {
|
|
2765
|
+
return target.thinking ?? role.thinking;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
// src/agent-execution/working-agent/targets/run-working-agent-target-attempt.ts
|
|
2769
|
+
async function runWorkingAgentTargetAttempt(options) {
|
|
2770
|
+
await rm(options.files.outputFile, { force: true });
|
|
2771
|
+
await rm(options.files.usageFile, { force: true });
|
|
2772
|
+
const provider = providerRegistry[options.target.provider];
|
|
2773
|
+
if (provider) {
|
|
2774
|
+
return runRegistryWorkingProvider({
|
|
2775
|
+
provider,
|
|
2776
|
+
role: options.role,
|
|
2777
|
+
step: options.step,
|
|
2778
|
+
renderedPrompt: options.renderedPrompt,
|
|
2779
|
+
cwd: options.cwd,
|
|
2780
|
+
providerWorkingRunner: options.providerWorkingRunner,
|
|
2781
|
+
target: options.target,
|
|
2782
|
+
files: options.files,
|
|
2783
|
+
signal: options.signal
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
return runCustomWorkingProvider({
|
|
2787
|
+
config: options.config,
|
|
2788
|
+
step: options.step,
|
|
2789
|
+
cwd: options.cwd,
|
|
2790
|
+
runner: options.runner,
|
|
2791
|
+
target: options.target,
|
|
2792
|
+
files: options.files,
|
|
2793
|
+
signal: options.signal
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
// src/agent-execution/working-agent/targets/target-failures.ts
|
|
2798
|
+
function summarizeWorkingAgentAttemptFailure(target, error) {
|
|
2799
|
+
if (error instanceof TrailStepFailureError) {
|
|
2800
|
+
return {
|
|
2801
|
+
target: target.provider,
|
|
2802
|
+
...target.model === void 0 ? {} : { model: target.model },
|
|
2803
|
+
code: error.failure.code,
|
|
2804
|
+
message: error.failure.message,
|
|
2805
|
+
...error.failure.details === void 0 ? {} : { details: error.failure.details }
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
return {
|
|
2809
|
+
target: target.provider,
|
|
2810
|
+
...target.model === void 0 ? {} : { model: target.model },
|
|
2811
|
+
code: "agent_target_failed",
|
|
2812
|
+
message: error instanceof Error ? error.message : "Working agent target failed."
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/agent-execution/working-agent/run-working-agent-command.ts
|
|
2817
|
+
async function runWorkingAgentCommand(options) {
|
|
2818
|
+
const targets = resolveAgentTargets({
|
|
2819
|
+
config: options.config,
|
|
2820
|
+
workflowId: options.workflowId,
|
|
2821
|
+
roleName: options.roleName,
|
|
2822
|
+
roleSize: options.role.size
|
|
2823
|
+
});
|
|
2824
|
+
const files = options.files ?? resolveStepAgentFiles({
|
|
2825
|
+
runDir: options.runDir,
|
|
2826
|
+
stepId: options.step.id,
|
|
2827
|
+
stepIndex: options.stepIndex
|
|
2828
|
+
});
|
|
2829
|
+
await mkdir3(files.stepDir, { recursive: true });
|
|
2830
|
+
await writeFile7(
|
|
2831
|
+
files.promptFile,
|
|
2832
|
+
buildWorkingAgentPrompt({
|
|
2833
|
+
prompt: options.renderedPrompt,
|
|
2834
|
+
outputFile: files.outputFile,
|
|
2835
|
+
outputSchema: options.step.output.jsonSchema,
|
|
2836
|
+
captureMode: options.step.output.captureMode
|
|
2837
|
+
}),
|
|
2838
|
+
"utf8"
|
|
2839
|
+
);
|
|
2840
|
+
const failures = [];
|
|
2841
|
+
for (const target of targets) {
|
|
2842
|
+
try {
|
|
2843
|
+
return await runWorkingAgentTargetAttempt({ ...options, target, files });
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
failures.push(summarizeWorkingAgentAttemptFailure(target, error));
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
throw new TrailStepFailureError({
|
|
2849
|
+
code: "agent_target_exhausted",
|
|
2850
|
+
message: `Working agent step ${options.step.id} for role ${options.roleName} exhausted ${failures.length} target(s).`,
|
|
2851
|
+
details: {
|
|
2852
|
+
stepId: options.step.id,
|
|
2853
|
+
roleName: options.roleName,
|
|
2854
|
+
attempts: failures
|
|
2855
|
+
}
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
// src/agent-execution/dispatch-agent-step/dispatch-agent-step.ts
|
|
2860
|
+
async function dispatchAgentStep(options) {
|
|
2861
|
+
const { config, outputSchema } = options;
|
|
2862
|
+
const resolvedRole = resolveWorkflowAgentRole({
|
|
2863
|
+
agent: config.agent,
|
|
2864
|
+
workflowAgents: options.workflowAgents,
|
|
2865
|
+
workflowId: options.workflowId,
|
|
2866
|
+
stepId: config.id
|
|
2867
|
+
});
|
|
2868
|
+
const renderedPrompt = await resolvePromptSource(config.prompt, config.input, options.cwd);
|
|
2869
|
+
const agentMode = config.mode ?? "working";
|
|
2870
|
+
const agentStep = {
|
|
2871
|
+
kind: "agent",
|
|
2872
|
+
id: config.id,
|
|
2873
|
+
output: outputSchema,
|
|
2874
|
+
prompt: renderedPrompt,
|
|
2875
|
+
requirements: resolvedRole.role,
|
|
2876
|
+
adapter: config.adapter
|
|
2877
|
+
};
|
|
2878
|
+
if (!options.trailstepConfig && config.adapter === void 0) {
|
|
2879
|
+
throwMissingAgentConfig({
|
|
2880
|
+
workflowId: options.workflowId,
|
|
2881
|
+
stepId: config.id,
|
|
2882
|
+
agent: resolvedRole.roleName,
|
|
2883
|
+
mode: agentMode
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
if (options.trailstepConfig && agentMode === "interactive") {
|
|
2887
|
+
await options.emit(
|
|
2888
|
+
createEvent({
|
|
2889
|
+
runId: options.runId,
|
|
2890
|
+
workflowId: options.workflowId,
|
|
2891
|
+
stepId: config.id,
|
|
2892
|
+
type: "interactive.sessionStarted",
|
|
2893
|
+
payload: { roleName: resolvedRole.roleName, stepIndex: options.stepIndex }
|
|
2894
|
+
})
|
|
2895
|
+
);
|
|
2896
|
+
const artifactPaths = resolveStepArtifactPaths({
|
|
2897
|
+
runDir: options.runDir,
|
|
2898
|
+
stepId: config.id,
|
|
2899
|
+
stepIndex: options.stepIndex
|
|
2900
|
+
});
|
|
2901
|
+
const interactiveResult = await runInteractiveAgentCommand({
|
|
2902
|
+
config: options.trailstepConfig,
|
|
2903
|
+
workflowId: options.workflowId,
|
|
2904
|
+
roleName: resolvedRole.roleName,
|
|
2905
|
+
role: resolvedRole.role,
|
|
2906
|
+
stepId: config.id,
|
|
2907
|
+
renderedPrompt,
|
|
2908
|
+
runDir: options.runDir,
|
|
2909
|
+
runner: options.processRunner,
|
|
2910
|
+
outputSchema,
|
|
2911
|
+
outputMode: options.interactiveOutputMode,
|
|
2912
|
+
artifactPaths,
|
|
2913
|
+
signal: options.signal
|
|
2914
|
+
});
|
|
2915
|
+
await options.emit(
|
|
2916
|
+
createEvent({
|
|
2917
|
+
runId: options.runId,
|
|
2918
|
+
workflowId: options.workflowId,
|
|
2919
|
+
stepId: config.id,
|
|
2920
|
+
type: "interactive.sessionCompleted",
|
|
2921
|
+
payload: {
|
|
2922
|
+
exitCode: interactiveResult.exitCode,
|
|
2923
|
+
outputMode: options.interactiveOutputMode,
|
|
2924
|
+
stepDir: artifactPaths.runRelativeStepDir
|
|
2925
|
+
}
|
|
2926
|
+
})
|
|
2927
|
+
);
|
|
2928
|
+
return interactiveResult.output;
|
|
2929
|
+
}
|
|
2930
|
+
return options.trailstepConfig && agentMode === "working" ? await runWorkingAgentCommand({
|
|
2931
|
+
config: options.trailstepConfig,
|
|
2932
|
+
workflowId: options.workflowId,
|
|
2933
|
+
roleName: resolvedRole.roleName,
|
|
2934
|
+
role: resolvedRole.role,
|
|
2935
|
+
step: agentStep,
|
|
2936
|
+
renderedPrompt,
|
|
2937
|
+
runDir: options.runDir,
|
|
2938
|
+
cwd: options.cwd,
|
|
2939
|
+
runner: options.workingAgentProcessRunner,
|
|
2940
|
+
providerWorkingRunner: options.providerWorkingRunner,
|
|
2941
|
+
stepIndex: options.stepIndex,
|
|
2942
|
+
signal: options.signal
|
|
2943
|
+
}) : await runAgentStep({
|
|
2944
|
+
step: agentStep,
|
|
2945
|
+
input: config.input,
|
|
2946
|
+
onToolCall: async (toolCall) => {
|
|
2947
|
+
await options.emit(
|
|
2948
|
+
createEvent({
|
|
2949
|
+
runId: options.runId,
|
|
2950
|
+
workflowId: options.workflowId,
|
|
2951
|
+
stepId: config.id,
|
|
2952
|
+
type: "agent.toolCall",
|
|
2953
|
+
payload: { ...toolCall }
|
|
2954
|
+
})
|
|
2955
|
+
);
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
async function resolvePromptSource(source, input, cwd) {
|
|
2960
|
+
if (isPromptTemplateSource(source)) {
|
|
2961
|
+
return await readFile5(resolve(cwd, source.path), "utf8");
|
|
2962
|
+
}
|
|
2963
|
+
return renderAgentPrompt(source, input);
|
|
2964
|
+
}
|
|
2965
|
+
function isPromptTemplateSource(source) {
|
|
2966
|
+
return typeof source === "object" && source !== null && source.kind === "promptTemplate";
|
|
2967
|
+
}
|
|
2968
|
+
function throwMissingAgentConfig(options) {
|
|
2969
|
+
throw new TrailStepFailureError({
|
|
2970
|
+
code: "missing_agent_config",
|
|
2971
|
+
message: `Missing .trailstep/config.json: workflow ${options.workflowId} step ${options.stepId} needs configured ${options.mode} agent '${options.agent}'.`,
|
|
2972
|
+
details: {
|
|
2973
|
+
workflowId: options.workflowId,
|
|
2974
|
+
stepId: options.stepId,
|
|
2975
|
+
agent: options.agent,
|
|
2976
|
+
mode: options.mode
|
|
2977
|
+
}
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
var DEFAULT_AGENT_ROLE_NAME = "default";
|
|
2981
|
+
var BUILTIN_DEFAULT_AGENT_ROLE = { size: "default" };
|
|
2982
|
+
function resolveWorkflowAgentRole(options) {
|
|
2983
|
+
const roleName = options.agent ?? DEFAULT_AGENT_ROLE_NAME;
|
|
2984
|
+
const role = options.workflowAgents[roleName];
|
|
2985
|
+
if (role) {
|
|
2986
|
+
return { role, roleName };
|
|
2987
|
+
}
|
|
2988
|
+
if (options.agent === void 0) {
|
|
2989
|
+
return { role: BUILTIN_DEFAULT_AGENT_ROLE, roleName: DEFAULT_AGENT_ROLE_NAME };
|
|
2990
|
+
}
|
|
2991
|
+
throw new TrailStepFailureError({
|
|
2992
|
+
code: "agent_role_unknown",
|
|
2993
|
+
message: `Step ${options.stepId} references unknown agent role '${options.agent}' in workflow ${options.workflowId}. Declare workflow.agents.${options.agent} before referencing it with step agent.`,
|
|
2994
|
+
details: {
|
|
2995
|
+
workflowId: options.workflowId,
|
|
2996
|
+
stepId: options.stepId,
|
|
2997
|
+
agent: options.agent,
|
|
2998
|
+
declaredAgents: Object.keys(options.workflowAgents)
|
|
2999
|
+
}
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
// src/runtime/sub-prompts/sub-prompt-artifacts.ts
|
|
3004
|
+
import { join as join6, relative } from "path";
|
|
3005
|
+
function resolveSubPromptArtifactPaths(options) {
|
|
3006
|
+
const artifactSubPromptId = `${String(options.ordinal).padStart(4, "0")}-${options.fingerprint.slice(0, 12)}`;
|
|
3007
|
+
const subPromptDir = join6(options.stepDir, "subPrompts", artifactSubPromptId);
|
|
3008
|
+
const promptFile = join6(subPromptDir, "prompt.txt");
|
|
3009
|
+
const outputFile = join6(subPromptDir, "output.json");
|
|
3010
|
+
const usageFile = join6(subPromptDir, "usage.json");
|
|
3011
|
+
return {
|
|
3012
|
+
subPromptDir,
|
|
3013
|
+
promptFile,
|
|
3014
|
+
outputFile,
|
|
3015
|
+
usageFile,
|
|
3016
|
+
runRelative: {
|
|
3017
|
+
subPromptDir: toRunRelativePath(options.runDir, subPromptDir),
|
|
3018
|
+
promptFile: toRunRelativePath(options.runDir, promptFile),
|
|
3019
|
+
outputFile: toRunRelativePath(options.runDir, outputFile)
|
|
3020
|
+
}
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
function toRunRelativePath(runDir, path) {
|
|
3024
|
+
return relative(runDir, path).replaceAll("\\", "/");
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
// src/runtime/failures/failure-like.ts
|
|
3028
|
+
function isFailureLikeError(error) {
|
|
3029
|
+
return typeof error === "object" && error !== null && "failure" in error && typeof error.failure === "object" && error.failure !== null && "code" in error.failure && typeof error.failure.code === "string" && "message" in error.failure && typeof error.failure.message === "string";
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
// src/runtime/sub-prompts/sub-prompt-failures.ts
|
|
3033
|
+
async function emitSubPromptFailed(options) {
|
|
3034
|
+
await options.context.emit?.(
|
|
3035
|
+
createEvent({
|
|
3036
|
+
runId: options.context.id,
|
|
3037
|
+
workflowId: options.context.workflowId ?? "",
|
|
3038
|
+
stepId: options.parentStepId,
|
|
3039
|
+
type: "subPrompt.failed",
|
|
3040
|
+
payload: {
|
|
3041
|
+
parentStepId: options.parentStepId,
|
|
3042
|
+
ordinal: options.ordinal,
|
|
3043
|
+
...options.fingerprint === void 0 ? {} : { fingerprint: options.fingerprint },
|
|
3044
|
+
...options.artifactPaths === void 0 ? {} : { artifactPaths: options.artifactPaths },
|
|
3045
|
+
failure: options.failure
|
|
3046
|
+
}
|
|
3047
|
+
})
|
|
3048
|
+
);
|
|
3049
|
+
}
|
|
3050
|
+
function maxSubPromptsGuardError(maxSubPrompts) {
|
|
3051
|
+
return new TrailStepFailureError({
|
|
3052
|
+
code: "max_sub_prompts_exceeded",
|
|
3053
|
+
message: `workflow exceeded maxSubPrompts guard (${maxSubPrompts})`
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
function throwMissingSubPromptAgentConfig(options) {
|
|
3057
|
+
throw new TrailStepFailureError({
|
|
3058
|
+
code: "missing_agent_config",
|
|
3059
|
+
message: `Missing .trailstep/config.json: workflow ${options.workflowId} parent step ${options.parentStepId} subPrompt ${options.subPromptId} needs configured working agent '${options.agent}'.`,
|
|
3060
|
+
details: {
|
|
3061
|
+
workflowId: options.workflowId,
|
|
3062
|
+
parentStepId: options.parentStepId,
|
|
3063
|
+
subPromptId: options.subPromptId,
|
|
3064
|
+
agent: options.agent,
|
|
3065
|
+
mode: "working"
|
|
3066
|
+
}
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
function subPromptFailureError(error) {
|
|
3070
|
+
if (error instanceof TrailStepFailureError) {
|
|
3071
|
+
if (error.failure.code === "validation_failed") {
|
|
3072
|
+
return new TrailStepFailureError({
|
|
3073
|
+
...error.failure,
|
|
3074
|
+
message: normalizeSubPromptValidationMessage(error.failure.message)
|
|
3075
|
+
});
|
|
3076
|
+
}
|
|
3077
|
+
return error;
|
|
3078
|
+
}
|
|
3079
|
+
if (isFailureLikeError(error)) {
|
|
3080
|
+
return new TrailStepFailureError(error.failure);
|
|
3081
|
+
}
|
|
3082
|
+
return new TrailStepFailureError({
|
|
3083
|
+
code: "sub_prompt_failed",
|
|
3084
|
+
message: error instanceof Error ? error.message : "subPrompt failed.",
|
|
3085
|
+
...error instanceof Error ? { details: { name: error.name } } : error === void 0 ? {} : { details: { cause: error } }
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
function normalizeSubPromptValidationMessage(message) {
|
|
3089
|
+
if (message.startsWith("subPrompt output failed schema validation")) {
|
|
3090
|
+
return message;
|
|
3091
|
+
}
|
|
3092
|
+
const diagnostics = message.match(/failed schema validation: (.*)$/)?.[1];
|
|
3093
|
+
return diagnostics === void 0 ? `subPrompt output failed schema validation: ${message}` : `subPrompt output failed schema validation: ${diagnostics}`;
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
// src/runtime/sub-prompts/sub-prompt-fingerprint.ts
|
|
3097
|
+
import { createHash } from "crypto";
|
|
3098
|
+
function fingerprintSubPrompt(options) {
|
|
3099
|
+
return createHash("sha256").update(stableJsonStringify(options)).digest("hex");
|
|
3100
|
+
}
|
|
3101
|
+
function stableJsonStringify(value) {
|
|
3102
|
+
if (Array.isArray(value)) {
|
|
3103
|
+
return `[${value.map((item) => stableJsonStringify(item)).join(",")}]`;
|
|
3104
|
+
}
|
|
3105
|
+
if (isPlainObject4(value)) {
|
|
3106
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
3107
|
+
return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableJsonStringify(entryValue)}`).join(",")}}`;
|
|
3108
|
+
}
|
|
3109
|
+
return JSON.stringify(value);
|
|
3110
|
+
}
|
|
3111
|
+
function isPlainObject4(value) {
|
|
3112
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
// src/runtime/sub-prompts/sub-prompt-replay.ts
|
|
3116
|
+
function findCompletedSubPromptEvent(events, parentStepId, ordinal, fingerprint) {
|
|
3117
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
3118
|
+
const event = events[index];
|
|
3119
|
+
if (event?.type !== "subPrompt.completed") {
|
|
3120
|
+
continue;
|
|
3121
|
+
}
|
|
3122
|
+
const payload = event.payload;
|
|
3123
|
+
if (payload.parentStepId === parentStepId && payload.ordinal === ordinal && payload.fingerprint === fingerprint && isPlainObject5(payload.output)) {
|
|
3124
|
+
return event;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
return void 0;
|
|
3128
|
+
}
|
|
3129
|
+
function isPlainObject5(value) {
|
|
3130
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
// src/runtime/sub-prompts/run-sub-prompt.ts
|
|
3134
|
+
function resolveMaxSubPrompts(value) {
|
|
3135
|
+
if (value === void 0) {
|
|
3136
|
+
return 25;
|
|
3137
|
+
}
|
|
3138
|
+
if (Number.isInteger(value) && typeof value === "number" && value > 0) {
|
|
3139
|
+
return value;
|
|
3140
|
+
}
|
|
3141
|
+
throw new TrailStepFailureError({
|
|
3142
|
+
code: "invalid_max_sub_prompts",
|
|
3143
|
+
message: "maxSubPrompts must be a positive integer."
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
async function runSubPrompt(source, options, input) {
|
|
3147
|
+
const resolvedInput = input ?? {};
|
|
3148
|
+
const context = runContextStorage.getStore();
|
|
3149
|
+
if (!context?.currentStep) {
|
|
3150
|
+
throw new TrailStepFailureError({
|
|
3151
|
+
code: "sub_prompt_outside_step_context",
|
|
3152
|
+
message: "subPrompt requires an active TrailStep step run context."
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
if (!context.workflowId || !context.workflowAgents || !context.cwd || !context.emit) {
|
|
3156
|
+
throw new TrailStepFailureError({
|
|
3157
|
+
code: "sub_prompt_context_unavailable",
|
|
3158
|
+
message: "subPrompt requires a full active TrailStep step run context."
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
const parentStep = context.currentStep;
|
|
3162
|
+
if (options.output === void 0) {
|
|
3163
|
+
throw new TrailStepFailureError({
|
|
3164
|
+
code: "sub_prompt_output_required",
|
|
3165
|
+
message: `subPrompt in step ${parentStep.id} with a prompt requires an output shape`
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
const outputSchema = normalizeShape(options.output);
|
|
3169
|
+
const ordinal = parentStep.nextSubPromptIndex();
|
|
3170
|
+
const maxSubPrompts = resolveMaxSubPrompts(options.maxSubPrompts ?? parentStep.maxSubPrompts);
|
|
3171
|
+
if (ordinal > maxSubPrompts) {
|
|
3172
|
+
const failureError = maxSubPromptsGuardError(maxSubPrompts);
|
|
3173
|
+
await emitSubPromptFailed({
|
|
3174
|
+
context,
|
|
3175
|
+
parentStepId: parentStep.id,
|
|
3176
|
+
ordinal,
|
|
3177
|
+
failure: failureError.failure
|
|
3178
|
+
});
|
|
3179
|
+
throw failureError;
|
|
3180
|
+
}
|
|
3181
|
+
const renderedPrompt = await resolvePromptSource(source, resolvedInput, context.cwd);
|
|
3182
|
+
const fingerprint = fingerprintSubPrompt({ input: resolvedInput, prompt: renderedPrompt });
|
|
3183
|
+
const cachedEvent = findCompletedSubPromptEvent(
|
|
3184
|
+
context.events?.() ?? [],
|
|
3185
|
+
parentStep.id,
|
|
3186
|
+
ordinal,
|
|
3187
|
+
fingerprint
|
|
3188
|
+
);
|
|
3189
|
+
if (cachedEvent) {
|
|
3190
|
+
return outputSchema.assert(cachedEvent.payload.output, "subPrompt output");
|
|
3191
|
+
}
|
|
3192
|
+
const artifactPaths = resolveSubPromptArtifactPaths({
|
|
3193
|
+
runDir: context.path,
|
|
3194
|
+
stepDir: parentStep.dir,
|
|
3195
|
+
ordinal,
|
|
3196
|
+
fingerprint
|
|
3197
|
+
});
|
|
3198
|
+
await mkdir4(artifactPaths.subPromptDir, { recursive: true });
|
|
3199
|
+
await writeFile8(artifactPaths.promptFile, renderedPrompt, "utf8");
|
|
3200
|
+
const resolvedRole = resolveWorkflowAgentRole({
|
|
3201
|
+
agent: options.agent,
|
|
3202
|
+
workflowAgents: context.workflowAgents,
|
|
3203
|
+
workflowId: context.workflowId,
|
|
3204
|
+
stepId: parentStep.id
|
|
3205
|
+
});
|
|
3206
|
+
await context.emit(
|
|
3207
|
+
createEvent({
|
|
3208
|
+
runId: context.id,
|
|
3209
|
+
workflowId: context.workflowId,
|
|
3210
|
+
stepId: parentStep.id,
|
|
3211
|
+
type: "subPrompt.started",
|
|
3212
|
+
payload: {
|
|
3213
|
+
parentStepId: parentStep.id,
|
|
3214
|
+
ordinal,
|
|
3215
|
+
fingerprint,
|
|
3216
|
+
artifactPaths: artifactPaths.runRelative
|
|
3217
|
+
}
|
|
3218
|
+
})
|
|
3219
|
+
);
|
|
3220
|
+
try {
|
|
3221
|
+
const agentStep = {
|
|
3222
|
+
kind: "agent",
|
|
3223
|
+
id: `${parentStep.id}.subPrompt.${ordinal}`,
|
|
3224
|
+
output: outputSchema,
|
|
3225
|
+
prompt: renderedPrompt,
|
|
3226
|
+
requirements: resolvedRole.role,
|
|
3227
|
+
adapter: options.adapter
|
|
3228
|
+
};
|
|
3229
|
+
if (!context.trailstepConfig && options.adapter === void 0) {
|
|
3230
|
+
throwMissingSubPromptAgentConfig({
|
|
3231
|
+
workflowId: context.workflowId,
|
|
3232
|
+
parentStepId: parentStep.id,
|
|
3233
|
+
subPromptId: agentStep.id,
|
|
3234
|
+
agent: resolvedRole.roleName
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
const rawOutput = context.trailstepConfig && options.adapter === void 0 ? await runWorkingAgentCommand({
|
|
3238
|
+
config: context.trailstepConfig,
|
|
3239
|
+
workflowId: context.workflowId,
|
|
3240
|
+
roleName: resolvedRole.roleName,
|
|
3241
|
+
role: resolvedRole.role,
|
|
3242
|
+
step: agentStep,
|
|
3243
|
+
renderedPrompt,
|
|
3244
|
+
runDir: context.path,
|
|
3245
|
+
cwd: context.cwd,
|
|
3246
|
+
runner: context.workingAgentProcessRunner,
|
|
3247
|
+
providerWorkingRunner: context.providerWorkingRunner,
|
|
3248
|
+
stepIndex: ordinal,
|
|
3249
|
+
files: {
|
|
3250
|
+
stepDir: artifactPaths.subPromptDir,
|
|
3251
|
+
promptFile: artifactPaths.promptFile,
|
|
3252
|
+
outputFile: artifactPaths.outputFile,
|
|
3253
|
+
usageFile: artifactPaths.usageFile
|
|
3254
|
+
}
|
|
3255
|
+
}) : await runAgentStep({
|
|
3256
|
+
step: agentStep,
|
|
3257
|
+
input: resolvedInput,
|
|
3258
|
+
onToolCall: async (toolCall) => {
|
|
3259
|
+
await context.emit?.(
|
|
3260
|
+
createEvent({
|
|
3261
|
+
runId: context.id,
|
|
3262
|
+
workflowId: context.workflowId ?? "",
|
|
3263
|
+
stepId: parentStep.id,
|
|
3264
|
+
type: "agent.toolCall",
|
|
3265
|
+
payload: { ...toolCall }
|
|
3266
|
+
})
|
|
3267
|
+
);
|
|
3268
|
+
}
|
|
3269
|
+
});
|
|
3270
|
+
const output = outputSchema.assert(rawOutput, "subPrompt output");
|
|
3271
|
+
await writeFile8(artifactPaths.outputFile, `${JSON.stringify(output, null, 2)}
|
|
3272
|
+
`, "utf8");
|
|
3273
|
+
await context.emit(
|
|
3274
|
+
createEvent({
|
|
3275
|
+
runId: context.id,
|
|
3276
|
+
workflowId: context.workflowId,
|
|
3277
|
+
stepId: parentStep.id,
|
|
3278
|
+
type: "subPrompt.completed",
|
|
3279
|
+
payload: {
|
|
3280
|
+
parentStepId: parentStep.id,
|
|
3281
|
+
ordinal,
|
|
3282
|
+
fingerprint,
|
|
3283
|
+
output,
|
|
3284
|
+
artifactPaths: artifactPaths.runRelative
|
|
3285
|
+
}
|
|
3286
|
+
})
|
|
3287
|
+
);
|
|
3288
|
+
return output;
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
const failureError = subPromptFailureError(error);
|
|
3291
|
+
await emitSubPromptFailed({
|
|
3292
|
+
context,
|
|
3293
|
+
parentStepId: parentStep.id,
|
|
3294
|
+
ordinal,
|
|
3295
|
+
fingerprint,
|
|
3296
|
+
artifactPaths: artifactPaths.runRelative,
|
|
3297
|
+
failure: failureError.failure
|
|
3298
|
+
});
|
|
3299
|
+
throw failureError;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
// src/authoring/step/sub-prompt.ts
|
|
3304
|
+
function subPrompt(source, options) {
|
|
3305
|
+
const run = (input) => runSubPrompt(source, options, input);
|
|
3306
|
+
return run;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
// src/deprecations/deprecation-manifest.ts
|
|
3310
|
+
import { lte } from "semver";
|
|
3311
|
+
var deprecationManifest = [];
|
|
3312
|
+
function findDeprecationsAsOf(manifest, query) {
|
|
3313
|
+
return manifest.filter((entry) => entry.package === query.package && lte(entry.deprecatedSince, query.version)).map((entry) => toDeprecationStatus(entry, query.version));
|
|
3314
|
+
}
|
|
3315
|
+
function toDeprecationStatus(entry, version) {
|
|
3316
|
+
const isRemoved = entry.removedIn !== void 0 && lte(entry.removedIn, version);
|
|
3317
|
+
return { ...entry, severity: isRemoved ? "blocking" : "warning" };
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
// src/runtime/retry/latest-unresolved-failure.ts
|
|
3321
|
+
function selectLatestUnresolvedFailure(events) {
|
|
3322
|
+
const startedEvent = events.find((event) => event.type === "workflow.started");
|
|
3323
|
+
const workflowInput = startedEvent ? readPlainPayload(startedEvent, "input") : void 0;
|
|
3324
|
+
const resolvedFailures = events.flatMap(
|
|
3325
|
+
(event) => event.type === "workflow.retryStarted" ? readResolvedFailureIdentity(event) : []
|
|
3326
|
+
);
|
|
3327
|
+
let workflowFailureFallback;
|
|
3328
|
+
let laterWorkflowTerminalSeen = false;
|
|
3329
|
+
const laterTerminalStepIds = /* @__PURE__ */ new Set();
|
|
3330
|
+
for (let replayPosition = events.length - 1; replayPosition >= 0; replayPosition -= 1) {
|
|
3331
|
+
const event = events[replayPosition];
|
|
3332
|
+
if (!event) {
|
|
3333
|
+
continue;
|
|
3334
|
+
}
|
|
3335
|
+
if (event.type === "workflow.completed") {
|
|
3336
|
+
return void 0;
|
|
3337
|
+
}
|
|
3338
|
+
if (event.type === "step.completed") {
|
|
3339
|
+
rememberTerminalStep(event, laterTerminalStepIds);
|
|
3340
|
+
continue;
|
|
3341
|
+
}
|
|
3342
|
+
if (event.type === "step.failed") {
|
|
3343
|
+
rememberTerminalStep(event, laterTerminalStepIds);
|
|
3344
|
+
if (isResolvedFailure(event, replayPosition, resolvedFailures)) {
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
3347
|
+
return toLatestUnresolvedFailure(event, replayPosition, workflowInput);
|
|
3348
|
+
}
|
|
3349
|
+
if (event.type === "workflow.failed") {
|
|
3350
|
+
laterWorkflowTerminalSeen = true;
|
|
3351
|
+
if (!workflowFailureFallback) {
|
|
3352
|
+
if (isResolvedFailure(event, replayPosition, resolvedFailures)) {
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
workflowFailureFallback = toLatestUnresolvedFailure(event, replayPosition, workflowInput);
|
|
3356
|
+
}
|
|
3357
|
+
continue;
|
|
3358
|
+
}
|
|
3359
|
+
if (event.type === "step.started" && event.stepId && !laterWorkflowTerminalSeen && !laterTerminalStepIds.has(event.stepId)) {
|
|
3360
|
+
if (isResolvedFailure(event, replayPosition, resolvedFailures)) {
|
|
3361
|
+
continue;
|
|
3362
|
+
}
|
|
3363
|
+
return toLatestUnresolvedFailure(event, replayPosition, workflowInput);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
return workflowFailureFallback;
|
|
3367
|
+
}
|
|
3368
|
+
function rememberTerminalStep(event, stepIds) {
|
|
3369
|
+
if (event.stepId) {
|
|
3370
|
+
stepIds.add(event.stepId);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
function readResolvedFailureIdentity(event) {
|
|
3374
|
+
const { sourceFailureEventId, sourceFailureReplayPosition } = event.payload;
|
|
3375
|
+
if (typeof sourceFailureEventId !== "string" && typeof sourceFailureReplayPosition !== "number") {
|
|
3376
|
+
return [];
|
|
3377
|
+
}
|
|
3378
|
+
return [
|
|
3379
|
+
{
|
|
3380
|
+
eventId: typeof sourceFailureEventId === "string" ? sourceFailureEventId : void 0,
|
|
3381
|
+
replayPosition: typeof sourceFailureReplayPosition === "number" ? sourceFailureReplayPosition : void 0
|
|
3382
|
+
}
|
|
3383
|
+
];
|
|
3384
|
+
}
|
|
3385
|
+
function isResolvedFailure(event, replayPosition, resolvedFailures) {
|
|
3386
|
+
return resolvedFailures.some((resolvedFailure) => {
|
|
3387
|
+
if (resolvedFailure.replayPosition !== void 0) {
|
|
3388
|
+
return resolvedFailure.replayPosition === replayPosition;
|
|
3389
|
+
}
|
|
3390
|
+
return resolvedFailure.eventId !== void 0 && resolvedFailure.eventId === event.id;
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
function toLatestUnresolvedFailure(event, replayPosition, workflowInput) {
|
|
3394
|
+
return {
|
|
3395
|
+
event,
|
|
3396
|
+
replayPosition,
|
|
3397
|
+
sourceFailureEventId: typeof event.id === "string" ? event.id : void 0,
|
|
3398
|
+
workflowId: event.workflowId,
|
|
3399
|
+
workflowInput,
|
|
3400
|
+
stepId: event.stepId
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
function readPlainPayload(event, key) {
|
|
3404
|
+
const value = event.payload[key];
|
|
3405
|
+
return isPlainObject6(value) ? value : void 0;
|
|
3406
|
+
}
|
|
3407
|
+
function isPlainObject6(value) {
|
|
3408
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
// src/runtime/retry/retry-policy.ts
|
|
3412
|
+
var BUILT_IN_RETRY_POLICY = { maxAttempts: 2 };
|
|
3413
|
+
function resolveRetryPolicy(options) {
|
|
3414
|
+
return validateRetryPolicy(
|
|
3415
|
+
firstConfiguredPolicy(options.step, options.workflow, options.global) ?? BUILT_IN_RETRY_POLICY
|
|
3416
|
+
);
|
|
3417
|
+
}
|
|
3418
|
+
function validateRetryPolicy(input) {
|
|
3419
|
+
const maxAttempts = input.maxAttempts ?? BUILT_IN_RETRY_POLICY.maxAttempts;
|
|
3420
|
+
if (typeof maxAttempts !== "number" || !Number.isInteger(maxAttempts) || maxAttempts < 1) {
|
|
3421
|
+
throw new TypeError("retry.maxAttempts must be an integer greater than or equal to 1.");
|
|
3422
|
+
}
|
|
3423
|
+
return { maxAttempts };
|
|
3424
|
+
}
|
|
3425
|
+
function firstConfiguredPolicy(...policies) {
|
|
3426
|
+
return policies.find((policy) => policy?.maxAttempts !== void 0);
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
// src/runtime/run-workflow/run-workflow.ts
|
|
3430
|
+
import { basename as basename3 } from "path";
|
|
3431
|
+
|
|
3432
|
+
// src/runtime/failures/step-execution-failure.ts
|
|
3433
|
+
function stepExecutionFailure(error) {
|
|
3434
|
+
if (isFailureLikeError(error)) {
|
|
3435
|
+
return error.failure;
|
|
3436
|
+
}
|
|
3437
|
+
return {
|
|
3438
|
+
code: "step_execution_failed",
|
|
3439
|
+
message: error instanceof Error ? error.message : "Step execution failed.",
|
|
3440
|
+
...error instanceof Error ? {
|
|
3441
|
+
details: {
|
|
3442
|
+
name: error.name
|
|
3443
|
+
}
|
|
3444
|
+
} : error === void 0 ? {} : { details: { cause: error } }
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
// src/runtime/run-context/with-step-context.ts
|
|
3449
|
+
async function withStepContext(stepId, stepDir, fn, options = {}) {
|
|
3450
|
+
const parentContext = runContextStorage.getStore();
|
|
3451
|
+
if (!parentContext) {
|
|
3452
|
+
return await fn();
|
|
3453
|
+
}
|
|
3454
|
+
let nextDocumentIndex = 0;
|
|
3455
|
+
let nextSubPromptIndex = 0;
|
|
3456
|
+
const stepContext = {
|
|
3457
|
+
...parentContext,
|
|
3458
|
+
currentStep: {
|
|
3459
|
+
id: stepId,
|
|
3460
|
+
dir: stepDir,
|
|
3461
|
+
maxSubPrompts: options.maxSubPrompts,
|
|
3462
|
+
nextDocumentIndex() {
|
|
3463
|
+
nextDocumentIndex += 1;
|
|
3464
|
+
return nextDocumentIndex;
|
|
3465
|
+
},
|
|
3466
|
+
nextSubPromptIndex() {
|
|
3467
|
+
nextSubPromptIndex += 1;
|
|
3468
|
+
return nextSubPromptIndex;
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
};
|
|
3472
|
+
return await runContextStorage.run(stepContext, fn);
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
// src/runtime/timeout/timeout-policy.ts
|
|
3476
|
+
var BUILT_IN_TIMEOUT_POLICY = {};
|
|
3477
|
+
function resolveTimeoutPolicy(options) {
|
|
3478
|
+
const configured = firstConfiguredPolicy2(options.step, options.workflow, options.global);
|
|
3479
|
+
return configured !== void 0 ? validateTimeoutPolicy(configured) : BUILT_IN_TIMEOUT_POLICY;
|
|
3480
|
+
}
|
|
3481
|
+
function validateTimeoutPolicy(input) {
|
|
3482
|
+
if (typeof input !== "number" || !Number.isInteger(input) || input < 1) {
|
|
3483
|
+
throw new TypeError(
|
|
3484
|
+
"timeout must be an integer number of milliseconds greater than or equal to 1."
|
|
3485
|
+
);
|
|
3486
|
+
}
|
|
3487
|
+
return { timeoutMs: input };
|
|
3488
|
+
}
|
|
3489
|
+
function firstConfiguredPolicy2(...policies) {
|
|
3490
|
+
return policies.find((policy) => policy !== void 0);
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
// src/runtime/interactive-session/default-interactive-output-shape.ts
|
|
3494
|
+
var DEFAULT_INTERACTIVE_OUTPUT_SHAPE = { sessionFile: "string" };
|
|
3495
|
+
|
|
3496
|
+
// src/runtime/continuation/resolve-step-output-schema/resolve-step-output-schema.ts
|
|
3497
|
+
function resolveStepOutputSchema(config) {
|
|
3498
|
+
const effectiveOutput = config.output ?? (config.mode === "interactive" ? DEFAULT_INTERACTIVE_OUTPUT_SHAPE : void 0);
|
|
3499
|
+
return effectiveOutput ? normalizeShape(effectiveOutput) : void 0;
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
// src/runtime/continuation/run-continuation/run-continuation.ts
|
|
3503
|
+
async function runContinuation(options) {
|
|
3504
|
+
let node = options.node;
|
|
3505
|
+
let source = options.initialSource;
|
|
3506
|
+
let executedSteps = options.initialExecutedSteps ?? 0;
|
|
3507
|
+
const trailstepConfig = options.trailstepConfig;
|
|
3508
|
+
while (true) {
|
|
3509
|
+
if (isDoneNode(node)) {
|
|
3510
|
+
return { status: "success", output: node.output };
|
|
3511
|
+
}
|
|
3512
|
+
if (isFailNode(node)) {
|
|
3513
|
+
return { status: "failure", failure: node.failure };
|
|
3514
|
+
}
|
|
3515
|
+
if (!isStepNode(node)) {
|
|
3516
|
+
return {
|
|
3517
|
+
status: "failure",
|
|
3518
|
+
failure: continuationFailure(source)
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
if (executedSteps >= options.maxSteps) {
|
|
3522
|
+
return {
|
|
3523
|
+
status: "failure",
|
|
3524
|
+
failure: stepExecutionFailure(
|
|
3525
|
+
new Error(`workflow exceeded maxSteps guard (${options.maxSteps})`)
|
|
3526
|
+
)
|
|
3527
|
+
};
|
|
3528
|
+
}
|
|
3529
|
+
executedSteps += 1;
|
|
3530
|
+
const stepIndex = executedSteps;
|
|
3531
|
+
const stepNode = node;
|
|
3532
|
+
const { config } = stepNode;
|
|
3533
|
+
const hasPrompt = config.prompt !== void 0;
|
|
3534
|
+
const timeoutPolicy = resolveTimeoutPolicy({
|
|
3535
|
+
global: trailstepConfig?.settings?.timeout,
|
|
3536
|
+
workflow: options.workflowTimeout ?? trailstepConfig?.workflows?.[options.workflowId]?.settings?.timeout,
|
|
3537
|
+
step: config.timeout
|
|
3538
|
+
});
|
|
3539
|
+
const maxSubPrompts = config.maxSubPrompts ?? trailstepConfig?.workflows?.[options.workflowId]?.settings?.maxSubPrompts;
|
|
3540
|
+
await options.emit(
|
|
3541
|
+
createEvent({
|
|
3542
|
+
runId: options.runId,
|
|
3543
|
+
workflowId: options.workflowId,
|
|
3544
|
+
stepId: config.id,
|
|
3545
|
+
type: "step.started",
|
|
3546
|
+
payload: { stepName: config.id, kind: hasPrompt ? "agent" : "code" }
|
|
3547
|
+
})
|
|
3548
|
+
);
|
|
3549
|
+
try {
|
|
3550
|
+
const stepDir = resolveStepArtifactPaths({
|
|
3551
|
+
runDir: options.runDir,
|
|
3552
|
+
stepId: config.id,
|
|
3553
|
+
stepIndex
|
|
3554
|
+
}).stepDir;
|
|
3555
|
+
const nextNode = await runWithStepTimeout({
|
|
3556
|
+
stepId: config.id,
|
|
3557
|
+
timeoutMs: timeoutPolicy.timeoutMs,
|
|
3558
|
+
run: async (signal) => await withStepContext(
|
|
3559
|
+
config.id,
|
|
3560
|
+
stepDir,
|
|
3561
|
+
async () => {
|
|
3562
|
+
let paramForNext;
|
|
3563
|
+
if (hasPrompt) {
|
|
3564
|
+
const outputSchema = resolveStepOutputSchema(config);
|
|
3565
|
+
if (!outputSchema) {
|
|
3566
|
+
throw new Error(`step ${config.id} with a prompt requires an output shape`);
|
|
3567
|
+
}
|
|
3568
|
+
const rawOutput = await dispatchAgentStep({
|
|
3569
|
+
config,
|
|
3570
|
+
outputSchema,
|
|
3571
|
+
interactiveOutputMode: config.mode === "interactive" && config.output !== void 0 ? "json" : "session-file",
|
|
3572
|
+
runId: options.runId,
|
|
3573
|
+
workflowId: options.workflowId,
|
|
3574
|
+
emit: options.emit,
|
|
3575
|
+
workflowAgents: options.workflowAgents,
|
|
3576
|
+
runDir: options.runDir,
|
|
3577
|
+
cwd: options.cwd,
|
|
3578
|
+
trailstepConfig,
|
|
3579
|
+
workingAgentProcessRunner: options.workingAgentProcessRunner,
|
|
3580
|
+
providerWorkingRunner: options.providerWorkingRunner,
|
|
3581
|
+
processRunner: options.processRunner,
|
|
3582
|
+
stepIndex,
|
|
3583
|
+
signal
|
|
3584
|
+
});
|
|
3585
|
+
throwIfStepTimedOut(signal, config.id, timeoutPolicy.timeoutMs);
|
|
3586
|
+
paramForNext = outputSchema.assert(rawOutput, `step ${config.id} output`);
|
|
3587
|
+
await options.emit(
|
|
3588
|
+
createEvent({
|
|
3589
|
+
runId: options.runId,
|
|
3590
|
+
workflowId: options.workflowId,
|
|
3591
|
+
stepId: config.id,
|
|
3592
|
+
type: "step.completed",
|
|
3593
|
+
payload: { output: paramForNext }
|
|
3594
|
+
})
|
|
3595
|
+
);
|
|
3596
|
+
} else {
|
|
3597
|
+
paramForNext = config.input;
|
|
3598
|
+
}
|
|
3599
|
+
const nextNode2 = await stepNode.onOutput(paramForNext, config.input);
|
|
3600
|
+
throwIfStepTimedOut(signal, config.id, timeoutPolicy.timeoutMs);
|
|
3601
|
+
if (isFailNode(nextNode2)) {
|
|
3602
|
+
await options.emit(
|
|
3603
|
+
createEvent({
|
|
3604
|
+
runId: options.runId,
|
|
3605
|
+
workflowId: options.workflowId,
|
|
3606
|
+
stepId: config.id,
|
|
3607
|
+
type: "step.failed",
|
|
3608
|
+
payload: { failure: nextNode2.failure }
|
|
3609
|
+
})
|
|
3610
|
+
);
|
|
3611
|
+
return nextNode2;
|
|
3612
|
+
}
|
|
3613
|
+
if (!hasPrompt) {
|
|
3614
|
+
await options.emit(
|
|
3615
|
+
createEvent({
|
|
3616
|
+
runId: options.runId,
|
|
3617
|
+
workflowId: options.workflowId,
|
|
3618
|
+
stepId: config.id,
|
|
3619
|
+
type: "step.completed",
|
|
3620
|
+
payload: {}
|
|
3621
|
+
})
|
|
3622
|
+
);
|
|
3623
|
+
}
|
|
3624
|
+
throwIfStepTimedOut(signal, config.id, timeoutPolicy.timeoutMs);
|
|
3625
|
+
return nextNode2;
|
|
3626
|
+
},
|
|
3627
|
+
{ maxSubPrompts }
|
|
3628
|
+
)
|
|
3629
|
+
});
|
|
3630
|
+
if (!isStepNode(nextNode) && !isDoneNode(nextNode) && !isFailNode(nextNode)) {
|
|
3631
|
+
const failure = continuationFailure(`step ${config.id}`);
|
|
3632
|
+
await options.emit(
|
|
3633
|
+
createEvent({
|
|
3634
|
+
runId: options.runId,
|
|
3635
|
+
workflowId: options.workflowId,
|
|
3636
|
+
stepId: config.id,
|
|
3637
|
+
type: "step.failed",
|
|
3638
|
+
payload: { failure }
|
|
3639
|
+
})
|
|
3640
|
+
);
|
|
3641
|
+
return { status: "failure", failure };
|
|
3642
|
+
}
|
|
3643
|
+
if (isFailNode(nextNode)) {
|
|
3644
|
+
return { status: "failure", failure: nextNode.failure };
|
|
3645
|
+
}
|
|
3646
|
+
node = nextNode;
|
|
3647
|
+
source = `step ${config.id}`;
|
|
3648
|
+
} catch (error) {
|
|
3649
|
+
const failure = stepExecutionFailure(error);
|
|
3650
|
+
await options.emit(
|
|
3651
|
+
createEvent({
|
|
3652
|
+
runId: options.runId,
|
|
3653
|
+
workflowId: options.workflowId,
|
|
3654
|
+
stepId: config.id,
|
|
3655
|
+
type: "step.failed",
|
|
3656
|
+
payload: { failure }
|
|
3657
|
+
})
|
|
3658
|
+
);
|
|
3659
|
+
if (!stepNode.onError) {
|
|
3660
|
+
return { status: "failure", failure };
|
|
3661
|
+
}
|
|
3662
|
+
try {
|
|
3663
|
+
const nextNode = stepNode.onError(failure);
|
|
3664
|
+
if (!isStepNode(nextNode) && !isDoneNode(nextNode) && !isFailNode(nextNode)) {
|
|
3665
|
+
return {
|
|
3666
|
+
status: "failure",
|
|
3667
|
+
failure: continuationFailure(`error continuation for step ${config.id}`)
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
node = nextNode;
|
|
3671
|
+
source = `error continuation for step ${config.id}`;
|
|
3672
|
+
} catch (errorContinuationError) {
|
|
3673
|
+
return {
|
|
3674
|
+
status: "failure",
|
|
3675
|
+
failure: stepExecutionFailure(
|
|
3676
|
+
new Error(
|
|
3677
|
+
`error continuation for step ${config.id} failed: ${errorMessage(errorContinuationError)}`
|
|
3678
|
+
)
|
|
3679
|
+
)
|
|
3680
|
+
};
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
async function runWithStepTimeout(options) {
|
|
3686
|
+
if (options.timeoutMs === void 0) {
|
|
3687
|
+
return await options.run();
|
|
3688
|
+
}
|
|
3689
|
+
const timeoutMs = options.timeoutMs;
|
|
3690
|
+
const abortController = new AbortController();
|
|
3691
|
+
let timeout;
|
|
3692
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
3693
|
+
timeout = setTimeout(() => {
|
|
3694
|
+
abortController.abort();
|
|
3695
|
+
reject(stepTimeoutFailure(options.stepId, timeoutMs));
|
|
3696
|
+
}, timeoutMs);
|
|
3697
|
+
});
|
|
3698
|
+
try {
|
|
3699
|
+
return await Promise.race([options.run(abortController.signal), timeoutPromise]);
|
|
3700
|
+
} finally {
|
|
3701
|
+
if (timeout !== void 0) {
|
|
3702
|
+
clearTimeout(timeout);
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
function throwIfStepTimedOut(signal, stepId, timeoutMs) {
|
|
3707
|
+
if (signal?.aborted && timeoutMs !== void 0) {
|
|
3708
|
+
throw stepTimeoutFailure(stepId, timeoutMs);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
function stepTimeoutFailure(stepId, timeoutMs) {
|
|
3712
|
+
return new TrailStepFailureError({
|
|
3713
|
+
code: "step_timeout",
|
|
3714
|
+
message: `Step ${stepId} timed out after ${timeoutMs}ms.`,
|
|
3715
|
+
details: { stepId, timeoutMs }
|
|
3716
|
+
});
|
|
3717
|
+
}
|
|
3718
|
+
function errorMessage(error) {
|
|
3719
|
+
if (error instanceof TrailStepFailureError) {
|
|
3720
|
+
return error.message;
|
|
3721
|
+
}
|
|
3722
|
+
if (error instanceof Error) {
|
|
3723
|
+
return error.message;
|
|
3724
|
+
}
|
|
3725
|
+
return "Error continuation failed.";
|
|
3726
|
+
}
|
|
3727
|
+
function continuationFailure(source) {
|
|
3728
|
+
return {
|
|
3729
|
+
code: "invalid_continuation",
|
|
3730
|
+
message: `${source} returned an invalid continuation node.`
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
// src/runtime/failures/workflow-failure.ts
|
|
3735
|
+
function workflowFailure(error) {
|
|
3736
|
+
if (isFailureLikeError(error)) {
|
|
3737
|
+
return error.failure;
|
|
3738
|
+
}
|
|
3739
|
+
return {
|
|
3740
|
+
code: "workflow_failed",
|
|
3741
|
+
message: error instanceof Error ? error.message : "Unknown workflow failure.",
|
|
3742
|
+
...error === void 0 ? {} : { details: { cause: error } }
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
// src/runtime/resume/replay-completed-steps/replay-completed-steps.ts
|
|
3747
|
+
async function replayCompletedSteps(options) {
|
|
3748
|
+
let node = options.workflow.start(options.input);
|
|
3749
|
+
const completedStepEvents = options.events.filter((event) => event.type === "step.completed");
|
|
3750
|
+
for (const [completedIndex, completedEvent] of completedStepEvents.entries()) {
|
|
3751
|
+
const stepIndex = completedIndex + 1;
|
|
3752
|
+
if (!isStepNode(node)) {
|
|
3753
|
+
return {
|
|
3754
|
+
status: "failure",
|
|
3755
|
+
failure: replayFailure(
|
|
3756
|
+
"resume_step_id_drift",
|
|
3757
|
+
"Completed history continues after the current workflow reaches done."
|
|
3758
|
+
)
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
if (node.config.id !== completedEvent.stepId) {
|
|
3762
|
+
return {
|
|
3763
|
+
status: "failure",
|
|
3764
|
+
failure: replayFailure(
|
|
3765
|
+
"resume_step_id_drift",
|
|
3766
|
+
`Expected completed step ${node.config.id} but found ${completedEvent.stepId ?? "<missing>"}.`
|
|
3767
|
+
)
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
if (node.config.prompt !== void 0) {
|
|
3771
|
+
const recordedOutput = readPlainPayload2(completedEvent, "output");
|
|
3772
|
+
if (!recordedOutput) {
|
|
3773
|
+
return {
|
|
3774
|
+
status: "failure",
|
|
3775
|
+
failure: replayFailure(
|
|
3776
|
+
"resume_missing_step_output",
|
|
3777
|
+
`Completed step ${node.config.id} has no recorded output to resume from.`
|
|
3778
|
+
)
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
const outputSchema = resolveStepOutputSchema(node.config);
|
|
3782
|
+
if (!outputSchema) {
|
|
3783
|
+
return {
|
|
3784
|
+
status: "failure",
|
|
3785
|
+
failure: replayFailure(
|
|
3786
|
+
"resume_output_schema_unresolvable",
|
|
3787
|
+
`Step ${node.config.id} has no resolvable output schema to replay with.`
|
|
3788
|
+
)
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
const validatedOutput = outputSchema.assert(recordedOutput, `step ${node.config.id} output`);
|
|
3792
|
+
const stepDir = resolveStepArtifactPaths({
|
|
3793
|
+
runDir: options.runDir,
|
|
3794
|
+
stepId: node.config.id,
|
|
3795
|
+
stepIndex
|
|
3796
|
+
}).stepDir;
|
|
3797
|
+
const completedNode = node;
|
|
3798
|
+
node = await withStepContext(
|
|
3799
|
+
completedNode.config.id,
|
|
3800
|
+
stepDir,
|
|
3801
|
+
async () => completedNode.onOutput(validatedOutput, completedNode.config.input)
|
|
3802
|
+
);
|
|
3803
|
+
} else {
|
|
3804
|
+
const stepDir = resolveStepArtifactPaths({
|
|
3805
|
+
runDir: options.runDir,
|
|
3806
|
+
stepId: node.config.id,
|
|
3807
|
+
stepIndex
|
|
3808
|
+
}).stepDir;
|
|
3809
|
+
const completedNode = node;
|
|
3810
|
+
node = await withStepContext(
|
|
3811
|
+
completedNode.config.id,
|
|
3812
|
+
stepDir,
|
|
3813
|
+
async () => completedNode.onOutput(completedNode.config.input, completedNode.config.input)
|
|
3814
|
+
);
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
if (!isStepNode(node) || node.config.id !== options.targetStepId) {
|
|
3818
|
+
return {
|
|
3819
|
+
status: "failure",
|
|
3820
|
+
failure: replayFailure(
|
|
3821
|
+
"resume_step_id_drift",
|
|
3822
|
+
`Target step ${options.targetStepId} is not the next live step.`
|
|
3823
|
+
)
|
|
3824
|
+
};
|
|
3825
|
+
}
|
|
3826
|
+
return { status: "success", node };
|
|
3827
|
+
}
|
|
3828
|
+
function readPlainPayload2(event, key) {
|
|
3829
|
+
const value = event.payload[key];
|
|
3830
|
+
return isPlainObject7(value) ? value : void 0;
|
|
3831
|
+
}
|
|
3832
|
+
function isPlainObject7(value) {
|
|
3833
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
3834
|
+
}
|
|
3835
|
+
function replayFailure(code, message) {
|
|
3836
|
+
return { code, message };
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
// src/runtime/resume/reattach-in-progress-step/reattach-in-progress-step.ts
|
|
3840
|
+
function findDanglingInteractiveSessionStart(events) {
|
|
3841
|
+
const anchor = events.at(-1);
|
|
3842
|
+
if (anchor?.type !== "interactive.sessionStarted" || !anchor.stepId) {
|
|
3843
|
+
return void 0;
|
|
3844
|
+
}
|
|
3845
|
+
const hasTerminalWorkflowEvent = events.some(
|
|
3846
|
+
(event) => event.type === "workflow.failed" || event.type === "workflow.completed"
|
|
3847
|
+
);
|
|
3848
|
+
return hasTerminalWorkflowEvent ? void 0 : anchor;
|
|
3849
|
+
}
|
|
3850
|
+
async function reattachInProgressStep(options) {
|
|
3851
|
+
const anchor = findDanglingInteractiveSessionStart(options.events);
|
|
3852
|
+
if (!anchor?.stepId) {
|
|
3853
|
+
return {
|
|
3854
|
+
status: "failure",
|
|
3855
|
+
failure: resumeFailure(
|
|
3856
|
+
"resume_target_not_dangling",
|
|
3857
|
+
"Resume target has no dangling interactive.sessionStarted event to reattach."
|
|
3858
|
+
)
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3861
|
+
const stepIndex = anchor.payload.stepIndex;
|
|
3862
|
+
if (typeof stepIndex !== "number") {
|
|
3863
|
+
return {
|
|
3864
|
+
status: "failure",
|
|
3865
|
+
failure: resumeFailure(
|
|
3866
|
+
"resume_missing_step_index",
|
|
3867
|
+
`Dangling interactive.sessionStarted event for step ${anchor.stepId} has no recorded stepIndex.`
|
|
3868
|
+
)
|
|
3869
|
+
};
|
|
3870
|
+
}
|
|
3871
|
+
const startedEvent = options.events.find((event) => event.type === "workflow.started");
|
|
3872
|
+
if (!startedEvent) {
|
|
3873
|
+
return {
|
|
3874
|
+
status: "failure",
|
|
3875
|
+
failure: resumeFailure(
|
|
3876
|
+
"resume_target_not_found",
|
|
3877
|
+
"Resume target has no workflow.started event."
|
|
3878
|
+
)
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
if (startedEvent.workflowId !== options.workflow.id) {
|
|
3882
|
+
return {
|
|
3883
|
+
status: "failure",
|
|
3884
|
+
failure: resumeFailure(
|
|
3885
|
+
"resume_workflow_mismatch",
|
|
3886
|
+
`Resume target workflow ${startedEvent.workflowId} does not match ${options.workflow.id}.`
|
|
3887
|
+
)
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
const input = readPlainPayload3(startedEvent, "input");
|
|
3891
|
+
if (!input) {
|
|
3892
|
+
return {
|
|
3893
|
+
status: "failure",
|
|
3894
|
+
failure: resumeFailure(
|
|
3895
|
+
"resume_target_not_found",
|
|
3896
|
+
"workflow.started payload is missing input."
|
|
3897
|
+
)
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
const replay = await replayCompletedSteps({
|
|
3901
|
+
workflow: options.workflow,
|
|
3902
|
+
events: options.events,
|
|
3903
|
+
input,
|
|
3904
|
+
targetStepId: anchor.stepId,
|
|
3905
|
+
runDir: options.runDir
|
|
3906
|
+
});
|
|
3907
|
+
if (replay.status === "failure") {
|
|
3908
|
+
return replay;
|
|
3909
|
+
}
|
|
3910
|
+
const { node } = replay;
|
|
3911
|
+
const artifactPaths = resolveStepArtifactPaths({
|
|
3912
|
+
runDir: options.runDir,
|
|
3913
|
+
stepId: anchor.stepId,
|
|
3914
|
+
stepIndex
|
|
3915
|
+
});
|
|
3916
|
+
const outputSchema = resolveStepOutputSchema(node.config);
|
|
3917
|
+
if (!outputSchema) {
|
|
3918
|
+
return {
|
|
3919
|
+
status: "failure",
|
|
3920
|
+
failure: resumeFailure(
|
|
3921
|
+
"resume_output_schema_unresolvable",
|
|
3922
|
+
`Step ${node.config.id} has no resolvable output schema to reattach with.`
|
|
3923
|
+
)
|
|
3924
|
+
};
|
|
3925
|
+
}
|
|
3926
|
+
const reattached = await readReattachedInteractiveOutput({
|
|
3927
|
+
stepId: anchor.stepId,
|
|
3928
|
+
interactiveFile: artifactPaths.interactiveFile,
|
|
3929
|
+
outputSchema
|
|
3930
|
+
});
|
|
3931
|
+
if (reattached.status === "failure") {
|
|
3932
|
+
return reattached;
|
|
3933
|
+
}
|
|
3934
|
+
const nextNode = await withStepContext(
|
|
3935
|
+
anchor.stepId,
|
|
3936
|
+
artifactPaths.stepDir,
|
|
3937
|
+
async () => node.onOutput(
|
|
3938
|
+
outputSchema.assert(reattached.output, `step ${node.config.id} output`),
|
|
3939
|
+
node.config.input
|
|
3940
|
+
)
|
|
3941
|
+
);
|
|
3942
|
+
return {
|
|
3943
|
+
status: "success",
|
|
3944
|
+
input,
|
|
3945
|
+
node: nextNode,
|
|
3946
|
+
resumedStepId: anchor.stepId,
|
|
3947
|
+
sourceFailureEventId: anchor.id
|
|
3948
|
+
};
|
|
3949
|
+
}
|
|
3950
|
+
async function readReattachedInteractiveOutput(options) {
|
|
3951
|
+
const first = await tryReadCompletedInteractiveOutput(options);
|
|
3952
|
+
if (first.status !== "still-active") {
|
|
3953
|
+
return first;
|
|
3954
|
+
}
|
|
3955
|
+
const abortController = new AbortController();
|
|
3956
|
+
await waitForInteractiveCompletion(options.interactiveFile, abortController.signal);
|
|
3957
|
+
const second = await tryReadCompletedInteractiveOutput(options);
|
|
3958
|
+
if (second.status !== "still-active") {
|
|
3959
|
+
return second;
|
|
3960
|
+
}
|
|
3961
|
+
throw new TrailStepFailureError({
|
|
3962
|
+
code: "interactive_session_incomplete",
|
|
3963
|
+
message: `Interactive agent step ${options.stepId} did not complete after reattaching.`
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
async function tryReadCompletedInteractiveOutput(options) {
|
|
3967
|
+
try {
|
|
3968
|
+
return { status: "success", output: await readCompletedInteractiveOutput(options) };
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
if (error instanceof TrailStepFailureError) {
|
|
3971
|
+
if (error.failure.code === "interactive_session_incomplete") {
|
|
3972
|
+
return { status: "still-active" };
|
|
3973
|
+
}
|
|
3974
|
+
if (error.failure.code === "interactive_session_cancelled") {
|
|
3975
|
+
return {
|
|
3976
|
+
status: "failure",
|
|
3977
|
+
failure: resumeFailure(
|
|
3978
|
+
"resume_interactive_session_cancelled",
|
|
3979
|
+
`Interactive agent step ${options.stepId} was cancelled and cannot be resumed.`
|
|
3980
|
+
)
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
if (error.failure.code === "interactive_session_invalid") {
|
|
3984
|
+
return {
|
|
3985
|
+
status: "failure",
|
|
3986
|
+
failure: resumeFailure(
|
|
3987
|
+
"resume_interactive_protocol_missing",
|
|
3988
|
+
`Interactive agent step ${options.stepId} has no readable interactive.json to reattach to.`
|
|
3989
|
+
)
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
throw error;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
function readPlainPayload3(event, key) {
|
|
3997
|
+
const value = event.payload[key];
|
|
3998
|
+
return isPlainObject8(value) ? value : void 0;
|
|
3999
|
+
}
|
|
4000
|
+
function isPlainObject8(value) {
|
|
4001
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
4002
|
+
}
|
|
4003
|
+
function resumeFailure(code, message) {
|
|
4004
|
+
return { code, message };
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
// src/runtime/resume/replay-to-failed-step/replay-to-failed-step.ts
|
|
4008
|
+
async function replayToFailedStep(options) {
|
|
4009
|
+
const startedEvent = options.events.find((event) => event.type === "workflow.started");
|
|
4010
|
+
if (!startedEvent) {
|
|
4011
|
+
return {
|
|
4012
|
+
status: "failure",
|
|
4013
|
+
failure: resumeFailure2(
|
|
4014
|
+
"resume_target_not_found",
|
|
4015
|
+
"Resume target has no workflow.started event."
|
|
4016
|
+
)
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
if (startedEvent.workflowId !== options.workflow.id) {
|
|
4020
|
+
return {
|
|
4021
|
+
status: "failure",
|
|
4022
|
+
failure: resumeFailure2(
|
|
4023
|
+
"resume_workflow_mismatch",
|
|
4024
|
+
`Resume target workflow ${startedEvent.workflowId} does not match ${options.workflow.id}.`
|
|
4025
|
+
)
|
|
4026
|
+
};
|
|
4027
|
+
}
|
|
4028
|
+
const terminalEvent = options.events.at(-1);
|
|
4029
|
+
if (terminalEvent?.type !== "workflow.failed") {
|
|
4030
|
+
return {
|
|
4031
|
+
status: "failure",
|
|
4032
|
+
failure: resumeFailure2(
|
|
4033
|
+
"resume_target_not_failed",
|
|
4034
|
+
"Resume target must end in workflow.failed."
|
|
4035
|
+
)
|
|
4036
|
+
};
|
|
4037
|
+
}
|
|
4038
|
+
const recoveredFailedStep = options.events.find(
|
|
4039
|
+
(event, index) => event.type === "step.failed" && options.events.slice(index + 1).some(
|
|
4040
|
+
(laterEvent) => laterEvent.type === "step.completed" && laterEvent.stepId === event.stepId
|
|
4041
|
+
)
|
|
4042
|
+
);
|
|
4043
|
+
if (recoveredFailedStep) {
|
|
4044
|
+
return {
|
|
4045
|
+
status: "failure",
|
|
4046
|
+
failure: resumeFailure2(
|
|
4047
|
+
"resume_unsupported_history",
|
|
4048
|
+
`Resume does not support recovered onError history for step ${recoveredFailedStep.stepId ?? "<missing>"}.`
|
|
4049
|
+
)
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
const failedStepEvents = options.events.filter((event) => event.type === "step.failed");
|
|
4053
|
+
if (failedStepEvents.length !== 1) {
|
|
4054
|
+
return {
|
|
4055
|
+
status: "failure",
|
|
4056
|
+
failure: resumeFailure2(
|
|
4057
|
+
"resume_multiple_failed_steps",
|
|
4058
|
+
"Resume target must contain exactly one failed step."
|
|
4059
|
+
)
|
|
4060
|
+
};
|
|
4061
|
+
}
|
|
4062
|
+
const failedStepEvent = failedStepEvents[0];
|
|
4063
|
+
if (!failedStepEvent) {
|
|
4064
|
+
return {
|
|
4065
|
+
status: "failure",
|
|
4066
|
+
failure: resumeFailure2("resume_target_not_failed", "Resume target has no failed step.")
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
if (!failedStepEvent.stepId) {
|
|
4070
|
+
return {
|
|
4071
|
+
status: "failure",
|
|
4072
|
+
failure: resumeFailure2("resume_target_not_failed", "Failed step event has no step id.")
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
const input = readPlainPayload4(startedEvent, "input");
|
|
4076
|
+
if (!input) {
|
|
4077
|
+
return {
|
|
4078
|
+
status: "failure",
|
|
4079
|
+
failure: resumeFailure2(
|
|
4080
|
+
"resume_target_not_found",
|
|
4081
|
+
"workflow.started payload is missing input."
|
|
4082
|
+
)
|
|
4083
|
+
};
|
|
4084
|
+
}
|
|
4085
|
+
const replay = await replayCompletedSteps({
|
|
4086
|
+
workflow: options.workflow,
|
|
4087
|
+
events: options.events,
|
|
4088
|
+
input,
|
|
4089
|
+
targetStepId: failedStepEvent.stepId,
|
|
4090
|
+
runDir: options.runDir
|
|
4091
|
+
});
|
|
4092
|
+
if (replay.status === "failure") {
|
|
4093
|
+
return replay;
|
|
4094
|
+
}
|
|
4095
|
+
const { node } = replay;
|
|
4096
|
+
if (node.onError) {
|
|
4097
|
+
return {
|
|
4098
|
+
status: "failure",
|
|
4099
|
+
failure: resumeFailure2(
|
|
4100
|
+
"resume_unsupported_history",
|
|
4101
|
+
`Resume does not support onError history for step ${node.config.id}.`
|
|
4102
|
+
)
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
return {
|
|
4106
|
+
status: "success",
|
|
4107
|
+
input,
|
|
4108
|
+
node,
|
|
4109
|
+
resumedStepId: failedStepEvent.stepId,
|
|
4110
|
+
sourceFailureEventId: failedStepEvent.id
|
|
4111
|
+
};
|
|
4112
|
+
}
|
|
4113
|
+
function readPlainPayload4(event, key) {
|
|
4114
|
+
const value = event.payload[key];
|
|
4115
|
+
return isPlainObject9(value) ? value : void 0;
|
|
4116
|
+
}
|
|
4117
|
+
function isPlainObject9(value) {
|
|
4118
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
4119
|
+
}
|
|
4120
|
+
function resumeFailure2(code, message) {
|
|
4121
|
+
return { code, message };
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
// src/runtime/retry/replay-to-retry-failure.ts
|
|
4125
|
+
async function replayToRetryFailure(options) {
|
|
4126
|
+
const failure = selectLatestUnresolvedFailure(options.events);
|
|
4127
|
+
if (!failure) {
|
|
4128
|
+
return {
|
|
4129
|
+
status: "failure",
|
|
4130
|
+
failure: retryFailure(
|
|
4131
|
+
"retry_target_not_failed",
|
|
4132
|
+
"Retry target has no latest unresolved failure."
|
|
4133
|
+
)
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
if (failure.workflowId !== options.workflow.id) {
|
|
4137
|
+
return {
|
|
4138
|
+
status: "failure",
|
|
4139
|
+
failure: retryFailure(
|
|
4140
|
+
"retry_workflow_mismatch",
|
|
4141
|
+
`Retry target workflow ${failure.workflowId} does not match ${options.workflow.id}.`
|
|
4142
|
+
)
|
|
4143
|
+
};
|
|
4144
|
+
}
|
|
4145
|
+
if (!failure.workflowInput) {
|
|
4146
|
+
return {
|
|
4147
|
+
status: "failure",
|
|
4148
|
+
failure: retryFailure("retry_target_not_found", "workflow.started payload is missing input.")
|
|
4149
|
+
};
|
|
4150
|
+
}
|
|
4151
|
+
if (!failure.stepId) {
|
|
4152
|
+
return {
|
|
4153
|
+
status: "failure",
|
|
4154
|
+
failure: retryFailure(
|
|
4155
|
+
"retry_target_not_failed",
|
|
4156
|
+
failure.event.type === "workflow.failed" ? "Workflow failure has no associated step ID. This run may use unsupported historical retry metadata." : "Failed retry target event has no step ID."
|
|
4157
|
+
)
|
|
4158
|
+
};
|
|
4159
|
+
}
|
|
4160
|
+
const replay = await replayCompletedSteps({
|
|
4161
|
+
workflow: options.workflow,
|
|
4162
|
+
events: eventsBeforeRetryTarget(options.events, failure.replayPosition, failure.stepId),
|
|
4163
|
+
input: failure.workflowInput,
|
|
4164
|
+
targetStepId: failure.stepId,
|
|
4165
|
+
runDir: options.runDir
|
|
4166
|
+
});
|
|
4167
|
+
if (replay.status === "failure") {
|
|
4168
|
+
return replay;
|
|
4169
|
+
}
|
|
4170
|
+
const { node } = replay;
|
|
4171
|
+
if (node.onError) {
|
|
4172
|
+
return {
|
|
4173
|
+
status: "failure",
|
|
4174
|
+
failure: retryFailure(
|
|
4175
|
+
"retry_unsupported_history",
|
|
4176
|
+
`Retry does not support onError history for step ${node.config.id}.`
|
|
4177
|
+
)
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
return {
|
|
4181
|
+
status: "success",
|
|
4182
|
+
input: failure.workflowInput,
|
|
4183
|
+
node,
|
|
4184
|
+
retriedStepId: failure.stepId,
|
|
4185
|
+
sourceFailureEventId: failure.sourceFailureEventId ?? failure.event.id,
|
|
4186
|
+
sourceFailureReplayPosition: failure.replayPosition
|
|
4187
|
+
};
|
|
4188
|
+
}
|
|
4189
|
+
function eventsBeforeRetryTarget(events, replayPosition, targetStepId) {
|
|
4190
|
+
const eventsBeforeFailure = events.slice(0, replayPosition);
|
|
4191
|
+
let targetAttemptStartPosition = -1;
|
|
4192
|
+
for (let index = eventsBeforeFailure.length - 1; index >= 0; index -= 1) {
|
|
4193
|
+
const event = eventsBeforeFailure[index];
|
|
4194
|
+
if (event?.type === "step.started" && event.stepId === targetStepId) {
|
|
4195
|
+
targetAttemptStartPosition = index;
|
|
4196
|
+
break;
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
if (targetAttemptStartPosition === -1) {
|
|
4200
|
+
return eventsBeforeFailure;
|
|
4201
|
+
}
|
|
4202
|
+
return eventsBeforeFailure.filter(
|
|
4203
|
+
(event, index) => index <= targetAttemptStartPosition || event.type !== "step.completed" || event.stepId !== targetStepId
|
|
4204
|
+
);
|
|
4205
|
+
}
|
|
4206
|
+
function retryFailure(code, message) {
|
|
4207
|
+
return { code, message };
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
// src/runtime/run-context/create-run-context.ts
|
|
4211
|
+
function createRunContext(options) {
|
|
4212
|
+
let cache;
|
|
4213
|
+
let loadPromise;
|
|
4214
|
+
let writeQueue = Promise.resolve();
|
|
4215
|
+
function ensureLoaded() {
|
|
4216
|
+
if (cache) return Promise.resolve(cache);
|
|
4217
|
+
loadPromise ??= readRunState(options.runDir).then((state2) => cache = state2);
|
|
4218
|
+
return loadPromise;
|
|
4219
|
+
}
|
|
4220
|
+
function enqueueWrite() {
|
|
4221
|
+
const next = writeQueue.then(async () => {
|
|
4222
|
+
const state2 = await ensureLoaded();
|
|
4223
|
+
await writeRunState(options.runDir, state2);
|
|
4224
|
+
});
|
|
4225
|
+
writeQueue = next.catch(() => {
|
|
4226
|
+
});
|
|
4227
|
+
return next;
|
|
4228
|
+
}
|
|
4229
|
+
return {
|
|
4230
|
+
id: options.runId,
|
|
4231
|
+
name: options.runName,
|
|
4232
|
+
path: options.runDir,
|
|
4233
|
+
workflowId: options.workflowId,
|
|
4234
|
+
workflowAgents: options.workflowAgents,
|
|
4235
|
+
cwd: options.cwd,
|
|
4236
|
+
trailstepConfig: options.trailstepConfig,
|
|
4237
|
+
workingAgentProcessRunner: options.workingAgentProcessRunner,
|
|
4238
|
+
providerWorkingRunner: options.providerWorkingRunner,
|
|
4239
|
+
emit: options.emit,
|
|
4240
|
+
events: options.events,
|
|
4241
|
+
state: {
|
|
4242
|
+
async get(key) {
|
|
4243
|
+
const state2 = await ensureLoaded();
|
|
4244
|
+
return state2[key];
|
|
4245
|
+
},
|
|
4246
|
+
async set(key, value) {
|
|
4247
|
+
const state2 = await ensureLoaded();
|
|
4248
|
+
state2[key] = value;
|
|
4249
|
+
await enqueueWrite();
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
};
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
// src/runtime/run-workflow/initialize-run.ts
|
|
4256
|
+
import { basename as basename2 } from "path";
|
|
4257
|
+
async function initializeRun(options) {
|
|
4258
|
+
const existingRunDir = options.resume?.runDir ?? options.retry?.runDir;
|
|
4259
|
+
if (existingRunDir) {
|
|
4260
|
+
const previousEvents = await readRunEvents(existingRunDir);
|
|
4261
|
+
const startedEvent = previousEvents.find((event) => event.type === "workflow.started");
|
|
4262
|
+
return {
|
|
4263
|
+
runId: startedEvent?.runId ?? basename2(existingRunDir),
|
|
4264
|
+
runName: startedEvent?.runId ?? basename2(existingRunDir),
|
|
4265
|
+
runDir: existingRunDir,
|
|
4266
|
+
previousEvents
|
|
4267
|
+
};
|
|
4268
|
+
}
|
|
4269
|
+
const cwd = options.cwd ?? process.cwd();
|
|
4270
|
+
const runName = options.runName;
|
|
4271
|
+
if (runName === void 0) {
|
|
4272
|
+
throw new Error("Expected runName for a new workflow run.");
|
|
4273
|
+
}
|
|
4274
|
+
const { runId, runDir } = await createRunDirectory({
|
|
4275
|
+
cwd,
|
|
4276
|
+
runName,
|
|
4277
|
+
runsRoot: options.runsRoot
|
|
4278
|
+
});
|
|
4279
|
+
return { runId, runName, runDir, previousEvents: [] };
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
// src/runtime/run-workflow/trailstep-config-input.ts
|
|
4283
|
+
function parseTrailStepConfigInput(value) {
|
|
4284
|
+
if (isParsedTrailStepConfig(value) || isFlattenedTrailStepConfig(value)) {
|
|
4285
|
+
return value;
|
|
4286
|
+
}
|
|
4287
|
+
return parseTrailStepConfig(value);
|
|
4288
|
+
}
|
|
4289
|
+
function isFlattenedTrailStepConfig(value) {
|
|
4290
|
+
if (!isPlainRecord(value) || value.version !== 1) {
|
|
4291
|
+
return false;
|
|
4292
|
+
}
|
|
4293
|
+
if (!isPlainRecord(value.customProviders) || !isFlattenedAgentMappings(value.agents)) {
|
|
4294
|
+
return false;
|
|
4295
|
+
}
|
|
4296
|
+
if (value.settings !== void 0 && !isFlattenedSettings(value.settings)) {
|
|
4297
|
+
return false;
|
|
4298
|
+
}
|
|
4299
|
+
if (value.workflows === void 0) {
|
|
4300
|
+
return true;
|
|
4301
|
+
}
|
|
4302
|
+
if (!isPlainRecord(value.workflows)) {
|
|
4303
|
+
return false;
|
|
4304
|
+
}
|
|
4305
|
+
return Object.values(value.workflows).every(
|
|
4306
|
+
(workflow) => isPlainRecord(workflow) && (workflow.agents === void 0 || isFlattenedAgentMappings(workflow.agents)) && (workflow.settings === void 0 || isFlattenedSettings(workflow.settings))
|
|
4307
|
+
);
|
|
4308
|
+
}
|
|
4309
|
+
function isFlattenedSettings(value) {
|
|
4310
|
+
return isPlainRecord(value) && (value.retry === void 0 || isPlainRecord(value.retry)) && (value.timeout === void 0 || typeof value.timeout === "number");
|
|
4311
|
+
}
|
|
4312
|
+
function isFlattenedAgentMappings(value) {
|
|
4313
|
+
return isPlainRecord(value) && Object.values(value).every(
|
|
4314
|
+
(targets) => Array.isArray(targets) && targets.every(isFlattenedAgentTarget)
|
|
4315
|
+
);
|
|
4316
|
+
}
|
|
4317
|
+
function isFlattenedAgentTarget(value) {
|
|
4318
|
+
return isPlainRecord(value) && typeof value.provider === "string";
|
|
4319
|
+
}
|
|
4320
|
+
function isPlainRecord(value) {
|
|
4321
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
// src/runtime/run-workflow/run-workflow.ts
|
|
4325
|
+
async function runWorkflow(options) {
|
|
4326
|
+
const maxSteps = options.maxSteps ?? 1e3;
|
|
4327
|
+
const isResume = options.resume !== void 0;
|
|
4328
|
+
const isRetry = options.retry !== void 0;
|
|
4329
|
+
const initialized = await initializeRun(options).catch((error) => {
|
|
4330
|
+
const existingRunDir = options.resume?.runDir ?? options.retry?.runDir;
|
|
4331
|
+
if (existingRunDir && isFailureLikeError(error)) {
|
|
4332
|
+
return {
|
|
4333
|
+
status: "failure",
|
|
4334
|
+
runId: basename3(existingRunDir),
|
|
4335
|
+
runName: basename3(existingRunDir),
|
|
4336
|
+
runDir: existingRunDir,
|
|
4337
|
+
previousEvents: [],
|
|
4338
|
+
failure: workflowFailure(error)
|
|
4339
|
+
};
|
|
4340
|
+
}
|
|
4341
|
+
throw error;
|
|
4342
|
+
});
|
|
4343
|
+
if ("status" in initialized && initialized.status === "failure") {
|
|
4344
|
+
return {
|
|
4345
|
+
status: "failure",
|
|
4346
|
+
runId: initialized.runId,
|
|
4347
|
+
runDir: initialized.runDir,
|
|
4348
|
+
failure: initialized.failure,
|
|
4349
|
+
events: initialized.previousEvents
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
const { runId, runName, runDir, previousEvents } = initialized;
|
|
4353
|
+
const trailstepConfig = options.trailstepConfig === void 0 ? void 0 : parseTrailStepConfigInput(options.trailstepConfig);
|
|
4354
|
+
const cwd = options.cwd ?? process.cwd();
|
|
4355
|
+
const events = [...previousEvents];
|
|
4356
|
+
const emit = async (event) => {
|
|
4357
|
+
events.push(event);
|
|
4358
|
+
await appendEvent(runDir, event);
|
|
4359
|
+
await options.eventSink?.(event);
|
|
4360
|
+
};
|
|
4361
|
+
const runContext = createRunContext({
|
|
4362
|
+
runId,
|
|
4363
|
+
runName,
|
|
4364
|
+
runDir,
|
|
4365
|
+
workflowId: options.workflow.id,
|
|
4366
|
+
workflowAgents: options.workflow.agents ?? {},
|
|
4367
|
+
cwd,
|
|
4368
|
+
trailstepConfig,
|
|
4369
|
+
workingAgentProcessRunner: options.workingAgentProcessRunner,
|
|
4370
|
+
providerWorkingRunner: options.providerWorkingRunner,
|
|
4371
|
+
emit,
|
|
4372
|
+
events: () => events
|
|
4373
|
+
});
|
|
4374
|
+
const failWorkflow = async (failure) => {
|
|
4375
|
+
await emit(
|
|
4376
|
+
createEvent({
|
|
4377
|
+
runId,
|
|
4378
|
+
workflowId: options.workflow.id,
|
|
4379
|
+
type: "workflow.failed",
|
|
4380
|
+
payload: { failure }
|
|
4381
|
+
})
|
|
4382
|
+
);
|
|
4383
|
+
return {
|
|
4384
|
+
status: "failure",
|
|
4385
|
+
runId,
|
|
4386
|
+
runDir,
|
|
4387
|
+
failure,
|
|
4388
|
+
events
|
|
4389
|
+
};
|
|
4390
|
+
};
|
|
4391
|
+
const failResumeValidation = (failure) => ({
|
|
4392
|
+
status: "failure",
|
|
4393
|
+
runId,
|
|
4394
|
+
runDir,
|
|
4395
|
+
failure,
|
|
4396
|
+
events
|
|
4397
|
+
});
|
|
4398
|
+
try {
|
|
4399
|
+
return await runContextStorage.run(runContext, () => runWorkflowBody());
|
|
4400
|
+
} catch (error) {
|
|
4401
|
+
return await failWorkflow(workflowFailure(error));
|
|
4402
|
+
}
|
|
4403
|
+
async function runWorkflowBody() {
|
|
4404
|
+
const inputSchema = options.workflow.inputShape ? normalizeShape(options.workflow.inputShape) : options.workflow.input;
|
|
4405
|
+
let workflowInput;
|
|
4406
|
+
let startNode;
|
|
4407
|
+
if (isResume) {
|
|
4408
|
+
const danglingAnchor = findDanglingInteractiveSessionStart(previousEvents);
|
|
4409
|
+
const replay = danglingAnchor ? await reattachInProgressStep({
|
|
4410
|
+
workflow: options.workflow,
|
|
4411
|
+
events: previousEvents,
|
|
4412
|
+
runDir
|
|
4413
|
+
}) : await replayToFailedStep({
|
|
4414
|
+
workflow: options.workflow,
|
|
4415
|
+
events: previousEvents,
|
|
4416
|
+
runDir
|
|
4417
|
+
});
|
|
4418
|
+
if (replay.status === "failure") {
|
|
4419
|
+
return failResumeValidation(replay.failure);
|
|
4420
|
+
}
|
|
4421
|
+
workflowInput = inputSchema ? inputSchema.assert(replay.input, "workflow input") : replay.input;
|
|
4422
|
+
startNode = replay.node;
|
|
4423
|
+
await emit(
|
|
4424
|
+
createEvent({
|
|
4425
|
+
runId,
|
|
4426
|
+
workflowId: options.workflow.id,
|
|
4427
|
+
type: "workflow.resumed",
|
|
4428
|
+
payload: {
|
|
4429
|
+
resumedFromRunDir: runDir,
|
|
4430
|
+
resumedStepId: replay.resumedStepId,
|
|
4431
|
+
sourceFailureEventId: replay.sourceFailureEventId
|
|
4432
|
+
}
|
|
4433
|
+
})
|
|
4434
|
+
);
|
|
4435
|
+
} else if (isRetry) {
|
|
4436
|
+
const replay = await replayToRetryFailure({
|
|
4437
|
+
workflow: options.workflow,
|
|
4438
|
+
events: previousEvents,
|
|
4439
|
+
runDir
|
|
4440
|
+
});
|
|
4441
|
+
if (replay.status === "failure") {
|
|
4442
|
+
return failResumeValidation(replay.failure);
|
|
4443
|
+
}
|
|
4444
|
+
workflowInput = inputSchema ? inputSchema.assert(replay.input, "workflow input") : replay.input;
|
|
4445
|
+
startNode = replay.node;
|
|
4446
|
+
await emit(
|
|
4447
|
+
createEvent({
|
|
4448
|
+
runId,
|
|
4449
|
+
workflowId: options.workflow.id,
|
|
4450
|
+
type: "workflow.retryStarted",
|
|
4451
|
+
payload: {
|
|
4452
|
+
retryKind: options.retry.kind,
|
|
4453
|
+
retriedFromRunDir: runDir,
|
|
4454
|
+
retriedStepId: replay.retriedStepId,
|
|
4455
|
+
sourceFailureEventId: replay.sourceFailureEventId,
|
|
4456
|
+
sourceFailureReplayPosition: replay.sourceFailureReplayPosition
|
|
4457
|
+
}
|
|
4458
|
+
})
|
|
4459
|
+
);
|
|
4460
|
+
} else {
|
|
4461
|
+
workflowInput = inputSchema ? inputSchema.assert(options.input, "workflow input") : options.input;
|
|
4462
|
+
await emit(
|
|
4463
|
+
createEvent({
|
|
4464
|
+
runId,
|
|
4465
|
+
workflowId: options.workflow.id,
|
|
4466
|
+
type: "workflow.started",
|
|
4467
|
+
payload: { input: workflowInput }
|
|
4468
|
+
})
|
|
4469
|
+
);
|
|
4470
|
+
}
|
|
4471
|
+
const continuationResult = await runContinuation({
|
|
4472
|
+
node: startNode ?? options.workflow.start(workflowInput),
|
|
4473
|
+
runId,
|
|
4474
|
+
workflowId: options.workflow.id,
|
|
4475
|
+
emit,
|
|
4476
|
+
maxSteps,
|
|
4477
|
+
initialSource: isRetry ? `retry for workflow ${options.workflow.id}` : isResume ? `resume for workflow ${options.workflow.id}` : `workflow.start for workflow ${options.workflow.id}`,
|
|
4478
|
+
// The original run already used one step-index slot per step.started
|
|
4479
|
+
// event ever recorded (successful or failed) -- newly-dispatched steps
|
|
4480
|
+
// after resume must continue that sequence, not restart at 1, or their
|
|
4481
|
+
// artifact directories collide with the pre-resume steps' directories.
|
|
4482
|
+
initialExecutedSteps: isResume || isRetry ? previousEvents.filter((event) => event.type === "step.started").length : void 0,
|
|
4483
|
+
workflowAgents: options.workflow.agents ?? {},
|
|
4484
|
+
workflowTimeout: options.workflow.timeout,
|
|
4485
|
+
runDir,
|
|
4486
|
+
cwd,
|
|
4487
|
+
trailstepConfig,
|
|
4488
|
+
workingAgentProcessRunner: options.workingAgentProcessRunner,
|
|
4489
|
+
providerWorkingRunner: options.providerWorkingRunner,
|
|
4490
|
+
processRunner: options.processRunner
|
|
4491
|
+
});
|
|
4492
|
+
if (continuationResult.status === "failure") {
|
|
4493
|
+
return await failWorkflow(continuationResult.failure);
|
|
4494
|
+
}
|
|
4495
|
+
const current = continuationResult.output;
|
|
4496
|
+
const outputSchema = options.workflow.outputShape ? normalizeShape(options.workflow.outputShape) : options.workflow.output;
|
|
4497
|
+
const output = outputSchema ? outputSchema.assert(current, "workflow output") : current;
|
|
4498
|
+
await emit(
|
|
4499
|
+
createEvent({
|
|
4500
|
+
runId,
|
|
4501
|
+
workflowId: options.workflow.id,
|
|
4502
|
+
type: "workflow.completed",
|
|
4503
|
+
payload: { output }
|
|
4504
|
+
})
|
|
4505
|
+
);
|
|
4506
|
+
return {
|
|
4507
|
+
status: "success",
|
|
4508
|
+
runId,
|
|
4509
|
+
runDir,
|
|
4510
|
+
output,
|
|
4511
|
+
events
|
|
4512
|
+
};
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
// src/runtime/runs/run-summaries.ts
|
|
4517
|
+
import { readdir } from "fs/promises";
|
|
4518
|
+
import { join as join7 } from "path";
|
|
4519
|
+
async function listRunSummaries(options) {
|
|
4520
|
+
const runsRoot = options.runsRoot ?? defaultRunsRoot(options.cwd);
|
|
4521
|
+
let entries;
|
|
4522
|
+
try {
|
|
4523
|
+
entries = await readdir(runsRoot, { withFileTypes: true });
|
|
4524
|
+
} catch (error) {
|
|
4525
|
+
if (isNodeError2(error) && error.code === "ENOENT") {
|
|
4526
|
+
return [];
|
|
4527
|
+
}
|
|
4528
|
+
throw error;
|
|
4529
|
+
}
|
|
4530
|
+
const summaries = [];
|
|
4531
|
+
for (const entry of entries) {
|
|
4532
|
+
if (!entry.isDirectory()) {
|
|
4533
|
+
continue;
|
|
4534
|
+
}
|
|
4535
|
+
const runId = entry.name;
|
|
4536
|
+
const runDir = join7(runsRoot, runId);
|
|
4537
|
+
try {
|
|
4538
|
+
summaries.push(summarizeReadableRun({ runId, runDir, events: await readRunEvents(runDir) }));
|
|
4539
|
+
} catch (error) {
|
|
4540
|
+
summaries.push({
|
|
4541
|
+
runId,
|
|
4542
|
+
runDir,
|
|
4543
|
+
status: "unknown",
|
|
4544
|
+
warning: `Warning: Could not read run ${runId}: ${readErrorMessage(error)}`
|
|
4545
|
+
});
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
return summaries.sort(newestFirst);
|
|
4549
|
+
}
|
|
4550
|
+
function selectRecentFailedRunSummaries(summaries, options = {}) {
|
|
4551
|
+
const cutoffMs = (options.now ?? /* @__PURE__ */ new Date()).getTime() - 7 * 24 * 60 * 60 * 1e3;
|
|
4552
|
+
return summaries.filter((summary) => {
|
|
4553
|
+
if (summary.status !== "failed" || !summary.latestFailure) {
|
|
4554
|
+
return false;
|
|
4555
|
+
}
|
|
4556
|
+
const failureTime = Date.parse(summary.latestFailure.event.timestamp);
|
|
4557
|
+
return Number.isFinite(failureTime) && failureTime >= cutoffMs;
|
|
4558
|
+
}).sort(newestFirst).slice(0, 10);
|
|
4559
|
+
}
|
|
4560
|
+
function newestFirst(left, right) {
|
|
4561
|
+
return compareTimestampDescending(left.lastTimestamp, right.lastTimestamp) || left.runId.localeCompare(right.runId);
|
|
4562
|
+
}
|
|
4563
|
+
function summarizeReadableRun(options) {
|
|
4564
|
+
const latestFailure = selectLatestUnresolvedFailure(options.events);
|
|
4565
|
+
const terminalStatus = selectTerminalStatus(options.events);
|
|
4566
|
+
const lastEvent = options.events.at(-1);
|
|
4567
|
+
const workflowId = lastEvent?.workflowId ?? options.events.find((event) => event.workflowId)?.workflowId;
|
|
4568
|
+
if (terminalStatus === "completed") {
|
|
4569
|
+
return {
|
|
4570
|
+
...options,
|
|
4571
|
+
status: "completed",
|
|
4572
|
+
workflowId,
|
|
4573
|
+
lastTimestamp: lastEvent?.timestamp
|
|
4574
|
+
};
|
|
4575
|
+
}
|
|
4576
|
+
if (latestFailure) {
|
|
4577
|
+
return {
|
|
4578
|
+
...options,
|
|
4579
|
+
status: "failed",
|
|
4580
|
+
workflowId: latestFailure.workflowId,
|
|
4581
|
+
lastTimestamp: latestFailure.event.timestamp,
|
|
4582
|
+
latestFailure
|
|
4583
|
+
};
|
|
4584
|
+
}
|
|
4585
|
+
return {
|
|
4586
|
+
...options,
|
|
4587
|
+
status: terminalStatus ?? "active",
|
|
4588
|
+
workflowId,
|
|
4589
|
+
lastTimestamp: lastEvent?.timestamp
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
function selectTerminalStatus(events) {
|
|
4593
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
4594
|
+
const event = events[index];
|
|
4595
|
+
if (event?.type === "workflow.completed") {
|
|
4596
|
+
return "completed";
|
|
4597
|
+
}
|
|
4598
|
+
if (event?.type === "workflow.failed") {
|
|
4599
|
+
return "failed";
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
return void 0;
|
|
4603
|
+
}
|
|
4604
|
+
function compareTimestampDescending(left, right) {
|
|
4605
|
+
const leftTime = left ? Date.parse(left) : Number.NEGATIVE_INFINITY;
|
|
4606
|
+
const rightTime = right ? Date.parse(right) : Number.NEGATIVE_INFINITY;
|
|
4607
|
+
return rightTime - leftTime;
|
|
4608
|
+
}
|
|
4609
|
+
function readErrorMessage(error) {
|
|
4610
|
+
return error instanceof Error ? error.message : String(error);
|
|
4611
|
+
}
|
|
4612
|
+
function isNodeError2(error) {
|
|
4613
|
+
return error instanceof Error && "code" in error;
|
|
4614
|
+
}
|
|
4615
|
+
export {
|
|
4616
|
+
Document,
|
|
4617
|
+
TrailStepFailureError,
|
|
4618
|
+
defaultRunsRoot,
|
|
4619
|
+
deprecationManifest,
|
|
4620
|
+
document,
|
|
4621
|
+
done,
|
|
4622
|
+
fail,
|
|
4623
|
+
findDeprecationsAsOf,
|
|
4624
|
+
isDoneNode,
|
|
4625
|
+
isFailNode,
|
|
4626
|
+
isStepNode,
|
|
4627
|
+
jsonSchema,
|
|
4628
|
+
list,
|
|
4629
|
+
listRunSummaries,
|
|
4630
|
+
loadFragments,
|
|
4631
|
+
newestFirst,
|
|
4632
|
+
normalizeShape,
|
|
4633
|
+
parseTrailStepConfig,
|
|
4634
|
+
promptSections,
|
|
4635
|
+
promptTemplate,
|
|
4636
|
+
providerRegistry,
|
|
4637
|
+
readRunEvents,
|
|
4638
|
+
readRunState,
|
|
4639
|
+
resolveAgentTargets,
|
|
4640
|
+
resolveRetryPolicy,
|
|
4641
|
+
resolveTimeoutPolicy,
|
|
4642
|
+
runWorkflow,
|
|
4643
|
+
section,
|
|
4644
|
+
selectLatestUnresolvedFailure,
|
|
4645
|
+
selectRecentFailedRunSummaries,
|
|
4646
|
+
shape,
|
|
4647
|
+
state,
|
|
4648
|
+
step,
|
|
4649
|
+
subPrompt,
|
|
4650
|
+
validateRetryPolicy,
|
|
4651
|
+
validateTimeoutPolicy,
|
|
4652
|
+
writeRunState
|
|
4653
|
+
};
|
|
4654
|
+
//# sourceMappingURL=index.js.map
|