pi-subagents 0.58.0 → 0.60.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/CHANGELOG.md +102 -0
- package/docs/agents.md +5 -3
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +1 -1
- package/docs/models.md +24 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +93 -5
- package/docs/workflows.md +94 -3
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +48 -243
- package/skills/council-mode/references/pass-contracts.md +150 -0
- package/skills/pi-subagents/SKILL.md +87 -37
- package/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/skills/pi-subagents/references/execution-controls.md +52 -7
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +54 -26
- package/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/src/agents/agent-management.ts +159 -42
- package/src/agents/agent-serializer.ts +4 -2
- package/src/agents/agents.ts +67 -26
- package/src/agents/runtime-agent-registry.ts +9 -16
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +6 -9
- package/src/api/shared-types.ts +2 -0
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +51 -23
- package/src/extension/public-execution.ts +16 -2
- package/src/extension/schemas.ts +36 -10
- package/src/extension/tool-description.ts +24 -5
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +52 -37
- package/src/runs/background/async-job-tracker.ts +17 -12
- package/src/runs/background/async-resume.ts +23 -25
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +110 -8
- package/src/runs/background/chain-append.ts +6 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/control-channel.ts +3 -2
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +158 -6
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +2 -0
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +30 -9
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +351 -358
- package/src/runs/background/subagent-wait.ts +68 -12
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +25 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +125 -122
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +576 -240
- package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +597 -0
- package/src/runs/shared/background-process-options.ts +9 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/mcp-config-sources.ts +42 -6
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
- package/src/runs/shared/mcp-direct-tool-grant.ts +194 -0
- package/src/runs/shared/model-fallback.ts +9 -2
- package/src/runs/shared/mutation-evidence.ts +52 -3
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +67 -3
- package/src/runs/shared/single-output.ts +72 -22
- package/src/runs/shared/subagent-prompt-runtime.ts +33 -6
- package/src/runs/shared/workflow-graph.ts +15 -0
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +5 -2
- package/src/shared/launch-contract.ts +1 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +232 -30
- package/src/shared/utils.ts +35 -55
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/tui/fleet-status.ts +134 -75
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render-helpers.ts +31 -0
- package/src/tui/render.ts +895 -121
- package/src/watchdog/change-signature.ts +40 -1
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +235 -0
- package/src/workflows/scripted-workflow.ts +503 -38
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +43 -4
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
|
@@ -2,6 +2,10 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { dirname, resolve as resolvePath } from "node:path";
|
|
4
4
|
import { Worker } from "node:worker_threads";
|
|
5
|
+
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../runs/shared/parallel-utils.ts";
|
|
6
|
+
import { HOST_STEP_MAX_COUNT } from "../runs/shared/host-step-status.ts";
|
|
7
|
+
import type { HostStepNodeV1, SingleResult } from "../shared/types.ts";
|
|
8
|
+
import { normalizeWorkflowHostCommandParams, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "./host-command.ts";
|
|
5
9
|
|
|
6
10
|
const KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
7
11
|
const requireFromPackage = createRequire(import.meta.url);
|
|
@@ -252,11 +256,11 @@ function hostCall(method, args, observation) {
|
|
|
252
256
|
: promise;
|
|
253
257
|
}
|
|
254
258
|
|
|
255
|
-
function runHostCall(key, params, collectFailure, batch) {
|
|
259
|
+
function runHostCall(key, params, collectFailure, batch, generatedLaneKey) {
|
|
256
260
|
const callId = ++nextCallId;
|
|
257
261
|
const promise = new Promise((resolve, reject) => {
|
|
258
262
|
pending.set(callId, { resolve, reject });
|
|
259
|
-
parentPort.postMessage({ type: "call", callId, method: "run", args: { key, params, ...(collectFailure ? { collectFailure: true } : {}), ...(batch ? { batch } : {}) } });
|
|
263
|
+
parentPort.postMessage({ type: "call", callId, method: "run", args: { key, params, ...(collectFailure ? { collectFailure: true } : {}), ...(batch ? { batch } : {}), ...(generatedLaneKey ? { generatedLaneKey } : {}) } });
|
|
260
264
|
});
|
|
261
265
|
return { key, callId, promise };
|
|
262
266
|
}
|
|
@@ -269,6 +273,15 @@ function isArrayIndexProperty(prop) {
|
|
|
269
273
|
|
|
270
274
|
const runsAllResultTargets = new WeakMap();
|
|
271
275
|
|
|
276
|
+
const MAX_LANES = 32;
|
|
277
|
+
const MAX_LANE_STAGES = 16;
|
|
278
|
+
const MAX_LANE_STAGE_COUNT = 64;
|
|
279
|
+
const MAX_LANE_SPEC_BYTES = 64 * 1024;
|
|
280
|
+
const MAX_LANE_TASK_BYTES = 1024 * 1024;
|
|
281
|
+
const MAX_LANE_PATH_BYTES = 32 * 1024;
|
|
282
|
+
const MAX_LANE_BOARD_TEXT_BYTES = 256;
|
|
283
|
+
const LANE_PATH_FIELDS = new Set(["cwd", "output", "sessionDir"]);
|
|
284
|
+
|
|
272
285
|
function runsAllKeyAccessError(prop) {
|
|
273
286
|
return new Error("Cannot read runs.all result property '" + prop + "'. runs.all resolves to an ordered array, not a key map. Use results[0], array destructuring, or results.map((result) => result.output), not results." + prop + ".");
|
|
274
287
|
}
|
|
@@ -288,6 +301,242 @@ function wrapRunsAllResults(results, keys) {
|
|
|
288
301
|
return proxy;
|
|
289
302
|
}
|
|
290
303
|
|
|
304
|
+
function laneByteLength(value) {
|
|
305
|
+
return new TextEncoder().encode(value).byteLength;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function laneBoardText(value) {
|
|
309
|
+
if (typeof value !== "string" || !value) return undefined;
|
|
310
|
+
if (laneByteLength(value) <= MAX_LANE_BOARD_TEXT_BYTES) return value;
|
|
311
|
+
const bytes = new TextEncoder().encode(value).subarray(0, MAX_LANE_BOARD_TEXT_BYTES - 3);
|
|
312
|
+
return new TextDecoder().decode(bytes) + "...";
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function laneBoardPath(value) {
|
|
316
|
+
if (typeof value !== "string" || !value || laneByteLength(value) > MAX_LANE_BOARD_TEXT_BYTES) return undefined;
|
|
317
|
+
return value;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function validateLaneStageBounds(params, label) {
|
|
321
|
+
if (typeof params.task === "string" && laneByteLength(params.task) > MAX_LANE_TASK_BYTES) throw new Error(label + " task exceeds 1 MiB when UTF-8 encoded.");
|
|
322
|
+
for (const field of LANE_PATH_FIELDS) if (typeof params[field] === "string" && laneByteLength(params[field]) > MAX_LANE_PATH_BYTES) throw new Error(label + " " + field + " exceeds 32 KiB when UTF-8 encoded.");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function validateLaneKey(value, owner) {
|
|
326
|
+
if (typeof value !== "string" || !runKeyPattern.test(value)) throw new Error(owner + " key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.");
|
|
327
|
+
return value;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function laneStageParams(stage, previous) {
|
|
331
|
+
const resume = stage.resume;
|
|
332
|
+
const params = { ...stage.params };
|
|
333
|
+
if (resume !== "previous") return params;
|
|
334
|
+
if (!previous || typeof previous.runId !== "string" || !previous.runId.trim()) return undefined;
|
|
335
|
+
return { ...params, resume: previous.runId.trim() };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function laneStageVerdict(result) {
|
|
339
|
+
const structured = result && typeof result === "object" && !Array.isArray(result) ? result.structuredOutput : undefined;
|
|
340
|
+
const verdict = structured && typeof structured === "object" && !Array.isArray(structured) ? structured.verdict : undefined;
|
|
341
|
+
return typeof verdict === "string" && verdict.trim() ? laneBoardText(verdict.trim()) : undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function laneStageIsBlocked(result) {
|
|
345
|
+
const structured = result && typeof result === "object" && !Array.isArray(result) ? result.structuredOutput : undefined;
|
|
346
|
+
return structured && typeof structured === "object" && !Array.isArray(structured) && structured.verdict === "blocked";
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function laneStageRecord(stageKey, child, forcedState) {
|
|
350
|
+
const result = child && typeof child === "object" && !Array.isArray(child) ? child : undefined;
|
|
351
|
+
const ok = result?.ok === true;
|
|
352
|
+
const verdict = laneStageVerdict(result);
|
|
353
|
+
const blocked = laneStageIsBlocked(result);
|
|
354
|
+
const state = forcedState ?? (result?.stopped ? "stopped" : result?.detached ? "detached" : ok ? blocked ? "blocked" : "completed" : "failed");
|
|
355
|
+
const error = state !== "completed"
|
|
356
|
+
? state === "blocked" && blocked
|
|
357
|
+
? "Stage returned a blocked verdict."
|
|
358
|
+
: typeof result?.error === "string"
|
|
359
|
+
? result.error
|
|
360
|
+
: typeof result?.output === "string"
|
|
361
|
+
? result.output
|
|
362
|
+
: "Stage did not complete successfully."
|
|
363
|
+
: undefined;
|
|
364
|
+
return {
|
|
365
|
+
key: stageKey,
|
|
366
|
+
...(typeof result?.runId === "string" && result.runId.trim() ? { runId: laneBoardText(result.runId.trim()) } : {}),
|
|
367
|
+
...(result ? { ok } : {}),
|
|
368
|
+
state,
|
|
369
|
+
...(typeof result?.outputReference === "string" ? (laneBoardPath(result.outputReference) ? { outputReference: laneBoardPath(result.outputReference) } : {}) : {}),
|
|
370
|
+
...(verdict ? { verdict } : {}),
|
|
371
|
+
...(error ? { error: laneBoardText(error) } : {}),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function laneFailure(stageKey, error) {
|
|
376
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
377
|
+
return { key: stageKey, ok: false, output: text, error: text, artifactPaths: [] };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function runCollected(key, params, observe, generatedLaneKey) {
|
|
381
|
+
validateRunCall(key, params, "runs.lanes stage", runFingerprints);
|
|
382
|
+
const launched = runHostCall(key, params, true, undefined, generatedLaneKey);
|
|
383
|
+
observe([{ key, operation: "run", callId: launched.callId }]);
|
|
384
|
+
return launched.promise.then(decorateWorkflowChildResult);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function validateLaneSpecs(laneSpecs) {
|
|
388
|
+
if (!Array.isArray(laneSpecs)) throw new Error("runs.lanes(lanes) requires an array.");
|
|
389
|
+
if (laneSpecs.length === 0) throw new Error("runs.lanes(lanes) requires at least one lane.");
|
|
390
|
+
if (laneSpecs.length > MAX_LANES) throw new Error("runs.lanes supports at most " + MAX_LANES + " lanes.");
|
|
391
|
+
assertJsonValue(laneSpecs, "runs.lanes lanes");
|
|
392
|
+
if (laneByteLength(stableRunJson(laneSpecs)) > MAX_LANE_SPEC_BYTES) throw new Error("runs.lanes canonical JSON exceeds 64 KiB.");
|
|
393
|
+
const generatedKeys = new Set();
|
|
394
|
+
const validationFingerprints = new Map();
|
|
395
|
+
let stageCount = 0;
|
|
396
|
+
const normalized = [];
|
|
397
|
+
for (let laneIndex = 0; laneIndex < laneSpecs.length; laneIndex++) {
|
|
398
|
+
const lane = laneSpecs[laneIndex];
|
|
399
|
+
const laneLabel = "runs.lanes lane " + laneIndex;
|
|
400
|
+
if (!lane || typeof lane !== "object" || Array.isArray(lane)) throw new Error(laneLabel + " must be an object.");
|
|
401
|
+
const laneFields = Object.keys(lane);
|
|
402
|
+
if (laneFields.some((field) => field !== "key" && field !== "stages")) throw new Error(laneLabel + " contains unsupported fields.");
|
|
403
|
+
const laneKey = validateLaneKey(lane.key, laneLabel);
|
|
404
|
+
if (!Array.isArray(lane.stages) || lane.stages.length === 0) throw new Error(laneLabel + " stages must contain at least one stage.");
|
|
405
|
+
if (lane.stages.length > MAX_LANE_STAGES) throw new Error(laneLabel + " supports at most " + MAX_LANE_STAGES + " stages.");
|
|
406
|
+
const stageKeys = new Set();
|
|
407
|
+
const stages = [];
|
|
408
|
+
for (let stageIndex = 0; stageIndex < lane.stages.length; stageIndex++) {
|
|
409
|
+
stageCount++;
|
|
410
|
+
if (stageCount > MAX_LANE_STAGE_COUNT) throw new Error("runs.lanes supports at most " + MAX_LANE_STAGE_COUNT + " total stages.");
|
|
411
|
+
const stage = lane.stages[stageIndex];
|
|
412
|
+
const stageLabel = laneLabel + " stage " + stageIndex;
|
|
413
|
+
if (!stage || typeof stage !== "object" || Array.isArray(stage)) throw new Error(stageLabel + " must be an object.");
|
|
414
|
+
const stageKey = validateLaneKey(stage.key, stageLabel);
|
|
415
|
+
if (stageKeys.has(stageKey)) throw new Error(laneLabel + " contains duplicate stage key '" + stageKey + "'.");
|
|
416
|
+
stageKeys.add(stageKey);
|
|
417
|
+
const generatedKey = laneKey + "." + stageKey;
|
|
418
|
+
validateLaneKey(generatedKey, stageLabel + " generated");
|
|
419
|
+
if (generatedKeys.has(generatedKey)) throw new Error("runs.lanes generated child key '" + generatedKey + "' is duplicated.");
|
|
420
|
+
generatedKeys.add(generatedKey);
|
|
421
|
+
const resume = stage.resume;
|
|
422
|
+
if (resume !== undefined && resume !== "previous") {
|
|
423
|
+
if (stageIndex === 0 && typeof resume === "string") {
|
|
424
|
+
throw new Error(stageLabel + " cannot resume a retained run id in runs.lanes; use runs.run(key, { resume: id }) outside lanes, or start the lane with an agent stage and use resume: \"previous\" later.");
|
|
425
|
+
}
|
|
426
|
+
throw new Error(stageLabel + " resume must be 'previous'.");
|
|
427
|
+
}
|
|
428
|
+
if (stageIndex === 0 && resume === "previous") throw new Error(stageLabel + " cannot resume previous without a predecessor stage.");
|
|
429
|
+
const { key: _stageKey, resume: _resume, ...params } = stage;
|
|
430
|
+
const validationParams = resume === "previous" ? { ...params, resume: "retained-run-placeholder" } : params;
|
|
431
|
+
validateLaneStageBounds(validationParams, stageLabel);
|
|
432
|
+
validateRunCall(generatedKey, validationParams, stageLabel, validationFingerprints);
|
|
433
|
+
const existingFingerprint = runFingerprints.get(generatedKey);
|
|
434
|
+
if (existingFingerprint !== undefined && (resume === "previous" || existingFingerprint !== stableRunJson(params))) {
|
|
435
|
+
throw new Error("runs.lanes generated child key '" + generatedKey + "' is already used with incompatible launch params.");
|
|
436
|
+
}
|
|
437
|
+
stages.push({ key: stageKey, generatedKey, resume, params });
|
|
438
|
+
}
|
|
439
|
+
normalized.push({ key: laneKey, stages });
|
|
440
|
+
}
|
|
441
|
+
return normalized;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function runLane(lane, firstResult, observe) {
|
|
445
|
+
const records = [];
|
|
446
|
+
const appendSkipped = (start) => {
|
|
447
|
+
for (let index = start; index < lane.stages.length; index++) records.push({ key: lane.stages[index].key, state: "skipped" });
|
|
448
|
+
};
|
|
449
|
+
const finish = (state, failedStage) => ({ key: lane.key, state, ...(failedStage ? { failedStage } : {}), stages: records });
|
|
450
|
+
const visit = (index, previous) => {
|
|
451
|
+
if (index >= lane.stages.length) return Promise.resolve(finish("complete"));
|
|
452
|
+
const stage = lane.stages[index];
|
|
453
|
+
if (index === 0) {
|
|
454
|
+
const record = laneStageRecord(stage.key, previous);
|
|
455
|
+
records.push(record);
|
|
456
|
+
if (record.state !== "completed") {
|
|
457
|
+
appendSkipped(index + 1);
|
|
458
|
+
return Promise.resolve(finish("blocked", stage.key));
|
|
459
|
+
}
|
|
460
|
+
return visit(index + 1, previous);
|
|
461
|
+
}
|
|
462
|
+
if (stage.resume === "previous" && (!previous || typeof previous.runId !== "string" || !previous.runId.trim())) {
|
|
463
|
+
records.push({ key: stage.key, state: "blocked", error: "Previous stage did not return a retained run id." });
|
|
464
|
+
appendSkipped(index + 1);
|
|
465
|
+
return Promise.resolve(finish("blocked", stage.key));
|
|
466
|
+
}
|
|
467
|
+
if (!previous || previous.ok !== true || laneStageIsBlocked(previous)) {
|
|
468
|
+
records.push({ key: stage.key, state: "blocked", error: "Previous stage did not complete successfully." });
|
|
469
|
+
appendSkipped(index + 1);
|
|
470
|
+
return Promise.resolve(finish("blocked", stage.key));
|
|
471
|
+
}
|
|
472
|
+
const params = laneStageParams(stage, previous);
|
|
473
|
+
if (!params) {
|
|
474
|
+
records.push({ key: stage.key, state: "blocked", error: "Previous stage did not return a retained run id." });
|
|
475
|
+
appendSkipped(index + 1);
|
|
476
|
+
return Promise.resolve(finish("blocked", stage.key));
|
|
477
|
+
}
|
|
478
|
+
let launched;
|
|
479
|
+
try {
|
|
480
|
+
launched = runCollected(stage.generatedKey, params, observe, lane.key);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
const failed = laneFailure(stage.generatedKey, error);
|
|
483
|
+
records.push(laneStageRecord(stage.key, failed));
|
|
484
|
+
appendSkipped(index + 1);
|
|
485
|
+
return Promise.resolve(finish("blocked", stage.key));
|
|
486
|
+
}
|
|
487
|
+
return launched.then((result) => {
|
|
488
|
+
const record = laneStageRecord(stage.key, result);
|
|
489
|
+
records.push(record);
|
|
490
|
+
if (record.state === "completed") return visit(index + 1, result);
|
|
491
|
+
appendSkipped(index + 1);
|
|
492
|
+
return finish("blocked", stage.key);
|
|
493
|
+
}, (error) => {
|
|
494
|
+
const failed = laneFailure(stage.generatedKey, error);
|
|
495
|
+
records.push(laneStageRecord(stage.key, failed));
|
|
496
|
+
appendSkipped(index + 1);
|
|
497
|
+
return finish("blocked", stage.key);
|
|
498
|
+
});
|
|
499
|
+
};
|
|
500
|
+
return visit(0, firstResult);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function workflowPlanStringMetadata(params) {
|
|
504
|
+
return {
|
|
505
|
+
...(typeof params.phase === "string" && params.phase.trim() ? { phase: params.phase.trim() } : {}),
|
|
506
|
+
...(typeof params.label === "string" && params.label.trim() ? { label: params.label.trim() } : {}),
|
|
507
|
+
...(typeof params.agent === "string" && params.agent.trim() ? { agent: params.agent.trim() } : {}),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function runLanes(laneSpecs) {
|
|
512
|
+
const lanes = validateLaneSpecs(laneSpecs);
|
|
513
|
+
parentPort.postMessage({ type: "lanePlan", lanes: lanes.map((lane) => ({
|
|
514
|
+
key: lane.key,
|
|
515
|
+
stages: lane.stages.map((stage) => ({
|
|
516
|
+
key: stage.key,
|
|
517
|
+
generatedKey: stage.generatedKey,
|
|
518
|
+
...workflowPlanStringMetadata(stage.params),
|
|
519
|
+
...(typeof stage.params.as === "string" && stage.params.as.trim() ? { outputName: stage.params.as.trim() } : {}),
|
|
520
|
+
...(stage.params.outputSchema !== undefined ? { structured: true } : {}),
|
|
521
|
+
})),
|
|
522
|
+
})) });
|
|
523
|
+
const firstItems = lanes.map((lane) => {
|
|
524
|
+
const first = lane.stages[0];
|
|
525
|
+
return { key: first.generatedKey, ...first.params };
|
|
526
|
+
});
|
|
527
|
+
// Share the runs.all batch launcher while allowing each lane to advance independently.
|
|
528
|
+
const firstBatch = launchRunsAll(firstItems, lanes.map((lane) => lane.key));
|
|
529
|
+
const firstResults = firstBatch.launched.map(({ promise }) => promise.then(decorateWorkflowChildResult));
|
|
530
|
+
let trackedAggregate;
|
|
531
|
+
const observe = (observations) => trackRunObservation(observations, trackedAggregate);
|
|
532
|
+
const laneAggregate = Promise.all(lanes.map((lane, index) => firstResults[index].then(
|
|
533
|
+
(result) => runLane(lane, result, observe),
|
|
534
|
+
(error) => runLane(lane, laneFailure(lane.stages[0].generatedKey, error), observe),
|
|
535
|
+
)));
|
|
536
|
+
trackedAggregate = trackRunObservation(firstBatch.launched.map(({ key, callId }) => ({ key, operation: "run", callId })), laneAggregate);
|
|
537
|
+
return trackedAggregate;
|
|
538
|
+
}
|
|
539
|
+
|
|
291
540
|
function formatRef(result) {
|
|
292
541
|
if (!result || typeof result !== "object") throw new Error("runs.ref(result) requires a run result object.");
|
|
293
542
|
const parts = ["run " + (result.key || "unknown")];
|
|
@@ -332,8 +581,28 @@ function validateExtensionBindings(value, label) {
|
|
|
332
581
|
if (new TextEncoder().encode(stableRunJson(value)).byteLength > 16384) throw new Error(label + " extensionBindings canonical JSON exceeds 16384 bytes.");
|
|
333
582
|
}
|
|
334
583
|
|
|
584
|
+
function validateLaneMetadata(value, label, workflowKey) {
|
|
585
|
+
if (value === undefined) return;
|
|
586
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(label + " must be a plain JSON object.");
|
|
587
|
+
const fields = Object.keys(value);
|
|
588
|
+
const allowed = ["version", "key", "mode", "sourceRef", "claims", "outputPaths"];
|
|
589
|
+
const unknown = fields.filter((field) => !allowed.includes(field));
|
|
590
|
+
if (unknown.length > 0) throw new Error(label + " has unsupported fields: " + unknown.join(", ") + ".");
|
|
591
|
+
if (value.version !== 1) throw new Error(label + ".version must be 1.");
|
|
592
|
+
if (typeof value.key !== "string" || !value.key.trim() || !runKeyPattern.test(value.key.trim())) throw new Error(label + ".key is invalid.");
|
|
593
|
+
if (workflowKey !== undefined && value.key.trim() !== workflowKey) throw new Error(label + ".key must match workflow key '" + workflowKey + "'.");
|
|
594
|
+
if (value.mode !== undefined && !["mutation", "review", "scout", "gate"].includes(value.mode)) throw new Error(label + ".mode is invalid.");
|
|
595
|
+
const bounded = (entry, maxBytes) => typeof entry === "string" && entry.trim() && new TextEncoder().encode(entry.trim()).byteLength <= maxBytes && !/[\r\n\u0000]/.test(entry);
|
|
596
|
+
if (value.sourceRef !== undefined && !bounded(value.sourceRef, 128)) throw new Error(label + ".sourceRef is invalid.");
|
|
597
|
+
for (const [name, maxItems, maxLength] of [["claims", 20, 160], ["outputPaths", 10, 256]]) {
|
|
598
|
+
if (value[name] === undefined) continue;
|
|
599
|
+
if (!Array.isArray(value[name]) || value[name].length > maxItems || value[name].some((entry) => !bounded(entry, maxLength))) throw new Error(label + "." + name + " is invalid.");
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
335
603
|
function validateRunCall(key, params, label, fingerprints) {
|
|
336
604
|
if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error(label + " has an invalid key.");
|
|
605
|
+
if (hostKeys.has(key)) throw new Error("Workflow key '" + key + "' is already used by runs.host.");
|
|
337
606
|
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error(label + " requires a params object.");
|
|
338
607
|
if (Object.prototype.hasOwnProperty.call(params, "action") || Object.prototype.hasOwnProperty.call(params, "workflowScript") || Object.prototype.hasOwnProperty.call(params, "tasks") || Object.prototype.hasOwnProperty.call(params, "chain") || Object.prototype.hasOwnProperty.call(params, "parallel") || Object.prototype.hasOwnProperty.call(params, "concurrency") || Object.prototype.hasOwnProperty.call(params, "chainDir")) {
|
|
339
608
|
const hint = label === "runs.run" ? "; use runs.all(...) and JavaScript control flow for orchestration." : ".";
|
|
@@ -341,6 +610,7 @@ function validateRunCall(key, params, label, fingerprints) {
|
|
|
341
610
|
}
|
|
342
611
|
if (Object.prototype.hasOwnProperty.call(params, "clarify")) throw new Error(label + " does not support clarify UI.");
|
|
343
612
|
if (params.worktree !== undefined && typeof params.worktree !== "boolean") throw new Error(label + " worktree must be true or false.");
|
|
613
|
+
validateLaneMetadata(params.lane, label + " lane", key);
|
|
344
614
|
if (params.gate !== undefined && (typeof params.gate !== "string" || !params.gate.trim())) throw new Error(label + " gate must be a non-empty command string.");
|
|
345
615
|
if (params.gate !== undefined && params.acceptance !== undefined) throw new Error(label + " gate cannot be combined with acceptance; use one gate command or acceptance.verify.");
|
|
346
616
|
if (params.gate !== undefined && params.resume !== undefined) throw new Error(label + " gate is not supported with retained resume.");
|
|
@@ -365,6 +635,49 @@ function validateRunCall(key, params, label, fingerprints) {
|
|
|
365
635
|
fingerprints.set(key, fingerprint);
|
|
366
636
|
}
|
|
367
637
|
|
|
638
|
+
const hostKeys = new Set();
|
|
639
|
+
|
|
640
|
+
function validateHostCommand(key, params) {
|
|
641
|
+
validateLaneKey(key, "runs.host");
|
|
642
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("runs.host('" + key + "') params must be an object.");
|
|
643
|
+
const allowed = new Set(["kind", "command", "timeoutMs", "output", "role", "provider"]);
|
|
644
|
+
const unknown = Object.keys(params).filter((field) => !allowed.has(field));
|
|
645
|
+
if (unknown.length) {
|
|
646
|
+
const cwdHint = unknown.includes("cwd")
|
|
647
|
+
? " The host step does not accept per-step cwd; commands and relative output paths use the workflow cwd. Set cwd on the outer subagent request, or put a trusted directory change in command (for example, 'cd /path/to/worktree && npm test')."
|
|
648
|
+
: "";
|
|
649
|
+
throw new Error("runs.host('" + key + "') params have unsupported fields: " + unknown.join(", ") + "." + cwdHint);
|
|
650
|
+
}
|
|
651
|
+
if (params.kind !== "command") throw new Error("runs.host('" + key + "') kind must be 'command'.");
|
|
652
|
+
if (typeof params.command !== "string" || !params.command.trim() || params.command.includes("\u0000") || new TextEncoder().encode(params.command.trim()).byteLength > 16384) throw new Error("runs.host('" + key + "') command must be a non-empty string of at most 16384 bytes without NUL.");
|
|
653
|
+
if (!Number.isInteger(params.timeoutMs) || params.timeoutMs < 1 || params.timeoutMs > 86400000) throw new Error("runs.host('" + key + "') timeoutMs must be an integer from 1 to 86400000.");
|
|
654
|
+
if (params.output !== undefined && (typeof params.output !== "string" || !params.output.trim() || /^[/\\]|(?:^|[/\\])\.\.(?:[/\\]|$)/.test(params.output) || /[\u0000-\u001f\u007f]/.test(params.output) || new TextEncoder().encode(params.output.trim()).byteLength > 240)) throw new Error("runs.host('" + key + "') output must be a bounded relative path without traversal or control characters.");
|
|
655
|
+
if (params.role !== undefined && params.role !== "ci" && params.role !== "gate") throw new Error("runs.host('" + key + "') role must be 'ci' or 'gate'.");
|
|
656
|
+
if (params.provider !== undefined && (typeof params.provider !== "string" || !params.provider.trim() || /[\r\n\u0000]/.test(params.provider) || new TextEncoder().encode(params.provider.trim()).byteLength > 64)) throw new Error("runs.host('" + key + "') provider must be a non-empty single-line string of at most 64 bytes.");
|
|
657
|
+
assertJsonValue(params, "runs.host('" + key + "') params");
|
|
658
|
+
if (hostKeys.has(key) || runFingerprints.has(key)) throw new Error("Workflow key '" + key + "' is already in use.");
|
|
659
|
+
if (hostKeys.size >= 32) throw new Error("workflowScript supports at most 32 runs.host calls.");
|
|
660
|
+
hostKeys.add(key);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function launchRunsAll(items, generatedLaneKeys) {
|
|
664
|
+
if (!Array.isArray(items)) throw new Error("runs.all(items) requires an array.");
|
|
665
|
+
const fingerprints = new Map(runFingerprints);
|
|
666
|
+
const calls = [];
|
|
667
|
+
for (let index = 0; index < items.length; index++) {
|
|
668
|
+
if (!Object.prototype.hasOwnProperty.call(items, index)) throw new Error("runs.all items must not contain sparse entries.");
|
|
669
|
+
const item = items[index];
|
|
670
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error("runs.all item " + index + " must be an object.");
|
|
671
|
+
const { key, ...params } = item;
|
|
672
|
+
validateRunCall(key, params, "runs.all item " + index, fingerprints);
|
|
673
|
+
calls.push({ key, params });
|
|
674
|
+
}
|
|
675
|
+
runFingerprints = fingerprints;
|
|
676
|
+
const batch = { id: "batch-" + (++nextCallId), calls };
|
|
677
|
+
const launched = calls.map(({ key, params }, index) => runHostCall(key, params, true, batch, generatedLaneKeys?.[index]));
|
|
678
|
+
return { calls, launched };
|
|
679
|
+
}
|
|
680
|
+
|
|
368
681
|
const runs = Object.freeze({
|
|
369
682
|
run(key, params) {
|
|
370
683
|
validateRunCall(key, params, "runs.run", runFingerprints);
|
|
@@ -372,22 +685,16 @@ const runs = Object.freeze({
|
|
|
372
685
|
return trackRunObservation([{ key, operation: "run", callId: launched.callId }], launched.promise.then(decorateWorkflowChildResult));
|
|
373
686
|
},
|
|
374
687
|
all(items) {
|
|
375
|
-
|
|
376
|
-
const fingerprints = new Map(runFingerprints);
|
|
377
|
-
const calls = [];
|
|
378
|
-
for (let index = 0; index < items.length; index++) {
|
|
379
|
-
if (!Object.prototype.hasOwnProperty.call(items, index)) throw new Error("runs.all items must not contain sparse entries.");
|
|
380
|
-
const item = items[index];
|
|
381
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error("runs.all item " + index + " must be an object.");
|
|
382
|
-
const { key, ...params } = item;
|
|
383
|
-
validateRunCall(key, params, "runs.all item " + index, fingerprints);
|
|
384
|
-
calls.push({ key, params });
|
|
385
|
-
}
|
|
386
|
-
runFingerprints = fingerprints;
|
|
387
|
-
const batch = { id: "batch-" + (++nextCallId), calls };
|
|
388
|
-
const launched = calls.map(({ key, params }) => runHostCall(key, params, true, batch));
|
|
688
|
+
const { calls, launched } = launchRunsAll(items);
|
|
389
689
|
return trackRunObservation(launched.map(({ key, callId }) => ({ key, operation: "run", callId })), Promise.all(launched.map(({ promise }) => promise)).then((results) => wrapRunsAllResults(results.map(decorateWorkflowChildResult), calls.map(({ key }) => key))));
|
|
390
690
|
},
|
|
691
|
+
lanes(laneSpecs) {
|
|
692
|
+
return runLanes(laneSpecs);
|
|
693
|
+
},
|
|
694
|
+
host(key, params) {
|
|
695
|
+
validateHostCommand(key, params);
|
|
696
|
+
return hostCall("host", { key, params }, { key, operation: "host" });
|
|
697
|
+
},
|
|
391
698
|
steer(key, message, options = {}) {
|
|
392
699
|
if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error("runs.steer has an invalid key.");
|
|
393
700
|
if (typeof message !== "string" || !message.trim()) throw new Error("runs.steer message must be a non-empty string.");
|
|
@@ -654,6 +961,7 @@ parentPort.on("message", async (message) => {
|
|
|
654
961
|
export interface WorkflowScriptChildResult {
|
|
655
962
|
key: string;
|
|
656
963
|
ok: boolean;
|
|
964
|
+
lane?: import("../shared/types.ts").WorkflowLaneMetadata;
|
|
657
965
|
terminalOutcome?: import("../shared/types.ts").WorkflowTerminalOutcome;
|
|
658
966
|
stopped?: boolean;
|
|
659
967
|
/** Canonical child agent name when launch resolution produced one. */
|
|
@@ -672,11 +980,11 @@ export interface WorkflowScriptChildResult {
|
|
|
672
980
|
resumability?: { state: "resumable" } | { state: "not-resumable"; reason: string };
|
|
673
981
|
continuation?: { runIds: string[] };
|
|
674
982
|
artifactPaths: string[];
|
|
675
|
-
results?:
|
|
983
|
+
results?: SingleResult[];
|
|
676
984
|
}
|
|
677
985
|
|
|
678
986
|
export interface WorkflowScriptTraceEntry {
|
|
679
|
-
operation: "run" | "status" | "steer";
|
|
987
|
+
operation: "run" | "status" | "steer" | "host";
|
|
680
988
|
key: string;
|
|
681
989
|
state: "started" | "completed" | "failed" | "detached" | "stopped" | "reused" | "queued" | "delivered" | "missed";
|
|
682
990
|
/** Canonical child agent name when resolved launch or result data is available. */
|
|
@@ -686,6 +994,26 @@ export interface WorkflowScriptTraceEntry {
|
|
|
686
994
|
phase?: string;
|
|
687
995
|
label?: string;
|
|
688
996
|
error?: string;
|
|
997
|
+
/** Internal provenance for a generated runs.lanes child key. */
|
|
998
|
+
generatedLaneKey?: string;
|
|
999
|
+
lane?: import("../shared/types.ts").WorkflowLaneMetadata;
|
|
1000
|
+
warning?: string;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/** Bounded plan metadata emitted when a workflow materializes a runs.lanes graph. */
|
|
1004
|
+
export interface WorkflowLanePlanStage {
|
|
1005
|
+
key: string;
|
|
1006
|
+
generatedKey: string;
|
|
1007
|
+
agent?: string;
|
|
1008
|
+
phase?: string;
|
|
1009
|
+
label?: string;
|
|
1010
|
+
outputName?: string;
|
|
1011
|
+
structured?: boolean;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
export interface WorkflowLanePlan {
|
|
1015
|
+
key: string;
|
|
1016
|
+
stages: WorkflowLanePlanStage[];
|
|
689
1017
|
}
|
|
690
1018
|
|
|
691
1019
|
export interface WorkflowSteerOptions {
|
|
@@ -740,17 +1068,22 @@ export interface RunWorkflowScriptOptions {
|
|
|
740
1068
|
oneUsePermit?: { claim: (key: string) => string | undefined };
|
|
741
1069
|
timeoutMs?: number;
|
|
742
1070
|
signal?: AbortSignal;
|
|
1071
|
+
/** Maximum children executing concurrently within this workflow. Defaults to 20. */
|
|
1072
|
+
globalConcurrencyLimit?: number;
|
|
743
1073
|
admit?: (calls: Array<{ key: string; params: Record<string, unknown> }>) => void | Promise<void>;
|
|
744
1074
|
launch: (key: string, params: Record<string, unknown>, signal: AbortSignal, admission: { admitted: boolean; batch: boolean }) => Promise<WorkflowScriptChildResult>;
|
|
745
1075
|
resolveResume?: (reference: WorkflowReceiptResumeReference, signal: AbortSignal) => string | WorkflowResolvedResumeReference | Promise<string | WorkflowResolvedResumeReference>;
|
|
746
1076
|
status: (keyOrRunId: string, signal: AbortSignal) => Promise<WorkflowScriptChildResult>;
|
|
747
1077
|
steer?: (key: string, message: string, options: WorkflowSteerOptions, signal: AbortSignal) => Promise<WorkflowSteerResult>;
|
|
1078
|
+
host?: (key: string, params: WorkflowHostCommandParams, signal: AbortSignal) => Promise<WorkflowHostCommandResult>;
|
|
1079
|
+
onHostStep?: (hostStep: HostStepNodeV1) => void;
|
|
748
1080
|
state?: {
|
|
749
1081
|
get: (key: string) => unknown | Promise<unknown>;
|
|
750
1082
|
set: (key: string, value: unknown) => void | Promise<void>;
|
|
751
1083
|
};
|
|
752
1084
|
registerStopChild?: (stop: ((key: string, message?: string) => boolean) | undefined) => void;
|
|
753
1085
|
onTrace?: (trace: WorkflowScriptTraceEntry[]) => void;
|
|
1086
|
+
onLanePlan?: (lanes: WorkflowLanePlan[]) => void;
|
|
754
1087
|
onEmit?: (emits: unknown[]) => void;
|
|
755
1088
|
}
|
|
756
1089
|
|
|
@@ -916,12 +1249,47 @@ function literalString(node: unknown): string | undefined {
|
|
|
916
1249
|
return undefined;
|
|
917
1250
|
}
|
|
918
1251
|
|
|
919
|
-
function directRunsCall(node: unknown, method: "run" | "all"): node is AstNode {
|
|
1252
|
+
function directRunsCall(node: unknown, method: "run" | "all" | "host"): node is AstNode {
|
|
920
1253
|
if (!astNode(node) || node.type !== "CallExpression" || !astNode(node.callee) || node.callee.type !== "MemberExpression") return false;
|
|
921
1254
|
const property = node.callee.computed === true ? literalString(node.callee.property) : astNode(node.callee.property) && node.callee.property.type === "Identifier" ? node.callee.property.name : undefined;
|
|
922
1255
|
return property === method && astNode(node.callee.object) && node.callee.object.type === "Identifier" && node.callee.object.name === "runs";
|
|
923
1256
|
}
|
|
924
1257
|
|
|
1258
|
+
function validateStaticHostCall(call: AstNode): WorkflowScriptValidationError[] {
|
|
1259
|
+
const args = Array.isArray(call.arguments) ? call.arguments : [];
|
|
1260
|
+
const keyNode = astNode(args[0]) ? args[0] : undefined;
|
|
1261
|
+
const params = astNode(args[1]) ? args[1] : undefined;
|
|
1262
|
+
const errors: WorkflowScriptValidationError[] = [];
|
|
1263
|
+
const key = literalString(keyNode);
|
|
1264
|
+
if (keyNode && key !== undefined && !KEY_PATTERN.test(key)) errors.push({ message: "runs.host key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.", ...nodeLocation(keyNode) });
|
|
1265
|
+
if (!params || params.type !== "ObjectExpression" || !Array.isArray(params.properties) || params.properties.some((property) => !astNode(property) || property.type !== "Property")) return errors;
|
|
1266
|
+
const allowed = new Set(["kind", "command", "timeoutMs", "output", "role", "provider"]);
|
|
1267
|
+
for (const property of params.properties) {
|
|
1268
|
+
if (!astNode(property)) continue;
|
|
1269
|
+
const name = staticPropertyKey(property);
|
|
1270
|
+
if (name !== undefined && !allowed.has(name)) errors.push({ message: name === "cwd" ? "runs.host params contain unsupported field 'cwd'. The host step does not accept per-step cwd; commands and relative output paths use the workflow cwd. Set cwd on the outer subagent request, or put a trusted directory change in command (for example, 'cd /path/to/worktree && npm test')." : `runs.host params contain unsupported field '${name}'.`, ...nodeLocation(property) });
|
|
1271
|
+
}
|
|
1272
|
+
const kindNode = directObjectPropertyValue(params, "kind");
|
|
1273
|
+
const commandNode = directObjectPropertyValue(params, "command");
|
|
1274
|
+
const timeoutNode = directObjectPropertyValue(params, "timeoutMs");
|
|
1275
|
+
const kind = literalString(kindNode);
|
|
1276
|
+
const command = literalString(commandNode);
|
|
1277
|
+
const timeout = astNode(timeoutNode) && timeoutNode.type === "Literal" ? timeoutNode.value : undefined;
|
|
1278
|
+
if (!kindNode) errors.push({ message: "runs.host params require kind: 'command'.", ...nodeLocation(params) });
|
|
1279
|
+
else if (kind !== undefined && kind !== "command") errors.push({ message: "runs.host params kind must be 'command'.", ...nodeLocation(kindNode) });
|
|
1280
|
+
if (!commandNode) errors.push({ message: "runs.host params require command.", ...nodeLocation(params) });
|
|
1281
|
+
else if (command !== undefined && !command.trim()) errors.push({ message: "runs.host params command must be non-empty.", ...nodeLocation(commandNode) });
|
|
1282
|
+
if (!timeoutNode) errors.push({ message: "runs.host params require timeoutMs.", ...nodeLocation(params) });
|
|
1283
|
+
else if (typeof timeout === "number" && (!Number.isInteger(timeout) || timeout < 1)) errors.push({ message: "runs.host params timeoutMs must be a positive integer.", ...nodeLocation(timeoutNode) });
|
|
1284
|
+
const outputNode = directObjectPropertyValue(params, "output");
|
|
1285
|
+
const output = literalString(outputNode);
|
|
1286
|
+
if (outputNode && output !== undefined && (!output.trim() || /^[/\\]|(?:^|[/\\])\.\.(?:[/\\]|$)/.test(output))) errors.push({ message: "runs.host params output must be a non-empty relative path without traversal.", ...nodeLocation(outputNode) });
|
|
1287
|
+
const roleNode = directObjectPropertyValue(params, "role");
|
|
1288
|
+
const role = literalString(roleNode);
|
|
1289
|
+
if (roleNode && role !== undefined && role !== "ci" && role !== "gate") errors.push({ message: "runs.host params role must be 'ci' or 'gate'.", ...nodeLocation(roleNode) });
|
|
1290
|
+
return errors;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
925
1293
|
function nodeLocation(node: AstNode): Pick<WorkflowScriptValidationError, "line" | "column"> {
|
|
926
1294
|
return node.loc ? { line: Math.max(1, node.loc.start.line - 1), column: node.loc.start.column + 1 } : {};
|
|
927
1295
|
}
|
|
@@ -1042,6 +1410,7 @@ export function validateWorkflowScript(script: string): WorkflowScriptValidation
|
|
|
1042
1410
|
}
|
|
1043
1411
|
}
|
|
1044
1412
|
}
|
|
1413
|
+
if (directRunsCall(node, "host")) errors.push(...validateStaticHostCall(node));
|
|
1045
1414
|
const boundaryValue = node.type === "CallExpression" && astNode(node.callee) && node.callee.type === "Identifier" && node.callee.name === "emit" && Array.isArray(node.arguments) && astNode(node.arguments[0])
|
|
1046
1415
|
? node.arguments[0]
|
|
1047
1416
|
: node.type === "CallExpression" && astNode(node.callee) && node.callee.type === "MemberExpression" && astNode(node.callee.object) && node.callee.object.type === "Identifier" && node.callee.object.name === "state" && astNode(node.callee.property) && node.callee.property.type === "Identifier" && node.callee.property.name === "set" && Array.isArray(node.arguments) && astNode(node.arguments[1])
|
|
@@ -1111,7 +1480,7 @@ function resolveWorkflowParserEntry(): string {
|
|
|
1111
1480
|
}
|
|
1112
1481
|
}
|
|
1113
1482
|
|
|
1114
|
-
const AUTO_RESUME_PARAM_KEYS = ["acceptance", "agentContract", "index", "intercomBridge", "label", "maxRuntimeMs", "output", "outputMode", "outputSchema", "phase", "skill", "skills", "task", "timeoutMs", "toolBudget", "
|
|
1483
|
+
const AUTO_RESUME_PARAM_KEYS = ["acceptance", "agentContract", "index", "intercomBridge", "label", "lane", "maxRuntimeMs", "output", "outputMode", "outputSchema", "phase", "skill", "skills", "task", "timeoutMs", "toolBudget", "worktree"] as const;
|
|
1115
1484
|
|
|
1116
1485
|
function isZeroUsage(usage: unknown): boolean {
|
|
1117
1486
|
if (!isRecord(usage)) return false;
|
|
@@ -1149,6 +1518,10 @@ function setupAbortResumeParams(params: Record<string, unknown>, result: Workflo
|
|
|
1149
1518
|
export async function runWorkflowScript(options: RunWorkflowScriptOptions): Promise<WorkflowScriptResult> {
|
|
1150
1519
|
if (!options.script.trim()) throw new Error("workflowScript must not be empty.");
|
|
1151
1520
|
if (options.timeoutMs !== undefined && (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1)) throw new Error("workflow script timeout must be a positive integer.");
|
|
1521
|
+
if (options.globalConcurrencyLimit !== undefined && (!Number.isInteger(options.globalConcurrencyLimit) || options.globalConcurrencyLimit < 1)) {
|
|
1522
|
+
throw new Error("workflow script global concurrency limit must be a positive integer.");
|
|
1523
|
+
}
|
|
1524
|
+
const launchSemaphore = new Semaphore(options.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT);
|
|
1152
1525
|
|
|
1153
1526
|
let acornPath: string;
|
|
1154
1527
|
try {
|
|
@@ -1162,13 +1535,15 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1162
1535
|
const trace: WorkflowScriptTraceEntry[] = [];
|
|
1163
1536
|
const children = new Map<string, WorkflowScriptChildResult>();
|
|
1164
1537
|
const childOrder: string[] = [];
|
|
1165
|
-
const launches = new Map<string, { fingerprint: string; promise: Promise<WorkflowScriptChildResult>; observed: boolean }>();
|
|
1538
|
+
const launches = new Map<string, { fingerprint: string; promise: Promise<WorkflowScriptChildResult>; observed: boolean; generatedLaneKey?: string }>();
|
|
1166
1539
|
const steers = new Map<number, { key: string; promise: Promise<WorkflowSteerResult>; observed: boolean }>();
|
|
1540
|
+
const hostCalls = new Map<number, { key: string; promise: Promise<WorkflowHostCommandResult>; observed: boolean }>();
|
|
1167
1541
|
const stoppedLaunches = new Set<string>();
|
|
1168
1542
|
const childStopControllers = new Map<string, AbortController>();
|
|
1169
1543
|
const batchAdmissions = new Map<string, Promise<void>>();
|
|
1170
1544
|
const observedRunCalls = new Set<number>();
|
|
1171
1545
|
const observedSteerCalls = new Set<number>();
|
|
1546
|
+
const observedHostCalls = new Set<number>();
|
|
1172
1547
|
const childController = new AbortController();
|
|
1173
1548
|
let settled = false;
|
|
1174
1549
|
let finishing = false;
|
|
@@ -1189,7 +1564,25 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1189
1564
|
console.error("Workflow onTrace callback failed:", error);
|
|
1190
1565
|
}
|
|
1191
1566
|
};
|
|
1567
|
+
const lanePlanChanged = (lanes: WorkflowLanePlan[]) => {
|
|
1568
|
+
try {
|
|
1569
|
+
options.onLanePlan?.(lanes);
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
console.error("Workflow onLanePlan callback failed:", error);
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
const hostStepChanged = (hostStep: HostStepNodeV1) => {
|
|
1575
|
+
try {
|
|
1576
|
+
options.onHostStep?.(hostStep);
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
console.error("Workflow onHostStep callback failed:", error);
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1192
1581
|
const stoppedChildResult = (key: string, message: string): WorkflowScriptChildResult => ({ key, ok: false, stopped: true, output: message, error: message, artifactPaths: [] });
|
|
1582
|
+
const responseBoundaryFailure = (key: string, error: unknown): WorkflowScriptChildResult => {
|
|
1583
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
1584
|
+
return { key, ok: false, output: text, error: text, artifactPaths: [] };
|
|
1585
|
+
};
|
|
1193
1586
|
const stopChild = (key: string, message = `Workflow child '${key}' stopped by user.`): boolean => {
|
|
1194
1587
|
if (!launches.has(key) || children.has(key)) return false;
|
|
1195
1588
|
stoppedLaunches.add(key);
|
|
@@ -1203,6 +1596,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1203
1596
|
...(started?.agent ? { agent: started.agent } : {}),
|
|
1204
1597
|
...(started?.phase ? { phase: started.phase } : {}),
|
|
1205
1598
|
...(started?.label ? { label: started.label } : {}),
|
|
1599
|
+
...(started?.generatedLaneKey ? { generatedLaneKey: started.generatedLaneKey } : {}),
|
|
1206
1600
|
error: message,
|
|
1207
1601
|
});
|
|
1208
1602
|
traceChanged();
|
|
@@ -1215,7 +1609,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1215
1609
|
if (settled || finishing) return;
|
|
1216
1610
|
finishing = true;
|
|
1217
1611
|
childController.abort("error" in outcome ? outcome.error : new Error("Workflow script completed."));
|
|
1218
|
-
void Promise.allSettled([...steers.values()].map(({ promise }) => promise)).then(() => {
|
|
1612
|
+
void Promise.allSettled([...steers.values(), ...hostCalls.values()].map(({ promise }) => promise)).then(() => {
|
|
1219
1613
|
if (settled) return;
|
|
1220
1614
|
settled = true;
|
|
1221
1615
|
options.registerStopChild?.(undefined);
|
|
@@ -1228,7 +1622,9 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1228
1622
|
: "value" in outcome
|
|
1229
1623
|
? (() => {
|
|
1230
1624
|
const unobservedSteers = [...steers.values()].filter((steer) => !steer.observed).map((steer) => steer.key);
|
|
1231
|
-
|
|
1625
|
+
if (unobservedSteers.length > 0) return new Error(`workflowScript completed with unawaited runs.steer call(s): ${unobservedSteers.map((key) => `'${key}'`).join(", ")}. Await or return each call.`);
|
|
1626
|
+
const unobservedHosts = [...hostCalls.values()].filter((host) => !host.observed).map((host) => host.key);
|
|
1627
|
+
return unobservedHosts.length > 0 ? new Error(`workflowScript completed with unawaited runs.host call(s): ${unobservedHosts.map((key) => `'${key}'`).join(", ")}. Await or return each call.`) : undefined;
|
|
1232
1628
|
})()
|
|
1233
1629
|
: undefined;
|
|
1234
1630
|
if ("error" in outcome) reject(new WorkflowScriptError(outcome.error.message, partial(), outcome.error.workflowErrorKind === "detached-child" || outcome.error.workflowErrorKind === "timeout" ? outcome.error.workflowErrorKind : undefined));
|
|
@@ -1254,6 +1650,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1254
1650
|
...(started?.agent ? { agent: started.agent } : {}),
|
|
1255
1651
|
...(started?.phase ? { phase: started.phase } : {}),
|
|
1256
1652
|
...(started?.label ? { label: started.label } : {}),
|
|
1653
|
+
...(started?.generatedLaneKey ? { generatedLaneKey: started.generatedLaneKey } : {}),
|
|
1257
1654
|
error: error.message,
|
|
1258
1655
|
});
|
|
1259
1656
|
}
|
|
@@ -1276,6 +1673,10 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1276
1673
|
});
|
|
1277
1674
|
worker.on("message", (message: Record<string, unknown>) => {
|
|
1278
1675
|
if (settled) return;
|
|
1676
|
+
if (message.type === "lanePlan" && Array.isArray(message.lanes)) {
|
|
1677
|
+
lanePlanChanged(message.lanes as WorkflowLanePlan[]);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1279
1680
|
if (message.type === "emit") {
|
|
1280
1681
|
try {
|
|
1281
1682
|
assertWorkflowJsonValue(message.value, "emit");
|
|
@@ -1322,12 +1723,16 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1322
1723
|
const steer = steers.get(message.callId);
|
|
1323
1724
|
if (steer) steer.observed = true;
|
|
1324
1725
|
else observedSteerCalls.add(message.callId);
|
|
1726
|
+
} else if (message.operation === "host") {
|
|
1727
|
+
const host = hostCalls.get(message.callId);
|
|
1728
|
+
if (host) host.observed = true;
|
|
1729
|
+
else observedHostCalls.add(message.callId);
|
|
1325
1730
|
}
|
|
1326
1731
|
return;
|
|
1327
1732
|
}
|
|
1328
1733
|
if (message.type !== "call" || typeof message.callId !== "number" || typeof message.method !== "string" || !isRecord(message.args)) return;
|
|
1329
1734
|
|
|
1330
|
-
const respond = (promise: Promise<unknown>, responsePath?: string) => {
|
|
1735
|
+
const respond = (promise: Promise<unknown>, responsePath?: string, onBoundaryError?: (error: unknown) => void) => {
|
|
1331
1736
|
void promise.then(
|
|
1332
1737
|
(value) => {
|
|
1333
1738
|
if (settled) return;
|
|
@@ -1340,6 +1745,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1340
1745
|
assertWorkflowJsonValue(normalized, responsePath);
|
|
1341
1746
|
worker.postMessage({ type: "response", callId: message.callId, ok: true, value: normalized });
|
|
1342
1747
|
} catch (error) {
|
|
1748
|
+
onBoundaryError?.(error);
|
|
1343
1749
|
worker.postMessage({ type: "response", callId: message.callId, ok: false, error: `${responsePath} must contain only JSON data before it can be returned from workflowScript. Return a plain projection such as { runId, ok, output }. ${error instanceof Error ? error.message : String(error)}` });
|
|
1344
1750
|
}
|
|
1345
1751
|
},
|
|
@@ -1414,6 +1820,52 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1414
1820
|
respond(promise);
|
|
1415
1821
|
return;
|
|
1416
1822
|
}
|
|
1823
|
+
if (message.method === "host") {
|
|
1824
|
+
let key: string;
|
|
1825
|
+
let params: WorkflowHostCommandParams;
|
|
1826
|
+
try {
|
|
1827
|
+
key = validateKey(message.args.key, "runs.host");
|
|
1828
|
+
params = normalizeWorkflowHostCommandParams(message.args.params, `runs.host('${key}') params`);
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
return respond(Promise.reject(error));
|
|
1831
|
+
}
|
|
1832
|
+
if (!options.host) return respond(Promise.reject(new Error("runs.host is unavailable in this host context.")));
|
|
1833
|
+
if (hostCalls.size >= HOST_STEP_MAX_COUNT) return respond(Promise.reject(new Error(`workflowScript supports at most ${HOST_STEP_MAX_COUNT} runs.host calls.`)));
|
|
1834
|
+
const startedAt = Date.now();
|
|
1835
|
+
const startedStep: HostStepNodeV1 = {
|
|
1836
|
+
version: 1,
|
|
1837
|
+
kind: "host-step",
|
|
1838
|
+
monitorKind: "command",
|
|
1839
|
+
id: key,
|
|
1840
|
+
label: key,
|
|
1841
|
+
...(params.role ? { role: params.role } : {}),
|
|
1842
|
+
...(params.provider ? { provider: params.provider } : {}),
|
|
1843
|
+
state: "running",
|
|
1844
|
+
updatedAt: startedAt,
|
|
1845
|
+
deadlineAt: startedAt + params.timeoutMs,
|
|
1846
|
+
};
|
|
1847
|
+
hostStepChanged(startedStep);
|
|
1848
|
+
trace.push({ operation: "host", key, state: "started" });
|
|
1849
|
+
traceChanged();
|
|
1850
|
+
const promise = Promise.resolve().then(() => options.host!(key, params, childController.signal)).then((result) => {
|
|
1851
|
+
const state = result.state === "stopped" ? "cancelled" : result.ok ? "done" : "error";
|
|
1852
|
+
const detail = [result.error, result.stderr.trim() || result.stdout.trim()].filter(Boolean).join(" ").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
1853
|
+
hostStepChanged({ ...startedStep, state, ...(state === "done" ? { verdict: "pass" as const } : {}), ...(!result.ok ? { reasonCode: result.state === "timed-out" ? "timed_out" : result.state === "stopped" ? "aborted" : "command_failed" } : {}), ...(detail ? { detail } : {}), reportPath: params.output ?? result.outputPath.split(/[\\/]/).at(-1), exitCode: result.exitCode, updatedAt: Date.now() });
|
|
1854
|
+
trace.push({ operation: "host", key, state: result.ok ? "completed" : result.state === "stopped" ? "stopped" : "failed", durationMs: result.durationMs, ...(!result.ok ? { error: result.error ?? "Host command failed." } : {}) });
|
|
1855
|
+
traceChanged();
|
|
1856
|
+
if (!result.ok) throw new Error(`Host command '${key}' failed: ${detail || result.error || `exit code ${result.exitCode ?? "unknown"}`}`);
|
|
1857
|
+
return result;
|
|
1858
|
+
}, (error: unknown) => {
|
|
1859
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
1860
|
+
hostStepChanged({ ...startedStep, state: "error", reasonCode: "execution_failed", detail: text.replace(/\s+/g, " ").slice(0, 200), updatedAt: Date.now() });
|
|
1861
|
+
trace.push({ operation: "host", key, state: "failed", durationMs: Date.now() - startedAt, error: text });
|
|
1862
|
+
traceChanged();
|
|
1863
|
+
throw error;
|
|
1864
|
+
});
|
|
1865
|
+
hostCalls.set(message.callId, { key, promise, observed: observedHostCalls.delete(message.callId) });
|
|
1866
|
+
respond(promise, `runs.host('${key}') result`);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1417
1869
|
if (message.method !== "run") return respond(Promise.reject(new Error(`Unknown runs API method '${message.method}'.`)));
|
|
1418
1870
|
|
|
1419
1871
|
let key: string;
|
|
@@ -1424,6 +1876,9 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1424
1876
|
}
|
|
1425
1877
|
const params = message.args.params;
|
|
1426
1878
|
if (!isRecord(params)) return respond(Promise.reject(new Error(`runs.run('${key}', params) requires a params object.`)));
|
|
1879
|
+
const generatedLaneKey = typeof message.args.generatedLaneKey === "string" && KEY_PATTERN.test(message.args.generatedLaneKey) && key.startsWith(`${message.args.generatedLaneKey}.`)
|
|
1880
|
+
? message.args.generatedLaneKey
|
|
1881
|
+
: undefined;
|
|
1427
1882
|
const collectFailure = message.args.collectFailure === true;
|
|
1428
1883
|
const callObserved = observedRunCalls.delete(message.callId);
|
|
1429
1884
|
const deliver = (promise: Promise<WorkflowScriptChildResult>) => collectFailure
|
|
@@ -1441,9 +1896,9 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1441
1896
|
if (existing) {
|
|
1442
1897
|
if (existing.fingerprint !== fingerprint) return respond(Promise.reject(new Error(`Duplicate workflow key '${key}' used with incompatible launch params.`)));
|
|
1443
1898
|
if (callObserved) existing.observed = true;
|
|
1444
|
-
trace.push({ operation: "run", key, state: "reused", ...workflowStringMetadata(params) });
|
|
1899
|
+
trace.push({ operation: "run", key, state: "reused", ...workflowStringMetadata(params), ...(existing.generatedLaneKey ? { generatedLaneKey: existing.generatedLaneKey } : {}) });
|
|
1445
1900
|
traceChanged();
|
|
1446
|
-
return respond(deliver(existing.promise), `runs.run('${key}') result
|
|
1901
|
+
return respond(deliver(existing.promise), `runs.run('${key}') result`, (error) => children.set(key, responseBoundaryFailure(key, error)));
|
|
1447
1902
|
}
|
|
1448
1903
|
const permitError = options.oneUsePermit?.claim(key);
|
|
1449
1904
|
if (permitError) return respond(Promise.reject(new Error(permitError)));
|
|
@@ -1528,13 +1983,23 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1528
1983
|
if (resolvedResumeLineage.at(-1) !== resolvedResumeId) resolvedResumeLineage.push(resolvedResumeId!);
|
|
1529
1984
|
}
|
|
1530
1985
|
const launchParams = resolvedResumeId ? { ...params, resume: resolvedResumeId } : params;
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1986
|
+
await launchSemaphore.acquire();
|
|
1987
|
+
try {
|
|
1988
|
+
if (settled || finishing || stoppedLaunches.has(key) || childSignal.aborted) {
|
|
1989
|
+
const reason = childSignal.reason;
|
|
1990
|
+
const text = children.get(key)?.error ?? (reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "Workflow script aborted.");
|
|
1991
|
+
return stoppedChildResult(key, text);
|
|
1992
|
+
}
|
|
1993
|
+
const result = await options.launch(key, launchParams, childSignal, { admitted: true, batch: batch !== undefined });
|
|
1994
|
+
const autoResumeParams = setupAbortResumeParams(params, result, childSignal);
|
|
1995
|
+
if (!autoResumeParams) return result;
|
|
1996
|
+
resolvedResumeLineage = [...new Set([...(resolvedResumeLineage ?? []), result.runId!])];
|
|
1997
|
+
trace.push({ operation: "run", key, state: "started", ...workflowStringMetadata(autoResumeParams), ...(generatedLaneKey ? { generatedLaneKey } : {}), phase: "auto-resume", runId: result.runId });
|
|
1998
|
+
traceChanged();
|
|
1999
|
+
return options.launch(key, autoResumeParams, childSignal, { admitted: true, batch: batch !== undefined });
|
|
2000
|
+
} finally {
|
|
2001
|
+
launchSemaphore.release();
|
|
2002
|
+
}
|
|
1538
2003
|
}).then((result) => {
|
|
1539
2004
|
let normalized = !result.ok && !result.error ? { ...result, error: result.output } : result;
|
|
1540
2005
|
if (resolvedResumeLineage?.length && normalized.runId) {
|
|
@@ -1544,7 +2009,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1544
2009
|
if (stoppedLaunches.has(key)) return children.get(key) ?? normalized;
|
|
1545
2010
|
children.set(key, normalized);
|
|
1546
2011
|
const state = normalized.ok ? "completed" : normalized.stopped ? "stopped" : normalized.detached ? "detached" : "failed";
|
|
1547
|
-
trace.push({ operation: "run", key, state, durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), ...(normalized.agent ? { agent: normalized.agent } : {}), ...(normalized.runId ? { runId: normalized.runId } : {}), ...(!normalized.ok ? { error: normalized.error ?? normalized.output } : {}) });
|
|
2012
|
+
trace.push({ operation: "run", key, state, durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), ...(generatedLaneKey ? { generatedLaneKey } : {}), ...(normalized.agent ? { agent: normalized.agent } : {}), ...(normalized.runId ? { runId: normalized.runId } : {}), ...(!normalized.ok ? { error: normalized.error ?? normalized.output } : {}) });
|
|
1548
2013
|
traceChanged();
|
|
1549
2014
|
return normalized;
|
|
1550
2015
|
}, (error: unknown) => {
|
|
@@ -1553,15 +2018,15 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
1553
2018
|
childStopControllers.delete(key);
|
|
1554
2019
|
if (stoppedLaunches.has(key)) return children.get(key) ?? { ...failure, stopped: true };
|
|
1555
2020
|
children.set(key, failure);
|
|
1556
|
-
trace.push({ operation: "run", key, state: "failed", durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), error: text });
|
|
2021
|
+
trace.push({ operation: "run", key, state: "failed", durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), ...(generatedLaneKey ? { generatedLaneKey } : {}), error: text });
|
|
1557
2022
|
traceChanged();
|
|
1558
2023
|
return failure;
|
|
1559
2024
|
});
|
|
1560
|
-
launches.set(key, { fingerprint, promise, observed: callObserved });
|
|
2025
|
+
launches.set(key, { fingerprint, promise, observed: callObserved, ...(generatedLaneKey ? { generatedLaneKey } : {}) });
|
|
1561
2026
|
childOrder.push(key);
|
|
1562
|
-
trace.push({ operation: "run", key, state: "started", ...workflowStringMetadata(params) });
|
|
2027
|
+
trace.push({ operation: "run", key, state: "started", ...workflowStringMetadata(params), ...(generatedLaneKey ? { generatedLaneKey } : {}) });
|
|
1563
2028
|
traceChanged();
|
|
1564
|
-
respond(deliver(promise), `runs.run('${key}') result
|
|
2029
|
+
respond(deliver(promise), `runs.run('${key}') result`, (error) => children.set(key, responseBoundaryFailure(key, error)));
|
|
1565
2030
|
});
|
|
1566
2031
|
|
|
1567
2032
|
worker.postMessage({ type: "start", script: options.script, stateEnabled: options.state !== undefined });
|