pi-subagents 0.42.0 → 0.43.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 +35 -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 +27 -43
- 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/control-notices.ts +2 -2
- package/src/extension/fanout-child.ts +46 -22
- package/src/extension/index.ts +29 -12
- package/src/extension/public-execution.ts +71 -0
- package/src/extension/rpc.ts +7 -8
- package/src/extension/schemas.ts +17 -17
- 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 +77 -0
- package/src/profiles/profiles.ts +1 -3
- package/src/runs/background/async-execution.ts +3 -0
- package/src/runs/background/async-job-tracker.ts +2 -17
- package/src/runs/background/control-channel.ts +50 -6
- package/src/runs/background/retained-children.ts +68 -0
- package/src/runs/background/scheduled-runs.ts +17 -13
- package/src/runs/background/steering.ts +7 -5
- package/src/runs/background/subagent-runner.ts +10 -4
- package/src/runs/foreground/async-steering-action.ts +36 -10
- package/src/runs/foreground/chain-clarify.ts +13 -16
- package/src/runs/foreground/execution.ts +4 -0
- package/src/runs/foreground/subagent-executor.ts +200 -59
- package/src/runs/shared/acceptance.ts +154 -9
- package/src/runs/shared/subagent-prompt-runtime.ts +94 -25
- package/src/shared/types.ts +17 -3
- 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 +27 -12
- 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 +13 -31
- package/src/workflows/chat-progress.ts +2 -2
- package/src/workflows/scripted-workflow.ts +97 -10
- 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")) {
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { registerNativeSupervisorClient } from "../../intercom/native-supervisor-channel.ts";
|
|
5
5
|
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";
|
|
6
|
+
import { consumeSteerRequestsFromDir, MAX_STEER_QUEUE_SIZE, steerAckPathFromDir, writeSteerAckAt, writeSteerCapabilityAt, writeSteerRequestToDir, type SteerDeliveryStatus, type SteerRequest } from "../background/control-channel.ts";
|
|
7
7
|
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
8
|
import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV, isRuntimeAcknowledgedExtensionId, writeRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
|
|
9
9
|
import { createStructuredOutputToolParameters, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
@@ -205,6 +205,38 @@ function isSubagentToolCallBlock(block: unknown): boolean {
|
|
|
205
205
|
return b?.type === "toolCall" && b.name === "subagent";
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
const PORTABLE_TOOL_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
209
|
+
const COMPOSITE_TOOL_ID_APIS = new Set([
|
|
210
|
+
"azure-openai-responses",
|
|
211
|
+
"openai-codex-responses",
|
|
212
|
+
"openai-completions",
|
|
213
|
+
"openai-responses",
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
function portableToolId(id: string): string {
|
|
217
|
+
if (PORTABLE_TOOL_ID_PATTERN.test(id)) return id;
|
|
218
|
+
return `tool_${Buffer.from(id).toString("base64url") || "empty"}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function sanitizeToolHistoryMessage(message: unknown): unknown {
|
|
222
|
+
const m = message as { role?: string; content?: unknown; toolCallId?: unknown };
|
|
223
|
+
if (m?.role === "toolResult" && typeof m.toolCallId === "string") {
|
|
224
|
+
const toolCallId = portableToolId(m.toolCallId);
|
|
225
|
+
return toolCallId === m.toolCallId ? message : { ...m, toolCallId };
|
|
226
|
+
}
|
|
227
|
+
if (m?.role !== "assistant" || !Array.isArray(m.content)) return message;
|
|
228
|
+
let changed = false;
|
|
229
|
+
const content = m.content.map((block) => {
|
|
230
|
+
const b = block as { type?: string; id?: unknown };
|
|
231
|
+
if (b?.type !== "toolCall" || typeof b.id !== "string") return block;
|
|
232
|
+
const id = portableToolId(b.id);
|
|
233
|
+
if (id === b.id) return block;
|
|
234
|
+
changed = true;
|
|
235
|
+
return { ...b, id };
|
|
236
|
+
});
|
|
237
|
+
return changed ? { ...m, content } : message;
|
|
238
|
+
}
|
|
239
|
+
|
|
208
240
|
function stripAssistantSubagentToolCallBlocks(message: unknown): unknown | undefined {
|
|
209
241
|
const m = message as { role?: string; content?: unknown };
|
|
210
242
|
if (m?.role !== "assistant" || !Array.isArray(m.content)) return message;
|
|
@@ -214,8 +246,9 @@ function stripAssistantSubagentToolCallBlocks(message: unknown): unknown | undef
|
|
|
214
246
|
return { ...m, content: filteredContent };
|
|
215
247
|
}
|
|
216
248
|
|
|
217
|
-
export function stripParentOnlySubagentMessages(messages: unknown[]): unknown[] {
|
|
249
|
+
export function stripParentOnlySubagentMessages(messages: unknown[], options: { sanitizeToolIds?: boolean } = {}): unknown[] {
|
|
218
250
|
const preserveCurrentFanoutToolHistory = process.env[SUBAGENT_FANOUT_CHILD_ENV] === "1";
|
|
251
|
+
const sanitizeToolIds = options.sanitizeToolIds ?? true;
|
|
219
252
|
let changed = false;
|
|
220
253
|
const filtered: unknown[] = [];
|
|
221
254
|
for (const message of messages) {
|
|
@@ -228,15 +261,16 @@ export function stripParentOnlySubagentMessages(messages: unknown[]): unknown[]
|
|
|
228
261
|
changed = true;
|
|
229
262
|
continue;
|
|
230
263
|
}
|
|
231
|
-
|
|
232
|
-
|
|
264
|
+
const sanitized = sanitizeToolIds ? sanitizeToolHistoryMessage(stripped) : stripped;
|
|
265
|
+
if (stripped !== message || sanitized !== stripped) changed = true;
|
|
266
|
+
filtered.push(sanitized);
|
|
233
267
|
}
|
|
234
268
|
return changed ? filtered : messages;
|
|
235
269
|
}
|
|
236
270
|
|
|
237
271
|
export function formatSteerMessage(request: SteerRequest): string {
|
|
238
272
|
return [
|
|
239
|
-
"Mid-run steering from the parent orchestrator:",
|
|
273
|
+
request.mode === "follow_up" ? "Queued follow-up from the parent orchestrator:" : "Mid-run steering from the parent orchestrator:",
|
|
240
274
|
"",
|
|
241
275
|
request.message,
|
|
242
276
|
"",
|
|
@@ -299,22 +333,26 @@ export function registerSteeringInbox(
|
|
|
299
333
|
if (!steerInbox) return;
|
|
300
334
|
const capabilityPath = process.env[SUBAGENT_STEER_CAPABILITY_ENV]?.trim();
|
|
301
335
|
const ackDir = process.env[SUBAGENT_STEER_ACK_DIR_ENV]?.trim();
|
|
302
|
-
const sendUserMessage = (pi as { sendUserMessage?: (content: string, options
|
|
336
|
+
const sendUserMessage = (pi as { sendUserMessage?: (content: string, options?: { deliverAs: "steer" | "followUp" }) => unknown }).sendUserMessage;
|
|
303
337
|
const childIndex = Number(process.env[SUBAGENT_CHILD_INDEX_ENV]);
|
|
304
|
-
const pending = new Map<string,
|
|
338
|
+
const pending = new Map<string, Array<{ request: SteerRequest; deliveryStatus: SteerDeliveryStatus }>>();
|
|
339
|
+
const queued: Array<{ request: SteerRequest; ready: boolean }> = [];
|
|
305
340
|
let disposed = false;
|
|
341
|
+
let agentRunning = false;
|
|
342
|
+
let inTurn = false;
|
|
306
343
|
let flushing = false;
|
|
307
344
|
let started = false;
|
|
308
345
|
let canSteer = typeof sendUserMessage === "function";
|
|
309
346
|
let watcher: fs.FSWatcher | undefined;
|
|
310
347
|
let interval: NodeJS.Timeout | undefined;
|
|
311
|
-
const acknowledge = (request: SteerRequest, state: "delivered" | "failed", message: string): void => {
|
|
348
|
+
const acknowledge = (request: SteerRequest, state: "delivered" | "queued" | "failed", message: string, deliveryStatus?: SteerDeliveryStatus): void => {
|
|
312
349
|
if (!ackDir || !Number.isInteger(childIndex) || childIndex < 0) return;
|
|
313
350
|
writeSteerAckAt(steerAckPathFromDir(ackDir, request.id), {
|
|
314
351
|
requestId: request.id,
|
|
315
352
|
index: childIndex,
|
|
316
353
|
ts: Date.now(),
|
|
317
354
|
state,
|
|
355
|
+
...(deliveryStatus ? { deliveryStatus } : {}),
|
|
318
356
|
message,
|
|
319
357
|
});
|
|
320
358
|
};
|
|
@@ -333,15 +371,22 @@ export function registerSteeringInbox(
|
|
|
333
371
|
acknowledge(request, "failed", "Child Pi session does not support sendUserMessage steering.");
|
|
334
372
|
continue;
|
|
335
373
|
}
|
|
374
|
+
const requestedMode = request.mode ?? "steer";
|
|
375
|
+
const delivery = requestedMode === "follow_up" || (requestedMode === "auto" && inTurn) ? "followUp" as const : "steer" as const;
|
|
376
|
+
const pendingFollowUps = [...pending.values()].reduce((count, entries) => count + entries.filter((entry) => entry.deliveryStatus === "queued").length, 0);
|
|
377
|
+
if (delivery === "followUp" && queued.length + pendingFollowUps >= MAX_STEER_QUEUE_SIZE) {
|
|
378
|
+
acknowledge(request, "failed", `Follow-up queue is full (${MAX_STEER_QUEUE_SIZE} messages).`);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
336
381
|
const formatted = formatSteerMessage(request);
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
pending.set(formatted,
|
|
382
|
+
const entries = pending.get(formatted) ?? [];
|
|
383
|
+
entries.push({ request, deliveryStatus: delivery === "followUp" ? "queued" : "delivered" });
|
|
384
|
+
pending.set(formatted, entries);
|
|
340
385
|
try {
|
|
341
|
-
sendUserMessage(formatted, { deliverAs:
|
|
386
|
+
sendUserMessage(formatted, requestedMode === "auto" && !agentRunning ? undefined : { deliverAs: delivery });
|
|
342
387
|
} catch (error) {
|
|
343
|
-
|
|
344
|
-
if (
|
|
388
|
+
entries.pop();
|
|
389
|
+
if (entries.length === 0) pending.delete(formatted);
|
|
345
390
|
acknowledge(request, "failed", error instanceof Error ? error.message : String(error));
|
|
346
391
|
for (const retry of requests.slice(index + 1)) writeSteerRequestToDir(steerInbox, retry);
|
|
347
392
|
break;
|
|
@@ -354,14 +399,19 @@ export function registerSteeringInbox(
|
|
|
354
399
|
const onInput = (event: unknown): undefined => {
|
|
355
400
|
if (disposed || !event || typeof event !== "object") return undefined;
|
|
356
401
|
const input = event as { source?: unknown; streamingBehavior?: unknown; text?: unknown; content?: unknown };
|
|
357
|
-
if (input.source !== "extension"
|
|
402
|
+
if (input.source !== "extension") return undefined;
|
|
358
403
|
const text = typeof input.text === "string" ? input.text : typeof input.content === "string" ? input.content : undefined;
|
|
359
404
|
if (!text) return undefined;
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
if (!
|
|
363
|
-
if (
|
|
364
|
-
|
|
405
|
+
const entries = pending.get(text);
|
|
406
|
+
const entry = entries?.shift();
|
|
407
|
+
if (!entry) return undefined;
|
|
408
|
+
if (entries?.length === 0) pending.delete(text);
|
|
409
|
+
if (entry.deliveryStatus === "queued") {
|
|
410
|
+
queued.push({ request: entry.request, ready: !inTurn });
|
|
411
|
+
acknowledge(entry.request, "queued", "Pi queued the correlated follow-up input.", "queued");
|
|
412
|
+
} else {
|
|
413
|
+
acknowledge(entry.request, "delivered", "Pi accepted the correlated steering input.", "delivered");
|
|
414
|
+
}
|
|
365
415
|
return undefined;
|
|
366
416
|
};
|
|
367
417
|
const start = (): void => {
|
|
@@ -392,10 +442,27 @@ export function registerSteeringInbox(
|
|
|
392
442
|
// Register input before the watcher so an accepted extension input cannot race request dispatch.
|
|
393
443
|
onRuntimeEvent("input", onInput);
|
|
394
444
|
onRuntimeEvent("session_start", () => start());
|
|
395
|
-
|
|
445
|
+
onRuntimeEvent("agent_start", () => { agentRunning = true; return activate(); });
|
|
446
|
+
onRuntimeEvent("agent_end", () => { agentRunning = false; inTurn = false; return activate(); });
|
|
447
|
+
onRuntimeEvent("turn_start", () => {
|
|
448
|
+
inTurn = true;
|
|
449
|
+
const next = queued.findIndex((entry) => entry.ready);
|
|
450
|
+
if (next >= 0) {
|
|
451
|
+
const [entry] = queued.splice(next, 1);
|
|
452
|
+
if (entry) acknowledge(entry.request, "delivered", "Pi delivered the queued follow-up at a turn boundary.", "delivered");
|
|
453
|
+
}
|
|
454
|
+
return activate();
|
|
455
|
+
});
|
|
456
|
+
onRuntimeEvent("turn_end", () => {
|
|
457
|
+
inTurn = false;
|
|
458
|
+
for (const entry of queued) entry.ready = true;
|
|
459
|
+
return activate();
|
|
460
|
+
});
|
|
461
|
+
for (const eventName of ["message_start", "message_update", "message_end", "tool_execution_start", "tool_execution_end"] as const) {
|
|
396
462
|
onRuntimeEvent(eventName, activate);
|
|
397
463
|
}
|
|
398
464
|
onRuntimeEvent("session_shutdown", () => {
|
|
465
|
+
for (const entry of queued) acknowledge(entry.request, "failed", "Run ended before queued follow-up delivery.", "queued");
|
|
399
466
|
disposed = true;
|
|
400
467
|
try { watcher?.close(); } catch {}
|
|
401
468
|
if (interval) clearInterval(interval);
|
|
@@ -437,8 +504,8 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
437
504
|
nativeSupervisorFallbackRegistered = true;
|
|
438
505
|
registerNativeSupervisorClient(pi);
|
|
439
506
|
};
|
|
440
|
-
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown, ctx?:
|
|
441
|
-
onRuntimeEvent("session_start", (_event: unknown, ctx
|
|
507
|
+
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown, ctx?: ExtensionContext) => unknown) => void;
|
|
508
|
+
onRuntimeEvent("session_start", (_event: unknown, ctx?: ExtensionContext) => {
|
|
442
509
|
const sessionManager = (ctx as { sessionManager?: Parameters<typeof resolveCurrentSessionId>[0] } | undefined)?.sessionManager;
|
|
443
510
|
waitState.currentSessionId = sessionManager ? resolveCurrentSessionId(sessionManager) : null;
|
|
444
511
|
registerNativeSupervisorClientOnce();
|
|
@@ -484,9 +551,11 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
484
551
|
});
|
|
485
552
|
}
|
|
486
553
|
|
|
487
|
-
onRuntimeEvent("context", (event: unknown) => {
|
|
554
|
+
onRuntimeEvent("context", (event: unknown, ctx?: ExtensionContext) => {
|
|
488
555
|
if (!event || typeof event !== "object" || !("messages" in event) || !Array.isArray(event.messages)) return undefined;
|
|
489
|
-
const messages = stripParentOnlySubagentMessages(event.messages
|
|
556
|
+
const messages = stripParentOnlySubagentMessages(event.messages, {
|
|
557
|
+
sanitizeToolIds: !COMPOSITE_TOOL_ID_APIS.has(ctx?.model?.api ?? ""),
|
|
558
|
+
});
|
|
490
559
|
if (messages === event.messages) return undefined;
|
|
491
560
|
return { messages };
|
|
492
561
|
});
|
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 {
|
|
@@ -1007,7 +1021,7 @@ export interface Details {
|
|
|
1007
1021
|
console: Array<{ level: "log" | "info" | "warn" | "error"; text: string }>;
|
|
1008
1022
|
};
|
|
1009
1023
|
chatProgress?: {
|
|
1010
|
-
mode: "off" | "
|
|
1024
|
+
mode: "off" | "live-card";
|
|
1011
1025
|
repoRelation: "same" | "other";
|
|
1012
1026
|
repoLabel?: string;
|
|
1013
1027
|
};
|
|
@@ -1868,7 +1882,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
|
|
|
1868
1882
|
export const POLL_INTERVAL_MS = 250;
|
|
1869
1883
|
export const MAX_WIDGET_JOBS = 4;
|
|
1870
1884
|
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;
|
|
1885
|
+
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
1886
|
|
|
1873
1887
|
export const DEFAULT_FORK_PREAMBLE =
|
|
1874
1888
|
"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;
|
|
@@ -213,13 +213,19 @@ function workflowParams(workflow: PromptWorkflow, args: string[], runtime: Retur
|
|
|
213
213
|
return {
|
|
214
214
|
agent: runtime.agentOverride ?? workflow.agent,
|
|
215
215
|
task,
|
|
216
|
-
clarify: false,
|
|
217
216
|
agentScope: "both",
|
|
218
217
|
...(context ? { context } : {}),
|
|
219
218
|
...(workflow.model ? { model: workflow.model } : {}),
|
|
220
219
|
...(workflow.skill !== undefined ? { skill: workflow.skill } : {}),
|
|
221
220
|
...(workflow.cwd ? { cwd: workflow.cwd } : {}),
|
|
222
|
-
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function promptWorkflowExecutionParams(workflows: PromptWorkflow[], args: string[], runtime: ReturnType<typeof parseRuntimeOptions>): SubagentParamsLike {
|
|
225
|
+
return {
|
|
226
|
+
workflowScript: promptWorkflowScript(workflows, args, runtime),
|
|
227
|
+
agentScope: "both",
|
|
228
|
+
async: runtime.bg ? true : false,
|
|
223
229
|
};
|
|
224
230
|
}
|
|
225
231
|
|
|
@@ -281,10 +287,10 @@ export function registerPromptWorkflowCommands(input: {
|
|
|
281
287
|
if (!step) throw new Error(`Unknown prompt workflow in chain '${workflow.name}': ${stepName}`);
|
|
282
288
|
return step;
|
|
283
289
|
});
|
|
284
|
-
await run(
|
|
290
|
+
await run(promptWorkflowExecutionParams(chain, runtime.args, runtime), ctx);
|
|
285
291
|
return;
|
|
286
292
|
}
|
|
287
|
-
await run(
|
|
293
|
+
await run(promptWorkflowExecutionParams([workflow], runtime.args, runtime), ctx);
|
|
288
294
|
} catch (error) {
|
|
289
295
|
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
290
296
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { normalizePublicSubagentExecution } from "../extension/public-execution.ts";
|
|
3
4
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
4
5
|
import {
|
|
5
6
|
SLASH_SUBAGENT_CANCEL_EVENT,
|
|
@@ -77,21 +78,22 @@ export function registerSlashSubagentBridge(options: SlashBridgeOptions): {
|
|
|
77
78
|
if (!data || typeof data !== "object") return;
|
|
78
79
|
const request = data as Partial<SlashSubagentRequest>;
|
|
79
80
|
if (typeof request.requestId !== "string" || !request.params) return;
|
|
80
|
-
const { requestId
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const { requestId } = request as SlashSubagentRequest;
|
|
82
|
+
const normalized = normalizePublicSubagentExecution((request as SlashSubagentRequest).params);
|
|
83
|
+
if (!normalized.ok) {
|
|
83
84
|
options.events.emit(SLASH_SUBAGENT_RESPONSE_EVENT, {
|
|
84
85
|
requestId,
|
|
85
86
|
result: {
|
|
86
|
-
content: [{ type: "text", text:
|
|
87
|
+
content: [{ type: "text", text: normalized.error }],
|
|
87
88
|
isError: true,
|
|
88
|
-
details: { mode:
|
|
89
|
+
details: { mode: normalized.mode, results: [] },
|
|
89
90
|
},
|
|
90
91
|
isError: true,
|
|
91
|
-
errorText:
|
|
92
|
+
errorText: normalized.error,
|
|
92
93
|
} satisfies SlashSubagentResponse);
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
96
|
+
const params = normalized.params;
|
|
95
97
|
|
|
96
98
|
const ctx = request.ctx ?? options.getContext();
|
|
97
99
|
if (!ctx) {
|