@vetala/vetala 0.5.6 → 0.6.1
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 +13 -12
- package/dist/src/agent.d.ts +17 -0
- package/dist/src/agent.js +315 -6
- package/dist/src/agent.js.map +1 -1
- package/dist/src/app-meta.d.ts +1 -1
- package/dist/src/app-meta.js +1 -1
- package/dist/src/deliberation.d.ts +22 -0
- package/dist/src/deliberation.js +168 -3
- package/dist/src/deliberation.js.map +1 -1
- package/dist/src/ipc-ui.d.ts +2 -0
- package/dist/src/ipc-ui.js +7 -0
- package/dist/src/ipc-ui.js.map +1 -1
- package/dist/src/skills/runtime.js +148 -4
- package/dist/src/skills/runtime.js.map +1 -1
- package/dist/src/terminal-ui.d.ts +3 -0
- package/dist/src/terminal-ui.js +28 -0
- package/dist/src/terminal-ui.js.map +1 -1
- package/dist/src/tools/advanced.js +80 -1
- package/dist/src/tools/advanced.js.map +1 -1
- package/dist/src/tools/filesystem.js +90 -14
- package/dist/src/tools/filesystem.js.map +1 -1
- package/dist/src/tools/lsp.js +198 -49
- package/dist/src/tools/lsp.js.map +1 -1
- package/dist/src/tools/shell.js +114 -3
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/skill.js +1 -1
- package/dist/src/tools/skill.js.map +1 -1
- package/dist/src/tools/tree-sitter-check.d.ts +1 -0
- package/dist/src/tools/tree-sitter-check.js +32 -0
- package/dist/src/tools/tree-sitter-check.js.map +1 -1
- package/dist/src/turn-targets.d.ts +12 -0
- package/dist/src/turn-targets.js +106 -0
- package/dist/src/turn-targets.js.map +1 -0
- package/dist/src/types.d.ts +24 -0
- package/package.json +1 -1
- package/tui/vetala-darwin-arm64 +0 -0
- package/tui/vetala-darwin-x64 +0 -0
- package/tui/vetala-linux-arm64 +0 -0
- package/tui/vetala-linux-x64 +0 -0
- package/tui/vetala-win32-arm64.exe +0 -0
- package/tui/vetala-win32-x64.exe +0 -0
package/README.md
CHANGED
|
@@ -27,25 +27,26 @@ Current provider support includes Sarvam AI and OpenRouter.
|
|
|
27
27
|
|
|
28
28
|
## Patch Notes
|
|
29
29
|
|
|
30
|
-
### v0.
|
|
30
|
+
### v0.6.1
|
|
31
31
|
Added:
|
|
32
|
-
-
|
|
33
|
-
-
|
|
32
|
+
- Enhanced the core cognitive execution loop with explicit `Think -> Act -> Observe -> Reflect` guardrails.
|
|
33
|
+
- Added a completion confidence and verification system requiring empirical proof before declaring tasks finished.
|
|
34
|
+
- Migrated from hardcoded default plans to a dynamic, model-driven task planning system with granular state tracking.
|
|
35
|
+
- Introduced a strict constraint to prevent "fake shell execution" and force the use of real filesystem tools over markdown code blocks.
|
|
34
36
|
|
|
35
37
|
Patched:
|
|
36
|
-
-
|
|
37
|
-
- Repo-wide search cancellation now propagates through the TypeScript backend and Go TUI worker path.
|
|
38
|
-
- Non-trivial turns now surface reasoning and phase state more clearly during execution.
|
|
38
|
+
- Added smart API error detection to immediately stop and alert the user on non-retryable OpenRouter guardrail, data policy, or auth errors instead of blind retrying.
|
|
39
39
|
|
|
40
|
-
### v0.
|
|
40
|
+
### v0.6.0
|
|
41
41
|
Added:
|
|
42
|
-
-
|
|
43
|
-
-
|
|
42
|
+
- Deliberation-aware TUI turns with thinking summaries, checkbox plans, and live reasoning/phase state.
|
|
43
|
+
- Deterministic active-skill routing with richer pre-turn skill guidance and clearer skill visibility in the UI.
|
|
44
|
+
- `Ctrl+K` full-turn log copy for sharing the latest prompt, plan, tool activity, and reply context.
|
|
44
45
|
|
|
45
46
|
Patched:
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
47
|
+
- Explicit file-path tasks now stay scoped to the named target instead of drifting into broad repo exploration.
|
|
48
|
+
- Edit plans are evidence-driven, verification is file-targeted, and no-op edits are rejected instead of being reported as successful changes.
|
|
49
|
+
- Tool interruption, repo search cancellation, malformed tool-call recovery, and post-tool activity updates are more reliable.
|
|
49
50
|
|
|
50
51
|
## Compatibility
|
|
51
52
|
|
package/dist/src/agent.d.ts
CHANGED
|
@@ -45,6 +45,17 @@ export declare class Agent {
|
|
|
45
45
|
private turnDeliberationPrompt;
|
|
46
46
|
private turnReasoningEffort;
|
|
47
47
|
private turnReasoningLabel;
|
|
48
|
+
private turnPlan;
|
|
49
|
+
private turnPlanSignature;
|
|
50
|
+
private turnPlanInspected;
|
|
51
|
+
private turnPlanHasMutation;
|
|
52
|
+
private turnPlanHasVerification;
|
|
53
|
+
private turnTaskKind;
|
|
54
|
+
private turnTargets;
|
|
55
|
+
private turnChangedFiles;
|
|
56
|
+
private turnVerificationRecords;
|
|
57
|
+
private turnNoOpEdits;
|
|
58
|
+
private turnVerificationNudges;
|
|
48
59
|
constructor(options: AgentOptions);
|
|
49
60
|
get session(): SessionState;
|
|
50
61
|
requestStop(): void;
|
|
@@ -55,9 +66,15 @@ export declare class Agent {
|
|
|
55
66
|
private appendAndRenderAssistantMessage;
|
|
56
67
|
private persistedMessage;
|
|
57
68
|
private systemPrompt;
|
|
69
|
+
private currentExecutionPrompt;
|
|
70
|
+
private pendingVerificationPrompt;
|
|
58
71
|
private beginRequest;
|
|
59
72
|
private endRequest;
|
|
60
73
|
private throwIfStopped;
|
|
74
|
+
private advanceCurrentPlan;
|
|
75
|
+
private observeToolResult;
|
|
76
|
+
private finalizeCurrentPlan;
|
|
77
|
+
private setTurnPlan;
|
|
61
78
|
}
|
|
62
79
|
export declare class AgentInterruptedError extends Error {
|
|
63
80
|
constructor();
|
package/dist/src/agent.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { compactConversation } from "./context-memory.js";
|
|
2
|
-
import { analyzeTurnDeliberation, phaseForTool } from "./deliberation.js";
|
|
2
|
+
import { advanceTurnPlan, analyzeTurnDeliberation, cloneTurnPlan, phaseForTool, updateTurnPlan } from "./deliberation.js";
|
|
3
3
|
import { loadMemoriesPrompt, loadRulesPrompt } from "./context-files.js";
|
|
4
4
|
import { appendHistoryEntry } from "./history-store.js";
|
|
5
5
|
import { createProviderClient, getProviderDefinition, providerLabel, withSystemMessage } from "./providers/index.js";
|
|
6
6
|
import { createSearchProvider } from "./search-provider.js";
|
|
7
|
+
import { formatTurnTargetList, resolveTurnTargets } from "./turn-targets.js";
|
|
8
|
+
const EMPTY_TURN_TARGETS = {
|
|
9
|
+
taskKind: "chat",
|
|
10
|
+
explicitPaths: [],
|
|
11
|
+
explicitFiles: [],
|
|
12
|
+
explicitDirs: [],
|
|
13
|
+
preferredRoot: null
|
|
14
|
+
};
|
|
7
15
|
export class Agent {
|
|
8
16
|
options;
|
|
9
17
|
client;
|
|
@@ -14,6 +22,17 @@ export class Agent {
|
|
|
14
22
|
turnDeliberationPrompt = null;
|
|
15
23
|
turnReasoningEffort = null;
|
|
16
24
|
turnReasoningLabel = "none";
|
|
25
|
+
turnPlan = null;
|
|
26
|
+
turnPlanSignature = "";
|
|
27
|
+
turnPlanInspected = false;
|
|
28
|
+
turnPlanHasMutation = false;
|
|
29
|
+
turnPlanHasVerification = false;
|
|
30
|
+
turnTaskKind = "chat";
|
|
31
|
+
turnTargets = EMPTY_TURN_TARGETS;
|
|
32
|
+
turnChangedFiles = new Set();
|
|
33
|
+
turnVerificationRecords = [];
|
|
34
|
+
turnNoOpEdits = [];
|
|
35
|
+
turnVerificationNudges = 0;
|
|
17
36
|
constructor(options) {
|
|
18
37
|
this.options = options;
|
|
19
38
|
this.client = createProviderClient(options.config.providers[options.session.provider]);
|
|
@@ -31,6 +50,16 @@ export class Agent {
|
|
|
31
50
|
this.activeTurnController = turnController;
|
|
32
51
|
this.stopRequested = false;
|
|
33
52
|
try {
|
|
53
|
+
this.turnPlanInspected = false;
|
|
54
|
+
this.turnPlanHasMutation = false;
|
|
55
|
+
this.turnPlanHasVerification = false;
|
|
56
|
+
this.turnTaskKind = "chat";
|
|
57
|
+
this.turnTargets = EMPTY_TURN_TARGETS;
|
|
58
|
+
this.turnChangedFiles.clear();
|
|
59
|
+
this.turnVerificationRecords = [];
|
|
60
|
+
this.turnNoOpEdits = [];
|
|
61
|
+
this.turnVerificationNudges = 0;
|
|
62
|
+
this.setTurnPlan(null);
|
|
34
63
|
const userMessage = this.persistedMessage({
|
|
35
64
|
role: "user",
|
|
36
65
|
content: userInput
|
|
@@ -55,6 +84,8 @@ export class Agent {
|
|
|
55
84
|
configuredEffort: providerDefinition.supportsReasoningEffort ? this.options.config.reasoningEffort : null,
|
|
56
85
|
activeSkills: turnSkillContext.labels
|
|
57
86
|
});
|
|
87
|
+
this.turnTaskKind = deliberation.taskKind;
|
|
88
|
+
this.turnTargets = await resolveTurnTargets(userInput, deliberation.taskKind, this.options.pathPolicy, this.options.session.workspaceRoot);
|
|
58
89
|
this.turnDeliberationPrompt = deliberation.guidance;
|
|
59
90
|
this.turnReasoningEffort = providerDefinition.supportsReasoningEffort ? deliberation.reasoningEffort : null;
|
|
60
91
|
this.turnReasoningLabel = deliberation.reasoningLabel;
|
|
@@ -62,7 +93,11 @@ export class Agent {
|
|
|
62
93
|
if (deliberation.shouldShowThinking && deliberation.thinkingSummary) {
|
|
63
94
|
this.options.ui.printThinking(deliberation.thinkingSummary);
|
|
64
95
|
}
|
|
96
|
+
if (deliberation.plan) {
|
|
97
|
+
this.advanceCurrentPlan("inspect", deliberation.plan);
|
|
98
|
+
}
|
|
65
99
|
if (!provider.authValue) {
|
|
100
|
+
this.setTurnPlan(null);
|
|
66
101
|
const missingAuthMessage = provider.authSource === "stored_hash"
|
|
67
102
|
? `A stored SHA-256 fingerprint exists for ${providerDefinition.label}, but the raw credential is not available in this process. Use /model to enter the key again or set ${providerDefinition.auth.envVars.join(", ")}.`
|
|
68
103
|
: `${providerDefinition.label} credentials are missing. Set ${providerDefinition.auth.envVars.join(", ")} and try again.`;
|
|
@@ -100,15 +135,23 @@ export class Agent {
|
|
|
100
135
|
}
|
|
101
136
|
consecutiveApiErrors += 1;
|
|
102
137
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
const isNonRetryable = errorMessage.includes("guardrail") ||
|
|
139
|
+
errorMessage.includes("policy") ||
|
|
140
|
+
errorMessage.includes("unauthorized") ||
|
|
141
|
+
errorMessage.includes("401") ||
|
|
142
|
+
errorMessage.includes("403");
|
|
143
|
+
if (consecutiveApiErrors >= 3 || isNonRetryable) {
|
|
144
|
+
const finalMessage = isNonRetryable
|
|
145
|
+
? `I encountered a non-retryable API error: ${errorMessage}. Please check your provider settings or guardrail restrictions.`
|
|
146
|
+
: `I've encountered multiple consecutive API errors and must stop. Last error: ${errorMessage}`;
|
|
147
|
+
await this.appendAndRenderAssistantMessage(finalMessage, true);
|
|
105
148
|
return;
|
|
106
149
|
}
|
|
107
150
|
this.options.ui.warn(`API Error: ${errorMessage}`);
|
|
108
151
|
this.options.ui.activity("Retrying automatically...");
|
|
109
152
|
const syntheticUserMessage = this.persistedMessage({
|
|
110
153
|
role: "user",
|
|
111
|
-
content: `SYSTEM ALERT: The previous generation failed with an API error: ${errorMessage}\nIf
|
|
154
|
+
content: `SYSTEM ALERT: The previous generation failed with an API error: ${errorMessage}\nIf this error persists, consider breaking your request into smaller parts or checking your model configuration.`
|
|
112
155
|
});
|
|
113
156
|
await this.options.sessionStore.appendMessage(this.options.session, syntheticUserMessage);
|
|
114
157
|
continue;
|
|
@@ -149,17 +192,56 @@ export class Agent {
|
|
|
149
192
|
content: turn.content || null,
|
|
150
193
|
tool_calls: partitionedToolCalls.validToolCalls.length > 0 ? partitionedToolCalls.validToolCalls : null
|
|
151
194
|
});
|
|
195
|
+
// Extract thinking summary from content if not already set
|
|
196
|
+
if (turn.content && !this.turnPlanInspected) {
|
|
197
|
+
const lines = turn.content.split("\n");
|
|
198
|
+
const thinkingLines = lines.filter(l => l.trim().startsWith("- ") || l.trim().startsWith("* "));
|
|
199
|
+
if (thinkingLines.length > 0) {
|
|
200
|
+
this.options.ui.printThinking(thinkingLines.slice(0, 3).join("\n"));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
152
203
|
await this.options.sessionStore.appendMessage(this.options.session, assistantMessage);
|
|
153
204
|
if (partitionedToolCalls.validToolCalls.length === 0) {
|
|
205
|
+
if (this.turnTaskKind !== "chat") {
|
|
206
|
+
const verificationPrompt = this.pendingVerificationPrompt();
|
|
207
|
+
if (verificationPrompt) {
|
|
208
|
+
this.turnVerificationNudges += 1;
|
|
209
|
+
await this.options.sessionStore.appendMessage(this.options.session, this.persistedMessage({
|
|
210
|
+
role: "user",
|
|
211
|
+
content: verificationPrompt
|
|
212
|
+
}));
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
// Force the use of task_completed for non-chat tasks if they just wrote text
|
|
216
|
+
const textContent = (turn.content || "").toLowerCase();
|
|
217
|
+
const looksLikeCodeBlock = textContent.includes("```");
|
|
218
|
+
if (this.turnVerificationNudges < 3 && !textContent.includes("task_completed")) {
|
|
219
|
+
this.turnVerificationNudges += 1;
|
|
220
|
+
const pushback = looksLikeCodeBlock
|
|
221
|
+
? "SYSTEM ALERT: You provided a code block in your response instead of using a tool to write it. You MUST use 'replace_in_file' or 'write_file' to apply these changes. Do not just write code in markdown."
|
|
222
|
+
: "SYSTEM ALERT: You stopped calling tools. If you are finished with the task, you MUST call the `task_completed` tool to formalize completion. If you are not finished, please use the appropriate tools to continue.";
|
|
223
|
+
this.options.ui.warn("Agent stopped prematurely. Nudging to use tools or task_completed...");
|
|
224
|
+
await this.options.sessionStore.appendMessage(this.options.session, this.persistedMessage({
|
|
225
|
+
role: "user",
|
|
226
|
+
content: pushback
|
|
227
|
+
}));
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
this.finalizeCurrentPlan();
|
|
154
232
|
this.options.ui.endAssistantTurn();
|
|
155
233
|
return;
|
|
156
234
|
}
|
|
157
235
|
let suppressedRepeats = 0;
|
|
236
|
+
let taskCompletedCalled = false;
|
|
158
237
|
for (const toolCall of partitionedToolCalls.validToolCalls) {
|
|
159
238
|
this.throwIfStopped();
|
|
160
239
|
this.options.ui.printToolCall(toolCall);
|
|
161
240
|
const signature = toolCallSignature(toolCall);
|
|
162
241
|
let result;
|
|
242
|
+
if (toolCall.function.name === "task_completed") {
|
|
243
|
+
taskCompletedCalled = true;
|
|
244
|
+
}
|
|
163
245
|
if (seenToolCalls.has(signature)) {
|
|
164
246
|
suppressedRepeats += 1;
|
|
165
247
|
this.options.ui.activity(`Skipping repeated ${toolCall.function.name} call.`);
|
|
@@ -174,7 +256,7 @@ export class Agent {
|
|
|
174
256
|
this.options.ui.updateTurnState(this.turnReasoningLabel, phaseForTool(toolCall.function.name));
|
|
175
257
|
this.options.ui.activity(`Running ${toolCall.function.name}.`);
|
|
176
258
|
const toolSpec = this.options.tools.getTool(toolCall.function.name);
|
|
177
|
-
const isMutating = toolSpec && !toolSpec.readOnly;
|
|
259
|
+
const isMutating = Boolean(toolSpec && !toolSpec.readOnly);
|
|
178
260
|
try {
|
|
179
261
|
result = await this.options.tools.execute(toolCall, this.toolContext(turnController));
|
|
180
262
|
}
|
|
@@ -185,6 +267,7 @@ export class Agent {
|
|
|
185
267
|
}
|
|
186
268
|
throw error;
|
|
187
269
|
}
|
|
270
|
+
this.observeToolResult(toolCall, isMutating, result);
|
|
188
271
|
if (result.isError) {
|
|
189
272
|
seenToolCalls.delete(signature);
|
|
190
273
|
}
|
|
@@ -194,12 +277,19 @@ export class Agent {
|
|
|
194
277
|
}
|
|
195
278
|
this.throwIfStopped();
|
|
196
279
|
this.options.ui.printToolResult(result.summary, result.isError, result.content);
|
|
280
|
+
this.options.ui.activity("Thinking...");
|
|
197
281
|
await this.options.sessionStore.appendMessage(this.options.session, this.persistedMessage({
|
|
198
282
|
role: "tool",
|
|
199
283
|
content: result.content,
|
|
200
284
|
tool_call_id: toolCall.id
|
|
201
285
|
}));
|
|
202
286
|
}
|
|
287
|
+
// If the agent successfully completed the task this turn, exit the loop cleanly.
|
|
288
|
+
if (taskCompletedCalled) {
|
|
289
|
+
this.finalizeCurrentPlan();
|
|
290
|
+
this.options.ui.endAssistantTurn();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
203
293
|
if (shouldInjectRepeatWarning(suppressedRepeats, repeatWarningInjected)) {
|
|
204
294
|
repeatWarningInjected += 1;
|
|
205
295
|
const warning = this.persistedMessage({
|
|
@@ -219,6 +309,15 @@ export class Agent {
|
|
|
219
309
|
this.turnDeliberationPrompt = null;
|
|
220
310
|
this.turnReasoningEffort = null;
|
|
221
311
|
this.turnReasoningLabel = "none";
|
|
312
|
+
this.turnTaskKind = "chat";
|
|
313
|
+
this.turnTargets = EMPTY_TURN_TARGETS;
|
|
314
|
+
this.turnChangedFiles.clear();
|
|
315
|
+
this.turnVerificationRecords = [];
|
|
316
|
+
this.turnNoOpEdits = [];
|
|
317
|
+
this.turnVerificationNudges = 0;
|
|
318
|
+
this.turnPlanInspected = false;
|
|
319
|
+
this.turnPlanHasMutation = false;
|
|
320
|
+
this.turnPlanHasVerification = false;
|
|
222
321
|
this.options.ui.updateTurnState(null, null);
|
|
223
322
|
}
|
|
224
323
|
}
|
|
@@ -288,6 +387,14 @@ export class Agent {
|
|
|
288
387
|
return {
|
|
289
388
|
cwd: process.cwd(),
|
|
290
389
|
workspaceRoot: this.options.session.workspaceRoot,
|
|
390
|
+
turn: {
|
|
391
|
+
taskKind: this.turnTaskKind,
|
|
392
|
+
explicitPaths: [...this.turnTargets.explicitPaths],
|
|
393
|
+
explicitFiles: [...this.turnTargets.explicitFiles],
|
|
394
|
+
explicitDirs: [...this.turnTargets.explicitDirs],
|
|
395
|
+
preferredRoot: this.turnTargets.preferredRoot,
|
|
396
|
+
changedFiles: [...this.turnChangedFiles]
|
|
397
|
+
},
|
|
291
398
|
lifecycle: {
|
|
292
399
|
signal: turnController.signal,
|
|
293
400
|
throwIfAborted: () => {
|
|
@@ -371,11 +478,43 @@ export class Agent {
|
|
|
371
478
|
`Active provider: ${providerLabel(this.options.session.provider)}`,
|
|
372
479
|
`Active model: ${this.options.session.model}`,
|
|
373
480
|
`Allowed roots right now: ${this.options.pathPolicy.allowedRoots().join(", ")}`,
|
|
481
|
+
"",
|
|
482
|
+
"=== CORE EXECUTION LOOP (THINK -> ACT -> OBSERVE -> REFLECT) ===",
|
|
483
|
+
"1. You do not just run tools once and stop. You operate in a sustained, multi-step execution loop.",
|
|
484
|
+
"2. For every step, you MUST maintain a mental state of what you have tried, what succeeded, and what failed. Do not repeat the exact same failed actions.",
|
|
485
|
+
"3. Before executing any tool, explicitly evaluate whether it is the most strategic choice (e.g., use fast searches before reading entire files).",
|
|
486
|
+
"4. You MUST generate a concise, bulleted thinking summary and a structured plan (sub-tasks) at the beginning of your response to inform the user of your intent.",
|
|
487
|
+
"5. NEVER write code blocks in your markdown response as a way of delivering fixes. You MUST use 'replace_in_file' or 'write_file' tools to apply changes directly to the file system.",
|
|
488
|
+
"6. NEVER hallucinate or claim you ran a test or command (like 'npm start' or 'tsc') unless you actually executed it using the 'run_shell' tool.",
|
|
489
|
+
"",
|
|
490
|
+
"=== GOAL AWARENESS & LONG-HORIZON STABILITY ===",
|
|
491
|
+
"1. Deconstruct complex requests into explicit sub-tasks. Maintain an internal tracker of these tasks.",
|
|
492
|
+
"2. Do not treat generic 'inspect, decide, execute' as a real plan. Formulate specific, verifiable steps.",
|
|
493
|
+
"3. Continuously re-evaluate your progress toward the ultimate goal. If you are drifting, explicitly correct your course.",
|
|
494
|
+
"",
|
|
495
|
+
"=== FAILURE HANDLING & ADAPTIVE STRATEGY ===",
|
|
496
|
+
"1. Errors are not just noise; they are critical signals. If a tool call or compilation fails, deeply analyze WHY it failed.",
|
|
497
|
+
"2. You MUST NOT brute-force the same approach if it fails twice. Pivot to an entirely different strategy.",
|
|
498
|
+
"3. Persist memory of failures across your turn to prevent repeating mistakes.",
|
|
499
|
+
"",
|
|
500
|
+
"=== CONTEXT INTELLIGENCE & VERIFICATION DEPTH ===",
|
|
501
|
+
"1. Be ruthless about context. Only read files and lines that are strictly necessary.",
|
|
502
|
+
"2. You MUST empirically verify all changes. Do not claim a change works unless you have run a test, compiled the code, or executed a script that proves it.",
|
|
503
|
+
"3. Validate the environment and edge cases before declaring completion.",
|
|
504
|
+
"",
|
|
505
|
+
"=== COMPLETION CONFIDENCE ===",
|
|
506
|
+
"1. Before ending your turn, internally evaluate your Confidence Score (0-100%).",
|
|
507
|
+
"2. If your confidence is below 95%, you must gather more information, run more tests, or ask the user for clarification before stopping.",
|
|
508
|
+
"",
|
|
509
|
+
"=== BASE RULES ===",
|
|
374
510
|
"When using tools, emit valid JSON object arguments only.",
|
|
375
511
|
"Prefer smaller tool calls over giant payloads. Break large refactors and large apply_patch edits into multiple steps.",
|
|
376
512
|
"When the user names a concrete file path, read that file directly with read_file or read_file_chunk before considering search_repo.",
|
|
377
|
-
"
|
|
513
|
+
"If the user named concrete files or directories, stay scoped to them unless you have concrete evidence that broader repo context is required.",
|
|
514
|
+
"When active skill guidance is already injected below, use it directly. Do not call the skill tool again unless you need a specific deeper file that is not already summarized.",
|
|
378
515
|
"If the target, scope, or acceptance criteria remain unclear after initial inspection, use ask_user before editing.",
|
|
516
|
+
"For source-file edits, prefer targeted diagnostics on the named file or changed file before and after editing when available.",
|
|
517
|
+
"Do not claim a build, test, or diagnostics check verified your change unless the tool output explicitly supports that claim.",
|
|
379
518
|
"For Git-aware tasks, prefer the dedicated git tools over ad-hoc shell commands.",
|
|
380
519
|
"For change review, start with git_review targeting the full worktree so you cover staged, unstaged, and untracked files.",
|
|
381
520
|
"For branch review, compare against the merge base with the requested base branch instead of assuming HEAD~1 or the previous commit.",
|
|
@@ -389,12 +528,25 @@ export class Agent {
|
|
|
389
528
|
lines.push("", rulesPrompt);
|
|
390
529
|
}
|
|
391
530
|
lines.push("", skillInventory);
|
|
531
|
+
if (this.turnTargets.explicitPaths.length > 0) {
|
|
532
|
+
lines.push("", [
|
|
533
|
+
"Concrete turn targets:",
|
|
534
|
+
`- named targets: ${formatTurnTargetList(this.turnTargets.explicitPaths, this.options.session.workspaceRoot)}`,
|
|
535
|
+
this.turnTargets.preferredRoot
|
|
536
|
+
? `- preferred root: ${formatTurnTargetList([this.turnTargets.preferredRoot], this.options.session.workspaceRoot)}`
|
|
537
|
+
: "- preferred root: (none)"
|
|
538
|
+
].join("\n"));
|
|
539
|
+
}
|
|
392
540
|
if (this.turnSkillPrompt) {
|
|
393
541
|
lines.push("", this.turnSkillPrompt);
|
|
394
542
|
}
|
|
395
543
|
if (this.turnDeliberationPrompt) {
|
|
396
544
|
lines.push("", this.turnDeliberationPrompt);
|
|
397
545
|
}
|
|
546
|
+
const executionPrompt = this.currentExecutionPrompt();
|
|
547
|
+
if (executionPrompt) {
|
|
548
|
+
lines.push("", executionPrompt);
|
|
549
|
+
}
|
|
398
550
|
if (persistentMemory) {
|
|
399
551
|
lines.push("", persistentMemory);
|
|
400
552
|
}
|
|
@@ -403,6 +555,58 @@ export class Agent {
|
|
|
403
555
|
}
|
|
404
556
|
return lines.join("\n");
|
|
405
557
|
}
|
|
558
|
+
currentExecutionPrompt() {
|
|
559
|
+
const lines = [];
|
|
560
|
+
if (this.turnChangedFiles.size > 0) {
|
|
561
|
+
lines.push(`- changed files: ${formatTurnTargetList([...this.turnChangedFiles], this.options.session.workspaceRoot)}`);
|
|
562
|
+
}
|
|
563
|
+
if (this.turnVerificationRecords.length > 0) {
|
|
564
|
+
const latest = this.turnVerificationRecords[this.turnVerificationRecords.length - 1];
|
|
565
|
+
lines.push(`- latest verification: ${latest.toolName} -> ${latest.trusted ? "trusted" : "untrusted"} / ${latest.passed ? "passed" : "failed"}`);
|
|
566
|
+
if (latest.reason) {
|
|
567
|
+
lines.push(`- verification note: ${latest.reason}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else if (this.turnChangedFiles.size > 0) {
|
|
571
|
+
lines.push("- verification: none yet");
|
|
572
|
+
}
|
|
573
|
+
if (this.turnNoOpEdits.length > 0) {
|
|
574
|
+
lines.push(`- no-op edit attempts: ${this.turnNoOpEdits.length}`);
|
|
575
|
+
}
|
|
576
|
+
if (lines.length === 0) {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
return [
|
|
580
|
+
"Current turn execution state:",
|
|
581
|
+
...lines,
|
|
582
|
+
"- Only summarize changes that actually happened in the changed files listed above.",
|
|
583
|
+
"- If verification is missing or untrusted, verify first or explicitly say so."
|
|
584
|
+
].join("\n");
|
|
585
|
+
}
|
|
586
|
+
pendingVerificationPrompt() {
|
|
587
|
+
if (this.turnTaskKind !== "edit" || !this.turnPlanHasMutation || this.turnPlanHasVerification) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
if (this.turnVerificationNudges >= 3) {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
const changedFiles = [...this.turnChangedFiles];
|
|
594
|
+
const targets = changedFiles.length > 0 ? changedFiles : this.turnTargets.explicitFiles;
|
|
595
|
+
const formattedTargets = formatTurnTargetList(targets, this.options.session.workspaceRoot);
|
|
596
|
+
if (this.turnVerificationNudges >= 2) {
|
|
597
|
+
return [
|
|
598
|
+
"SYSTEM ALERT: Trustworthy verification is still missing for this edit.",
|
|
599
|
+
`Changed or named targets: ${formattedTargets}.`,
|
|
600
|
+
"If you cannot verify in this environment, say that explicitly and do not claim the change is verified or all tests passed."
|
|
601
|
+
].join("\n");
|
|
602
|
+
}
|
|
603
|
+
return [
|
|
604
|
+
"SYSTEM ALERT: You changed files but have not yet produced trustworthy verification for them.",
|
|
605
|
+
`Changed or named targets: ${formattedTargets}.`,
|
|
606
|
+
"Before finalizing, run get_diagnostics on the changed file or run a build/test/check command that actually validates the changed scope.",
|
|
607
|
+
"Do not claim success until verification is trustworthy."
|
|
608
|
+
].join("\n");
|
|
609
|
+
}
|
|
406
610
|
beginRequest() {
|
|
407
611
|
const controller = new AbortController();
|
|
408
612
|
this.activeRequestController = controller;
|
|
@@ -418,6 +622,111 @@ export class Agent {
|
|
|
418
622
|
throw new AgentInterruptedError();
|
|
419
623
|
}
|
|
420
624
|
}
|
|
625
|
+
advanceCurrentPlan(stage, planOverride) {
|
|
626
|
+
const next = advanceTurnPlan(planOverride ?? this.turnPlan, stage);
|
|
627
|
+
this.setTurnPlan(next);
|
|
628
|
+
}
|
|
629
|
+
observeToolResult(toolCall, isMutating, result) {
|
|
630
|
+
const toolName = toolCall.function.name;
|
|
631
|
+
const phase = phaseForTool(toolName);
|
|
632
|
+
const meta = result.meta;
|
|
633
|
+
if (toolName === "update_task_state" && !result.isError) {
|
|
634
|
+
try {
|
|
635
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
636
|
+
if (Array.isArray(args.sub_tasks)) {
|
|
637
|
+
const nextPlan = this.turnPlan ? cloneTurnPlan(this.turnPlan) : {
|
|
638
|
+
taskKind: this.turnTaskKind,
|
|
639
|
+
title: "Dynamic Plan",
|
|
640
|
+
explanation: args.current_goal,
|
|
641
|
+
steps: []
|
|
642
|
+
};
|
|
643
|
+
nextPlan.steps = args.sub_tasks.map((st) => ({
|
|
644
|
+
id: st.id || "step-" + Math.random().toString(36).slice(2, 7),
|
|
645
|
+
label: st.label,
|
|
646
|
+
status: st.status
|
|
647
|
+
}));
|
|
648
|
+
this.setTurnPlan(nextPlan);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
catch (e) {
|
|
652
|
+
// Ignore malformed arguments for plan updates
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (meta?.noOp) {
|
|
656
|
+
this.turnNoOpEdits.push(result.summary);
|
|
657
|
+
}
|
|
658
|
+
for (const changedFile of meta?.changedFiles ?? []) {
|
|
659
|
+
this.turnChangedFiles.add(changedFile);
|
|
660
|
+
}
|
|
661
|
+
if (meta?.verification) {
|
|
662
|
+
this.turnVerificationRecords.push({
|
|
663
|
+
...meta.verification,
|
|
664
|
+
toolName
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
const inspected = meta?.inspectedPaths?.length ? meta.inspectedPaths : [];
|
|
668
|
+
if ((inspected.length > 0 || (phase === "inspecting" && !result.isError)) && !this.turnPlanInspected) {
|
|
669
|
+
this.turnPlanInspected = true;
|
|
670
|
+
this.setTurnPlan(updateTurnPlan(this.turnPlan, {
|
|
671
|
+
completed: ["inspect"],
|
|
672
|
+
inProgress: "decide"
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
if (isMutating && !result.isError && (meta?.changedFiles?.length ?? 0) > 0) {
|
|
676
|
+
this.turnPlanHasMutation = true;
|
|
677
|
+
this.setTurnPlan(updateTurnPlan(this.turnPlan, {
|
|
678
|
+
completed: ["inspect", "decide"],
|
|
679
|
+
inProgress: "execute"
|
|
680
|
+
}));
|
|
681
|
+
}
|
|
682
|
+
if (meta?.verification &&
|
|
683
|
+
meta.verification.trusted &&
|
|
684
|
+
meta.verification.passed &&
|
|
685
|
+
this.turnPlanHasMutation) {
|
|
686
|
+
this.turnPlanHasVerification = true;
|
|
687
|
+
this.setTurnPlan(updateTurnPlan(this.turnPlan, {
|
|
688
|
+
completed: ["inspect", "decide", "execute"],
|
|
689
|
+
inProgress: "summarize"
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
finalizeCurrentPlan() {
|
|
694
|
+
if (!this.turnPlan) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (this.turnPlan.taskKind === "edit") {
|
|
698
|
+
if (this.turnPlanHasMutation && this.turnPlanHasVerification) {
|
|
699
|
+
this.advanceCurrentPlan("complete");
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (this.turnPlanHasMutation) {
|
|
703
|
+
this.setTurnPlan(updateTurnPlan(this.turnPlan, {
|
|
704
|
+
completed: ["inspect", "decide", "execute"],
|
|
705
|
+
inProgress: null
|
|
706
|
+
}));
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (this.turnPlanInspected) {
|
|
710
|
+
this.setTurnPlan(updateTurnPlan(this.turnPlan, {
|
|
711
|
+
completed: ["inspect"],
|
|
712
|
+
inProgress: "decide"
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (this.turnPlanInspected) {
|
|
718
|
+
this.advanceCurrentPlan("complete");
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
setTurnPlan(plan) {
|
|
722
|
+
this.turnPlan = cloneTurnPlan(plan);
|
|
723
|
+
const signature = this.turnPlan ? JSON.stringify(this.turnPlan) : "";
|
|
724
|
+
if (signature === this.turnPlanSignature) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
this.turnPlanSignature = signature;
|
|
728
|
+
this.options.ui.updatePlan(cloneTurnPlan(this.turnPlan));
|
|
729
|
+
}
|
|
421
730
|
}
|
|
422
731
|
export class AgentInterruptedError extends Error {
|
|
423
732
|
constructor() {
|