@pentoshi/clai 2.0.37 → 2.0.42
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 +43 -34
- package/dist/agent/events.d.ts +5 -0
- package/dist/agent/plan-tool.js +7 -1
- package/dist/agent/plan-tool.js.map +1 -1
- package/dist/agent/runner.js +126 -22
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tool-call-parser.d.ts +11 -0
- package/dist/agent/tool-call-parser.js +33 -0
- package/dist/agent/tool-call-parser.js.map +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/prompts/index.d.ts +8 -1
- package/dist/prompts/index.js +5 -2
- package/dist/prompts/index.js.map +1 -1
- package/dist/tools/wordlists.d.ts +0 -1
- package/dist/tools/wordlists.js +149 -67
- package/dist/tools/wordlists.js.map +1 -1
- package/dist/tui/state.js +6 -0
- package/dist/tui/state.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -182,40 +182,49 @@ export OLLAMA_HOST=http://localhost:11434
|
|
|
182
182
|
|
|
183
183
|
## REPL Commands
|
|
184
184
|
|
|
185
|
-
| Command
|
|
186
|
-
|
|
187
|
-
| `/ask`
|
|
188
|
-
| `/agent`
|
|
189
|
-
| `/model`
|
|
190
|
-
| `/model <name\|#>`
|
|
191
|
-
| `/provider [name]`
|
|
192
|
-
| `/use <provider>`
|
|
193
|
-
| `/set <provider> [key]` | Store API key (masked input if key omitted)
|
|
194
|
-
| `/unset <provider>`
|
|
195
|
-
| `/keys`
|
|
196
|
-
| `/info [provider]`
|
|
197
|
-
| `/
|
|
198
|
-
| `/
|
|
199
|
-
| `/
|
|
200
|
-
| `/
|
|
201
|
-
| `/
|
|
202
|
-
| `/
|
|
203
|
-
| `/
|
|
204
|
-
| `/
|
|
205
|
-
| `/
|
|
206
|
-
| `/
|
|
207
|
-
| `/
|
|
208
|
-
| `/
|
|
209
|
-
| `/
|
|
210
|
-
| `/
|
|
211
|
-
| `/
|
|
212
|
-
| `/
|
|
213
|
-
| `/
|
|
214
|
-
| `/
|
|
215
|
-
| `/
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
185
|
+
| Command | Action |
|
|
186
|
+
|---------|--------|
|
|
187
|
+
| `/ask` | Switch to read-only ask mode (suggests commands/steps but does not execute them) |
|
|
188
|
+
| `/agent` | Switch to active agent mode (proposes plans, executes commands, installs tools) |
|
|
189
|
+
| `/model` | Open the interactive model picker to select a model (press Enter to open) |
|
|
190
|
+
| `/model <name\|#>` | Switch active model by model name or index number (e.g. `/model 2`) |
|
|
191
|
+
| `/provider [name]` | Switch LLM provider or open interactive provider picker |
|
|
192
|
+
| `/use <provider>` | Alias for `/provider <name>` |
|
|
193
|
+
| `/set <provider> [key]` | Store API key for a provider (prompts with masked input if key is omitted) |
|
|
194
|
+
| `/unset <provider>` | Remove stored API key for a provider |
|
|
195
|
+
| `/keys` | List configured providers with their API keys masked |
|
|
196
|
+
| `/info [provider]` | Show model parameters, token/thinking limits, and usage details for a provider |
|
|
197
|
+
| `/search [provider]` | Switch search provider (Brave, Tavily, etc.) or open interactive picker |
|
|
198
|
+
| `/search-provider [name]`| Alias for `/search` |
|
|
199
|
+
| `/mouse [on\|off]` | Enable (`on`) or disable (`off`) REPL mouse tracking for scrolling vs native copy-paste selection |
|
|
200
|
+
| `/variants [on\|off\|none\|minimal\|low\|medium\|high\|xhigh]` | Configure reasoning effort. Use `on`/`off` to toggle, or set reasoning effort level (opens picker if no arg) |
|
|
201
|
+
| `/reasoning [...]` | Alias for `/variants` |
|
|
202
|
+
| `/think` / `/thinking` | Print the full, hidden reasoning/thinking trace from the last LLM response |
|
|
203
|
+
| `/output [last\|id\|list]`| Toggle full tool output pager (`Ctrl+O`) or list saved output files |
|
|
204
|
+
| `/clear` | Clear the current conversation messages and active state in memory |
|
|
205
|
+
| `/new` / `/clean` | Save current session to history and start a fresh session with cleared context |
|
|
206
|
+
| `/history` | Open interactive session browser to search and restore past conversation sessions |
|
|
207
|
+
| `/save <name>` | Manually save the current session under a custom name |
|
|
208
|
+
| `/reset` | Clear all saved history sessions permanently |
|
|
209
|
+
| `/cwd <path>` | Change the current working directory for shell and tool executions |
|
|
210
|
+
| `/allow <tool>` | Whitelist a tool for the current session to run without prompting for confirmation |
|
|
211
|
+
| `/disallow <tool>` | Revoke session authorization for a tool |
|
|
212
|
+
| `/freeonly [on\|off]` | Toggle (`on` to restrict, `off` to permit) paid providers when automatic fallback is active |
|
|
213
|
+
| `/fallback [on\|off]` | Toggle (`on` to try other providers after a primary failure, `off` to fail immediately) |
|
|
214
|
+
| `/compact` | Compress session history to fit context window constraints |
|
|
215
|
+
| `/context` | Display estimated current context size in tokens |
|
|
216
|
+
| `/plan` | View the current session plan checklist (also `Ctrl+P`) |
|
|
217
|
+
| `/implement` | Approve the proposed plan and begin automated execution |
|
|
218
|
+
| `/discard` | Discard the current plan and return to normal conversational mode |
|
|
219
|
+
| `/scope [show\|clear\|new\|add <targets>]` | View or adjust authorized penetration testing target scopes |
|
|
220
|
+
| `/privacy [status\|clear-history\|clear-logs\|clear-artifacts\|clear-all\|on\|off]` | Toggle private mode (`on` disables all disk writing, `off` enables) or purge logs/history |
|
|
221
|
+
| `/permissions [default\|allow-all]` | Set confirmation prompt behavior (`allow-all` bypasses confirm prompts for mutating tasks except keys) |
|
|
222
|
+
| `/update` | Check for updates and upgrade clai |
|
|
223
|
+
| `/exit` / `/quit` | Quit and close the REPL |
|
|
224
|
+
| `/help` | Print this command reference guide |
|
|
225
|
+
| `Ctrl+C` | Abort current response (second Ctrl+C exits) |
|
|
226
|
+
| `Ctrl+O` | Toggle full tool output (same keys on all OSes) |
|
|
227
|
+
| `Ctrl+P` | View the current session plan |
|
|
219
228
|
|
|
220
229
|
### Plan → Implement workflow
|
|
221
230
|
|
package/dist/agent/events.d.ts
CHANGED
package/dist/agent/plan-tool.js
CHANGED
|
@@ -17,9 +17,15 @@ export function planContextMessage(plan, approved) {
|
|
|
17
17
|
lines.push(` ${i + 1}. [${t.id}] (${t.state}) ${t.title}`);
|
|
18
18
|
});
|
|
19
19
|
if (approved) {
|
|
20
|
+
const inProgress = plan.tasks.find((t) => t.state === "in_progress");
|
|
20
21
|
const firstPending = plan.tasks.find((t) => t.state === "pending");
|
|
21
22
|
lines.push("The user APPROVED this plan. Execute it task by task NOW.");
|
|
22
|
-
if (
|
|
23
|
+
if (inProgress) {
|
|
24
|
+
lines.push(`RESUME TASK ${inProgress.id} (${inProgress.title}) — it was started but interrupted. ` +
|
|
25
|
+
"Retry what was in progress; do NOT restart completed work from scratch. " +
|
|
26
|
+
"Do NOT re-do tasks already marked done, and do NOT skip ahead to later tasks.");
|
|
27
|
+
}
|
|
28
|
+
else if (firstPending) {
|
|
23
29
|
lines.push(`START WITH TASK ${firstPending.id} (${firstPending.title}). ` +
|
|
24
30
|
"Do NOT re-do tasks already marked done, and do NOT skip ahead to later tasks.");
|
|
25
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-tool.js","sourceRoot":"","sources":["../../src/agent/plan-tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EACL,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,QAAQ,GAGT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,gFAAgF;AAChF,MAAM,UAAU,qBAAqB,CAAC,IAAiB;IACrD,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAYD,2EAA2E;AAC3E,MAAM,UAAU,kBAAkB,CAAC,IAAiB,EAAE,QAAiB;IACrE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CACR,uCAAuC,IAAI,CAAC,IAAI,aAAa,IAAI,CAAC,MAAM,IAAI,CAC7E,CAAC;IACF,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC1B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IACH,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;QACnE,KAAK,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;QACxE,IAAI,YAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"plan-tool.js","sourceRoot":"","sources":["../../src/agent/plan-tool.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/runner.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { join, relative, resolve } from "node:path";
|
|
4
4
|
import { streamWithProvider, completeWithProvider } from "../llm/router.js";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import { jobManager } from "../tools/jobs.js";
|
|
7
|
-
import { renderAgentSystemPrompt, } from "../prompts/index.js";
|
|
7
|
+
import { renderAgentSystemPrompt, scratchDirFor, } from "../prompts/index.js";
|
|
8
8
|
import { getConfig } from "../store/config.js";
|
|
9
9
|
import { classifyToolCall, isPentestToolCall, scopeHint, scopeTargetForToolCall, } from "../safety/classifier.js";
|
|
10
10
|
import { availableToolNames, normalizeToolCall, runToolCall, BATCH_SAFE_TOOLS, } from "../tools/registry.js";
|
|
11
11
|
import { looksInteractiveStdin } from "../tools/shell.js";
|
|
12
12
|
import { formatViewportHint, registerViewport } from "../ui/output-pane.js";
|
|
13
|
-
import { compactMessagesWithSummary, estimateMessagesTokens, } from "./context-manager.js";
|
|
13
|
+
import { compactMessagesWithSummary, estimateMessagesTokens, COMPACTION_MEMORY_PREFIX, } from "./context-manager.js";
|
|
14
14
|
import { auditLog } from "../store/logs.js";
|
|
15
15
|
import { loadProjectContext } from "../store/project.js";
|
|
16
16
|
import { loadScope, isScopeActive, targetInScope } from "../store/scope.js";
|
|
@@ -23,7 +23,7 @@ import { analyzeTask } from "./task-analyzer.js";
|
|
|
23
23
|
import { LoopGuard } from "./loop-guard.js";
|
|
24
24
|
import { loadPlan } from "../store/plan.js";
|
|
25
25
|
import { pathInsideSandbox } from "../tools/fs.js";
|
|
26
|
-
import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeActionNarration, looksLikePlanNarration, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
|
|
26
|
+
import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeActionNarration, looksLikePlanNarration, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, pentestWorkflowDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
|
|
27
27
|
import { createSessionPolicy, isPreApprovalAllowedTool, isPlanApprovedByStatus, planHasOpenWork, isAbortError, shouldEnableImageOcr, } from "./session-policy.js";
|
|
28
28
|
import { saveToolOutput, summarizeOutput, formatToolContext, } from "./tool-output-formatting.js";
|
|
29
29
|
import { planContextMessage, handlePlanTool, } from "./plan-tool.js";
|
|
@@ -38,6 +38,78 @@ export { createSessionPolicy, isPreApprovalAllowedTool, isPlanApprovedByStatus,
|
|
|
38
38
|
export function styleToolChatter(call, text) {
|
|
39
39
|
return shouldDimToolChatter(call) ? chalk.dim(text) : text;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Tool names that may write into the project tree. Writes restricted to the
|
|
43
|
+
* per-project scratch directory (under tmpdir()/clai/<name>) are exempted
|
|
44
|
+
* from the active-plan and plan-approved gates so the model can use scratch
|
|
45
|
+
* space to build / inspect / stage work without first creating a plan.
|
|
46
|
+
*/
|
|
47
|
+
const SCRATCH_WRITABLE_TOOLS = new Set([
|
|
48
|
+
"fs.write",
|
|
49
|
+
"fs.writeMany",
|
|
50
|
+
"fs.edit",
|
|
51
|
+
"fs.replaceLines",
|
|
52
|
+
"fs.append",
|
|
53
|
+
"fs.delete",
|
|
54
|
+
]);
|
|
55
|
+
/**
|
|
56
|
+
* Expand `~` the same way `src/tools/fs.ts` does so callers can compare an
|
|
57
|
+
* already-expanded scratch path against paths supplied by the model.
|
|
58
|
+
*/
|
|
59
|
+
function expandHomeLocal(path) {
|
|
60
|
+
if (path === "~")
|
|
61
|
+
return homedir();
|
|
62
|
+
if (path.startsWith("~/") || path.startsWith("~\\")) {
|
|
63
|
+
return resolve(homedir(), path.slice(2));
|
|
64
|
+
}
|
|
65
|
+
return path;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract the target path(s) a write-shaped tool call would touch. Returns
|
|
69
|
+
* an empty array when the call has no resolvable path (so the caller can
|
|
70
|
+
* treat it as NOT scratch-only and fall through to the normal gates).
|
|
71
|
+
*/
|
|
72
|
+
function scratchWriteTargetPaths(call) {
|
|
73
|
+
if (call.name === "fs.writeMany") {
|
|
74
|
+
const files = call.args.files;
|
|
75
|
+
if (!Array.isArray(files))
|
|
76
|
+
return [];
|
|
77
|
+
const paths = [];
|
|
78
|
+
for (const entry of files) {
|
|
79
|
+
if (entry && typeof entry === "object" && "path" in entry) {
|
|
80
|
+
const p = entry.path;
|
|
81
|
+
if (typeof p === "string" && p.length > 0)
|
|
82
|
+
paths.push(p);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return paths;
|
|
86
|
+
}
|
|
87
|
+
const pathArg = call.args.path;
|
|
88
|
+
if (typeof pathArg !== "string" || pathArg.length === 0)
|
|
89
|
+
return [];
|
|
90
|
+
return [pathArg];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* True iff every target path this call would write is inside the resolved
|
|
94
|
+
* scratch directory. A path is considered inside when its `path.relative`
|
|
95
|
+
* against the scratch root is empty (the scratch root itself) or does not
|
|
96
|
+
* start with `..` (no parent traversal). Calls without a recognizable
|
|
97
|
+
* target path return false so they fall through to the normal gates.
|
|
98
|
+
*/
|
|
99
|
+
function isScratchOnlyWrite(call, scratchDir) {
|
|
100
|
+
if (!SCRATCH_WRITABLE_TOOLS.has(call.name))
|
|
101
|
+
return false;
|
|
102
|
+
const paths = scratchWriteTargetPaths(call);
|
|
103
|
+
if (paths.length === 0)
|
|
104
|
+
return false;
|
|
105
|
+
const resolvedScratch = resolve(scratchDir);
|
|
106
|
+
return paths.every((raw) => {
|
|
107
|
+
const expanded = expandHomeLocal(raw);
|
|
108
|
+
const resolved = resolve(expanded);
|
|
109
|
+
const rel = relative(resolvedScratch, resolved);
|
|
110
|
+
return rel === "" || (!rel.startsWith("..") && rel !== "..");
|
|
111
|
+
});
|
|
112
|
+
}
|
|
41
113
|
export async function runAgentLoop(prompt, options = {}) {
|
|
42
114
|
const writesDirectly = !options.onEvent;
|
|
43
115
|
const emit = (event) => options.onEvent?.(event);
|
|
@@ -114,6 +186,22 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
114
186
|
}
|
|
115
187
|
emit(event);
|
|
116
188
|
};
|
|
189
|
+
/** Strip a known prefix from a string, returning the remainder unchanged. */
|
|
190
|
+
const insertedText = (value, prefix) => value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
|
191
|
+
/**
|
|
192
|
+
* Surface the compacted-context summary to both the TUI (via an event)
|
|
193
|
+
* and, when running with a direct stdout writer, as a rendered box.
|
|
194
|
+
* Token-count stats are always emitted for logs.
|
|
195
|
+
*/
|
|
196
|
+
const writeCompacted = (summary, beforeTokens, afterTokens) => {
|
|
197
|
+
emit({ type: "compacted", summary, beforeTokens, afterTokens });
|
|
198
|
+
if (writesDirectly) {
|
|
199
|
+
const header = chalk.dim(" \u2726 Compacted Context");
|
|
200
|
+
const footer = chalk.dim(` ~${beforeTokens.toLocaleString()} \u2192 ~${afterTokens.toLocaleString()} tokens`);
|
|
201
|
+
const body = summary ? renderMarkdown(summary) : "(empty summary)";
|
|
202
|
+
process.stdout.write(`${header}\n\n${body}\n${footer}\n`);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
117
205
|
// Points at the live message array so finishTurn can hand the full
|
|
118
206
|
// conversation back to the caller. Assigned once `messages` is built below;
|
|
119
207
|
// all later mutations are in-place so this reference stays current.
|
|
@@ -199,6 +287,15 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
199
287
|
if (buildLikeTurn && !activePlan && !informationalQuery) {
|
|
200
288
|
systemSections.push(buildWorkflowDirective());
|
|
201
289
|
}
|
|
290
|
+
// Pentest / security engagements need a different shape than a coding
|
|
291
|
+
// build: recon first, then a plan built from real findings, then
|
|
292
|
+
// incremental task additions as new attack surface appears. The
|
|
293
|
+
// directive is only injected before a plan exists; once a plan is in
|
|
294
|
+
// place (or being refined), the ACTIVE PLAN block already carries the
|
|
295
|
+
// current task state and recon-vs-active-tool guidance.
|
|
296
|
+
if (pentestLikeTurn && !activePlan && !informationalQuery) {
|
|
297
|
+
systemSections.push(pentestWorkflowDirective());
|
|
298
|
+
}
|
|
202
299
|
const fullSystemPrompt = systemSections.join("\n\n");
|
|
203
300
|
const userMessage = { role: "user", content: prompt };
|
|
204
301
|
if (options.images && options.images.length > 0) {
|
|
@@ -321,6 +418,9 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
321
418
|
},
|
|
322
419
|
};
|
|
323
420
|
async function executeSingleTool(rawCall, toolEventId, parentSignal) {
|
|
421
|
+
// Resolved once per call so the scratch-only exemption can compare the
|
|
422
|
+
// model-supplied paths against the canonical per-project scratch root.
|
|
423
|
+
const scratchDir = scratchDirFor(safeCwd());
|
|
324
424
|
let call = normalizeToolCall(rawCall);
|
|
325
425
|
if (call.name === "image.ocr" && !imageOcrEnabled) {
|
|
326
426
|
writeNotice("info", "skipped OCR because the original image is attached to the vision model", chalk.dim(" ℹ skipped OCR — inspecting the attached image directly\n"));
|
|
@@ -385,21 +485,10 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
385
485
|
scope: isScopeActive(scope) ? (scope.name ?? "(unnamed)") : "(none)",
|
|
386
486
|
});
|
|
387
487
|
const isMutatingAction = (decision.level === "confirm" || decision.level === "block") &&
|
|
388
|
-
!isPreApprovalAllowedTool(call.name)
|
|
488
|
+
!isPreApprovalAllowedTool(call.name) &&
|
|
489
|
+
!isScratchOnlyWrite(call, scratchDir);
|
|
389
490
|
if (isMutatingAction) {
|
|
390
|
-
if (!
|
|
391
|
-
const reason = `No active plan — ${call.name} is blocked. You must first create a plan using plan.create before executing mutating actions.`;
|
|
392
|
-
writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
|
|
393
|
-
const result = { ok: false, output: reason, exitCode: 1 };
|
|
394
|
-
return {
|
|
395
|
-
ok: false,
|
|
396
|
-
call,
|
|
397
|
-
result,
|
|
398
|
-
contextOutput: reason,
|
|
399
|
-
blockOrCancel: true,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
if (!session.planApproved.value) {
|
|
491
|
+
if (activePlan && !session.planApproved.value) {
|
|
403
492
|
const reason = `plan awaiting approval — ${call.name} is blocked until you /implement (or /discard)`;
|
|
404
493
|
writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
|
|
405
494
|
const result = { ok: false, output: reason, exitCode: 1 };
|
|
@@ -721,7 +810,7 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
721
810
|
// what is done, and what remains. The estimate is chars/4; the budget is
|
|
722
811
|
// deliberately conservative so we compact a little early rather than hit a
|
|
723
812
|
// provider context-window error mid-task.
|
|
724
|
-
const AUTO_COMPACT_TOKEN_BUDGET =
|
|
813
|
+
const AUTO_COMPACT_TOKEN_BUDGET = 120_000;
|
|
725
814
|
const AUTO_COMPACT_KEEP_RECENT = 12;
|
|
726
815
|
let lastCompactionMsgCount = 0;
|
|
727
816
|
const summarizeForCompaction = async (summaryPrompt) => {
|
|
@@ -773,6 +862,16 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
773
862
|
estimatedTokens: afterTokens,
|
|
774
863
|
reason,
|
|
775
864
|
});
|
|
865
|
+
// Extract the inserted compaction memory so we can surface the
|
|
866
|
+
// summary itself (not just token-count stats). The summary lives in
|
|
867
|
+
// the first system message whose content begins with
|
|
868
|
+
// COMPACTION_MEMORY_PREFIX.
|
|
869
|
+
const insertedSummary = messages.find((m) => m.role === "system" &&
|
|
870
|
+
m.content.startsWith(COMPACTION_MEMORY_PREFIX))?.content ?? "";
|
|
871
|
+
const summaryText = insertedSummary.startsWith(`${COMPACTION_MEMORY_PREFIX}\n\n`)
|
|
872
|
+
? insertedText(insertedSummary, `${COMPACTION_MEMORY_PREFIX}\n\n`)
|
|
873
|
+
: insertedText(insertedSummary, COMPACTION_MEMORY_PREFIX);
|
|
874
|
+
writeCompacted(summaryText, beforeTokens, afterTokens);
|
|
776
875
|
writeNotice("info", `context auto-compacted to fit the window (~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens)`, chalk.dim(` ℹ context auto-compacted (~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens)\n`));
|
|
777
876
|
}
|
|
778
877
|
catch (error) {
|
|
@@ -1136,9 +1235,10 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1136
1235
|
messages.push({
|
|
1137
1236
|
role: "user",
|
|
1138
1237
|
content: "Your previous tool call was cut off before it finished — the JSON was incomplete, so NOTHING ran. " +
|
|
1139
|
-
"
|
|
1140
|
-
"If it was fs.writeMany, split the FILE LIST into smaller batches
|
|
1141
|
-
"For one large file
|
|
1238
|
+
"Keep your reasoning SHORT and emit the ```tool block as early as possible. " +
|
|
1239
|
+
"If it was fs.writeMany, split the FILE LIST into smaller batches (3-5 files per call). " +
|
|
1240
|
+
"For one large file (e.g. a report), write it in sections: use fs.write for the first section, then shell.exec with 'cat >> file' or separate fs.write calls for remaining sections. " +
|
|
1241
|
+
"Do not shorten, truncate, or simplify file contents. Do NOT claim any file was written until a tool call actually succeeds.",
|
|
1142
1242
|
});
|
|
1143
1243
|
continue;
|
|
1144
1244
|
}
|
|
@@ -1428,6 +1528,10 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1428
1528
|
content: `Tool ${res.call.name} result (exit=${res.result.exitCode ?? 0}, ok=${res.result.ok}):\n${res.contextOutput}`,
|
|
1429
1529
|
});
|
|
1430
1530
|
productiveSteps += 1;
|
|
1531
|
+
// Reset retry counters — they track consecutive failures, not cumulative.
|
|
1532
|
+
truncatedToolRetries = 0;
|
|
1533
|
+
malformedFenceRetries = 0;
|
|
1534
|
+
bareToolJsonRetries = 0;
|
|
1431
1535
|
if (res.ok && res.call.name === "shell.start")
|
|
1432
1536
|
sawServerStart = true;
|
|
1433
1537
|
if (res.ok && res.call.name === "shell.tail")
|