@pentoshi/clai 3.8.19 → 3.8.21
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/compaction-summary.d.ts +1 -1
- package/dist/agent/compaction-summary.js +3 -2
- package/dist/agent/compaction-summary.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/context-manager.d.ts +29 -2
- package/dist/agent/context-manager.js +125 -17
- package/dist/agent/context-manager.js.map +1 -1
- package/dist/agent/continue-orient.d.ts +39 -0
- package/dist/agent/continue-orient.js +152 -0
- package/dist/agent/continue-orient.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 +280 -141
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/session-state.d.ts +13 -1
- package/dist/agent/session-state.js +30 -17
- package/dist/agent/session-state.js.map +1 -1
- package/dist/agent/stop-summary.d.ts +2 -2
- package/dist/agent/stop-summary.js +23 -4
- 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 +21 -8
- package/dist/agent/tool-output-formatting.js.map +1 -1
- package/dist/app/controllers/session-compact-helper.d.ts +2 -0
- package/dist/app/controllers/session-compact-helper.js +14 -5
- package/dist/app/controllers/session-compact-helper.js.map +1 -1
- package/dist/app/controllers/session-controller.d.ts +6 -0
- package/dist/app/controllers/session-controller.js +27 -2
- package/dist/app/controllers/session-controller.js.map +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/http.js +22 -12
- package/dist/llm/http.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 +55 -7
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/system.agent.md +4 -3
- 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 +17 -2
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/http.d.ts +10 -0
- package/dist/tools/http.js +293 -58
- 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 +26 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/shell.d.ts +1 -1
- package/dist/tui/format-keys.js +29 -6
- package/dist/tui/format-keys.js.map +1 -1
- package/dist/tui-v2/app/App.js +39 -1
- package/dist/tui-v2/app/App.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 +13 -7
- 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/start-tui-v2.js +10 -4
- package/dist/tui-v2/bootstrap/start-tui-v2.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/modal/secret-modal.d.ts +3 -1
- package/dist/tui-v2/components/modal/secret-modal.js +110 -18
- package/dist/tui-v2/components/modal/secret-modal.js.map +1 -1
- 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/toast/toast-anim.d.ts +5 -4
- package/dist/tui-v2/components/toast/toast-anim.js +10 -7
- package/dist/tui-v2/components/toast/toast-anim.js.map +1 -1
- package/dist/tui-v2/components/toast/toast-host.d.ts +9 -3
- package/dist/tui-v2/components/toast/toast-host.js +124 -26
- package/dist/tui-v2/components/toast/toast-host.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 +42 -0
- package/dist/tui-v2/controllers/overlay-controller.js +53 -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/tui-v2/state/transcript-reducer.js +70 -30
- package/dist/tui-v2/state/transcript-reducer.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";
|
|
@@ -37,6 +40,7 @@ import { saveToolOutput, summarizeOutput, formatToolContext, } from "./tool-outp
|
|
|
37
40
|
import { renderPlanForTerminal, planContextMessage, handlePlanTool, resolvePlanTaskId, } from "./plan-tool.js";
|
|
38
41
|
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
42
|
import { buildSessionStateBlock, inferNextHint, upsertSessionStateMessage, } from "./session-state.js";
|
|
43
|
+
import { buildContinueOrientation } from "./continue-orient.js";
|
|
40
44
|
import { detectPackageManager } from "./workspace-orient.js";
|
|
41
45
|
import { budgetRemaining, consumeBudget, createRecoveryBudgets, freestyleClaimsAppReady, looksLikeShallowPentestReport, recoveryForErrorDiagnosis, recoveryForFailedProbe, recoveryForFreshness, recoveryForMissingFeature, recoveryForMissingPlan, recoveryForNarration, recoveryForPrematureComplete, recoveryForRuntimeVerify, recoveryForShallowPentest, } from "./must-continue.js";
|
|
42
46
|
import { scopeContextMessage } from "./scope-context.js";
|
|
@@ -84,9 +88,17 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
84
88
|
if (/\/output\b|open full output|Ctrl\+O or|\.clai\/outputs/i.test(cleaned)) {
|
|
85
89
|
return;
|
|
86
90
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
// API key rotation / retry lines need more room than tool-name chips.
|
|
92
|
+
const keyLine = /^(using |switching |⏳ |all .+ API keys)/i.test(cleaned);
|
|
93
|
+
const maxLen = keyLine ? 96 : 64;
|
|
94
|
+
if (cleaned.length > maxLen) {
|
|
95
|
+
if (keyLine) {
|
|
96
|
+
cleaned = cleaned.slice(0, maxLen - 1) + "…";
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const short = cleaned.match(/^[\w./-]+/);
|
|
100
|
+
cleaned = short ? short[0] : cleaned.slice(0, maxLen - 3) + "…";
|
|
101
|
+
}
|
|
90
102
|
}
|
|
91
103
|
emit({ type: "status", text: cleaned || "working" });
|
|
92
104
|
if (writesDirectly)
|
|
@@ -277,6 +289,11 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
277
289
|
};
|
|
278
290
|
let lastAnswer = "";
|
|
279
291
|
const session = options.session ?? createSessionPolicy();
|
|
292
|
+
// One-shot CLI / tests that never entered TUI/REPL still need an isolated
|
|
293
|
+
// scratch+output workspace. No-op when a session already bound one.
|
|
294
|
+
if (!getActiveSessionWorkspace()) {
|
|
295
|
+
beginSessionWorkspace();
|
|
296
|
+
}
|
|
280
297
|
// Active plan context
|
|
281
298
|
// If this session already has a plan, inject it so the model keeps it in
|
|
282
299
|
// context. When the user has approved it (via /implement) we instruct the
|
|
@@ -322,11 +339,16 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
322
339
|
setActiveProjectRootIfValid(discoveredProjects[0]);
|
|
323
340
|
}
|
|
324
341
|
const buildSystemContent = (native) => {
|
|
342
|
+
const reliability = getReliabilityPolicy();
|
|
325
343
|
const sections = [
|
|
326
344
|
(useCompactSystemPrompt
|
|
327
345
|
? renderCompactAgentSystemPrompt
|
|
328
346
|
: renderAgentSystemPrompt)(toolNames.join(", "), {
|
|
329
347
|
nativeTools: native,
|
|
348
|
+
// E6: slim native constitution when API tool schemas are attached.
|
|
349
|
+
...(native
|
|
350
|
+
? { slimNative: reliability.slimNativePrompt }
|
|
351
|
+
: {}),
|
|
330
352
|
}),
|
|
331
353
|
];
|
|
332
354
|
if (projectContext) {
|
|
@@ -380,6 +402,22 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
380
402
|
if (activePlan) {
|
|
381
403
|
systemSections.push(planContextMessage(activePlan, session.planApproved.value));
|
|
382
404
|
}
|
|
405
|
+
// Soft mid-work recovery (any domain): re-attach to jobs / open tasks /
|
|
406
|
+
// last tools after interrupt or "continue" — not a hard gate.
|
|
407
|
+
if (!informationalQuery && !idleOrSocialPrompt) {
|
|
408
|
+
const continueBrief = buildContinueOrientation({
|
|
409
|
+
prompt,
|
|
410
|
+
history: options.history,
|
|
411
|
+
plan: activePlan,
|
|
412
|
+
runningJobs: jobManager.getRunningJobs(),
|
|
413
|
+
recentJobs: jobManager.getRecentJobs(12),
|
|
414
|
+
informationalQuery,
|
|
415
|
+
idleOrSocial: idleOrSocialPrompt,
|
|
416
|
+
});
|
|
417
|
+
if (continueBrief) {
|
|
418
|
+
systemSections.push(continueBrief);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
383
421
|
if (isPlanMode) {
|
|
384
422
|
systemSections.push(planModeDirective());
|
|
385
423
|
}
|
|
@@ -699,9 +737,14 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
699
737
|
refreshSessionState = (plan) => {
|
|
700
738
|
if (idleOrSocialPrompt || informationalQuery)
|
|
701
739
|
return;
|
|
702
|
-
|
|
703
|
-
return;
|
|
740
|
+
const runningJobs = jobManager.getRunningJobs();
|
|
704
741
|
const p = plan ?? activePlan;
|
|
742
|
+
if (!buildLikeTurn &&
|
|
743
|
+
!pentestLikeTurn &&
|
|
744
|
+
!p &&
|
|
745
|
+
runningJobs.length === 0) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
705
748
|
const root = getActiveProjectRoot() ?? p?.meta?.projectRoot;
|
|
706
749
|
const pm = p?.meta?.packageManager ??
|
|
707
750
|
(root ? detectPackageManager(root) : undefined);
|
|
@@ -712,6 +755,10 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
712
755
|
const done = p?.tasks
|
|
713
756
|
.filter((t) => t.state === "done" || t.state === "skipped")
|
|
714
757
|
.map((t) => t.id);
|
|
758
|
+
const jobBits = runningJobs.slice(0, 4).map((j) => {
|
|
759
|
+
const cmd = (j.commandDisplay || j.command).replace(/\s+/g, " ").trim();
|
|
760
|
+
return `${j.id} ${j.status} ${cmd.slice(0, 40)}`;
|
|
761
|
+
});
|
|
715
762
|
const snap = {
|
|
716
763
|
goal: p?.goal ?? prompt.slice(0, 160),
|
|
717
764
|
projectRoot: root,
|
|
@@ -728,6 +775,9 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
728
775
|
serverProbedOk: sawLocalHttpProbe,
|
|
729
776
|
lastProbeFailed: sawFailedLocalHttpProbe,
|
|
730
777
|
lastOkTool: taskWorkLedger?.lastOkTool,
|
|
778
|
+
backgroundJobs: jobBits.length > 0
|
|
779
|
+
? `${runningJobs.length} running: ${jobBits.join("; ")}`
|
|
780
|
+
: undefined,
|
|
731
781
|
engagementNote: pentestSession
|
|
732
782
|
? "remote/security engagement — no local dev server as completion"
|
|
733
783
|
: undefined,
|
|
@@ -1879,8 +1929,14 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1879
1929
|
}
|
|
1880
1930
|
return { ok: result.ok, call, result, contextOutput };
|
|
1881
1931
|
}
|
|
1882
|
-
|
|
1932
|
+
// Align with /compact default: small recency + dense memory (not keepRecent=6 fat tails).
|
|
1933
|
+
const AUTO_COMPACT_KEEP_RECENT = 2;
|
|
1883
1934
|
let lastCompactionMsgCount = 0;
|
|
1935
|
+
/** E5: identical tool bodies within this turn → pointer instead of re-append. */
|
|
1936
|
+
const toolResultHashes = new Map();
|
|
1937
|
+
/** E4: consecutive free-tier stream failures this turn. */
|
|
1938
|
+
let freeTierConsecutiveFailures = 0;
|
|
1939
|
+
let freeTierLargeContextWarned = false;
|
|
1884
1940
|
const summarizeForCompaction = async (summaryPrompt) => {
|
|
1885
1941
|
const response = await completeWithProvider({
|
|
1886
1942
|
provider,
|
|
@@ -1890,14 +1946,17 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1890
1946
|
{ role: "user", content: summaryPrompt },
|
|
1891
1947
|
],
|
|
1892
1948
|
temperature: 0.1,
|
|
1893
|
-
|
|
1949
|
+
// Prefer dense memory; enough room for findings without forcing a tiny stub.
|
|
1950
|
+
maxTokens: 2_048,
|
|
1894
1951
|
signal: options.signal,
|
|
1895
1952
|
});
|
|
1896
1953
|
return response.text;
|
|
1897
1954
|
};
|
|
1898
1955
|
async function maybeAutoCompact(reason, force = false) {
|
|
1899
1956
|
const beforeTokens = estimateMessagesTokens(messages);
|
|
1900
|
-
|
|
1957
|
+
// E1: soft early compact (default 70k) while hard ceiling remains 100k.
|
|
1958
|
+
const compactTrigger = autoCompactTriggerTokens();
|
|
1959
|
+
if (!force && beforeTokens < compactTrigger)
|
|
1901
1960
|
return;
|
|
1902
1961
|
if (messages.length <= AUTO_COMPACT_KEEP_RECENT + 2)
|
|
1903
1962
|
return;
|
|
@@ -1907,8 +1966,17 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1907
1966
|
return;
|
|
1908
1967
|
try {
|
|
1909
1968
|
const result = await compactMessagesWithSummary(messages, summarizeForCompaction, { budgetTokens: 0, keepRecent: AUTO_COMPACT_KEEP_RECENT });
|
|
1910
|
-
|
|
1969
|
+
const summaryBody = result.messages.find((m) => isCompactionMemoryMessage(m))?.content ??
|
|
1970
|
+
"";
|
|
1971
|
+
if (!shouldApplyAutoCompact({
|
|
1972
|
+
summarized: result.summarized,
|
|
1973
|
+
summaryBody,
|
|
1974
|
+
beforeTokens: result.beforeTokens,
|
|
1975
|
+
afterTokens: result.afterTokens,
|
|
1976
|
+
afterMessages: result.messages,
|
|
1977
|
+
})) {
|
|
1911
1978
|
return;
|
|
1979
|
+
}
|
|
1912
1980
|
messages.splice(0, messages.length, ...result.messages);
|
|
1913
1981
|
loopGuard.resetReadOnly();
|
|
1914
1982
|
// Token stats BEFORE plan re-injection so the reduction is accurate.
|
|
@@ -2017,159 +2085,222 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2017
2085
|
({ dialect: toolDialect, native: nativeToolsActive } =
|
|
2018
2086
|
resolveNativeTools(provider, model));
|
|
2019
2087
|
if (messages[0]?.role === "system") {
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2088
|
+
// Recompose only when content actually changes (hour-stable env clock
|
|
2089
|
+
// keeps the constitution prefix identical across steps, which helps
|
|
2090
|
+
// provider prompt caching and avoids needless object churn).
|
|
2091
|
+
const nextSystem = composeCurrentSystemPrompt(nativeToolsActive);
|
|
2092
|
+
if (messages[0].content !== nextSystem) {
|
|
2093
|
+
messages[0] = {
|
|
2094
|
+
role: "system",
|
|
2095
|
+
content: nextSystem,
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2024
2098
|
}
|
|
2025
2099
|
const turnTools = selectToolDefs(nativeToolsActive, useCompactSystemPrompt);
|
|
2026
2100
|
toolsAttached = Boolean(turnTools?.length);
|
|
2101
|
+
const contextBreakdown = buildContextBreakdown(messages, toolsAttached ? turnTools : undefined);
|
|
2102
|
+
// E4: advisory only — never blocks free-tier users.
|
|
2103
|
+
if (!freeTierLargeContextWarned) {
|
|
2104
|
+
const notices = freeTierGuardNotices({
|
|
2105
|
+
provider,
|
|
2106
|
+
estimatedInputTokens: contextBreakdown.estimatedTotalTokens,
|
|
2107
|
+
consecutiveFailures: freeTierConsecutiveFailures,
|
|
2108
|
+
});
|
|
2109
|
+
for (const notice of notices) {
|
|
2110
|
+
if (notice.includes("Large context")) {
|
|
2111
|
+
freeTierLargeContextWarned = true;
|
|
2112
|
+
}
|
|
2113
|
+
writeNotice("info", notice, chalk.dim(` ℹ ${notice}\n`));
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2027
2116
|
await auditLog("agent.turn", {
|
|
2028
2117
|
provider,
|
|
2029
2118
|
model,
|
|
2030
2119
|
tool_protocol: toolsAttached ? "native" : "text",
|
|
2031
2120
|
dialect: toolDialect,
|
|
2032
2121
|
step,
|
|
2122
|
+
// Metadata-only composition metrics (no prompt/tool text).
|
|
2123
|
+
...contextBreakdownAuditPayload(contextBreakdown),
|
|
2124
|
+
compactTriggerTokens: autoCompactTriggerTokens(),
|
|
2125
|
+
maxTokensBudget: resolveStepMaxTokens({
|
|
2126
|
+
nativeToolsActive,
|
|
2127
|
+
toolsAttached,
|
|
2128
|
+
recoveryNudge: retryWithoutThinking,
|
|
2129
|
+
}),
|
|
2033
2130
|
});
|
|
2131
|
+
// Resume / mid-turn abort can leave orphan tool rows or a user
|
|
2132
|
+
// "continue" before tool results. Heal first so multi-key retry and
|
|
2133
|
+
// history reloads don't hard-fail on protocol asserts.
|
|
2134
|
+
// Heal once per step if needed; silent (no toast) — placeholders are
|
|
2135
|
+
// ok=true so the model doesn't thrash on fake exit=130 failures.
|
|
2136
|
+
repairToolProtocol(messages);
|
|
2034
2137
|
assertValidToolProtocol(messages);
|
|
2035
|
-
completion
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
:
|
|
2046
|
-
|
|
2047
|
-
?
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2138
|
+
// E3: adaptive completion budget (still large enough for writes).
|
|
2139
|
+
const stepMaxTokens = resolveStepMaxTokens({
|
|
2140
|
+
nativeToolsActive,
|
|
2141
|
+
toolsAttached,
|
|
2142
|
+
recoveryNudge: retryWithoutThinking,
|
|
2143
|
+
});
|
|
2144
|
+
try {
|
|
2145
|
+
completion = await streamWithProvider({
|
|
2146
|
+
provider,
|
|
2147
|
+
model,
|
|
2148
|
+
allowModelFallback: false,
|
|
2149
|
+
messages,
|
|
2150
|
+
temperature: /minimax-m3/i.test(model) ? 1.0 : 0.2,
|
|
2151
|
+
maxTokens: stepMaxTokens,
|
|
2152
|
+
signal: options.signal,
|
|
2153
|
+
thinking: retryWithoutThinking
|
|
2154
|
+
? { ...config.thinking, enabled: false, effort: "low" }
|
|
2155
|
+
: config.thinking,
|
|
2156
|
+
...(toolsAttached
|
|
2157
|
+
? {
|
|
2158
|
+
tools: turnTools,
|
|
2159
|
+
toolChoice: freshWebSearchRequired && !sawFreshWebSearch
|
|
2160
|
+
? { type: "function", name: "web.search" }
|
|
2161
|
+
: "auto",
|
|
2162
|
+
parallelToolCalls: true,
|
|
2163
|
+
// P2-3: emit tool cards as soon as the function name arrives.
|
|
2164
|
+
onToolCallDelta: (delta) => {
|
|
2165
|
+
if (!delta.name)
|
|
2166
|
+
return;
|
|
2167
|
+
const name = fromWireName(delta.name) ?? delta.name;
|
|
2168
|
+
const existing = deferredToolCalls[delta.index];
|
|
2169
|
+
if (existing) {
|
|
2170
|
+
if (delta.argumentsBytes &&
|
|
2171
|
+
delta.argumentsBytes >= 4096 &&
|
|
2172
|
+
!writesDirectly) {
|
|
2173
|
+
emit({
|
|
2174
|
+
type: "status",
|
|
2175
|
+
text: `${name} (${Math.round(delta.argumentsBytes / 1024)}KB args)`,
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
// Ensure slots are dense so index maps to deferredToolCalls[i].
|
|
2181
|
+
while (deferredToolCalls.length < delta.index) {
|
|
2182
|
+
deferredToolCalls.push({
|
|
2183
|
+
eventId: `tool-${++nextToolEventId}`,
|
|
2184
|
+
call: { name: "…", args: {} },
|
|
2185
|
+
rendered: "",
|
|
2066
2186
|
});
|
|
2067
2187
|
}
|
|
2068
|
-
|
|
2188
|
+
const call = normalizeToolCall({
|
|
2189
|
+
name,
|
|
2190
|
+
args: {},
|
|
2191
|
+
});
|
|
2192
|
+
const eventId = `tool-${++nextToolEventId}`;
|
|
2193
|
+
callIds.push(eventId);
|
|
2194
|
+
alreadyPrintedIds.add(eventId);
|
|
2195
|
+
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
2196
|
+
chalk.gray(` ${formatToolArgs(call)}`);
|
|
2197
|
+
const entry = {
|
|
2198
|
+
eventId,
|
|
2199
|
+
call,
|
|
2200
|
+
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
2201
|
+
};
|
|
2202
|
+
if (deferredToolCalls.length === delta.index) {
|
|
2203
|
+
deferredToolCalls.push(entry);
|
|
2204
|
+
}
|
|
2205
|
+
else {
|
|
2206
|
+
deferredToolCalls[delta.index] = entry;
|
|
2207
|
+
}
|
|
2208
|
+
streamedCallsCount = Math.max(streamedCallsCount, deferredToolCalls.length);
|
|
2209
|
+
if (!writesDirectly) {
|
|
2210
|
+
emit({ type: "status", text: call.name });
|
|
2211
|
+
}
|
|
2212
|
+
else {
|
|
2213
|
+
spinner.stop();
|
|
2214
|
+
spinner = startThinkingSpinner(`tool ${call.name}…`, options.signal);
|
|
2215
|
+
}
|
|
2216
|
+
},
|
|
2217
|
+
}
|
|
2218
|
+
: {}),
|
|
2219
|
+
}, (token) => {
|
|
2220
|
+
deltaParser?.push(token);
|
|
2221
|
+
generatedTokens += 1;
|
|
2222
|
+
accumulatedText += token;
|
|
2223
|
+
// Early UI cards from text fences only when native tools are off
|
|
2224
|
+
// (native args stream as structured deltas, not prose).
|
|
2225
|
+
if (!toolsAttached) {
|
|
2226
|
+
const parsedCalls = parseAllToolCalls(accumulatedText);
|
|
2227
|
+
if (parsedCalls.length > streamedCallsCount) {
|
|
2228
|
+
if (writesDirectly) {
|
|
2229
|
+
spinner.stop();
|
|
2069
2230
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2231
|
+
while (streamedCallsCount < parsedCalls.length) {
|
|
2232
|
+
const call = parsedCalls[streamedCallsCount];
|
|
2233
|
+
const eventId = `tool-${++nextToolEventId}`;
|
|
2234
|
+
callIds.push(eventId);
|
|
2235
|
+
alreadyPrintedIds.add(eventId);
|
|
2236
|
+
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
2237
|
+
chalk.gray(` ${formatToolArgs(call)}`);
|
|
2072
2238
|
deferredToolCalls.push({
|
|
2073
|
-
eventId
|
|
2074
|
-
call
|
|
2075
|
-
rendered: "",
|
|
2239
|
+
eventId,
|
|
2240
|
+
call,
|
|
2241
|
+
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
2076
2242
|
});
|
|
2243
|
+
if (!writesDirectly) {
|
|
2244
|
+
emit({ type: "status", text: call.name });
|
|
2245
|
+
}
|
|
2246
|
+
streamedCallsCount += 1;
|
|
2077
2247
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
args: {},
|
|
2081
|
-
});
|
|
2082
|
-
const eventId = `tool-${++nextToolEventId}`;
|
|
2083
|
-
callIds.push(eventId);
|
|
2084
|
-
alreadyPrintedIds.add(eventId);
|
|
2085
|
-
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
2086
|
-
chalk.gray(` ${formatToolArgs(call)}`);
|
|
2087
|
-
const entry = {
|
|
2088
|
-
eventId,
|
|
2089
|
-
call,
|
|
2090
|
-
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
2091
|
-
};
|
|
2092
|
-
if (deferredToolCalls.length === delta.index) {
|
|
2093
|
-
deferredToolCalls.push(entry);
|
|
2094
|
-
}
|
|
2095
|
-
else {
|
|
2096
|
-
deferredToolCalls[delta.index] = entry;
|
|
2097
|
-
}
|
|
2098
|
-
streamedCallsCount = Math.max(streamedCallsCount, deferredToolCalls.length);
|
|
2099
|
-
if (!writesDirectly) {
|
|
2100
|
-
emit({ type: "status", text: call.name });
|
|
2101
|
-
}
|
|
2102
|
-
else {
|
|
2103
|
-
spinner.stop();
|
|
2104
|
-
spinner = startThinkingSpinner(`tool ${call.name}…`, options.signal);
|
|
2105
|
-
}
|
|
2106
|
-
},
|
|
2107
|
-
}
|
|
2108
|
-
: {}),
|
|
2109
|
-
}, (token) => {
|
|
2110
|
-
deltaParser?.push(token);
|
|
2111
|
-
generatedTokens += 1;
|
|
2112
|
-
accumulatedText += token;
|
|
2113
|
-
// Early UI cards from text fences only when native tools are off
|
|
2114
|
-
// (native args stream as structured deltas, not prose).
|
|
2115
|
-
if (!toolsAttached) {
|
|
2116
|
-
const parsedCalls = parseAllToolCalls(accumulatedText);
|
|
2117
|
-
if (parsedCalls.length > streamedCallsCount) {
|
|
2118
|
-
if (writesDirectly) {
|
|
2119
|
-
spinner.stop();
|
|
2120
|
-
}
|
|
2121
|
-
while (streamedCallsCount < parsedCalls.length) {
|
|
2122
|
-
const call = parsedCalls[streamedCallsCount];
|
|
2123
|
-
const eventId = `tool-${++nextToolEventId}`;
|
|
2124
|
-
callIds.push(eventId);
|
|
2125
|
-
alreadyPrintedIds.add(eventId);
|
|
2126
|
-
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
2127
|
-
chalk.gray(` ${formatToolArgs(call)}`);
|
|
2128
|
-
deferredToolCalls.push({
|
|
2129
|
-
eventId,
|
|
2130
|
-
call,
|
|
2131
|
-
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
2132
|
-
});
|
|
2133
|
-
if (!writesDirectly) {
|
|
2134
|
-
emit({ type: "status", text: call.name });
|
|
2248
|
+
if (writesDirectly) {
|
|
2249
|
+
spinner = startThinkingSpinner(`generating response (${generatedTokens} tokens)`, options.signal);
|
|
2135
2250
|
}
|
|
2136
|
-
streamedCallsCount += 1;
|
|
2137
2251
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2252
|
+
}
|
|
2253
|
+
if (!sawReasoning && /<think/i.test(token)) {
|
|
2254
|
+
sawReasoning = true;
|
|
2255
|
+
inThinking = true;
|
|
2256
|
+
spinner.setLabel("thinking");
|
|
2257
|
+
if (!writesDirectly)
|
|
2258
|
+
emit({ type: "status", text: "thinking" });
|
|
2259
|
+
}
|
|
2260
|
+
if (/<\/think>/i.test(token)) {
|
|
2261
|
+
inThinking = false;
|
|
2262
|
+
spinner.setLabel("generating response (0 tokens)");
|
|
2263
|
+
generatedTokens = 0;
|
|
2264
|
+
}
|
|
2265
|
+
if (inThinking) {
|
|
2266
|
+
const cleaned = token.replace(/<\/?think[^>]*>/gi, "");
|
|
2267
|
+
if (cleaned) {
|
|
2268
|
+
spinner.pushPreview(cleaned);
|
|
2269
|
+
const approx = cleaned.split(/\s+/).filter(Boolean).length;
|
|
2270
|
+
if (approx > 0)
|
|
2271
|
+
spinner.bumpReasoning(approx);
|
|
2140
2272
|
}
|
|
2141
2273
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
spinner.setLabel("thinking");
|
|
2147
|
-
if (!writesDirectly)
|
|
2148
|
-
emit({ type: "status", text: "thinking" });
|
|
2149
|
-
}
|
|
2150
|
-
if (/<\/think>/i.test(token)) {
|
|
2151
|
-
inThinking = false;
|
|
2152
|
-
spinner.setLabel("generating response (0 tokens)");
|
|
2153
|
-
generatedTokens = 0;
|
|
2154
|
-
}
|
|
2155
|
-
if (inThinking) {
|
|
2156
|
-
const cleaned = token.replace(/<\/?think[^>]*>/gi, "");
|
|
2157
|
-
if (cleaned) {
|
|
2158
|
-
spinner.pushPreview(cleaned);
|
|
2159
|
-
const approx = cleaned.split(/\s+/).filter(Boolean).length;
|
|
2160
|
-
if (approx > 0)
|
|
2161
|
-
spinner.bumpReasoning(approx);
|
|
2274
|
+
else {
|
|
2275
|
+
if (generatedTokens % 10 === 0) {
|
|
2276
|
+
spinner.setLabel(`generating response (${generatedTokens} tokens)`);
|
|
2277
|
+
}
|
|
2162
2278
|
}
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2279
|
+
}, (status) => {
|
|
2280
|
+
spinner.stop();
|
|
2281
|
+
writeStatus(status, chalk.dim(status));
|
|
2282
|
+
// Toast only on key *switch* after a failure — never on sticky
|
|
2283
|
+
// "using" or retry countdown ticks (those stay in composer status).
|
|
2284
|
+
if (/^switching /i.test(status.trim())) {
|
|
2285
|
+
writeNotice("warn", status.trim(), chalk.yellow(` ${status.trim()}\n`));
|
|
2167
2286
|
}
|
|
2287
|
+
});
|
|
2288
|
+
freeTierConsecutiveFailures = 0;
|
|
2289
|
+
}
|
|
2290
|
+
catch (streamError) {
|
|
2291
|
+
// E4: track free-tier failures for advisory notices (never blocks).
|
|
2292
|
+
freeTierConsecutiveFailures += 1;
|
|
2293
|
+
for (const notice of freeTierGuardNotices({
|
|
2294
|
+
provider,
|
|
2295
|
+
estimatedInputTokens: contextBreakdown.estimatedTotalTokens,
|
|
2296
|
+
consecutiveFailures: freeTierConsecutiveFailures,
|
|
2297
|
+
})) {
|
|
2298
|
+
if (notice.includes("Large context"))
|
|
2299
|
+
continue; // already shown above
|
|
2300
|
+
writeNotice("warn", notice, chalk.yellow(` ⚠ ${notice}\n`));
|
|
2168
2301
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
writeStatus(status, chalk.dim(status));
|
|
2172
|
-
});
|
|
2302
|
+
throw streamError;
|
|
2303
|
+
}
|
|
2173
2304
|
}
|
|
2174
2305
|
finally {
|
|
2175
2306
|
// Always clear the spinner — abort, network error, or success.
|
|
@@ -2937,9 +3068,17 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2937
3068
|
planCreatedThisTurn = true;
|
|
2938
3069
|
}
|
|
2939
3070
|
productiveSteps += 1;
|
|
3071
|
+
// E5: collapse identical large tool bodies within this turn to a pointer.
|
|
3072
|
+
const deduped = dedupeToolContextOutput({
|
|
3073
|
+
content: res.contextOutput,
|
|
3074
|
+
toolName: res.call.name,
|
|
3075
|
+
artifactPath: res.result.outputPath,
|
|
3076
|
+
seenHashes: toolResultHashes,
|
|
3077
|
+
});
|
|
3078
|
+
const contextForHistory = deduped.content;
|
|
2940
3079
|
// Soft plan-mode note on tool payloads only (never a user message).
|
|
2941
3080
|
// Stop once a plan with tasks exists so we don't nag after plan.create.
|
|
2942
|
-
let toolContent = `Tool ${res.call.name} result (exit=${res.result.exitCode ?? 0}, ok=${res.result.ok}):\n${
|
|
3081
|
+
let toolContent = `Tool ${res.call.name} result (exit=${res.result.exitCode ?? 0}, ok=${res.result.ok}):\n${contextForHistory}`;
|
|
2943
3082
|
const reminded = maybeAppendPlanModeReminder(toolContent, {
|
|
2944
3083
|
isPlanMode,
|
|
2945
3084
|
planApproved: session.planApproved.value,
|