fixo-cli 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fixo-cli might be problematic. Click here for more details.
- package/CHANGELOG.md +62 -0
- package/README.md +18 -14
- package/dist/agent/agent-client.d.ts +28 -6
- package/dist/agent/agent-client.d.ts.map +1 -1
- package/dist/agent/agent-client.js +118 -39
- package/dist/agent/agent-client.js.map +1 -1
- package/dist/agent/agent-pool.d.ts +55 -6
- package/dist/agent/agent-pool.d.ts.map +1 -1
- package/dist/agent/agent-pool.js +120 -20
- package/dist/agent/agent-pool.js.map +1 -1
- package/dist/agent/auto-verifier.d.ts +55 -0
- package/dist/agent/auto-verifier.d.ts.map +1 -0
- package/dist/agent/auto-verifier.js +50 -0
- package/dist/agent/auto-verifier.js.map +1 -0
- package/dist/agent/command-parser.d.ts +37 -0
- package/dist/agent/command-parser.d.ts.map +1 -1
- package/dist/agent/command-parser.js +473 -1
- package/dist/agent/command-parser.js.map +1 -1
- package/dist/agent/context-builder.d.ts +24 -0
- package/dist/agent/context-builder.d.ts.map +1 -0
- package/dist/agent/context-builder.js +197 -0
- package/dist/agent/context-builder.js.map +1 -0
- package/dist/agent/conversation.d.ts +32 -2
- package/dist/agent/conversation.d.ts.map +1 -1
- package/dist/agent/conversation.js +84 -9
- package/dist/agent/conversation.js.map +1 -1
- package/dist/agent/duration.d.ts +24 -0
- package/dist/agent/duration.d.ts.map +1 -0
- package/dist/agent/duration.js +42 -0
- package/dist/agent/duration.js.map +1 -0
- package/dist/agent/file-writing-rules.d.ts +19 -0
- package/dist/agent/file-writing-rules.d.ts.map +1 -0
- package/dist/agent/file-writing-rules.js +31 -0
- package/dist/agent/file-writing-rules.js.map +1 -0
- package/dist/agent/mcp-bridge.js +1 -1
- package/dist/agent/mcp-bridge.js.map +1 -1
- package/dist/agent/orchestrator.d.ts +45 -0
- package/dist/agent/orchestrator.d.ts.map +1 -1
- package/dist/agent/orchestrator.js +140 -3
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/parser-adapter.d.ts +17 -0
- package/dist/agent/parser-adapter.d.ts.map +1 -1
- package/dist/agent/parser-adapter.js +311 -7
- package/dist/agent/parser-adapter.js.map +1 -1
- package/dist/agent/predictive-gate.d.ts.map +1 -1
- package/dist/agent/predictive-gate.js +4 -1
- package/dist/agent/predictive-gate.js.map +1 -1
- package/dist/agent/provider-cooldown.d.ts.map +1 -1
- package/dist/agent/provider-cooldown.js +3 -2
- package/dist/agent/provider-cooldown.js.map +1 -1
- package/dist/agent/providers-manager.d.ts +5 -0
- package/dist/agent/providers-manager.d.ts.map +1 -1
- package/dist/agent/providers-manager.js +119 -8
- package/dist/agent/providers-manager.js.map +1 -1
- package/dist/agent/repo-map.d.ts +18 -1
- package/dist/agent/repo-map.d.ts.map +1 -1
- package/dist/agent/repo-map.js +144 -54
- package/dist/agent/repo-map.js.map +1 -1
- package/dist/agent/retry.js +1 -2
- package/dist/agent/retry.js.map +1 -1
- package/dist/agent/single-agent.d.ts +13 -0
- package/dist/agent/single-agent.d.ts.map +1 -1
- package/dist/agent/single-agent.js +225 -37
- package/dist/agent/single-agent.js.map +1 -1
- package/dist/agent/skills.d.ts.map +1 -1
- package/dist/agent/skills.js +2 -1
- package/dist/agent/skills.js.map +1 -1
- package/dist/agent/subagent.js +2 -2
- package/dist/agent/subagent.js.map +1 -1
- package/dist/agent/task-router.d.ts +46 -0
- package/dist/agent/task-router.d.ts.map +1 -0
- package/dist/agent/task-router.js +352 -0
- package/dist/agent/task-router.js.map +1 -0
- package/dist/agent/telemetry.d.ts +29 -1
- package/dist/agent/telemetry.d.ts.map +1 -1
- package/dist/agent/telemetry.js +29 -11
- package/dist/agent/telemetry.js.map +1 -1
- package/dist/agent/tool-definitions.d.ts +3 -0
- package/dist/agent/tool-definitions.d.ts.map +1 -0
- package/dist/agent/tool-definitions.js +519 -0
- package/dist/agent/tool-definitions.js.map +1 -0
- package/dist/agent/tool-executor.d.ts +6 -1
- package/dist/agent/tool-executor.d.ts.map +1 -1
- package/dist/agent/tool-executor.js +99 -553
- package/dist/agent/tool-executor.js.map +1 -1
- package/dist/agent/tools/command-tools.d.ts +6 -0
- package/dist/agent/tools/command-tools.d.ts.map +1 -0
- package/dist/agent/tools/command-tools.js +104 -0
- package/dist/agent/tools/command-tools.js.map +1 -0
- package/dist/agent/tools/file-tools.d.ts +15 -0
- package/dist/agent/tools/file-tools.d.ts.map +1 -0
- package/dist/agent/tools/file-tools.js +551 -0
- package/dist/agent/tools/file-tools.js.map +1 -0
- package/dist/agent/tools/todo-tools.d.ts +3 -0
- package/dist/agent/tools/todo-tools.d.ts.map +1 -0
- package/dist/agent/tools/todo-tools.js +70 -0
- package/dist/agent/tools/todo-tools.js.map +1 -0
- package/dist/agent/web-impl.d.ts.map +1 -1
- package/dist/agent/web-impl.js +45 -0
- package/dist/agent/web-impl.js.map +1 -1
- package/dist/agent/worker-agent.d.ts +3 -1
- package/dist/agent/worker-agent.d.ts.map +1 -1
- package/dist/agent/worker-agent.js +56 -16
- package/dist/agent/worker-agent.js.map +1 -1
- package/dist/config.d.ts +253 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +81 -1
- package/dist/config.js.map +1 -1
- package/dist/git/git-manager.d.ts +33 -2
- package/dist/git/git-manager.d.ts.map +1 -1
- package/dist/git/git-manager.js +111 -15
- package/dist/git/git-manager.js.map +1 -1
- package/dist/git/git-ops.d.ts.map +1 -1
- package/dist/git/git-ops.js +2 -1
- package/dist/git/git-ops.js.map +1 -1
- package/dist/index.js +89 -8
- package/dist/index.js.map +1 -1
- package/dist/lsp/lsp-manager.js +1 -1
- package/dist/lsp/lsp-manager.js.map +1 -1
- package/dist/model-outcomes.d.ts.map +1 -1
- package/dist/model-outcomes.js +2 -1
- package/dist/model-outcomes.js.map +1 -1
- package/dist/planner.d.ts +0 -9
- package/dist/planner.d.ts.map +1 -1
- package/dist/planner.js +0 -9
- package/dist/planner.js.map +1 -1
- package/dist/project-memory.d.ts +12 -1
- package/dist/project-memory.d.ts.map +1 -1
- package/dist/project-memory.js +8 -6
- package/dist/project-memory.js.map +1 -1
- package/dist/runtime/loop-mitigation.d.ts +119 -0
- package/dist/runtime/loop-mitigation.d.ts.map +1 -0
- package/dist/runtime/loop-mitigation.js +192 -0
- package/dist/runtime/loop-mitigation.js.map +1 -0
- package/dist/runtime/os-sandbox.d.ts +100 -0
- package/dist/runtime/os-sandbox.d.ts.map +1 -0
- package/dist/runtime/os-sandbox.js +246 -0
- package/dist/runtime/os-sandbox.js.map +1 -0
- package/dist/runtime/run-inventory.d.ts +17 -0
- package/dist/runtime/run-inventory.d.ts.map +1 -0
- package/dist/runtime/run-inventory.js +49 -0
- package/dist/runtime/run-inventory.js.map +1 -0
- package/dist/runtime/session-snapshots.d.ts +52 -2
- package/dist/runtime/session-snapshots.d.ts.map +1 -1
- package/dist/runtime/session-snapshots.js +76 -1
- package/dist/runtime/session-snapshots.js.map +1 -1
- package/dist/runtime/staging.d.ts.map +1 -1
- package/dist/runtime/staging.js +4 -1
- package/dist/runtime/staging.js.map +1 -1
- package/dist/runtime/task-session.d.ts +14 -0
- package/dist/runtime/task-session.d.ts.map +1 -1
- package/dist/runtime/task-session.js +26 -0
- package/dist/runtime/task-session.js.map +1 -1
- package/dist/setup-wizard.d.ts +11 -3
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +113 -15
- package/dist/setup-wizard.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/commands/context-commands.d.ts +7 -0
- package/dist/ui/commands/context-commands.d.ts.map +1 -0
- package/dist/ui/commands/context-commands.js +241 -0
- package/dist/ui/commands/context-commands.js.map +1 -0
- package/dist/ui/commands/index.d.ts +3 -0
- package/dist/ui/commands/index.d.ts.map +1 -0
- package/dist/ui/commands/index.js +46 -0
- package/dist/ui/commands/index.js.map +1 -0
- package/dist/ui/commands/info-commands.d.ts +15 -0
- package/dist/ui/commands/info-commands.d.ts.map +1 -0
- package/dist/ui/commands/info-commands.js +122 -0
- package/dist/ui/commands/info-commands.js.map +1 -0
- package/dist/ui/commands/model-commands.d.ts +5 -0
- package/dist/ui/commands/model-commands.d.ts.map +1 -0
- package/dist/ui/commands/model-commands.js +417 -0
- package/dist/ui/commands/model-commands.js.map +1 -0
- package/dist/ui/commands/session-commands.d.ts +5 -0
- package/dist/ui/commands/session-commands.d.ts.map +1 -0
- package/dist/ui/commands/session-commands.js +154 -0
- package/dist/ui/commands/session-commands.js.map +1 -0
- package/dist/ui/commands/task-commands.d.ts +8 -0
- package/dist/ui/commands/task-commands.d.ts.map +1 -0
- package/dist/ui/commands/task-commands.js +152 -0
- package/dist/ui/commands/task-commands.js.map +1 -0
- package/dist/ui/commands/types.d.ts +46 -0
- package/dist/ui/commands/types.d.ts.map +1 -0
- package/dist/ui/commands/types.js +2 -0
- package/dist/ui/commands/types.js.map +1 -0
- package/dist/ui/commands/workspace-commands.d.ts +8 -0
- package/dist/ui/commands/workspace-commands.d.ts.map +1 -0
- package/dist/ui/commands/workspace-commands.js +131 -0
- package/dist/ui/commands/workspace-commands.js.map +1 -0
- package/dist/ui/loading-animation.d.ts +24 -0
- package/dist/ui/loading-animation.d.ts.map +1 -0
- package/dist/ui/loading-animation.js +123 -0
- package/dist/ui/loading-animation.js.map +1 -0
- package/dist/ui/markdown-stream.js +2 -2
- package/dist/ui/markdown-stream.js.map +1 -1
- package/dist/ui/prompt.d.ts +7 -0
- package/dist/ui/prompt.d.ts.map +1 -1
- package/dist/ui/prompt.js +461 -1143
- package/dist/ui/prompt.js.map +1 -1
- package/dist/ui/render-primitives.d.ts +6 -0
- package/dist/ui/render-primitives.d.ts.map +1 -1
- package/dist/ui/render-primitives.js +30 -13
- package/dist/ui/render-primitives.js.map +1 -1
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +2 -0
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/session-header.d.ts +13 -0
- package/dist/ui/session-header.d.ts.map +1 -1
- package/dist/ui/session-header.js +6 -0
- package/dist/ui/session-header.js.map +1 -1
- package/package.json +22 -4
- package/scripts/check-vendor-wasm.js +55 -0
- package/vendor/tree-sitter-bash.wasm +0 -0
- package/vendor/tree-sitter-go.wasm +0 -0
- package/vendor/tree-sitter-javascript.wasm +0 -0
- package/vendor/tree-sitter-python.wasm +0 -0
- package/vendor/tree-sitter-rust.wasm +0 -0
- package/vendor/tree-sitter-tsx.wasm +0 -0
- package/vendor/tree-sitter-typescript.wasm +0 -0
- package/vendor/tree-sitter.wasm +0 -0
package/dist/agent/retry.js
CHANGED
|
@@ -36,7 +36,7 @@ export const DEFAULT_RETRYABLE_STATUS_CODES = new Set([
|
|
|
36
36
|
export function defaultIsRetryable(err) {
|
|
37
37
|
if (err instanceof Error) {
|
|
38
38
|
const name = err.name;
|
|
39
|
-
if (name === 'AbortError' ||
|
|
39
|
+
if (name === 'AbortError' || /abort/i.test(err.message)) {
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
42
|
if (name === 'HttpError' && 'status' in err) {
|
|
@@ -269,7 +269,6 @@ function emitRetryEvent(hook, event) {
|
|
|
269
269
|
catch (hookErr) {
|
|
270
270
|
// Telemetry must never break the retry chain.
|
|
271
271
|
const msg = hookErr instanceof Error ? hookErr.message : String(hookErr);
|
|
272
|
-
// eslint-disable-next-line no-console
|
|
273
272
|
console.warn(`[retry] onRetry hook threw: ${msg}`);
|
|
274
273
|
}
|
|
275
274
|
}
|
package/dist/agent/retry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/agent/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAyCH,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAC/C,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,KAAK;IAClB,UAAU,EAAE,MAAM;IAClB,MAAM,EAAE,MAAM;IACd,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI;CACxB,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,MAAM,8BAA8B,GAAwB,IAAI,GAAG,CAAC;IACzE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;CAC7B,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAY;IAC7C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,IAAI,KAAK,YAAY,IAAI,
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/agent/retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,19 @@ export declare const promptsWrapper: {
|
|
|
13
13
|
isCancel: typeof p.isCancel;
|
|
14
14
|
};
|
|
15
15
|
import type readline from 'readline';
|
|
16
|
+
/**
|
|
17
|
+
* Tools that mutate the workspace, the git tree, the network, or any
|
|
18
|
+
* external state. Exported so the budget logic and the permission
|
|
19
|
+
* prompt logic share one source of truth — keeping the
|
|
20
|
+
* "investigation vs mutation" classification aligned with what the
|
|
21
|
+
* user sees in the approval prompt.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MUTATING_TOOL_NAMES: ReadonlySet<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Returns true when the given tool name is a pure read / analysis
|
|
26
|
+
* operation (read_file, search_code, list_dir, extract_symbols, ...).
|
|
27
|
+
*/
|
|
28
|
+
export declare function isReadOnlyTool(name: string): boolean;
|
|
16
29
|
export declare function evaluateInputIntent(task: string): 'CHAT_ONLY' | 'MUTATION';
|
|
17
30
|
export declare class SingleAgent {
|
|
18
31
|
private client;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"single-agent.d.ts","sourceRoot":"","sources":["../../src/agent/single-agent.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAqC,MAAM,mBAAmB,CAAC;AACnF,OAAO,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"single-agent.d.ts","sourceRoot":"","sources":["../../src/agent/single-agent.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAqC,MAAM,mBAAmB,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAuB,MAAM,mBAAmB,CAAC;AAK7E,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyB7D,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,eAAO,MAAM,cAAc;;;;mBAgDgB,CAAC;kBACvB,CAAC,cAAc,CAAC;qBAIzB,CAAC;;;CAhDZ,CAAC;AACF,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAerC;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAclD,CAAC;AAEH;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAcD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAiC1E;AAoJD,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,0EAA0E;IAC1E,OAAO,CAAC,eAAe,CAAyB;IAChD;qDACiD;IACjD,OAAO,CAAC,qBAAqB,CAAS;gBAE1B,OAAO,UAAQ;IAY3B,2EAA2E;IAC3E,SAAS,IAAI,WAAW;IAIxB;uDACmD;IACnD,KAAK,IAAI,IAAI;IAKb;;8DAE0D;IAC1D,KAAK,IAAI,IAAI;IAKP,YAAY,CAChB,OAAO,EAAE,YAAY,EACrB,YAAY,EAAE,mBAAmB,EACjC,EAAE,CAAC,EAAE,QAAQ,CAAC,SAAS,GACtB,OAAO,CAAC,WAAW,CAAC;IAumBvB;;;;;;;;OAQG;YACW,aAAa;IA0C3B;;;;;;;OAOG;YACW,cAAc;YA6Dd,qBAAqB;IAwCnC;;;OAGG;YACW,mBAAmB;IA2BjC;;;;;;;;;;;;;;;;;OAiBG;IACG,oBAAoB,CACxB,YAAY,EAAE,mBAAmB,EACjC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAyCzE,sCAAsC;IAChC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;CAG/B"}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { AgentClient } from './agent-client.js';
|
|
2
|
-
import { ConversationManager } from './conversation.js';
|
|
2
|
+
import { ConversationManager, sanitizeUserContent } from './conversation.js';
|
|
3
3
|
import { getActiveTools, executeTool, classifyExecutionRole } from './tool-executor.js';
|
|
4
4
|
import { isTrivialQuery } from '../planner.js';
|
|
5
|
+
import { decideAutoVerify, classifyVerifyOutput, buildRepairMessage } from './auto-verifier.js';
|
|
5
6
|
import { buildRepoMap } from './repo-map.js';
|
|
6
|
-
import { loadConfig } from '../config.js';
|
|
7
|
+
import { loadConfig, getAgentLoopGuardConfig } from '../config.js';
|
|
7
8
|
import { recordTelemetry, telemetry } from './telemetry.js';
|
|
8
9
|
import { buildProjectInstructionsBlock, recordFixoMdLoad, } from '../context/fixo-md.js';
|
|
9
10
|
import { loadTodoList, summariseTodoList, } from '../context/todo.js';
|
|
10
11
|
import { C } from '../ui/colors.js';
|
|
11
12
|
import { MarkdownStreamRenderer, renderMarkdown } from '../ui/markdown-stream.js';
|
|
12
13
|
import { SemanticLoopDetector, SemanticLoopAbortedError, toSafetyAlertDirective, } from '../runtime/loop-trap.js';
|
|
14
|
+
import { LoopMitigationTracker, isReadTool, buildLoopBlockedReadResult, } from '../runtime/loop-mitigation.js';
|
|
13
15
|
import { dashboard } from '../ui/render.js';
|
|
14
|
-
import {
|
|
16
|
+
import { LoadingAnimation } from '../ui/loading-animation.js';
|
|
15
17
|
import * as p from '@clack/prompts';
|
|
16
18
|
export const promptsWrapper = {
|
|
17
19
|
select: p.select,
|
|
@@ -22,8 +24,38 @@ export const promptsWrapper = {
|
|
|
22
24
|
import { TaskSession } from '../runtime/task-session.js';
|
|
23
25
|
import { BackgroundAwareness } from './background-awareness.js';
|
|
24
26
|
import { FixoMdWatcher } from '../context/fixo-md-watcher.js';
|
|
27
|
+
import { FILE_WRITING_RULES_BLOCK } from './file-writing-rules.js';
|
|
25
28
|
/* ──────────────────────── Constants ──────────────────────── */
|
|
26
29
|
const MAX_TOOL_RESULT_LENGTH = 30_000;
|
|
30
|
+
/**
|
|
31
|
+
* Tools that mutate the workspace, the git tree, the network, or any
|
|
32
|
+
* external state. Exported so the budget logic and the permission
|
|
33
|
+
* prompt logic share one source of truth — keeping the
|
|
34
|
+
* "investigation vs mutation" classification aligned with what the
|
|
35
|
+
* user sees in the approval prompt.
|
|
36
|
+
*/
|
|
37
|
+
export const MUTATING_TOOL_NAMES = new Set([
|
|
38
|
+
'write_file',
|
|
39
|
+
'run_command',
|
|
40
|
+
'run_command_async',
|
|
41
|
+
'apply_patch',
|
|
42
|
+
'replace_range',
|
|
43
|
+
'insert_after',
|
|
44
|
+
'str_replace',
|
|
45
|
+
'rename_file',
|
|
46
|
+
'delete_file',
|
|
47
|
+
'create_branch',
|
|
48
|
+
'commit_changes',
|
|
49
|
+
'push_branch',
|
|
50
|
+
'create_pull_request',
|
|
51
|
+
]);
|
|
52
|
+
/**
|
|
53
|
+
* Returns true when the given tool name is a pure read / analysis
|
|
54
|
+
* operation (read_file, search_code, list_dir, extract_symbols, ...).
|
|
55
|
+
*/
|
|
56
|
+
export function isReadOnlyTool(name) {
|
|
57
|
+
return !MUTATING_TOOL_NAMES.has(name);
|
|
58
|
+
}
|
|
27
59
|
const colors = {
|
|
28
60
|
reset: C.RESET,
|
|
29
61
|
bold: C.BOLD,
|
|
@@ -104,10 +136,11 @@ function formatPermissionPrompt(name, args) {
|
|
|
104
136
|
*/
|
|
105
137
|
function buildUserContent(context) {
|
|
106
138
|
const attachments = context.pendingAttachments;
|
|
139
|
+
const sanitizedTask = sanitizeUserContent(context.task);
|
|
107
140
|
if (!attachments || attachments.length === 0) {
|
|
108
|
-
return
|
|
141
|
+
return sanitizedTask;
|
|
109
142
|
}
|
|
110
|
-
const blocks = [{ type: 'text', text:
|
|
143
|
+
const blocks = [{ type: 'text', text: sanitizedTask }];
|
|
111
144
|
for (const a of attachments)
|
|
112
145
|
blocks.push(a);
|
|
113
146
|
return blocks;
|
|
@@ -115,7 +148,7 @@ function buildUserContent(context) {
|
|
|
115
148
|
function buildSystemPrompt(repoMap, context, enableTools = true) {
|
|
116
149
|
const parts = [];
|
|
117
150
|
if (enableTools) {
|
|
118
|
-
parts.push(`You are FixO CLI, an autonomous AI coding agent. You help developers by reading, writing, and modifying code files in their workspace.`, ``, `## Capabilities`, `You have access to these tools:`, `- **read_file(path)** — Read a file's contents`, `- **write_file(path, content)** — Create or overwrite a file`, `- **run_command(command)** — Execute a shell command (npm test, git status, etc.)`, `- **search_code(query)** — Search for patterns in the codebase`, `- **list_dir(path)** — List directory contents`, ``, `## Guidelines`, `1. ALWAYS read existing files before modifying them to understand current code.`, `2. For new files, write complete contents — never use placeholders like "// ... rest of the file". For edits to existing files, follow the Editing Discipline below.`, `3. After making changes, run the verification command if one is configured.`, `4. Keep your text responses concise. Focus on what you did and why.`, `5. If the task is ambiguous, ask a clarifying question instead of guessing.`, `6. Preserve existing code comments and formatting unless asked to change them.`, ``, `## Editing Discipline`, `Pick the narrowest tool that fits the change. Rewriting a file you only need to tweak burns tokens, defeats the LSP pre-save granularity, and risks clobbering concurrent edits.`, `- **Single-region edit on an existing file** (one symbol, one block, one line) → use \`str_replace\`. It is surgical and atomic. By default it errors when the snippet is non-unique — narrow the snippet, don't disable the check.`, `- **Multi-region or hunked edit on an existing file** (several non-adjacent changes, or a diff you already have) → use \`apply_patch\` with a unified diff. One tool call, all hunks atomic.`, `- **New file** OR **full rewrite** where the prior content is genuinely irrelevant → use \`write_file\`. This is the only sanctioned use of \`write_file\` on an existing path.`, `Never use \`write_file\` to "edit" an existing file by rewriting it whole. If the diff is small enough to describe, it is small enough for \`str_replace\` or \`apply_patch
|
|
151
|
+
parts.push(`You are FixO CLI, an autonomous AI coding agent. You help developers by reading, writing, and modifying code files in their workspace.`, ``, `## Capabilities`, `You have access to these tools:`, `- **read_file(path)** — Read a file's contents`, `- **write_file(path, content)** — Create or overwrite a file`, `- **run_command(command)** — Execute a shell command (npm test, git status, etc.)`, `- **search_code(query)** — Search for patterns in the codebase`, `- **list_dir(path)** — List directory contents`, ``, `## Guidelines`, `1. ALWAYS read existing files before modifying them to understand current code.`, `2. For new files, write complete contents — never use placeholders like "// ... rest of the file". For edits to existing files, follow the Editing Discipline below.`, `3. After making changes, run the verification command if one is configured.`, `4. Keep your text responses concise. Focus on what you did and why.`, `5. If the task is ambiguous, ask a clarifying question instead of guessing.`, `6. Preserve existing code comments and formatting unless asked to change them.`, ``, `## Editing Discipline`, `Pick the narrowest tool that fits the change. Rewriting a file you only need to tweak burns tokens, defeats the LSP pre-save granularity, and risks clobbering concurrent edits.`, `- **Single-region edit on an existing file** (one symbol, one block, one line) → use \`str_replace\`. It is surgical and atomic. By default it errors when the snippet is non-unique — narrow the snippet, don't disable the check.`, `- **Multi-region or hunked edit on an existing file** (several non-adjacent changes, or a diff you already have) → use \`apply_patch\` with a unified diff. One tool call, all hunks atomic.`, `- **New file** OR **full rewrite** where the prior content is genuinely irrelevant → use \`write_file\`. This is the only sanctioned use of \`write_file\` on an existing path.`, `Never use \`write_file\` to "edit" an existing file by rewriting it whole. If the diff is small enough to describe, it is small enough for \`str_replace\` or \`apply_patch\`.`, FILE_WRITING_RULES_BLOCK);
|
|
119
152
|
}
|
|
120
153
|
else {
|
|
121
154
|
parts.push(`You are FixO CLI, a friendly AI coding assistant. You help developers by answering questions, explaining code, and discussing software engineering concepts.`, ``, `## Guidelines`, `1. Provide clear, detailed, and accurate explanations.`, `2. Keep your responses focused and helpful.`, `3. If you refer to code structure, do so conceptually as you currently do not have active tool access to modify code.`);
|
|
@@ -164,7 +197,7 @@ export class SingleAgent {
|
|
|
164
197
|
markedForCancellation = false;
|
|
165
198
|
constructor(verbose = false) {
|
|
166
199
|
const config = loadConfig();
|
|
167
|
-
this.client = new AgentClient(config.freellmapi_api_key || '', config.apiUrl, verbose);
|
|
200
|
+
this.client = new AgentClient(config.freellmapi_api_key || '', config.apiUrl, verbose, config.provider_mode, config.preferences.modelRouting);
|
|
168
201
|
this.verbose = verbose;
|
|
169
202
|
}
|
|
170
203
|
/** Expose the underlying client for direct API calls (e.g. compaction). */
|
|
@@ -222,8 +255,28 @@ export class SingleAgent {
|
|
|
222
255
|
return await this.executePureChatStream(context.task, conversation, context);
|
|
223
256
|
}
|
|
224
257
|
// ──── Complex task → tool loop ────
|
|
225
|
-
const repoMap = buildRepoMap(context.cwd
|
|
226
|
-
|
|
258
|
+
const repoMap = await buildRepoMap(context.cwd, {
|
|
259
|
+
maxDepth: loadConfig().preferences.repoMap?.maxDepth,
|
|
260
|
+
maxFiles: loadConfig().preferences.repoMap?.maxFiles,
|
|
261
|
+
});
|
|
262
|
+
// Phase 3.2 — auto-collect cross-file references for the files
|
|
263
|
+
// this run is likely to mutate (user-pinned files via /select).
|
|
264
|
+
// The block is empty if no LSP server is on $PATH, so adding it
|
|
265
|
+
// unconditionally is safe on machines without an LSP installed.
|
|
266
|
+
let referencesBlock = '';
|
|
267
|
+
if (context.selectedFiles && context.selectedFiles.length > 0) {
|
|
268
|
+
try {
|
|
269
|
+
const { gatherReferencesForTargets } = await import('./context-builder.js');
|
|
270
|
+
const { getLspManager } = await import('./tool-executor.js');
|
|
271
|
+
referencesBlock = await gatherReferencesForTargets(context.cwd, context.selectedFiles.map((f) => ({ file: f })), () => getLspManager(context.cwd));
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// safe: any failure in the LSP path must not block the run
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const systemPrompt = referencesBlock
|
|
278
|
+
? `${buildSystemPrompt(repoMap, context)}\n\n${referencesBlock}`
|
|
279
|
+
: buildSystemPrompt(repoMap, context);
|
|
227
280
|
// Auto-compact before building messages if context is near limit
|
|
228
281
|
await this.autoCompactIfNeeded(conversation, systemPrompt, context.task, context.model);
|
|
229
282
|
// Pillar 4 — proactive budget enforcement
|
|
@@ -284,6 +337,27 @@ export class SingleAgent {
|
|
|
284
337
|
const budget = safety.toolCalls;
|
|
285
338
|
let toolCallLimit = Math.max(1, budget.softLimit);
|
|
286
339
|
const toolCallHardLimit = Math.max(toolCallLimit, budget.hardLimit);
|
|
340
|
+
/**
|
|
341
|
+
* Investigation budget — applies when the agent has only invoked
|
|
342
|
+
* read-only tools so far. Audits, reviews, and "find vulnerabilities"
|
|
343
|
+
* tasks routinely need to read 80+ files before answering; if we
|
|
344
|
+
* cap them at `hardLimit` they force the user to type `continue`
|
|
345
|
+
* mid-investigation (the failure mode seen in Test 2 of the log).
|
|
346
|
+
* Snaps back to `hardLimit` the moment a mutation fires.
|
|
347
|
+
*/
|
|
348
|
+
const investigationMultiplier = Math.max(1, budget.investigationMultiplier ?? 1);
|
|
349
|
+
const toolCallInvestigationCeiling = Math.max(toolCallHardLimit, Math.floor(toolCallHardLimit * investigationMultiplier));
|
|
350
|
+
let anyMutationSeen = false;
|
|
351
|
+
let investigationModeAnnounced = false;
|
|
352
|
+
// Phase 2.2 — automatic post-edit verifier. After the tool loop
|
|
353
|
+
// reports "no more tool calls", re-check the project's tests
|
|
354
|
+
// (when configured) and — on failure — push a repair-request
|
|
355
|
+
// back into the same conversation up to `autoVerifyMaxRepairs`
|
|
356
|
+
// times before returning. Disabled outside BUILD mode and when
|
|
357
|
+
// no file-mutating tool ran. Gate + classification + message
|
|
358
|
+
// shape live in ./auto-verifier (testable in isolation).
|
|
359
|
+
let autoVerifyRepairsUsed = 0;
|
|
360
|
+
const autoVerifyMaxRepairs = Math.max(0, safety.autoVerifyMaxRepairs ?? 1);
|
|
287
361
|
// Pillar 2 — semantic loop detector. Tracks per-file frequency so
|
|
288
362
|
// an LLM which varies its search arguments but keeps hammering
|
|
289
363
|
// the same file still trips. The composite LoopTrapDetector is
|
|
@@ -291,6 +365,15 @@ export class SingleAgent {
|
|
|
291
365
|
// detectors run in parallel; the semantic one covers the most
|
|
292
366
|
// common accidental "stare at one file" failure mode.
|
|
293
367
|
const semanticLoopDetector = new SemanticLoopDetector(safety.semanticLoopTrap);
|
|
368
|
+
// Phase 1b — opt-in sliding-window block accounting. Default is OFF
|
|
369
|
+
// (legacy session-lifetime lockout) until Phase 7 flips the default
|
|
370
|
+
// after soak. Sliding mode prevents the "immortal file" deadlock
|
|
371
|
+
// observed in the June 22, 2026 log session.
|
|
372
|
+
const loopGuardConfig = getAgentLoopGuardConfig();
|
|
373
|
+
const loopMitigation = new LoopMitigationTracker({
|
|
374
|
+
useSlidingWindow: loopGuardConfig.useSlidingWindow,
|
|
375
|
+
blockWindowTurns: loopGuardConfig.blockWindowTurns,
|
|
376
|
+
});
|
|
294
377
|
let pendingSafetyDirective = null;
|
|
295
378
|
// Pillar 5 — per-turn background-job awareness. The LLM
|
|
296
379
|
// routinely forgets jobs it spawned earlier; we counter that by
|
|
@@ -304,21 +387,36 @@ export class SingleAgent {
|
|
|
304
387
|
// mid-run create/update/delete surfaces as a [Project
|
|
305
388
|
// Instructions] directive on the next chat().
|
|
306
389
|
const fixoMdWatcher = new FixoMdWatcher(context.cwd);
|
|
307
|
-
const indicator = new
|
|
390
|
+
const indicator = new LoadingAnimation();
|
|
308
391
|
indicator.start();
|
|
392
|
+
let lastUsage = null;
|
|
309
393
|
try {
|
|
310
394
|
while (toolCallCount < toolCallLimit) {
|
|
311
395
|
// Auto-extend the budget when the agent is at the soft limit
|
|
312
396
|
// but the semantic loop detector is quiet — i.e. the work is
|
|
313
|
-
// still progressing, not thrashing. Capped at hardLimit
|
|
397
|
+
// still progressing, not thrashing. Capped at hardLimit for
|
|
398
|
+
// mutating runs; lifted to the investigation ceiling
|
|
399
|
+
// (hardLimit * investigationMultiplier) while only read-only
|
|
400
|
+
// tools have fired.
|
|
314
401
|
if (budget.autoExtend &&
|
|
315
402
|
toolCallCount + 1 >= toolCallLimit &&
|
|
316
|
-
toolCallLimit < toolCallHardLimit &&
|
|
317
403
|
pendingSafetyDirective === null) {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
404
|
+
const ceiling = !anyMutationSeen && investigationMultiplier > 1
|
|
405
|
+
? toolCallInvestigationCeiling
|
|
406
|
+
: toolCallHardLimit;
|
|
407
|
+
if (toolCallLimit < ceiling) {
|
|
408
|
+
const previous = toolCallLimit;
|
|
409
|
+
toolCallLimit = Math.min(ceiling, toolCallLimit * 2);
|
|
410
|
+
if (toolCallLimit > previous) {
|
|
411
|
+
const investigation = !anyMutationSeen && investigationMultiplier > 1;
|
|
412
|
+
if (investigation && !investigationModeAnnounced) {
|
|
413
|
+
console.log(`${colors.dim}ⓘ Investigation mode — extended budget to ${toolCallLimit} (read-only tools only).${colors.reset}`);
|
|
414
|
+
investigationModeAnnounced = true;
|
|
415
|
+
}
|
|
416
|
+
else if (!investigation) {
|
|
417
|
+
console.log(`${colors.dim}↳ tool-call budget extended ${previous} → ${toolCallLimit} (no loop detected)${colors.reset}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
322
420
|
}
|
|
323
421
|
}
|
|
324
422
|
// Background-job awareness: surface newly-finished and
|
|
@@ -343,8 +441,8 @@ export class SingleAgent {
|
|
|
343
441
|
injectSafetyDirective(fixoDirective);
|
|
344
442
|
}
|
|
345
443
|
}
|
|
346
|
-
|
|
347
|
-
|
|
444
|
+
indicator.setPhase({ id: 'reasoning', label: 'Reasoning…', detail: 'Analyzing context paths', icon: '⚡' });
|
|
445
|
+
indicator.setTurn(toolCallCount + 1);
|
|
348
446
|
dashboard.emit({
|
|
349
447
|
type: 'turn-start',
|
|
350
448
|
turnIndex: toolCallCount + 1,
|
|
@@ -366,7 +464,7 @@ export class SingleAgent {
|
|
|
366
464
|
catch (err) {
|
|
367
465
|
// Handle context overflow — auto-compact and retry once
|
|
368
466
|
if (ConversationManager.isContextOverflowError(err)) {
|
|
369
|
-
|
|
467
|
+
indicator.setPhase({ id: 'reasoning', label: 'Context full…', detail: 'Auto-compacting history', icon: '🔄' });
|
|
370
468
|
console.log(`${colors.yellow}🔄 Context window full — auto-compacting...${colors.reset}`);
|
|
371
469
|
const compacted = await conversation.compact(this.client, context.model);
|
|
372
470
|
if (compacted) {
|
|
@@ -381,7 +479,6 @@ export class SingleAgent {
|
|
|
381
479
|
throw err;
|
|
382
480
|
}
|
|
383
481
|
finally {
|
|
384
|
-
spinner.stop('🤖 Thought completed');
|
|
385
482
|
dashboard.emit({
|
|
386
483
|
type: 'status',
|
|
387
484
|
message: `Turn ${toolCallCount + 1} complete`,
|
|
@@ -390,15 +487,47 @@ export class SingleAgent {
|
|
|
390
487
|
totalUsage.prompt_tokens += result.usage.prompt_tokens;
|
|
391
488
|
totalUsage.completion_tokens += result.usage.completion_tokens;
|
|
392
489
|
totalUsage.total_tokens += result.usage.total_tokens;
|
|
393
|
-
|
|
490
|
+
lastUsage = result.usage;
|
|
491
|
+
// No tool calls → potentially run the auto-verifier, then
|
|
492
|
+
// either continue the loop (one repair pass) or return.
|
|
394
493
|
if (!result.tool_calls || result.tool_calls.length === 0) {
|
|
395
494
|
const response = result.content ?? '';
|
|
396
495
|
// Print the response (already received in non-streaming mode)
|
|
397
496
|
if (response) {
|
|
398
497
|
renderMarkdown(response);
|
|
399
498
|
}
|
|
499
|
+
// Phase 2.2 — automatic verifier.
|
|
500
|
+
const verifyGate = decideAutoVerify({
|
|
501
|
+
safety,
|
|
502
|
+
context,
|
|
503
|
+
modifiedFilesCount: modifiedFiles.length,
|
|
504
|
+
repairsUsed: autoVerifyRepairsUsed,
|
|
505
|
+
});
|
|
506
|
+
if (verifyGate.run) {
|
|
507
|
+
const { runProjectTests } = await import('../test-runner.js');
|
|
508
|
+
const verifyOutput = runProjectTests(context.cwd);
|
|
509
|
+
const outcome = classifyVerifyOutput(verifyOutput);
|
|
510
|
+
if (outcome === 'failing') {
|
|
511
|
+
autoVerifyRepairsUsed += 1;
|
|
512
|
+
console.log(`\n${colors.yellow}🔍 [Auto-Verify] Verification failed (repair attempt ${autoVerifyRepairsUsed}/${autoVerifyMaxRepairs}). Asking the model to fix...${colors.reset}`);
|
|
513
|
+
if (this.verbose) {
|
|
514
|
+
console.log(`${colors.dim}${verifyOutput}${colors.reset}\n`);
|
|
515
|
+
}
|
|
516
|
+
messages.push({ role: 'assistant', content: response });
|
|
517
|
+
messages.push({ role: 'user', content: buildRepairMessage(verifyOutput) });
|
|
518
|
+
// Counts toward tool budget so pathological repairs
|
|
519
|
+
// don't extend the run indefinitely.
|
|
520
|
+
toolCallCount += 1;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
// outcome === 'passing' or 'no-command' → fall through
|
|
524
|
+
// to the success return below.
|
|
525
|
+
}
|
|
400
526
|
indicator.stop();
|
|
401
527
|
conversation.addTurn(context.task, response);
|
|
528
|
+
if (result.usage && result.usage.total_tokens) {
|
|
529
|
+
conversation.setProviderReportedTokens(result.usage.total_tokens);
|
|
530
|
+
}
|
|
402
531
|
taskSession.finish('success', response);
|
|
403
532
|
return {
|
|
404
533
|
success: true,
|
|
@@ -439,6 +568,13 @@ export class SingleAgent {
|
|
|
439
568
|
pendingSafetyDirective = toSafetyAlertDirective(verdict);
|
|
440
569
|
console.log(`${colors.yellow}⚠ Semantic loop warning: ${verdict.target} ` +
|
|
441
570
|
`accessed ${verdict.count}× in the last ${verdict.windowSize} turns.${colors.reset}`);
|
|
571
|
+
const nowBlocking = loopMitigation.recordWarn(verdict.target, toolCallCount);
|
|
572
|
+
if (nowBlocking) {
|
|
573
|
+
const blockMsg = loopGuardConfig.useSlidingWindow
|
|
574
|
+
? `Further reads of ${verdict.target} will be rejected for the next ${loopGuardConfig.blockWindowTurns} turns — agent will be forced to pivot.`
|
|
575
|
+
: `Further reads of ${verdict.target} will be rejected this session — agent will be forced to pivot.`;
|
|
576
|
+
console.log(`${colors.yellow}⚠ ${blockMsg}${colors.reset}`);
|
|
577
|
+
}
|
|
442
578
|
}
|
|
443
579
|
else if (verdict.state === 'hard-abort') {
|
|
444
580
|
// Rollback any staged writes from this run before
|
|
@@ -462,6 +598,41 @@ export class SingleAgent {
|
|
|
462
598
|
injectSafetyDirective(pendingSafetyDirective);
|
|
463
599
|
pendingSafetyDirective = null;
|
|
464
600
|
}
|
|
601
|
+
// Active loop mitigation: if the model is trying to read
|
|
602
|
+
// a target the loop-trap has already warned on N times,
|
|
603
|
+
// short-circuit with a tool-error result instead of letting
|
|
604
|
+
// the LLM stare at the same file again. The mitigation
|
|
605
|
+
// tracker is per-session, so a future user task can re-read
|
|
606
|
+
// the same file freely.
|
|
607
|
+
if (isReadTool(toolCall.function.name) &&
|
|
608
|
+
typeof parsedArgs.path === 'string' &&
|
|
609
|
+
loopMitigation.isBlocked(parsedArgs.path, toolCallCount)) {
|
|
610
|
+
const warns = loopMitigation.warnsFor(parsedArgs.path, toolCallCount);
|
|
611
|
+
const blockedResult = buildLoopBlockedReadResult(parsedArgs.path, warns);
|
|
612
|
+
console.log(`${colors.yellow}⚠ Loop-blocked read intercepted: ${parsedArgs.path}${colors.reset}`);
|
|
613
|
+
messages.push({
|
|
614
|
+
role: 'tool',
|
|
615
|
+
tool_call_id: toolCall.id,
|
|
616
|
+
content: blockedResult,
|
|
617
|
+
});
|
|
618
|
+
toolCallCount++;
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
// Phase 1b — escape valve for the loop-mitigation deadlock.
|
|
622
|
+
// If the model is now trying to MUTATE a loop-blocked file
|
|
623
|
+
// (write, rename, delete, patch), the prior canMutate check
|
|
624
|
+
// would refuse it because the read was never satisfied. We
|
|
625
|
+
// register a forced read hash on the session so the next
|
|
626
|
+
// canMutate call succeeds — preserving the staleness-check
|
|
627
|
+
// intent without trapping the file as "immortal". Clearing
|
|
628
|
+
// the loop block as well prevents the lockout from carrying
|
|
629
|
+
// over after the pivot has already happened.
|
|
630
|
+
if (MUTATING_TOOL_NAMES.has(toolCall.function.name) &&
|
|
631
|
+
typeof parsedArgs.path === 'string' &&
|
|
632
|
+
loopMitigation.isBlocked(parsedArgs.path, toolCallCount)) {
|
|
633
|
+
taskSession.noteReadForMutation(parsedArgs.path);
|
|
634
|
+
loopMitigation.reset(parsedArgs.path);
|
|
635
|
+
}
|
|
465
636
|
const allowed = await this.askPermission(toolCall.function.name, parsedArgs, rl, context.yes);
|
|
466
637
|
let event;
|
|
467
638
|
if (!allowed) {
|
|
@@ -482,6 +653,19 @@ export class SingleAgent {
|
|
|
482
653
|
}
|
|
483
654
|
else {
|
|
484
655
|
const toolStart = Date.now();
|
|
656
|
+
// Set dynamic phase based on tool kind
|
|
657
|
+
if (isReadTool(toolCall.function.name)) {
|
|
658
|
+
indicator.setPhase({ id: 'reading', label: 'Reading codebase…', detail: parsedArgs.path || parsedArgs.from || '', icon: '✦' });
|
|
659
|
+
}
|
|
660
|
+
else if (toolCall.function.name === 'run_command' || toolCall.function.name === 'run_command_async') {
|
|
661
|
+
indicator.setPhase({ id: 'executing', label: 'Running command…', detail: parsedArgs.command || '', icon: '$' });
|
|
662
|
+
}
|
|
663
|
+
else if (toolCall.function.name === 'search_code' || toolCall.function.name === 'search_symbols') {
|
|
664
|
+
indicator.setPhase({ id: 'searching', label: 'Searching…', detail: parsedArgs.query || '', icon: '⌕' });
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
indicator.setPhase({ id: 'writing', label: 'Writing code…', detail: parsedArgs.path || parsedArgs.file || '', icon: '✎' });
|
|
668
|
+
}
|
|
485
669
|
dashboard.emit({
|
|
486
670
|
type: 'tool-start',
|
|
487
671
|
tool: toolCall.function.name,
|
|
@@ -507,7 +691,18 @@ export class SingleAgent {
|
|
|
507
691
|
modifiedFiles.push(event.affectedPath);
|
|
508
692
|
}
|
|
509
693
|
}
|
|
510
|
-
|
|
694
|
+
// Investigation-budget gate: any mutating tool snaps the
|
|
695
|
+
// ceiling back to hardLimit on the next iteration. We check
|
|
696
|
+
// the tool name (not just event.isWrite) because run_command
|
|
697
|
+
// is mutating-by-default even when it doesn't touch a file.
|
|
698
|
+
if (!anyMutationSeen &&
|
|
699
|
+
(event.isWrite || MUTATING_TOOL_NAMES.has(toolCall.function.name))) {
|
|
700
|
+
anyMutationSeen = true;
|
|
701
|
+
if (toolCallLimit > toolCallHardLimit) {
|
|
702
|
+
toolCallLimit = toolCallHardLimit;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
let toolResult = sanitizeUserContent(event.result);
|
|
511
706
|
if (toolResult.length > MAX_TOOL_RESULT_LENGTH) {
|
|
512
707
|
toolResult =
|
|
513
708
|
toolResult.slice(0, MAX_TOOL_RESULT_LENGTH) +
|
|
@@ -524,6 +719,9 @@ export class SingleAgent {
|
|
|
524
719
|
indicator.stop();
|
|
525
720
|
console.log(`${colors.yellow}⚠ Tool call limit reached (${toolCallLimit}).${colors.reset}`);
|
|
526
721
|
conversation.addTurn(context.task, `Task processed with ${toolCallCount} tool calls.`);
|
|
722
|
+
if (lastUsage && lastUsage.total_tokens) {
|
|
723
|
+
conversation.setProviderReportedTokens(lastUsage.total_tokens);
|
|
724
|
+
}
|
|
527
725
|
const limitResponse = `Completed with ${toolCallCount} tool calls (limit reached).`;
|
|
528
726
|
taskSession.finish('success', limitResponse);
|
|
529
727
|
return {
|
|
@@ -553,20 +751,7 @@ export class SingleAgent {
|
|
|
553
751
|
* are auto-allowed.
|
|
554
752
|
*/
|
|
555
753
|
async askPermission(name, args, rl, allowWithoutPrompt) {
|
|
556
|
-
|
|
557
|
-
'write_file',
|
|
558
|
-
'run_command',
|
|
559
|
-
'apply_patch',
|
|
560
|
-
'replace_range',
|
|
561
|
-
'insert_after',
|
|
562
|
-
'rename_file',
|
|
563
|
-
'delete_file',
|
|
564
|
-
'create_branch',
|
|
565
|
-
'commit_changes',
|
|
566
|
-
'push_branch',
|
|
567
|
-
'create_pull_request',
|
|
568
|
-
]);
|
|
569
|
-
if (!MUTATING_TOOLS.has(name)) {
|
|
754
|
+
if (!MUTATING_TOOL_NAMES.has(name)) {
|
|
570
755
|
return true;
|
|
571
756
|
}
|
|
572
757
|
if (allowWithoutPrompt || this.allowAll) {
|
|
@@ -660,7 +845,10 @@ export class SingleAgent {
|
|
|
660
845
|
async executePureChatStream(task, conversation, context) {
|
|
661
846
|
const startTime = Date.now();
|
|
662
847
|
const totalUsage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
663
|
-
const repoMap = buildRepoMap(context.cwd
|
|
848
|
+
const repoMap = await buildRepoMap(context.cwd, {
|
|
849
|
+
maxDepth: loadConfig().preferences.repoMap?.maxDepth,
|
|
850
|
+
maxFiles: loadConfig().preferences.repoMap?.maxFiles,
|
|
851
|
+
});
|
|
664
852
|
const systemPrompt = buildSystemPrompt(repoMap, context, false);
|
|
665
853
|
// Auto-compact before chat if context is near limit
|
|
666
854
|
await this.autoCompactIfNeeded(conversation, systemPrompt, task, context.model);
|