pi-subagents 0.42.1 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +3 -5
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +2 -2
- package/skills/pi-subagents/references/constraints-and-recipes.md +35 -32
- package/skills/pi-subagents/references/execution-controls.md +30 -44
- package/skills/pi-subagents/references/management-authoring-rpc.md +20 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +16 -49
- package/src/agents/agent-refinements.ts +624 -0
- package/src/agents/agents.ts +0 -2
- package/src/agents/proactive-skills.ts +1 -1
- package/src/api/delegation.ts +1 -2
- package/src/extension/config.ts +28 -7
- package/src/extension/control-notices.ts +2 -2
- package/src/extension/fanout-child.ts +46 -22
- package/src/extension/index.ts +32 -13
- package/src/extension/public-execution.ts +71 -0
- package/src/extension/rpc.ts +7 -8
- package/src/extension/schemas.ts +14 -14
- package/src/extension/tool-description.ts +14 -14
- package/src/missions/actions.ts +47 -11
- package/src/missions/goal-driver.ts +162 -0
- package/src/missions/lifecycle.ts +44 -12
- package/src/missions/store.ts +68 -3
- package/src/missions/types.ts +25 -3
- package/src/missions/workflow-state.ts +250 -0
- package/src/profiles/profiles.ts +1 -3
- package/src/runs/background/async-execution.ts +5 -1
- package/src/runs/background/async-job-tracker.ts +2 -17
- package/src/runs/background/async-status.ts +2 -0
- package/src/runs/background/control-channel.ts +50 -6
- package/src/runs/background/retained-children.ts +68 -0
- package/src/runs/background/run-id-resolver.ts +71 -0
- package/src/runs/background/run-status.ts +3 -2
- package/src/runs/background/scheduled-runs.ts +17 -13
- package/src/runs/background/steering.ts +7 -5
- package/src/runs/background/subagent-runner.ts +50 -23
- package/src/runs/foreground/async-steering-action.ts +36 -10
- package/src/runs/foreground/chain-clarify.ts +7 -3
- package/src/runs/foreground/execution.ts +29 -19
- package/src/runs/foreground/subagent-executor.ts +282 -77
- package/src/runs/shared/acceptance.ts +154 -9
- package/src/runs/shared/subagent-prompt-runtime.ts +97 -25
- package/src/shared/settings.ts +10 -6
- package/src/shared/types.ts +22 -2
- package/src/slash/delegation-adapters.ts +0 -13
- package/src/slash/prompt-template-bridge.ts +14 -12
- package/src/slash/prompt-workflows.ts +10 -4
- package/src/slash/slash-bridge.ts +8 -6
- package/src/slash/slash-commands.ts +24 -9
- package/src/slash/slash-live-state.ts +4 -2
- package/src/tui/fleet-status.ts +8 -4
- package/src/tui/fleet.ts +15 -5
- package/src/tui/render.ts +10 -29
- package/src/workflows/scripted-workflow.ts +91 -6
- package/agents/context-builder.md +0 -46
- package/agents/planner.md +0 -56
- package/prompts/parallel-context-build.md +0 -55
- package/prompts/parallel-handoff-plan.md +0 -61
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import * as fs from "node:fs";
|
|
4
5
|
import * as path from "node:path";
|
|
5
6
|
import type {
|
|
@@ -95,7 +96,7 @@ function inferLevel(input: {
|
|
|
95
96
|
&& /\bpatch\s+(?:(?:\.{0,2}[\\/])?(?:[\w.-]+[\\/])+[\w.-]+|[\w.-]+\.[a-z0-9]+\b|(?:the\s+)?parser\b)/.test(task);
|
|
96
97
|
const taskMayWrite = readOnlyTask ? false : taskMayMutate(input.task ?? "") || intent.kind === "implementation" || rolePatchTask;
|
|
97
98
|
const readOnlyAgent = input.acceptanceRole === "read-only"
|
|
98
|
-
|| (input.acceptanceRole === undefined && /\b(?:reviewer|oracle|scout|
|
|
99
|
+
|| (input.acceptanceRole === undefined && /\b(?:reviewer|oracle|scout|researcher|analyst)\b/.test(agent));
|
|
99
100
|
const writeTask = taskMayWrite
|
|
100
101
|
|| (input.acceptanceRole === "writer" && !readOnlyTask)
|
|
101
102
|
|| (input.acceptanceRole === undefined && /\bworker\b/.test(agent) && !readOnlyTask);
|
|
@@ -152,6 +153,17 @@ export function normalizeAcceptanceInput(input: AcceptanceInput | undefined): Ac
|
|
|
152
153
|
return { ...input };
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
type GateAcceptanceNormalizationResult =
|
|
157
|
+
| { ok: true; acceptance?: AcceptanceInput }
|
|
158
|
+
| { ok: false; error: string };
|
|
159
|
+
|
|
160
|
+
export function normalizeGateAcceptance(gate: unknown, acceptance: AcceptanceInput | undefined): GateAcceptanceNormalizationResult {
|
|
161
|
+
if (gate === undefined) return acceptance === undefined ? { ok: true } : { ok: true, acceptance };
|
|
162
|
+
if (typeof gate !== "string" || !gate.trim()) return { ok: false, error: "gate must be a non-empty command string." };
|
|
163
|
+
if (acceptance !== undefined) return { ok: false, error: "gate cannot be combined with acceptance; use one gate command or acceptance.verify." };
|
|
164
|
+
return { ok: true, acceptance: { level: "verified", verify: [{ id: "gate", command: gate.trim() }] } };
|
|
165
|
+
}
|
|
166
|
+
|
|
155
167
|
function explicitAcceptanceCanDisable(explicit: AcceptanceConfig): boolean {
|
|
156
168
|
return explicit.level === "none" && typeof explicit.reason === "string" && explicit.reason.trim().length > 0;
|
|
157
169
|
}
|
|
@@ -959,6 +971,28 @@ function trimOutput(value: string): string | undefined {
|
|
|
959
971
|
return trimmed.length > 12_000 ? `${trimmed.slice(0, 12_000)}\n...[truncated]` : trimmed;
|
|
960
972
|
}
|
|
961
973
|
|
|
974
|
+
const SENSITIVE_ENV_KEY_PATTERN = /(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASS|AUTH|CREDENTIAL|COOKIE|SESSION|PRIVATE|API_KEY|ACCESS_KEY)(?:_|$)/i;
|
|
975
|
+
|
|
976
|
+
function effectiveVerifyEnv(env: Record<string, string> | undefined): Record<string, string> {
|
|
977
|
+
const inherited = Object.fromEntries(Object.entries(process.env).flatMap(([key, value]) => {
|
|
978
|
+
return typeof value === "string" ? [[key, value]] : [];
|
|
979
|
+
}));
|
|
980
|
+
return { ...inherited, ...(env ?? {}) };
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function verifyRedactionEnv(env: Record<string, string> | undefined): Record<string, string> {
|
|
984
|
+
return Object.fromEntries(Object.entries(effectiveVerifyEnv(env)).filter(([key, value]) => {
|
|
985
|
+
return value.length >= 4 && SENSITIVE_ENV_KEY_PATTERN.test(key);
|
|
986
|
+
}));
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function redactVerifyEnv(value: string, env: Record<string, string> | undefined): string {
|
|
990
|
+
let redacted = value;
|
|
991
|
+
const secrets = [...new Set(Object.values(verifyRedactionEnv(env)).filter(Boolean))].sort((left, right) => right.length - left.length);
|
|
992
|
+
for (const secret of secrets) redacted = redacted.replaceAll(secret, "[REDACTED]");
|
|
993
|
+
return redacted;
|
|
994
|
+
}
|
|
995
|
+
|
|
962
996
|
function uniqueStrings(items: Array<string | undefined>): string[] {
|
|
963
997
|
return unique(items.map((item) => item?.trim()).filter((item): item is string => Boolean(item)));
|
|
964
998
|
}
|
|
@@ -995,6 +1029,108 @@ export function aggregateAcceptanceReport(input: {
|
|
|
995
1029
|
};
|
|
996
1030
|
}
|
|
997
1031
|
|
|
1032
|
+
const DEFAULT_VERIFY_TIMEOUT_MS = 120_000;
|
|
1033
|
+
|
|
1034
|
+
function hash(value: string): string {
|
|
1035
|
+
return createHash("sha256").update(value).digest("hex");
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
interface VerifyWorkspaceState {
|
|
1039
|
+
kind: "git-tracked";
|
|
1040
|
+
repoRoot: string;
|
|
1041
|
+
cwdRelative: string;
|
|
1042
|
+
head: string;
|
|
1043
|
+
diffHash: string;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function readVerifyWorkspaceState(cwd: string): VerifyWorkspaceState | undefined {
|
|
1047
|
+
const repo = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf-8" });
|
|
1048
|
+
if (repo.status !== 0 || !repo.stdout.trim()) return undefined;
|
|
1049
|
+
const repoRoot = fs.realpathSync(repo.stdout.trim());
|
|
1050
|
+
const head = spawnSync("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf-8" });
|
|
1051
|
+
const diff = spawnSync("git", ["diff", "--binary", "--full-index", "HEAD", "--"], { cwd: repoRoot, encoding: "utf-8", maxBuffer: 50 * 1024 * 1024 });
|
|
1052
|
+
if (head.status !== 0 || diff.status !== 0 || !head.stdout.trim()) return undefined;
|
|
1053
|
+
return {
|
|
1054
|
+
kind: "git-tracked",
|
|
1055
|
+
repoRoot,
|
|
1056
|
+
cwdRelative: path.relative(repoRoot, fs.realpathSync(cwd)) || ".",
|
|
1057
|
+
head: head.stdout.trim(),
|
|
1058
|
+
diffHash: hash(diff.stdout),
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function isCachedVerifyResult(value: unknown): value is AcceptanceVerifyResult {
|
|
1063
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1064
|
+
const result = value as Partial<AcceptanceVerifyResult>;
|
|
1065
|
+
return typeof result.id === "string"
|
|
1066
|
+
&& typeof result.command === "string"
|
|
1067
|
+
&& (typeof result.exitCode === "number" || result.exitCode === null)
|
|
1068
|
+
&& (result.status === "passed" || result.status === "failed" || result.status === "timed-out" || result.status === "allowed-failure")
|
|
1069
|
+
&& typeof result.durationMs === "number";
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
async function runMemoizedVerifyCommand(command: AcceptanceVerifyCommand, defaultCwd: string, options: {
|
|
1073
|
+
signal?: AbortSignal;
|
|
1074
|
+
abortMessage?: string;
|
|
1075
|
+
artifactsDir?: string;
|
|
1076
|
+
runId?: string;
|
|
1077
|
+
} = {}): Promise<AcceptanceVerifyResult> {
|
|
1078
|
+
const cwd = command.cwd ? path.resolve(defaultCwd, command.cwd) : defaultCwd;
|
|
1079
|
+
let workspaceState: VerifyWorkspaceState | undefined;
|
|
1080
|
+
try {
|
|
1081
|
+
workspaceState = readVerifyWorkspaceState(cwd);
|
|
1082
|
+
} catch {
|
|
1083
|
+
workspaceState = undefined;
|
|
1084
|
+
}
|
|
1085
|
+
if (!workspaceState || !options.artifactsDir || !options.runId) {
|
|
1086
|
+
return runVerifyCommand(command, defaultCwd, options);
|
|
1087
|
+
}
|
|
1088
|
+
const envKeys = Object.keys(command.env ?? {}).sort();
|
|
1089
|
+
const envHash = hash(JSON.stringify(Object.fromEntries(Object.entries(effectiveVerifyEnv(command.env)).sort(([left], [right]) => left.localeCompare(right)))));
|
|
1090
|
+
const timeoutMs = command.timeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
1091
|
+
const cacheKey = hash(JSON.stringify({
|
|
1092
|
+
version: 1,
|
|
1093
|
+
command: command.command,
|
|
1094
|
+
cwdRelative: workspaceState.cwdRelative,
|
|
1095
|
+
envKeys,
|
|
1096
|
+
envHash,
|
|
1097
|
+
timeoutMs,
|
|
1098
|
+
allowFailure: command.allowFailure === true,
|
|
1099
|
+
head: workspaceState.head,
|
|
1100
|
+
diffHash: workspaceState.diffHash,
|
|
1101
|
+
}));
|
|
1102
|
+
const artifactPath = path.join(options.artifactsDir, "acceptance", "verify", options.runId, `${cacheKey}.json`);
|
|
1103
|
+
try {
|
|
1104
|
+
const cached = JSON.parse(fs.readFileSync(artifactPath, "utf-8")) as { cacheKey?: unknown; result?: unknown };
|
|
1105
|
+
if (cached.cacheKey === cacheKey && isCachedVerifyResult(cached.result)) {
|
|
1106
|
+
return { ...cached.result, id: command.id, command: command.command, cwd, artifactPath, cacheKey, memoized: true, envKeys, envHash, workspaceState };
|
|
1107
|
+
}
|
|
1108
|
+
} catch {
|
|
1109
|
+
// A cache miss or unreadable artifact must not prevent host verification.
|
|
1110
|
+
}
|
|
1111
|
+
const result = await runVerifyCommand(command, defaultCwd, options);
|
|
1112
|
+
const evidenced: AcceptanceVerifyResult = { ...result, artifactPath, cacheKey, memoized: false, envKeys, envHash, workspaceState };
|
|
1113
|
+
try {
|
|
1114
|
+
fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
|
|
1115
|
+
fs.writeFileSync(artifactPath, JSON.stringify({
|
|
1116
|
+
version: 1,
|
|
1117
|
+
cacheKey,
|
|
1118
|
+
command: command.command,
|
|
1119
|
+
cwdRelative: workspaceState.cwdRelative,
|
|
1120
|
+
envKeys,
|
|
1121
|
+
envHash,
|
|
1122
|
+
timeoutMs,
|
|
1123
|
+
allowFailure: command.allowFailure === true,
|
|
1124
|
+
workspaceState,
|
|
1125
|
+
result: evidenced,
|
|
1126
|
+
}, null, 2), "utf-8");
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
evidenced.artifactError = error instanceof Error ? error.message : String(error);
|
|
1129
|
+
delete evidenced.artifactPath;
|
|
1130
|
+
}
|
|
1131
|
+
return evidenced;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
998
1134
|
function runVerifyCommand(command: AcceptanceVerifyCommand, defaultCwd: string, options: { signal?: AbortSignal; abortMessage?: string } = {}): Promise<AcceptanceVerifyResult> {
|
|
999
1135
|
return new Promise((resolve) => {
|
|
1000
1136
|
const startedAt = Date.now();
|
|
@@ -1006,7 +1142,7 @@ function runVerifyCommand(command: AcceptanceVerifyCommand, defaultCwd: string,
|
|
|
1006
1142
|
let hardKill: NodeJS.Timeout | undefined;
|
|
1007
1143
|
const child = spawn(command.command, {
|
|
1008
1144
|
cwd,
|
|
1009
|
-
env:
|
|
1145
|
+
env: effectiveVerifyEnv(command.env),
|
|
1010
1146
|
shell: true,
|
|
1011
1147
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1012
1148
|
windowsHide: true,
|
|
@@ -1034,13 +1170,13 @@ function runVerifyCommand(command: AcceptanceVerifyCommand, defaultCwd: string,
|
|
|
1034
1170
|
finish({
|
|
1035
1171
|
exitCode: null,
|
|
1036
1172
|
status: "timed-out",
|
|
1037
|
-
stdout: trimOutput(stdout),
|
|
1038
|
-
stderr: trimOutput(stderr || options.abortMessage || "Acceptance verification timed out."),
|
|
1173
|
+
stdout: trimOutput(redactVerifyEnv(stdout, command.env)),
|
|
1174
|
+
stderr: trimOutput(redactVerifyEnv(stderr || options.abortMessage || "Acceptance verification timed out.", command.env)),
|
|
1039
1175
|
});
|
|
1040
1176
|
}, 1000);
|
|
1041
1177
|
hardKill.unref?.();
|
|
1042
1178
|
};
|
|
1043
|
-
const timeout = setTimeout(abortVerification, command.timeoutMs ??
|
|
1179
|
+
const timeout = setTimeout(abortVerification, command.timeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS);
|
|
1044
1180
|
timeout.unref?.();
|
|
1045
1181
|
if (options.signal?.aborted) abortVerification();
|
|
1046
1182
|
else options.signal?.addEventListener("abort", abortVerification, { once: true });
|
|
@@ -1055,15 +1191,17 @@ function runVerifyCommand(command: AcceptanceVerifyCommand, defaultCwd: string,
|
|
|
1055
1191
|
finish({
|
|
1056
1192
|
exitCode,
|
|
1057
1193
|
status: timedOut ? "timed-out" : passed ? "passed" : command.allowFailure ? "allowed-failure" : "failed",
|
|
1058
|
-
stdout: trimOutput(stdout),
|
|
1059
|
-
stderr: trimOutput(stderr || (timedOut ? options.abortMessage ?? "" : "")),
|
|
1194
|
+
stdout: trimOutput(redactVerifyEnv(stdout, command.env)),
|
|
1195
|
+
stderr: trimOutput(redactVerifyEnv(stderr || (timedOut ? options.abortMessage ?? "" : ""), command.env)),
|
|
1060
1196
|
});
|
|
1061
1197
|
});
|
|
1062
1198
|
child.on("error", (error) => {
|
|
1063
1199
|
finish({
|
|
1064
1200
|
exitCode: timedOut ? null : 1,
|
|
1065
1201
|
status: timedOut ? "timed-out" : command.allowFailure ? "allowed-failure" : "failed",
|
|
1066
|
-
stderr: timedOut
|
|
1202
|
+
stderr: timedOut
|
|
1203
|
+
? trimOutput(redactVerifyEnv(stderr || options.abortMessage || "Acceptance verification timed out.", command.env))
|
|
1204
|
+
: redactVerifyEnv(error instanceof Error ? error.message : String(error), command.env),
|
|
1067
1205
|
});
|
|
1068
1206
|
});
|
|
1069
1207
|
});
|
|
@@ -1085,6 +1223,8 @@ export async function evaluateAcceptance(input: {
|
|
|
1085
1223
|
signal?: AbortSignal;
|
|
1086
1224
|
abortMessage?: string;
|
|
1087
1225
|
reportOptional?: boolean;
|
|
1226
|
+
artifactsDir?: string;
|
|
1227
|
+
runId?: string;
|
|
1088
1228
|
}): Promise<AcceptanceLedger> {
|
|
1089
1229
|
const acceptance = input.acceptance;
|
|
1090
1230
|
const initialStatus = acceptance.level === "none" ? "not-required" : "claimed";
|
|
@@ -1146,7 +1286,12 @@ export async function evaluateAcceptance(input: {
|
|
|
1146
1286
|
}
|
|
1147
1287
|
ledger.verifyRuns = [];
|
|
1148
1288
|
for (const command of acceptance.verify) {
|
|
1149
|
-
ledger.verifyRuns.push(await
|
|
1289
|
+
ledger.verifyRuns.push(await runMemoizedVerifyCommand(command, input.cwd, {
|
|
1290
|
+
signal: input.signal,
|
|
1291
|
+
abortMessage: input.abortMessage,
|
|
1292
|
+
artifactsDir: input.artifactsDir,
|
|
1293
|
+
runId: input.runId,
|
|
1294
|
+
}));
|
|
1150
1295
|
if (input.signal?.aborted) break;
|
|
1151
1296
|
}
|
|
1152
1297
|
if (ledger.verifyRuns.some((run) => run.status === "failed" || run.status === "timed-out")) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import { registerNativeSupervisorClient } from "../../intercom/native-supervisor-channel.ts";
|
|
5
6
|
import { decodePermissionRules, permissionDecision, PERMISSION_AUDIT_PATH_ENV, PERMISSION_POLICY_ENV } from "./permissions.ts";
|
|
6
|
-
import { consumeSteerRequestsFromDir, steerAckPathFromDir, writeSteerAckAt, writeSteerCapabilityAt, writeSteerRequestToDir, type SteerRequest } from "../background/control-channel.ts";
|
|
7
|
+
import { consumeSteerRequestsFromDir, MAX_STEER_QUEUE_SIZE, steerAckPathFromDir, writeSteerAckAt, writeSteerCapabilityAt, writeSteerRequestToDir, type SteerDeliveryStatus, type SteerRequest } from "../background/control-channel.ts";
|
|
7
8
|
import { SUBAGENT_CHILD_AGENT_ENV, SUBAGENT_CHILD_INDEX_ENV, SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_STEER_ACK_DIR_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_INBOX_ENV } from "./pi-args.ts";
|
|
8
9
|
import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV, isRuntimeAcknowledgedExtensionId, writeRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
|
|
9
10
|
import { createStructuredOutputToolParameters, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
@@ -205,6 +206,40 @@ function isSubagentToolCallBlock(block: unknown): boolean {
|
|
|
205
206
|
return b?.type === "toolCall" && b.name === "subagent";
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
const PORTABLE_TOOL_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
210
|
+
const MAX_PORTABLE_TOOL_ID_LENGTH = 64;
|
|
211
|
+
const COMPOSITE_TOOL_ID_APIS = new Set([
|
|
212
|
+
"azure-openai-responses",
|
|
213
|
+
"openai-completions",
|
|
214
|
+
"openai-responses",
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
function portableToolId(id: string): string {
|
|
218
|
+
if (PORTABLE_TOOL_ID_PATTERN.test(id) && id.length <= MAX_PORTABLE_TOOL_ID_LENGTH) return id;
|
|
219
|
+
const encoded = `tool_${Buffer.from(id).toString("base64url") || "empty"}`;
|
|
220
|
+
if (encoded.length <= MAX_PORTABLE_TOOL_ID_LENGTH) return encoded;
|
|
221
|
+
return `tool_${createHash("sha256").update(id).digest("base64url")}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function sanitizeToolHistoryMessage(message: unknown): unknown {
|
|
225
|
+
const m = message as { role?: string; content?: unknown; toolCallId?: unknown };
|
|
226
|
+
if (m?.role === "toolResult" && typeof m.toolCallId === "string") {
|
|
227
|
+
const toolCallId = portableToolId(m.toolCallId);
|
|
228
|
+
return toolCallId === m.toolCallId ? message : { ...m, toolCallId };
|
|
229
|
+
}
|
|
230
|
+
if (m?.role !== "assistant" || !Array.isArray(m.content)) return message;
|
|
231
|
+
let changed = false;
|
|
232
|
+
const content = m.content.map((block) => {
|
|
233
|
+
const b = block as { type?: string; id?: unknown };
|
|
234
|
+
if (b?.type !== "toolCall" || typeof b.id !== "string") return block;
|
|
235
|
+
const id = portableToolId(b.id);
|
|
236
|
+
if (id === b.id) return block;
|
|
237
|
+
changed = true;
|
|
238
|
+
return { ...b, id };
|
|
239
|
+
});
|
|
240
|
+
return changed ? { ...m, content } : message;
|
|
241
|
+
}
|
|
242
|
+
|
|
208
243
|
function stripAssistantSubagentToolCallBlocks(message: unknown): unknown | undefined {
|
|
209
244
|
const m = message as { role?: string; content?: unknown };
|
|
210
245
|
if (m?.role !== "assistant" || !Array.isArray(m.content)) return message;
|
|
@@ -214,8 +249,9 @@ function stripAssistantSubagentToolCallBlocks(message: unknown): unknown | undef
|
|
|
214
249
|
return { ...m, content: filteredContent };
|
|
215
250
|
}
|
|
216
251
|
|
|
217
|
-
export function stripParentOnlySubagentMessages(messages: unknown[]): unknown[] {
|
|
252
|
+
export function stripParentOnlySubagentMessages(messages: unknown[], options: { sanitizeToolIds?: boolean } = {}): unknown[] {
|
|
218
253
|
const preserveCurrentFanoutToolHistory = process.env[SUBAGENT_FANOUT_CHILD_ENV] === "1";
|
|
254
|
+
const sanitizeToolIds = options.sanitizeToolIds ?? true;
|
|
219
255
|
let changed = false;
|
|
220
256
|
const filtered: unknown[] = [];
|
|
221
257
|
for (const message of messages) {
|
|
@@ -228,15 +264,16 @@ export function stripParentOnlySubagentMessages(messages: unknown[]): unknown[]
|
|
|
228
264
|
changed = true;
|
|
229
265
|
continue;
|
|
230
266
|
}
|
|
231
|
-
|
|
232
|
-
|
|
267
|
+
const sanitized = sanitizeToolIds ? sanitizeToolHistoryMessage(stripped) : stripped;
|
|
268
|
+
if (stripped !== message || sanitized !== stripped) changed = true;
|
|
269
|
+
filtered.push(sanitized);
|
|
233
270
|
}
|
|
234
271
|
return changed ? filtered : messages;
|
|
235
272
|
}
|
|
236
273
|
|
|
237
274
|
export function formatSteerMessage(request: SteerRequest): string {
|
|
238
275
|
return [
|
|
239
|
-
"Mid-run steering from the parent orchestrator:",
|
|
276
|
+
request.mode === "follow_up" ? "Queued follow-up from the parent orchestrator:" : "Mid-run steering from the parent orchestrator:",
|
|
240
277
|
"",
|
|
241
278
|
request.message,
|
|
242
279
|
"",
|
|
@@ -299,22 +336,26 @@ export function registerSteeringInbox(
|
|
|
299
336
|
if (!steerInbox) return;
|
|
300
337
|
const capabilityPath = process.env[SUBAGENT_STEER_CAPABILITY_ENV]?.trim();
|
|
301
338
|
const ackDir = process.env[SUBAGENT_STEER_ACK_DIR_ENV]?.trim();
|
|
302
|
-
const sendUserMessage = (pi as { sendUserMessage?: (content: string, options
|
|
339
|
+
const sendUserMessage = (pi as { sendUserMessage?: (content: string, options?: { deliverAs: "steer" | "followUp" }) => unknown }).sendUserMessage;
|
|
303
340
|
const childIndex = Number(process.env[SUBAGENT_CHILD_INDEX_ENV]);
|
|
304
|
-
const pending = new Map<string,
|
|
341
|
+
const pending = new Map<string, Array<{ request: SteerRequest; deliveryStatus: SteerDeliveryStatus }>>();
|
|
342
|
+
const queued: Array<{ request: SteerRequest; ready: boolean }> = [];
|
|
305
343
|
let disposed = false;
|
|
344
|
+
let agentRunning = false;
|
|
345
|
+
let inTurn = false;
|
|
306
346
|
let flushing = false;
|
|
307
347
|
let started = false;
|
|
308
348
|
let canSteer = typeof sendUserMessage === "function";
|
|
309
349
|
let watcher: fs.FSWatcher | undefined;
|
|
310
350
|
let interval: NodeJS.Timeout | undefined;
|
|
311
|
-
const acknowledge = (request: SteerRequest, state: "delivered" | "failed", message: string): void => {
|
|
351
|
+
const acknowledge = (request: SteerRequest, state: "delivered" | "queued" | "failed", message: string, deliveryStatus?: SteerDeliveryStatus): void => {
|
|
312
352
|
if (!ackDir || !Number.isInteger(childIndex) || childIndex < 0) return;
|
|
313
353
|
writeSteerAckAt(steerAckPathFromDir(ackDir, request.id), {
|
|
314
354
|
requestId: request.id,
|
|
315
355
|
index: childIndex,
|
|
316
356
|
ts: Date.now(),
|
|
317
357
|
state,
|
|
358
|
+
...(deliveryStatus ? { deliveryStatus } : {}),
|
|
318
359
|
message,
|
|
319
360
|
});
|
|
320
361
|
};
|
|
@@ -333,15 +374,22 @@ export function registerSteeringInbox(
|
|
|
333
374
|
acknowledge(request, "failed", "Child Pi session does not support sendUserMessage steering.");
|
|
334
375
|
continue;
|
|
335
376
|
}
|
|
377
|
+
const requestedMode = request.mode ?? "steer";
|
|
378
|
+
const delivery = requestedMode === "follow_up" || (requestedMode === "auto" && inTurn) ? "followUp" as const : "steer" as const;
|
|
379
|
+
const pendingFollowUps = [...pending.values()].reduce((count, entries) => count + entries.filter((entry) => entry.deliveryStatus === "queued").length, 0);
|
|
380
|
+
if (delivery === "followUp" && queued.length + pendingFollowUps >= MAX_STEER_QUEUE_SIZE) {
|
|
381
|
+
acknowledge(request, "failed", `Follow-up queue is full (${MAX_STEER_QUEUE_SIZE} messages).`);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
336
384
|
const formatted = formatSteerMessage(request);
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
pending.set(formatted,
|
|
385
|
+
const entries = pending.get(formatted) ?? [];
|
|
386
|
+
entries.push({ request, deliveryStatus: delivery === "followUp" ? "queued" : "delivered" });
|
|
387
|
+
pending.set(formatted, entries);
|
|
340
388
|
try {
|
|
341
|
-
sendUserMessage(formatted, { deliverAs:
|
|
389
|
+
sendUserMessage(formatted, requestedMode === "auto" && !agentRunning ? undefined : { deliverAs: delivery });
|
|
342
390
|
} catch (error) {
|
|
343
|
-
|
|
344
|
-
if (
|
|
391
|
+
entries.pop();
|
|
392
|
+
if (entries.length === 0) pending.delete(formatted);
|
|
345
393
|
acknowledge(request, "failed", error instanceof Error ? error.message : String(error));
|
|
346
394
|
for (const retry of requests.slice(index + 1)) writeSteerRequestToDir(steerInbox, retry);
|
|
347
395
|
break;
|
|
@@ -354,14 +402,19 @@ export function registerSteeringInbox(
|
|
|
354
402
|
const onInput = (event: unknown): undefined => {
|
|
355
403
|
if (disposed || !event || typeof event !== "object") return undefined;
|
|
356
404
|
const input = event as { source?: unknown; streamingBehavior?: unknown; text?: unknown; content?: unknown };
|
|
357
|
-
if (input.source !== "extension"
|
|
405
|
+
if (input.source !== "extension") return undefined;
|
|
358
406
|
const text = typeof input.text === "string" ? input.text : typeof input.content === "string" ? input.content : undefined;
|
|
359
407
|
if (!text) return undefined;
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
if (!
|
|
363
|
-
if (
|
|
364
|
-
|
|
408
|
+
const entries = pending.get(text);
|
|
409
|
+
const entry = entries?.shift();
|
|
410
|
+
if (!entry) return undefined;
|
|
411
|
+
if (entries?.length === 0) pending.delete(text);
|
|
412
|
+
if (entry.deliveryStatus === "queued") {
|
|
413
|
+
queued.push({ request: entry.request, ready: !inTurn });
|
|
414
|
+
acknowledge(entry.request, "queued", "Pi queued the correlated follow-up input.", "queued");
|
|
415
|
+
} else {
|
|
416
|
+
acknowledge(entry.request, "delivered", "Pi accepted the correlated steering input.", "delivered");
|
|
417
|
+
}
|
|
365
418
|
return undefined;
|
|
366
419
|
};
|
|
367
420
|
const start = (): void => {
|
|
@@ -392,10 +445,27 @@ export function registerSteeringInbox(
|
|
|
392
445
|
// Register input before the watcher so an accepted extension input cannot race request dispatch.
|
|
393
446
|
onRuntimeEvent("input", onInput);
|
|
394
447
|
onRuntimeEvent("session_start", () => start());
|
|
395
|
-
|
|
448
|
+
onRuntimeEvent("agent_start", () => { agentRunning = true; return activate(); });
|
|
449
|
+
onRuntimeEvent("agent_end", () => { agentRunning = false; inTurn = false; return activate(); });
|
|
450
|
+
onRuntimeEvent("turn_start", () => {
|
|
451
|
+
inTurn = true;
|
|
452
|
+
const next = queued.findIndex((entry) => entry.ready);
|
|
453
|
+
if (next >= 0) {
|
|
454
|
+
const [entry] = queued.splice(next, 1);
|
|
455
|
+
if (entry) acknowledge(entry.request, "delivered", "Pi delivered the queued follow-up at a turn boundary.", "delivered");
|
|
456
|
+
}
|
|
457
|
+
return activate();
|
|
458
|
+
});
|
|
459
|
+
onRuntimeEvent("turn_end", () => {
|
|
460
|
+
inTurn = false;
|
|
461
|
+
for (const entry of queued) entry.ready = true;
|
|
462
|
+
return activate();
|
|
463
|
+
});
|
|
464
|
+
for (const eventName of ["message_start", "message_update", "message_end", "tool_execution_start", "tool_execution_end"] as const) {
|
|
396
465
|
onRuntimeEvent(eventName, activate);
|
|
397
466
|
}
|
|
398
467
|
onRuntimeEvent("session_shutdown", () => {
|
|
468
|
+
for (const entry of queued) acknowledge(entry.request, "failed", "Run ended before queued follow-up delivery.", "queued");
|
|
399
469
|
disposed = true;
|
|
400
470
|
try { watcher?.close(); } catch {}
|
|
401
471
|
if (interval) clearInterval(interval);
|
|
@@ -437,8 +507,8 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
437
507
|
nativeSupervisorFallbackRegistered = true;
|
|
438
508
|
registerNativeSupervisorClient(pi);
|
|
439
509
|
};
|
|
440
|
-
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown, ctx?:
|
|
441
|
-
onRuntimeEvent("session_start", (_event: unknown, ctx
|
|
510
|
+
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown, ctx?: ExtensionContext) => unknown) => void;
|
|
511
|
+
onRuntimeEvent("session_start", (_event: unknown, ctx?: ExtensionContext) => {
|
|
442
512
|
const sessionManager = (ctx as { sessionManager?: Parameters<typeof resolveCurrentSessionId>[0] } | undefined)?.sessionManager;
|
|
443
513
|
waitState.currentSessionId = sessionManager ? resolveCurrentSessionId(sessionManager) : null;
|
|
444
514
|
registerNativeSupervisorClientOnce();
|
|
@@ -484,9 +554,11 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
484
554
|
});
|
|
485
555
|
}
|
|
486
556
|
|
|
487
|
-
onRuntimeEvent("context", (event: unknown) => {
|
|
557
|
+
onRuntimeEvent("context", (event: unknown, ctx?: ExtensionContext) => {
|
|
488
558
|
if (!event || typeof event !== "object" || !("messages" in event) || !Array.isArray(event.messages)) return undefined;
|
|
489
|
-
const messages = stripParentOnlySubagentMessages(event.messages
|
|
559
|
+
const messages = stripParentOnlySubagentMessages(event.messages, {
|
|
560
|
+
sanitizeToolIds: !COMPOSITE_TOOL_ID_APIS.has(ctx?.model?.api ?? ""),
|
|
561
|
+
});
|
|
490
562
|
if (messages === event.messages) return undefined;
|
|
491
563
|
return { messages };
|
|
492
564
|
});
|
package/src/shared/settings.ts
CHANGED
|
@@ -23,8 +23,10 @@ export interface ResolvedStepBehavior {
|
|
|
23
23
|
model?: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export type OutputOverrideInput = string | boolean;
|
|
27
|
+
|
|
26
28
|
export interface StepOverrides {
|
|
27
|
-
output?:
|
|
29
|
+
output?: OutputOverrideInput;
|
|
28
30
|
outputMode?: OutputMode;
|
|
29
31
|
reads?: string[] | false;
|
|
30
32
|
progress?: boolean;
|
|
@@ -32,8 +34,10 @@ export interface StepOverrides {
|
|
|
32
34
|
model?: string;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
function normalizeOutputOverride(output:
|
|
36
|
-
|
|
37
|
+
function normalizeOutputOverride(output: unknown): string | false | undefined {
|
|
38
|
+
if (output === false || output === "false") return false;
|
|
39
|
+
if (output === true || output === "true") return undefined;
|
|
40
|
+
return typeof output === "string" && output.length > 0 ? output : undefined;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
// =============================================================================
|
|
@@ -49,7 +53,7 @@ export interface SequentialStep {
|
|
|
49
53
|
as?: string;
|
|
50
54
|
outputSchema?: JsonSchemaObject;
|
|
51
55
|
cwd?: string;
|
|
52
|
-
output?:
|
|
56
|
+
output?: OutputOverrideInput;
|
|
53
57
|
outputMode?: OutputMode;
|
|
54
58
|
reads?: string[] | false;
|
|
55
59
|
progress?: boolean;
|
|
@@ -71,7 +75,7 @@ export interface ParallelTaskItem {
|
|
|
71
75
|
outputSchema?: JsonSchemaObject;
|
|
72
76
|
cwd?: string;
|
|
73
77
|
count?: number;
|
|
74
|
-
output?:
|
|
78
|
+
output?: OutputOverrideInput;
|
|
75
79
|
outputMode?: OutputMode;
|
|
76
80
|
reads?: string[] | false;
|
|
77
81
|
progress?: boolean;
|
|
@@ -123,7 +127,7 @@ export interface CheckpointStep {
|
|
|
123
127
|
agent?: string;
|
|
124
128
|
task?: string;
|
|
125
129
|
as?: string;
|
|
126
|
-
output?:
|
|
130
|
+
output?: OutputOverrideInput;
|
|
127
131
|
outputMode?: OutputMode;
|
|
128
132
|
reads?: string[] | false;
|
|
129
133
|
progress?: boolean;
|
package/src/shared/types.ts
CHANGED
|
@@ -400,7 +400,7 @@ export type ProcessTerminalV1 =
|
|
|
400
400
|
});
|
|
401
401
|
|
|
402
402
|
export type SteeringActionState = "delivered" | "scheduled" | "pending" | "partial" | "recovered" | "failed";
|
|
403
|
-
export type SteeringTargetState = "scheduled" | "pending" | "routed" | "delivered" | "late" | "failed" | "recovered";
|
|
403
|
+
export type SteeringTargetState = "scheduled" | "pending" | "routed" | "queued" | "delivered" | "late" | "failed" | "recovered";
|
|
404
404
|
|
|
405
405
|
export interface SteeringTargetStatus {
|
|
406
406
|
index: number;
|
|
@@ -446,6 +446,7 @@ export interface SteerActionTarget {
|
|
|
446
446
|
export interface SteerActionResult {
|
|
447
447
|
requestId: string;
|
|
448
448
|
state: SteeringActionState;
|
|
449
|
+
deliveryStatus: "delivered" | "queued";
|
|
449
450
|
sourceRunId: string;
|
|
450
451
|
replacementRunId?: string;
|
|
451
452
|
targets: SteerActionTarget[];
|
|
@@ -741,6 +742,19 @@ export interface AcceptanceVerifyResult {
|
|
|
741
742
|
stdout?: string;
|
|
742
743
|
stderr?: string;
|
|
743
744
|
durationMs: number;
|
|
745
|
+
artifactPath?: string;
|
|
746
|
+
cacheKey?: string;
|
|
747
|
+
memoized?: boolean;
|
|
748
|
+
envKeys?: string[];
|
|
749
|
+
envHash?: string;
|
|
750
|
+
workspaceState?: {
|
|
751
|
+
kind: "git-tracked";
|
|
752
|
+
repoRoot: string;
|
|
753
|
+
cwdRelative: string;
|
|
754
|
+
head: string;
|
|
755
|
+
diffHash: string;
|
|
756
|
+
};
|
|
757
|
+
artifactError?: string;
|
|
744
758
|
}
|
|
745
759
|
|
|
746
760
|
export interface AcceptanceReviewResult {
|
|
@@ -936,6 +950,8 @@ export interface SpawnBudgetSnapshot {
|
|
|
936
950
|
export interface Details {
|
|
937
951
|
mode: SubagentResultMode | "management";
|
|
938
952
|
runId?: string;
|
|
953
|
+
/** Host tool-call id retained when it differs from the internal run id. */
|
|
954
|
+
toolCallId?: string;
|
|
939
955
|
/** Run-level context summary. "mixed" when children resolved to different modes. */
|
|
940
956
|
context?: "fresh" | "fork" | "mixed";
|
|
941
957
|
results: SingleResult[];
|
|
@@ -1232,6 +1248,8 @@ export interface ExternalProcessStatus {
|
|
|
1232
1248
|
export interface AsyncStatus {
|
|
1233
1249
|
lifecycleArtifactVersion?: SubagentLifecycleArtifactVersion;
|
|
1234
1250
|
runId: string;
|
|
1251
|
+
/** Host tool-call id retained when it differs from the internal run id. */
|
|
1252
|
+
toolCallId?: string;
|
|
1235
1253
|
sessionId?: string;
|
|
1236
1254
|
mode: SubagentRunMode;
|
|
1237
1255
|
context?: "fresh" | "fork" | "mixed";
|
|
@@ -1722,6 +1740,8 @@ export type InlineToolDisplay = "rich" | "summary";
|
|
|
1722
1740
|
export interface ScheduledRunsConfig {
|
|
1723
1741
|
enabled?: boolean;
|
|
1724
1742
|
maxPending?: number;
|
|
1743
|
+
/** Absolute or `~/` root for per-project durable schedules. */
|
|
1744
|
+
storeRoot?: string;
|
|
1725
1745
|
}
|
|
1726
1746
|
|
|
1727
1747
|
export type FleetViewPlacement = "aboveEditor" | "belowEditor";
|
|
@@ -1868,7 +1888,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
|
|
|
1868
1888
|
export const POLL_INTERVAL_MS = 250;
|
|
1869
1889
|
export const MAX_WIDGET_JOBS = 4;
|
|
1870
1890
|
export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
|
|
1871
|
-
export const SUBAGENT_ACTIONS = ["list", "get", "models", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.attach-run", "mission.close", "worktree.discard", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "append-step", "approve-checkpoint", "reject-checkpoint", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
|
|
1891
|
+
export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "append-step", "approve-checkpoint", "reject-checkpoint", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
|
|
1872
1892
|
|
|
1873
1893
|
export const DEFAULT_FORK_PREAMBLE =
|
|
1874
1894
|
"You are a delegated subagent running from a fork of the parent session. " +
|
|
@@ -286,19 +286,6 @@ export function toDelegationUpdate(requestId: string, update: PromptTemplateBrid
|
|
|
286
286
|
};
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
export function toLegacyExecutionParams(request: PromptTemplateDelegationRequest): DelegatedSubagentExecutionParams {
|
|
290
|
-
return {
|
|
291
|
-
agent: request.agent,
|
|
292
|
-
task: request.task,
|
|
293
|
-
context: request.context,
|
|
294
|
-
model: request.model,
|
|
295
|
-
cwd: request.cwd,
|
|
296
|
-
async: false,
|
|
297
|
-
foregroundOnly: true,
|
|
298
|
-
clarify: false,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
289
|
export function toSubagentDelegationExecutionParams(request: SubagentDelegationRequest): DelegatedSubagentExecutionParams {
|
|
303
290
|
return {
|
|
304
291
|
agent: request.agent,
|
|
@@ -12,7 +12,6 @@ import { parseSubagentDelegationRequest } from "./delegation-request.ts";
|
|
|
12
12
|
import {
|
|
13
13
|
parsePromptTemplateRequest,
|
|
14
14
|
toDelegationUpdate,
|
|
15
|
-
toLegacyExecutionParams,
|
|
16
15
|
toPromptTemplateResponse,
|
|
17
16
|
toSubagentDelegationExecutionParams,
|
|
18
17
|
toSubagentDelegationResponse,
|
|
@@ -183,22 +182,25 @@ export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: strin
|
|
|
183
182
|
} else {
|
|
184
183
|
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
185
184
|
const legacy = data as Record<string, unknown>;
|
|
186
|
-
if (legacy.tasks !== undefined || legacy.worktree !== undefined) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
});
|
|
194
|
-
}
|
|
185
|
+
if ((legacy.tasks !== undefined || legacy.worktree !== undefined) && typeof legacy.requestId === "string" && legacy.requestId) {
|
|
186
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, {
|
|
187
|
+
requestId: legacy.requestId,
|
|
188
|
+
messages: [],
|
|
189
|
+
isError: true,
|
|
190
|
+
errorText: "Legacy prompt-template tasks/worktree orchestration was removed; use workflowScript.",
|
|
191
|
+
});
|
|
195
192
|
return;
|
|
196
193
|
}
|
|
197
194
|
}
|
|
198
195
|
legacyRequest = parsePromptTemplateRequest(data);
|
|
199
196
|
if (!legacyRequest) return;
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, {
|
|
198
|
+
...legacyRequest,
|
|
199
|
+
messages: [],
|
|
200
|
+
isError: true,
|
|
201
|
+
errorText: "Legacy prompt-template direct delegation was removed; use workflowScript through the subagent tool or structured delegation.",
|
|
202
|
+
} satisfies PromptTemplateDelegationResponse);
|
|
203
|
+
return;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
if (!structuredRequest && legacyControllers.has(requestId)) return;
|