MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / AgentLoop

Class AgentLoop

src/agent/loop.ts:96–2671  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

94 */
95 maxIterationsOverride?: number;
96 /** Per-session reasoning effort (set via /effort). undefined = model default. */
97 reasoningEffort?: 'low' | 'medium' | 'high';
98}
99
100/**
101 * Synthesize tool-result messages for tool_calls that were NEVER executed — e.g. a
102 * loop detector (read-loop / stuck-loop / error-loop) fired and skipped the batch with
103 * `continue`. The assistant message carrying those tool_calls was already appended to
104 * history, so without a matching tool result for each id the conversation is malformed:
105 * OpenAI-format providers (kimi, DeepSeek, LM Studio, …) reject the next request with
106 * "an assistant message with 'tool_calls' must be followed by tool messages responding
107 * to each 'tool_call_id'". (Anthropic tolerates it, which is why it hid until an
108 * OpenAI-compatible model was used.) Emitting a short skip-marker per id keeps the
109 * assistant→tool invariant intact so the run can continue.
110 */
111function skippedToolResults(toolCalls: ToolCall[], reason: string): Message[] {
112 return toolCalls.map(tc => ({
113 role: 'tool' as const,
114 tool_call_id: tc.id,
115 name: tc.function.name,
116 content: `[skipped] ${reason} — this tool call was not executed; do not wait on its result.`,
117 }));
118}
119
120export class AgentLoop {
121 private router: ModelRouter;
122 private registry: ToolRegistry;
123 private permissions: PermissionEngine;
124 private config: QodexConfig;
125 private cwd: string;
126 /** Working root for tool path-resolution when the user attaches a directory. Sticky across
127 * turns in a session until a new directory is attached. Falls back to `cwd` when unset. */
128 private effectiveCwd?: string;
129 /** Auto-snapshot service (only set when safety.autoSnapshot is enabled). */
130 private snapshotService: SnapshotService | undefined;
131 /** Active git sandbox for the current run (set by runSandboxed), exposed to tools. */
132 private activeSandbox: GitSandbox | null = null;
133 /** Tracks current turn — used for snapshot retention. */
134 private currentTurn = 0;
135 /**
136 * Mid-task steering queue. The UI pushes `/btw …` notes here while a run is in
137 * flight; the run loop drains them at the top of each iteration and injects them
138 * into the conversation so the model can adjust course without being stopped.
139 * Single-threaded JS event loop ⇒ no lock needed.
140 */
141 private steerQueue: string[] = [];
142 /** Session ledger of files the model has demonstrably read (read-before-write gate). */
143 private readLedger = new ReadLedger();
144 /** Skills already auto-injected this session — never inject the same one twice. */
145 private autoInjectedSkills = new Set<string>();
146 /** Monotonic union of tool names shipped this session. The relevance gate only ever
147 * ADDS to this — never drops — so the tools block stays a byte-stable cache prefix
148 * across turns (a sliding per-turn set would flip and invalidate the prompt cache). */
149 private sessionToolNames = new Set<string>();
150 /** Where the active model is actually served from ('ollama'/'lmstudio'/'anthropic'/…) and
151 * its LIVE context window — set each iteration, forwarded on budget_update for the UI. */
152 private lastModelSource = '';
153 private lastEffectiveCtxWindow = 0;

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected