@wichayutdew/pi-workflows 2.5.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/index.js +1133 -2409
- package/examples/starter-kit/agents/planner.md +7 -0
- package/examples/starter-kit/agents/reviewer.md +6 -0
- package/examples/starter-kit/agents/scout.md +6 -0
- package/examples/starter-kit/agents/worker.md +7 -0
- package/examples/starter-kit/agents/workspace-preparer.md +6 -0
- package/examples/starter-kit/investigate.workflow.yaml +114 -0
- package/examples/starter-kit/mr-comment.workflow.yaml +114 -54
- package/examples/starter-kit/mr-review.workflow.yaml +77 -56
- package/examples/starter-kit/settings.yaml +3 -0
- package/examples/starter-kit/steps/investigate/investigate.md +77 -0
- package/examples/starter-kit/steps/investigate/retrieve.md +63 -0
- package/examples/starter-kit/steps/investigate/validate.md +46 -0
- package/examples/starter-kit/steps/mr-comment/checkout-source.md +67 -0
- package/examples/starter-kit/steps/mr-comment/fetch.md +34 -26
- package/examples/starter-kit/steps/mr-comment/implement.md +34 -27
- package/examples/starter-kit/steps/mr-comment/plan.md +54 -40
- package/examples/starter-kit/steps/mr-comment/publish.md +28 -27
- package/examples/starter-kit/steps/mr-comment/verify.md +42 -35
- package/examples/starter-kit/steps/mr-review/fetch.md +52 -22
- package/examples/starter-kit/steps/mr-review/publish-approved.md +49 -0
- package/examples/starter-kit/steps/mr-review/review-for-approval.md +142 -0
- package/examples/starter-kit/steps/mr-review/verify-published.md +37 -0
- package/examples/starter-kit/steps/shared/prepare-workspace.md +91 -73
- package/examples/starter-kit/steps/shared/publish-remote.md +45 -0
- package/examples/starter-kit/steps/ticket/implement.md +59 -25
- package/examples/starter-kit/steps/ticket/plan.md +145 -72
- package/examples/starter-kit/steps/ticket/verify.md +80 -45
- package/examples/starter-kit/steps/work/implement.md +60 -24
- package/examples/starter-kit/steps/work/plan.md +129 -57
- package/examples/starter-kit/steps/work/verify.md +58 -22
- package/examples/starter-kit/ticket.workflow.yaml +75 -36
- package/examples/starter-kit/work.workflow.yaml +63 -33
- package/package.json +3 -16
- package/schemas/workflow.schema.json +2 -15
- package/src/agents/profile.ts +98 -0
- package/src/config/ceiling.ts +0 -82
- package/src/config/types.ts +6 -53
- package/src/config/validation/settings.ts +2 -14
- package/src/config/validation/step.ts +12 -8
- package/src/config/validation/workflow.ts +1 -10
- package/src/engine/run-workflow-validation.ts +0 -3
- package/src/engine/state-types.ts +1 -1
- package/src/harness/action-context.ts +1 -13
- package/src/harness/delegation-control-actions.ts +4 -63
- package/src/harness/delegation-plan.ts +26 -80
- package/src/harness/delegation-response-actions.ts +22 -135
- package/src/harness/dependencies.ts +1 -12
- package/src/harness/status-actions.ts +1 -0
- package/src/harness/step-execution-actions.ts +2 -9
- package/src/harness/types.ts +2 -40
- package/src/harness.ts +2 -17
- package/src/index.ts +4 -9
- package/src/integrations/subagents/child-policy-validation.ts +6 -7
- package/src/integrations/subagents/child-runtime-dependencies.ts +1 -1
- package/src/integrations/subagents/child-runtime-policy.ts +1 -7
- package/src/integrations/subagents/child-runtime.ts +18 -9
- package/src/integrations/subagents/client.ts +240 -98
- package/src/integrations/subagents/protocol-events.ts +32 -15
- package/src/integrations/subagents/protocol.ts +1 -1
- package/src/preflight.ts +0 -8
- package/src/prompt/main-workflow-notice.ts +8 -15
- package/src/prompt/step-task.ts +7 -6
- package/src/workflow-status/format-status.ts +5 -1
- package/src/workflow-status/render-step-detail.ts +94 -0
- package/src/workflow-status/types.ts +1 -0
- package/src/workflow-status/view.ts +49 -14
- package/agents/step.md +0 -32
- package/examples/mr-comments.workflow.yaml +0 -125
- package/examples/prompts/mr-comments/implement.md +0 -17
- package/examples/prompts/mr-comments/inspect.md +0 -5
- package/examples/prompts/mr-comments/plan.md +0 -54
- package/examples/prompts/mr-comments/verify.md +0 -9
- package/examples/settings.yaml +0 -27
- package/examples/starter-kit/steps/mr-review/publish.md +0 -48
- package/examples/starter-kit/steps/mr-review/review.md +0 -76
- package/examples/starter-kit/steps/mr-review/verify.md +0 -35
- package/src/config/validation/subagent.ts +0 -288
- package/src/harness/delegation-failure.ts +0 -248
- package/src/harness/delegation-recovery-validation.ts +0 -161
- package/src/harness/delegation-retry-policy.ts +0 -120
- package/src/integrations/subagents/client-delegation.ts +0 -181
- package/src/integrations/subagents/client-messages.ts +0 -66
- package/src/integrations/subagents/client-types.ts +0 -36
- package/src/integrations/subagents/diagnostic-format.ts +0 -45
- package/src/integrations/subagents/diagnostic-text.ts +0 -114
- package/src/integrations/subagents/diagnostic-types.ts +0 -83
- package/src/integrations/subagents/diagnostics.ts +0 -26
- package/src/integrations/subagents/failure-correlation.ts +0 -285
- package/src/integrations/subagents/failure-transcript.ts +0 -252
- package/src/integrations/subagents/hidden-bash-failure.ts +0 -98
- package/src/integrations/subagents/replay-audit.ts +0 -146
- package/src/integrations/subagents/replay-safety.ts +0 -67
- package/src/integrations/subagents/session-diagnostics.ts +0 -357
package/dist/index.js
CHANGED
|
@@ -28,47 +28,6 @@ function bashWithinCeiling(requested, ceiling) {
|
|
|
28
28
|
const allowedRules = new Set(ceiling.allow.map(ruleKey));
|
|
29
29
|
return requested.allow.every((rule) => allowedRules.has(ruleKey(rule)));
|
|
30
30
|
}
|
|
31
|
-
function turnBudgetErrors(subagent, ceiling, path) {
|
|
32
|
-
if (!subagent.turnBudget) {
|
|
33
|
-
return [`${path}.turnBudget: required for a project workflow`];
|
|
34
|
-
}
|
|
35
|
-
return [
|
|
36
|
-
...subagent.turnBudget.maxTurns > ceiling.maxTurns ? [`${path}.turnBudget.maxTurns: exceeds the user permission ceiling`] : [],
|
|
37
|
-
...(subagent.turnBudget.graceTurns ?? 0) > ceiling.maxGraceTurns ? [`${path}.turnBudget.graceTurns: exceeds the user permission ceiling`] : []
|
|
38
|
-
];
|
|
39
|
-
}
|
|
40
|
-
function toolBudgetErrors(subagent, ceiling, path) {
|
|
41
|
-
if (!subagent.toolBudget) {
|
|
42
|
-
return [`${path}.toolBudget: required for a project workflow`];
|
|
43
|
-
}
|
|
44
|
-
return [
|
|
45
|
-
...subagent.toolBudget.hard > ceiling.maxToolCalls ? [`${path}.toolBudget.hard: exceeds the user permission ceiling`] : [],
|
|
46
|
-
...subagent.toolBudget.block !== "*" ? [`${path}.toolBudget.block: must be "*" for a project workflow`] : []
|
|
47
|
-
];
|
|
48
|
-
}
|
|
49
|
-
function subagentErrors(subagent, ceiling, path) {
|
|
50
|
-
if (!subagent)
|
|
51
|
-
return [];
|
|
52
|
-
if (!ceiling) {
|
|
53
|
-
return [`${path}: subagent execution exceeds the user permission ceiling`];
|
|
54
|
-
}
|
|
55
|
-
return [
|
|
56
|
-
...!ceiling.agents.includes(subagent.agent) ? [
|
|
57
|
-
`${path}.agent: "${subagent.agent}" exceeds the user permission ceiling`
|
|
58
|
-
] : [],
|
|
59
|
-
...!ceiling.contexts.includes(subagent.context) ? [
|
|
60
|
-
`${path}.context: "${subagent.context}" exceeds the user permission ceiling`
|
|
61
|
-
] : [],
|
|
62
|
-
...subagent.model && !ceiling.models.includes(subagent.model) ? [
|
|
63
|
-
`${path}.model: "${subagent.model}" exceeds the user permission ceiling`
|
|
64
|
-
] : [],
|
|
65
|
-
...subagent.timeoutMs > ceiling.maxTimeoutMs ? [`${path}.timeoutMs: exceeds the user permission ceiling`] : [],
|
|
66
|
-
...subagent.artifacts && !ceiling.artifacts ? [`${path}.artifacts: exceeds the user permission ceiling`] : [],
|
|
67
|
-
...subagent.retryToolFailures && !ceiling.retryToolFailures ? [`${path}.retryToolFailures: exceeds the user permission ceiling`] : [],
|
|
68
|
-
...turnBudgetErrors(subagent, ceiling, path),
|
|
69
|
-
...toolBudgetErrors(subagent, ceiling, path)
|
|
70
|
-
];
|
|
71
|
-
}
|
|
72
31
|
function checkWorkflowAgainstCeiling(workflow, ceiling) {
|
|
73
32
|
return Object.entries(workflow.steps).flatMap(([stepId, step]) => {
|
|
74
33
|
const path = `workflow.steps.${stepId}.permissions`;
|
|
@@ -80,8 +39,7 @@ function checkWorkflowAgainstCeiling(workflow, ceiling) {
|
|
|
80
39
|
...step.permissions.mcp.filter((selector) => !selectorAllowed(selector, ceiling.mcp)).map((selector) => `${path}.mcp: "${selector}" exceeds the user permission ceiling`),
|
|
81
40
|
...step.permissions.extensions.filter((extension) => !ceiling.extensions.includes(extension)).map((extension) => `${path}.extensions: "${extension}" exceeds the user permission ceiling`),
|
|
82
41
|
...step.permissions.skills.filter((skill) => !ceiling.skills.includes(skill)).map((skill) => `${path}.skills: "${skill}" exceeds the user permission ceiling`),
|
|
83
|
-
...!bashWithinCeiling(step.permissions.bash, ceiling.bash) ? [`${path}.bash: exceeds the user permission ceiling`] : []
|
|
84
|
-
...subagentErrors(step.subagent, ceiling.subagent, `workflow.steps.${stepId}.subagent`)
|
|
42
|
+
...!bashWithinCeiling(step.permissions.bash, ceiling.bash) ? [`${path}.bash: exceeds the user permission ceiling`] : []
|
|
85
43
|
];
|
|
86
44
|
});
|
|
87
45
|
}
|
|
@@ -106,7 +64,7 @@ import { join } from "node:path";
|
|
|
106
64
|
// src/config/types.ts
|
|
107
65
|
var WORKFLOW_SCHEMA_VERSION = 1;
|
|
108
66
|
var DEFAULT_STATUS_SHORTCUT = "ctrl+alt+w";
|
|
109
|
-
var
|
|
67
|
+
var AGENT_PROFILE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*$/;
|
|
110
68
|
var MAX_WORKSPACE_PATH_CHARS = 4096;
|
|
111
69
|
var MAX_WORKSPACE_ALLOWED_ROOTS = 32;
|
|
112
70
|
var EMPTY_PERMISSIONS = {
|
|
@@ -116,13 +74,6 @@ var EMPTY_PERMISSIONS = {
|
|
|
116
74
|
skills: [],
|
|
117
75
|
bash: { mode: "deny", allow: [] }
|
|
118
76
|
};
|
|
119
|
-
var DEFAULT_STEP_SUBAGENT = {
|
|
120
|
-
agent: "pi-workflows.step",
|
|
121
|
-
context: "fresh",
|
|
122
|
-
timeoutMs: 900000,
|
|
123
|
-
artifacts: false,
|
|
124
|
-
retryToolFailures: false
|
|
125
|
-
};
|
|
126
77
|
var DEFAULT_SETTINGS = {
|
|
127
78
|
version: WORKFLOW_SCHEMA_VERSION,
|
|
128
79
|
allowProjectWorkflows: false,
|
|
@@ -190,15 +141,6 @@ function readInteger(value, fallback, path, errors, limits) {
|
|
|
190
141
|
}
|
|
191
142
|
return value;
|
|
192
143
|
}
|
|
193
|
-
function readBoolean(value, fallback, path, errors) {
|
|
194
|
-
if (value === undefined)
|
|
195
|
-
return fallback;
|
|
196
|
-
if (typeof value !== "boolean") {
|
|
197
|
-
errors.push(`${path}: expected a boolean`);
|
|
198
|
-
return fallback;
|
|
199
|
-
}
|
|
200
|
-
return value;
|
|
201
|
-
}
|
|
202
144
|
function readStringList(value, path, errors, pattern) {
|
|
203
145
|
if (value === undefined)
|
|
204
146
|
return [];
|
|
@@ -437,164 +379,6 @@ function readStatusShortcut(value, path, errors) {
|
|
|
437
379
|
return isStatusShortcut(normalized) ? normalized : DEFAULT_STATUS_SHORTCUT;
|
|
438
380
|
}
|
|
439
381
|
|
|
440
|
-
// src/config/validation/subagent.ts
|
|
441
|
-
function parseSubagentTurnBudget(value, path, errors) {
|
|
442
|
-
if (value === undefined)
|
|
443
|
-
return;
|
|
444
|
-
if (!isJsonObject(value)) {
|
|
445
|
-
errors.push(`${path}: expected an object`);
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
rejectUnknownKeys(value, ["maxTurns", "graceTurns"], path, errors);
|
|
449
|
-
const maxTurns = readInteger(value.maxTurns, 0, `${path}.maxTurns`, errors, {
|
|
450
|
-
min: 1,
|
|
451
|
-
max: 1000
|
|
452
|
-
});
|
|
453
|
-
const graceTurns = value.graceTurns === undefined ? undefined : readInteger(value.graceTurns, 0, `${path}.graceTurns`, errors, {
|
|
454
|
-
min: 0,
|
|
455
|
-
max: 100
|
|
456
|
-
});
|
|
457
|
-
if (maxTurns === 0)
|
|
458
|
-
return;
|
|
459
|
-
return {
|
|
460
|
-
maxTurns,
|
|
461
|
-
...graceTurns !== undefined ? { graceTurns } : {}
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
function parseSubagentToolBudget(value, path, errors) {
|
|
465
|
-
if (value === undefined)
|
|
466
|
-
return;
|
|
467
|
-
if (!isJsonObject(value)) {
|
|
468
|
-
errors.push(`${path}: expected an object`);
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
rejectUnknownKeys(value, ["soft", "hard", "block"], path, errors);
|
|
472
|
-
const hard = readInteger(value.hard, 0, `${path}.hard`, errors, {
|
|
473
|
-
min: 1,
|
|
474
|
-
max: 1e5
|
|
475
|
-
});
|
|
476
|
-
const soft = value.soft === undefined ? undefined : readInteger(value.soft, 0, `${path}.soft`, errors, {
|
|
477
|
-
min: 1,
|
|
478
|
-
max: 1e5
|
|
479
|
-
});
|
|
480
|
-
let block;
|
|
481
|
-
if (value.block === "*") {
|
|
482
|
-
block = "*";
|
|
483
|
-
} else if (value.block !== undefined) {
|
|
484
|
-
block = readStringList(value.block, `${path}.block`, errors, TOOL_PATTERN);
|
|
485
|
-
if (block.length === 0) {
|
|
486
|
-
errors.push(`${path}.block: expected "*" or at least one tool name`);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
if (soft !== undefined && hard > 0 && soft > hard) {
|
|
490
|
-
errors.push(`${path}.soft: must not exceed hard`);
|
|
491
|
-
}
|
|
492
|
-
if (hard === 0)
|
|
493
|
-
return;
|
|
494
|
-
return {
|
|
495
|
-
hard,
|
|
496
|
-
...soft !== undefined ? { soft } : {},
|
|
497
|
-
...block !== undefined ? { block } : {}
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
function parseStepSubagent(value, path, errors) {
|
|
501
|
-
if (value === undefined)
|
|
502
|
-
return;
|
|
503
|
-
if (typeof value === "string") {
|
|
504
|
-
const agent2 = readString(value, path, errors, {
|
|
505
|
-
pattern: SUBAGENT_RUNTIME_NAME_PATTERN
|
|
506
|
-
}) ?? DEFAULT_STEP_SUBAGENT.agent;
|
|
507
|
-
return { ...DEFAULT_STEP_SUBAGENT, agent: agent2 };
|
|
508
|
-
}
|
|
509
|
-
if (!isJsonObject(value)) {
|
|
510
|
-
errors.push(`${path}: expected an agent profile name or object`);
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
rejectUnknownKeys(value, [
|
|
514
|
-
"agent",
|
|
515
|
-
"context",
|
|
516
|
-
"model",
|
|
517
|
-
"timeoutMs",
|
|
518
|
-
"turnBudget",
|
|
519
|
-
"toolBudget",
|
|
520
|
-
"artifacts",
|
|
521
|
-
"retryToolFailures"
|
|
522
|
-
], path, errors);
|
|
523
|
-
const agent = value.agent === undefined ? DEFAULT_STEP_SUBAGENT.agent : readString(value.agent, `${path}.agent`, errors, {
|
|
524
|
-
pattern: SUBAGENT_RUNTIME_NAME_PATTERN
|
|
525
|
-
}) ?? DEFAULT_STEP_SUBAGENT.agent;
|
|
526
|
-
const contextValue = value.context === undefined ? DEFAULT_STEP_SUBAGENT.context : readString(value.context, `${path}.context`, errors);
|
|
527
|
-
const context = DEFAULT_STEP_SUBAGENT.context;
|
|
528
|
-
if (contextValue !== "fresh") {
|
|
529
|
-
errors.push(`${path}.context: expected fresh`);
|
|
530
|
-
}
|
|
531
|
-
const model = value.model === undefined ? undefined : readString(value.model, `${path}.model`, errors, {
|
|
532
|
-
pattern: RESOURCE_SELECTOR_PATTERN
|
|
533
|
-
});
|
|
534
|
-
const timeoutMs = readInteger(value.timeoutMs, DEFAULT_STEP_SUBAGENT.timeoutMs, `${path}.timeoutMs`, errors, { min: 1000, max: 86400000 });
|
|
535
|
-
const turnBudget = parseSubagentTurnBudget(value.turnBudget, `${path}.turnBudget`, errors);
|
|
536
|
-
const toolBudget = parseSubagentToolBudget(value.toolBudget, `${path}.toolBudget`, errors);
|
|
537
|
-
const artifacts = readBoolean(value.artifacts, DEFAULT_STEP_SUBAGENT.artifacts, `${path}.artifacts`, errors);
|
|
538
|
-
const retryToolFailures = readBoolean(value.retryToolFailures, DEFAULT_STEP_SUBAGENT.retryToolFailures, `${path}.retryToolFailures`, errors);
|
|
539
|
-
return {
|
|
540
|
-
agent,
|
|
541
|
-
context,
|
|
542
|
-
...model ? { model } : {},
|
|
543
|
-
timeoutMs,
|
|
544
|
-
...turnBudget ? { turnBudget } : {},
|
|
545
|
-
...toolBudget ? { toolBudget } : {},
|
|
546
|
-
artifacts,
|
|
547
|
-
retryToolFailures
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
function parseSubagentPermissionCeiling(value, path, errors) {
|
|
551
|
-
if (!isJsonObject(value)) {
|
|
552
|
-
errors.push(`${path}: expected an object`);
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
rejectUnknownKeys(value, [
|
|
556
|
-
"agents",
|
|
557
|
-
"contexts",
|
|
558
|
-
"models",
|
|
559
|
-
"maxTimeoutMs",
|
|
560
|
-
"maxTurns",
|
|
561
|
-
"maxGraceTurns",
|
|
562
|
-
"maxToolCalls",
|
|
563
|
-
"artifacts",
|
|
564
|
-
"retryToolFailures"
|
|
565
|
-
], path, errors);
|
|
566
|
-
const agents = readStringList(value.agents, `${path}.agents`, errors, SUBAGENT_RUNTIME_NAME_PATTERN);
|
|
567
|
-
const contexts = readStringList(value.contexts, `${path}.contexts`, errors, /^fresh$/).filter((context) => context === "fresh");
|
|
568
|
-
const models = readStringList(value.models, `${path}.models`, errors, RESOURCE_SELECTOR_PATTERN);
|
|
569
|
-
if (agents.length === 0) {
|
|
570
|
-
errors.push(`${path}.agents: at least one subagent is required`);
|
|
571
|
-
}
|
|
572
|
-
if (contexts.length === 0) {
|
|
573
|
-
errors.push(`${path}.contexts: at least one context mode is required`);
|
|
574
|
-
}
|
|
575
|
-
[
|
|
576
|
-
"maxTimeoutMs",
|
|
577
|
-
"maxTurns",
|
|
578
|
-
"maxGraceTurns",
|
|
579
|
-
"maxToolCalls",
|
|
580
|
-
"artifacts"
|
|
581
|
-
].filter((field) => value[field] === undefined).forEach((field) => errors.push(`${path}.${field}: required`));
|
|
582
|
-
return {
|
|
583
|
-
agents,
|
|
584
|
-
contexts,
|
|
585
|
-
models,
|
|
586
|
-
maxTimeoutMs: readInteger(value.maxTimeoutMs, 0, `${path}.maxTimeoutMs`, errors, { min: 1000, max: 86400000 }),
|
|
587
|
-
maxTurns: readInteger(value.maxTurns, 0, `${path}.maxTurns`, errors, {
|
|
588
|
-
min: 1,
|
|
589
|
-
max: 1000
|
|
590
|
-
}),
|
|
591
|
-
maxGraceTurns: readInteger(value.maxGraceTurns, 0, `${path}.maxGraceTurns`, errors, { min: 0, max: 100 }),
|
|
592
|
-
maxToolCalls: readInteger(value.maxToolCalls, 0, `${path}.maxToolCalls`, errors, { min: 1, max: 1e5 }),
|
|
593
|
-
artifacts: readBoolean(value.artifacts, false, `${path}.artifacts`, errors),
|
|
594
|
-
retryToolFailures: readBoolean(value.retryToolFailures, false, `${path}.retryToolFailures`, errors)
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
|
|
598
382
|
// src/config/validation/settings.ts
|
|
599
383
|
function parsePermissionCeiling(value, path, errors) {
|
|
600
384
|
if (value === undefined)
|
|
@@ -603,7 +387,7 @@ function parsePermissionCeiling(value, path, errors) {
|
|
|
603
387
|
errors.push(`${path}: expected an object`);
|
|
604
388
|
return;
|
|
605
389
|
}
|
|
606
|
-
rejectUnknownKeys(value, ["tools", "mcp", "extensions", "skills", "bash"
|
|
390
|
+
rejectUnknownKeys(value, ["tools", "mcp", "extensions", "skills", "bash"], path, errors);
|
|
607
391
|
const permissions = parsePermissions({
|
|
608
392
|
...value.tools !== undefined ? { tools: value.tools } : {},
|
|
609
393
|
...value.mcp !== undefined ? { mcp: value.mcp } : {},
|
|
@@ -611,11 +395,7 @@ function parsePermissionCeiling(value, path, errors) {
|
|
|
611
395
|
...value.skills !== undefined ? { skills: value.skills } : {},
|
|
612
396
|
...value.bash !== undefined ? { bash: value.bash } : {}
|
|
613
397
|
}, path, errors);
|
|
614
|
-
|
|
615
|
-
return {
|
|
616
|
-
...permissions,
|
|
617
|
-
...subagent ? { subagent } : {}
|
|
618
|
-
};
|
|
398
|
+
return permissions;
|
|
619
399
|
}
|
|
620
400
|
function validateSettings(value) {
|
|
621
401
|
const errors = [];
|
|
@@ -841,7 +621,7 @@ function parseWorkflowStep(value, stepId, path, errors) {
|
|
|
841
621
|
rejectUnknownKeys(value, [
|
|
842
622
|
"title",
|
|
843
623
|
"prompt",
|
|
844
|
-
"
|
|
624
|
+
"agent",
|
|
845
625
|
"permissions",
|
|
846
626
|
"requires",
|
|
847
627
|
"transitions",
|
|
@@ -850,7 +630,10 @@ function parseWorkflowStep(value, stepId, path, errors) {
|
|
|
850
630
|
], path, errors);
|
|
851
631
|
const title = value.title === undefined ? stepId : readString(value.title, `${path}.title`, errors);
|
|
852
632
|
const prompt = parsePrompt(value.prompt, `${path}.prompt`, errors);
|
|
853
|
-
const
|
|
633
|
+
const agentName = value.agent === undefined ? undefined : readString(value.agent, `${path}.agent`, errors, {
|
|
634
|
+
pattern: AGENT_PROFILE_NAME_PATTERN
|
|
635
|
+
});
|
|
636
|
+
const agent = agentName ? { name: agentName } : undefined;
|
|
854
637
|
const permissions = parsePermissions(value.permissions, `${path}.permissions`, errors);
|
|
855
638
|
const requires = parseRequirements(value.requires, permissions, `${path}.requires`, errors);
|
|
856
639
|
const transitions = parseTransitions(value.transitions, `${path}.transitions`, errors);
|
|
@@ -879,7 +662,7 @@ function parseWorkflowStep(value, stepId, path, errors) {
|
|
|
879
662
|
return {
|
|
880
663
|
title,
|
|
881
664
|
prompt,
|
|
882
|
-
...
|
|
665
|
+
...agent ? { agent } : {},
|
|
883
666
|
permissions,
|
|
884
667
|
requires,
|
|
885
668
|
transitions,
|
|
@@ -928,9 +711,6 @@ function validateWorkspaceGraph(steps, errors) {
|
|
|
928
711
|
if (index > 0) {
|
|
929
712
|
errors.push(`${workspacePath}: only one workspace-binding step is allowed; "${firstBinderId}" also configures workspace binding`);
|
|
930
713
|
}
|
|
931
|
-
if (!step.subagent) {
|
|
932
|
-
errors.push(`${workspacePath}: workspace binding requires a subagent`);
|
|
933
|
-
}
|
|
934
714
|
if (step.gate) {
|
|
935
715
|
errors.push(`${workspacePath}: workspace binding is not allowed on a gated step`);
|
|
936
716
|
}
|
|
@@ -942,10 +722,10 @@ function validateWorkspaceGraph(steps, errors) {
|
|
|
942
722
|
}
|
|
943
723
|
return target && Object.hasOwn(steps, target) ? [target] : [];
|
|
944
724
|
});
|
|
945
|
-
validateWorkspaceDescendants(steps, downstream
|
|
725
|
+
validateWorkspaceDescendants(steps, downstream);
|
|
946
726
|
});
|
|
947
727
|
}
|
|
948
|
-
function validateWorkspaceDescendants(steps, initialStepIds
|
|
728
|
+
function validateWorkspaceDescendants(steps, initialStepIds) {
|
|
949
729
|
const pending = [...initialStepIds];
|
|
950
730
|
const visited = new Set;
|
|
951
731
|
while (pending.length > 0) {
|
|
@@ -956,9 +736,6 @@ function validateWorkspaceDescendants(steps, initialStepIds, errors) {
|
|
|
956
736
|
const step = steps[stepId];
|
|
957
737
|
if (!step)
|
|
958
738
|
continue;
|
|
959
|
-
if (!step.subagent) {
|
|
960
|
-
errors.push(`workflow.steps.${stepId}.subagent: every nonterminal step reachable after workspace binding must use a subagent`);
|
|
961
|
-
}
|
|
962
739
|
Object.values(step.transitions).forEach((target) => {
|
|
963
740
|
if (target !== "$done" && target !== "$pause" && Object.hasOwn(steps, target) && !visited.has(target)) {
|
|
964
741
|
pending.push(target);
|
|
@@ -1396,1666 +1173,129 @@ function registerHarnessCommands(pi, controller) {
|
|
|
1396
1173
|
}
|
|
1397
1174
|
}
|
|
1398
1175
|
|
|
1399
|
-
// src/
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1176
|
+
// src/harness/catalog.ts
|
|
1177
|
+
function createEmptyCatalog() {
|
|
1178
|
+
return {
|
|
1179
|
+
workflows: new Map,
|
|
1180
|
+
settings: DEFAULT_SETTINGS,
|
|
1181
|
+
diagnostics: [],
|
|
1182
|
+
userDirectory: ""
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
function formatCatalogDiagnostics(catalog) {
|
|
1186
|
+
const shownDiagnostics = catalog.diagnostics.slice(0, 3).map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`);
|
|
1187
|
+
const remainingCount = catalog.diagnostics.length - shownDiagnostics.length;
|
|
1403
1188
|
return [
|
|
1404
|
-
|
|
1405
|
-
...
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
...
|
|
1412
|
-
`Post-completion watchdog warning: ${diagnostic.postCompletionWarning}`
|
|
1413
|
-
] : [],
|
|
1414
|
-
...diagnostic.correlation === "latest-before-completion" ? [
|
|
1415
|
-
"Correlation: latest failed tool call before successful structured_output; terminal text did not identify the call"
|
|
1416
|
-
] : [],
|
|
1417
|
-
...hasSuccessfulOutputCorrelation ? [
|
|
1418
|
-
"Correlation: terminal error text came from a successful tool result before the final structured_output"
|
|
1419
|
-
] : []
|
|
1189
|
+
...shownDiagnostics,
|
|
1190
|
+
...remainingCount > 0 ? [`${remainingCount} more diagnostic(s)`] : []
|
|
1191
|
+
].join(`
|
|
1192
|
+
`);
|
|
1193
|
+
}
|
|
1194
|
+
function parseAvailableSkills(systemPrompt) {
|
|
1195
|
+
const sections = [
|
|
1196
|
+
...systemPrompt.matchAll(/<available_skills>([\s\S]*?)<\/available_skills>/g)
|
|
1420
1197
|
];
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
var
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
if (!Array.isArray(value))
|
|
1445
|
-
return;
|
|
1446
|
-
const content = value;
|
|
1447
|
-
const text = content.find((item) => isDiagnosticRecord(item) && item.type === "text" && typeof item.text === "string");
|
|
1448
|
-
return isDiagnosticRecord(text) && typeof text.text === "string" ? text.text : undefined;
|
|
1449
|
-
};
|
|
1450
|
-
var diagnosticToolCallText = ({
|
|
1451
|
-
tool,
|
|
1452
|
-
argumentsValue
|
|
1453
|
-
}) => {
|
|
1454
|
-
if (!isDiagnosticRecord(argumentsValue))
|
|
1455
|
-
return;
|
|
1456
|
-
if (tool === "bash") {
|
|
1457
|
-
const command = argumentsValue.command ?? argumentsValue.cmd;
|
|
1458
|
-
if (typeof command === "string" && command.trim()) {
|
|
1459
|
-
return boundedDiagnosticText(command.trim());
|
|
1460
|
-
}
|
|
1198
|
+
const section = sections.at(-1)?.[1] ?? "";
|
|
1199
|
+
return [...section.matchAll(/<name>([^<]+)<\/name>/g)].flatMap((match) => {
|
|
1200
|
+
const name = match[1]?.trim();
|
|
1201
|
+
return name ? [{ name }] : [];
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// src/harness/dependencies.ts
|
|
1206
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
1207
|
+
import { constants as constants2, mkdtempSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1208
|
+
import { lstat as lstat2, open as open2, rm } from "node:fs/promises";
|
|
1209
|
+
import { tmpdir } from "node:os";
|
|
1210
|
+
import { join as join4 } from "node:path";
|
|
1211
|
+
|
|
1212
|
+
// src/integrations/plannotator-responses.ts
|
|
1213
|
+
var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1214
|
+
var errorText = (value, fallback) => typeof value.error === "string" && value.error.trim() ? value.error : fallback;
|
|
1215
|
+
var normalizePlannotatorStartResponse = (value) => {
|
|
1216
|
+
if (!isRecord(value)) {
|
|
1217
|
+
return {
|
|
1218
|
+
status: "error",
|
|
1219
|
+
error: "Plannotator returned an invalid response"
|
|
1220
|
+
};
|
|
1461
1221
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
if (Object.keys(argumentsValue).length !== 1 || !Object.hasOwn(argumentsValue, "value") || !isDiagnosticRecord(argumentsValue.value)) {
|
|
1468
|
-
return;
|
|
1222
|
+
if (value.status === "unavailable") {
|
|
1223
|
+
return {
|
|
1224
|
+
status: "unavailable",
|
|
1225
|
+
error: errorText(value, "Plannotator is unavailable")
|
|
1226
|
+
};
|
|
1469
1227
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
const lines = value.split(/\r?\n/).map(normalizeDiagnosticText).filter((line) => line.length >= 8);
|
|
1476
|
-
return [...new Set([normalizedValue, ...lines])].filter((fragment) => fragment.length >= 8);
|
|
1477
|
-
};
|
|
1478
|
-
|
|
1479
|
-
// src/integrations/subagents/failure-transcript.ts
|
|
1480
|
-
var WATCHDOG_WARNING_TYPE = "subagent_watchdog_warning";
|
|
1481
|
-
var MAX_WARNING_FIELD_CHARS = 600;
|
|
1482
|
-
var boundedWarningField = (value) => {
|
|
1483
|
-
const normalized = value.trim().replaceAll(/\s+/g, " ");
|
|
1484
|
-
return normalized.length <= MAX_WARNING_FIELD_CHARS ? normalized : `${normalized.slice(0, MAX_WARNING_FIELD_CHARS - 1)}…`;
|
|
1485
|
-
};
|
|
1486
|
-
var transcriptWarningContent = (entry) => {
|
|
1487
|
-
if (entry.type !== "custom_message" || entry.customType !== WATCHDOG_WARNING_TYPE) {
|
|
1488
|
-
return;
|
|
1228
|
+
if (value.status === "error") {
|
|
1229
|
+
return {
|
|
1230
|
+
status: "error",
|
|
1231
|
+
error: errorText(value, "Plannotator failed")
|
|
1232
|
+
};
|
|
1489
1233
|
}
|
|
1490
|
-
const
|
|
1491
|
-
if (
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
].filter((field) => Boolean(field?.trim()));
|
|
1497
|
-
if (fields.length > 0)
|
|
1498
|
-
return boundedWarningField(fields.join(" "));
|
|
1499
|
-
}
|
|
1500
|
-
return typeof entry.content === "string" && entry.content.trim() ? boundedWarningField(entry.content) : "The child emitted an unresolved watchdog warning after completion.";
|
|
1501
|
-
};
|
|
1502
|
-
var isBenignTerminalAssistant = (message) => message.stopReason === "stop" && message.errorMessage === undefined && Array.isArray(message.content) && message.content.length === 1 && isDiagnosticRecord(message.content[0]) && message.content[0].type === "text" && message.content[0].text === "";
|
|
1503
|
-
var parseFailureTranscript = (transcript) => {
|
|
1504
|
-
const calls = new Map;
|
|
1505
|
-
const recordedCalls = [];
|
|
1506
|
-
const diagnostics = [];
|
|
1507
|
-
const successfulResults = [];
|
|
1508
|
-
const successfulCompletions = [];
|
|
1509
|
-
const transcriptWarnings = [];
|
|
1510
|
-
const recordedMessages = [];
|
|
1511
|
-
const resultCallIds = new Set;
|
|
1512
|
-
let hasValidFalsePositiveProof = true;
|
|
1513
|
-
let lastInteractionOrder = 0;
|
|
1514
|
-
let order = 0;
|
|
1515
|
-
for (const line of transcript.split(`
|
|
1516
|
-
`)) {
|
|
1517
|
-
order += 1;
|
|
1518
|
-
if (!line.trim())
|
|
1519
|
-
continue;
|
|
1520
|
-
let entry;
|
|
1521
|
-
try {
|
|
1522
|
-
entry = JSON.parse(line);
|
|
1523
|
-
} catch {
|
|
1524
|
-
hasValidFalsePositiveProof = false;
|
|
1525
|
-
continue;
|
|
1526
|
-
}
|
|
1527
|
-
if (isDiagnosticRecord(entry)) {
|
|
1528
|
-
const warning = transcriptWarningContent(entry);
|
|
1529
|
-
if (warning) {
|
|
1530
|
-
transcriptWarnings.push({ order, content: warning });
|
|
1531
|
-
lastInteractionOrder = order;
|
|
1532
|
-
continue;
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
if (!isDiagnosticRecord(entry) || entry.type !== "message")
|
|
1536
|
-
continue;
|
|
1537
|
-
const message = entry.message;
|
|
1538
|
-
if (!isDiagnosticRecord(message)) {
|
|
1539
|
-
hasValidFalsePositiveProof = false;
|
|
1540
|
-
continue;
|
|
1541
|
-
}
|
|
1542
|
-
recordedMessages.push({ order, value: message });
|
|
1543
|
-
if (message.role === "assistant" && (typeof message.errorMessage === "string" && message.errorMessage.trim().length > 0 || message.stopReason === "error" || message.stopReason === "aborted")) {
|
|
1544
|
-
hasValidFalsePositiveProof = false;
|
|
1545
|
-
}
|
|
1546
|
-
if (message.role === "assistant") {
|
|
1547
|
-
if (!Array.isArray(message.content)) {
|
|
1548
|
-
hasValidFalsePositiveProof = false;
|
|
1549
|
-
continue;
|
|
1550
|
-
}
|
|
1551
|
-
if (!isBenignTerminalAssistant(message)) {
|
|
1552
|
-
lastInteractionOrder = order;
|
|
1553
|
-
}
|
|
1554
|
-
const toolCalls = message.content.filter((item) => isDiagnosticRecord(item) && item.type === "toolCall" && typeof item.id === "string" && typeof item.name === "string");
|
|
1555
|
-
for (const item of message.content) {
|
|
1556
|
-
if (!isDiagnosticRecord(item) || item.type !== "toolCall" || typeof item.id !== "string" || typeof item.name !== "string") {
|
|
1557
|
-
if (isDiagnosticRecord(item) && item.type === "toolCall") {
|
|
1558
|
-
hasValidFalsePositiveProof = false;
|
|
1559
|
-
}
|
|
1560
|
-
continue;
|
|
1561
|
-
}
|
|
1562
|
-
if (calls.has(item.id))
|
|
1563
|
-
hasValidFalsePositiveProof = false;
|
|
1564
|
-
const call = diagnosticToolCallText({
|
|
1565
|
-
tool: item.name,
|
|
1566
|
-
argumentsValue: item.arguments
|
|
1567
|
-
});
|
|
1568
|
-
const isExclusiveCompletion = toolCalls.length === 1 && message.content.every((contentItem) => isDiagnosticRecord(contentItem) && (contentItem.type === "thinking" || contentItem.type === "text" || contentItem.type === "toolCall"));
|
|
1569
|
-
const completionValue = item.name === "structured_output" && isExclusiveCompletion ? structuredCompletionValue(item.arguments) : undefined;
|
|
1570
|
-
const recordedCall = {
|
|
1571
|
-
id: item.id,
|
|
1572
|
-
order,
|
|
1573
|
-
tool: item.name,
|
|
1574
|
-
...call ? { call } : {},
|
|
1575
|
-
...completionValue ? { completionValue } : {}
|
|
1576
|
-
};
|
|
1577
|
-
calls.set(item.id, recordedCall);
|
|
1578
|
-
recordedCalls.push(recordedCall);
|
|
1579
|
-
}
|
|
1580
|
-
continue;
|
|
1581
|
-
}
|
|
1582
|
-
if (message.role !== "toolResult" || typeof message.toolName !== "string") {
|
|
1583
|
-
if (message.role === "toolResult")
|
|
1584
|
-
hasValidFalsePositiveProof = false;
|
|
1585
|
-
continue;
|
|
1586
|
-
}
|
|
1587
|
-
lastInteractionOrder = order;
|
|
1588
|
-
if (typeof message.toolCallId !== "string" || typeof message.isError !== "boolean" || !Array.isArray(message.content) || resultCallIds.has(message.toolCallId)) {
|
|
1589
|
-
hasValidFalsePositiveProof = false;
|
|
1590
|
-
}
|
|
1591
|
-
if (typeof message.toolCallId === "string") {
|
|
1592
|
-
resultCallIds.add(message.toolCallId);
|
|
1593
|
-
}
|
|
1594
|
-
const recorded = typeof message.toolCallId === "string" ? calls.get(message.toolCallId) : undefined;
|
|
1595
|
-
const doesCallMatchResult = recorded?.tool === message.toolName;
|
|
1596
|
-
if (!doesCallMatchResult)
|
|
1597
|
-
hasValidFalsePositiveProof = false;
|
|
1598
|
-
if (message.toolName === "structured_output" && message.isError === false && doesCallMatchResult && recorded.completionValue) {
|
|
1599
|
-
successfulCompletions.push({
|
|
1600
|
-
order,
|
|
1601
|
-
value: recorded.completionValue
|
|
1602
|
-
});
|
|
1603
|
-
}
|
|
1604
|
-
if (message.isError === false && message.toolName !== "structured_output" && doesCallMatchResult) {
|
|
1605
|
-
const output2 = diagnosticTextContent(message.content);
|
|
1606
|
-
const detectorOutput = firstDiagnosticTextContent(message.content);
|
|
1607
|
-
successfulResults.push({
|
|
1608
|
-
order,
|
|
1609
|
-
tool: message.toolName,
|
|
1610
|
-
...recorded.call ? { call: recorded.call } : {},
|
|
1611
|
-
...output2 ? { output: output2 } : {},
|
|
1612
|
-
...detectorOutput !== undefined ? { detectorOutput } : {}
|
|
1613
|
-
});
|
|
1614
|
-
}
|
|
1615
|
-
if (message.isError !== true)
|
|
1616
|
-
continue;
|
|
1617
|
-
const output = diagnosticTextContent(message.content);
|
|
1618
|
-
diagnostics.push({
|
|
1619
|
-
tool: message.toolName,
|
|
1620
|
-
...doesCallMatchResult && recorded.call ? { call: recorded.call } : {},
|
|
1621
|
-
...output ? { output } : {},
|
|
1622
|
-
...doesCallMatchResult && typeof message.toolCallId === "string" ? { callId: message.toolCallId } : {},
|
|
1623
|
-
order
|
|
1624
|
-
});
|
|
1234
|
+
const result = isRecord(value.result) ? value.result : undefined;
|
|
1235
|
+
if (value.status === "handled" && result?.status === "pending" && typeof result.reviewId === "string") {
|
|
1236
|
+
return {
|
|
1237
|
+
status: "handled",
|
|
1238
|
+
result: { status: "pending", reviewId: result.reviewId }
|
|
1239
|
+
};
|
|
1625
1240
|
}
|
|
1626
1241
|
return {
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
successfulResults,
|
|
1630
|
-
successfulCompletions,
|
|
1631
|
-
transcriptWarnings,
|
|
1632
|
-
recordedMessages,
|
|
1633
|
-
resultCallIds,
|
|
1634
|
-
hasValidFalsePositiveProof,
|
|
1635
|
-
lastInteractionOrder
|
|
1242
|
+
status: "error",
|
|
1243
|
+
error: "Plannotator returned an invalid start result"
|
|
1636
1244
|
};
|
|
1637
1245
|
};
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
if (!recordedMessage || message?.role !== "toolResult" || message.toolName !== "bash" || message.isError !== false) {
|
|
1674
|
-
continue;
|
|
1246
|
+
var normalizePlannotatorStatusResponse = (value, requestedReviewId) => {
|
|
1247
|
+
if (!isRecord(value)) {
|
|
1248
|
+
return {
|
|
1249
|
+
status: "error",
|
|
1250
|
+
error: "Plannotator returned an invalid response"
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
if (value.status === "unavailable") {
|
|
1254
|
+
return {
|
|
1255
|
+
status: "unavailable",
|
|
1256
|
+
error: errorText(value, "Plannotator is unavailable")
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
if (value.status === "error") {
|
|
1260
|
+
return {
|
|
1261
|
+
status: "error",
|
|
1262
|
+
error: errorText(value, "Plannotator failed")
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
const result = isRecord(value.result) ? value.result : undefined;
|
|
1266
|
+
if (value.status !== "handled" || !result) {
|
|
1267
|
+
return {
|
|
1268
|
+
status: "error",
|
|
1269
|
+
error: "Plannotator returned an invalid status result"
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
if (result.status === "pending" || result.status === "missing") {
|
|
1273
|
+
return { status: "handled", result: { status: result.status } };
|
|
1274
|
+
}
|
|
1275
|
+
if (result.status === "completed" && typeof result.reviewId === "string" && typeof result.approved === "boolean") {
|
|
1276
|
+
if (result.reviewId !== requestedReviewId) {
|
|
1277
|
+
return {
|
|
1278
|
+
status: "error",
|
|
1279
|
+
error: "Plannotator returned a result for a different review"
|
|
1280
|
+
};
|
|
1675
1281
|
}
|
|
1676
|
-
const output = firstDiagnosticTextContent(message.content);
|
|
1677
|
-
if (output === undefined)
|
|
1678
|
-
continue;
|
|
1679
|
-
const detectedExitCode = hiddenExitCode(output);
|
|
1680
|
-
if (detectedExitCode === undefined)
|
|
1681
|
-
continue;
|
|
1682
|
-
const result = successfulResults.find((candidate) => candidate.order === recordedMessage.order && candidate.tool === "bash" && candidate.detectorOutput === output);
|
|
1683
|
-
if (!result)
|
|
1684
|
-
return;
|
|
1685
1282
|
return {
|
|
1686
|
-
|
|
1687
|
-
|
|
1283
|
+
status: "handled",
|
|
1284
|
+
result: {
|
|
1285
|
+
status: "completed",
|
|
1286
|
+
reviewId: result.reviewId,
|
|
1287
|
+
approved: result.approved,
|
|
1288
|
+
feedback: typeof result.feedback === "string" ? result.feedback : ""
|
|
1289
|
+
}
|
|
1688
1290
|
};
|
|
1689
1291
|
}
|
|
1690
|
-
return
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
import { basename } from "node:path";
|
|
1695
|
-
|
|
1696
|
-
// src/policy/restricted-command.ts
|
|
1697
|
-
var UNQUOTED_SHELL_METACHARACTERS = new Set([
|
|
1698
|
-
";",
|
|
1699
|
-
"&",
|
|
1700
|
-
"|",
|
|
1701
|
-
"<",
|
|
1702
|
-
">",
|
|
1703
|
-
`
|
|
1704
|
-
`,
|
|
1705
|
-
"\r",
|
|
1706
|
-
"`",
|
|
1707
|
-
"$",
|
|
1708
|
-
"(",
|
|
1709
|
-
")",
|
|
1710
|
-
"{",
|
|
1711
|
-
"}",
|
|
1712
|
-
"#",
|
|
1713
|
-
"\x00"
|
|
1714
|
-
]);
|
|
1715
|
-
var PATHNAME_EXPANSION_CHARACTERS = new Set([
|
|
1716
|
-
"*",
|
|
1717
|
-
"?",
|
|
1718
|
-
"[",
|
|
1719
|
-
"]",
|
|
1720
|
-
"~"
|
|
1721
|
-
]);
|
|
1722
|
-
var invalidCharacter = (character) => {
|
|
1723
|
-
if (character === `
|
|
1724
|
-
` || character === "\r" || character === "\x00") {
|
|
1725
|
-
return "multiline and null characters are not allowed";
|
|
1726
|
-
}
|
|
1727
|
-
return;
|
|
1728
|
-
};
|
|
1729
|
-
var tokenizeRestrictedCommand = (command) => {
|
|
1730
|
-
if (!command.trim())
|
|
1731
|
-
return { error: "empty Bash command" };
|
|
1732
|
-
const tokens = [];
|
|
1733
|
-
let token = "";
|
|
1734
|
-
let quote;
|
|
1735
|
-
let isEscaping = false;
|
|
1736
|
-
let isTokenStarted = false;
|
|
1737
|
-
for (const character of command) {
|
|
1738
|
-
if (quote === "'") {
|
|
1739
|
-
const error = invalidCharacter(character);
|
|
1740
|
-
if (error)
|
|
1741
|
-
return { error };
|
|
1742
|
-
if (character === "'") {
|
|
1743
|
-
quote = undefined;
|
|
1744
|
-
} else {
|
|
1745
|
-
token += character;
|
|
1746
|
-
}
|
|
1747
|
-
isTokenStarted = true;
|
|
1748
|
-
continue;
|
|
1749
|
-
}
|
|
1750
|
-
if (quote === '"') {
|
|
1751
|
-
const error = invalidCharacter(character);
|
|
1752
|
-
if (error)
|
|
1753
|
-
return { error };
|
|
1754
|
-
if (character === '"') {
|
|
1755
|
-
quote = undefined;
|
|
1756
|
-
} else if (character === "$" || character === "`" || character === "\\") {
|
|
1757
|
-
return {
|
|
1758
|
-
error: "substitutions and escapes are not allowed inside double quotes"
|
|
1759
|
-
};
|
|
1760
|
-
} else {
|
|
1761
|
-
token += character;
|
|
1762
|
-
}
|
|
1763
|
-
isTokenStarted = true;
|
|
1764
|
-
continue;
|
|
1765
|
-
}
|
|
1766
|
-
if (isEscaping) {
|
|
1767
|
-
const error = invalidCharacter(character);
|
|
1768
|
-
if (error)
|
|
1769
|
-
return { error };
|
|
1770
|
-
token += character;
|
|
1771
|
-
isEscaping = false;
|
|
1772
|
-
isTokenStarted = true;
|
|
1773
|
-
continue;
|
|
1774
|
-
}
|
|
1775
|
-
if (character === "\\") {
|
|
1776
|
-
isEscaping = true;
|
|
1777
|
-
isTokenStarted = true;
|
|
1778
|
-
continue;
|
|
1779
|
-
}
|
|
1780
|
-
if (character === "'" || character === '"') {
|
|
1781
|
-
quote = character;
|
|
1782
|
-
isTokenStarted = true;
|
|
1783
|
-
continue;
|
|
1784
|
-
}
|
|
1785
|
-
if (UNQUOTED_SHELL_METACHARACTERS.has(character)) {
|
|
1786
|
-
return {
|
|
1787
|
-
error: "shell operators, substitutions, expansions, and comments are not allowed"
|
|
1788
|
-
};
|
|
1789
|
-
}
|
|
1790
|
-
if (PATHNAME_EXPANSION_CHARACTERS.has(character)) {
|
|
1791
|
-
return {
|
|
1792
|
-
error: "unquoted pathname and tilde expansion are not allowed"
|
|
1793
|
-
};
|
|
1794
|
-
}
|
|
1795
|
-
if (/\s/u.test(character)) {
|
|
1796
|
-
if (isTokenStarted) {
|
|
1797
|
-
tokens.push(token);
|
|
1798
|
-
token = "";
|
|
1799
|
-
isTokenStarted = false;
|
|
1800
|
-
}
|
|
1801
|
-
continue;
|
|
1802
|
-
}
|
|
1803
|
-
token += character;
|
|
1804
|
-
isTokenStarted = true;
|
|
1805
|
-
}
|
|
1806
|
-
if (isEscaping)
|
|
1807
|
-
return { error: "trailing Bash escape is not allowed" };
|
|
1808
|
-
if (quote)
|
|
1809
|
-
return { error: "unterminated Bash quote" };
|
|
1810
|
-
if (isTokenStarted)
|
|
1811
|
-
tokens.push(token);
|
|
1812
|
-
return tokens.length > 0 ? { tokens } : { error: "empty Bash command" };
|
|
1813
|
-
};
|
|
1814
|
-
|
|
1815
|
-
// src/policy/bash-authorization.ts
|
|
1816
|
-
var SHELL_WRAPPERS = new Set([
|
|
1817
|
-
"bash",
|
|
1818
|
-
"builtin",
|
|
1819
|
-
"command",
|
|
1820
|
-
"env",
|
|
1821
|
-
"exec",
|
|
1822
|
-
"fish",
|
|
1823
|
-
"sh",
|
|
1824
|
-
"time",
|
|
1825
|
-
"xargs",
|
|
1826
|
-
"zsh"
|
|
1827
|
-
]);
|
|
1828
|
-
var ENVIRONMENT_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/u;
|
|
1829
|
-
var reject = (reason) => ({
|
|
1830
|
-
allowed: false,
|
|
1831
|
-
reason
|
|
1832
|
-
});
|
|
1833
|
-
var matchesRule = (tokens, rule) => tokens[0] === rule.executable && rule.argsPrefix.every((expected, index) => tokens[index + 1] === expected);
|
|
1834
|
-
var authorizeBash = (command, permission) => {
|
|
1835
|
-
if (permission.mode === "unrestricted") {
|
|
1836
|
-
return { allowed: true };
|
|
1837
|
-
}
|
|
1838
|
-
if (permission.mode === "deny") {
|
|
1839
|
-
return reject("Bash is disabled for this workflow step");
|
|
1840
|
-
}
|
|
1841
|
-
const parsed = tokenizeRestrictedCommand(command);
|
|
1842
|
-
if (!parsed.tokens)
|
|
1843
|
-
return reject(parsed.error);
|
|
1844
|
-
const executable = parsed.tokens[0] ?? "";
|
|
1845
|
-
if (SHELL_WRAPPERS.has(basename(executable))) {
|
|
1846
|
-
return reject(`shell wrapper "${executable}" is not allowed in restricted mode`);
|
|
1847
|
-
}
|
|
1848
|
-
if (ENVIRONMENT_ASSIGNMENT.test(executable)) {
|
|
1849
|
-
return reject("environment assignments are not allowed in restricted mode");
|
|
1850
|
-
}
|
|
1851
|
-
const rule = permission.allow.find((candidate) => matchesRule(parsed.tokens, candidate));
|
|
1852
|
-
if (!rule) {
|
|
1853
|
-
return reject("command does not match this step's Bash allow-list");
|
|
1854
|
-
}
|
|
1855
|
-
return { allowed: true, tokens: [...parsed.tokens] };
|
|
1856
|
-
};
|
|
1857
|
-
// src/integrations/subagents/replay-safety.ts
|
|
1858
|
-
var REPLAY_SAFE_TOOLS = new Set([
|
|
1859
|
-
"find",
|
|
1860
|
-
"grep",
|
|
1861
|
-
"ls",
|
|
1862
|
-
"read",
|
|
1863
|
-
"structured_output"
|
|
1864
|
-
]);
|
|
1865
|
-
var isPreExecutionBashFailure = (output, rejectionReason) => Boolean(output && rejectionReason && output.toLowerCase().includes(rejectionReason.toLowerCase()));
|
|
1866
|
-
var isReplaySafeToolCall = ({
|
|
1867
|
-
call,
|
|
1868
|
-
diagnostics,
|
|
1869
|
-
bashPermission
|
|
1870
|
-
}) => {
|
|
1871
|
-
const tool = call.tool.toLowerCase();
|
|
1872
|
-
if (REPLAY_SAFE_TOOLS.has(tool))
|
|
1873
|
-
return true;
|
|
1874
|
-
if (tool !== "bash" || !call.call)
|
|
1875
|
-
return false;
|
|
1876
|
-
if (!bashPermission) {
|
|
1877
|
-
return false;
|
|
1878
|
-
}
|
|
1879
|
-
const authorization = authorizeBash(call.call, bashPermission);
|
|
1880
|
-
if (authorization.allowed)
|
|
1881
|
-
return false;
|
|
1882
|
-
const failure = diagnostics.find((diagnostic) => diagnostic.callId === call.id);
|
|
1883
|
-
return isPreExecutionBashFailure(failure?.output, authorization.reason);
|
|
1884
|
-
};
|
|
1885
|
-
var isFailureTranscriptReplaySafe = ({
|
|
1886
|
-
calls,
|
|
1887
|
-
diagnostics,
|
|
1888
|
-
isCompleteTranscript
|
|
1889
|
-
}) => isCompleteTranscript && calls.length > 0 && calls.every((call) => isReplaySafeToolCall({ call, diagnostics }));
|
|
1890
|
-
|
|
1891
|
-
// src/integrations/subagents/failure-correlation.ts
|
|
1892
|
-
var diagnosticMatchesTerminalError = (diagnostic, terminalError) => {
|
|
1893
|
-
if (!diagnostic.output)
|
|
1894
|
-
return false;
|
|
1895
|
-
const detail = terminalError.match(/\b[a-z][\w-]* failed(?:\s*\([^)]*\))?\s*:\s*([\s\S]+)/i)?.[1] ?? terminalError;
|
|
1896
|
-
const outputFragments = comparableDiagnosticFragments(diagnostic.output);
|
|
1897
|
-
const errorFragments = comparableDiagnosticFragments(`${terminalError}
|
|
1898
|
-
${detail}`);
|
|
1899
|
-
return outputFragments.some((output) => errorFragments.some((error) => output.includes(error) || error.includes(output)));
|
|
1900
|
-
};
|
|
1901
|
-
var latestMatching = (diagnostics, predicate) => {
|
|
1902
|
-
for (let index = diagnostics.length - 1;index >= 0; index -= 1) {
|
|
1903
|
-
const diagnostic = diagnostics[index];
|
|
1904
|
-
if (diagnostic && predicate(diagnostic))
|
|
1905
|
-
return diagnostic;
|
|
1906
|
-
}
|
|
1907
|
-
return;
|
|
1908
|
-
};
|
|
1909
|
-
var latestWarningAfter = (warnings, order) => {
|
|
1910
|
-
for (let index = warnings.length - 1;index >= 0; index -= 1) {
|
|
1911
|
-
const warning = warnings[index];
|
|
1912
|
-
if (warning && warning.order > order)
|
|
1913
|
-
return warning;
|
|
1914
|
-
}
|
|
1915
|
-
return;
|
|
1916
|
-
};
|
|
1917
|
-
var finalCompletion = (completions, latestFailureOrder, lastInteractionOrder, allowCompletionProof) => {
|
|
1918
|
-
if (!allowCompletionProof || completions.length !== 1)
|
|
1919
|
-
return;
|
|
1920
|
-
const completion = completions[0];
|
|
1921
|
-
return completion && completion.order > latestFailureOrder && completion.order === lastInteractionOrder ? completion : undefined;
|
|
1922
|
-
};
|
|
1923
|
-
var publicDiagnostic = (diagnostic, diagnostics, recordedCalls, successfulCompletions, postCompletionWarning, lastInteractionOrder, allowCompletionProof, correlation) => {
|
|
1924
|
-
const result = {
|
|
1925
|
-
tool: diagnostic.tool,
|
|
1926
|
-
...diagnostic.call ? { call: diagnostic.call } : {},
|
|
1927
|
-
...diagnostic.output ? { output: diagnostic.output } : {}
|
|
1928
|
-
};
|
|
1929
|
-
const latestFailureOrder = diagnostics.at(-1)?.order ?? diagnostic.order;
|
|
1930
|
-
const completion = finalCompletion(successfulCompletions, latestFailureOrder, lastInteractionOrder, allowCompletionProof);
|
|
1931
|
-
return {
|
|
1932
|
-
...result,
|
|
1933
|
-
...postCompletionWarning ? { postCompletionWarning: postCompletionWarning.content } : {},
|
|
1934
|
-
...isFailureTranscriptReplaySafe({
|
|
1935
|
-
calls: recordedCalls,
|
|
1936
|
-
diagnostics,
|
|
1937
|
-
isCompleteTranscript: allowCompletionProof
|
|
1938
|
-
}) ? { replaySafe: true } : {},
|
|
1939
|
-
...completion ? {
|
|
1940
|
-
completionAfterFailure: true,
|
|
1941
|
-
completionValue: completion.value
|
|
1942
|
-
} : {},
|
|
1943
|
-
...correlation ? { correlation } : {}
|
|
1944
|
-
};
|
|
1945
|
-
};
|
|
1946
|
-
var hiddenFalsePositiveDiagnostic = (expectedTool, terminalError, allowCompletionProof, transcript) => {
|
|
1947
|
-
const {
|
|
1948
|
-
diagnostics,
|
|
1949
|
-
hasValidFalsePositiveProof,
|
|
1950
|
-
lastInteractionOrder,
|
|
1951
|
-
recordedCalls,
|
|
1952
|
-
recordedMessages,
|
|
1953
|
-
resultCallIds,
|
|
1954
|
-
successfulCompletions,
|
|
1955
|
-
successfulResults,
|
|
1956
|
-
transcriptWarnings
|
|
1957
|
-
} = transcript;
|
|
1958
|
-
const completionOrder = successfulCompletions.at(-1)?.order ?? 0;
|
|
1959
|
-
const postCompletionWarning = latestWarningAfter(transcriptWarnings, completionOrder);
|
|
1960
|
-
const falsePositive = reproduceHiddenBashFalsePositive(recordedMessages, successfulResults);
|
|
1961
|
-
const hasLaterFailure = falsePositive !== undefined && diagnostics.some((diagnostic) => diagnostic.order > falsePositive.result.order);
|
|
1962
|
-
const completion = falsePositive ? finalCompletion(successfulCompletions, Math.max(falsePositive.result.order, diagnostics.at(-1)?.order ?? falsePositive.result.order), lastInteractionOrder, allowCompletionProof) : undefined;
|
|
1963
|
-
if (!hasValidFalsePositiveProof || recordedCalls.length !== resultCallIds.size || !recordedCalls.every((call) => resultCallIds.has(call.id)) || recordedCalls.filter((call) => call.tool === "structured_output").length !== 1 || expectedTool?.toLowerCase() !== "bash" || !falsePositive || hasLaterFailure || postCompletionWarning !== undefined || terminalError !== falsePositive.terminalError || !completion) {
|
|
1964
|
-
return;
|
|
1965
|
-
}
|
|
1966
|
-
const successfulOutput = falsePositive.result;
|
|
1967
|
-
return {
|
|
1968
|
-
tool: successfulOutput.tool,
|
|
1969
|
-
...successfulOutput.call ? { call: successfulOutput.call } : {},
|
|
1970
|
-
...successfulOutput.output ? { output: successfulOutput.output } : {},
|
|
1971
|
-
completionAfterFailure: true,
|
|
1972
|
-
completionValue: completion.value,
|
|
1973
|
-
transcriptToolCount: recordedCalls.length,
|
|
1974
|
-
transcriptTurnCount: recordedMessages.filter(({ value }) => value.role === "assistant").length,
|
|
1975
|
-
correlation: "successful-output-before-completion"
|
|
1976
|
-
};
|
|
1977
|
-
};
|
|
1978
|
-
var parseToolFailureDiagnostic = (transcript, expectedTool, terminalError, allowCompletionProof = true) => {
|
|
1979
|
-
const parsed = parseFailureTranscript(transcript);
|
|
1980
|
-
const {
|
|
1981
|
-
diagnostics,
|
|
1982
|
-
lastInteractionOrder,
|
|
1983
|
-
recordedCalls,
|
|
1984
|
-
successfulCompletions,
|
|
1985
|
-
transcriptWarnings
|
|
1986
|
-
} = parsed;
|
|
1987
|
-
const completionOrder = successfulCompletions.at(-1)?.order ?? 0;
|
|
1988
|
-
const postCompletionWarning = latestWarningAfter(transcriptWarnings, completionOrder);
|
|
1989
|
-
const matchesExpectedTool = (diagnostic) => expectedTool === undefined || diagnostic.tool.toLowerCase() === expectedTool.toLowerCase();
|
|
1990
|
-
let selected;
|
|
1991
|
-
if (terminalError) {
|
|
1992
|
-
selected = latestMatching(diagnostics, (diagnostic) => matchesExpectedTool(diagnostic) && diagnosticMatchesTerminalError(diagnostic, terminalError));
|
|
1993
|
-
if (!selected) {
|
|
1994
|
-
const fallback = latestMatching(diagnostics, matchesExpectedTool);
|
|
1995
|
-
const latestFailureOrder = diagnostics.at(-1)?.order;
|
|
1996
|
-
if (fallback && latestFailureOrder !== undefined && finalCompletion(successfulCompletions, latestFailureOrder, lastInteractionOrder, allowCompletionProof)) {
|
|
1997
|
-
return publicDiagnostic(fallback, diagnostics, recordedCalls, successfulCompletions, postCompletionWarning, lastInteractionOrder, allowCompletionProof, "latest-before-completion");
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
if (!selected) {
|
|
2001
|
-
const falsePositive = hiddenFalsePositiveDiagnostic(expectedTool, terminalError, allowCompletionProof, parsed);
|
|
2002
|
-
if (falsePositive)
|
|
2003
|
-
return falsePositive;
|
|
2004
|
-
}
|
|
2005
|
-
} else {
|
|
2006
|
-
selected = latestMatching(diagnostics, matchesExpectedTool);
|
|
2007
|
-
}
|
|
2008
|
-
if (!selected && postCompletionWarning) {
|
|
2009
|
-
selected = latestMatching(diagnostics, matchesExpectedTool) ?? diagnostics.at(-1);
|
|
2010
|
-
if (!selected) {
|
|
2011
|
-
return {
|
|
2012
|
-
tool: expectedTool ?? "subagent",
|
|
2013
|
-
postCompletionWarning: postCompletionWarning.content
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
return selected ? publicDiagnostic(selected, diagnostics, recordedCalls, successfulCompletions, postCompletionWarning, lastInteractionOrder, allowCompletionProof) : undefined;
|
|
2018
|
-
};
|
|
2019
|
-
// src/integrations/subagents/replay-audit.ts
|
|
2020
|
-
var initialDelegationTask = (transcript) => {
|
|
2021
|
-
for (const line of transcript.split(`
|
|
2022
|
-
`)) {
|
|
2023
|
-
if (!line.trim())
|
|
2024
|
-
continue;
|
|
2025
|
-
let entry;
|
|
2026
|
-
try {
|
|
2027
|
-
entry = JSON.parse(line);
|
|
2028
|
-
} catch {
|
|
2029
|
-
continue;
|
|
2030
|
-
}
|
|
2031
|
-
if (!isDiagnosticRecord(entry) || entry.type !== "message")
|
|
2032
|
-
continue;
|
|
2033
|
-
const message = entry.message;
|
|
2034
|
-
if (!isDiagnosticRecord(message) || message.role !== "user")
|
|
2035
|
-
continue;
|
|
2036
|
-
if (!Array.isArray(message.content))
|
|
2037
|
-
return;
|
|
2038
|
-
const textParts = message.content.flatMap((item) => isDiagnosticRecord(item) && item.type === "text" && typeof item.text === "string" ? [item.text] : []);
|
|
2039
|
-
return textParts.length === 1 ? textParts[0] : undefined;
|
|
2040
|
-
}
|
|
2041
|
-
return;
|
|
2042
|
-
};
|
|
2043
|
-
var parseDelegationReplayAudit = (transcript, expectation, isCompleteTranscript = true) => {
|
|
2044
|
-
const calls = new Map;
|
|
2045
|
-
const recordedCalls = [];
|
|
2046
|
-
const diagnostics = [];
|
|
2047
|
-
const resultCallIds = new Set;
|
|
2048
|
-
let isStructurallyValid = true;
|
|
2049
|
-
let order = 0;
|
|
2050
|
-
for (const line of transcript.split(`
|
|
2051
|
-
`)) {
|
|
2052
|
-
order += 1;
|
|
2053
|
-
if (!line.trim())
|
|
2054
|
-
continue;
|
|
2055
|
-
let entry;
|
|
2056
|
-
try {
|
|
2057
|
-
entry = JSON.parse(line);
|
|
2058
|
-
} catch {
|
|
2059
|
-
isStructurallyValid = false;
|
|
2060
|
-
continue;
|
|
2061
|
-
}
|
|
2062
|
-
if (!isDiagnosticRecord(entry) || entry.type !== "message")
|
|
2063
|
-
continue;
|
|
2064
|
-
const message = entry.message;
|
|
2065
|
-
if (!isDiagnosticRecord(message)) {
|
|
2066
|
-
isStructurallyValid = false;
|
|
2067
|
-
continue;
|
|
2068
|
-
}
|
|
2069
|
-
if (message.role === "assistant") {
|
|
2070
|
-
if (!Array.isArray(message.content)) {
|
|
2071
|
-
isStructurallyValid = false;
|
|
2072
|
-
continue;
|
|
2073
|
-
}
|
|
2074
|
-
for (const item of message.content) {
|
|
2075
|
-
if (!isDiagnosticRecord(item) || item.type !== "toolCall")
|
|
2076
|
-
continue;
|
|
2077
|
-
if (typeof item.id !== "string" || typeof item.name !== "string" || calls.has(item.id)) {
|
|
2078
|
-
isStructurallyValid = false;
|
|
2079
|
-
continue;
|
|
2080
|
-
}
|
|
2081
|
-
const call = diagnosticToolCallText({
|
|
2082
|
-
tool: item.name,
|
|
2083
|
-
argumentsValue: item.arguments
|
|
2084
|
-
});
|
|
2085
|
-
const recordedCall = {
|
|
2086
|
-
id: item.id,
|
|
2087
|
-
order,
|
|
2088
|
-
tool: item.name,
|
|
2089
|
-
...call ? { call } : {}
|
|
2090
|
-
};
|
|
2091
|
-
calls.set(item.id, recordedCall);
|
|
2092
|
-
recordedCalls.push(recordedCall);
|
|
2093
|
-
}
|
|
2094
|
-
continue;
|
|
2095
|
-
}
|
|
2096
|
-
if (message.role !== "toolResult")
|
|
2097
|
-
continue;
|
|
2098
|
-
if (typeof message.toolCallId !== "string" || typeof message.toolName !== "string" || typeof message.isError !== "boolean" || !Array.isArray(message.content) || resultCallIds.has(message.toolCallId)) {
|
|
2099
|
-
isStructurallyValid = false;
|
|
2100
|
-
continue;
|
|
2101
|
-
}
|
|
2102
|
-
resultCallIds.add(message.toolCallId);
|
|
2103
|
-
const recorded = calls.get(message.toolCallId);
|
|
2104
|
-
if (recorded?.tool !== message.toolName) {
|
|
2105
|
-
isStructurallyValid = false;
|
|
2106
|
-
continue;
|
|
2107
|
-
}
|
|
2108
|
-
if (message.isError) {
|
|
2109
|
-
const output = diagnosticTextContent(message.content);
|
|
2110
|
-
diagnostics.push({
|
|
2111
|
-
tool: message.toolName,
|
|
2112
|
-
callId: message.toolCallId,
|
|
2113
|
-
order,
|
|
2114
|
-
...recorded.call ? { call: recorded.call } : {},
|
|
2115
|
-
...output ? { output } : {}
|
|
2116
|
-
});
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
return {
|
|
2120
|
-
replaySafe: isCompleteTranscript && isStructurallyValid && initialDelegationTask(transcript) === expectation.task && recordedCalls.every((call) => isReplaySafeToolCall({
|
|
2121
|
-
call,
|
|
2122
|
-
diagnostics,
|
|
2123
|
-
bashPermission: expectation.bashPermission
|
|
2124
|
-
})),
|
|
2125
|
-
toolCount: recordedCalls.length
|
|
2126
|
-
};
|
|
2127
|
-
};
|
|
2128
|
-
// src/integrations/subagents/session-diagnostics.ts
|
|
2129
|
-
import { constants } from "node:fs";
|
|
2130
|
-
import { lstat, open, realpath as realpath2 } from "node:fs/promises";
|
|
2131
|
-
import {
|
|
2132
|
-
basename as basename2,
|
|
2133
|
-
dirname as dirname2,
|
|
2134
|
-
isAbsolute as isAbsolute3,
|
|
2135
|
-
join as join4,
|
|
2136
|
-
relative as relative2,
|
|
2137
|
-
resolve as resolve3,
|
|
2138
|
-
sep as sep2
|
|
2139
|
-
} from "node:path";
|
|
2140
|
-
var SESSION_FILE_NAME = "session.jsonl";
|
|
2141
|
-
var SESSION_RUN_DIRECTORY = /^run-\d+$/;
|
|
2142
|
-
var SESSION_FILE_SUFFIX = ".jsonl";
|
|
2143
|
-
var MAX_SESSION_TAIL_BYTES = 1024 * 1024;
|
|
2144
|
-
var DEFAULT_DIAGNOSTIC_DEPENDENCIES = {
|
|
2145
|
-
fileSystem: {
|
|
2146
|
-
inspect: lstat,
|
|
2147
|
-
realPath: realpath2,
|
|
2148
|
-
openReadOnlyNoFollow: (path) => open(path, constants.O_RDONLY | constants.O_NOFOLLOW)
|
|
2149
|
-
}
|
|
2150
|
-
};
|
|
2151
|
-
var isSessionFilePath = (path) => isAbsolute3(path) && !path.includes("\x00") && basename2(path) === SESSION_FILE_NAME && SESSION_RUN_DIRECTORY.test(basename2(dirname2(path)));
|
|
2152
|
-
var pathIsWithin = (root, candidate) => {
|
|
2153
|
-
const relativePath = relative2(resolve3(root), resolve3(candidate));
|
|
2154
|
-
return relativePath !== "" && relativePath !== ".." && !relativePath.startsWith(`..${sep2}`) && !isAbsolute3(relativePath);
|
|
2155
|
-
};
|
|
2156
|
-
var isValidSessionIdentity = (identity) => identity.runId.length > 0 && !identity.runId.includes("\x00") && basename2(identity.runId) === identity.runId && identity.runId !== "." && identity.runId !== ".." && Number.isSafeInteger(identity.childIndex) && identity.childIndex >= 0;
|
|
2157
|
-
var readStableTail = async (handle) => {
|
|
2158
|
-
const opened = await handle.stat();
|
|
2159
|
-
if (!opened.isFile())
|
|
2160
|
-
return;
|
|
2161
|
-
const bytesToRead = Math.min(opened.size, MAX_SESSION_TAIL_BYTES);
|
|
2162
|
-
if (bytesToRead === 0)
|
|
2163
|
-
return { content: "", truncated: false };
|
|
2164
|
-
const start = opened.size - bytesToRead;
|
|
2165
|
-
const buffer = Buffer.alloc(bytesToRead);
|
|
2166
|
-
const { bytesRead } = await handle.read(buffer, 0, bytesToRead, start);
|
|
2167
|
-
const afterRead = await handle.stat();
|
|
2168
|
-
const didFileChange = afterRead.dev !== opened.dev || afterRead.ino !== opened.ino || afterRead.size !== opened.size || afterRead.mtimeMs !== opened.mtimeMs;
|
|
2169
|
-
if (didFileChange)
|
|
2170
|
-
return;
|
|
2171
|
-
let content = buffer.subarray(0, bytesRead).toString("utf8");
|
|
2172
|
-
if (start > 0) {
|
|
2173
|
-
const firstNewline = content.indexOf(`
|
|
2174
|
-
`);
|
|
2175
|
-
content = firstNewline === -1 ? "" : content.slice(firstNewline + 1);
|
|
2176
|
-
}
|
|
2177
|
-
return { content, truncated: start > 0 };
|
|
2178
|
-
};
|
|
2179
|
-
var readContainedSessionTail = async ({
|
|
2180
|
-
sessionFile,
|
|
2181
|
-
trustedRoot,
|
|
2182
|
-
identity,
|
|
2183
|
-
dependencies
|
|
2184
|
-
}) => {
|
|
2185
|
-
const expectedSessionFile = resolve3(trustedRoot, identity.runId, `run-${identity.childIndex}`, SESSION_FILE_NAME);
|
|
2186
|
-
if (!isSessionFilePath(sessionFile) || !isAbsolute3(trustedRoot) || trustedRoot.includes("\x00") || !pathIsWithin(trustedRoot, sessionFile) || !isValidSessionIdentity(identity) || resolve3(sessionFile) !== expectedSessionFile) {
|
|
2187
|
-
return;
|
|
2188
|
-
}
|
|
2189
|
-
const runDirectory = resolve3(trustedRoot, identity.runId);
|
|
2190
|
-
const childDirectory = resolve3(runDirectory, `run-${identity.childIndex}`);
|
|
2191
|
-
const resolvedSessionFile = resolve3(sessionFile);
|
|
2192
|
-
const [runDirectoryInfo, childDirectoryInfo, inspected] = await Promise.all([
|
|
2193
|
-
dependencies.fileSystem.inspect(runDirectory),
|
|
2194
|
-
dependencies.fileSystem.inspect(childDirectory),
|
|
2195
|
-
dependencies.fileSystem.inspect(resolvedSessionFile)
|
|
2196
|
-
]);
|
|
2197
|
-
if (runDirectoryInfo.isSymbolicLink() || !runDirectoryInfo.isDirectory() || childDirectoryInfo.isSymbolicLink() || !childDirectoryInfo.isDirectory() || inspected.isSymbolicLink() || !inspected.isFile()) {
|
|
2198
|
-
return;
|
|
2199
|
-
}
|
|
2200
|
-
const [canonicalRoot, canonicalSessionFile] = await Promise.all([
|
|
2201
|
-
dependencies.fileSystem.realPath(trustedRoot),
|
|
2202
|
-
dependencies.fileSystem.realPath(resolvedSessionFile)
|
|
2203
|
-
]);
|
|
2204
|
-
const canonicalExpectedSessionFile = resolve3(canonicalRoot, identity.runId, `run-${identity.childIndex}`, SESSION_FILE_NAME);
|
|
2205
|
-
if (!pathIsWithin(canonicalRoot, canonicalSessionFile) || canonicalSessionFile !== canonicalExpectedSessionFile) {
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
const handle = await dependencies.fileSystem.openReadOnlyNoFollow(canonicalSessionFile);
|
|
2209
|
-
try {
|
|
2210
|
-
return await readStableTail(handle);
|
|
2211
|
-
} finally {
|
|
2212
|
-
await handle.close();
|
|
2213
|
-
}
|
|
2214
|
-
};
|
|
2215
|
-
var deriveSubagentSessionRoot = (parentSessionFile) => {
|
|
2216
|
-
if (!parentSessionFile || !isAbsolute3(parentSessionFile) || parentSessionFile.includes("\x00")) {
|
|
2217
|
-
return;
|
|
2218
|
-
}
|
|
2219
|
-
const parentName = basename2(parentSessionFile);
|
|
2220
|
-
if (!parentName.endsWith(SESSION_FILE_SUFFIX) || parentName === SESSION_FILE_SUFFIX) {
|
|
2221
|
-
return;
|
|
2222
|
-
}
|
|
2223
|
-
return join4(dirname2(parentSessionFile), parentName.slice(0, -SESSION_FILE_SUFFIX.length));
|
|
2224
|
-
};
|
|
2225
|
-
var readToolFailureDiagnostic = async (sessionFile, trustedRoot, identity, expectedTool, terminalError, dependencies = DEFAULT_DIAGNOSTIC_DEPENDENCIES) => {
|
|
2226
|
-
if (!sessionFile || !trustedRoot || !identity)
|
|
2227
|
-
return;
|
|
2228
|
-
try {
|
|
2229
|
-
const tail = await readContainedSessionTail({
|
|
2230
|
-
sessionFile,
|
|
2231
|
-
trustedRoot,
|
|
2232
|
-
identity,
|
|
2233
|
-
dependencies
|
|
2234
|
-
});
|
|
2235
|
-
return parseToolFailureDiagnostic(tail?.content ?? "", expectedTool, terminalError, tail?.truncated !== true);
|
|
2236
|
-
} catch {
|
|
2237
|
-
return;
|
|
2238
|
-
}
|
|
2239
|
-
};
|
|
2240
|
-
var auditCompletedDelegationTranscript = async (sessionFile, trustedRoot, identity, dependencies = DEFAULT_DIAGNOSTIC_DEPENDENCIES) => {
|
|
2241
|
-
if (!sessionFile || !trustedRoot || !identity) {
|
|
2242
|
-
return {
|
|
2243
|
-
verified: false,
|
|
2244
|
-
reason: "completed response has no trusted child transcript identity"
|
|
2245
|
-
};
|
|
2246
|
-
}
|
|
2247
|
-
try {
|
|
2248
|
-
const tail = await readContainedSessionTail({
|
|
2249
|
-
sessionFile,
|
|
2250
|
-
trustedRoot,
|
|
2251
|
-
identity,
|
|
2252
|
-
dependencies
|
|
2253
|
-
});
|
|
2254
|
-
if (!tail) {
|
|
2255
|
-
return {
|
|
2256
|
-
verified: false,
|
|
2257
|
-
reason: "completed child transcript is missing, unstable, or untrusted"
|
|
2258
|
-
};
|
|
2259
|
-
}
|
|
2260
|
-
const parsed = parseFailureTranscript(tail.content);
|
|
2261
|
-
const completion = parsed.successfulCompletions.at(-1);
|
|
2262
|
-
if (!completion) {
|
|
2263
|
-
return {
|
|
2264
|
-
verified: false,
|
|
2265
|
-
reason: "completed child transcript does not contain a successful structured_output result"
|
|
2266
|
-
};
|
|
2267
|
-
}
|
|
2268
|
-
const warning = parsed.transcriptWarnings.find((candidate) => candidate.order > completion.order);
|
|
2269
|
-
if (warning)
|
|
2270
|
-
return { verified: true, warning: warning.content };
|
|
2271
|
-
if (!parsed.hasValidFalsePositiveProof || completion.order !== parsed.lastInteractionOrder) {
|
|
2272
|
-
return {
|
|
2273
|
-
verified: false,
|
|
2274
|
-
reason: "completed child transcript has malformed or later terminal interactions"
|
|
2275
|
-
};
|
|
2276
|
-
}
|
|
2277
|
-
return { verified: true };
|
|
2278
|
-
} catch {
|
|
2279
|
-
return {
|
|
2280
|
-
verified: false,
|
|
2281
|
-
reason: "completed child transcript could not be read safely"
|
|
2282
|
-
};
|
|
2283
|
-
}
|
|
2284
|
-
};
|
|
2285
|
-
var readDelegationReplayAudit = async (sessionFile, trustedRoot, identity, expectation, dependencies = DEFAULT_DIAGNOSTIC_DEPENDENCIES) => {
|
|
2286
|
-
if (!sessionFile || !trustedRoot || !identity)
|
|
2287
|
-
return;
|
|
2288
|
-
try {
|
|
2289
|
-
const tail = await readContainedSessionTail({
|
|
2290
|
-
sessionFile,
|
|
2291
|
-
trustedRoot,
|
|
2292
|
-
identity,
|
|
2293
|
-
dependencies
|
|
2294
|
-
});
|
|
2295
|
-
return tail ? parseDelegationReplayAudit(tail.content, expectation, !tail.truncated) : undefined;
|
|
2296
|
-
} catch {
|
|
2297
|
-
return;
|
|
2298
|
-
}
|
|
2299
|
-
};
|
|
2300
|
-
// src/integrations/subagents/child-policy-envelope.ts
|
|
2301
|
-
import { basename as basename4, dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
2302
|
-
|
|
2303
|
-
// src/integrations/subagents/child-policy-paths.ts
|
|
2304
|
-
import { tmpdir } from "node:os";
|
|
2305
|
-
import { basename as basename3, dirname as dirname3, relative as relative3, resolve as resolve4 } from "node:path";
|
|
2306
|
-
var RESULT_FILE_NAME = "result.json";
|
|
2307
|
-
var CAPABILITY_FILE_NAME = "capability";
|
|
2308
|
-
var RESULT_DIRECTORY_PREFIX = "pi-workflows-step-";
|
|
2309
|
-
var DEFAULT_CHILD_POLICY_ENVIRONMENT = {
|
|
2310
|
-
temporaryDirectory: tmpdir
|
|
2311
|
-
};
|
|
2312
|
-
var isSafeStepFilePath = ({
|
|
2313
|
-
path,
|
|
2314
|
-
expectedName,
|
|
2315
|
-
environment
|
|
2316
|
-
}) => {
|
|
2317
|
-
const temporaryRoot = resolve4(environment.temporaryDirectory());
|
|
2318
|
-
const candidate = resolve4(path);
|
|
2319
|
-
const relativePath = relative3(temporaryRoot, candidate);
|
|
2320
|
-
return relativePath !== "" && !relativePath.startsWith("..") && !relativePath.includes("\x00") && basename3(candidate) === expectedName && basename3(dirname3(candidate)).startsWith(RESULT_DIRECTORY_PREFIX);
|
|
2321
|
-
};
|
|
2322
|
-
var isSafeStepResultPath = (path, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => isSafeStepFilePath({
|
|
2323
|
-
path,
|
|
2324
|
-
expectedName: RESULT_FILE_NAME,
|
|
2325
|
-
environment
|
|
2326
|
-
});
|
|
2327
|
-
var isSafeStepCapabilityPath = (path, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => isSafeStepFilePath({
|
|
2328
|
-
path,
|
|
2329
|
-
expectedName: CAPABILITY_FILE_NAME,
|
|
2330
|
-
environment
|
|
2331
|
-
});
|
|
2332
|
-
|
|
2333
|
-
// src/integrations/subagents/child-policy-validation.ts
|
|
2334
|
-
import { dirname as dirname4, isAbsolute as isAbsolute5, resolve as resolve5 } from "node:path";
|
|
2335
|
-
|
|
2336
|
-
// src/integrations/subagents/child-policy-sections.ts
|
|
2337
|
-
import { isAbsolute as isAbsolute4, win32 as win322 } from "node:path";
|
|
2338
|
-
var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2339
|
-
var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
2340
|
-
var hasOnlyKeys = (value, allowed) => Object.keys(value).every((key) => allowed.has(key));
|
|
2341
|
-
var isStepPermissions = (value) => {
|
|
2342
|
-
if (!isRecord(value) || !isRecord(value.bash))
|
|
2343
|
-
return false;
|
|
2344
|
-
const bash = value.bash;
|
|
2345
|
-
const bashRules = Array.isArray(bash.allow) ? bash.allow : undefined;
|
|
2346
|
-
const isValidMode = bash.mode === "deny" || bash.mode === "allow-list" || bash.mode === "unrestricted";
|
|
2347
|
-
const hasValidRules = bashRules !== undefined && bashRules.every((rule) => isRecord(rule) && hasOnlyKeys(rule, new Set(["executable", "argsPrefix"])) && typeof rule.executable === "string" && isStringArray(rule.argsPrefix));
|
|
2348
|
-
return hasOnlyKeys(value, new Set(["tools", "mcp", "extensions", "skills", "bash"])) && hasOnlyKeys(bash, new Set(["mode", "allow"])) && isStringArray(value.tools) && isStringArray(value.mcp) && isStringArray(value.extensions) && isStringArray(value.skills) && isValidMode && hasValidRules && (bash.mode !== "allow-list" || bashRules.length > 0);
|
|
2349
|
-
};
|
|
2350
|
-
var parsePermissions2 = (value) => {
|
|
2351
|
-
if (!isStepPermissions(value.permissions)) {
|
|
2352
|
-
throw new Error("child policy permissions are invalid");
|
|
2353
|
-
}
|
|
2354
|
-
return { permissions: value.permissions };
|
|
2355
|
-
};
|
|
2356
|
-
var parseOutcomes = (value) => {
|
|
2357
|
-
const outcomes = value.outcomes;
|
|
2358
|
-
if (!isStringArray(outcomes) || outcomes.length === 0 || new Set(outcomes).size !== outcomes.length) {
|
|
2359
|
-
throw new Error("child policy outcomes are invalid");
|
|
2360
|
-
}
|
|
2361
|
-
const pauseOutcomes = value.pauseOutcomes;
|
|
2362
|
-
if (!isStringArray(pauseOutcomes) || new Set(pauseOutcomes).size !== pauseOutcomes.length || pauseOutcomes.some((outcome) => !outcomes.includes(outcome))) {
|
|
2363
|
-
throw new Error("child policy pause outcomes are invalid");
|
|
2364
|
-
}
|
|
2365
|
-
const summaryMaxChars = value.summaryMaxChars;
|
|
2366
|
-
if (typeof summaryMaxChars !== "number" || !Number.isInteger(summaryMaxChars) || summaryMaxChars < 100 || summaryMaxChars > 50000) {
|
|
2367
|
-
throw new Error("child policy summaryMaxChars is invalid");
|
|
2368
|
-
}
|
|
2369
|
-
const gateSubmitOutcome = value.gateSubmitOutcome;
|
|
2370
|
-
if (gateSubmitOutcome !== undefined && (typeof gateSubmitOutcome !== "string" || !outcomes.includes(gateSubmitOutcome))) {
|
|
2371
|
-
throw new Error("child policy gate outcome is invalid");
|
|
2372
|
-
}
|
|
2373
|
-
return {
|
|
2374
|
-
outcomes,
|
|
2375
|
-
pauseOutcomes,
|
|
2376
|
-
summaryMaxChars,
|
|
2377
|
-
...gateSubmitOutcome === undefined ? {} : { gateSubmitOutcome }
|
|
2378
|
-
};
|
|
2379
|
-
};
|
|
2380
|
-
var parseWorkspace2 = (value, outcomes) => {
|
|
2381
|
-
if (value.workspace === undefined)
|
|
2382
|
-
return {};
|
|
2383
|
-
if (!isRecord(value.workspace) || !hasOnlyKeys(value.workspace, new Set(["bindOn", "allowedRoots"]))) {
|
|
2384
|
-
throw new Error("child policy workspace is invalid");
|
|
2385
|
-
}
|
|
2386
|
-
const bindOn = value.workspace.bindOn;
|
|
2387
|
-
const allowedRoots = value.workspace.allowedRoots;
|
|
2388
|
-
if (!isStringArray(bindOn) || bindOn.length === 0 || new Set(bindOn).size !== bindOn.length || bindOn.some((outcome) => !outcomes.includes(outcome))) {
|
|
2389
|
-
throw new Error("child policy workspace bindOn outcomes are invalid");
|
|
2390
|
-
}
|
|
2391
|
-
if (!isStringArray(allowedRoots) || allowedRoots.length === 0 || allowedRoots.length > MAX_WORKSPACE_ALLOWED_ROOTS || new Set(allowedRoots).size !== allowedRoots.length || allowedRoots.some((root) => !root.trim() || root !== root.trim() || root.length > MAX_WORKSPACE_PATH_CHARS || root.includes("\x00") || win322.parse(root).root !== "" && !isAbsolute4(root))) {
|
|
2392
|
-
throw new Error("child policy workspace allowed roots are invalid");
|
|
2393
|
-
}
|
|
2394
|
-
return { workspace: { bindOn, allowedRoots } };
|
|
2395
|
-
};
|
|
2396
|
-
var parseChildPolicySections = (value) => {
|
|
2397
|
-
const outcomeSections = parseOutcomes(value);
|
|
2398
|
-
return {
|
|
2399
|
-
...parsePermissions2(value),
|
|
2400
|
-
...outcomeSections,
|
|
2401
|
-
...parseWorkspace2(value, outcomeSections.outcomes)
|
|
2402
|
-
};
|
|
2403
|
-
};
|
|
2404
|
-
|
|
2405
|
-
// src/integrations/subagents/child-policy-validation.ts
|
|
2406
|
-
var POLICY_DIGEST_PATTERN = /^[a-f0-9]{64}$/;
|
|
2407
|
-
var CAPABILITY_TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
2408
|
-
var POLICY_KEYS = new Set([
|
|
2409
|
-
"version",
|
|
2410
|
-
"requestId",
|
|
2411
|
-
"agent",
|
|
2412
|
-
"workflowId",
|
|
2413
|
-
"runId",
|
|
2414
|
-
"stepId",
|
|
2415
|
-
"stepTitle",
|
|
2416
|
-
"cwd",
|
|
2417
|
-
"policyDigest",
|
|
2418
|
-
"capabilityPath",
|
|
2419
|
-
"capabilityToken",
|
|
2420
|
-
"resultPath",
|
|
2421
|
-
"permissions",
|
|
2422
|
-
"outcomes",
|
|
2423
|
-
"pauseOutcomes",
|
|
2424
|
-
"summaryMaxChars",
|
|
2425
|
-
"gateSubmitOutcome",
|
|
2426
|
-
"workspace"
|
|
2427
|
-
]);
|
|
2428
|
-
var isRecord2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2429
|
-
var requiredString = (value, field) => {
|
|
2430
|
-
const candidate = value[field];
|
|
2431
|
-
if (typeof candidate !== "string" || !candidate) {
|
|
2432
|
-
throw new Error(`child policy ${field} must be a non-empty string`);
|
|
2433
|
-
}
|
|
2434
|
-
return candidate;
|
|
2435
|
-
};
|
|
2436
|
-
var isSubagentRuntimeName = (name) => Boolean(name && SUBAGENT_RUNTIME_NAME_PATTERN.test(name));
|
|
2437
|
-
var rejectUnknownProperties = (value) => {
|
|
2438
|
-
const unknownKey = Object.keys(value).find((key) => !POLICY_KEYS.has(key));
|
|
2439
|
-
if (unknownKey) {
|
|
2440
|
-
throw new Error(`child policy has unknown property "${unknownKey}"`);
|
|
2441
|
-
}
|
|
2442
|
-
};
|
|
2443
|
-
var parseIdentityAndPaths = (value, environment) => {
|
|
2444
|
-
const requestId = requiredString(value, "requestId");
|
|
2445
|
-
const agent = requiredString(value, "agent");
|
|
2446
|
-
const workflowId = requiredString(value, "workflowId");
|
|
2447
|
-
const runId = requiredString(value, "runId");
|
|
2448
|
-
const stepId = requiredString(value, "stepId");
|
|
2449
|
-
const stepTitle = requiredString(value, "stepTitle");
|
|
2450
|
-
const cwd = requiredString(value, "cwd");
|
|
2451
|
-
const policyDigest = requiredString(value, "policyDigest");
|
|
2452
|
-
const capabilityPath = requiredString(value, "capabilityPath");
|
|
2453
|
-
const capabilityToken = requiredString(value, "capabilityToken");
|
|
2454
|
-
const resultPath = requiredString(value, "resultPath");
|
|
2455
|
-
if (value.version !== 1)
|
|
2456
|
-
throw new Error("unsupported child policy version");
|
|
2457
|
-
if (!isAbsolute5(cwd)) {
|
|
2458
|
-
throw new Error("child policy cwd must be an absolute path");
|
|
2459
|
-
}
|
|
2460
|
-
if (!POLICY_DIGEST_PATTERN.test(policyDigest)) {
|
|
2461
|
-
throw new Error("child policy digest is invalid");
|
|
2462
|
-
}
|
|
2463
|
-
if (!isSubagentRuntimeName(agent)) {
|
|
2464
|
-
throw new Error("child policy agent is not a valid subagent runtime name");
|
|
2465
|
-
}
|
|
2466
|
-
if (!CAPABILITY_TOKEN_PATTERN.test(capabilityToken)) {
|
|
2467
|
-
throw new Error("child policy capability token is invalid");
|
|
2468
|
-
}
|
|
2469
|
-
if (!isSafeStepCapabilityPath(capabilityPath, environment)) {
|
|
2470
|
-
throw new Error("child policy capability path is outside its temporary directory");
|
|
2471
|
-
}
|
|
2472
|
-
if (!isSafeStepResultPath(resultPath, environment)) {
|
|
2473
|
-
throw new Error("child policy result path is outside its temporary directory");
|
|
2474
|
-
}
|
|
2475
|
-
if (dirname4(resolve5(capabilityPath)) !== dirname4(resolve5(resultPath))) {
|
|
2476
|
-
throw new Error("child policy files must share one temporary directory");
|
|
2477
|
-
}
|
|
2478
|
-
return {
|
|
2479
|
-
version: 1,
|
|
2480
|
-
requestId,
|
|
2481
|
-
agent,
|
|
2482
|
-
workflowId,
|
|
2483
|
-
runId,
|
|
2484
|
-
stepId,
|
|
2485
|
-
stepTitle,
|
|
2486
|
-
cwd,
|
|
2487
|
-
policyDigest,
|
|
2488
|
-
capabilityPath,
|
|
2489
|
-
capabilityToken,
|
|
2490
|
-
resultPath
|
|
2491
|
-
};
|
|
2492
|
-
};
|
|
2493
|
-
var parseChildPolicy = (value, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => {
|
|
2494
|
-
if (!isRecord2(value))
|
|
2495
|
-
throw new Error("child policy must be an object");
|
|
2496
|
-
rejectUnknownProperties(value);
|
|
2497
|
-
return {
|
|
2498
|
-
...parseIdentityAndPaths(value, environment),
|
|
2499
|
-
...parseChildPolicySections(value)
|
|
2500
|
-
};
|
|
2501
|
-
};
|
|
2502
|
-
|
|
2503
|
-
// src/integrations/subagents/child-policy-envelope.ts
|
|
2504
|
-
var CHILD_POLICY_OPEN = "<pi-workflows-policy-v1>";
|
|
2505
|
-
var CHILD_POLICY_CLOSE = "</pi-workflows-policy-v1>";
|
|
2506
|
-
var UPSTREAM_TASK_PREFIX = "Task: ";
|
|
2507
|
-
var UPSTREAM_TASK_FILE_OPEN = '<file name="';
|
|
2508
|
-
var UPSTREAM_TASK_FILE_HEADER_CLOSE = `">
|
|
2509
|
-
`;
|
|
2510
|
-
var UPSTREAM_TASK_FILE_CLOSE = `
|
|
2511
|
-
</file>
|
|
2512
|
-
`;
|
|
2513
|
-
var UPSTREAM_TASK_DIRECTORY_PREFIX = "pi-subagent-";
|
|
2514
|
-
var encodeChildPolicy = (policy) => {
|
|
2515
|
-
const encoded = Buffer.from(JSON.stringify(policy), "utf8").toString("base64url");
|
|
2516
|
-
return `${CHILD_POLICY_OPEN}${encoded}${CHILD_POLICY_CLOSE}`;
|
|
2517
|
-
};
|
|
2518
|
-
var unwrapTaskFile = ({
|
|
2519
|
-
text,
|
|
2520
|
-
environment
|
|
2521
|
-
}) => {
|
|
2522
|
-
if (!text.startsWith(UPSTREAM_TASK_FILE_OPEN) || !text.endsWith(UPSTREAM_TASK_FILE_CLOSE)) {
|
|
2523
|
-
return;
|
|
2524
|
-
}
|
|
2525
|
-
const pathStart = UPSTREAM_TASK_FILE_OPEN.length;
|
|
2526
|
-
const headerEnd = text.indexOf(UPSTREAM_TASK_FILE_HEADER_CLOSE, pathStart);
|
|
2527
|
-
if (headerEnd === -1)
|
|
2528
|
-
return;
|
|
2529
|
-
const taskFilePath = text.slice(pathStart, headerEnd);
|
|
2530
|
-
const taskDirectory = dirname5(resolve6(taskFilePath));
|
|
2531
|
-
const isExpectedTaskFile = basename4(taskFilePath) === "task.md" && basename4(taskDirectory).startsWith(UPSTREAM_TASK_DIRECTORY_PREFIX) && dirname5(taskDirectory) === resolve6(environment.temporaryDirectory());
|
|
2532
|
-
if (!isExpectedTaskFile)
|
|
2533
|
-
return;
|
|
2534
|
-
const bodyStart = headerEnd + UPSTREAM_TASK_FILE_HEADER_CLOSE.length;
|
|
2535
|
-
const body = text.slice(bodyStart, -UPSTREAM_TASK_FILE_CLOSE.length);
|
|
2536
|
-
if (!body.startsWith(`${UPSTREAM_TASK_PREFIX}${CHILD_POLICY_OPEN}`)) {
|
|
2537
|
-
return;
|
|
2538
|
-
}
|
|
2539
|
-
return body.slice(UPSTREAM_TASK_PREFIX.length);
|
|
2540
|
-
};
|
|
2541
|
-
var unwrapUpstreamTask = ({
|
|
2542
|
-
text,
|
|
2543
|
-
environment
|
|
2544
|
-
}) => {
|
|
2545
|
-
if (text.startsWith(CHILD_POLICY_OPEN))
|
|
2546
|
-
return text;
|
|
2547
|
-
if (text.startsWith(`${UPSTREAM_TASK_PREFIX}${CHILD_POLICY_OPEN}`)) {
|
|
2548
|
-
return text.slice(UPSTREAM_TASK_PREFIX.length);
|
|
2549
|
-
}
|
|
2550
|
-
return unwrapTaskFile({ text, environment });
|
|
2551
|
-
};
|
|
2552
|
-
var decodePolicy = (encoded) => {
|
|
2553
|
-
try {
|
|
2554
|
-
return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
2555
|
-
} catch {
|
|
2556
|
-
throw new Error("delegated task child policy cannot be decoded");
|
|
2557
|
-
}
|
|
2558
|
-
};
|
|
2559
|
-
var extractChildPolicy = (text, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => {
|
|
2560
|
-
const taskWithPolicy = unwrapUpstreamTask({ text, environment });
|
|
2561
|
-
if (taskWithPolicy === undefined)
|
|
2562
|
-
return;
|
|
2563
|
-
const payloadStart = CHILD_POLICY_OPEN.length;
|
|
2564
|
-
const payloadEnd = taskWithPolicy.indexOf(CHILD_POLICY_CLOSE, payloadStart);
|
|
2565
|
-
const hasNestedEnvelope = taskWithPolicy.indexOf(CHILD_POLICY_OPEN, payloadStart) !== -1;
|
|
2566
|
-
if (payloadEnd === -1 || hasNestedEnvelope) {
|
|
2567
|
-
throw new Error("delegated task contains an invalid child policy envelope");
|
|
2568
|
-
}
|
|
2569
|
-
const encoded = taskWithPolicy.slice(payloadStart, payloadEnd);
|
|
2570
|
-
const task = taskWithPolicy.slice(payloadEnd + CHILD_POLICY_CLOSE.length).trim();
|
|
2571
|
-
if (!task)
|
|
2572
|
-
throw new Error("delegated task is empty after policy extraction");
|
|
2573
|
-
return {
|
|
2574
|
-
policy: parseChildPolicy(decodePolicy(encoded), environment),
|
|
2575
|
-
task
|
|
2576
|
-
};
|
|
2577
|
-
};
|
|
2578
|
-
// src/runtime/step-result.ts
|
|
2579
|
-
import { isAbsolute as isAbsolute6 } from "node:path";
|
|
2580
|
-
var MAX_ARTIFACT_CHARS = 200000;
|
|
2581
|
-
var RESULT_KEYS = new Set([
|
|
2582
|
-
"version",
|
|
2583
|
-
"policyDigest",
|
|
2584
|
-
"outcome",
|
|
2585
|
-
"summary",
|
|
2586
|
-
"artifact",
|
|
2587
|
-
"workspace"
|
|
2588
|
-
]);
|
|
2589
|
-
var isObject = (value) => {
|
|
2590
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2591
|
-
};
|
|
2592
|
-
function parseResultWorkspace(value, outcome, policy) {
|
|
2593
|
-
const requiresWorkspace = policy.workspace?.bindOn.includes(outcome) === true;
|
|
2594
|
-
if (!requiresWorkspace) {
|
|
2595
|
-
if (value !== undefined) {
|
|
2596
|
-
throw new Error("workflow step workspace is forbidden for this outcome");
|
|
2597
|
-
}
|
|
2598
|
-
return;
|
|
2599
|
-
}
|
|
2600
|
-
if (!isObject(value)) {
|
|
2601
|
-
throw new Error(`workflow step outcome "${outcome}" requires workspace.cwd`);
|
|
2602
|
-
}
|
|
2603
|
-
const unknownKey = Object.keys(value).find((key) => key !== "cwd");
|
|
2604
|
-
if (unknownKey) {
|
|
2605
|
-
throw new Error(`workflow step workspace has unknown property "${unknownKey}"`);
|
|
2606
|
-
}
|
|
2607
|
-
if (typeof value.cwd !== "string") {
|
|
2608
|
-
throw new Error("workflow step workspace cwd must be a string");
|
|
2609
|
-
}
|
|
2610
|
-
const cwd = value.cwd;
|
|
2611
|
-
if (!cwd || cwd.includes("\x00") || !isAbsolute6(cwd)) {
|
|
2612
|
-
throw new Error("workflow step workspace cwd must be an absolute path");
|
|
2613
|
-
}
|
|
2614
|
-
if (cwd.length > MAX_WORKSPACE_PATH_CHARS) {
|
|
2615
|
-
throw new Error(`workflow step workspace cwd exceeds ${MAX_WORKSPACE_PATH_CHARS} characters`);
|
|
2616
|
-
}
|
|
2617
|
-
return { cwd };
|
|
2618
|
-
}
|
|
2619
|
-
function parseWorkflowStepResult(value, policy) {
|
|
2620
|
-
if (!isObject(value)) {
|
|
2621
|
-
throw new Error("workflow step result must be an object");
|
|
2622
|
-
}
|
|
2623
|
-
const unknownKey = Object.keys(value).find((key) => !RESULT_KEYS.has(key));
|
|
2624
|
-
if (unknownKey) {
|
|
2625
|
-
throw new Error(`workflow step result has unknown property "${unknownKey}"`);
|
|
2626
|
-
}
|
|
2627
|
-
if (value.version !== 1) {
|
|
2628
|
-
throw new Error("unsupported workflow step result version");
|
|
2629
|
-
}
|
|
2630
|
-
if (value.policyDigest !== policy.policyDigest) {
|
|
2631
|
-
throw new Error("workflow step result does not match the active policy");
|
|
2632
|
-
}
|
|
2633
|
-
if (typeof value.outcome !== "string" || !policy.outcomes.includes(value.outcome)) {
|
|
2634
|
-
throw new Error(`workflow step returned invalid outcome "${String(value.outcome)}"`);
|
|
2635
|
-
}
|
|
2636
|
-
if (typeof value.summary !== "string") {
|
|
2637
|
-
throw new Error("workflow step summary must be a string");
|
|
2638
|
-
}
|
|
2639
|
-
const summary = value.summary.trim();
|
|
2640
|
-
if (!summary) {
|
|
2641
|
-
throw new Error("workflow step summary must not be empty");
|
|
2642
|
-
}
|
|
2643
|
-
if (summary.length > policy.summaryMaxChars) {
|
|
2644
|
-
throw new Error(`workflow step summary exceeds ${policy.summaryMaxChars} characters`);
|
|
2645
|
-
}
|
|
2646
|
-
if (value.artifact !== undefined && typeof value.artifact !== "string") {
|
|
2647
|
-
throw new Error("workflow step artifact must be a string");
|
|
2648
|
-
}
|
|
2649
|
-
const artifact = typeof value.artifact === "string" ? value.artifact : undefined;
|
|
2650
|
-
if (artifact !== undefined && artifact.length > MAX_ARTIFACT_CHARS) {
|
|
2651
|
-
throw new Error(`workflow step artifact exceeds ${MAX_ARTIFACT_CHARS} characters`);
|
|
2652
|
-
}
|
|
2653
|
-
if (value.outcome === policy.gateSubmitOutcome && (!artifact || !artifact.trim())) {
|
|
2654
|
-
throw new Error("workflow gate outcome requires a non-empty artifact");
|
|
2655
|
-
}
|
|
2656
|
-
const workspace = parseResultWorkspace(value.workspace, value.outcome, policy);
|
|
2657
|
-
return {
|
|
2658
|
-
version: 1,
|
|
2659
|
-
policyDigest: policy.policyDigest,
|
|
2660
|
-
outcome: value.outcome,
|
|
2661
|
-
summary,
|
|
2662
|
-
...artifact !== undefined ? { artifact } : {},
|
|
2663
|
-
...workspace ? { workspace } : {}
|
|
2664
|
-
};
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
// src/integrations/subagents/delegated-result.ts
|
|
2668
|
-
var parseDelegatedStepResult = (value, policy) => {
|
|
2669
|
-
try {
|
|
2670
|
-
return parseWorkflowStepResult(value, {
|
|
2671
|
-
policyDigest: policy.policyDigest,
|
|
2672
|
-
outcomes: [...policy.outcomes],
|
|
2673
|
-
summaryMaxChars: policy.summaryMaxChars,
|
|
2674
|
-
...policy.gateSubmitOutcome ? { gateSubmitOutcome: policy.gateSubmitOutcome } : {},
|
|
2675
|
-
...policy.workspace ? { workspace: policy.workspace } : {}
|
|
2676
|
-
});
|
|
2677
|
-
} catch (error) {
|
|
2678
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2679
|
-
throw new Error(message.replaceAll("workflow step", "delegated step"), {
|
|
2680
|
-
cause: error
|
|
2681
|
-
});
|
|
2682
|
-
}
|
|
2683
|
-
};
|
|
2684
|
-
// src/integrations/subagents/protocol-events.ts
|
|
2685
|
-
var SUBAGENT_DELEGATION_PROTOCOL_VERSION = 1;
|
|
2686
|
-
var SUBAGENT_DELEGATION_REQUEST_EVENT = "prompt-template:subagent:request";
|
|
2687
|
-
var SUBAGENT_DELEGATION_STARTED_EVENT = "prompt-template:subagent:started";
|
|
2688
|
-
var SUBAGENT_DELEGATION_UPDATE_EVENT = "prompt-template:subagent:update";
|
|
2689
|
-
var SUBAGENT_DELEGATION_RESPONSE_EVENT = "prompt-template:subagent:response";
|
|
2690
|
-
var SUBAGENT_DELEGATION_CANCEL_EVENT = "prompt-template:subagent:cancel";
|
|
2691
|
-
// src/harness/delegation-recovery-validation.ts
|
|
2692
|
-
function completionMatchesResult(diagnostic, result, policy) {
|
|
2693
|
-
const value = diagnostic.completionValue;
|
|
2694
|
-
if (!value)
|
|
2695
|
-
return false;
|
|
2696
|
-
const expectedKeys = [
|
|
2697
|
-
"outcome",
|
|
2698
|
-
"summary",
|
|
2699
|
-
...result.artifact === undefined ? [] : ["artifact"],
|
|
2700
|
-
...result.workspace === undefined ? [] : ["workspace"]
|
|
2701
|
-
].sort();
|
|
2702
|
-
const actualKeys = Object.keys(value).sort();
|
|
2703
|
-
if (actualKeys.length !== expectedKeys.length || !actualKeys.every((key, index) => key === expectedKeys[index])) {
|
|
2704
|
-
return false;
|
|
2705
|
-
}
|
|
2706
|
-
try {
|
|
2707
|
-
const completion = parseDelegatedStepResult({
|
|
2708
|
-
...value,
|
|
2709
|
-
version: 1,
|
|
2710
|
-
policyDigest: policy.policyDigest
|
|
2711
|
-
}, policy);
|
|
2712
|
-
return completion.outcome === result.outcome && completion.summary === result.summary && completion.artifact === result.artifact && completion.workspace?.cwd === result.workspace?.cwd;
|
|
2713
|
-
} catch {
|
|
2714
|
-
return false;
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
|
-
function recoveredProjectionError(active, response, diagnostic) {
|
|
2718
|
-
if (response.agent !== active.agent) {
|
|
2719
|
-
return `terminal agent identity is ${JSON.stringify(response.agent)}; expected ${JSON.stringify(active.agent)}`;
|
|
2720
|
-
}
|
|
2721
|
-
if (response.childIndex !== 0) {
|
|
2722
|
-
return `terminal child index is ${JSON.stringify(response.childIndex)}; expected 0`;
|
|
2723
|
-
}
|
|
2724
|
-
if (typeof response.exitCode !== "number" || !Number.isSafeInteger(response.exitCode) || response.exitCode <= 0) {
|
|
2725
|
-
return `terminal exit code is ${JSON.stringify(response.exitCode)}; expected a positive safe integer`;
|
|
2726
|
-
}
|
|
2727
|
-
const execution = response.execution;
|
|
2728
|
-
if (!execution)
|
|
2729
|
-
return "terminal response has no execution projection";
|
|
2730
|
-
if (execution.status !== "failed" || execution.success) {
|
|
2731
|
-
return `execution projection is ${JSON.stringify({
|
|
2732
|
-
status: execution.status,
|
|
2733
|
-
success: execution.success
|
|
2734
|
-
})}; expected failed/false`;
|
|
2735
|
-
}
|
|
2736
|
-
if (execution.exitCode !== response.exitCode) {
|
|
2737
|
-
return `execution exit code ${JSON.stringify(execution.exitCode)} does not match terminal exit code ${JSON.stringify(response.exitCode)}`;
|
|
2738
|
-
}
|
|
2739
|
-
if (typeof response.error !== "string" || !response.error || typeof execution.error !== "string" || execution.error !== response.error) {
|
|
2740
|
-
return "terminal and execution errors are missing or do not match exactly";
|
|
2741
|
-
}
|
|
2742
|
-
const warnings = response.warnings;
|
|
2743
|
-
if (warnings !== undefined && (!Array.isArray(warnings) || warnings.some((warning) => typeof warning !== "string" || warning.trim().length > 0))) {
|
|
2744
|
-
return `terminal response contains warning evidence: ${JSON.stringify(warnings)}`;
|
|
2745
|
-
}
|
|
2746
|
-
if (diagnostic.transcriptToolCount !== undefined && response.toolCount !== undefined && response.toolCount !== diagnostic.transcriptToolCount) {
|
|
2747
|
-
return `terminal tool count ${response.toolCount} does not match transcript tool count ${diagnostic.transcriptToolCount}`;
|
|
2748
|
-
}
|
|
2749
|
-
if (diagnostic.transcriptTurnCount !== undefined && response.turns !== undefined && response.turns !== diagnostic.transcriptTurnCount) {
|
|
2750
|
-
return `terminal turn count ${response.turns} does not match transcript turn count ${diagnostic.transcriptTurnCount}`;
|
|
2751
|
-
}
|
|
2752
|
-
const toolFailure = response.error.match(/^\s*([a-z][\w-]*) failed\s*\(exit\s+(\d+)\)\s*:/i);
|
|
2753
|
-
if (!toolFailure) {
|
|
2754
|
-
return 'terminal error is not a recognized "<tool> failed (exit N): <detail>" failure';
|
|
2755
|
-
}
|
|
2756
|
-
const terminalTool = toolFailure[1];
|
|
2757
|
-
const terminalExitCodeText = toolFailure[2];
|
|
2758
|
-
if (!terminalTool || !terminalExitCodeText) {
|
|
2759
|
-
return "terminal error does not contain a complete tool failure projection";
|
|
2760
|
-
}
|
|
2761
|
-
const terminalExitCode = Number(terminalExitCodeText);
|
|
2762
|
-
if (terminalTool.toLowerCase() !== diagnostic.tool.toLowerCase() || terminalExitCode !== response.exitCode) {
|
|
2763
|
-
return `terminal tool/exit ${JSON.stringify({
|
|
2764
|
-
tool: terminalTool,
|
|
2765
|
-
exitCode: terminalExitCode
|
|
2766
|
-
})} does not match the correlated failure ${JSON.stringify({
|
|
2767
|
-
tool: diagnostic.tool,
|
|
2768
|
-
exitCode: response.exitCode
|
|
2769
|
-
})}`;
|
|
2770
|
-
}
|
|
2771
|
-
const unsafeFlag = [
|
|
2772
|
-
["interrupted", execution.interrupted],
|
|
2773
|
-
["timedOut", execution.timedOut],
|
|
2774
|
-
["stopped", execution.stopped],
|
|
2775
|
-
["detached", execution.detached]
|
|
2776
|
-
].find(([, isEnabled]) => isEnabled === true)?.[0];
|
|
2777
|
-
return unsafeFlag ? `execution projection reports ${unsafeFlag}=true` : undefined;
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
|
-
// src/harness/delegation-retry-policy.ts
|
|
2781
|
-
var MAX_FAILURE_FIELD_CHARS = 1600;
|
|
2782
|
-
var MAX_DELEGATION_RECOVERY_ATTEMPTS = 2;
|
|
2783
|
-
var normalizedFingerprintField = (value) => value?.trim().replaceAll(/\s+/g, " ") || undefined;
|
|
2784
|
-
function delegationFailureFingerprint(failure) {
|
|
2785
|
-
return digest({
|
|
2786
|
-
status: failure.status,
|
|
2787
|
-
exitCode: failure.exitCode,
|
|
2788
|
-
error: normalizedFingerprintField(failure.error),
|
|
2789
|
-
recoveryBlocker: failure.recoveryBlocker,
|
|
2790
|
-
replayToolCount: failure.replayAudit?.toolCount,
|
|
2791
|
-
diagnostic: failure.diagnostic ? {
|
|
2792
|
-
tool: normalizedFingerprintField(failure.diagnostic.tool),
|
|
2793
|
-
call: normalizedFingerprintField(failure.diagnostic.call),
|
|
2794
|
-
output: normalizedFingerprintField(failure.diagnostic.output),
|
|
2795
|
-
postCompletionWarning: normalizedFingerprintField(failure.diagnostic.postCompletionWarning)
|
|
2796
|
-
} : undefined
|
|
2797
|
-
});
|
|
2798
|
-
}
|
|
2799
|
-
function boundedFailureField(value) {
|
|
2800
|
-
if (value.length <= MAX_FAILURE_FIELD_CHARS)
|
|
2801
|
-
return value;
|
|
2802
|
-
const marker = "… [truncated] …";
|
|
2803
|
-
const available = MAX_FAILURE_FIELD_CHARS - marker.length - 2;
|
|
2804
|
-
const startLength = Math.ceil(available / 2);
|
|
2805
|
-
const endLength = Math.floor(available / 2);
|
|
2806
|
-
return `${value.slice(0, startLength)}
|
|
2807
|
-
${marker}
|
|
2808
|
-
${value.slice(-endLength)}`;
|
|
2809
|
-
}
|
|
2810
|
-
function isRetryableTerminalFailure(failure) {
|
|
2811
|
-
if (failure.recoveryBlocker !== undefined)
|
|
2812
|
-
return false;
|
|
2813
|
-
if (failure.diagnostic?.postCompletionWarning !== undefined)
|
|
2814
|
-
return false;
|
|
2815
|
-
if (failure.status === "timed_out" || failure.status === "turn_budget_exhausted" || failure.status === "tool_budget_exhausted") {
|
|
2816
|
-
return true;
|
|
2817
|
-
}
|
|
2818
|
-
if (failure.status !== "failed" && failure.status !== "structured_output_failed") {
|
|
2819
|
-
return false;
|
|
2820
|
-
}
|
|
2821
|
-
return failure.error !== undefined || Number.isSafeInteger(failure.exitCode) && failure.exitCode !== 0;
|
|
2822
|
-
}
|
|
2823
|
-
function isSafeToRetryDelegation(policy, isReplayExplicitlyAuthorized, replayAudit) {
|
|
2824
|
-
return replayAudit?.replaySafe === true && (isReplayExplicitlyAuthorized || policy.permissions.bash.mode === "deny");
|
|
2825
|
-
}
|
|
2826
|
-
function rejectedRecoveryReason(failure, error) {
|
|
2827
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
2828
|
-
return `${failure.reason}
|
|
2829
|
-
Recovery rejected: ${boundedFailureField(detail)}`;
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
// src/harness/delegation-failure.ts
|
|
2833
|
-
function nonEmptyTerminalError(response) {
|
|
2834
|
-
return [response.error, response.execution?.error].find((error) => typeof error === "string" && error.trim().length > 0);
|
|
2835
|
-
}
|
|
2836
|
-
function nonzeroTerminalExitCode(response) {
|
|
2837
|
-
return [response.exitCode, response.execution?.exitCode].find((exitCode) => typeof exitCode === "number" && Number.isSafeInteger(exitCode) && exitCode !== 0);
|
|
2838
|
-
}
|
|
2839
|
-
function hasContradictoryCompletion(response) {
|
|
2840
|
-
return response.status === "completed" && (nonEmptyTerminalError(response) !== undefined || nonzeroTerminalExitCode(response) !== undefined);
|
|
2841
|
-
}
|
|
2842
|
-
function recoveryBlocker(response) {
|
|
2843
|
-
const execution = response.execution;
|
|
2844
|
-
const fileMutation = response.effects?.fileMutation;
|
|
2845
|
-
if (fileMutation?.attempted === true || fileMutation?.status === "observed") {
|
|
2846
|
-
return "reported-mutation";
|
|
2847
|
-
}
|
|
2848
|
-
if (execution?.detached === true || execution?.status === "detached") {
|
|
2849
|
-
return "detached";
|
|
2850
|
-
}
|
|
2851
|
-
if (execution?.stopped === true || execution?.status === "stopped") {
|
|
2852
|
-
return "stopped";
|
|
2853
|
-
}
|
|
2854
|
-
if (response.status === "interrupted" || execution?.interrupted === true || execution?.status === "paused") {
|
|
2855
|
-
return "interrupted";
|
|
2856
|
-
}
|
|
2857
|
-
if (response.status === "cancelled")
|
|
2858
|
-
return "cancelled";
|
|
2859
|
-
if (execution?.timedOut === true && response.status !== "timed_out") {
|
|
2860
|
-
return "inconsistent-timeout";
|
|
2861
|
-
}
|
|
2862
|
-
return;
|
|
2863
|
-
}
|
|
2864
|
-
function validateReplayAudit(response, replayAudit) {
|
|
2865
|
-
if (!replayAudit)
|
|
2866
|
-
return;
|
|
2867
|
-
if (response.toolCount !== undefined && response.toolCount !== replayAudit.toolCount) {
|
|
2868
|
-
return { ...replayAudit, replaySafe: false };
|
|
2869
|
-
}
|
|
2870
|
-
return replayAudit;
|
|
2871
|
-
}
|
|
2872
|
-
function createDelegationFailureActions(dependencies) {
|
|
2873
|
-
const responseIdentity = (active, response) => {
|
|
2874
|
-
if (response.childIndex !== 0 || typeof response.runId !== "string" || response.agent !== undefined && response.agent !== active.agent) {
|
|
2875
|
-
return;
|
|
2876
|
-
}
|
|
2877
|
-
return { runId: response.runId, childIndex: 0 };
|
|
2878
|
-
};
|
|
2879
|
-
const completedResponseAudit = async (active, response) => {
|
|
2880
|
-
if (!dependencies.auditCompletedDelegationTranscript) {
|
|
2881
|
-
return {
|
|
2882
|
-
verified: false,
|
|
2883
|
-
reason: "completed transcript auditing is unavailable"
|
|
2884
|
-
};
|
|
2885
|
-
}
|
|
2886
|
-
return dependencies.auditCompletedDelegationTranscript(response.sessionFile, active.trustedSessionRoot, responseIdentity(active, response));
|
|
2887
|
-
};
|
|
2888
|
-
const describeDelegationFailure = async (active, response) => {
|
|
2889
|
-
const blocker = recoveryBlocker(response);
|
|
2890
|
-
const terminalError = nonEmptyTerminalError(response);
|
|
2891
|
-
const error = terminalError ?? "The subagent returned no terminal error details.";
|
|
2892
|
-
const identity = responseIdentity(active, response);
|
|
2893
|
-
const [diagnostic, replayAudit] = await Promise.all([
|
|
2894
|
-
dependencies.readToolFailureDiagnostic(response.sessionFile, active.trustedSessionRoot, identity, failedToolName(terminalError), terminalError),
|
|
2895
|
-
dependencies.readDelegationReplayAudit(response.sessionFile, active.trustedSessionRoot, identity, {
|
|
2896
|
-
task: active.transcriptTask,
|
|
2897
|
-
bashPermission: active.policy.permissions.bash
|
|
2898
|
-
})
|
|
2899
|
-
]);
|
|
2900
|
-
const validatedReplayAudit = validateReplayAudit(response, replayAudit);
|
|
2901
|
-
const exitCode = nonzeroTerminalExitCode(response) ?? response.exitCode ?? response.execution?.exitCode;
|
|
2902
|
-
const reason = [
|
|
2903
|
-
hasContradictoryCompletion(response) ? `Subagent "${active.agent}" reported terminal failure signals with completed status.` : `Subagent "${active.agent}" ${response.status.replaceAll("_", " ")}.`,
|
|
2904
|
-
...diagnostic ? formatToolFailureDiagnostic(diagnostic) : [],
|
|
2905
|
-
...exitCode !== undefined ? [`Subagent exit code: ${exitCode}`] : [],
|
|
2906
|
-
...blocker ? [`Automatic recovery blocked by: ${blocker.replaceAll("-", " ")}`] : [],
|
|
2907
|
-
`Terminal error: ${boundedFailureField(error)}`,
|
|
2908
|
-
...diagnostic && response.sessionFile ? [
|
|
2909
|
-
`Diagnostic session: ${boundedFailureField(response.sessionFile.replaceAll(/\s+/g, " "))}`
|
|
2910
|
-
] : []
|
|
2911
|
-
].join(`
|
|
2912
|
-
`);
|
|
2913
|
-
return {
|
|
2914
|
-
reason,
|
|
2915
|
-
status: response.status,
|
|
2916
|
-
...terminalError ? { error: terminalError } : {},
|
|
2917
|
-
...exitCode !== undefined ? { exitCode } : {},
|
|
2918
|
-
...blocker ? { recoveryBlocker: blocker } : {},
|
|
2919
|
-
...diagnostic ? { diagnostic } : {},
|
|
2920
|
-
...validatedReplayAudit ? { replayAudit: validatedReplayAudit } : {}
|
|
2921
|
-
};
|
|
2922
|
-
};
|
|
2923
|
-
return {
|
|
2924
|
-
delegationFailureFingerprint,
|
|
2925
|
-
hasContradictoryCompletion,
|
|
2926
|
-
completedResponseAudit,
|
|
2927
|
-
isRetryableTerminalFailure,
|
|
2928
|
-
isSafeToRetryDelegation,
|
|
2929
|
-
rejectedRecoveryReason,
|
|
2930
|
-
completionMatchesResult,
|
|
2931
|
-
recoveredProjectionError,
|
|
2932
|
-
describeDelegationFailure
|
|
2933
|
-
};
|
|
2934
|
-
}
|
|
2935
|
-
|
|
2936
|
-
// src/harness/catalog.ts
|
|
2937
|
-
function createEmptyCatalog() {
|
|
2938
|
-
return {
|
|
2939
|
-
workflows: new Map,
|
|
2940
|
-
settings: DEFAULT_SETTINGS,
|
|
2941
|
-
diagnostics: [],
|
|
2942
|
-
userDirectory: ""
|
|
2943
|
-
};
|
|
2944
|
-
}
|
|
2945
|
-
function formatCatalogDiagnostics(catalog) {
|
|
2946
|
-
const shownDiagnostics = catalog.diagnostics.slice(0, 3).map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`);
|
|
2947
|
-
const remainingCount = catalog.diagnostics.length - shownDiagnostics.length;
|
|
2948
|
-
return [
|
|
2949
|
-
...shownDiagnostics,
|
|
2950
|
-
...remainingCount > 0 ? [`${remainingCount} more diagnostic(s)`] : []
|
|
2951
|
-
].join(`
|
|
2952
|
-
`);
|
|
2953
|
-
}
|
|
2954
|
-
function parseAvailableSkills(systemPrompt) {
|
|
2955
|
-
const sections = [
|
|
2956
|
-
...systemPrompt.matchAll(/<available_skills>([\s\S]*?)<\/available_skills>/g)
|
|
2957
|
-
];
|
|
2958
|
-
const section = sections.at(-1)?.[1] ?? "";
|
|
2959
|
-
return [...section.matchAll(/<name>([^<]+)<\/name>/g)].flatMap((match) => {
|
|
2960
|
-
const name = match[1]?.trim();
|
|
2961
|
-
return name ? [{ name }] : [];
|
|
2962
|
-
});
|
|
2963
|
-
}
|
|
2964
|
-
|
|
2965
|
-
// src/harness/dependencies.ts
|
|
2966
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
2967
|
-
import { constants as constants3, mkdtempSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2968
|
-
import { lstat as lstat3, open as open3, rm } from "node:fs/promises";
|
|
2969
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
2970
|
-
import { join as join5 } from "node:path";
|
|
2971
|
-
|
|
2972
|
-
// src/integrations/plannotator-responses.ts
|
|
2973
|
-
var isRecord3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2974
|
-
var errorText = (value, fallback) => typeof value.error === "string" && value.error.trim() ? value.error : fallback;
|
|
2975
|
-
var normalizePlannotatorStartResponse = (value) => {
|
|
2976
|
-
if (!isRecord3(value)) {
|
|
2977
|
-
return {
|
|
2978
|
-
status: "error",
|
|
2979
|
-
error: "Plannotator returned an invalid response"
|
|
2980
|
-
};
|
|
2981
|
-
}
|
|
2982
|
-
if (value.status === "unavailable") {
|
|
2983
|
-
return {
|
|
2984
|
-
status: "unavailable",
|
|
2985
|
-
error: errorText(value, "Plannotator is unavailable")
|
|
2986
|
-
};
|
|
2987
|
-
}
|
|
2988
|
-
if (value.status === "error") {
|
|
2989
|
-
return {
|
|
2990
|
-
status: "error",
|
|
2991
|
-
error: errorText(value, "Plannotator failed")
|
|
2992
|
-
};
|
|
2993
|
-
}
|
|
2994
|
-
const result = isRecord3(value.result) ? value.result : undefined;
|
|
2995
|
-
if (value.status === "handled" && result?.status === "pending" && typeof result.reviewId === "string") {
|
|
2996
|
-
return {
|
|
2997
|
-
status: "handled",
|
|
2998
|
-
result: { status: "pending", reviewId: result.reviewId }
|
|
2999
|
-
};
|
|
3000
|
-
}
|
|
3001
|
-
return {
|
|
3002
|
-
status: "error",
|
|
3003
|
-
error: "Plannotator returned an invalid start result"
|
|
3004
|
-
};
|
|
3005
|
-
};
|
|
3006
|
-
var normalizePlannotatorStatusResponse = (value, requestedReviewId) => {
|
|
3007
|
-
if (!isRecord3(value)) {
|
|
3008
|
-
return {
|
|
3009
|
-
status: "error",
|
|
3010
|
-
error: "Plannotator returned an invalid response"
|
|
3011
|
-
};
|
|
3012
|
-
}
|
|
3013
|
-
if (value.status === "unavailable") {
|
|
3014
|
-
return {
|
|
3015
|
-
status: "unavailable",
|
|
3016
|
-
error: errorText(value, "Plannotator is unavailable")
|
|
3017
|
-
};
|
|
3018
|
-
}
|
|
3019
|
-
if (value.status === "error") {
|
|
3020
|
-
return {
|
|
3021
|
-
status: "error",
|
|
3022
|
-
error: errorText(value, "Plannotator failed")
|
|
3023
|
-
};
|
|
3024
|
-
}
|
|
3025
|
-
const result = isRecord3(value.result) ? value.result : undefined;
|
|
3026
|
-
if (value.status !== "handled" || !result) {
|
|
3027
|
-
return {
|
|
3028
|
-
status: "error",
|
|
3029
|
-
error: "Plannotator returned an invalid status result"
|
|
3030
|
-
};
|
|
3031
|
-
}
|
|
3032
|
-
if (result.status === "pending" || result.status === "missing") {
|
|
3033
|
-
return { status: "handled", result: { status: result.status } };
|
|
3034
|
-
}
|
|
3035
|
-
if (result.status === "completed" && typeof result.reviewId === "string" && typeof result.approved === "boolean") {
|
|
3036
|
-
if (result.reviewId !== requestedReviewId) {
|
|
3037
|
-
return {
|
|
3038
|
-
status: "error",
|
|
3039
|
-
error: "Plannotator returned a result for a different review"
|
|
3040
|
-
};
|
|
3041
|
-
}
|
|
3042
|
-
return {
|
|
3043
|
-
status: "handled",
|
|
3044
|
-
result: {
|
|
3045
|
-
status: "completed",
|
|
3046
|
-
reviewId: result.reviewId,
|
|
3047
|
-
approved: result.approved,
|
|
3048
|
-
feedback: typeof result.feedback === "string" ? result.feedback : ""
|
|
3049
|
-
}
|
|
3050
|
-
};
|
|
3051
|
-
}
|
|
3052
|
-
return {
|
|
3053
|
-
status: "error",
|
|
3054
|
-
error: "Plannotator returned an invalid status result"
|
|
3055
|
-
};
|
|
1292
|
+
return {
|
|
1293
|
+
status: "error",
|
|
1294
|
+
error: "Plannotator returned an invalid status result"
|
|
1295
|
+
};
|
|
3056
1296
|
};
|
|
3057
1297
|
var parsePlannotatorResult = (value) => {
|
|
3058
|
-
if (!
|
|
1298
|
+
if (!isRecord(value))
|
|
3059
1299
|
return;
|
|
3060
1300
|
if (typeof value.reviewId !== "string" || typeof value.approved !== "boolean") {
|
|
3061
1301
|
return;
|
|
@@ -3084,14 +1324,14 @@ var requestResponse = ({
|
|
|
3084
1324
|
normalize,
|
|
3085
1325
|
ignoreResponse,
|
|
3086
1326
|
dependencies
|
|
3087
|
-
}) => new Promise((
|
|
1327
|
+
}) => new Promise((resolve3) => {
|
|
3088
1328
|
let isSettled = false;
|
|
3089
1329
|
const finish = (response) => {
|
|
3090
1330
|
if (isSettled || ignoreResponse?.(response))
|
|
3091
1331
|
return;
|
|
3092
1332
|
isSettled = true;
|
|
3093
1333
|
dependencies.cancelTimeout(timer);
|
|
3094
|
-
|
|
1334
|
+
resolve3(normalize(response));
|
|
3095
1335
|
};
|
|
3096
1336
|
const timer = dependencies.scheduleTimeout(() => {
|
|
3097
1337
|
finish(timeoutResponse);
|
|
@@ -3103,8 +1343,8 @@ var requestResponse = ({
|
|
|
3103
1343
|
});
|
|
3104
1344
|
});
|
|
3105
1345
|
var MISSING_PLAN_CONTENT_RESPONSE = "Missing planContent for plan-review request.";
|
|
3106
|
-
var
|
|
3107
|
-
var isUnclaimedPlanReviewResponse = (response) =>
|
|
1346
|
+
var isRecord2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1347
|
+
var isUnclaimedPlanReviewResponse = (response) => isRecord2(response) && response.status === "error" && response.error === MISSING_PLAN_CONTENT_RESPONSE;
|
|
3108
1348
|
var singleConsumerPlanPayload = (planContent, origin) => {
|
|
3109
1349
|
let claimed = false;
|
|
3110
1350
|
return {
|
|
@@ -3177,201 +1417,162 @@ ${artifact}`, [APPROVE, REQUEST_CHANGES, PAUSE], ...selectionOptions(signal));
|
|
|
3177
1417
|
};
|
|
3178
1418
|
};
|
|
3179
1419
|
|
|
3180
|
-
// src/integrations/subagents/client
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
"
|
|
3185
|
-
"
|
|
3186
|
-
"
|
|
3187
|
-
"
|
|
3188
|
-
"
|
|
3189
|
-
"
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
var
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
1420
|
+
// src/integrations/subagents/client.ts
|
|
1421
|
+
import { spawn } from "node:child_process";
|
|
1422
|
+
import { StringDecoder } from "node:string_decoder";
|
|
1423
|
+
var directWorkerCommand = (request) => [
|
|
1424
|
+
"--no-session",
|
|
1425
|
+
"--mode",
|
|
1426
|
+
"json",
|
|
1427
|
+
...request.model ? ["--model", request.model] : [],
|
|
1428
|
+
...request.thinking ? ["--thinking", request.thinking] : [],
|
|
1429
|
+
"--print",
|
|
1430
|
+
request.task
|
|
1431
|
+
];
|
|
1432
|
+
function directWorkerResponse(request, code, signal, stderr) {
|
|
1433
|
+
const status = code === 0 ? "completed" : signal ? "cancelled" : "failed";
|
|
1434
|
+
return {
|
|
1435
|
+
requestId: request.requestId,
|
|
1436
|
+
agent: request.agent,
|
|
1437
|
+
status,
|
|
1438
|
+
...code === null ? {} : { exitCode: code },
|
|
1439
|
+
...status !== "completed" && stderr.trim() ? { error: stderr.trim().slice(-4000) } : {}
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
var MAX_PROGRESS_DETAIL_CHARS = 480;
|
|
1443
|
+
var SECRET_KEY = /authorization|cookie|password|secret|token|api[-_]?key/i;
|
|
1444
|
+
function redactProgressValue(value, key = "") {
|
|
1445
|
+
if (SECRET_KEY.test(key))
|
|
1446
|
+
return "[redacted]";
|
|
1447
|
+
if (typeof value === "string") {
|
|
1448
|
+
return value.length > MAX_PROGRESS_DETAIL_CHARS ? `${value.slice(0, MAX_PROGRESS_DETAIL_CHARS - 1)}…` : value;
|
|
3207
1449
|
}
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
1450
|
+
if (Array.isArray(value))
|
|
1451
|
+
return value.map((item) => redactProgressValue(item));
|
|
1452
|
+
if (value && typeof value === "object") {
|
|
1453
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
1454
|
+
entryKey,
|
|
1455
|
+
redactProgressValue(entryValue, entryKey)
|
|
1456
|
+
]));
|
|
1457
|
+
}
|
|
1458
|
+
return value;
|
|
3211
1459
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
const subscriptions = [];
|
|
3232
|
-
const subscribe = (event, handler) => {
|
|
3233
|
-
const unsubscribe = events.on(event, handler);
|
|
3234
|
-
if (isUnsubscribe(unsubscribe)) {
|
|
3235
|
-
subscriptions.push(() => {
|
|
3236
|
-
unsubscribe();
|
|
3237
|
-
});
|
|
3238
|
-
}
|
|
3239
|
-
};
|
|
3240
|
-
const stopLocalWatchers = () => {
|
|
3241
|
-
dependencies.cancelTimeout(startTimer);
|
|
3242
|
-
dependencies.cancelTimeout(overallTimer);
|
|
3243
|
-
options.signal?.removeEventListener("abort", abort);
|
|
3244
|
-
};
|
|
3245
|
-
const cleanup = () => {
|
|
3246
|
-
stopLocalWatchers();
|
|
3247
|
-
for (const unsubscribe of subscriptions)
|
|
3248
|
-
unsubscribe();
|
|
3249
|
-
releaseActive(request.requestId);
|
|
3250
|
-
};
|
|
3251
|
-
const finish = (result) => {
|
|
3252
|
-
if (isSettled)
|
|
3253
|
-
return;
|
|
3254
|
-
isSettled = true;
|
|
3255
|
-
cleanup();
|
|
3256
|
-
if ("response" in result)
|
|
3257
|
-
resolve7(result.response);
|
|
3258
|
-
else
|
|
3259
|
-
reject2(result.error);
|
|
3260
|
-
};
|
|
3261
|
-
const emitCancel = () => {
|
|
3262
|
-
if (isCancellationRequested || isSettled)
|
|
3263
|
-
return;
|
|
3264
|
-
isCancellationRequested = true;
|
|
3265
|
-
events.emit(SUBAGENT_DELEGATION_CANCEL_EVENT, {
|
|
3266
|
-
version: SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
3267
|
-
requestId: request.requestId
|
|
3268
|
-
});
|
|
3269
|
-
};
|
|
3270
|
-
const failAndCancel = (reason) => {
|
|
3271
|
-
if (isSettled)
|
|
3272
|
-
return;
|
|
3273
|
-
emitCancel();
|
|
3274
|
-
if (isSettled)
|
|
3275
|
-
return;
|
|
3276
|
-
isSettled = true;
|
|
3277
|
-
stopLocalWatchers();
|
|
3278
|
-
reject2(new Error(reason));
|
|
1460
|
+
function formatToolCall(toolName, args) {
|
|
1461
|
+
const rendered = JSON.stringify(redactProgressValue(args));
|
|
1462
|
+
return `call ${toolName} ${rendered}`.slice(0, MAX_PROGRESS_DETAIL_CHARS);
|
|
1463
|
+
}
|
|
1464
|
+
function workerProgressFromJsonLine(line, requestId, toolCount, responseText = "") {
|
|
1465
|
+
let event;
|
|
1466
|
+
try {
|
|
1467
|
+
const parsed = JSON.parse(line);
|
|
1468
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
1469
|
+
return { toolCount, responseText };
|
|
1470
|
+
event = parsed;
|
|
1471
|
+
} catch {
|
|
1472
|
+
return { toolCount, responseText };
|
|
1473
|
+
}
|
|
1474
|
+
if (event.type === "agent_start") {
|
|
1475
|
+
return {
|
|
1476
|
+
toolCount,
|
|
1477
|
+
responseText,
|
|
1478
|
+
update: { requestId, activity: "thinking", toolCount }
|
|
3279
1479
|
};
|
|
3280
|
-
|
|
3281
|
-
|
|
1480
|
+
}
|
|
1481
|
+
if (event.type === "message_start" && event.message?.role === "assistant") {
|
|
1482
|
+
return { toolCount, responseText: "" };
|
|
1483
|
+
}
|
|
1484
|
+
if (event.type === "message_update" && event.assistantMessageEvent?.type === "text_delta" && typeof event.assistantMessageEvent.delta === "string") {
|
|
1485
|
+
const nextResponseText = `${responseText}${event.assistantMessageEvent.delta}`.slice(-MAX_PROGRESS_DETAIL_CHARS);
|
|
1486
|
+
return {
|
|
1487
|
+
toolCount,
|
|
1488
|
+
responseText: nextResponseText,
|
|
1489
|
+
update: {
|
|
1490
|
+
requestId,
|
|
1491
|
+
activity: "responding",
|
|
1492
|
+
detail: `response: ${nextResponseText}`,
|
|
1493
|
+
toolCount
|
|
1494
|
+
}
|
|
3282
1495
|
};
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
});
|
|
3295
|
-
subscribe(SUBAGENT_DELEGATION_RESPONSE_EVENT, (data) => {
|
|
3296
|
-
const response = parseDelegationResponse(data);
|
|
3297
|
-
if (!response || response.requestId !== request.requestId)
|
|
3298
|
-
return;
|
|
3299
|
-
resolveTerminal();
|
|
3300
|
-
if (isSettled) {
|
|
3301
|
-
cleanup();
|
|
3302
|
-
options.onLateTerminal?.(response);
|
|
3303
|
-
return;
|
|
1496
|
+
}
|
|
1497
|
+
if ((event.type === "tool_execution_start" || event.type === "tool_execution_update") && typeof event.toolName === "string") {
|
|
1498
|
+
const nextToolCount = event.type === "tool_execution_start" ? toolCount + 1 : toolCount;
|
|
1499
|
+
return {
|
|
1500
|
+
toolCount: nextToolCount,
|
|
1501
|
+
responseText,
|
|
1502
|
+
update: {
|
|
1503
|
+
requestId,
|
|
1504
|
+
currentTool: event.toolName,
|
|
1505
|
+
...event.type === "tool_execution_start" ? { detail: formatToolCall(event.toolName, event.args) } : {},
|
|
1506
|
+
toolCount: nextToolCount
|
|
3304
1507
|
}
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
const overallTimer = dependencies.scheduleTimeout(() => {
|
|
3311
|
-
failAndCancel("pi-subagents did not settle the delegation request before its deadline");
|
|
3312
|
-
}, (request.timeoutMs ?? 900000) + 5000);
|
|
3313
|
-
startTimer.unref();
|
|
3314
|
-
overallTimer.unref();
|
|
3315
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
3316
|
-
});
|
|
3317
|
-
return {
|
|
3318
|
-
active: {
|
|
3319
|
-
requestId: request.requestId,
|
|
3320
|
-
requestCancellation: () => {
|
|
3321
|
-
requestCancellation();
|
|
3322
|
-
},
|
|
3323
|
-
terminal
|
|
3324
|
-
},
|
|
3325
|
-
promise,
|
|
3326
|
-
start: () => {
|
|
3327
|
-
events.emit(SUBAGENT_DELEGATION_REQUEST_EVENT, request);
|
|
3328
|
-
}
|
|
3329
|
-
};
|
|
3330
|
-
};
|
|
3331
|
-
|
|
3332
|
-
// src/integrations/subagents/client.ts
|
|
3333
|
-
function createSubagentDelegationClient(events, dependencies = DEFAULT_CLIENT_DEPENDENCIES) {
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
return { toolCount, responseText };
|
|
1511
|
+
}
|
|
1512
|
+
function createSubagentDelegationClient(spawnWorker = spawn) {
|
|
3334
1513
|
let active;
|
|
3335
1514
|
const delegate = (request, options = {}) => {
|
|
3336
1515
|
if (active) {
|
|
3337
|
-
return Promise.reject(new Error(`
|
|
1516
|
+
return Promise.reject(new Error(`workflow worker "${active.requestId}" is still active`));
|
|
3338
1517
|
}
|
|
3339
1518
|
if (options.signal?.aborted) {
|
|
3340
|
-
return Promise.reject(new Error("
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
1519
|
+
return Promise.reject(new Error("workflow worker was cancelled"));
|
|
1520
|
+
}
|
|
1521
|
+
return new Promise((resolve3, reject) => {
|
|
1522
|
+
const child = spawnWorker("pi", [...directWorkerCommand(request)], {
|
|
1523
|
+
cwd: request.cwd,
|
|
1524
|
+
env: {
|
|
1525
|
+
...process.env,
|
|
1526
|
+
PI_WORKFLOWS_CHILD: "1",
|
|
1527
|
+
PI_WORKFLOWS_CHILD_AGENT: request.agent
|
|
1528
|
+
},
|
|
1529
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1530
|
+
});
|
|
1531
|
+
active = { requestId: request.requestId, process: child };
|
|
1532
|
+
let stderr = "";
|
|
1533
|
+
let stdoutBuffer = "";
|
|
1534
|
+
let toolCount = 0;
|
|
1535
|
+
let responseText = "";
|
|
1536
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
1537
|
+
const consumeWorkerLines = () => {
|
|
1538
|
+
while (true) {
|
|
1539
|
+
const newline = stdoutBuffer.indexOf(`
|
|
1540
|
+
`);
|
|
1541
|
+
if (newline === -1)
|
|
1542
|
+
return;
|
|
1543
|
+
const line = stdoutBuffer.slice(0, newline);
|
|
1544
|
+
stdoutBuffer = stdoutBuffer.slice(newline + 1);
|
|
1545
|
+
const progress = workerProgressFromJsonLine(line, request.requestId, toolCount, responseText);
|
|
1546
|
+
toolCount = progress.toolCount;
|
|
1547
|
+
responseText = progress.responseText;
|
|
1548
|
+
if (progress.update)
|
|
1549
|
+
options.onUpdate?.(progress.update);
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
const consumeWorkerOutput = (chunk) => {
|
|
1553
|
+
stdoutBuffer += stdoutDecoder.write(chunk);
|
|
1554
|
+
consumeWorkerLines();
|
|
3369
1555
|
};
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
1556
|
+
child.stdout.on("data", consumeWorkerOutput);
|
|
1557
|
+
child.stderr.on("data", (chunk) => {
|
|
1558
|
+
stderr += chunk.toString("utf8");
|
|
1559
|
+
});
|
|
1560
|
+
const abort = () => {
|
|
1561
|
+
child.kill("SIGTERM");
|
|
1562
|
+
};
|
|
1563
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
1564
|
+
child.once("error", (error) => {
|
|
1565
|
+
if (active?.process === child)
|
|
1566
|
+
active = undefined;
|
|
1567
|
+
reject(error);
|
|
1568
|
+
});
|
|
1569
|
+
child.once("close", (code, signal) => {
|
|
1570
|
+
stdoutBuffer += stdoutDecoder.end();
|
|
1571
|
+
consumeWorkerLines();
|
|
1572
|
+
if (active?.process === child)
|
|
1573
|
+
active = undefined;
|
|
1574
|
+
options.signal?.removeEventListener("abort", abort);
|
|
1575
|
+
resolve3(directWorkerResponse(request, code, signal, stderr));
|
|
3375
1576
|
});
|
|
3376
1577
|
});
|
|
3377
1578
|
};
|
|
@@ -3380,34 +1581,41 @@ function createSubagentDelegationClient(events, dependencies = DEFAULT_CLIENT_DE
|
|
|
3380
1581
|
return active?.requestId;
|
|
3381
1582
|
},
|
|
3382
1583
|
delegate,
|
|
3383
|
-
cancelActiveAndWait
|
|
1584
|
+
async cancelActiveAndWait() {
|
|
1585
|
+
const current = active;
|
|
1586
|
+
if (!current)
|
|
1587
|
+
return true;
|
|
1588
|
+
current.process.kill("SIGTERM");
|
|
1589
|
+
return new Promise((resolve3) => {
|
|
1590
|
+
current.process.once("close", () => {
|
|
1591
|
+
resolve3(true);
|
|
1592
|
+
});
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
3384
1595
|
};
|
|
3385
1596
|
}
|
|
3386
1597
|
|
|
3387
1598
|
class SubagentDelegationClient {
|
|
3388
|
-
#
|
|
3389
|
-
constructor(events, dependencies = DEFAULT_CLIENT_DEPENDENCIES) {
|
|
3390
|
-
this.#controller = createSubagentDelegationClient(events, dependencies);
|
|
3391
|
-
}
|
|
1599
|
+
#client = createSubagentDelegationClient();
|
|
3392
1600
|
get activeRequestId() {
|
|
3393
|
-
return this.#
|
|
1601
|
+
return this.#client.activeRequestId;
|
|
3394
1602
|
}
|
|
3395
|
-
delegate(request, options
|
|
3396
|
-
return this.#
|
|
1603
|
+
delegate(request, options) {
|
|
1604
|
+
return this.#client.delegate(request, options);
|
|
3397
1605
|
}
|
|
3398
|
-
cancelActiveAndWait(waitMs
|
|
3399
|
-
return this.#
|
|
1606
|
+
cancelActiveAndWait(waitMs) {
|
|
1607
|
+
return this.#client.cancelActiveAndWait(waitMs);
|
|
3400
1608
|
}
|
|
3401
1609
|
}
|
|
3402
1610
|
|
|
3403
1611
|
// src/policy/completion-batch.ts
|
|
3404
|
-
var
|
|
1612
|
+
var isRecord3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3405
1613
|
var toolCalls = (message) => {
|
|
3406
|
-
if (!
|
|
1614
|
+
if (!isRecord3(message))
|
|
3407
1615
|
return [];
|
|
3408
1616
|
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
3409
1617
|
return [];
|
|
3410
|
-
return message.content.filter((item) =>
|
|
1618
|
+
return message.content.filter((item) => isRecord3(item) && item.type === "toolCall" && typeof item.id === "string" && typeof item.name === "string");
|
|
3411
1619
|
};
|
|
3412
1620
|
function invalidCompletionCallIds(message, completionTool) {
|
|
3413
1621
|
const calls = toolCalls(message);
|
|
@@ -3440,7 +1648,7 @@ var UNSUPPORTED_MCP_PROXY_FIELDS = [
|
|
|
3440
1648
|
"regex",
|
|
3441
1649
|
"includeSchemas"
|
|
3442
1650
|
];
|
|
3443
|
-
var
|
|
1651
|
+
var reject = (reason) => ({
|
|
3444
1652
|
allowed: false,
|
|
3445
1653
|
reason
|
|
3446
1654
|
});
|
|
@@ -3452,21 +1660,185 @@ var selectorAllows = (selectors, server, tool) => selectors.some((selector) => {
|
|
|
3452
1660
|
});
|
|
3453
1661
|
var authorizeMcpProxy = (input, selectors) => {
|
|
3454
1662
|
if (selectors.length === 0) {
|
|
3455
|
-
return
|
|
1663
|
+
return reject("MCP access is disabled for this workflow step");
|
|
3456
1664
|
}
|
|
3457
1665
|
const unsupportedMode = UNSUPPORTED_MCP_PROXY_FIELDS.find((field) => input[field] !== undefined);
|
|
3458
1666
|
if (unsupportedMode) {
|
|
3459
|
-
return
|
|
1667
|
+
return reject(`MCP proxy mode "${unsupportedMode}" is disabled; use an explicit server and tool`);
|
|
3460
1668
|
}
|
|
3461
1669
|
if (typeof input.server !== "string" || !input.server.trim()) {
|
|
3462
|
-
return
|
|
1670
|
+
return reject("MCP proxy calls must name an explicit server");
|
|
3463
1671
|
}
|
|
3464
1672
|
if (typeof input.tool !== "string" || !input.tool.trim()) {
|
|
3465
|
-
return
|
|
1673
|
+
return reject("MCP proxy calls must name an explicit tool");
|
|
3466
1674
|
}
|
|
3467
1675
|
const server = input.server.trim();
|
|
3468
1676
|
const tool = input.tool.trim();
|
|
3469
|
-
return selectorAllows(selectors, server, tool) ? { allowed: true } :
|
|
1677
|
+
return selectorAllows(selectors, server, tool) ? { allowed: true } : reject(`MCP tool "${server}/${tool}" is not allowed for this workflow step`);
|
|
1678
|
+
};
|
|
1679
|
+
// src/policy/bash-authorization.ts
|
|
1680
|
+
import { basename } from "node:path";
|
|
1681
|
+
|
|
1682
|
+
// src/policy/restricted-command.ts
|
|
1683
|
+
var UNQUOTED_SHELL_METACHARACTERS = new Set([
|
|
1684
|
+
";",
|
|
1685
|
+
"&",
|
|
1686
|
+
"|",
|
|
1687
|
+
"<",
|
|
1688
|
+
">",
|
|
1689
|
+
`
|
|
1690
|
+
`,
|
|
1691
|
+
"\r",
|
|
1692
|
+
"`",
|
|
1693
|
+
"$",
|
|
1694
|
+
"(",
|
|
1695
|
+
")",
|
|
1696
|
+
"{",
|
|
1697
|
+
"}",
|
|
1698
|
+
"#",
|
|
1699
|
+
"\x00"
|
|
1700
|
+
]);
|
|
1701
|
+
var PATHNAME_EXPANSION_CHARACTERS = new Set([
|
|
1702
|
+
"*",
|
|
1703
|
+
"?",
|
|
1704
|
+
"[",
|
|
1705
|
+
"]",
|
|
1706
|
+
"~"
|
|
1707
|
+
]);
|
|
1708
|
+
var invalidCharacter = (character) => {
|
|
1709
|
+
if (character === `
|
|
1710
|
+
` || character === "\r" || character === "\x00") {
|
|
1711
|
+
return "multiline and null characters are not allowed";
|
|
1712
|
+
}
|
|
1713
|
+
return;
|
|
1714
|
+
};
|
|
1715
|
+
var tokenizeRestrictedCommand = (command) => {
|
|
1716
|
+
if (!command.trim())
|
|
1717
|
+
return { error: "empty Bash command" };
|
|
1718
|
+
const tokens = [];
|
|
1719
|
+
let token = "";
|
|
1720
|
+
let quote;
|
|
1721
|
+
let isEscaping = false;
|
|
1722
|
+
let isTokenStarted = false;
|
|
1723
|
+
for (const character of command) {
|
|
1724
|
+
if (quote === "'") {
|
|
1725
|
+
const error = invalidCharacter(character);
|
|
1726
|
+
if (error)
|
|
1727
|
+
return { error };
|
|
1728
|
+
if (character === "'") {
|
|
1729
|
+
quote = undefined;
|
|
1730
|
+
} else {
|
|
1731
|
+
token += character;
|
|
1732
|
+
}
|
|
1733
|
+
isTokenStarted = true;
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
if (quote === '"') {
|
|
1737
|
+
const error = invalidCharacter(character);
|
|
1738
|
+
if (error)
|
|
1739
|
+
return { error };
|
|
1740
|
+
if (character === '"') {
|
|
1741
|
+
quote = undefined;
|
|
1742
|
+
} else if (character === "$" || character === "`" || character === "\\") {
|
|
1743
|
+
return {
|
|
1744
|
+
error: "substitutions and escapes are not allowed inside double quotes"
|
|
1745
|
+
};
|
|
1746
|
+
} else {
|
|
1747
|
+
token += character;
|
|
1748
|
+
}
|
|
1749
|
+
isTokenStarted = true;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
if (isEscaping) {
|
|
1753
|
+
const error = invalidCharacter(character);
|
|
1754
|
+
if (error)
|
|
1755
|
+
return { error };
|
|
1756
|
+
token += character;
|
|
1757
|
+
isEscaping = false;
|
|
1758
|
+
isTokenStarted = true;
|
|
1759
|
+
continue;
|
|
1760
|
+
}
|
|
1761
|
+
if (character === "\\") {
|
|
1762
|
+
isEscaping = true;
|
|
1763
|
+
isTokenStarted = true;
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
if (character === "'" || character === '"') {
|
|
1767
|
+
quote = character;
|
|
1768
|
+
isTokenStarted = true;
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
if (UNQUOTED_SHELL_METACHARACTERS.has(character)) {
|
|
1772
|
+
return {
|
|
1773
|
+
error: "shell operators, substitutions, expansions, and comments are not allowed"
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
if (PATHNAME_EXPANSION_CHARACTERS.has(character)) {
|
|
1777
|
+
return {
|
|
1778
|
+
error: "unquoted pathname and tilde expansion are not allowed"
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
if (/\s/u.test(character)) {
|
|
1782
|
+
if (isTokenStarted) {
|
|
1783
|
+
tokens.push(token);
|
|
1784
|
+
token = "";
|
|
1785
|
+
isTokenStarted = false;
|
|
1786
|
+
}
|
|
1787
|
+
continue;
|
|
1788
|
+
}
|
|
1789
|
+
token += character;
|
|
1790
|
+
isTokenStarted = true;
|
|
1791
|
+
}
|
|
1792
|
+
if (isEscaping)
|
|
1793
|
+
return { error: "trailing Bash escape is not allowed" };
|
|
1794
|
+
if (quote)
|
|
1795
|
+
return { error: "unterminated Bash quote" };
|
|
1796
|
+
if (isTokenStarted)
|
|
1797
|
+
tokens.push(token);
|
|
1798
|
+
return tokens.length > 0 ? { tokens } : { error: "empty Bash command" };
|
|
1799
|
+
};
|
|
1800
|
+
|
|
1801
|
+
// src/policy/bash-authorization.ts
|
|
1802
|
+
var SHELL_WRAPPERS = new Set([
|
|
1803
|
+
"bash",
|
|
1804
|
+
"builtin",
|
|
1805
|
+
"command",
|
|
1806
|
+
"env",
|
|
1807
|
+
"exec",
|
|
1808
|
+
"fish",
|
|
1809
|
+
"sh",
|
|
1810
|
+
"time",
|
|
1811
|
+
"xargs",
|
|
1812
|
+
"zsh"
|
|
1813
|
+
]);
|
|
1814
|
+
var ENVIRONMENT_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/u;
|
|
1815
|
+
var reject2 = (reason) => ({
|
|
1816
|
+
allowed: false,
|
|
1817
|
+
reason
|
|
1818
|
+
});
|
|
1819
|
+
var matchesRule = (tokens, rule) => tokens[0] === rule.executable && rule.argsPrefix.every((expected, index) => tokens[index + 1] === expected);
|
|
1820
|
+
var authorizeBash = (command, permission) => {
|
|
1821
|
+
if (permission.mode === "unrestricted") {
|
|
1822
|
+
return { allowed: true };
|
|
1823
|
+
}
|
|
1824
|
+
if (permission.mode === "deny") {
|
|
1825
|
+
return reject2("Bash is disabled for this workflow step");
|
|
1826
|
+
}
|
|
1827
|
+
const parsed = tokenizeRestrictedCommand(command);
|
|
1828
|
+
if (!parsed.tokens)
|
|
1829
|
+
return reject2(parsed.error);
|
|
1830
|
+
const executable = parsed.tokens[0] ?? "";
|
|
1831
|
+
if (SHELL_WRAPPERS.has(basename(executable))) {
|
|
1832
|
+
return reject2(`shell wrapper "${executable}" is not allowed in restricted mode`);
|
|
1833
|
+
}
|
|
1834
|
+
if (ENVIRONMENT_ASSIGNMENT.test(executable)) {
|
|
1835
|
+
return reject2("environment assignments are not allowed in restricted mode");
|
|
1836
|
+
}
|
|
1837
|
+
const rule = permission.allow.find((candidate) => matchesRule(parsed.tokens, candidate));
|
|
1838
|
+
if (!rule) {
|
|
1839
|
+
return reject2("command does not match this step's Bash allow-list");
|
|
1840
|
+
}
|
|
1841
|
+
return { allowed: true, tokens: [...parsed.tokens] };
|
|
3470
1842
|
};
|
|
3471
1843
|
// src/policy/tool-selection.ts
|
|
3472
1844
|
var sourceText = (tool) => `${tool.sourceInfo?.source ?? ""}
|
|
@@ -3665,9 +2037,9 @@ var MAX_WORKFLOW_TRACE_CHARS = 2000000;
|
|
|
3665
2037
|
|
|
3666
2038
|
// src/step-log.ts
|
|
3667
2039
|
var REDACTED = "[redacted]";
|
|
3668
|
-
var
|
|
2040
|
+
var SECRET_KEY2 = /(?:^|[-_])(authorization|cookie|set-cookie|api[-_]?key|access[-_]?token|refresh[-_]?token|token|secret|password|passwd|credential|private[-_]?key|client[-_]?secret)(?:$|[-_])/i;
|
|
3669
2041
|
var LABELED_VALUE = /(^|[^A-Za-z0-9_])([A-Za-z0-9_-]*(?:authorization|proxy[-_]?authorization|cookie|set[-_]?cookie|api[-_]?key|access[-_]?token|refresh[-_]?token|client[-_]?secret|private[-_]?key|password|passwd|credential|token|secret)[A-Za-z0-9_-]*)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;]+)/gim;
|
|
3670
|
-
var
|
|
2042
|
+
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3671
2043
|
var sanitizeControls = (value) => {
|
|
3672
2044
|
let safe = "";
|
|
3673
2045
|
for (const character of value) {
|
|
@@ -3693,15 +2065,15 @@ var redactStructured = (value, depth = 0) => {
|
|
|
3693
2065
|
if (Array.isArray(value)) {
|
|
3694
2066
|
return value.map((item) => redactStructured(item, depth + 1));
|
|
3695
2067
|
}
|
|
3696
|
-
if (!
|
|
2068
|
+
if (!isRecord4(value)) {
|
|
3697
2069
|
return typeof value === "string" ? sanitizeStepLogText(value) : value;
|
|
3698
2070
|
}
|
|
3699
2071
|
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
3700
2072
|
key,
|
|
3701
|
-
|
|
2073
|
+
SECRET_KEY2.test(`-${key}-`) ? REDACTED : redactStructured(item, depth + 1)
|
|
3702
2074
|
]));
|
|
3703
2075
|
};
|
|
3704
|
-
var redactCommonCredentials = (value) => value.replaceAll(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, `$1 ${REDACTED}`).replace(LABELED_VALUE, (original, prefix, key) =>
|
|
2076
|
+
var redactCommonCredentials = (value) => value.replaceAll(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, `$1 ${REDACTED}`).replace(LABELED_VALUE, (original, prefix, key) => SECRET_KEY2.test(`-${key}-`) ? `${prefix}${key}=${REDACTED}` : original);
|
|
3705
2077
|
function redactStepLogText(value) {
|
|
3706
2078
|
return redactCommonCredentials(sanitizeStepLogText(value));
|
|
3707
2079
|
}
|
|
@@ -3723,7 +2095,7 @@ var contentText = (content) => {
|
|
|
3723
2095
|
if (!Array.isArray(content))
|
|
3724
2096
|
return "";
|
|
3725
2097
|
return content.flatMap((item) => {
|
|
3726
|
-
if (!
|
|
2098
|
+
if (!isRecord4(item))
|
|
3727
2099
|
return [];
|
|
3728
2100
|
if (item.type === "text" && typeof item.text === "string") {
|
|
3729
2101
|
return [item.text];
|
|
@@ -3737,24 +2109,24 @@ var contentText = (content) => {
|
|
|
3737
2109
|
`);
|
|
3738
2110
|
};
|
|
3739
2111
|
function textOnlyUserMessage(message) {
|
|
3740
|
-
if (!
|
|
2112
|
+
if (!isRecord4(message) || message.role !== "user")
|
|
3741
2113
|
return;
|
|
3742
2114
|
if (typeof message.content === "string")
|
|
3743
2115
|
return message.content;
|
|
3744
|
-
if (!Array.isArray(message.content) || message.content.some((item) => !
|
|
2116
|
+
if (!Array.isArray(message.content) || message.content.some((item) => !isRecord4(item) || item.type !== "text" || typeof item.text !== "string")) {
|
|
3745
2117
|
return;
|
|
3746
2118
|
}
|
|
3747
2119
|
return message.content.map((item) => item.text).join(`
|
|
3748
2120
|
`);
|
|
3749
2121
|
}
|
|
3750
2122
|
function stepLogLinesFromMessage(message) {
|
|
3751
|
-
if (!
|
|
2123
|
+
if (!isRecord4(message))
|
|
3752
2124
|
return [];
|
|
3753
2125
|
if (message.role === "assistant") {
|
|
3754
2126
|
if (!Array.isArray(message.content))
|
|
3755
2127
|
return [];
|
|
3756
2128
|
const contentLines = message.content.flatMap((item) => {
|
|
3757
|
-
if (!
|
|
2129
|
+
if (!isRecord4(item))
|
|
3758
2130
|
return [];
|
|
3759
2131
|
if (item.type === "text" && typeof item.text === "string") {
|
|
3760
2132
|
const text = redactStepLogText(item.text);
|
|
@@ -3870,6 +2242,95 @@ function registerMainStepPolicy({
|
|
|
3870
2242
|
});
|
|
3871
2243
|
}
|
|
3872
2244
|
|
|
2245
|
+
// src/runtime/step-result.ts
|
|
2246
|
+
import { isAbsolute as isAbsolute3 } from "node:path";
|
|
2247
|
+
var MAX_ARTIFACT_CHARS = 200000;
|
|
2248
|
+
var RESULT_KEYS = new Set([
|
|
2249
|
+
"version",
|
|
2250
|
+
"policyDigest",
|
|
2251
|
+
"outcome",
|
|
2252
|
+
"summary",
|
|
2253
|
+
"artifact",
|
|
2254
|
+
"workspace"
|
|
2255
|
+
]);
|
|
2256
|
+
var isObject = (value) => {
|
|
2257
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2258
|
+
};
|
|
2259
|
+
function parseResultWorkspace(value, outcome, policy) {
|
|
2260
|
+
const requiresWorkspace = policy.workspace?.bindOn.includes(outcome) === true;
|
|
2261
|
+
if (!requiresWorkspace) {
|
|
2262
|
+
if (value !== undefined) {
|
|
2263
|
+
throw new Error("workflow step workspace is forbidden for this outcome");
|
|
2264
|
+
}
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
if (!isObject(value)) {
|
|
2268
|
+
throw new Error(`workflow step outcome "${outcome}" requires workspace.cwd`);
|
|
2269
|
+
}
|
|
2270
|
+
const unknownKey = Object.keys(value).find((key) => key !== "cwd");
|
|
2271
|
+
if (unknownKey) {
|
|
2272
|
+
throw new Error(`workflow step workspace has unknown property "${unknownKey}"`);
|
|
2273
|
+
}
|
|
2274
|
+
if (typeof value.cwd !== "string") {
|
|
2275
|
+
throw new Error("workflow step workspace cwd must be a string");
|
|
2276
|
+
}
|
|
2277
|
+
const cwd = value.cwd;
|
|
2278
|
+
if (!cwd || cwd.includes("\x00") || !isAbsolute3(cwd)) {
|
|
2279
|
+
throw new Error("workflow step workspace cwd must be an absolute path");
|
|
2280
|
+
}
|
|
2281
|
+
if (cwd.length > MAX_WORKSPACE_PATH_CHARS) {
|
|
2282
|
+
throw new Error(`workflow step workspace cwd exceeds ${MAX_WORKSPACE_PATH_CHARS} characters`);
|
|
2283
|
+
}
|
|
2284
|
+
return { cwd };
|
|
2285
|
+
}
|
|
2286
|
+
function parseWorkflowStepResult(value, policy) {
|
|
2287
|
+
if (!isObject(value)) {
|
|
2288
|
+
throw new Error("workflow step result must be an object");
|
|
2289
|
+
}
|
|
2290
|
+
const unknownKey = Object.keys(value).find((key) => !RESULT_KEYS.has(key));
|
|
2291
|
+
if (unknownKey) {
|
|
2292
|
+
throw new Error(`workflow step result has unknown property "${unknownKey}"`);
|
|
2293
|
+
}
|
|
2294
|
+
if (value.version !== 1) {
|
|
2295
|
+
throw new Error("unsupported workflow step result version");
|
|
2296
|
+
}
|
|
2297
|
+
if (value.policyDigest !== policy.policyDigest) {
|
|
2298
|
+
throw new Error("workflow step result does not match the active policy");
|
|
2299
|
+
}
|
|
2300
|
+
if (typeof value.outcome !== "string" || !policy.outcomes.includes(value.outcome)) {
|
|
2301
|
+
throw new Error(`workflow step returned invalid outcome "${String(value.outcome)}"`);
|
|
2302
|
+
}
|
|
2303
|
+
if (typeof value.summary !== "string") {
|
|
2304
|
+
throw new Error("workflow step summary must be a string");
|
|
2305
|
+
}
|
|
2306
|
+
const summary = value.summary.trim();
|
|
2307
|
+
if (!summary) {
|
|
2308
|
+
throw new Error("workflow step summary must not be empty");
|
|
2309
|
+
}
|
|
2310
|
+
if (summary.length > policy.summaryMaxChars) {
|
|
2311
|
+
throw new Error(`workflow step summary exceeds ${policy.summaryMaxChars} characters`);
|
|
2312
|
+
}
|
|
2313
|
+
if (value.artifact !== undefined && typeof value.artifact !== "string") {
|
|
2314
|
+
throw new Error("workflow step artifact must be a string");
|
|
2315
|
+
}
|
|
2316
|
+
const artifact = typeof value.artifact === "string" ? value.artifact : undefined;
|
|
2317
|
+
if (artifact !== undefined && artifact.length > MAX_ARTIFACT_CHARS) {
|
|
2318
|
+
throw new Error(`workflow step artifact exceeds ${MAX_ARTIFACT_CHARS} characters`);
|
|
2319
|
+
}
|
|
2320
|
+
if (value.outcome === policy.gateSubmitOutcome && (!artifact || !artifact.trim())) {
|
|
2321
|
+
throw new Error("workflow gate outcome requires a non-empty artifact");
|
|
2322
|
+
}
|
|
2323
|
+
const workspace = parseResultWorkspace(value.workspace, value.outcome, policy);
|
|
2324
|
+
return {
|
|
2325
|
+
version: 1,
|
|
2326
|
+
policyDigest: policy.policyDigest,
|
|
2327
|
+
outcome: value.outcome,
|
|
2328
|
+
summary,
|
|
2329
|
+
...artifact !== undefined ? { artifact } : {},
|
|
2330
|
+
...workspace ? { workspace } : {}
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
|
|
3873
2334
|
// src/runtime/main-step-runtime.ts
|
|
3874
2335
|
var DEFAULT_DEPENDENCIES2 = {
|
|
3875
2336
|
invalidCompletionCallIds,
|
|
@@ -4296,7 +2757,8 @@ function formatWorkflowProgressStatus(snapshot, statusShortcutLabel) {
|
|
|
4296
2757
|
const { run, workflow } = snapshot;
|
|
4297
2758
|
const currentStep = formatStepName(stepTitle(workflow, run.currentStepId), run.currentStepId);
|
|
4298
2759
|
const activity = run.status === "awaiting-gate" ? "awaiting review" : "working";
|
|
4299
|
-
|
|
2760
|
+
const workerProgress = snapshot.execution?.kind === "subagent" ? ` · ${snapshot.execution.progress}` : "";
|
|
2761
|
+
return `${workflowStatusIcon(run, snapshot.now)} ${run.workflowId} · step ${currentStep} · ${activity}${workerProgress} · ${statusShortcutLabel}`;
|
|
4300
2762
|
}
|
|
4301
2763
|
// src/workflow-status/view.ts
|
|
4302
2764
|
import {
|
|
@@ -4440,6 +2902,46 @@ function attemptDisplayOrdinal(attempt, index, omittedAttempts) {
|
|
|
4440
2902
|
return index + 1;
|
|
4441
2903
|
return omittedAttempts + index + 1;
|
|
4442
2904
|
}
|
|
2905
|
+
function renderLiveWorkerSession(theme, snapshot, detail, width) {
|
|
2906
|
+
if (snapshot.execution?.kind !== "subagent")
|
|
2907
|
+
return [];
|
|
2908
|
+
const attempt = detail.attempts.at(-1);
|
|
2909
|
+
const events = snapshot.execution.activityLog ?? [];
|
|
2910
|
+
const eventLines = events.flatMap((event) => {
|
|
2911
|
+
const isToolCall = event.startsWith("call ");
|
|
2912
|
+
const isResponse = event.startsWith("response: ");
|
|
2913
|
+
const label = isToolCall ? "● Tool call" : isResponse ? "● Assistant" : "● Worker";
|
|
2914
|
+
const value = isResponse ? event.slice("response: ".length) : event;
|
|
2915
|
+
return [
|
|
2916
|
+
theme.bold(theme.fg(isToolCall ? "warning" : "accent", label)),
|
|
2917
|
+
...wrapPlain(value, width, theme),
|
|
2918
|
+
""
|
|
2919
|
+
];
|
|
2920
|
+
});
|
|
2921
|
+
return [
|
|
2922
|
+
"",
|
|
2923
|
+
theme.bold(theme.fg("accent", "Live Worker Session")),
|
|
2924
|
+
...keyValueLines(theme, "worker", snapshot.execution.agent, width, "accent"),
|
|
2925
|
+
...keyValueLines(theme, "state", snapshot.execution.progress, width, "accent"),
|
|
2926
|
+
"",
|
|
2927
|
+
theme.bold(theme.fg("accent", "● Input prompt")),
|
|
2928
|
+
...wrapPlain(attempt ? `${attempt.task}${attempt.taskTruncated ? `
|
|
2929
|
+
… [${attempt.omittedTaskChars ?? 0} prompt characters omitted from the bounded checkpoint trace]` : ""}` : "The worker prompt is being prepared.", width, theme),
|
|
2930
|
+
"",
|
|
2931
|
+
theme.bold(theme.fg("accent", "● Live message chain")),
|
|
2932
|
+
...eventLines.length > 0 ? eventLines : [theme.fg("muted", "Waiting for the worker to emit activity…"), ""]
|
|
2933
|
+
];
|
|
2934
|
+
}
|
|
2935
|
+
function renderLiveWorkerActivity(theme, snapshot, selectedIndex, width) {
|
|
2936
|
+
const entry = buildPathEntries(snapshot)[selectedIndex];
|
|
2937
|
+
const detail = selectedStepDetail(snapshot, selectedIndex);
|
|
2938
|
+
if (!entry?.isCurrent || !detail || snapshot.execution?.kind !== "subagent") {
|
|
2939
|
+
return boxed(theme, "Live Worker Session", width, [
|
|
2940
|
+
theme.fg("muted", "Live activity is available only for the active worker step.")
|
|
2941
|
+
], "borderAccent");
|
|
2942
|
+
}
|
|
2943
|
+
return boxed(theme, `Live Worker Session · ${entry.title} · visit ${entry.visit}`, width, renderLiveWorkerSession(theme, snapshot, detail, width - 4), "borderAccent");
|
|
2944
|
+
}
|
|
4443
2945
|
function renderStepDetail(theme, snapshot, selectedIndex, cache, width) {
|
|
4444
2946
|
const entries = buildPathEntries(snapshot);
|
|
4445
2947
|
const entry = entries[selectedIndex];
|
|
@@ -4487,9 +2989,9 @@ function renderStepDetail(theme, snapshot, selectedIndex, cache, width) {
|
|
|
4487
2989
|
}
|
|
4488
2990
|
|
|
4489
2991
|
// src/workflow-status/transcript-reader.ts
|
|
4490
|
-
import { constants
|
|
4491
|
-
import { lstat
|
|
4492
|
-
import { isAbsolute as
|
|
2992
|
+
import { constants } from "node:fs";
|
|
2993
|
+
import { lstat, open, realpath as realpath2 } from "node:fs/promises";
|
|
2994
|
+
import { isAbsolute as isAbsolute5, relative as relative2, resolve as resolve4, sep as sep2 } from "node:path";
|
|
4493
2995
|
|
|
4494
2996
|
// src/engine/create-run.ts
|
|
4495
2997
|
var createRun = (workflow, input, baselineTools, runId, now, cwd, iteration = 1) => {
|
|
@@ -4519,10 +3021,9 @@ var createRun = (workflow, input, baselineTools, runId, now, cwd, iteration = 1)
|
|
|
4519
3021
|
};
|
|
4520
3022
|
};
|
|
4521
3023
|
// src/engine/run-validation.ts
|
|
4522
|
-
import { isAbsolute as
|
|
3024
|
+
import { isAbsolute as isAbsolute4, resolve as resolve3 } from "node:path";
|
|
4523
3025
|
|
|
4524
3026
|
// src/engine/step-trace.ts
|
|
4525
|
-
import { isAbsolute as isAbsolute7, relative as relative4, resolve as resolve7, sep as sep3 } from "node:path";
|
|
4526
3027
|
var COMPACTED_FIELD_CHARS = 512;
|
|
4527
3028
|
var COMPACTED_LOG_CHARS = 4096;
|
|
4528
3029
|
function logChars(lines) {
|
|
@@ -4755,29 +3256,6 @@ function appendMainStepLog(run, requestId, lines, now) {
|
|
|
4755
3256
|
updatedAt: now
|
|
4756
3257
|
});
|
|
4757
3258
|
}
|
|
4758
|
-
function isSafeTranscriptReference(reference) {
|
|
4759
|
-
if (!isAbsolute7(reference.trustedRoot) || !isAbsolute7(reference.sessionFile) || reference.trustedRoot.includes("\x00") || reference.sessionFile.includes("\x00") || !reference.runId || reference.runId.includes("\x00") || reference.runId.includes("/") || reference.runId.includes("\\") || reference.runId === "." || reference.runId === ".." || !Number.isSafeInteger(reference.childIndex) || reference.childIndex < 0) {
|
|
4760
|
-
return false;
|
|
4761
|
-
}
|
|
4762
|
-
const expected = resolve7(reference.trustedRoot, reference.runId, `run-${reference.childIndex}`, "session.jsonl");
|
|
4763
|
-
const relativePath = relative4(resolve7(reference.trustedRoot), resolve7(reference.sessionFile));
|
|
4764
|
-
const isWithinTrustedRoot = relativePath !== "" && relativePath !== ".." && !relativePath.startsWith(`..${sep3}`) && !isAbsolute7(relativePath);
|
|
4765
|
-
return isWithinTrustedRoot && resolve7(reference.sessionFile) === expected;
|
|
4766
|
-
}
|
|
4767
|
-
function attachSubagentTranscript(run, requestId, reference, now) {
|
|
4768
|
-
if (!isSafeTranscriptReference(reference))
|
|
4769
|
-
return run;
|
|
4770
|
-
const attempts = run.currentStepAttempts;
|
|
4771
|
-
const index = attempts?.findIndex((attempt2) => attempt2.requestId === requestId && attempt2.kind === "subagent");
|
|
4772
|
-
if (index === undefined || index < 0 || !attempts)
|
|
4773
|
-
return run;
|
|
4774
|
-
const currentStepAttempts = [...attempts];
|
|
4775
|
-
const attempt = currentStepAttempts[index];
|
|
4776
|
-
if (!attempt || attempt.kind !== "subagent")
|
|
4777
|
-
return run;
|
|
4778
|
-
currentStepAttempts[index] = { ...attempt, transcript: reference };
|
|
4779
|
-
return compactRunTraceBudget({ ...run, currentStepAttempts, updatedAt: now });
|
|
4780
|
-
}
|
|
4781
3259
|
function attemptResult(result, workspaceCwd) {
|
|
4782
3260
|
const summary = result.summary.slice(0, MAX_STEP_TRACE_SUMMARY_CHARS);
|
|
4783
3261
|
const artifact = result.artifact?.slice(0, MAX_STEP_TRACE_ARTIFACT_CHARS);
|
|
@@ -4835,11 +3313,11 @@ function recordCurrentGateDecision(run, decision, now) {
|
|
|
4835
3313
|
}
|
|
4836
3314
|
|
|
4837
3315
|
// src/engine/run-validation.ts
|
|
4838
|
-
var
|
|
4839
|
-
var isAbsoluteCwd = (value) => typeof value === "string" && value.length > 0 &&
|
|
4840
|
-
var isGateApproval = (value) =>
|
|
4841
|
-
var isStepAttemptResult = (value) =>
|
|
4842
|
-
var isStepGateDecision = (value) =>
|
|
3316
|
+
var isRecord5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3317
|
+
var isAbsoluteCwd = (value) => typeof value === "string" && value.length > 0 && isAbsolute4(value) && !value.includes("\x00");
|
|
3318
|
+
var isGateApproval = (value) => isRecord5(value) && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.artifact === "string" && value.artifact.trim().length > 0 && typeof value.feedback === "string" && value.feedback.length <= MAX_GATE_FEEDBACK_CHARS && typeof value.stepStructuralDigest === "string" && value.stepStructuralDigest.length > 0;
|
|
3319
|
+
var isStepAttemptResult = (value) => isRecord5(value) && typeof value.outcome === "string" && typeof value.summary === "string" && value.summary.length <= MAX_STEP_TRACE_SUMMARY_CHARS && (value.summaryTruncated === undefined || value.summaryTruncated === true) && (value.artifact === undefined || typeof value.artifact === "string") && (typeof value.artifact !== "string" || value.artifact.length <= MAX_STEP_TRACE_ARTIFACT_CHARS) && (value.artifactTruncated === undefined || value.artifactTruncated === true) && !(value.artifactTruncated === true && value.artifact === undefined) && (value.workspaceCwd === undefined || isAbsoluteCwd(value.workspaceCwd));
|
|
3320
|
+
var isStepGateDecision = (value) => isRecord5(value) && (value.provider === "prompt" || value.provider === "plannotator") && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.approved === "boolean" && typeof value.feedback === "string" && value.feedback.length <= MAX_STEP_TRACE_SUMMARY_CHARS && (value.feedbackTruncated === undefined || value.feedbackTruncated === true) && typeof value.resolvedAt === "number" && (value.reviewId === undefined || typeof value.reviewId === "string");
|
|
4843
3321
|
var isSafeTraceIdentityField = (value) => typeof value === "string" && value.length > 0 && !value.includes("\x00") && !value.includes("/") && !value.includes("\\") && value !== "." && value !== "..";
|
|
4844
3322
|
var hasValidMainStepLog = (value) => {
|
|
4845
3323
|
const log = value.log;
|
|
@@ -4855,7 +3333,7 @@ var hasValidMainStepLog = (value) => {
|
|
|
4855
3333
|
return value.logTruncated === true === (typeof value.omittedLogEvents === "number") && !(log === undefined && (value.logTruncated !== undefined || value.omittedLogEvents !== undefined));
|
|
4856
3334
|
};
|
|
4857
3335
|
var isStepExecutionAttempt = (value) => {
|
|
4858
|
-
if (!
|
|
3336
|
+
if (!isRecord5(value) || value.kind !== "main" && value.kind !== "subagent" || typeof value.requestId !== "string" || value.requestId.length === 0 || value.requestId.includes("\x00") || value.ordinal !== undefined && (!Number.isSafeInteger(value.ordinal) || value.ordinal <= 0) || typeof value.task !== "string" || value.task.trim().length === 0 || value.task.length > MAX_STEP_TRACE_TASK_CHARS || value.taskTruncated !== undefined && value.taskTruncated !== true || value.omittedTaskChars !== undefined && (!Number.isSafeInteger(value.omittedTaskChars) || value.omittedTaskChars <= 0) || value.taskTruncated === true !== (typeof value.omittedTaskChars === "number") || typeof value.startedAt !== "number" || value.result !== undefined && !isStepAttemptResult(value.result) || value.gateDecision !== undefined && !isStepGateDecision(value.gateDecision)) {
|
|
4859
3337
|
return false;
|
|
4860
3338
|
}
|
|
4861
3339
|
if (value.kind === "main") {
|
|
@@ -4869,13 +3347,13 @@ var isStepExecutionAttempt = (value) => {
|
|
|
4869
3347
|
}
|
|
4870
3348
|
if (value.transcript === undefined)
|
|
4871
3349
|
return true;
|
|
4872
|
-
if (!
|
|
3350
|
+
if (!isRecord5(value.transcript))
|
|
4873
3351
|
return false;
|
|
4874
3352
|
const transcript = value.transcript;
|
|
4875
3353
|
if (!isAbsoluteCwd(transcript.trustedRoot) || !isAbsoluteCwd(transcript.sessionFile) || !isSafeTraceIdentityField(transcript.runId) || !Number.isSafeInteger(transcript.childIndex) || transcript.childIndex < 0) {
|
|
4876
3354
|
return false;
|
|
4877
3355
|
}
|
|
4878
|
-
return
|
|
3356
|
+
return resolve3(transcript.trustedRoot, transcript.runId, `run-${String(transcript.childIndex)}`, "session.jsonl") === resolve3(transcript.sessionFile);
|
|
4879
3357
|
};
|
|
4880
3358
|
var isStepExecutionAttempts = (value) => Array.isArray(value) && value.length <= MAX_STEP_TRACE_ATTEMPTS && value.every(isStepExecutionAttempt) && new Set(value.map((attempt) => attempt.requestId)).size === value.length && value.every((attempt, index) => {
|
|
4881
3359
|
if (attempt.ordinal === undefined)
|
|
@@ -4883,10 +3361,10 @@ var isStepExecutionAttempts = (value) => Array.isArray(value) && value.length <=
|
|
|
4883
3361
|
const ordinal = attempt.ordinal;
|
|
4884
3362
|
return value.slice(0, index).every((earlier) => earlier.ordinal === undefined || earlier.ordinal < ordinal);
|
|
4885
3363
|
});
|
|
4886
|
-
var isStepHistoryEntry = (value) =>
|
|
4887
|
-
var isGateResolution = (value) =>
|
|
4888
|
-
var isPendingGate = (value) =>
|
|
4889
|
-
var isVisitCounts = (value) =>
|
|
3364
|
+
var isStepHistoryEntry = (value) => isRecord5(value) && typeof value.stepId === "string" && typeof value.stepDigest === "string" && typeof value.outcome === "string" && typeof value.summary === "string" && (value.workspaceCwd === undefined || isAbsoluteCwd(value.workspaceCwd)) && (value.artifact === undefined || typeof value.artifact === "string") && (value.approval === undefined || isGateApproval(value.approval) && value.artifact === value.approval.artifact) && (value.attempts === undefined || isStepExecutionAttempts(value.attempts)) && (value.omittedAttempts === undefined || Number.isSafeInteger(value.omittedAttempts) && value.omittedAttempts > 0) && typeof value.completedAt === "number";
|
|
3365
|
+
var isGateResolution = (value) => isRecord5(value) && typeof value.approved === "boolean" && typeof value.feedback === "string" && value.feedback.length <= MAX_GATE_FEEDBACK_CHARS && typeof value.resolvedAt === "number";
|
|
3366
|
+
var isPendingGate = (value) => isRecord5(value) && (value.provider === "prompt" || value.provider === "plannotator") && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.stepId === "string" && typeof value.artifact === "string" && (value.summary === undefined || typeof value.summary === "string") && typeof value.submittedOutcome === "string" && typeof value.requestedAt === "number" && (value.reviewId === undefined || typeof value.reviewId === "string") && (value.resolution === undefined || isGateResolution(value.resolution));
|
|
3367
|
+
var isVisitCounts = (value) => isRecord5(value) && Object.values(value).every((count) => typeof count === "number" && Number.isInteger(count) && count >= 0);
|
|
4890
3368
|
var isOptionalString = (value) => value === undefined || typeof value === "string";
|
|
4891
3369
|
var isOptionalResumeInput = (value) => value === undefined || typeof value === "string" && value.length <= MAX_RESUME_INPUT_CHARS;
|
|
4892
3370
|
var isOptionalIteration = (value) => value === undefined || Number.isSafeInteger(value) && value >= 1;
|
|
@@ -4911,7 +3389,7 @@ var hasValidWorkspaceState = (run, history) => {
|
|
|
4911
3389
|
return startCwd === undefined || cwd === startCwd;
|
|
4912
3390
|
};
|
|
4913
3391
|
var isWorkflowRun = (value) => {
|
|
4914
|
-
if (!
|
|
3392
|
+
if (!isRecord5(value))
|
|
4915
3393
|
return false;
|
|
4916
3394
|
const hasValidRequiredFields = value.stateVersion === RUN_STATE_VERSION && typeof value.runId === "string" && typeof value.workflowId === "string" && typeof value.workflowDigest === "string" && typeof value.input === "string" && isWorkflowRunStatus(value.status) && typeof value.currentStepId === "string" && typeof value.currentStepDigest === "string" && Array.isArray(value.baselineTools) && value.baselineTools.every((tool) => typeof tool === "string") && Array.isArray(value.history) && value.history.every(isStepHistoryEntry) && (value.currentStepAttempts === undefined || isStepExecutionAttempts(value.currentStepAttempts)) && (value.currentStepOmittedAttempts === undefined || Number.isSafeInteger(value.currentStepOmittedAttempts) && value.currentStepOmittedAttempts > 0) && isVisitCounts(value.visits) && typeof value.startedAt === "number" && typeof value.updatedAt === "number" && typeof value.lastSummary === "string" && typeof value.gateFeedback === "string" && value.gateFeedback.length <= MAX_GATE_FEEDBACK_CHARS;
|
|
4917
3395
|
if (!hasValidRequiredFields)
|
|
@@ -4929,18 +3407,18 @@ var isWorkflowRun = (value) => {
|
|
|
4929
3407
|
};
|
|
4930
3408
|
// src/workflow-status/transcript-reader.ts
|
|
4931
3409
|
var MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
|
|
4932
|
-
function
|
|
3410
|
+
function isRecord6(value) {
|
|
4933
3411
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4934
3412
|
}
|
|
4935
3413
|
function isWithin(root, candidate) {
|
|
4936
|
-
const pathFromRoot =
|
|
4937
|
-
return pathFromRoot !== "" && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${
|
|
3414
|
+
const pathFromRoot = relative2(resolve4(root), resolve4(candidate));
|
|
3415
|
+
return pathFromRoot !== "" && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep2}`) && !isAbsolute5(pathFromRoot);
|
|
4938
3416
|
}
|
|
4939
3417
|
function hasSafeIdentity(reference) {
|
|
4940
|
-
return
|
|
3418
|
+
return isAbsolute5(reference.trustedRoot) && isAbsolute5(reference.sessionFile) && !reference.trustedRoot.includes("\x00") && !reference.sessionFile.includes("\x00") && reference.runId.length > 0 && !reference.runId.includes("\x00") && !reference.runId.includes("/") && !reference.runId.includes("\\") && reference.runId !== "." && reference.runId !== ".." && Number.isSafeInteger(reference.childIndex) && reference.childIndex >= 0 && resolve4(reference.sessionFile) === resolve4(reference.trustedRoot, reference.runId, `run-${reference.childIndex}`, "session.jsonl");
|
|
4941
3419
|
}
|
|
4942
3420
|
function transcriptEntryLines(entry) {
|
|
4943
|
-
if (!
|
|
3421
|
+
if (!isRecord6(entry))
|
|
4944
3422
|
return [];
|
|
4945
3423
|
if (entry.type === "custom_message") {
|
|
4946
3424
|
const customType = typeof entry.customType === "string" ? entry.customType : "custom";
|
|
@@ -4948,7 +3426,7 @@ function transcriptEntryLines(entry) {
|
|
|
4948
3426
|
return content ? [`event ${sanitizeStepLogText(customType)}
|
|
4949
3427
|
${content}`] : [];
|
|
4950
3428
|
}
|
|
4951
|
-
if (entry.type !== "message" || !
|
|
3429
|
+
if (entry.type !== "message" || !isRecord6(entry.message))
|
|
4952
3430
|
return [];
|
|
4953
3431
|
return stepLogLinesFromMessage(entry.message);
|
|
4954
3432
|
}
|
|
@@ -4988,11 +3466,11 @@ async function readStepTranscript(reference) {
|
|
|
4988
3466
|
};
|
|
4989
3467
|
}
|
|
4990
3468
|
try {
|
|
4991
|
-
const runDirectory =
|
|
4992
|
-
const childDirectory =
|
|
3469
|
+
const runDirectory = resolve4(reference.trustedRoot, reference.runId);
|
|
3470
|
+
const childDirectory = resolve4(runDirectory, `run-${reference.childIndex}`);
|
|
4993
3471
|
const [runDirectoryInfo, childDirectoryInfo] = await Promise.all([
|
|
4994
|
-
|
|
4995
|
-
|
|
3472
|
+
lstat(runDirectory),
|
|
3473
|
+
lstat(childDirectory)
|
|
4996
3474
|
]);
|
|
4997
3475
|
if (runDirectoryInfo.isSymbolicLink() || !runDirectoryInfo.isDirectory() || childDirectoryInfo.isSymbolicLink() || !childDirectoryInfo.isDirectory()) {
|
|
4998
3476
|
return {
|
|
@@ -5000,7 +3478,7 @@ async function readStepTranscript(reference) {
|
|
|
5000
3478
|
reason: "The recorded child transcript directory identity is not trusted."
|
|
5001
3479
|
};
|
|
5002
3480
|
}
|
|
5003
|
-
const inspected = await
|
|
3481
|
+
const inspected = await lstat(reference.sessionFile);
|
|
5004
3482
|
if (inspected.isSymbolicLink() || !inspected.isFile()) {
|
|
5005
3483
|
return {
|
|
5006
3484
|
status: "unavailable",
|
|
@@ -5008,17 +3486,17 @@ async function readStepTranscript(reference) {
|
|
|
5008
3486
|
};
|
|
5009
3487
|
}
|
|
5010
3488
|
const [canonicalRoot, canonicalSession] = await Promise.all([
|
|
5011
|
-
|
|
5012
|
-
|
|
3489
|
+
realpath2(reference.trustedRoot),
|
|
3490
|
+
realpath2(reference.sessionFile)
|
|
5013
3491
|
]);
|
|
5014
|
-
const canonicalExpected =
|
|
3492
|
+
const canonicalExpected = resolve4(canonicalRoot, reference.runId, `run-${reference.childIndex}`, "session.jsonl");
|
|
5015
3493
|
if (!isWithin(canonicalRoot, canonicalSession) || canonicalSession !== canonicalExpected) {
|
|
5016
3494
|
return {
|
|
5017
3495
|
status: "unavailable",
|
|
5018
3496
|
reason: "The recorded child transcript does not match its trusted canonical identity."
|
|
5019
3497
|
};
|
|
5020
3498
|
}
|
|
5021
|
-
const handle = await
|
|
3499
|
+
const handle = await open(canonicalSession, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
5022
3500
|
try {
|
|
5023
3501
|
const before = await handle.stat();
|
|
5024
3502
|
if (!before.isFile()) {
|
|
@@ -5140,7 +3618,9 @@ class WorkflowStatusView {
|
|
|
5140
3618
|
return;
|
|
5141
3619
|
}
|
|
5142
3620
|
if (matchesKey(data, "escape")) {
|
|
5143
|
-
if (this.state.mode === "
|
|
3621
|
+
if (this.state.mode === "live") {
|
|
3622
|
+
this.showDetail();
|
|
3623
|
+
} else if (this.state.mode === "detail") {
|
|
5144
3624
|
this.showBoard();
|
|
5145
3625
|
} else {
|
|
5146
3626
|
this.close();
|
|
@@ -5150,7 +3630,14 @@ class WorkflowStatusView {
|
|
|
5150
3630
|
const pageSize = Math.max(1, this.state.viewportRows - 2);
|
|
5151
3631
|
const contentHeight = Math.max(1, this.state.viewportRows - 1);
|
|
5152
3632
|
const halfPageSize = Math.max(1, Math.floor(contentHeight / 2));
|
|
5153
|
-
if (this.state.mode === "detail") {
|
|
3633
|
+
if (this.state.mode === "detail" || this.state.mode === "live") {
|
|
3634
|
+
if (matchesKey(data, Key.tab)) {
|
|
3635
|
+
if (this.state.mode === "detail")
|
|
3636
|
+
this.showLive();
|
|
3637
|
+
else
|
|
3638
|
+
this.showDetail();
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
5154
3641
|
if (data === "gg" || data === "g" && this.pendingDetailTopKey) {
|
|
5155
3642
|
this.pendingDetailTopKey = false;
|
|
5156
3643
|
this.setScrollOffset(0);
|
|
@@ -5164,7 +3651,10 @@ class WorkflowStatusView {
|
|
|
5164
3651
|
if (data === "G") {
|
|
5165
3652
|
this.setScrollOffset(Number.MAX_SAFE_INTEGER);
|
|
5166
3653
|
} else if (matchesKey(data, Key.left) || data === "h") {
|
|
5167
|
-
this.
|
|
3654
|
+
if (this.state.mode === "live")
|
|
3655
|
+
this.showDetail();
|
|
3656
|
+
else
|
|
3657
|
+
this.showBoard();
|
|
5168
3658
|
} else if (matchesKey(data, Key.down) || data === "j") {
|
|
5169
3659
|
this.setScrollOffset(this.state.scrollOffset + 1);
|
|
5170
3660
|
} else if (matchesKey(data, Key.up) || data === "k") {
|
|
@@ -5211,12 +3701,12 @@ class WorkflowStatusView {
|
|
|
5211
3701
|
const contentWidth = viewportWidth - 2;
|
|
5212
3702
|
if (snapshot)
|
|
5213
3703
|
this.normalizeSelection(snapshot);
|
|
5214
|
-
const lines = snapshot ? this.state.mode === "detail" ? renderStepDetail(this.theme, snapshot, this.state.selectedIndex, this.transcriptCache, contentWidth) : renderBoard(this.theme, snapshot, contentWidth, false, this.statusShortcutLabel, this.state.selectedIndex) : renderEmptyBoard(this.theme, contentWidth);
|
|
3704
|
+
const lines = snapshot ? this.state.mode === "detail" ? renderStepDetail(this.theme, snapshot, this.state.selectedIndex, this.transcriptCache, contentWidth) : this.state.mode === "live" ? renderLiveWorkerActivity(this.theme, snapshot, this.state.selectedIndex, contentWidth) : renderBoard(this.theme, snapshot, contentWidth, false, this.statusShortcutLabel, this.state.selectedIndex) : renderEmptyBoard(this.theme, contentWidth);
|
|
5215
3705
|
if (snapshot && this.state.mode === "detail") {
|
|
5216
3706
|
this.ensureSelectedTranscripts(snapshot);
|
|
5217
3707
|
}
|
|
5218
3708
|
const rendered = lines.map((line) => padAnsi(truncateToWidth4(line, contentWidth, "…"), viewportWidth));
|
|
5219
|
-
const page = paginateBoard(this.state, rendered, viewportWidth, this.tui.terminal?.rows, this.statusShortcutLabel, this.theme, this.state.mode === "detail" ? "↑↓/jk · Ctrl+D/U half-page · gg/G top/bottom · PgUp/PgDn · ←/h/Esc" : "↑/↓ or j/k select · Enter/→/l inspect · PgUp/PgDn");
|
|
3709
|
+
const page = paginateBoard(this.state, rendered, viewportWidth, this.tui.terminal?.rows, this.statusShortcutLabel, this.theme, this.state.mode === "detail" ? "↑↓/jk · Ctrl+D/U half-page · gg/G top/bottom · PgUp/PgDn · Tab live · ←/h/Esc" : this.state.mode === "live" ? "↑↓/jk · Ctrl+D/U half-page · gg/G top/bottom · PgUp/PgDn · Tab/←/h/Esc overview" : "↑/↓ or j/k select · Enter/→/l inspect · PgUp/PgDn");
|
|
5220
3710
|
this.state = page.state;
|
|
5221
3711
|
return page.lines;
|
|
5222
3712
|
}
|
|
@@ -5270,6 +3760,24 @@ class WorkflowStatusView {
|
|
|
5270
3760
|
this.state = { ...this.state, mode: "board", scrollOffset: 0 };
|
|
5271
3761
|
this.tui.requestRender(true);
|
|
5272
3762
|
}
|
|
3763
|
+
showDetail() {
|
|
3764
|
+
if (this.state.mode === "detail")
|
|
3765
|
+
return;
|
|
3766
|
+
this.pendingDetailTopKey = false;
|
|
3767
|
+
this.state = { ...this.state, mode: "detail", scrollOffset: 0 };
|
|
3768
|
+
this.tui.requestRender(true);
|
|
3769
|
+
}
|
|
3770
|
+
showLive() {
|
|
3771
|
+
const snapshot = this.getSnapshot();
|
|
3772
|
+
if (!snapshot)
|
|
3773
|
+
return;
|
|
3774
|
+
const entry = buildPathEntries(snapshot)[this.state.selectedIndex];
|
|
3775
|
+
if (!entry?.isCurrent || snapshot.execution?.kind !== "subagent")
|
|
3776
|
+
return;
|
|
3777
|
+
this.pendingDetailTopKey = false;
|
|
3778
|
+
this.state = { ...this.state, mode: "live", scrollOffset: 0 };
|
|
3779
|
+
this.tui.requestRender(true);
|
|
3780
|
+
}
|
|
5273
3781
|
ensureSelectedTranscripts(snapshot) {
|
|
5274
3782
|
const detail = selectedStepDetail(snapshot, this.state.selectedIndex);
|
|
5275
3783
|
if (!detail)
|
|
@@ -5324,21 +3832,21 @@ async function showWorkflowStatus(ctx, getSnapshot, statusShortcut = DEFAULT_STA
|
|
|
5324
3832
|
// src/harness/workspace-directory.ts
|
|
5325
3833
|
import { realpathSync, statSync } from "node:fs";
|
|
5326
3834
|
import { homedir as homedir2 } from "node:os";
|
|
5327
|
-
import { isAbsolute as
|
|
3835
|
+
import { isAbsolute as isAbsolute6, relative as relative3, resolve as resolve5, sep as sep3, win32 as win322 } from "node:path";
|
|
5328
3836
|
var isWithin2 = (root, candidate) => {
|
|
5329
|
-
const pathFromRoot =
|
|
5330
|
-
return pathFromRoot === "" || pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${
|
|
3837
|
+
const pathFromRoot = relative3(root, candidate);
|
|
3838
|
+
return pathFromRoot === "" || pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep3}`) && !isAbsolute6(pathFromRoot);
|
|
5331
3839
|
};
|
|
5332
|
-
var resolveAllowedRoot = (startCwd, allowedRoot) => allowedRoot === "~" ? homedir2() : allowedRoot.startsWith("~/") ?
|
|
3840
|
+
var resolveAllowedRoot = (startCwd, allowedRoot) => allowedRoot === "~" ? homedir2() : allowedRoot.startsWith("~/") ? resolve5(homedir2(), allowedRoot.slice(2)) : isAbsolute6(allowedRoot) ? allowedRoot : resolve5(startCwd, allowedRoot);
|
|
5333
3841
|
function resolveWorkspaceDirectory({
|
|
5334
3842
|
candidateCwd,
|
|
5335
3843
|
startCwd,
|
|
5336
3844
|
allowedRoots
|
|
5337
3845
|
}) {
|
|
5338
|
-
if (!candidateCwd || !
|
|
3846
|
+
if (!candidateCwd || !isAbsolute6(candidateCwd) || candidateCwd.includes("\x00")) {
|
|
5339
3847
|
throw new Error("workspace cwd must be a non-empty absolute path");
|
|
5340
3848
|
}
|
|
5341
|
-
if (!startCwd || !
|
|
3849
|
+
if (!startCwd || !isAbsolute6(startCwd) || startCwd.includes("\x00")) {
|
|
5342
3850
|
throw new Error("workflow start cwd must be a non-empty absolute path");
|
|
5343
3851
|
}
|
|
5344
3852
|
if (allowedRoots.length === 0) {
|
|
@@ -5346,7 +3854,7 @@ function resolveWorkspaceDirectory({
|
|
|
5346
3854
|
}
|
|
5347
3855
|
const canonicalStart = realpathSync(startCwd);
|
|
5348
3856
|
const canonicalRoots = allowedRoots.map((allowedRoot) => {
|
|
5349
|
-
if (!allowedRoot ||
|
|
3857
|
+
if (!allowedRoot || win322.parse(allowedRoot).root !== "" && !isAbsolute6(allowedRoot) || allowedRoot.includes("\x00")) {
|
|
5350
3858
|
throw new Error("workspace allowed roots must be non-empty relative, absolute, or home-relative paths");
|
|
5351
3859
|
}
|
|
5352
3860
|
return realpathSync(resolveAllowedRoot(canonicalStart, allowedRoot));
|
|
@@ -5400,10 +3908,10 @@ function flushUnwrittenSession(session) {
|
|
|
5400
3908
|
// src/harness/dependencies.ts
|
|
5401
3909
|
var MAX_DELEGATED_RESULT_BYTES = 1024 * 1024;
|
|
5402
3910
|
function createDelegationWorkspace() {
|
|
5403
|
-
const resultDirectory = mkdtempSync(
|
|
5404
|
-
const capabilityPath =
|
|
3911
|
+
const resultDirectory = mkdtempSync(join4(tmpdir(), "pi-workflows-step-"));
|
|
3912
|
+
const capabilityPath = join4(resultDirectory, "capability");
|
|
5405
3913
|
const capabilityToken = randomBytes(32).toString("hex");
|
|
5406
|
-
const resultPath =
|
|
3914
|
+
const resultPath = join4(resultDirectory, "result.json");
|
|
5407
3915
|
writeFileSync2(capabilityPath, capabilityToken, {
|
|
5408
3916
|
encoding: "utf8",
|
|
5409
3917
|
flag: "wx",
|
|
@@ -5417,15 +3925,15 @@ function createDelegationWorkspace() {
|
|
|
5417
3925
|
};
|
|
5418
3926
|
}
|
|
5419
3927
|
async function readDelegatedResult(active) {
|
|
5420
|
-
const expectedPath =
|
|
3928
|
+
const expectedPath = join4(active.resultDirectory, "result.json");
|
|
5421
3929
|
if (active.policy.resultPath !== expectedPath) {
|
|
5422
3930
|
throw new Error("delegated result path does not match its private directory");
|
|
5423
3931
|
}
|
|
5424
|
-
const inspected = await
|
|
3932
|
+
const inspected = await lstat2(expectedPath);
|
|
5425
3933
|
if (inspected.isSymbolicLink() || !inspected.isFile()) {
|
|
5426
3934
|
throw new Error("delegated result is not a regular file");
|
|
5427
3935
|
}
|
|
5428
|
-
const handle = await
|
|
3936
|
+
const handle = await open2(expectedPath, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
5429
3937
|
try {
|
|
5430
3938
|
const beforeRead = await handle.stat();
|
|
5431
3939
|
if (!beforeRead.isFile() || beforeRead.size > MAX_DELEGATED_RESULT_BYTES) {
|
|
@@ -5448,17 +3956,14 @@ var DEFAULT_DEPENDENCIES3 = {
|
|
|
5448
3956
|
createDelegationWorkspace,
|
|
5449
3957
|
readDelegatedResult,
|
|
5450
3958
|
removeDelegationWorkspace: async (resultDirectory) => rm(resultDirectory, { recursive: true, force: true }),
|
|
5451
|
-
waitForDelay: async (delayMs) => new Promise((
|
|
3959
|
+
waitForDelay: async (delayMs) => new Promise((resolve6) => setTimeout(resolve6, delayMs)),
|
|
5452
3960
|
resolveWorkspaceDirectory,
|
|
5453
3961
|
loadCatalog: loadCatalog2,
|
|
5454
3962
|
requestPlannotatorReview,
|
|
5455
3963
|
requestPlannotatorReviewStatus,
|
|
5456
3964
|
requestPromptGateReview,
|
|
5457
|
-
readDelegationReplayAudit,
|
|
5458
|
-
readToolFailureDiagnostic,
|
|
5459
|
-
auditCompletedDelegationTranscript,
|
|
5460
3965
|
showWorkflowStatus,
|
|
5461
|
-
createSubagentClient: (
|
|
3966
|
+
createSubagentClient: () => createSubagentDelegationClient(),
|
|
5462
3967
|
createMainStepRuntime: (pi) => createMainStepRuntime({ pi }),
|
|
5463
3968
|
createMutationQueue: createSerialTaskQueue,
|
|
5464
3969
|
flushUnwrittenSession,
|
|
@@ -5485,7 +3990,8 @@ function workflowStatusSnapshot() {
|
|
|
5485
3990
|
kind: "subagent",
|
|
5486
3991
|
agent: this.activeDelegation.agent,
|
|
5487
3992
|
requestId: this.activeDelegation.requestId,
|
|
5488
|
-
progress: this.activeDelegation.progress ?? "starting"
|
|
3993
|
+
progress: this.activeDelegation.progress ?? "starting",
|
|
3994
|
+
activityLog: this.activeDelegation.activityLog ?? []
|
|
5489
3995
|
};
|
|
5490
3996
|
} else if (this.mainSteps.activeStepId) {
|
|
5491
3997
|
execution = { kind: "main" };
|
|
@@ -6063,9 +4569,6 @@ function validateRunWorkflowSemantics(run, workflow) {
|
|
|
6063
4569
|
if (sameWorkflowDigest && currentStepChanged) {
|
|
6064
4570
|
return `current step "${run.currentStepId}" does not match the active workflow digest`;
|
|
6065
4571
|
}
|
|
6066
|
-
if (boundWorkspaceCwd && !currentStep2.subagent) {
|
|
6067
|
-
return `bound workflow current step "${run.currentStepId}" must use a subagent`;
|
|
6068
|
-
}
|
|
6069
4572
|
if (currentStepChanged)
|
|
6070
4573
|
return;
|
|
6071
4574
|
return validatePendingGate(run, currentStep2);
|
|
@@ -6875,69 +5378,83 @@ function buildMainWorkflowNotice(workflow, run, statusShortcutLabel = "Ctrl+Alt+
|
|
|
6875
5378
|
if (!step) {
|
|
6876
5379
|
throw new Error(`unknown workflow step "${run.currentStepId}"`);
|
|
6877
5380
|
}
|
|
6878
|
-
if (!step.subagent) {
|
|
6879
|
-
return [
|
|
6880
|
-
"# Active main-agent workflow",
|
|
6881
|
-
"",
|
|
6882
|
-
`Workflow "${workflow.definition.id}" is running step "${run.currentStepId}" (${step.title}) in this session.`,
|
|
6883
|
-
"Perform only the active workflow step with its allowed resources.",
|
|
6884
|
-
"Call `workflow_complete_step` exactly once when finished.",
|
|
6885
|
-
"Use `/workflow-pause` to halt and repair the workflow before resuming."
|
|
6886
|
-
].join(`
|
|
6887
|
-
`);
|
|
6888
|
-
}
|
|
6889
5381
|
return [
|
|
6890
|
-
"# Active
|
|
5382
|
+
"# Active workflow",
|
|
6891
5383
|
"",
|
|
6892
|
-
`Workflow "${workflow.definition.id}" is running step "${run.currentStepId}" (${step.title}) in
|
|
6893
|
-
|
|
6894
|
-
|
|
5384
|
+
`Workflow "${workflow.definition.id}" is running step "${run.currentStepId}" (${step.title}) in this session.`,
|
|
5385
|
+
...step.agent ? [`Apply the "${step.agent.name}" workflow role prompt.`] : [],
|
|
5386
|
+
"Perform only the active workflow step with its allowed resources.",
|
|
5387
|
+
"Call `workflow_complete_step` exactly once when finished.",
|
|
5388
|
+
`Use \`${statusShortcutLabel}\` or \`/workflow-status\` to inspect status, or \`/workflow-pause\` to halt and repair the workflow before resuming.`
|
|
6895
5389
|
].join(`
|
|
6896
5390
|
`);
|
|
6897
5391
|
}
|
|
6898
|
-
// src/
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
var
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
""
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
5392
|
+
// src/agents/profile.ts
|
|
5393
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
5394
|
+
import { join as join5 } from "node:path";
|
|
5395
|
+
import { fileURLToPath } from "node:url";
|
|
5396
|
+
import { parse } from "yaml";
|
|
5397
|
+
var THINKING_LEVELS = [
|
|
5398
|
+
"off",
|
|
5399
|
+
"minimal",
|
|
5400
|
+
"low",
|
|
5401
|
+
"medium",
|
|
5402
|
+
"high",
|
|
5403
|
+
"xhigh",
|
|
5404
|
+
"max"
|
|
5405
|
+
];
|
|
5406
|
+
var isRecord7 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5407
|
+
function parseAgentProfile(source, name) {
|
|
5408
|
+
if (!source.startsWith(`---
|
|
5409
|
+
`))
|
|
5410
|
+
return { prompt: source.trim() };
|
|
5411
|
+
const end = source.indexOf(`
|
|
5412
|
+
---
|
|
5413
|
+
`, 4);
|
|
5414
|
+
if (end === -1) {
|
|
5415
|
+
throw new Error(`workflow agent profile is invalid: ${name}`);
|
|
5416
|
+
}
|
|
5417
|
+
const metadata = parse(source.slice(4, end));
|
|
5418
|
+
if (!isRecord7(metadata)) {
|
|
5419
|
+
throw new Error(`workflow agent profile metadata must be an object: ${name}`);
|
|
5420
|
+
}
|
|
5421
|
+
const unknownKey = Object.keys(metadata).find((key) => key !== "model" && key !== "thinking");
|
|
5422
|
+
if (unknownKey) {
|
|
5423
|
+
throw new Error(`workflow agent profile has unknown metadata "${unknownKey}": ${name}`);
|
|
5424
|
+
}
|
|
5425
|
+
const model = metadata.model;
|
|
5426
|
+
if (model !== undefined && (typeof model !== "string" || !model.trim())) {
|
|
5427
|
+
throw new Error(`workflow agent profile model must be a non-empty string: ${name}`);
|
|
5428
|
+
}
|
|
5429
|
+
const thinking = metadata.thinking;
|
|
5430
|
+
if (thinking !== undefined && (typeof thinking !== "string" || !THINKING_LEVELS.includes(thinking))) {
|
|
5431
|
+
throw new Error(`workflow agent profile thinking must be one of ${THINKING_LEVELS.join(", ")}: ${name}`);
|
|
5432
|
+
}
|
|
5433
|
+
const prompt = source.slice(end + 5).trim();
|
|
5434
|
+
if (!prompt)
|
|
5435
|
+
throw new Error(`workflow agent profile prompt is empty: ${name}`);
|
|
5436
|
+
return {
|
|
5437
|
+
prompt,
|
|
5438
|
+
...typeof model === "string" ? { model: model.trim() } : {},
|
|
5439
|
+
...typeof thinking === "string" ? { thinking } : {}
|
|
5440
|
+
};
|
|
5441
|
+
}
|
|
5442
|
+
function loadAgentProfile(name, userDirectory = defaultUserWorkflowDirectory2()) {
|
|
5443
|
+
const userPath = join5(userDirectory, "agents", `${name}.md`);
|
|
5444
|
+
const bundledUrl = new URL(`../../examples/starter-kit/agents/${name}.md`, import.meta.url);
|
|
5445
|
+
const path = existsSync2(userPath) ? userPath : fileURLToPath(bundledUrl);
|
|
5446
|
+
try {
|
|
5447
|
+
return parseAgentProfile(readFileSync(path, "utf8"), name);
|
|
5448
|
+
} catch (error) {
|
|
5449
|
+
if (error instanceof Error && error.message.startsWith("workflow agent")) {
|
|
5450
|
+
throw error;
|
|
5451
|
+
}
|
|
5452
|
+
throw new Error(`workflow agent profile is unavailable: ${name}`, {
|
|
5453
|
+
cause: error
|
|
5454
|
+
});
|
|
5455
|
+
}
|
|
6940
5456
|
}
|
|
5457
|
+
|
|
6941
5458
|
// src/prompt/step-contract.ts
|
|
6942
5459
|
function createStepContract({
|
|
6943
5460
|
workflow,
|
|
@@ -7076,6 +5593,7 @@ function createTemplateValues({
|
|
|
7076
5593
|
}
|
|
7077
5594
|
|
|
7078
5595
|
// src/prompt/step-task.ts
|
|
5596
|
+
var rolePrompt = (name) => loadAgentProfile(name).prompt;
|
|
7079
5597
|
var resolveStep = (workflow, run) => {
|
|
7080
5598
|
const step = workflow.definition.steps[run.currentStepId];
|
|
7081
5599
|
if (!step) {
|
|
@@ -7137,13 +5655,11 @@ function buildStepTask(options) {
|
|
|
7137
5655
|
`Run: ${run.runId}`,
|
|
7138
5656
|
`Iteration: ${run.iteration ?? 1}`,
|
|
7139
5657
|
`Step: ${run.currentStepId} (${step.title})`,
|
|
7140
|
-
...
|
|
7141
|
-
`Agent profile: ${step.subagent?.agent ?? "generalist"}`,
|
|
7142
|
-
"Context: fresh workflow-step context; no parent or sibling transcript is inherited."
|
|
7143
|
-
] : [],
|
|
5658
|
+
...step.agent ? [`Agent profile: ${step.agent.name}`] : [],
|
|
7144
5659
|
"",
|
|
7145
5660
|
"## Step instructions",
|
|
7146
5661
|
"",
|
|
5662
|
+
...step.agent ? ["## Role prompt", "", rolePrompt(step.agent.name), ""] : [],
|
|
7147
5663
|
prompt,
|
|
7148
5664
|
"",
|
|
7149
5665
|
...isDelegated ? buildDelegatedHandoffSection(handoff) : [],
|
|
@@ -7287,29 +5803,330 @@ function registerPolicy() {
|
|
|
7287
5803
|
return {
|
|
7288
5804
|
systemPrompt: `${event.systemPrompt}
|
|
7289
5805
|
|
|
7290
|
-
${buildMainWorkflowNotice(workflow, this.run, this.statusShortcutLabel)}`
|
|
7291
|
-
};
|
|
7292
|
-
});
|
|
7293
|
-
}
|
|
7294
|
-
function createLifecycleActions() {
|
|
5806
|
+
${buildMainWorkflowNotice(workflow, this.run, this.statusShortcutLabel)}`
|
|
5807
|
+
};
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
function createLifecycleActions() {
|
|
5811
|
+
return {
|
|
5812
|
+
registerMultilineCommandInput,
|
|
5813
|
+
registerLifecycle,
|
|
5814
|
+
registerPolicy
|
|
5815
|
+
};
|
|
5816
|
+
}
|
|
5817
|
+
|
|
5818
|
+
// src/integrations/subagents/child-policy-envelope.ts
|
|
5819
|
+
import { basename as basename3, dirname as dirname4, resolve as resolve8 } from "node:path";
|
|
5820
|
+
|
|
5821
|
+
// src/integrations/subagents/child-policy-paths.ts
|
|
5822
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
5823
|
+
import { basename as basename2, dirname as dirname2, relative as relative4, resolve as resolve6 } from "node:path";
|
|
5824
|
+
var RESULT_FILE_NAME = "result.json";
|
|
5825
|
+
var CAPABILITY_FILE_NAME = "capability";
|
|
5826
|
+
var RESULT_DIRECTORY_PREFIX = "pi-workflows-step-";
|
|
5827
|
+
var DEFAULT_CHILD_POLICY_ENVIRONMENT = {
|
|
5828
|
+
temporaryDirectory: tmpdir2
|
|
5829
|
+
};
|
|
5830
|
+
var isSafeStepFilePath = ({
|
|
5831
|
+
path,
|
|
5832
|
+
expectedName,
|
|
5833
|
+
environment
|
|
5834
|
+
}) => {
|
|
5835
|
+
const temporaryRoot = resolve6(environment.temporaryDirectory());
|
|
5836
|
+
const candidate = resolve6(path);
|
|
5837
|
+
const relativePath = relative4(temporaryRoot, candidate);
|
|
5838
|
+
return relativePath !== "" && !relativePath.startsWith("..") && !relativePath.includes("\x00") && basename2(candidate) === expectedName && basename2(dirname2(candidate)).startsWith(RESULT_DIRECTORY_PREFIX);
|
|
5839
|
+
};
|
|
5840
|
+
var isSafeStepResultPath = (path, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => isSafeStepFilePath({
|
|
5841
|
+
path,
|
|
5842
|
+
expectedName: RESULT_FILE_NAME,
|
|
5843
|
+
environment
|
|
5844
|
+
});
|
|
5845
|
+
var isSafeStepCapabilityPath = (path, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => isSafeStepFilePath({
|
|
5846
|
+
path,
|
|
5847
|
+
expectedName: CAPABILITY_FILE_NAME,
|
|
5848
|
+
environment
|
|
5849
|
+
});
|
|
5850
|
+
|
|
5851
|
+
// src/integrations/subagents/child-policy-validation.ts
|
|
5852
|
+
import { dirname as dirname3, isAbsolute as isAbsolute8, resolve as resolve7 } from "node:path";
|
|
5853
|
+
|
|
5854
|
+
// src/integrations/subagents/child-policy-sections.ts
|
|
5855
|
+
import { isAbsolute as isAbsolute7, win32 as win323 } from "node:path";
|
|
5856
|
+
var isRecord8 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5857
|
+
var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
5858
|
+
var hasOnlyKeys = (value, allowed) => Object.keys(value).every((key) => allowed.has(key));
|
|
5859
|
+
var isStepPermissions = (value) => {
|
|
5860
|
+
if (!isRecord8(value) || !isRecord8(value.bash))
|
|
5861
|
+
return false;
|
|
5862
|
+
const bash = value.bash;
|
|
5863
|
+
const bashRules = Array.isArray(bash.allow) ? bash.allow : undefined;
|
|
5864
|
+
const isValidMode = bash.mode === "deny" || bash.mode === "allow-list" || bash.mode === "unrestricted";
|
|
5865
|
+
const hasValidRules = bashRules !== undefined && bashRules.every((rule) => isRecord8(rule) && hasOnlyKeys(rule, new Set(["executable", "argsPrefix"])) && typeof rule.executable === "string" && isStringArray(rule.argsPrefix));
|
|
5866
|
+
return hasOnlyKeys(value, new Set(["tools", "mcp", "extensions", "skills", "bash"])) && hasOnlyKeys(bash, new Set(["mode", "allow"])) && isStringArray(value.tools) && isStringArray(value.mcp) && isStringArray(value.extensions) && isStringArray(value.skills) && isValidMode && hasValidRules && (bash.mode !== "allow-list" || bashRules.length > 0);
|
|
5867
|
+
};
|
|
5868
|
+
var parsePermissions2 = (value) => {
|
|
5869
|
+
if (!isStepPermissions(value.permissions)) {
|
|
5870
|
+
throw new Error("child policy permissions are invalid");
|
|
5871
|
+
}
|
|
5872
|
+
return { permissions: value.permissions };
|
|
5873
|
+
};
|
|
5874
|
+
var parseOutcomes = (value) => {
|
|
5875
|
+
const outcomes = value.outcomes;
|
|
5876
|
+
if (!isStringArray(outcomes) || outcomes.length === 0 || new Set(outcomes).size !== outcomes.length) {
|
|
5877
|
+
throw new Error("child policy outcomes are invalid");
|
|
5878
|
+
}
|
|
5879
|
+
const pauseOutcomes = value.pauseOutcomes;
|
|
5880
|
+
if (!isStringArray(pauseOutcomes) || new Set(pauseOutcomes).size !== pauseOutcomes.length || pauseOutcomes.some((outcome) => !outcomes.includes(outcome))) {
|
|
5881
|
+
throw new Error("child policy pause outcomes are invalid");
|
|
5882
|
+
}
|
|
5883
|
+
const summaryMaxChars = value.summaryMaxChars;
|
|
5884
|
+
if (typeof summaryMaxChars !== "number" || !Number.isInteger(summaryMaxChars) || summaryMaxChars < 100 || summaryMaxChars > 50000) {
|
|
5885
|
+
throw new Error("child policy summaryMaxChars is invalid");
|
|
5886
|
+
}
|
|
5887
|
+
const gateSubmitOutcome = value.gateSubmitOutcome;
|
|
5888
|
+
if (gateSubmitOutcome !== undefined && (typeof gateSubmitOutcome !== "string" || !outcomes.includes(gateSubmitOutcome))) {
|
|
5889
|
+
throw new Error("child policy gate outcome is invalid");
|
|
5890
|
+
}
|
|
5891
|
+
return {
|
|
5892
|
+
outcomes,
|
|
5893
|
+
pauseOutcomes,
|
|
5894
|
+
summaryMaxChars,
|
|
5895
|
+
...gateSubmitOutcome === undefined ? {} : { gateSubmitOutcome }
|
|
5896
|
+
};
|
|
5897
|
+
};
|
|
5898
|
+
var parseWorkspace2 = (value, outcomes) => {
|
|
5899
|
+
if (value.workspace === undefined)
|
|
5900
|
+
return {};
|
|
5901
|
+
if (!isRecord8(value.workspace) || !hasOnlyKeys(value.workspace, new Set(["bindOn", "allowedRoots"]))) {
|
|
5902
|
+
throw new Error("child policy workspace is invalid");
|
|
5903
|
+
}
|
|
5904
|
+
const bindOn = value.workspace.bindOn;
|
|
5905
|
+
const allowedRoots = value.workspace.allowedRoots;
|
|
5906
|
+
if (!isStringArray(bindOn) || bindOn.length === 0 || new Set(bindOn).size !== bindOn.length || bindOn.some((outcome) => !outcomes.includes(outcome))) {
|
|
5907
|
+
throw new Error("child policy workspace bindOn outcomes are invalid");
|
|
5908
|
+
}
|
|
5909
|
+
if (!isStringArray(allowedRoots) || allowedRoots.length === 0 || allowedRoots.length > MAX_WORKSPACE_ALLOWED_ROOTS || new Set(allowedRoots).size !== allowedRoots.length || allowedRoots.some((root) => !root.trim() || root !== root.trim() || root.length > MAX_WORKSPACE_PATH_CHARS || root.includes("\x00") || win323.parse(root).root !== "" && !isAbsolute7(root))) {
|
|
5910
|
+
throw new Error("child policy workspace allowed roots are invalid");
|
|
5911
|
+
}
|
|
5912
|
+
return { workspace: { bindOn, allowedRoots } };
|
|
5913
|
+
};
|
|
5914
|
+
var parseChildPolicySections = (value) => {
|
|
5915
|
+
const outcomeSections = parseOutcomes(value);
|
|
5916
|
+
return {
|
|
5917
|
+
...parsePermissions2(value),
|
|
5918
|
+
...outcomeSections,
|
|
5919
|
+
...parseWorkspace2(value, outcomeSections.outcomes)
|
|
5920
|
+
};
|
|
5921
|
+
};
|
|
5922
|
+
|
|
5923
|
+
// src/integrations/subagents/child-policy-validation.ts
|
|
5924
|
+
var POLICY_DIGEST_PATTERN = /^[a-f0-9]{64}$/;
|
|
5925
|
+
var CAPABILITY_TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
5926
|
+
var POLICY_KEYS = new Set([
|
|
5927
|
+
"version",
|
|
5928
|
+
"requestId",
|
|
5929
|
+
"agent",
|
|
5930
|
+
"workflowId",
|
|
5931
|
+
"runId",
|
|
5932
|
+
"stepId",
|
|
5933
|
+
"stepTitle",
|
|
5934
|
+
"cwd",
|
|
5935
|
+
"policyDigest",
|
|
5936
|
+
"capabilityPath",
|
|
5937
|
+
"capabilityToken",
|
|
5938
|
+
"resultPath",
|
|
5939
|
+
"permissions",
|
|
5940
|
+
"outcomes",
|
|
5941
|
+
"pauseOutcomes",
|
|
5942
|
+
"summaryMaxChars",
|
|
5943
|
+
"gateSubmitOutcome",
|
|
5944
|
+
"workspace"
|
|
5945
|
+
]);
|
|
5946
|
+
var isRecord9 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5947
|
+
var requiredString = (value, field) => {
|
|
5948
|
+
const candidate = value[field];
|
|
5949
|
+
if (typeof candidate !== "string" || !candidate) {
|
|
5950
|
+
throw new Error(`child policy ${field} must be a non-empty string`);
|
|
5951
|
+
}
|
|
5952
|
+
return candidate;
|
|
5953
|
+
};
|
|
5954
|
+
var isAgentProfileName = (name) => Boolean(name && AGENT_PROFILE_NAME_PATTERN.test(name));
|
|
5955
|
+
var rejectUnknownProperties = (value) => {
|
|
5956
|
+
const unknownKey = Object.keys(value).find((key) => !POLICY_KEYS.has(key));
|
|
5957
|
+
if (unknownKey) {
|
|
5958
|
+
throw new Error(`child policy has unknown property "${unknownKey}"`);
|
|
5959
|
+
}
|
|
5960
|
+
};
|
|
5961
|
+
var parseIdentityAndPaths = (value, environment) => {
|
|
5962
|
+
const requestId = requiredString(value, "requestId");
|
|
5963
|
+
const agent = requiredString(value, "agent");
|
|
5964
|
+
const workflowId = requiredString(value, "workflowId");
|
|
5965
|
+
const runId = requiredString(value, "runId");
|
|
5966
|
+
const stepId = requiredString(value, "stepId");
|
|
5967
|
+
const stepTitle3 = requiredString(value, "stepTitle");
|
|
5968
|
+
const cwd = requiredString(value, "cwd");
|
|
5969
|
+
const policyDigest = requiredString(value, "policyDigest");
|
|
5970
|
+
const capabilityPath = requiredString(value, "capabilityPath");
|
|
5971
|
+
const capabilityToken = requiredString(value, "capabilityToken");
|
|
5972
|
+
const resultPath = requiredString(value, "resultPath");
|
|
5973
|
+
if (value.version !== 1)
|
|
5974
|
+
throw new Error("unsupported child policy version");
|
|
5975
|
+
if (!isAbsolute8(cwd)) {
|
|
5976
|
+
throw new Error("child policy cwd must be an absolute path");
|
|
5977
|
+
}
|
|
5978
|
+
if (!POLICY_DIGEST_PATTERN.test(policyDigest)) {
|
|
5979
|
+
throw new Error("child policy digest is invalid");
|
|
5980
|
+
}
|
|
5981
|
+
if (!isAgentProfileName(agent)) {
|
|
5982
|
+
throw new Error("child policy agent is not a valid agent profile name");
|
|
5983
|
+
}
|
|
5984
|
+
if (!CAPABILITY_TOKEN_PATTERN.test(capabilityToken)) {
|
|
5985
|
+
throw new Error("child policy capability token is invalid");
|
|
5986
|
+
}
|
|
5987
|
+
if (!isSafeStepCapabilityPath(capabilityPath, environment)) {
|
|
5988
|
+
throw new Error("child policy capability path is outside its temporary directory");
|
|
5989
|
+
}
|
|
5990
|
+
if (!isSafeStepResultPath(resultPath, environment)) {
|
|
5991
|
+
throw new Error("child policy result path is outside its temporary directory");
|
|
5992
|
+
}
|
|
5993
|
+
if (dirname3(resolve7(capabilityPath)) !== dirname3(resolve7(resultPath))) {
|
|
5994
|
+
throw new Error("child policy files must share one temporary directory");
|
|
5995
|
+
}
|
|
7295
5996
|
return {
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
5997
|
+
version: 1,
|
|
5998
|
+
requestId,
|
|
5999
|
+
agent,
|
|
6000
|
+
workflowId,
|
|
6001
|
+
runId,
|
|
6002
|
+
stepId,
|
|
6003
|
+
stepTitle: stepTitle3,
|
|
6004
|
+
cwd,
|
|
6005
|
+
policyDigest,
|
|
6006
|
+
capabilityPath,
|
|
6007
|
+
capabilityToken,
|
|
6008
|
+
resultPath
|
|
7299
6009
|
};
|
|
7300
|
-
}
|
|
6010
|
+
};
|
|
6011
|
+
var parseChildPolicy = (value, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => {
|
|
6012
|
+
if (!isRecord9(value))
|
|
6013
|
+
throw new Error("child policy must be an object");
|
|
6014
|
+
rejectUnknownProperties(value);
|
|
6015
|
+
return {
|
|
6016
|
+
...parseIdentityAndPaths(value, environment),
|
|
6017
|
+
...parseChildPolicySections(value)
|
|
6018
|
+
};
|
|
6019
|
+
};
|
|
7301
6020
|
|
|
6021
|
+
// src/integrations/subagents/child-policy-envelope.ts
|
|
6022
|
+
var CHILD_POLICY_OPEN = "<pi-workflows-policy-v1>";
|
|
6023
|
+
var CHILD_POLICY_CLOSE = "</pi-workflows-policy-v1>";
|
|
6024
|
+
var UPSTREAM_TASK_PREFIX = "Task: ";
|
|
6025
|
+
var UPSTREAM_TASK_FILE_OPEN = '<file name="';
|
|
6026
|
+
var UPSTREAM_TASK_FILE_HEADER_CLOSE = `">
|
|
6027
|
+
`;
|
|
6028
|
+
var UPSTREAM_TASK_FILE_CLOSE = `
|
|
6029
|
+
</file>
|
|
6030
|
+
`;
|
|
6031
|
+
var UPSTREAM_TASK_DIRECTORY_PREFIX = "pi-subagent-";
|
|
6032
|
+
var encodeChildPolicy = (policy) => {
|
|
6033
|
+
const encoded = Buffer.from(JSON.stringify(policy), "utf8").toString("base64url");
|
|
6034
|
+
return `${CHILD_POLICY_OPEN}${encoded}${CHILD_POLICY_CLOSE}`;
|
|
6035
|
+
};
|
|
6036
|
+
var unwrapTaskFile = ({
|
|
6037
|
+
text,
|
|
6038
|
+
environment
|
|
6039
|
+
}) => {
|
|
6040
|
+
if (!text.startsWith(UPSTREAM_TASK_FILE_OPEN) || !text.endsWith(UPSTREAM_TASK_FILE_CLOSE)) {
|
|
6041
|
+
return;
|
|
6042
|
+
}
|
|
6043
|
+
const pathStart = UPSTREAM_TASK_FILE_OPEN.length;
|
|
6044
|
+
const headerEnd = text.indexOf(UPSTREAM_TASK_FILE_HEADER_CLOSE, pathStart);
|
|
6045
|
+
if (headerEnd === -1)
|
|
6046
|
+
return;
|
|
6047
|
+
const taskFilePath = text.slice(pathStart, headerEnd);
|
|
6048
|
+
const taskDirectory = dirname4(resolve8(taskFilePath));
|
|
6049
|
+
const isExpectedTaskFile = basename3(taskFilePath) === "task.md" && basename3(taskDirectory).startsWith(UPSTREAM_TASK_DIRECTORY_PREFIX) && dirname4(taskDirectory) === resolve8(environment.temporaryDirectory());
|
|
6050
|
+
if (!isExpectedTaskFile)
|
|
6051
|
+
return;
|
|
6052
|
+
const bodyStart = headerEnd + UPSTREAM_TASK_FILE_HEADER_CLOSE.length;
|
|
6053
|
+
const body = text.slice(bodyStart, -UPSTREAM_TASK_FILE_CLOSE.length);
|
|
6054
|
+
if (!body.startsWith(`${UPSTREAM_TASK_PREFIX}${CHILD_POLICY_OPEN}`)) {
|
|
6055
|
+
return;
|
|
6056
|
+
}
|
|
6057
|
+
return body.slice(UPSTREAM_TASK_PREFIX.length);
|
|
6058
|
+
};
|
|
6059
|
+
var unwrapUpstreamTask = ({
|
|
6060
|
+
text,
|
|
6061
|
+
environment
|
|
6062
|
+
}) => {
|
|
6063
|
+
if (text.startsWith(CHILD_POLICY_OPEN))
|
|
6064
|
+
return text;
|
|
6065
|
+
if (text.startsWith(`${UPSTREAM_TASK_PREFIX}${CHILD_POLICY_OPEN}`)) {
|
|
6066
|
+
return text.slice(UPSTREAM_TASK_PREFIX.length);
|
|
6067
|
+
}
|
|
6068
|
+
return unwrapTaskFile({ text, environment });
|
|
6069
|
+
};
|
|
6070
|
+
var decodePolicy = (encoded) => {
|
|
6071
|
+
try {
|
|
6072
|
+
return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
6073
|
+
} catch {
|
|
6074
|
+
throw new Error("delegated task child policy cannot be decoded");
|
|
6075
|
+
}
|
|
6076
|
+
};
|
|
6077
|
+
var extractChildPolicy = (text, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => {
|
|
6078
|
+
const taskWithPolicy = unwrapUpstreamTask({ text, environment });
|
|
6079
|
+
if (taskWithPolicy === undefined)
|
|
6080
|
+
return;
|
|
6081
|
+
const payloadStart = CHILD_POLICY_OPEN.length;
|
|
6082
|
+
const payloadEnd = taskWithPolicy.indexOf(CHILD_POLICY_CLOSE, payloadStart);
|
|
6083
|
+
const hasNestedEnvelope = taskWithPolicy.indexOf(CHILD_POLICY_OPEN, payloadStart) !== -1;
|
|
6084
|
+
if (payloadEnd === -1 || hasNestedEnvelope) {
|
|
6085
|
+
throw new Error("delegated task contains an invalid child policy envelope");
|
|
6086
|
+
}
|
|
6087
|
+
const encoded = taskWithPolicy.slice(payloadStart, payloadEnd);
|
|
6088
|
+
const task = taskWithPolicy.slice(payloadEnd + CHILD_POLICY_CLOSE.length).trim();
|
|
6089
|
+
if (!task)
|
|
6090
|
+
throw new Error("delegated task is empty after policy extraction");
|
|
6091
|
+
return {
|
|
6092
|
+
policy: parseChildPolicy(decodePolicy(encoded), environment),
|
|
6093
|
+
task
|
|
6094
|
+
};
|
|
6095
|
+
};
|
|
6096
|
+
// src/integrations/subagents/delegated-result.ts
|
|
6097
|
+
var parseDelegatedStepResult = (value, policy) => {
|
|
6098
|
+
try {
|
|
6099
|
+
return parseWorkflowStepResult(value, {
|
|
6100
|
+
policyDigest: policy.policyDigest,
|
|
6101
|
+
outcomes: [...policy.outcomes],
|
|
6102
|
+
summaryMaxChars: policy.summaryMaxChars,
|
|
6103
|
+
...policy.gateSubmitOutcome ? { gateSubmitOutcome: policy.gateSubmitOutcome } : {},
|
|
6104
|
+
...policy.workspace ? { workspace: policy.workspace } : {}
|
|
6105
|
+
});
|
|
6106
|
+
} catch (error) {
|
|
6107
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6108
|
+
throw new Error(message.replaceAll("workflow step", "delegated step"), {
|
|
6109
|
+
cause: error
|
|
6110
|
+
});
|
|
6111
|
+
}
|
|
6112
|
+
};
|
|
7302
6113
|
// src/harness/delegation-plan.ts
|
|
7303
|
-
function delegationTranscriptBinding(requestId, policyDigest) {
|
|
7304
|
-
return `<pi-workflows-delegation-binding-v1>${requestId}:${policyDigest}</pi-workflows-delegation-binding-v1>`;
|
|
7305
|
-
}
|
|
7306
6114
|
function createDelegationPlan(input, dependencies) {
|
|
7307
|
-
const { workflow, run, step, latestContext
|
|
7308
|
-
const
|
|
7309
|
-
if (!
|
|
6115
|
+
const { workflow, run, step, latestContext } = input;
|
|
6116
|
+
const agent = step.agent?.name;
|
|
6117
|
+
if (!agent) {
|
|
6118
|
+
return {
|
|
6119
|
+
kind: "invalid",
|
|
6120
|
+
reason: `Step "${run.currentStepId}" has no agent profile`
|
|
6121
|
+
};
|
|
6122
|
+
}
|
|
6123
|
+
let agentProfile;
|
|
6124
|
+
try {
|
|
6125
|
+
agentProfile = loadAgentProfile(agent);
|
|
6126
|
+
} catch (error) {
|
|
7310
6127
|
return {
|
|
7311
6128
|
kind: "invalid",
|
|
7312
|
-
reason:
|
|
6129
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
7313
6130
|
};
|
|
7314
6131
|
}
|
|
7315
6132
|
if (!run.cwd) {
|
|
@@ -7391,7 +6208,7 @@ function createDelegationPlan(input, dependencies) {
|
|
|
7391
6208
|
const policyDigest = digest({
|
|
7392
6209
|
version: 1,
|
|
7393
6210
|
requestId,
|
|
7394
|
-
agent
|
|
6211
|
+
agent,
|
|
7395
6212
|
runId: run.runId,
|
|
7396
6213
|
stepId: run.currentStepId,
|
|
7397
6214
|
stepDigest: run.currentStepDigest,
|
|
@@ -7405,7 +6222,7 @@ function createDelegationPlan(input, dependencies) {
|
|
|
7405
6222
|
const policy = {
|
|
7406
6223
|
version: 1,
|
|
7407
6224
|
requestId,
|
|
7408
|
-
agent
|
|
6225
|
+
agent,
|
|
7409
6226
|
workflowId: workflow.definition.id,
|
|
7410
6227
|
runId: run.runId,
|
|
7411
6228
|
stepId: run.currentStepId,
|
|
@@ -7422,16 +6239,7 @@ function createDelegationPlan(input, dependencies) {
|
|
|
7422
6239
|
...step.gate ? { gateSubmitOutcome: step.gate.submitOutcome } : {},
|
|
7423
6240
|
...step.workspace ? { workspace: structuredClone(step.workspace) } : {}
|
|
7424
6241
|
};
|
|
7425
|
-
const
|
|
7426
|
-
const transcriptTask = [
|
|
7427
|
-
buildDelegatedStepTask(workflow, run, ""),
|
|
7428
|
-
delegationTranscriptBinding(requestId, policyDigest),
|
|
7429
|
-
...recovery ? [
|
|
7430
|
-
automaticRecoveryTask(recovery.failures.map(({ reason }) => reason), recovery.attempt, MAX_DELEGATION_RECOVERY_ATTEMPTS)
|
|
7431
|
-
] : []
|
|
7432
|
-
].join(`
|
|
7433
|
-
|
|
7434
|
-
`);
|
|
6242
|
+
const task = buildDelegatedStepTask(workflow, run, "");
|
|
7435
6243
|
const active = {
|
|
7436
6244
|
requestId,
|
|
7437
6245
|
runId: run.runId,
|
|
@@ -7440,39 +6248,20 @@ function createDelegationPlan(input, dependencies) {
|
|
|
7440
6248
|
sessionEpoch: input.sessionEpoch,
|
|
7441
6249
|
resultDirectory: workspace.resultDirectory,
|
|
7442
6250
|
policy,
|
|
7443
|
-
transcriptTask,
|
|
7444
|
-
agent
|
|
7445
|
-
...trustedSessionRoot ? { trustedSessionRoot } : {},
|
|
7446
|
-
broadRecoveryAuthorized: subagent.retryToolFailures,
|
|
7447
|
-
recoveryAttemptCount: recovery?.attempt ?? 0,
|
|
7448
|
-
recoveryFailures: recovery?.failures ?? []
|
|
6251
|
+
transcriptTask: task,
|
|
6252
|
+
agent
|
|
7449
6253
|
};
|
|
7450
6254
|
const request = {
|
|
7451
6255
|
version: 1,
|
|
7452
6256
|
requestId,
|
|
7453
|
-
agent
|
|
6257
|
+
agent,
|
|
7454
6258
|
task: `${encodeChildPolicy(policy)}
|
|
7455
6259
|
|
|
7456
|
-
${
|
|
7457
|
-
context: "fresh",
|
|
6260
|
+
${task}`,
|
|
7458
6261
|
cwd: delegationCwd,
|
|
7459
|
-
timeoutMs:
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
outputSchema: WORKFLOW_COMPLETION_PARAMETERS,
|
|
7463
|
-
agentContract: { version: 1 },
|
|
7464
|
-
artifacts: subagent.artifacts,
|
|
7465
|
-
...subagent.model ? { model: subagent.model } : {},
|
|
7466
|
-
...subagent.turnBudget ? { turnBudget: structuredClone(subagent.turnBudget) } : {},
|
|
7467
|
-
...subagent.toolBudget ? {
|
|
7468
|
-
toolBudget: {
|
|
7469
|
-
hard: subagent.toolBudget.hard,
|
|
7470
|
-
...subagent.toolBudget.soft === undefined ? {} : { soft: subagent.toolBudget.soft },
|
|
7471
|
-
...subagent.toolBudget.block === undefined ? {} : {
|
|
7472
|
-
block: subagent.toolBudget.block === "*" ? "*" : [...subagent.toolBudget.block]
|
|
7473
|
-
}
|
|
7474
|
-
}
|
|
7475
|
-
} : {}
|
|
6262
|
+
timeoutMs: 900000,
|
|
6263
|
+
...agentProfile.model ? { model: agentProfile.model } : {},
|
|
6264
|
+
...agentProfile.thinking ? { thinking: agentProfile.thinking } : {}
|
|
7476
6265
|
};
|
|
7477
6266
|
return { kind: "ready", active, request };
|
|
7478
6267
|
}
|
|
@@ -7513,7 +6302,7 @@ function resolveStepEffects(run, step, result, dependencies) {
|
|
|
7513
6302
|
}
|
|
7514
6303
|
|
|
7515
6304
|
// src/harness/step-execution-actions.ts
|
|
7516
|
-
function launchCurrentStep(workflow
|
|
6305
|
+
function launchCurrentStep(workflow) {
|
|
7517
6306
|
const run = this.run;
|
|
7518
6307
|
if (!run || run.status !== "running" || this.activeDelegation || this.mainSteps.activeStepId) {
|
|
7519
6308
|
return;
|
|
@@ -7523,17 +6312,12 @@ function launchCurrentStep(workflow, recovery) {
|
|
|
7523
6312
|
this.pauseForExecutionFailure("Workflow", `Step "${run.currentStepId}" is missing from the workflow`);
|
|
7524
6313
|
return;
|
|
7525
6314
|
}
|
|
7526
|
-
if (!step.subagent) {
|
|
7527
|
-
this.launchMainStep(workflow, run, step);
|
|
7528
|
-
return;
|
|
7529
|
-
}
|
|
7530
6315
|
const plan = createDelegationPlan({
|
|
7531
6316
|
workflow,
|
|
7532
6317
|
run,
|
|
7533
6318
|
step,
|
|
7534
6319
|
sessionEpoch: this.sessionEpoch,
|
|
7535
|
-
latestContext: this.latestContext
|
|
7536
|
-
recovery
|
|
6320
|
+
latestContext: this.latestContext
|
|
7537
6321
|
}, this.dependencies);
|
|
7538
6322
|
if (plan.kind === "invalid") {
|
|
7539
6323
|
this.pauseForExecutionFailure("Subagent step", plan.reason);
|
|
@@ -7620,6 +6404,7 @@ function launchMainStep(workflow, run, step) {
|
|
|
7620
6404
|
outcomes: allowedOutcomes(workflow, run),
|
|
7621
6405
|
summaryMaxChars: workflow.definition.summaryMaxChars,
|
|
7622
6406
|
...step.gate ? { gateSubmitOutcome: step.gate.submitOutcome } : {},
|
|
6407
|
+
...step.workspace ? { workspace: structuredClone(step.workspace) } : {},
|
|
7623
6408
|
onTrace: (lines, context) => this.queueMainStepLog(identity, lines, context),
|
|
7624
6409
|
onSettled: (result, context) => this.queueMainStepResult(identity, result, context)
|
|
7625
6410
|
};
|
|
@@ -7716,11 +6501,20 @@ function handleDelegationUpdate(active, update) {
|
|
|
7716
6501
|
if (this.activeDelegation !== active)
|
|
7717
6502
|
return;
|
|
7718
6503
|
const progress = [
|
|
6504
|
+
update.activity,
|
|
7719
6505
|
update.currentTool ? `tool ${update.currentTool}` : undefined,
|
|
7720
6506
|
update.toolCount !== undefined ? `${update.toolCount} calls` : undefined,
|
|
7721
6507
|
update.tokens !== undefined ? `${update.tokens} tokens` : undefined
|
|
7722
6508
|
].filter((part) => part !== undefined);
|
|
7723
6509
|
active.progress = progress.join(", ") || "running";
|
|
6510
|
+
if (update.detail) {
|
|
6511
|
+
const previous = active.activityLog ?? [];
|
|
6512
|
+
const replacesPreviousResponse = update.detail.startsWith("response: ") && previous.at(-1)?.startsWith("response: ");
|
|
6513
|
+
active.activityLog = [
|
|
6514
|
+
...replacesPreviousResponse ? previous.slice(0, -1) : previous,
|
|
6515
|
+
update.detail
|
|
6516
|
+
].slice(-8);
|
|
6517
|
+
}
|
|
7724
6518
|
this.updateStatus();
|
|
7725
6519
|
}
|
|
7726
6520
|
function queueDelegationResponse(active, response) {
|
|
@@ -7751,49 +6545,19 @@ async function finishDelegation(active, response) {
|
|
|
7751
6545
|
return;
|
|
7752
6546
|
}
|
|
7753
6547
|
this.activeDelegation = undefined;
|
|
7754
|
-
let terminalFailure;
|
|
7755
6548
|
let cleanupAttempted = false;
|
|
7756
6549
|
try {
|
|
7757
6550
|
if (!this.isSessionActive || this.sessionEpoch !== active.sessionEpoch || !this.run || this.run.status !== "running" || this.run.runId !== active.runId || this.run.currentStepId !== active.stepId || this.run.currentStepDigest !== active.stepDigest) {
|
|
7758
6551
|
return;
|
|
7759
6552
|
}
|
|
7760
6553
|
const terminalAt = this.dependencies.now();
|
|
7761
|
-
if (response.requestId === active.requestId && (response.agent === undefined || response.agent === active.agent) && response.sessionFile && active.trustedSessionRoot && typeof response.runId === "string" && response.runId && response.childIndex === 0) {
|
|
7762
|
-
try {
|
|
7763
|
-
this.run = attachSubagentTranscript(this.run, active.requestId, {
|
|
7764
|
-
trustedRoot: active.trustedSessionRoot,
|
|
7765
|
-
sessionFile: response.sessionFile,
|
|
7766
|
-
runId: response.runId,
|
|
7767
|
-
childIndex: response.childIndex
|
|
7768
|
-
}, terminalAt);
|
|
7769
|
-
} catch {}
|
|
7770
|
-
}
|
|
7771
6554
|
const workflow = this.catalog.workflows.get(this.run.workflowId);
|
|
7772
6555
|
const step = workflow?.definition.steps[this.run.currentStepId];
|
|
7773
6556
|
if (!workflow || !step) {
|
|
7774
6557
|
throw new Error("Active workflow configuration is unavailable");
|
|
7775
6558
|
}
|
|
7776
|
-
if (response.status
|
|
7777
|
-
|
|
7778
|
-
if (!transcriptAudit.verified) {
|
|
7779
|
-
throw new Error(`Subagent "${active.agent}" completed without a verifiable terminal transcript: ${transcriptAudit.reason}`);
|
|
7780
|
-
}
|
|
7781
|
-
if (transcriptAudit.warning) {
|
|
7782
|
-
throw new Error(`Subagent "${active.agent}" completed with an unresolved post-completion watchdog warning: ${transcriptAudit.warning}`);
|
|
7783
|
-
}
|
|
7784
|
-
}
|
|
7785
|
-
let recoveredTerminalFailure;
|
|
7786
|
-
if (response.status !== "completed" || this.delegationFailures.hasContradictoryCompletion(response)) {
|
|
7787
|
-
const failure = await this.delegationFailures.describeDelegationFailure(active, response);
|
|
7788
|
-
terminalFailure = failure;
|
|
7789
|
-
if (response.status !== "failed" || failure.diagnostic?.completionAfterFailure !== true) {
|
|
7790
|
-
throw new Error(failure.reason);
|
|
7791
|
-
}
|
|
7792
|
-
const projectionError = this.delegationFailures.recoveredProjectionError(active, response, failure.diagnostic);
|
|
7793
|
-
if (projectionError) {
|
|
7794
|
-
throw new Error(this.delegationFailures.rejectedRecoveryReason(failure, projectionError));
|
|
7795
|
-
}
|
|
7796
|
-
recoveredTerminalFailure = failure;
|
|
6559
|
+
if (response.status !== "completed") {
|
|
6560
|
+
throw new Error(`Workflow worker "${active.agent}" ${response.status.replaceAll("_", " ")}${response.error ? `: ${response.error}` : ""}`);
|
|
7797
6561
|
}
|
|
7798
6562
|
const requiredSkillWarning = step.requires.skills.length > 0 ? response.warnings?.find((warning) => /skill/i.test(warning)) : undefined;
|
|
7799
6563
|
if (requiredSkillWarning) {
|
|
@@ -7803,32 +6567,14 @@ async function finishDelegation(active, response) {
|
|
|
7803
6567
|
try {
|
|
7804
6568
|
serializedResult = await this.dependencies.readDelegatedResult(active);
|
|
7805
6569
|
} catch (error) {
|
|
7806
|
-
if (recoveredTerminalFailure) {
|
|
7807
|
-
throw new Error(this.delegationFailures.rejectedRecoveryReason(recoveredTerminalFailure, error), { cause: error });
|
|
7808
|
-
}
|
|
7809
6570
|
if (hasErrorCode(error, "ENOENT")) {
|
|
7810
6571
|
throw new Error(`Subagent "${active.agent}" completed without producing the required correlated structured_output result`, { cause: error });
|
|
7811
6572
|
}
|
|
7812
6573
|
throw error;
|
|
7813
6574
|
}
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
const rawResult = JSON.parse(serializedResult);
|
|
7817
|
-
result = parseDelegatedStepResult(rawResult, active.policy);
|
|
7818
|
-
} catch (error) {
|
|
7819
|
-
if (recoveredTerminalFailure) {
|
|
7820
|
-
throw new Error(this.delegationFailures.rejectedRecoveryReason(recoveredTerminalFailure, error), { cause: error });
|
|
7821
|
-
}
|
|
7822
|
-
throw error;
|
|
7823
|
-
}
|
|
7824
|
-
if (recoveredTerminalFailure?.diagnostic && !this.delegationFailures.completionMatchesResult(recoveredTerminalFailure.diagnostic, result, active.policy)) {
|
|
7825
|
-
throw new Error(this.delegationFailures.rejectedRecoveryReason(recoveredTerminalFailure, "structured_output transcript value does not match the correlated result"));
|
|
7826
|
-
}
|
|
6575
|
+
const rawResult = JSON.parse(serializedResult);
|
|
6576
|
+
const result = parseDelegatedStepResult(rawResult, active.policy);
|
|
7827
6577
|
const acceptedAt = terminalAt;
|
|
7828
|
-
if (recoveredTerminalFailure) {
|
|
7829
|
-
const isFalsePositive = recoveredTerminalFailure.diagnostic?.correlation === "successful-output-before-completion";
|
|
7830
|
-
this.latestContext?.ui.notify(isFalsePositive ? `Accepted "${active.stepId}" because the trusted child transcript proved the terminal tool error was a false positive and produced a matching structured result` : `Accepted "${active.stepId}" because the child resolved an earlier tool failure and produced a valid structured result`, "warning");
|
|
7831
|
-
}
|
|
7832
6578
|
if (step.gate?.submitOutcome === result.outcome) {
|
|
7833
6579
|
this.run = recordCurrentStepResult(this.run, result, acceptedAt);
|
|
7834
6580
|
await this.submitGate(workflow, this.run, result.outcome, result.summary, result.artifact ?? "");
|
|
@@ -7846,10 +6592,7 @@ async function finishDelegation(active, response) {
|
|
|
7846
6592
|
const reason = error instanceof Error ? error.message : String(error);
|
|
7847
6593
|
cleanupAttempted = true;
|
|
7848
6594
|
await this.cleanupDelegation(active);
|
|
7849
|
-
|
|
7850
|
-
const failureSummary = terminalFailure?.error ? `Subagent ${terminalFailure.status.replaceAll("_", " ")}: ${terminalFailure.error}` : terminalFailure ? `Subagent ${terminalFailure.status.replaceAll("_", " ")}` : reason;
|
|
7851
|
-
this.pauseForDelegationFailure(reason, failureSummary);
|
|
7852
|
-
}
|
|
6595
|
+
this.pauseForDelegationFailure(reason);
|
|
7853
6596
|
} finally {
|
|
7854
6597
|
try {
|
|
7855
6598
|
if (!cleanupAttempted)
|
|
@@ -7870,6 +6613,7 @@ function createDelegationResponseActions() {
|
|
|
7870
6613
|
}
|
|
7871
6614
|
|
|
7872
6615
|
// src/harness/delegation-control-actions.ts
|
|
6616
|
+
var boundedFailureField = (value) => value.length <= 500 ? value : `${value.slice(0, 499)}…`;
|
|
7873
6617
|
async function cancelActiveDelegation(reason) {
|
|
7874
6618
|
const active = this.activeDelegation;
|
|
7875
6619
|
if (!active)
|
|
@@ -7902,25 +6646,6 @@ async function cleanupDelegation(active) {
|
|
|
7902
6646
|
} catch {}
|
|
7903
6647
|
}
|
|
7904
6648
|
}
|
|
7905
|
-
function retryDelegationAfterFailure(active, failure, reason) {
|
|
7906
|
-
if (!failure)
|
|
7907
|
-
return false;
|
|
7908
|
-
const fingerprint = this.delegationFailures.delegationFailureFingerprint(failure);
|
|
7909
|
-
const isRepeatedFailure = active.recoveryFailures.some((previousFailure) => previousFailure.fingerprint === fingerprint);
|
|
7910
|
-
if (active.recoveryAttemptCount >= MAX_DELEGATION_RECOVERY_ATTEMPTS || isRepeatedFailure || !this.delegationFailures.isRetryableTerminalFailure(failure) || !this.delegationFailures.isSafeToRetryDelegation(active.policy, active.broadRecoveryAuthorized, failure.replayAudit) || !this.isSessionActive || this.sessionEpoch !== active.sessionEpoch || !this.run || this.run.status !== "running" || this.run.runId !== active.runId || this.run.currentStepId !== active.stepId || this.run.currentStepDigest !== active.stepDigest || this.activeDelegation) {
|
|
7911
|
-
return false;
|
|
7912
|
-
}
|
|
7913
|
-
const workflow = this.catalog.workflows.get(this.run.workflowId);
|
|
7914
|
-
if (!workflow)
|
|
7915
|
-
return false;
|
|
7916
|
-
const attempt = active.recoveryAttemptCount + 1;
|
|
7917
|
-
this.latestContext?.ui.notify(`Automatic recovery for "${active.stepId}" after a subagent failure (${attempt}/${MAX_DELEGATION_RECOVERY_ATTEMPTS})`, "warning");
|
|
7918
|
-
this.launchCurrentStep(workflow, {
|
|
7919
|
-
attempt,
|
|
7920
|
-
failures: [...active.recoveryFailures, { fingerprint, reason }]
|
|
7921
|
-
});
|
|
7922
|
-
return true;
|
|
7923
|
-
}
|
|
7924
6649
|
function pauseForDelegationFailure(reason, failureSummary = reason) {
|
|
7925
6650
|
this.pauseForExecutionFailure("Subagent step", reason, failureSummary);
|
|
7926
6651
|
}
|
|
@@ -7968,7 +6693,6 @@ function createDelegationControlActions() {
|
|
|
7968
6693
|
return {
|
|
7969
6694
|
cancelActiveDelegation,
|
|
7970
6695
|
cleanupDelegation,
|
|
7971
|
-
retryDelegationAfterFailure,
|
|
7972
6696
|
pauseForDelegationFailure,
|
|
7973
6697
|
pauseForExecutionFailure,
|
|
7974
6698
|
retainUnconfirmedDelegation,
|
|
@@ -8253,12 +6977,8 @@ function preflightStep(step, inventory) {
|
|
|
8253
6977
|
const toolNames = new Set(inventory.tools.map((tool) => tool.name));
|
|
8254
6978
|
const extensionResources = [...inventory.tools, ...inventory.commands];
|
|
8255
6979
|
const hasExtension = (extension) => extensionResources.some((resource) => sourceMatches(resource, extension));
|
|
8256
|
-
const hasSubagentTool = inventory.tools.some((tool) => tool.name === "subagent" && sourceMatches(tool, "pi-subagents"));
|
|
8257
6980
|
const isPlannotatorRequired = step.gate?.provider === "plannotator" && !step.requires.extensions.includes("plannotator");
|
|
8258
6981
|
return [
|
|
8259
|
-
...step.subagent && !hasSubagentTool ? [
|
|
8260
|
-
'pi-subagents is required, but its "subagent" tool is not installed or detectable'
|
|
8261
|
-
] : [],
|
|
8262
6982
|
...missingRequiredResources({
|
|
8263
6983
|
requiredNames: step.requires.tools,
|
|
8264
6984
|
hasResource: (toolName) => toolNames.has(toolName),
|
|
@@ -8463,7 +7183,6 @@ var CORE_ACTIONS = createCoreActions();
|
|
|
8463
7183
|
class WorkflowHarness {
|
|
8464
7184
|
pi;
|
|
8465
7185
|
dependencies;
|
|
8466
|
-
delegationFailures;
|
|
8467
7186
|
subagents;
|
|
8468
7187
|
mainSteps;
|
|
8469
7188
|
catalog = createEmptyCatalog();
|
|
@@ -8511,7 +7230,6 @@ class WorkflowHarness {
|
|
|
8511
7230
|
finishDelegation = DELEGATION_RESPONSE_ACTIONS.finishDelegation;
|
|
8512
7231
|
cancelActiveDelegation = DELEGATION_CONTROL_ACTIONS.cancelActiveDelegation;
|
|
8513
7232
|
cleanupDelegation = DELEGATION_CONTROL_ACTIONS.cleanupDelegation;
|
|
8514
|
-
retryDelegationAfterFailure = DELEGATION_CONTROL_ACTIONS.retryDelegationAfterFailure;
|
|
8515
7233
|
pauseForDelegationFailure = DELEGATION_CONTROL_ACTIONS.pauseForDelegationFailure;
|
|
8516
7234
|
pauseForExecutionFailure = DELEGATION_CONTROL_ACTIONS.pauseForExecutionFailure;
|
|
8517
7235
|
retainUnconfirmedDelegation = DELEGATION_CONTROL_ACTIONS.retainUnconfirmedDelegation;
|
|
@@ -8537,7 +7255,6 @@ class WorkflowHarness {
|
|
|
8537
7255
|
constructor(pi, statusShortcut = DEFAULT_STATUS_SHORTCUT, dependencyOverrides = {}) {
|
|
8538
7256
|
this.pi = pi;
|
|
8539
7257
|
this.dependencies = createWorkflowHarnessDependencies(dependencyOverrides);
|
|
8540
|
-
this.delegationFailures = createDelegationFailureActions(this.dependencies);
|
|
8541
7258
|
this.statusShortcut = statusShortcut;
|
|
8542
7259
|
this.statusShortcutLabel = formatShortcutLabel(statusShortcut);
|
|
8543
7260
|
this.subagents = this.dependencies.createSubagentClient(pi);
|
|
@@ -8597,6 +7314,9 @@ class WorkflowHarness {
|
|
|
8597
7314
|
}
|
|
8598
7315
|
}
|
|
8599
7316
|
|
|
7317
|
+
// src/integrations/subagents/child-runtime.ts
|
|
7318
|
+
import { Type as Type2 } from "typebox";
|
|
7319
|
+
|
|
8600
7320
|
// src/integrations/subagents/child-runtime-completion.ts
|
|
8601
7321
|
var CHILD_COMPLETION_TOOL = "structured_output";
|
|
8602
7322
|
var CHILD_COORDINATION_TOOLS = new Set([
|
|
@@ -8638,9 +7358,9 @@ var parseChildStructuredResult = ({
|
|
|
8638
7358
|
// src/integrations/subagents/child-runtime-dependencies.ts
|
|
8639
7359
|
import { randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
|
|
8640
7360
|
import {
|
|
8641
|
-
existsSync as
|
|
7361
|
+
existsSync as existsSync3,
|
|
8642
7362
|
lstatSync,
|
|
8643
|
-
readFileSync,
|
|
7363
|
+
readFileSync as readFileSync2,
|
|
8644
7364
|
realpathSync as realpathSync2,
|
|
8645
7365
|
renameSync,
|
|
8646
7366
|
statSync as statSync2,
|
|
@@ -8655,9 +7375,9 @@ var tokensAreEqual = (actual, expected) => {
|
|
|
8655
7375
|
};
|
|
8656
7376
|
var DEFAULT_CHILD_RUNTIME_DEPENDENCIES = {
|
|
8657
7377
|
fileSystem: {
|
|
8658
|
-
exists:
|
|
7378
|
+
exists: existsSync3,
|
|
8659
7379
|
inspect: lstatSync,
|
|
8660
|
-
readText: (path) =>
|
|
7380
|
+
readText: (path) => readFileSync2(path, "utf8"),
|
|
8661
7381
|
realPath: realpathSync2,
|
|
8662
7382
|
rename: renameSync,
|
|
8663
7383
|
stat: statSync2,
|
|
@@ -8672,7 +7392,7 @@ var DEFAULT_CHILD_RUNTIME_DEPENDENCIES = {
|
|
|
8672
7392
|
},
|
|
8673
7393
|
createUniqueId: randomUUID2,
|
|
8674
7394
|
currentWorkingDirectory: () => process.cwd(),
|
|
8675
|
-
environmentChildAgent: () => process.env.
|
|
7395
|
+
environmentChildAgent: () => process.env.PI_WORKFLOWS_CHILD_AGENT?.trim() || undefined,
|
|
8676
7396
|
temporaryDirectory: tmpdir3,
|
|
8677
7397
|
tokensAreEqual
|
|
8678
7398
|
};
|
|
@@ -8734,13 +7454,7 @@ var writeChildResult = ({
|
|
|
8734
7454
|
var childPolicyStep = (policy) => ({
|
|
8735
7455
|
title: policy.stepTitle,
|
|
8736
7456
|
prompt: { inline: "Delegated workflow step" },
|
|
8737
|
-
|
|
8738
|
-
agent: policy.agent,
|
|
8739
|
-
context: "fresh",
|
|
8740
|
-
timeoutMs: 900000,
|
|
8741
|
-
artifacts: false,
|
|
8742
|
-
retryToolFailures: false
|
|
8743
|
-
},
|
|
7457
|
+
agent: { name: policy.agent },
|
|
8744
7458
|
permissions: policy.permissions,
|
|
8745
7459
|
requires: { tools: [], extensions: [], skills: [] },
|
|
8746
7460
|
transitions: {},
|
|
@@ -8796,6 +7510,20 @@ var registerSubagentChildRuntime = (pi, options = {}) => {
|
|
|
8796
7510
|
const dependencies = options.dependencies ?? DEFAULT_CHILD_RUNTIME_DEPENDENCIES;
|
|
8797
7511
|
const childAgent = resolveChildAgent(options, dependencies);
|
|
8798
7512
|
let state = INITIAL_STATE;
|
|
7513
|
+
pi.registerTool({
|
|
7514
|
+
name: CHILD_COMPLETION_TOOL,
|
|
7515
|
+
label: "Complete Workflow Step",
|
|
7516
|
+
description: "Return the one structured result for this workflow step",
|
|
7517
|
+
parameters: Type2.Object({ value: Type2.Any() }),
|
|
7518
|
+
executionMode: "sequential",
|
|
7519
|
+
execute: async () => ({
|
|
7520
|
+
content: [
|
|
7521
|
+
{ type: "text", text: "Captured workflow step result." }
|
|
7522
|
+
],
|
|
7523
|
+
details: {},
|
|
7524
|
+
terminate: true
|
|
7525
|
+
})
|
|
7526
|
+
});
|
|
8799
7527
|
pi.on("input", (event) => {
|
|
8800
7528
|
let extracted;
|
|
8801
7529
|
try {
|
|
@@ -8815,9 +7543,8 @@ var registerSubagentChildRuntime = (pi, options = {}) => {
|
|
|
8815
7543
|
return invalidPolicyInput(pi, policyError, event.images);
|
|
8816
7544
|
}
|
|
8817
7545
|
try {
|
|
8818
|
-
if (!
|
|
8819
|
-
throw new Error("
|
|
8820
|
-
}
|
|
7546
|
+
if (!childAgent)
|
|
7547
|
+
throw new Error("workflow worker agent is unavailable");
|
|
8821
7548
|
verifyChildWorkingDirectory(extracted.policy, dependencies);
|
|
8822
7549
|
verifyChildCapability({
|
|
8823
7550
|
policy: extracted.policy,
|
|
@@ -8826,7 +7553,7 @@ var registerSubagentChildRuntime = (pi, options = {}) => {
|
|
|
8826
7553
|
});
|
|
8827
7554
|
const profileTools = new Set(pi.getActiveTools());
|
|
8828
7555
|
if (!profileTools.has(CHILD_COMPLETION_TOOL)) {
|
|
8829
|
-
throw new Error("
|
|
7556
|
+
throw new Error("workflow worker completion tool is unavailable");
|
|
8830
7557
|
}
|
|
8831
7558
|
const effectiveTools = new Set(resolveActiveTools(pi.getAllTools(), childPolicyStep(extracted.policy), CHILD_COMPLETION_TOOL).filter((toolName) => !CHILD_COORDINATION_TOOLS.has(toolName)));
|
|
8832
7559
|
state = {
|
|
@@ -8945,10 +7672,9 @@ var DEFAULT_DEPENDENCIES4 = {
|
|
|
8945
7672
|
loadSettings: loadSettings2,
|
|
8946
7673
|
userWorkflowDirectory: defaultUserWorkflowDirectory2,
|
|
8947
7674
|
runtimeEnvironment: () => ({
|
|
8948
|
-
isSubagentChild: process.env.
|
|
8949
|
-
childAgent: process.env.
|
|
7675
|
+
isSubagentChild: process.env.PI_WORKFLOWS_CHILD === "1",
|
|
7676
|
+
childAgent: process.env.PI_WORKFLOWS_CHILD_AGENT?.trim()
|
|
8950
7677
|
}),
|
|
8951
|
-
isSubagentRuntimeName,
|
|
8952
7678
|
registerChildRuntime: (pi, childAgent) => {
|
|
8953
7679
|
registerSubagentChildRuntime(pi, { childAgent });
|
|
8954
7680
|
},
|
|
@@ -8959,10 +7685,8 @@ var DEFAULT_DEPENDENCIES4 = {
|
|
|
8959
7685
|
function createPiWorkflowsExtension(dependencies) {
|
|
8960
7686
|
return async (pi) => {
|
|
8961
7687
|
const environment = dependencies.runtimeEnvironment();
|
|
8962
|
-
if (environment.isSubagentChild) {
|
|
8963
|
-
|
|
8964
|
-
dependencies.registerChildRuntime(pi, environment.childAgent);
|
|
8965
|
-
}
|
|
7688
|
+
if (environment.isSubagentChild && environment.childAgent) {
|
|
7689
|
+
dependencies.registerChildRuntime(pi, environment.childAgent);
|
|
8966
7690
|
return;
|
|
8967
7691
|
}
|
|
8968
7692
|
const { settings } = await dependencies.loadSettings(dependencies.userWorkflowDirectory());
|