pi-subagents 0.24.2 → 0.24.4
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 +26 -0
- package/README.md +13 -5
- package/package.json +4 -8
- package/prompts/review-loop.md +41 -0
- package/skills/pi-subagents/SKILL.md +51 -21
- package/src/agents/agent-management.ts +5 -0
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +30 -6
- package/src/agents/skills.ts +25 -23
- package/src/extension/config.ts +16 -0
- package/src/extension/index.ts +8 -24
- package/src/extension/schemas.ts +1 -1
- package/src/intercom/intercom-bridge.ts +2 -1
- package/src/runs/background/async-execution.ts +16 -5
- package/src/runs/background/async-job-tracker.ts +16 -8
- package/src/runs/background/async-status.ts +5 -2
- package/src/runs/background/run-status.ts +4 -1
- package/src/runs/background/subagent-runner.ts +34 -7
- package/src/runs/foreground/execution.ts +17 -5
- package/src/runs/foreground/subagent-executor.ts +6 -7
- package/src/runs/shared/completion-guard.ts +23 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +5 -0
- package/src/runs/shared/run-history.ts +12 -7
- package/src/runs/shared/single-output.ts +12 -2
- package/src/shared/artifacts.ts +2 -2
- package/src/shared/formatters.ts +13 -0
- package/src/shared/model-info.ts +10 -0
- package/src/shared/types.ts +1 -0
- package/src/shared/utils.ts +11 -1
- package/src/tui/render.ts +160 -147
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
formatWorktreeTaskCwdConflict,
|
|
67
67
|
type WorktreeSetup,
|
|
68
68
|
} from "../shared/worktree.ts";
|
|
69
|
+
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
69
70
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
70
71
|
|
|
71
72
|
interface SubagentRunConfig {
|
|
@@ -221,7 +222,12 @@ function runPiStreaming(
|
|
|
221
222
|
...(piPackageRoot ? { piPackageRoot } : {}),
|
|
222
223
|
...(piArgv1 ? { argv1: piArgv1 } : {}),
|
|
223
224
|
});
|
|
224
|
-
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
225
|
+
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
226
|
+
cwd,
|
|
227
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
228
|
+
env: spawnEnv,
|
|
229
|
+
windowsHide: true,
|
|
230
|
+
});
|
|
225
231
|
let stderr = "";
|
|
226
232
|
let stdoutBuf = "";
|
|
227
233
|
let stderrBuf = "";
|
|
@@ -229,6 +235,7 @@ function runPiStreaming(
|
|
|
229
235
|
const usage = emptyUsage();
|
|
230
236
|
let model: string | undefined;
|
|
231
237
|
let error: string | undefined;
|
|
238
|
+
let assistantError: string | undefined;
|
|
232
239
|
let interrupted = false;
|
|
233
240
|
let observedMutationAttempt = false;
|
|
234
241
|
const rawStdoutLines: string[] = [];
|
|
@@ -289,7 +296,7 @@ function runPiStreaming(
|
|
|
289
296
|
|
|
290
297
|
if (event.type !== "message_end" || event.message.role !== "assistant") return;
|
|
291
298
|
if (event.message.model) model = event.message.model;
|
|
292
|
-
if (event.message.errorMessage)
|
|
299
|
+
if (event.message.errorMessage) assistantError = event.message.errorMessage;
|
|
293
300
|
const eventUsage = event.message.usage;
|
|
294
301
|
if (eventUsage) {
|
|
295
302
|
usage.turns++;
|
|
@@ -303,6 +310,7 @@ function runPiStreaming(
|
|
|
303
310
|
const hasToolCall = Array.isArray(event.message.content)
|
|
304
311
|
&& event.message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
305
312
|
if (stopReason === "stop" && !hasToolCall) {
|
|
313
|
+
if (!event.message.errorMessage && extractTextFromContent(event.message.content).trim()) assistantError = undefined;
|
|
306
314
|
cleanTerminalAssistantStopReceived ||= !event.message.errorMessage;
|
|
307
315
|
startFinalDrain();
|
|
308
316
|
}
|
|
@@ -370,7 +378,7 @@ function runPiStreaming(
|
|
|
370
378
|
const termSent = trySignalChild(child, "SIGTERM");
|
|
371
379
|
if (!termSent) return;
|
|
372
380
|
forcedTerminationSignal = true;
|
|
373
|
-
if (!cleanTerminalAssistantStopReceived && !error) {
|
|
381
|
+
if (!cleanTerminalAssistantStopReceived && !error && !assistantError) {
|
|
374
382
|
error = `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its final message. Forcing termination.`;
|
|
375
383
|
}
|
|
376
384
|
finalHardKillTimer = setTimeout(() => {
|
|
@@ -394,14 +402,15 @@ function runPiStreaming(
|
|
|
394
402
|
if (stderrBuf.trim()) appendChildLine("subagent.child.stderr", stderrBuf);
|
|
395
403
|
outputStream.end();
|
|
396
404
|
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
397
|
-
const
|
|
405
|
+
const finalError = error ?? assistantError;
|
|
406
|
+
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !finalError;
|
|
398
407
|
resolve({
|
|
399
408
|
stderr,
|
|
400
409
|
exitCode: interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
401
410
|
messages,
|
|
402
411
|
usage,
|
|
403
412
|
model,
|
|
404
|
-
error: interrupted || forcedDrainAfterFinalSuccess ? undefined :
|
|
413
|
+
error: interrupted || forcedDrainAfterFinalSuccess ? undefined : finalError,
|
|
405
414
|
finalOutput,
|
|
406
415
|
interrupted,
|
|
407
416
|
observedMutationAttempt,
|
|
@@ -416,7 +425,7 @@ function runPiStreaming(
|
|
|
416
425
|
outputStream.end();
|
|
417
426
|
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
418
427
|
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
419
|
-
resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? spawnErrorMessage, finalOutput, observedMutationAttempt });
|
|
428
|
+
resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? assistantError ?? spawnErrorMessage, finalOutput, observedMutationAttempt });
|
|
420
429
|
});
|
|
421
430
|
});
|
|
422
431
|
}
|
|
@@ -545,6 +554,7 @@ interface SingleStepContext {
|
|
|
545
554
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
546
555
|
childIntercomTarget?: string;
|
|
547
556
|
orchestratorIntercomTarget?: string;
|
|
557
|
+
onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
|
|
548
558
|
onChildEvent?: (event: ChildEvent) => void;
|
|
549
559
|
}
|
|
550
560
|
|
|
@@ -596,6 +606,7 @@ async function runSingleStep(
|
|
|
596
606
|
|
|
597
607
|
for (let index = 0; index < candidates.length; index++) {
|
|
598
608
|
const candidate = candidates[index];
|
|
609
|
+
ctx.onAttemptStart?.({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking) });
|
|
599
610
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
600
611
|
const { args, env, tempDir } = buildPiArgs({
|
|
601
612
|
baseArgs: ["--mode", "json", "-p"],
|
|
@@ -611,6 +622,7 @@ async function runSingleStep(
|
|
|
611
622
|
systemPrompt: step.systemPrompt,
|
|
612
623
|
systemPromptMode: step.systemPromptMode,
|
|
613
624
|
mcpDirectTools: step.mcpDirectTools,
|
|
625
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
614
626
|
promptFileStem: step.agent,
|
|
615
627
|
intercomSessionName: ctx.childIntercomTarget,
|
|
616
628
|
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
@@ -633,11 +645,13 @@ async function runSingleStep(
|
|
|
633
645
|
cleanupTempDir(tempDir);
|
|
634
646
|
|
|
635
647
|
const hiddenError = run.exitCode === 0 && !run.error ? detectSubagentError(run.messages) : null;
|
|
636
|
-
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError
|
|
648
|
+
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && step.completionGuard !== false
|
|
637
649
|
? evaluateCompletionMutationGuard({
|
|
638
650
|
agent: step.agent,
|
|
639
651
|
task,
|
|
640
652
|
messages: run.messages,
|
|
653
|
+
tools: step.tools,
|
|
654
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
641
655
|
})
|
|
642
656
|
: undefined;
|
|
643
657
|
const completionGuardTriggered = completionGuard?.triggered === true && !run.observedMutationAttempt;
|
|
@@ -912,6 +926,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
912
926
|
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
913
927
|
skills: step.skills,
|
|
914
928
|
model: step.model,
|
|
929
|
+
thinking: step.thinking,
|
|
915
930
|
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
916
931
|
recentTools: [],
|
|
917
932
|
recentOutput: [],
|
|
@@ -1007,6 +1022,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1007
1022
|
appendControlEvent(event);
|
|
1008
1023
|
return true;
|
|
1009
1024
|
};
|
|
1025
|
+
const updateStepModel = (flatIndex: number, model: string | undefined, thinking: string | undefined, now = Date.now()): void => {
|
|
1026
|
+
const step = statusPayload.steps[flatIndex];
|
|
1027
|
+
if (!step) return;
|
|
1028
|
+
step.model = model;
|
|
1029
|
+
step.thinking = thinking;
|
|
1030
|
+
statusPayload.lastUpdate = now;
|
|
1031
|
+
writeAtomicJson(statusPath, statusPayload);
|
|
1032
|
+
};
|
|
1010
1033
|
const updateStepFromChildEvent = (flatIndex: number, event: ChildEvent): void => {
|
|
1011
1034
|
const step = statusPayload.steps[flatIndex];
|
|
1012
1035
|
if (!step) return;
|
|
@@ -1332,6 +1355,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1332
1355
|
registerInterrupt: (interrupt) => {
|
|
1333
1356
|
activeChildInterrupt = interrupt;
|
|
1334
1357
|
},
|
|
1358
|
+
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
1335
1359
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
1336
1360
|
});
|
|
1337
1361
|
if (task.sessionFile) {
|
|
@@ -1346,6 +1370,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1346
1370
|
statusPayload.steps[fi].durationMs = taskDuration;
|
|
1347
1371
|
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
1348
1372
|
statusPayload.steps[fi].model = singleResult.model;
|
|
1373
|
+
statusPayload.steps[fi].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[fi].thinking);
|
|
1349
1374
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
1350
1375
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1351
1376
|
statusPayload.steps[fi].error = singleResult.error;
|
|
@@ -1475,6 +1500,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1475
1500
|
registerInterrupt: (interrupt) => {
|
|
1476
1501
|
activeChildInterrupt = interrupt;
|
|
1477
1502
|
},
|
|
1503
|
+
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking),
|
|
1478
1504
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
1479
1505
|
});
|
|
1480
1506
|
if (seqStep.sessionFile) {
|
|
@@ -1522,6 +1548,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1522
1548
|
statusPayload.steps[flatIndex].durationMs = stepEndTime - stepStartTime;
|
|
1523
1549
|
statusPayload.steps[flatIndex].exitCode = singleResult.exitCode;
|
|
1524
1550
|
statusPayload.steps[flatIndex].model = singleResult.model;
|
|
1551
|
+
statusPayload.steps[flatIndex].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[flatIndex].thinking);
|
|
1525
1552
|
statusPayload.steps[flatIndex].attemptedModels = singleResult.attemptedModels;
|
|
1526
1553
|
statusPayload.steps[flatIndex].modelAttempts = singleResult.modelAttempts;
|
|
1527
1554
|
statusPayload.steps[flatIndex].error = singleResult.error;
|
|
@@ -151,6 +151,7 @@ async function runSingleAttempt(
|
|
|
151
151
|
extensions: agent.extensions,
|
|
152
152
|
systemPrompt: shared.systemPrompt,
|
|
153
153
|
mcpDirectTools: agent.mcpDirectTools,
|
|
154
|
+
cwd: options.cwd ?? runtimeCwd,
|
|
154
155
|
promptFileStem: agent.name,
|
|
155
156
|
intercomSessionName: options.intercomSessionName,
|
|
156
157
|
orchestratorIntercomTarget: options.orchestratorIntercomTarget,
|
|
@@ -207,6 +208,7 @@ async function runSingleAttempt(
|
|
|
207
208
|
cwd: options.cwd ?? runtimeCwd,
|
|
208
209
|
env: spawnEnv,
|
|
209
210
|
stdio: ["ignore", "pipe", "pipe"],
|
|
211
|
+
windowsHide: true,
|
|
210
212
|
});
|
|
211
213
|
const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
|
|
212
214
|
let buf = "";
|
|
@@ -214,6 +216,7 @@ async function runSingleAttempt(
|
|
|
214
216
|
let settled = false;
|
|
215
217
|
let detached = false;
|
|
216
218
|
let intercomStarted = false;
|
|
219
|
+
let assistantError: string | undefined;
|
|
217
220
|
let removeAbortListener: (() => void) | undefined;
|
|
218
221
|
let removeInterruptListener: (() => void) | undefined;
|
|
219
222
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
@@ -259,7 +262,7 @@ async function runSingleAttempt(
|
|
|
259
262
|
const termSent = trySignalChild(proc, "SIGTERM");
|
|
260
263
|
if (!termSent) return;
|
|
261
264
|
forcedTerminationSignal = true;
|
|
262
|
-
if (!cleanTerminalAssistantStopReceived) {
|
|
265
|
+
if (!cleanTerminalAssistantStopReceived && !assistantError) {
|
|
263
266
|
result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its final message. Forcing termination.`;
|
|
264
267
|
}
|
|
265
268
|
finalHardKillTimer = setTimeout(() => {
|
|
@@ -465,13 +468,15 @@ async function runSingleAttempt(
|
|
|
465
468
|
progress.tokens = result.usage.input + result.usage.output;
|
|
466
469
|
}
|
|
467
470
|
if (!result.model && evt.message.model) result.model = evt.message.model;
|
|
468
|
-
if (evt.message.errorMessage)
|
|
469
|
-
|
|
471
|
+
if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
|
|
472
|
+
const assistantText = extractTextFromContent(evt.message.content);
|
|
473
|
+
appendRecentOutput(progress, assistantText.split("\n").slice(-10));
|
|
470
474
|
// Final assistant message: start the exit drain window.
|
|
471
475
|
const stopReason = (evt.message as { stopReason?: string }).stopReason;
|
|
472
476
|
const hasToolCall = Array.isArray(evt.message.content)
|
|
473
477
|
&& evt.message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
474
478
|
if (stopReason === "stop" && !hasToolCall) {
|
|
479
|
+
if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
|
|
475
480
|
cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
|
|
476
481
|
startFinalDrain();
|
|
477
482
|
}
|
|
@@ -551,6 +556,7 @@ async function runSingleAttempt(
|
|
|
551
556
|
}
|
|
552
557
|
processClosed = true;
|
|
553
558
|
if (buf.trim()) processLine(buf);
|
|
559
|
+
if (!result.error && assistantError) result.error = assistantError;
|
|
554
560
|
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !result.error;
|
|
555
561
|
if (code !== 0 && stderrBuf.trim() && !result.error && !forcedDrainAfterFinalSuccess) {
|
|
556
562
|
result.error = stderrBuf.trim();
|
|
@@ -662,8 +668,14 @@ async function runSingleAttempt(
|
|
|
662
668
|
};
|
|
663
669
|
|
|
664
670
|
let fullOutput = getFinalOutput(result.messages);
|
|
665
|
-
const completionGuard = result.exitCode === 0 && !result.error
|
|
666
|
-
? evaluateCompletionMutationGuard({
|
|
671
|
+
const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
|
|
672
|
+
? evaluateCompletionMutationGuard({
|
|
673
|
+
agent: agent.name,
|
|
674
|
+
task,
|
|
675
|
+
messages: result.messages,
|
|
676
|
+
tools: agent.tools,
|
|
677
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
678
|
+
})
|
|
667
679
|
: undefined;
|
|
668
680
|
if (completionGuard?.triggered && !observedMutationAttempt) {
|
|
669
681
|
result.exitCode = 1;
|
|
@@ -35,7 +35,7 @@ import { createForkContextResolver } from "../../shared/fork-context.ts";
|
|
|
35
35
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
36
36
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
37
37
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
38
|
-
import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
38
|
+
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
39
39
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "../../shared/utils.ts";
|
|
40
40
|
import {
|
|
41
41
|
buildSubagentResultIntercomPayload,
|
|
@@ -979,7 +979,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
979
979
|
};
|
|
980
980
|
}
|
|
981
981
|
const rawOutput = params.output !== undefined ? params.output : a.output;
|
|
982
|
-
const effectiveOutput
|
|
982
|
+
const effectiveOutput = normalizeSingleOutputOverride(rawOutput, a.output);
|
|
983
983
|
const effectiveOutputMode = params.outputMode ?? "inline";
|
|
984
984
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
985
985
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
@@ -1716,7 +1716,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1716
1716
|
);
|
|
1717
1717
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
1718
1718
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
1719
|
-
let effectiveOutput
|
|
1719
|
+
let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
|
|
1720
1720
|
const effectiveOutputMode = params.outputMode ?? "inline";
|
|
1721
1721
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1722
1722
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
@@ -1750,7 +1750,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1750
1750
|
task = result.templates[0]!;
|
|
1751
1751
|
const override = result.behaviorOverrides[0];
|
|
1752
1752
|
if (override?.model) modelOverride = override.model;
|
|
1753
|
-
if (override?.output !== undefined) effectiveOutput = override.output;
|
|
1753
|
+
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
1754
1754
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
1755
1755
|
|
|
1756
1756
|
if (result.runInBackground) {
|
|
@@ -2127,9 +2127,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2127
2127
|
return toExecutionErrorResult(effectiveParams, error);
|
|
2128
2128
|
}
|
|
2129
2129
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
2130
|
-
const backgroundRequestedWhileClarifying = hasTasks && requestedAsync && effectiveParams.clarify === true;
|
|
2131
|
-
const effectiveAsync = requestedAsync
|
|
2132
|
-
&& (hasChain ? effectiveParams.clarify === false : effectiveParams.clarify !== true);
|
|
2130
|
+
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
2131
|
+
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
2133
2132
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
2134
2133
|
|
|
2135
2134
|
const artifactConfig: ArtifactConfig = {
|
|
@@ -54,11 +54,24 @@ const GENERAL_IMPLEMENTATION_PATTERNS = [
|
|
|
54
54
|
/\b(?:update|add|remove|replace|delete|create)\s+(?:the\s+)?(?:file|files|code|source|implementation|test|tests|component|function|module|class|method|logic|import|imports|readme|docs?|changelog|package\.json|config|manifest|extension|prompt|command)\b/i,
|
|
55
55
|
];
|
|
56
56
|
|
|
57
|
+
const READ_ONLY_BUILTIN_TOOLS = new Set([
|
|
58
|
+
"read",
|
|
59
|
+
"grep",
|
|
60
|
+
"find",
|
|
61
|
+
"ls",
|
|
62
|
+
"web_search",
|
|
63
|
+
"fetch_content",
|
|
64
|
+
"get_search_content",
|
|
65
|
+
"intercom",
|
|
66
|
+
"contact_supervisor",
|
|
67
|
+
]);
|
|
57
68
|
|
|
58
69
|
interface CompletionMutationGuardInput {
|
|
59
70
|
agent: string;
|
|
60
71
|
task: string;
|
|
61
72
|
messages: Message[];
|
|
73
|
+
tools?: string[];
|
|
74
|
+
mcpDirectTools?: string[];
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
interface CompletionMutationGuardResult {
|
|
@@ -83,6 +96,13 @@ function stripScopedNoEditConstraints(task: string): string {
|
|
|
83
96
|
return stripped;
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
function declaresOnlyReadOnlyTools(tools: string[] | undefined, mcpDirectTools: string[] | undefined): boolean {
|
|
100
|
+
return tools !== undefined
|
|
101
|
+
&& tools.length > 0
|
|
102
|
+
&& (mcpDirectTools?.length ?? 0) === 0
|
|
103
|
+
&& tools.every((tool) => READ_ONLY_BUILTIN_TOOLS.has(tool));
|
|
104
|
+
}
|
|
105
|
+
|
|
86
106
|
export function expectsImplementationMutation(agent: string, task: string): boolean {
|
|
87
107
|
const taskText = stripFrameworkInstructions(task);
|
|
88
108
|
const taskTextWithoutScopedConstraints = stripScopedNoEditConstraints(taskText);
|
|
@@ -115,7 +135,9 @@ export function hasMutationToolCall(messages: Message[]): boolean {
|
|
|
115
135
|
}
|
|
116
136
|
|
|
117
137
|
export function evaluateCompletionMutationGuard(input: CompletionMutationGuardInput): CompletionMutationGuardResult {
|
|
118
|
-
const expectedMutation =
|
|
138
|
+
const expectedMutation = declaresOnlyReadOnlyTools(input.tools, input.mcpDirectTools)
|
|
139
|
+
? false
|
|
140
|
+
: expectsImplementationMutation(input.agent, input.task);
|
|
119
141
|
const attemptedMutation = hasMutationToolCall(input.messages);
|
|
120
142
|
return {
|
|
121
143
|
expectedMutation,
|