@pentoshi/clai 3.8.20 → 3.8.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/agent/confirm-port.d.ts +5 -2
- package/dist/agent/confirm-port.js +11 -5
- package/dist/agent/confirm-port.js.map +1 -1
- package/dist/agent/context-breakdown.d.ts +37 -0
- package/dist/agent/context-breakdown.js +145 -0
- package/dist/agent/context-breakdown.js.map +1 -0
- package/dist/agent/plan-decision.js +7 -3
- package/dist/agent/plan-decision.js.map +1 -1
- package/dist/agent/plan-tool.js +63 -20
- package/dist/agent/plan-tool.js.map +1 -1
- package/dist/agent/progress-pause-policy.d.ts +21 -0
- package/dist/agent/progress-pause-policy.js +28 -0
- package/dist/agent/progress-pause-policy.js.map +1 -0
- package/dist/agent/reliability-policy.d.ts +85 -0
- package/dist/agent/reliability-policy.js +158 -0
- package/dist/agent/reliability-policy.js.map +1 -0
- package/dist/agent/runner.js +352 -164
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/session-state.d.ts +11 -1
- package/dist/agent/session-state.js +22 -15
- package/dist/agent/session-state.js.map +1 -1
- package/dist/agent/stop-summary.js +3 -1
- package/dist/agent/stop-summary.js.map +1 -1
- package/dist/agent/task-evidence.js +13 -0
- package/dist/agent/task-evidence.js.map +1 -1
- package/dist/agent/tool-history.d.ts +12 -0
- package/dist/agent/tool-history.js +159 -0
- package/dist/agent/tool-history.js.map +1 -1
- package/dist/agent/tool-output-formatting.d.ts +4 -0
- package/dist/agent/tool-output-formatting.js +29 -4
- package/dist/agent/tool-output-formatting.js.map +1 -1
- package/dist/app/controllers/session-controller.d.ts +6 -0
- package/dist/app/controllers/session-controller.js +21 -1
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/ports/confirm-port.d.ts +1 -1
- package/dist/commands/providers.js +57 -26
- package/dist/commands/providers.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/llm/gemini.js +4 -1
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/key-rotation.d.ts +52 -0
- package/dist/llm/key-rotation.js +122 -0
- package/dist/llm/key-rotation.js.map +1 -0
- package/dist/llm/provider.d.ts +2 -0
- package/dist/llm/provider.js +8 -0
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/router.d.ts +15 -2
- package/dist/llm/router.js +318 -125
- package/dist/llm/router.js.map +1 -1
- package/dist/prompts/embedded.js +1 -1
- package/dist/prompts/embedded.js.map +1 -1
- package/dist/prompts/index.d.ts +25 -0
- package/dist/prompts/index.js +61 -7
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/system.agent.md +3 -1
- package/dist/repl/slash-commands.js +14 -8
- package/dist/repl/slash-commands.js.map +1 -1
- package/dist/repl.d.ts +4 -1
- package/dist/repl.js +12 -2
- package/dist/repl.js.map +1 -1
- package/dist/store/config.d.ts +18 -0
- package/dist/store/config.js +9 -0
- package/dist/store/config.js.map +1 -1
- package/dist/store/history.d.ts +8 -0
- package/dist/store/history.js +38 -0
- package/dist/store/history.js.map +1 -1
- package/dist/store/keys.d.ts +55 -0
- package/dist/store/keys.js +226 -21
- package/dist/store/keys.js.map +1 -1
- package/dist/store/logs.js +35 -12
- package/dist/store/logs.js.map +1 -1
- package/dist/store/paths.d.ts +13 -0
- package/dist/store/paths.js +23 -1
- package/dist/store/paths.js.map +1 -1
- package/dist/store/session-workspace.d.ts +79 -0
- package/dist/store/session-workspace.js +199 -0
- package/dist/store/session-workspace.js.map +1 -0
- package/dist/tools/definitions.js +9 -1
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/http.d.ts +8 -0
- package/dist/tools/http.js +86 -2
- package/dist/tools/http.js.map +1 -1
- package/dist/tools/pentest-workflows.js +38 -19
- package/dist/tools/pentest-workflows.js.map +1 -1
- package/dist/tools/registry.js +25 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/shell.d.ts +1 -1
- package/dist/tools/web/search.js +22 -3
- package/dist/tools/web/search.js.map +1 -1
- package/dist/tui/format-keys.js +29 -6
- package/dist/tui/format-keys.js.map +1 -1
- package/dist/tui-v2/app/command-handlers.js +1 -2
- package/dist/tui-v2/app/command-handlers.js.map +1 -1
- package/dist/tui-v2/app/commands/config-commands.d.ts +1 -2
- package/dist/tui-v2/app/commands/config-commands.js +1 -11
- package/dist/tui-v2/app/commands/config-commands.js.map +1 -1
- package/dist/tui-v2/app/commands/key-commands.d.ts +1 -1
- package/dist/tui-v2/app/commands/key-commands.js +148 -63
- package/dist/tui-v2/app/commands/key-commands.js.map +1 -1
- package/dist/tui-v2/app/commands/picker-commands.js +6 -0
- package/dist/tui-v2/app/commands/picker-commands.js.map +1 -1
- package/dist/tui-v2/bootstrap/composition-root.js +8 -5
- package/dist/tui-v2/bootstrap/composition-root.js.map +1 -1
- package/dist/tui-v2/bootstrap/overlay-ports.js +7 -2
- package/dist/tui-v2/bootstrap/overlay-ports.js.map +1 -1
- package/dist/tui-v2/components/modal/keys-modal.d.ts +19 -0
- package/dist/tui-v2/components/modal/keys-modal.js +203 -0
- package/dist/tui-v2/components/modal/keys-modal.js.map +1 -0
- package/dist/tui-v2/components/overlay/overlay-host.js +6 -2
- package/dist/tui-v2/components/overlay/overlay-host.js.map +1 -1
- package/dist/tui-v2/components/pager/pager-line.js +5 -2
- package/dist/tui-v2/components/pager/pager-line.js.map +1 -1
- package/dist/tui-v2/components/pager/pager.js +6 -3
- package/dist/tui-v2/components/pager/pager.js.map +1 -1
- package/dist/tui-v2/components/plan/use-pane-presence.d.ts +6 -4
- package/dist/tui-v2/components/plan/use-pane-presence.js +8 -45
- package/dist/tui-v2/components/plan/use-pane-presence.js.map +1 -1
- package/dist/tui-v2/components/transcript/assistant-message.js +4 -1
- package/dist/tui-v2/components/transcript/assistant-message.js.map +1 -1
- package/dist/tui-v2/components/transcript/compacted-row.js +1 -1
- package/dist/tui-v2/components/transcript/compacted-row.js.map +1 -1
- package/dist/tui-v2/components/transcript/file-diff-card.js +3 -23
- package/dist/tui-v2/components/transcript/file-diff-card.js.map +1 -1
- package/dist/tui-v2/components/transcript/linkable-text.js +9 -2
- package/dist/tui-v2/components/transcript/linkable-text.js.map +1 -1
- package/dist/tui-v2/components/transcript/selectable-line.d.ts +22 -0
- package/dist/tui-v2/components/transcript/selectable-line.js +24 -0
- package/dist/tui-v2/components/transcript/selectable-line.js.map +1 -0
- package/dist/tui-v2/components/transcript/thinking-block.js +3 -1
- package/dist/tui-v2/components/transcript/thinking-block.js.map +1 -1
- package/dist/tui-v2/components/transcript/tool-card.d.ts +5 -3
- package/dist/tui-v2/components/transcript/tool-card.js +39 -33
- package/dist/tui-v2/components/transcript/tool-card.js.map +1 -1
- package/dist/tui-v2/components/transcript/use-click-without-drag.js +4 -1
- package/dist/tui-v2/components/transcript/use-click-without-drag.js.map +1 -1
- package/dist/tui-v2/components/transcript/user-message.js +1 -1
- package/dist/tui-v2/components/transcript/user-message.js.map +1 -1
- package/dist/tui-v2/controllers/overlay-controller.d.ts +36 -0
- package/dist/tui-v2/controllers/overlay-controller.js +28 -0
- package/dist/tui-v2/controllers/overlay-controller.js.map +1 -1
- package/dist/tui-v2/rendering/batch-sections.d.ts +15 -4
- package/dist/tui-v2/rendering/batch-sections.js +157 -64
- package/dist/tui-v2/rendering/batch-sections.js.map +1 -1
- package/dist/tui-v2/rendering/format-help.js +1 -1
- package/dist/tui-v2/rendering/format-help.js.map +1 -1
- package/dist/tui-v2/rendering/open-tool-output.js +10 -7
- package/dist/tui-v2/rendering/open-tool-output.js.map +1 -1
- package/dist/tui-v2/rendering/pager-markdown.d.ts +9 -0
- package/dist/tui-v2/rendering/pager-markdown.js +46 -0
- package/dist/tui-v2/rendering/pager-markdown.js.map +1 -1
- package/dist/tui-v2/rendering/pager-view-policy.d.ts +6 -3
- package/dist/tui-v2/rendering/pager-view-policy.js +9 -13
- package/dist/tui-v2/rendering/pager-view-policy.js.map +1 -1
- package/dist/tui-v2/rendering/sanitize-display.js +3 -1
- package/dist/tui-v2/rendering/sanitize-display.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/package.json +1 -1
package/dist/agent/runner.js
CHANGED
|
@@ -8,13 +8,16 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
import { jobManager } from "../tools/jobs.js";
|
|
9
9
|
import { agentModeDirective, planModeDirective, renderAgentSystemPrompt, renderCompactAgentSystemPrompt, scratchDirFor, toolNudge, } from "../prompts/index.js";
|
|
10
10
|
import { getConfig } from "../store/config.js";
|
|
11
|
+
import { beginSessionWorkspace, getActiveSessionWorkspace, } from "../store/session-workspace.js";
|
|
11
12
|
import { groqInputTokenBudget } from "../llm/groq.js";
|
|
12
13
|
import { classifyToolCall, isPentestToolCall, scopeHint, scopeTargetForToolCall, } from "../safety/classifier.js";
|
|
13
14
|
import { availableToolNames, normalizeToolCall, runToolCall, BATCH_SAFE_TOOLS, } from "../tools/registry.js";
|
|
14
15
|
import { getToolDefinitions, getCompactToolDefinitions, PLAN_TOOL_NAMES, } from "../tools/definitions.js";
|
|
15
|
-
import { appendAssistantWithTools, appendToolResult, assertValidToolProtocol, fillMissingToolResults, } from "./tool-history.js";
|
|
16
|
+
import { appendAssistantWithTools, appendToolResult, assertValidToolProtocol, fillMissingToolResults, repairToolProtocol, } from "./tool-history.js";
|
|
16
17
|
import { formatViewportHint, registerViewport } from "../ui/output-pane.js";
|
|
17
|
-
import { compactMessagesWithSummary, estimateMessagesTokens,
|
|
18
|
+
import { compactMessagesWithSummary, estimateMessagesTokens, shouldApplyAutoCompact, COMPACTION_MEMORY_PREFIX, PLAN_IMPLEMENT_MEMORY_PREFIX, isCompactionMemoryMessage, } from "./context-manager.js";
|
|
19
|
+
import { buildContextBreakdown, contextBreakdownAuditPayload, } from "./context-breakdown.js";
|
|
20
|
+
import { autoCompactTriggerTokens, dedupeToolContextOutput, freeTierGuardNotices, getReliabilityPolicy, resolveStepMaxTokens, } from "./reliability-policy.js";
|
|
18
21
|
import { auditLog } from "../store/logs.js";
|
|
19
22
|
import { loadProjectContext } from "../store/project.js";
|
|
20
23
|
import { loadScope, isScopeActive } from "../store/scope.js";
|
|
@@ -34,6 +37,7 @@ import { fsWrite, isOutsideWorkingDirectory, resolveFsToolPath, } from "../tools
|
|
|
34
37
|
import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, salvageTruncatedWrite, salvageTruncatedWriteFromNative, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeIdleOrSocialPrompt, looksLikeActionNarration, looksLikeWebActionNarration, looksLikePlanNarration, looksLikeErrorDiagnosisWithFixIntent, localHttpProbeIsFailure, localHttpProbeIsSuccess, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, narrowNmapOperationDirective, pentestWorkflowDirective, pentestNoLocalServerDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
|
|
35
38
|
import { createSessionPolicy, isPreApprovalAllowedTool, isPlanModeAllowedShellCommand, isPlanModeAllowedTool, isPlanApprovedByStatus, planHasOpenWork, isAbortError, shouldEnableImageOcr, } from "./session-policy.js";
|
|
36
39
|
import { saveToolOutput, summarizeOutput, formatToolContext, } from "./tool-output-formatting.js";
|
|
40
|
+
import { codingSessionFromContext, isProtocolPlaceholderOutput, progressPauseMode, } from "./progress-pause-policy.js";
|
|
37
41
|
import { renderPlanForTerminal, planContextMessage, handlePlanTool, resolvePlanTaskId, } from "./plan-tool.js";
|
|
38
42
|
import { absorbLooseWorkIntoLedger, applyDestinationCwd, canMarkTaskDone, hasLocalRuntimeProof, hasRemoteWorkProof, isDevServerCall, isEvidenceWorkTool, isFeatureImplementationCall, isPackageInstallCommand, isPlanPreflightTool, isPortListeningOutput, isReadOnlyReconTool, isRemoteActiveTestCall, isRemoteObservationTask, isRemoteReconToolCall, isRuntimeObservationTask, isScaffoldCreateCommand, isServerReadyOutput, ledgerFromTaskEvidence, pickPendingTaskForToolCall, recordTaskWorkSuccess, resolveUserDestinationHint, taskEvidenceFromLedger, TOOL_ABORT_GRACE_MS, toolHardBudgetMs, toolStallBudgetMs, userAskedForFeatureApp, } from "./task-evidence.js";
|
|
39
43
|
import { buildSessionStateBlock, inferNextHint, upsertSessionStateMessage, } from "./session-state.js";
|
|
@@ -85,9 +89,17 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
85
89
|
if (/\/output\b|open full output|Ctrl\+O or|\.clai\/outputs/i.test(cleaned)) {
|
|
86
90
|
return;
|
|
87
91
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
// API key rotation / retry lines need more room than tool-name chips.
|
|
93
|
+
const keyLine = /^(using |switching |⏳ |all .+ API keys)/i.test(cleaned);
|
|
94
|
+
const maxLen = keyLine ? 96 : 64;
|
|
95
|
+
if (cleaned.length > maxLen) {
|
|
96
|
+
if (keyLine) {
|
|
97
|
+
cleaned = cleaned.slice(0, maxLen - 1) + "…";
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const short = cleaned.match(/^[\w./-]+/);
|
|
101
|
+
cleaned = short ? short[0] : cleaned.slice(0, maxLen - 3) + "…";
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
emit({ type: "status", text: cleaned || "working" });
|
|
93
105
|
if (writesDirectly)
|
|
@@ -278,6 +290,11 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
278
290
|
};
|
|
279
291
|
let lastAnswer = "";
|
|
280
292
|
const session = options.session ?? createSessionPolicy();
|
|
293
|
+
// One-shot CLI / tests that never entered TUI/REPL still need an isolated
|
|
294
|
+
// scratch+output workspace. No-op when a session already bound one.
|
|
295
|
+
if (!getActiveSessionWorkspace()) {
|
|
296
|
+
beginSessionWorkspace();
|
|
297
|
+
}
|
|
281
298
|
// Active plan context
|
|
282
299
|
// If this session already has a plan, inject it so the model keeps it in
|
|
283
300
|
// context. When the user has approved it (via /implement) we instruct the
|
|
@@ -323,11 +340,16 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
323
340
|
setActiveProjectRootIfValid(discoveredProjects[0]);
|
|
324
341
|
}
|
|
325
342
|
const buildSystemContent = (native) => {
|
|
343
|
+
const reliability = getReliabilityPolicy();
|
|
326
344
|
const sections = [
|
|
327
345
|
(useCompactSystemPrompt
|
|
328
346
|
? renderCompactAgentSystemPrompt
|
|
329
347
|
: renderAgentSystemPrompt)(toolNames.join(", "), {
|
|
330
348
|
nativeTools: native,
|
|
349
|
+
// E6: slim native constitution when API tool schemas are attached.
|
|
350
|
+
...(native
|
|
351
|
+
? { slimNative: reliability.slimNativePrompt }
|
|
352
|
+
: {}),
|
|
331
353
|
}),
|
|
332
354
|
];
|
|
333
355
|
if (projectContext) {
|
|
@@ -772,6 +794,15 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
772
794
|
const hasHistory = (options.history?.length ?? 0) > 0;
|
|
773
795
|
const buildLike = buildLikeTurn;
|
|
774
796
|
const pentestLike = looksLikePentestTask(prompt, options.history);
|
|
797
|
+
/** Coding/build sessions never hard-pause mid-turn for the progress governor. */
|
|
798
|
+
let codingSession = codingSessionFromContext({
|
|
799
|
+
buildLike,
|
|
800
|
+
planKind: activePlan?.kind,
|
|
801
|
+
});
|
|
802
|
+
let pauseMode = progressPauseMode({
|
|
803
|
+
codingSession,
|
|
804
|
+
autoConfirm: Boolean(options.autoConfirm),
|
|
805
|
+
});
|
|
775
806
|
const continueExistingOutcome = /^(?:continue|resume|proceed|keep\s+going|finish|next)\b/i.test(prompt.trim()) ||
|
|
776
807
|
Boolean(activePlan && !isPlanTerminal(activePlan));
|
|
777
808
|
const outcomeState = await openOutcomeState({
|
|
@@ -1746,27 +1777,52 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1746
1777
|
((call.name === "shell.exec" || call.name === "shell.start") &&
|
|
1747
1778
|
isPackageInstallCommand(String(call.args.command ?? "")));
|
|
1748
1779
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1780
|
+
// Protocol-repair placeholders are not live work — never let them
|
|
1781
|
+
// accumulate into a mid-turn pause (they used to look like failed tools).
|
|
1782
|
+
if (!isProtocolPlaceholderOutput(result.output)) {
|
|
1783
|
+
const governed = governProgress(governorState, "activity", {
|
|
1784
|
+
evidenceDelta: newEvidence.length,
|
|
1785
|
+
hypothesisDelta,
|
|
1786
|
+
repetitionScore: loopGuard.getAttemptCount(call.name, call.args) > 1 ? 1 : 0,
|
|
1787
|
+
policy: {
|
|
1788
|
+
resourceEnvelope: Math.max(12, maxSteps),
|
|
1789
|
+
// Coding builds get a much higher ceiling; never use the tight
|
|
1790
|
+
// default that stopped multi-file scaffolds after a handful of steps.
|
|
1791
|
+
emergencyCeiling: codingSession
|
|
1792
|
+
? Math.max(200, maxSteps * 5)
|
|
1793
|
+
: Math.max(70, maxSteps * 3),
|
|
1794
|
+
reflectionAfterNoDelta: codingSession ? 5 : 3,
|
|
1795
|
+
pauseAfterNoDelta: codingSession ? 24 : 6,
|
|
1796
|
+
repetitionThreshold: 0.8,
|
|
1797
|
+
},
|
|
1766
1798
|
});
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1799
|
+
governorState = governed.state;
|
|
1800
|
+
if (governed.recommendation === "reflect") {
|
|
1801
|
+
deferredPostToolMessages.push({
|
|
1802
|
+
role: "system",
|
|
1803
|
+
content: `PROGRESS GOVERNOR: ${governed.reason}. Reassess the current premise and choose the next action that can produce criterion-linked evidence.` +
|
|
1804
|
+
(codingSession
|
|
1805
|
+
? " Keep working — coding builds do not stop for a continue prompt."
|
|
1806
|
+
: ""),
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
else if (governed.recommendation === "paused_budget") {
|
|
1810
|
+
if (pauseMode === "never") {
|
|
1811
|
+
// Soft reset so we do not re-trip every subsequent tool.
|
|
1812
|
+
governorState = {
|
|
1813
|
+
...governed.state,
|
|
1814
|
+
consecutiveNoDelta: 0,
|
|
1815
|
+
};
|
|
1816
|
+
deferredPostToolMessages.push({
|
|
1817
|
+
role: "system",
|
|
1818
|
+
content: `PROGRESS GOVERNOR (soft, coding build): ${governed.reason}. ` +
|
|
1819
|
+
"Change approach if stuck, but keep implementing — do not stop for user confirmation.",
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
else {
|
|
1823
|
+
governorPauseReason = governed.reason;
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1770
1826
|
}
|
|
1771
1827
|
await saveOutcomeState(outcomeState);
|
|
1772
1828
|
loopGuard.recordAttempt(step, call.name, call.args, result.ok, result.exitCode);
|
|
@@ -1911,6 +1967,11 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1911
1967
|
// Align with /compact default: small recency + dense memory (not keepRecent=6 fat tails).
|
|
1912
1968
|
const AUTO_COMPACT_KEEP_RECENT = 2;
|
|
1913
1969
|
let lastCompactionMsgCount = 0;
|
|
1970
|
+
/** E5: identical tool bodies within this turn → pointer instead of re-append. */
|
|
1971
|
+
const toolResultHashes = new Map();
|
|
1972
|
+
/** E4: consecutive free-tier stream failures this turn. */
|
|
1973
|
+
let freeTierConsecutiveFailures = 0;
|
|
1974
|
+
let freeTierLargeContextWarned = false;
|
|
1914
1975
|
const summarizeForCompaction = async (summaryPrompt) => {
|
|
1915
1976
|
const response = await completeWithProvider({
|
|
1916
1977
|
provider,
|
|
@@ -1928,7 +1989,9 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1928
1989
|
};
|
|
1929
1990
|
async function maybeAutoCompact(reason, force = false) {
|
|
1930
1991
|
const beforeTokens = estimateMessagesTokens(messages);
|
|
1931
|
-
|
|
1992
|
+
// E1: soft early compact (default 70k) while hard ceiling remains 100k.
|
|
1993
|
+
const compactTrigger = autoCompactTriggerTokens();
|
|
1994
|
+
if (!force && beforeTokens < compactTrigger)
|
|
1932
1995
|
return;
|
|
1933
1996
|
if (messages.length <= AUTO_COMPACT_KEEP_RECENT + 2)
|
|
1934
1997
|
return;
|
|
@@ -2005,14 +2068,53 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2005
2068
|
// advances when the previous iteration actually executed a tool.
|
|
2006
2069
|
step = productiveSteps;
|
|
2007
2070
|
if (governorPauseReason) {
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2071
|
+
// Non-coding: always ask continue/stop. Coding never sets this reason
|
|
2072
|
+
// (pauseMode === "never"), but guard anyway.
|
|
2073
|
+
if (pauseMode === "never") {
|
|
2074
|
+
governorPauseReason = undefined;
|
|
2075
|
+
governorState = {
|
|
2076
|
+
...governorState,
|
|
2077
|
+
consecutiveNoDelta: 0,
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
else {
|
|
2081
|
+
const confirmPort = options.confirm;
|
|
2082
|
+
let keepGoing = false;
|
|
2083
|
+
if (confirmPort?.confirmContinue) {
|
|
2084
|
+
try {
|
|
2085
|
+
keepGoing = await confirmPort.confirmContinue(productiveSteps, governorPauseReason);
|
|
2086
|
+
}
|
|
2087
|
+
catch {
|
|
2088
|
+
keepGoing = false;
|
|
2089
|
+
}
|
|
2090
|
+
finally {
|
|
2091
|
+
restoreInteractiveStdin();
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
if (keepGoing) {
|
|
2095
|
+
writeNotice("info", "continuing after progress pause", chalk.dim(` ℹ continuing after pause (${governorPauseReason}) — change approach if stuck\n`));
|
|
2096
|
+
deferredPostToolMessages.push({
|
|
2097
|
+
role: "system",
|
|
2098
|
+
content: `User chose CONTINUE after progress pause (${governorPauseReason}). ` +
|
|
2099
|
+
"Do not repeat the same failing step; change approach and produce new evidence.",
|
|
2100
|
+
});
|
|
2101
|
+
governorPauseReason = undefined;
|
|
2102
|
+
governorState = {
|
|
2103
|
+
...governorState,
|
|
2104
|
+
consecutiveNoDelta: 0,
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
else {
|
|
2108
|
+
const richSummary = await buildRichStopSummary(messages, session, productiveSteps);
|
|
2109
|
+
outcomeState.outcome.status = "paused_budget";
|
|
2110
|
+
await saveOutcomeState(outcomeState);
|
|
2111
|
+
moveTurn("paused_budget", governorPauseReason);
|
|
2112
|
+
lastAnswer = richSummary;
|
|
2113
|
+
return finishTurn(lastAnswer, productiveSteps, "paused_budget", outcomeState.outcome.criteria
|
|
2114
|
+
.filter((criterion) => criterion.required && criterion.status !== "proven")
|
|
2115
|
+
.map((criterion) => criterion.statement), governorPauseReason);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2016
2118
|
}
|
|
2017
2119
|
options.signal?.throwIfAborted();
|
|
2018
2120
|
let call;
|
|
@@ -2057,159 +2159,222 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2057
2159
|
({ dialect: toolDialect, native: nativeToolsActive } =
|
|
2058
2160
|
resolveNativeTools(provider, model));
|
|
2059
2161
|
if (messages[0]?.role === "system") {
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2162
|
+
// Recompose only when content actually changes (hour-stable env clock
|
|
2163
|
+
// keeps the constitution prefix identical across steps, which helps
|
|
2164
|
+
// provider prompt caching and avoids needless object churn).
|
|
2165
|
+
const nextSystem = composeCurrentSystemPrompt(nativeToolsActive);
|
|
2166
|
+
if (messages[0].content !== nextSystem) {
|
|
2167
|
+
messages[0] = {
|
|
2168
|
+
role: "system",
|
|
2169
|
+
content: nextSystem,
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2064
2172
|
}
|
|
2065
2173
|
const turnTools = selectToolDefs(nativeToolsActive, useCompactSystemPrompt);
|
|
2066
2174
|
toolsAttached = Boolean(turnTools?.length);
|
|
2175
|
+
const contextBreakdown = buildContextBreakdown(messages, toolsAttached ? turnTools : undefined);
|
|
2176
|
+
// E4: advisory only — never blocks free-tier users.
|
|
2177
|
+
if (!freeTierLargeContextWarned) {
|
|
2178
|
+
const notices = freeTierGuardNotices({
|
|
2179
|
+
provider,
|
|
2180
|
+
estimatedInputTokens: contextBreakdown.estimatedTotalTokens,
|
|
2181
|
+
consecutiveFailures: freeTierConsecutiveFailures,
|
|
2182
|
+
});
|
|
2183
|
+
for (const notice of notices) {
|
|
2184
|
+
if (notice.includes("Large context")) {
|
|
2185
|
+
freeTierLargeContextWarned = true;
|
|
2186
|
+
}
|
|
2187
|
+
writeNotice("info", notice, chalk.dim(` ℹ ${notice}\n`));
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2067
2190
|
await auditLog("agent.turn", {
|
|
2068
2191
|
provider,
|
|
2069
2192
|
model,
|
|
2070
2193
|
tool_protocol: toolsAttached ? "native" : "text",
|
|
2071
2194
|
dialect: toolDialect,
|
|
2072
2195
|
step,
|
|
2196
|
+
// Metadata-only composition metrics (no prompt/tool text).
|
|
2197
|
+
...contextBreakdownAuditPayload(contextBreakdown),
|
|
2198
|
+
compactTriggerTokens: autoCompactTriggerTokens(),
|
|
2199
|
+
maxTokensBudget: resolveStepMaxTokens({
|
|
2200
|
+
nativeToolsActive,
|
|
2201
|
+
toolsAttached,
|
|
2202
|
+
recoveryNudge: retryWithoutThinking,
|
|
2203
|
+
}),
|
|
2073
2204
|
});
|
|
2205
|
+
// Resume / mid-turn abort can leave orphan tool rows or a user
|
|
2206
|
+
// "continue" before tool results. Heal first so multi-key retry and
|
|
2207
|
+
// history reloads don't hard-fail on protocol asserts.
|
|
2208
|
+
// Heal once per step if needed; silent (no toast) — placeholders are
|
|
2209
|
+
// ok=true so the model doesn't thrash on fake exit=130 failures.
|
|
2210
|
+
repairToolProtocol(messages);
|
|
2074
2211
|
assertValidToolProtocol(messages);
|
|
2075
|
-
completion
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
:
|
|
2086
|
-
|
|
2087
|
-
?
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2212
|
+
// E3: adaptive completion budget (still large enough for writes).
|
|
2213
|
+
const stepMaxTokens = resolveStepMaxTokens({
|
|
2214
|
+
nativeToolsActive,
|
|
2215
|
+
toolsAttached,
|
|
2216
|
+
recoveryNudge: retryWithoutThinking,
|
|
2217
|
+
});
|
|
2218
|
+
try {
|
|
2219
|
+
completion = await streamWithProvider({
|
|
2220
|
+
provider,
|
|
2221
|
+
model,
|
|
2222
|
+
allowModelFallback: false,
|
|
2223
|
+
messages,
|
|
2224
|
+
temperature: /minimax-m3/i.test(model) ? 1.0 : 0.2,
|
|
2225
|
+
maxTokens: stepMaxTokens,
|
|
2226
|
+
signal: options.signal,
|
|
2227
|
+
thinking: retryWithoutThinking
|
|
2228
|
+
? { ...config.thinking, enabled: false, effort: "low" }
|
|
2229
|
+
: config.thinking,
|
|
2230
|
+
...(toolsAttached
|
|
2231
|
+
? {
|
|
2232
|
+
tools: turnTools,
|
|
2233
|
+
toolChoice: freshWebSearchRequired && !sawFreshWebSearch
|
|
2234
|
+
? { type: "function", name: "web.search" }
|
|
2235
|
+
: "auto",
|
|
2236
|
+
parallelToolCalls: true,
|
|
2237
|
+
// P2-3: emit tool cards as soon as the function name arrives.
|
|
2238
|
+
onToolCallDelta: (delta) => {
|
|
2239
|
+
if (!delta.name)
|
|
2240
|
+
return;
|
|
2241
|
+
const name = fromWireName(delta.name) ?? delta.name;
|
|
2242
|
+
const existing = deferredToolCalls[delta.index];
|
|
2243
|
+
if (existing) {
|
|
2244
|
+
if (delta.argumentsBytes &&
|
|
2245
|
+
delta.argumentsBytes >= 4096 &&
|
|
2246
|
+
!writesDirectly) {
|
|
2247
|
+
emit({
|
|
2248
|
+
type: "status",
|
|
2249
|
+
text: `${name} (${Math.round(delta.argumentsBytes / 1024)}KB args)`,
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
// Ensure slots are dense so index maps to deferredToolCalls[i].
|
|
2255
|
+
while (deferredToolCalls.length < delta.index) {
|
|
2256
|
+
deferredToolCalls.push({
|
|
2257
|
+
eventId: `tool-${++nextToolEventId}`,
|
|
2258
|
+
call: { name: "…", args: {} },
|
|
2259
|
+
rendered: "",
|
|
2106
2260
|
});
|
|
2107
2261
|
}
|
|
2108
|
-
|
|
2262
|
+
const call = normalizeToolCall({
|
|
2263
|
+
name,
|
|
2264
|
+
args: {},
|
|
2265
|
+
});
|
|
2266
|
+
const eventId = `tool-${++nextToolEventId}`;
|
|
2267
|
+
callIds.push(eventId);
|
|
2268
|
+
alreadyPrintedIds.add(eventId);
|
|
2269
|
+
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
2270
|
+
chalk.gray(` ${formatToolArgs(call)}`);
|
|
2271
|
+
const entry = {
|
|
2272
|
+
eventId,
|
|
2273
|
+
call,
|
|
2274
|
+
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
2275
|
+
};
|
|
2276
|
+
if (deferredToolCalls.length === delta.index) {
|
|
2277
|
+
deferredToolCalls.push(entry);
|
|
2278
|
+
}
|
|
2279
|
+
else {
|
|
2280
|
+
deferredToolCalls[delta.index] = entry;
|
|
2281
|
+
}
|
|
2282
|
+
streamedCallsCount = Math.max(streamedCallsCount, deferredToolCalls.length);
|
|
2283
|
+
if (!writesDirectly) {
|
|
2284
|
+
emit({ type: "status", text: call.name });
|
|
2285
|
+
}
|
|
2286
|
+
else {
|
|
2287
|
+
spinner.stop();
|
|
2288
|
+
spinner = startThinkingSpinner(`tool ${call.name}…`, options.signal);
|
|
2289
|
+
}
|
|
2290
|
+
},
|
|
2291
|
+
}
|
|
2292
|
+
: {}),
|
|
2293
|
+
}, (token) => {
|
|
2294
|
+
deltaParser?.push(token);
|
|
2295
|
+
generatedTokens += 1;
|
|
2296
|
+
accumulatedText += token;
|
|
2297
|
+
// Early UI cards from text fences only when native tools are off
|
|
2298
|
+
// (native args stream as structured deltas, not prose).
|
|
2299
|
+
if (!toolsAttached) {
|
|
2300
|
+
const parsedCalls = parseAllToolCalls(accumulatedText);
|
|
2301
|
+
if (parsedCalls.length > streamedCallsCount) {
|
|
2302
|
+
if (writesDirectly) {
|
|
2303
|
+
spinner.stop();
|
|
2109
2304
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2305
|
+
while (streamedCallsCount < parsedCalls.length) {
|
|
2306
|
+
const call = parsedCalls[streamedCallsCount];
|
|
2307
|
+
const eventId = `tool-${++nextToolEventId}`;
|
|
2308
|
+
callIds.push(eventId);
|
|
2309
|
+
alreadyPrintedIds.add(eventId);
|
|
2310
|
+
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
2311
|
+
chalk.gray(` ${formatToolArgs(call)}`);
|
|
2112
2312
|
deferredToolCalls.push({
|
|
2113
|
-
eventId
|
|
2114
|
-
call
|
|
2115
|
-
rendered: "",
|
|
2313
|
+
eventId,
|
|
2314
|
+
call,
|
|
2315
|
+
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
2116
2316
|
});
|
|
2317
|
+
if (!writesDirectly) {
|
|
2318
|
+
emit({ type: "status", text: call.name });
|
|
2319
|
+
}
|
|
2320
|
+
streamedCallsCount += 1;
|
|
2117
2321
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
args: {},
|
|
2121
|
-
});
|
|
2122
|
-
const eventId = `tool-${++nextToolEventId}`;
|
|
2123
|
-
callIds.push(eventId);
|
|
2124
|
-
alreadyPrintedIds.add(eventId);
|
|
2125
|
-
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
2126
|
-
chalk.gray(` ${formatToolArgs(call)}`);
|
|
2127
|
-
const entry = {
|
|
2128
|
-
eventId,
|
|
2129
|
-
call,
|
|
2130
|
-
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
2131
|
-
};
|
|
2132
|
-
if (deferredToolCalls.length === delta.index) {
|
|
2133
|
-
deferredToolCalls.push(entry);
|
|
2134
|
-
}
|
|
2135
|
-
else {
|
|
2136
|
-
deferredToolCalls[delta.index] = entry;
|
|
2137
|
-
}
|
|
2138
|
-
streamedCallsCount = Math.max(streamedCallsCount, deferredToolCalls.length);
|
|
2139
|
-
if (!writesDirectly) {
|
|
2140
|
-
emit({ type: "status", text: call.name });
|
|
2322
|
+
if (writesDirectly) {
|
|
2323
|
+
spinner = startThinkingSpinner(`generating response (${generatedTokens} tokens)`, options.signal);
|
|
2141
2324
|
}
|
|
2142
|
-
else {
|
|
2143
|
-
spinner.stop();
|
|
2144
|
-
spinner = startThinkingSpinner(`tool ${call.name}…`, options.signal);
|
|
2145
|
-
}
|
|
2146
|
-
},
|
|
2147
|
-
}
|
|
2148
|
-
: {}),
|
|
2149
|
-
}, (token) => {
|
|
2150
|
-
deltaParser?.push(token);
|
|
2151
|
-
generatedTokens += 1;
|
|
2152
|
-
accumulatedText += token;
|
|
2153
|
-
// Early UI cards from text fences only when native tools are off
|
|
2154
|
-
// (native args stream as structured deltas, not prose).
|
|
2155
|
-
if (!toolsAttached) {
|
|
2156
|
-
const parsedCalls = parseAllToolCalls(accumulatedText);
|
|
2157
|
-
if (parsedCalls.length > streamedCallsCount) {
|
|
2158
|
-
if (writesDirectly) {
|
|
2159
|
-
spinner.stop();
|
|
2160
|
-
}
|
|
2161
|
-
while (streamedCallsCount < parsedCalls.length) {
|
|
2162
|
-
const call = parsedCalls[streamedCallsCount];
|
|
2163
|
-
const eventId = `tool-${++nextToolEventId}`;
|
|
2164
|
-
callIds.push(eventId);
|
|
2165
|
-
alreadyPrintedIds.add(eventId);
|
|
2166
|
-
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
2167
|
-
chalk.gray(` ${formatToolArgs(call)}`);
|
|
2168
|
-
deferredToolCalls.push({
|
|
2169
|
-
eventId,
|
|
2170
|
-
call,
|
|
2171
|
-
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
2172
|
-
});
|
|
2173
|
-
if (!writesDirectly) {
|
|
2174
|
-
emit({ type: "status", text: call.name });
|
|
2175
|
-
}
|
|
2176
|
-
streamedCallsCount += 1;
|
|
2177
2325
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2326
|
+
}
|
|
2327
|
+
if (!sawReasoning && /<think/i.test(token)) {
|
|
2328
|
+
sawReasoning = true;
|
|
2329
|
+
inThinking = true;
|
|
2330
|
+
spinner.setLabel("thinking");
|
|
2331
|
+
if (!writesDirectly)
|
|
2332
|
+
emit({ type: "status", text: "thinking" });
|
|
2333
|
+
}
|
|
2334
|
+
if (/<\/think>/i.test(token)) {
|
|
2335
|
+
inThinking = false;
|
|
2336
|
+
spinner.setLabel("generating response (0 tokens)");
|
|
2337
|
+
generatedTokens = 0;
|
|
2338
|
+
}
|
|
2339
|
+
if (inThinking) {
|
|
2340
|
+
const cleaned = token.replace(/<\/?think[^>]*>/gi, "");
|
|
2341
|
+
if (cleaned) {
|
|
2342
|
+
spinner.pushPreview(cleaned);
|
|
2343
|
+
const approx = cleaned.split(/\s+/).filter(Boolean).length;
|
|
2344
|
+
if (approx > 0)
|
|
2345
|
+
spinner.bumpReasoning(approx);
|
|
2180
2346
|
}
|
|
2181
2347
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
spinner.setLabel("thinking");
|
|
2187
|
-
if (!writesDirectly)
|
|
2188
|
-
emit({ type: "status", text: "thinking" });
|
|
2189
|
-
}
|
|
2190
|
-
if (/<\/think>/i.test(token)) {
|
|
2191
|
-
inThinking = false;
|
|
2192
|
-
spinner.setLabel("generating response (0 tokens)");
|
|
2193
|
-
generatedTokens = 0;
|
|
2194
|
-
}
|
|
2195
|
-
if (inThinking) {
|
|
2196
|
-
const cleaned = token.replace(/<\/?think[^>]*>/gi, "");
|
|
2197
|
-
if (cleaned) {
|
|
2198
|
-
spinner.pushPreview(cleaned);
|
|
2199
|
-
const approx = cleaned.split(/\s+/).filter(Boolean).length;
|
|
2200
|
-
if (approx > 0)
|
|
2201
|
-
spinner.bumpReasoning(approx);
|
|
2348
|
+
else {
|
|
2349
|
+
if (generatedTokens % 10 === 0) {
|
|
2350
|
+
spinner.setLabel(`generating response (${generatedTokens} tokens)`);
|
|
2351
|
+
}
|
|
2202
2352
|
}
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2353
|
+
}, (status) => {
|
|
2354
|
+
spinner.stop();
|
|
2355
|
+
writeStatus(status, chalk.dim(status));
|
|
2356
|
+
// Toast only on key *switch* after a failure — never on sticky
|
|
2357
|
+
// "using" or retry countdown ticks (those stay in composer status).
|
|
2358
|
+
if (/^switching /i.test(status.trim())) {
|
|
2359
|
+
writeNotice("warn", status.trim(), chalk.yellow(` ${status.trim()}\n`));
|
|
2207
2360
|
}
|
|
2361
|
+
});
|
|
2362
|
+
freeTierConsecutiveFailures = 0;
|
|
2363
|
+
}
|
|
2364
|
+
catch (streamError) {
|
|
2365
|
+
// E4: track free-tier failures for advisory notices (never blocks).
|
|
2366
|
+
freeTierConsecutiveFailures += 1;
|
|
2367
|
+
for (const notice of freeTierGuardNotices({
|
|
2368
|
+
provider,
|
|
2369
|
+
estimatedInputTokens: contextBreakdown.estimatedTotalTokens,
|
|
2370
|
+
consecutiveFailures: freeTierConsecutiveFailures,
|
|
2371
|
+
})) {
|
|
2372
|
+
if (notice.includes("Large context"))
|
|
2373
|
+
continue; // already shown above
|
|
2374
|
+
writeNotice("warn", notice, chalk.yellow(` ⚠ ${notice}\n`));
|
|
2208
2375
|
}
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
writeStatus(status, chalk.dim(status));
|
|
2212
|
-
});
|
|
2376
|
+
throw streamError;
|
|
2377
|
+
}
|
|
2213
2378
|
}
|
|
2214
2379
|
finally {
|
|
2215
2380
|
// Always clear the spinner — abort, network error, or success.
|
|
@@ -2977,9 +3142,17 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2977
3142
|
planCreatedThisTurn = true;
|
|
2978
3143
|
}
|
|
2979
3144
|
productiveSteps += 1;
|
|
3145
|
+
// E5: collapse identical large tool bodies within this turn to a pointer.
|
|
3146
|
+
const deduped = dedupeToolContextOutput({
|
|
3147
|
+
content: res.contextOutput,
|
|
3148
|
+
toolName: res.call.name,
|
|
3149
|
+
artifactPath: res.result.outputPath,
|
|
3150
|
+
seenHashes: toolResultHashes,
|
|
3151
|
+
});
|
|
3152
|
+
const contextForHistory = deduped.content;
|
|
2980
3153
|
// Soft plan-mode note on tool payloads only (never a user message).
|
|
2981
3154
|
// Stop once a plan with tasks exists so we don't nag after plan.create.
|
|
2982
|
-
let toolContent = `Tool ${res.call.name} result (exit=${res.result.exitCode ?? 0}, ok=${res.result.ok}):\n${
|
|
3155
|
+
let toolContent = `Tool ${res.call.name} result (exit=${res.result.exitCode ?? 0}, ok=${res.result.ok}):\n${contextForHistory}`;
|
|
2983
3156
|
const reminded = maybeAppendPlanModeReminder(toolContent, {
|
|
2984
3157
|
isPlanMode,
|
|
2985
3158
|
planApproved: session.planApproved.value,
|
|
@@ -3117,6 +3290,19 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3117
3290
|
else {
|
|
3118
3291
|
session.planApproved.value = true;
|
|
3119
3292
|
}
|
|
3293
|
+
// Re-derive pause policy from the new plan kind (coding builds
|
|
3294
|
+
// must not hard-pause even if the free-text prompt was generic).
|
|
3295
|
+
const kindArg = typeof res.call.args.kind === "string"
|
|
3296
|
+
? res.call.args.kind
|
|
3297
|
+
: undefined;
|
|
3298
|
+
codingSession = codingSessionFromContext({
|
|
3299
|
+
buildLike,
|
|
3300
|
+
planKind: kindArg,
|
|
3301
|
+
});
|
|
3302
|
+
pauseMode = progressPauseMode({
|
|
3303
|
+
codingSession,
|
|
3304
|
+
autoConfirm: Boolean(options.autoConfirm),
|
|
3305
|
+
});
|
|
3120
3306
|
}
|
|
3121
3307
|
// User Esc/Ctrl+C only — never cancel siblings because a delete failed
|
|
3122
3308
|
// or a confirm was declined; the model must see every tool result.
|
|
@@ -3236,6 +3422,8 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3236
3422
|
}
|
|
3237
3423
|
}
|
|
3238
3424
|
}
|
|
3425
|
+
// Hard iteration ceiling (hundreds of steps) — rare. Mid-turn governor
|
|
3426
|
+
// pauses already confirm for non-coding; coding never hard-pauses there.
|
|
3239
3427
|
const richSummary = await buildRichStopSummary(messages, session, productiveSteps);
|
|
3240
3428
|
lastAnswer = richSummary;
|
|
3241
3429
|
outcomeState.outcome.status = "paused_budget";
|