@bubblebrain-ai/bubble 0.0.6 → 0.0.8
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/dist/agent/execution-governor.d.ts +5 -13
- package/dist/agent/execution-governor.js +33 -142
- package/dist/agent.d.ts +6 -0
- package/dist/agent.js +36 -3
- package/dist/context/budget.d.ts +1 -0
- package/dist/context/budget.js +1 -1
- package/dist/context/usage.d.ts +34 -0
- package/dist/context/usage.js +213 -0
- package/dist/diff-stats.d.ts +5 -0
- package/dist/diff-stats.js +21 -0
- package/dist/main.js +83 -44
- package/dist/mcp/transports.d.ts +1 -0
- package/dist/mcp/transports.js +8 -0
- package/dist/model-catalog.js +1 -1
- package/dist/orchestrator/default-hooks.js +9 -33
- package/dist/prompt/compose.js +2 -1
- package/dist/prompt/provider-prompts/kimi.js +3 -1
- package/dist/prompt/reminders.d.ts +2 -1
- package/dist/prompt/reminders.js +4 -3
- package/dist/provider-registry.js +3 -3
- package/dist/provider-transform.d.ts +3 -1
- package/dist/provider-transform.js +15 -0
- package/dist/provider.d.ts +4 -1
- package/dist/provider.js +89 -4
- package/dist/reasoning-debug.d.ts +7 -0
- package/dist/reasoning-debug.js +30 -0
- package/dist/session-log.js +13 -2
- package/dist/session-types.d.ts +1 -1
- package/dist/slash-commands/commands.js +36 -19
- package/dist/tools/edit.js +5 -0
- package/dist/tools/file-state.d.ts +19 -0
- package/dist/tools/file-state.js +15 -0
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.js +92 -11
- package/dist/tui/escape-confirmation.d.ts +15 -0
- package/dist/tui/escape-confirmation.js +30 -0
- package/dist/tui/run.js +93 -23
- package/dist/tui-ink/app.d.ts +43 -0
- package/dist/tui-ink/app.js +1016 -0
- package/dist/tui-ink/approval/approval-dialog.d.ts +13 -0
- package/dist/tui-ink/approval/approval-dialog.js +129 -0
- package/dist/tui-ink/approval/diff-view.d.ts +7 -0
- package/dist/tui-ink/approval/diff-view.js +43 -0
- package/dist/tui-ink/approval/select.d.ts +35 -0
- package/dist/tui-ink/approval/select.js +87 -0
- package/dist/tui-ink/code-highlight.d.ts +6 -0
- package/dist/tui-ink/code-highlight.js +94 -0
- package/dist/tui-ink/display-history.d.ts +38 -0
- package/dist/tui-ink/display-history.js +130 -0
- package/dist/tui-ink/edit-diff.d.ts +11 -0
- package/dist/tui-ink/edit-diff.js +52 -0
- package/dist/tui-ink/file-mentions.d.ts +29 -0
- package/dist/tui-ink/file-mentions.js +174 -0
- package/dist/tui-ink/footer.d.ts +19 -0
- package/dist/tui-ink/footer.js +44 -0
- package/dist/tui-ink/image-paste.d.ts +54 -0
- package/dist/tui-ink/image-paste.js +288 -0
- package/dist/tui-ink/input-box.d.ts +41 -0
- package/dist/tui-ink/input-box.js +637 -0
- package/dist/tui-ink/markdown.d.ts +38 -0
- package/dist/tui-ink/markdown.js +384 -0
- package/dist/tui-ink/message-list.d.ts +33 -0
- package/dist/tui-ink/message-list.js +571 -0
- package/dist/tui-ink/model-picker.d.ts +43 -0
- package/dist/tui-ink/model-picker.js +326 -0
- package/dist/tui-ink/plan-confirm.d.ts +7 -0
- package/dist/tui-ink/plan-confirm.js +104 -0
- package/dist/tui-ink/question-dialog.d.ts +8 -0
- package/dist/tui-ink/question-dialog.js +98 -0
- package/dist/tui-ink/recent-activity.d.ts +8 -0
- package/dist/tui-ink/recent-activity.js +71 -0
- package/dist/tui-ink/run.d.ts +33 -0
- package/dist/tui-ink/run.js +25 -0
- package/dist/tui-ink/theme.d.ts +37 -0
- package/dist/tui-ink/theme.js +42 -0
- package/dist/tui-ink/todos.d.ts +7 -0
- package/dist/tui-ink/todos.js +44 -0
- package/dist/tui-ink/trace-groups.d.ts +25 -0
- package/dist/tui-ink/trace-groups.js +310 -0
- package/dist/tui-ink/use-terminal-size.d.ts +4 -0
- package/dist/tui-ink/use-terminal-size.js +21 -0
- package/dist/tui-ink/welcome.d.ts +18 -0
- package/dist/tui-ink/welcome.js +119 -0
- package/dist/types.d.ts +4 -0
- package/package.json +6 -1
|
@@ -3,21 +3,16 @@ import type { TaskType } from "./task-classifier.js";
|
|
|
3
3
|
export interface GovernorDecision {
|
|
4
4
|
blockedResult?: ToolResult;
|
|
5
5
|
}
|
|
6
|
-
type WorkPhase = "explore" | "modify" | "verify";
|
|
7
6
|
export declare class ExecutionGovernor {
|
|
8
|
-
private taskType;
|
|
9
7
|
private budget;
|
|
10
8
|
private history;
|
|
11
9
|
private totalSteps;
|
|
12
10
|
private searchSteps;
|
|
13
11
|
private readSteps;
|
|
14
|
-
private
|
|
15
|
-
private searchFrozen;
|
|
16
|
-
private explorationFrozen;
|
|
17
|
-
private phase;
|
|
18
|
-
private codeChanged;
|
|
12
|
+
private mutationVersion;
|
|
19
13
|
private reminderQueue;
|
|
20
14
|
private warnedFamilies;
|
|
15
|
+
private warnedSignatures;
|
|
21
16
|
private softTotalWarned;
|
|
22
17
|
private softSearchWarned;
|
|
23
18
|
private softReadWarned;
|
|
@@ -29,16 +24,13 @@ export declare class ExecutionGovernor {
|
|
|
29
24
|
readSteps: number;
|
|
30
25
|
searchFrozen: boolean;
|
|
31
26
|
explorationFrozen: boolean;
|
|
32
|
-
phase:
|
|
27
|
+
phase: "observe";
|
|
33
28
|
};
|
|
34
29
|
filterToolDefinitions(toolDefinitions: ToolRegistryEntry[]): ToolRegistryEntry[];
|
|
35
30
|
beforeToolCall(toolCall: ParsedToolCall): GovernorDecision;
|
|
36
31
|
afterToolResult(toolCall: ParsedToolCall, result: ToolResult): void;
|
|
37
32
|
private trailingNoProgressCount;
|
|
38
|
-
private
|
|
39
|
-
private
|
|
40
|
-
private isModificationTask;
|
|
41
|
-
private historyCount;
|
|
33
|
+
private hasCurrentMutationObservation;
|
|
34
|
+
private warnOnce;
|
|
42
35
|
private maybeWarnOnSoftBudgets;
|
|
43
36
|
}
|
|
44
|
-
export {};
|
|
@@ -1,118 +1,84 @@
|
|
|
1
1
|
import { analyzeToolIntent } from "./tool-intent.js";
|
|
2
|
-
import {
|
|
2
|
+
import { buildInvestigationReminder, buildLoopWarningReminder } from "../prompt/reminders.js";
|
|
3
3
|
const BUDGETS = {
|
|
4
4
|
security_investigation: {
|
|
5
5
|
softTotalSteps: 14,
|
|
6
6
|
softSearchSteps: 6,
|
|
7
7
|
softReadSteps: 8,
|
|
8
|
-
|
|
9
|
-
maxNoProgressReadExactRepeats: 2,
|
|
10
|
-
maxNoProgressExactRepeats: 2,
|
|
11
|
-
maxNoProgressFamilyRepeats: 3,
|
|
8
|
+
warningExactRepeats: 2,
|
|
12
9
|
warningFamilyRepeats: 2,
|
|
13
10
|
},
|
|
14
11
|
code_search: {
|
|
15
12
|
softTotalSteps: 16,
|
|
16
13
|
softSearchSteps: 8,
|
|
17
14
|
softReadSteps: 10,
|
|
18
|
-
|
|
19
|
-
maxNoProgressReadExactRepeats: 2,
|
|
20
|
-
maxNoProgressExactRepeats: 3,
|
|
21
|
-
maxNoProgressFamilyRepeats: 4,
|
|
15
|
+
warningExactRepeats: 2,
|
|
22
16
|
warningFamilyRepeats: 3,
|
|
23
17
|
},
|
|
24
18
|
debugging: {
|
|
25
19
|
softTotalSteps: 18,
|
|
26
20
|
softSearchSteps: 8,
|
|
27
21
|
softReadSteps: 7,
|
|
28
|
-
|
|
29
|
-
maxNoProgressReadExactRepeats: 1,
|
|
30
|
-
maxNoProgressExactRepeats: 3,
|
|
31
|
-
maxNoProgressFamilyRepeats: 4,
|
|
22
|
+
warningExactRepeats: 1,
|
|
32
23
|
warningFamilyRepeats: 3,
|
|
33
24
|
},
|
|
34
25
|
implementation: {
|
|
35
26
|
softTotalSteps: 18,
|
|
36
27
|
softSearchSteps: 8,
|
|
37
28
|
softReadSteps: 6,
|
|
38
|
-
|
|
39
|
-
maxNoProgressReadExactRepeats: 1,
|
|
40
|
-
maxNoProgressExactRepeats: 3,
|
|
41
|
-
maxNoProgressFamilyRepeats: 4,
|
|
29
|
+
warningExactRepeats: 1,
|
|
42
30
|
warningFamilyRepeats: 3,
|
|
43
31
|
},
|
|
44
32
|
code_review: {
|
|
45
33
|
softTotalSteps: 14,
|
|
46
34
|
softSearchSteps: 6,
|
|
47
35
|
softReadSteps: 8,
|
|
48
|
-
|
|
49
|
-
maxNoProgressReadExactRepeats: 2,
|
|
50
|
-
maxNoProgressExactRepeats: 3,
|
|
51
|
-
maxNoProgressFamilyRepeats: 4,
|
|
36
|
+
warningExactRepeats: 2,
|
|
52
37
|
warningFamilyRepeats: 3,
|
|
53
38
|
},
|
|
54
39
|
code_explanation: {
|
|
55
40
|
softTotalSteps: 12,
|
|
56
41
|
softSearchSteps: 6,
|
|
57
42
|
softReadSteps: 8,
|
|
58
|
-
|
|
59
|
-
maxNoProgressReadExactRepeats: 2,
|
|
60
|
-
maxNoProgressExactRepeats: 3,
|
|
61
|
-
maxNoProgressFamilyRepeats: 4,
|
|
43
|
+
warningExactRepeats: 2,
|
|
62
44
|
warningFamilyRepeats: 3,
|
|
63
45
|
},
|
|
64
46
|
repo_orientation: {
|
|
65
47
|
softTotalSteps: 12,
|
|
66
48
|
softSearchSteps: 6,
|
|
67
49
|
softReadSteps: 8,
|
|
68
|
-
|
|
69
|
-
maxNoProgressReadExactRepeats: 2,
|
|
70
|
-
maxNoProgressExactRepeats: 3,
|
|
71
|
-
maxNoProgressFamilyRepeats: 4,
|
|
50
|
+
warningExactRepeats: 2,
|
|
72
51
|
warningFamilyRepeats: 3,
|
|
73
52
|
},
|
|
74
53
|
product_discussion: {
|
|
75
54
|
softTotalSteps: 10,
|
|
76
55
|
softSearchSteps: 4,
|
|
77
56
|
softReadSteps: 4,
|
|
78
|
-
|
|
79
|
-
maxNoProgressReadExactRepeats: 2,
|
|
80
|
-
maxNoProgressExactRepeats: 2,
|
|
81
|
-
maxNoProgressFamilyRepeats: 3,
|
|
57
|
+
warningExactRepeats: 2,
|
|
82
58
|
warningFamilyRepeats: 2,
|
|
83
59
|
},
|
|
84
60
|
general: {
|
|
85
61
|
softTotalSteps: 18,
|
|
86
62
|
softSearchSteps: 8,
|
|
87
63
|
softReadSteps: 10,
|
|
88
|
-
|
|
89
|
-
maxNoProgressReadExactRepeats: 2,
|
|
90
|
-
maxNoProgressExactRepeats: 3,
|
|
91
|
-
maxNoProgressFamilyRepeats: 4,
|
|
64
|
+
warningExactRepeats: 2,
|
|
92
65
|
warningFamilyRepeats: 3,
|
|
93
66
|
},
|
|
94
67
|
};
|
|
95
|
-
const SEARCH_TOOLS_DISABLED = new Set(["grep", "web_search", "web_fetch"]);
|
|
96
|
-
const EXPLORATION_TOOLS_DISABLED = new Set(["read", "glob", "grep", "web_search", "web_fetch", "spawn_agent", "wait_agent", "send_input", "tool_search"]);
|
|
97
68
|
export class ExecutionGovernor {
|
|
98
|
-
taskType;
|
|
99
69
|
budget;
|
|
100
70
|
history = [];
|
|
101
71
|
totalSteps = 0;
|
|
102
72
|
searchSteps = 0;
|
|
103
73
|
readSteps = 0;
|
|
104
|
-
|
|
105
|
-
searchFrozen = false;
|
|
106
|
-
explorationFrozen = false;
|
|
107
|
-
phase = "explore";
|
|
108
|
-
codeChanged = false;
|
|
74
|
+
mutationVersion = 0;
|
|
109
75
|
reminderQueue = [];
|
|
110
76
|
warnedFamilies = new Set();
|
|
77
|
+
warnedSignatures = new Set();
|
|
111
78
|
softTotalWarned = false;
|
|
112
79
|
softSearchWarned = false;
|
|
113
80
|
softReadWarned = false;
|
|
114
81
|
constructor(taskType) {
|
|
115
|
-
this.taskType = taskType;
|
|
116
82
|
this.budget = BUDGETS[taskType];
|
|
117
83
|
if (taskType === "security_investigation") {
|
|
118
84
|
this.reminderQueue.push(buildInvestigationReminder());
|
|
@@ -128,62 +94,33 @@ export class ExecutionGovernor {
|
|
|
128
94
|
totalSteps: this.totalSteps,
|
|
129
95
|
searchSteps: this.searchSteps,
|
|
130
96
|
readSteps: this.readSteps,
|
|
131
|
-
searchFrozen:
|
|
132
|
-
explorationFrozen:
|
|
133
|
-
phase:
|
|
97
|
+
searchFrozen: false,
|
|
98
|
+
explorationFrozen: false,
|
|
99
|
+
phase: "observe",
|
|
134
100
|
};
|
|
135
101
|
}
|
|
136
102
|
filterToolDefinitions(toolDefinitions) {
|
|
137
|
-
|
|
138
|
-
if (this.explorationFrozen) {
|
|
139
|
-
filtered = filtered.filter((tool) => !EXPLORATION_TOOLS_DISABLED.has(tool.name));
|
|
140
|
-
}
|
|
141
|
-
else if (this.searchFrozen) {
|
|
142
|
-
filtered = filtered.filter((tool) => !SEARCH_TOOLS_DISABLED.has(tool.name));
|
|
143
|
-
}
|
|
144
|
-
return filtered;
|
|
103
|
+
return toolDefinitions;
|
|
145
104
|
}
|
|
146
105
|
beforeToolCall(toolCall) {
|
|
147
106
|
const intent = analyzeToolIntent(toolCall);
|
|
148
|
-
if (
|
|
149
|
-
return {
|
|
150
|
-
blockedResult: blockedResult("Exploration blocked: this implementation task already has enough context. Use edit/write, verify an existing change, or explain the blocker.", "blocked", "Exploration frozen because tool calls stopped producing task progress.", metadataKindForFamily(intent.family)),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
if (this.isModificationTask() && !this.codeChanged && intent.family === "read") {
|
|
107
|
+
if (intent.family === "read") {
|
|
154
108
|
const signature = intent.read?.signature;
|
|
155
|
-
if (signature && this.
|
|
156
|
-
this.
|
|
157
|
-
return {
|
|
158
|
-
blockedResult: blockedResult("Read blocked: this file range was already read. You have enough context to make the requested change; use edit/write now or explain the blocker.", "blocked", "Repeated identical read before modification.", "read"),
|
|
159
|
-
};
|
|
109
|
+
if (signature && this.hasCurrentMutationObservation((entry) => entry.signature === signature)) {
|
|
110
|
+
this.warnOnce(`read:${signature}`, "This exact file range was already read since the last successful edit/write. If the content is still available and nothing changed, use the prior result; otherwise it is okay to re-read to recover context or verify a change.");
|
|
160
111
|
}
|
|
161
112
|
}
|
|
162
113
|
if (intent.family === "search") {
|
|
163
|
-
if (this.searchFrozen) {
|
|
164
|
-
return {
|
|
165
|
-
blockedResult: blockedResult("Search blocked: repeated low-yield searching is now frozen for this task.", "blocked", "Search frozen due to repeated low-yield searching.", "search"),
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
114
|
const signature = intent.search?.signature;
|
|
169
115
|
const familyKey = intent.search?.familyKey;
|
|
170
|
-
if (signature && this.trailingNoProgressCount((entry) => entry.signature === signature) >= this.budget.
|
|
171
|
-
this.
|
|
172
|
-
return {
|
|
173
|
-
blockedResult: blockedResult("Search blocked: repeated the same search multiple times without new evidence.", "blocked", "Repeated identical search without progress.", "search"),
|
|
174
|
-
};
|
|
116
|
+
if (signature && this.trailingNoProgressCount((entry) => entry.signature === signature) >= this.budget.warningExactRepeats) {
|
|
117
|
+
this.warnOnce(`search:${signature}`, "This search is very similar to one you already ran and it did not produce new evidence. Change the query/path, follow a concrete lead, or summarize the strongest findings.");
|
|
175
118
|
}
|
|
176
119
|
if (familyKey) {
|
|
177
120
|
const familyNoProgress = this.trailingNoProgressCount((entry) => entry.familyKey === familyKey);
|
|
178
|
-
if (familyNoProgress >= this.budget.maxNoProgressFamilyRepeats) {
|
|
179
|
-
this.freezeSearch(`Repeated the same search family without new evidence: ${familyKey}`);
|
|
180
|
-
return {
|
|
181
|
-
blockedResult: blockedResult("Search blocked: repeated the same search family without new evidence.", "blocked", "Repeated similar searches without progress.", "search"),
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
121
|
if (familyNoProgress >= this.budget.warningFamilyRepeats && !this.warnedFamilies.has(familyKey)) {
|
|
185
122
|
this.warnedFamilies.add(familyKey);
|
|
186
|
-
this.reminderQueue.push(buildLoopWarningReminder("Repeated searches are yielding little new evidence. Change your hypothesis, narrow the path, or summarize current findings instead of repeating variants."));
|
|
123
|
+
this.reminderQueue.push(buildLoopWarningReminder("Repeated searches in the same family are yielding little new evidence. Change your hypothesis, narrow the path, follow a specific file lead, or summarize current findings instead of repeating variants."));
|
|
187
124
|
}
|
|
188
125
|
}
|
|
189
126
|
}
|
|
@@ -194,9 +131,6 @@ export class ExecutionGovernor {
|
|
|
194
131
|
if (intent.family === "read") {
|
|
195
132
|
this.readSteps += 1;
|
|
196
133
|
}
|
|
197
|
-
if (isExplorationIntent(intent) && !this.codeChanged) {
|
|
198
|
-
this.explorationStepsWithoutWrite += 1;
|
|
199
|
-
}
|
|
200
134
|
this.maybeWarnOnSoftBudgets(intent.family === "search", intent.family === "read");
|
|
201
135
|
return {};
|
|
202
136
|
}
|
|
@@ -211,23 +145,19 @@ export class ExecutionGovernor {
|
|
|
211
145
|
signature: intent.search?.signature ?? intent.read?.signature,
|
|
212
146
|
familyKey: intent.search?.familyKey ?? intent.read?.familyKey,
|
|
213
147
|
progress,
|
|
148
|
+
mutationVersion: this.mutationVersion,
|
|
214
149
|
});
|
|
215
150
|
if (isSuccessfulWriteIntent(intent, result)) {
|
|
216
|
-
this.
|
|
217
|
-
this.phase = "verify";
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (this.isModificationTask()
|
|
221
|
-
&& !this.codeChanged
|
|
222
|
-
&& isExplorationIntent(intent)
|
|
223
|
-
&& this.explorationStepsWithoutWrite >= this.budget.maxExplorationStepsWithoutWrite) {
|
|
224
|
-
this.enterModifyPhase(`Used ${this.explorationStepsWithoutWrite} exploration tools without editing files.`);
|
|
151
|
+
this.mutationVersion += 1;
|
|
225
152
|
}
|
|
226
153
|
}
|
|
227
154
|
trailingNoProgressCount(predicate) {
|
|
228
155
|
let count = 0;
|
|
229
156
|
for (let index = this.history.length - 1; index >= 0; index--) {
|
|
230
157
|
const entry = this.history[index];
|
|
158
|
+
if (entry.mutationVersion !== this.mutationVersion) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
231
161
|
if (!predicate(entry)) {
|
|
232
162
|
break;
|
|
233
163
|
}
|
|
@@ -238,27 +168,15 @@ export class ExecutionGovernor {
|
|
|
238
168
|
}
|
|
239
169
|
return count;
|
|
240
170
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
this.searchFrozen = true;
|
|
246
|
-
this.reminderQueue.push(buildSearchFreezeReminder(reason));
|
|
171
|
+
hasCurrentMutationObservation(predicate) {
|
|
172
|
+
return this.history.some((entry) => entry.mutationVersion === this.mutationVersion && predicate(entry));
|
|
247
173
|
}
|
|
248
|
-
|
|
249
|
-
if (this.
|
|
174
|
+
warnOnce(key, reason) {
|
|
175
|
+
if (this.warnedSignatures.has(key)) {
|
|
250
176
|
return;
|
|
251
177
|
}
|
|
252
|
-
this.
|
|
253
|
-
this.
|
|
254
|
-
this.searchFrozen = true;
|
|
255
|
-
this.reminderQueue.push(buildExplorationFreezeReminder(reason));
|
|
256
|
-
}
|
|
257
|
-
isModificationTask() {
|
|
258
|
-
return this.taskType === "implementation" || this.taskType === "debugging";
|
|
259
|
-
}
|
|
260
|
-
historyCount(predicate) {
|
|
261
|
-
return this.history.reduce((count, entry) => count + (predicate(entry) ? 1 : 0), 0);
|
|
178
|
+
this.warnedSignatures.add(key);
|
|
179
|
+
this.reminderQueue.push(buildLoopWarningReminder(reason));
|
|
262
180
|
}
|
|
263
181
|
maybeWarnOnSoftBudgets(isSearchStep, isReadStep) {
|
|
264
182
|
if (!this.softTotalWarned && this.totalSteps >= this.budget.softTotalSteps) {
|
|
@@ -275,28 +193,12 @@ export class ExecutionGovernor {
|
|
|
275
193
|
}
|
|
276
194
|
}
|
|
277
195
|
}
|
|
278
|
-
function isExplorationIntent(intent) {
|
|
279
|
-
return intent.family === "search" || intent.family === "read" || intent.family === "web";
|
|
280
|
-
}
|
|
281
196
|
function isSuccessfulWriteIntent(intent, result) {
|
|
282
197
|
if (result.isError || result.status === "blocked" || result.status === "command_error") {
|
|
283
198
|
return false;
|
|
284
199
|
}
|
|
285
200
|
return intent.family === "write" || intent.family === "edit" || result.metadata?.kind === "write" || result.metadata?.kind === "edit";
|
|
286
201
|
}
|
|
287
|
-
function metadataKindForFamily(family) {
|
|
288
|
-
switch (family) {
|
|
289
|
-
case "search":
|
|
290
|
-
case "read":
|
|
291
|
-
case "write":
|
|
292
|
-
case "edit":
|
|
293
|
-
case "shell":
|
|
294
|
-
case "web":
|
|
295
|
-
return family;
|
|
296
|
-
default:
|
|
297
|
-
return "security";
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
202
|
function inferProgress(intent, result) {
|
|
301
203
|
if (result.status === "blocked" || result.status === "timeout" || result.status === "command_error") {
|
|
302
204
|
return false;
|
|
@@ -314,14 +216,3 @@ function inferProgress(intent, result) {
|
|
|
314
216
|
}
|
|
315
217
|
return !result.isError;
|
|
316
218
|
}
|
|
317
|
-
function blockedResult(content, status, reason, kind = "security") {
|
|
318
|
-
return {
|
|
319
|
-
content,
|
|
320
|
-
isError: true,
|
|
321
|
-
status,
|
|
322
|
-
metadata: {
|
|
323
|
-
kind,
|
|
324
|
-
reason,
|
|
325
|
-
},
|
|
326
|
-
};
|
|
327
|
-
}
|
package/dist/agent.d.ts
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Agent - The core decision loop.
|
|
3
3
|
* It maintains message state, calls the LLM, executes tools, and auto-continues.
|
|
4
4
|
*/
|
|
5
|
+
import { type ContextUsageSnapshot } from "./context/usage.js";
|
|
5
6
|
import type { AgentEvent, ContentPart, PermissionMode, Message, Provider, ThinkingLevel, Todo, ToolResult, ToolRegistryEntry, ToolUpdate } from "./types.js";
|
|
6
7
|
import { type TurnHooks } from "./orchestrator/hooks.js";
|
|
7
8
|
import { BudgetLedger } from "./agent/budget-ledger.js";
|
|
8
9
|
import { type AgentProfile, type SubagentRunResult } from "./agent/profiles.js";
|
|
9
10
|
import { type SubagentThreadSnapshot } from "./agent/subagent-control.js";
|
|
10
11
|
import type { SkillSummary } from "./skills/types.js";
|
|
12
|
+
import type { FileStateTracker } from "./tools/file-state.js";
|
|
11
13
|
export declare class AgentAbortError extends Error {
|
|
12
14
|
constructor(message?: string);
|
|
13
15
|
}
|
|
@@ -39,6 +41,7 @@ export interface AgentOptions {
|
|
|
39
41
|
};
|
|
40
42
|
skills?: SkillSummary[];
|
|
41
43
|
memoryPrompt?: string;
|
|
44
|
+
fileStateTracker?: FileStateTracker;
|
|
42
45
|
}
|
|
43
46
|
export declare class Agent {
|
|
44
47
|
messages: Message[];
|
|
@@ -65,6 +68,7 @@ export declare class Agent {
|
|
|
65
68
|
private budgetSource;
|
|
66
69
|
private skillSummaries;
|
|
67
70
|
private memoryPrompt?;
|
|
71
|
+
private fileStateTracker?;
|
|
68
72
|
private subagentThreads;
|
|
69
73
|
private pendingSubagentUpdates;
|
|
70
74
|
private lastInputTokens;
|
|
@@ -74,8 +78,10 @@ export declare class Agent {
|
|
|
74
78
|
unlockDeferredTools(names: string[]): void;
|
|
75
79
|
/** All deferred tools in this session (for tool_search to inspect). */
|
|
76
80
|
listDeferredTools(): ToolRegistryEntry[];
|
|
81
|
+
getContextUsageSnapshot(): ContextUsageSnapshot;
|
|
77
82
|
/** Whether a given tool is deferred and not yet unlocked. */
|
|
78
83
|
isDeferredAndLocked(name: string): boolean;
|
|
84
|
+
private getActiveToolEntries;
|
|
79
85
|
injectSystemReminder(content: string): void;
|
|
80
86
|
injectModeReminder(): void;
|
|
81
87
|
get model(): string;
|
package/dist/agent.js
CHANGED
|
@@ -6,6 +6,7 @@ import { compactMessages } from "./context/compact.js";
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { compactMessagesWithLLM } from "./context/compact-llm.js";
|
|
8
8
|
import { getContextBudget } from "./context/budget.js";
|
|
9
|
+
import { buildContextUsageSnapshot } from "./context/usage.js";
|
|
9
10
|
import { isContextOverflowError } from "./context/overflow.js";
|
|
10
11
|
import { projectMessages } from "./context/projector.js";
|
|
11
12
|
import { aggressivePruneMessages } from "./context/prune.js";
|
|
@@ -18,6 +19,7 @@ import { assignAgentNickname, builtinAgentProfiles, mergeUsage, selectToolsForAg
|
|
|
18
19
|
import { snapshotSubagentThread, subagentResultFromThread } from "./agent/subagent-control.js";
|
|
19
20
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
20
21
|
import { isOnlyProviderProtocolArtifacts, stripProviderProtocolArtifacts } from "./provider-artifacts.js";
|
|
22
|
+
import { debugReasoningStream, summarizeDebugText } from "./reasoning-debug.js";
|
|
21
23
|
const MAX_CONSECUTIVE_OVERFLOW_RECOVERIES = 3;
|
|
22
24
|
const RESIDENT_HISTORY_KEEP_RECENT_TURNS = 3;
|
|
23
25
|
const RESIDENT_HISTORY_MESSAGE_LIMIT = 160;
|
|
@@ -56,6 +58,7 @@ export class Agent {
|
|
|
56
58
|
budgetSource;
|
|
57
59
|
skillSummaries;
|
|
58
60
|
memoryPrompt;
|
|
61
|
+
fileStateTracker;
|
|
59
62
|
subagentThreads = new Map();
|
|
60
63
|
pendingSubagentUpdates = [];
|
|
61
64
|
lastInputTokens = null;
|
|
@@ -80,6 +83,7 @@ export class Agent {
|
|
|
80
83
|
this.budgetSource = options.budgetSource ?? { runId: this.sessionID ?? "agent" };
|
|
81
84
|
this.skillSummaries = options.skills ?? [];
|
|
82
85
|
this.memoryPrompt = options.memoryPrompt;
|
|
86
|
+
this.fileStateTracker = options.fileStateTracker;
|
|
83
87
|
if (options.systemPrompt) {
|
|
84
88
|
this.messages.push({ role: "system", content: options.systemPrompt });
|
|
85
89
|
}
|
|
@@ -111,11 +115,26 @@ export class Agent {
|
|
|
111
115
|
listDeferredTools() {
|
|
112
116
|
return [...this.tools.values()].filter((t) => t.deferred);
|
|
113
117
|
}
|
|
118
|
+
getContextUsageSnapshot() {
|
|
119
|
+
return buildContextUsageSnapshot({
|
|
120
|
+
providerId: this.providerId,
|
|
121
|
+
modelId: this.apiModel,
|
|
122
|
+
messages: this.messages,
|
|
123
|
+
toolEntries: this.getActiveToolEntries(),
|
|
124
|
+
deferredToolEntries: this.listDeferredTools(),
|
|
125
|
+
skills: this.skillSummaries,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
114
128
|
/** Whether a given tool is deferred and not yet unlocked. */
|
|
115
129
|
isDeferredAndLocked(name) {
|
|
116
130
|
const tool = this.tools.get(name);
|
|
117
131
|
return !!tool?.deferred && !this.unlockedDeferred.has(name);
|
|
118
132
|
}
|
|
133
|
+
getActiveToolEntries() {
|
|
134
|
+
return [...this.tools.values()]
|
|
135
|
+
.filter((tool) => !tool.deferred || this.unlockedDeferred.has(tool.name))
|
|
136
|
+
.filter((tool) => this._mode === "plan" || tool.name !== "exit_plan_mode");
|
|
137
|
+
}
|
|
119
138
|
injectSystemReminder(content) {
|
|
120
139
|
this.appendMessage({ role: "meta", kind: "system-reminder", content });
|
|
121
140
|
}
|
|
@@ -320,6 +339,15 @@ export class Agent {
|
|
|
320
339
|
yield { type: "text_delta", content: chunk.content };
|
|
321
340
|
break;
|
|
322
341
|
case "reasoning_delta":
|
|
342
|
+
debugReasoningStream({
|
|
343
|
+
stage: "agent_receive",
|
|
344
|
+
providerId: this._providerId,
|
|
345
|
+
modelId: this.apiModel,
|
|
346
|
+
turnStep: step,
|
|
347
|
+
beforeLength: assistantMsg.reasoning?.length ?? 0,
|
|
348
|
+
delta: summarizeDebugText(chunk.content),
|
|
349
|
+
afterLength: (assistantMsg.reasoning?.length ?? 0) + chunk.content.length,
|
|
350
|
+
});
|
|
323
351
|
assistantMsg.reasoning = (assistantMsg.reasoning || "") + chunk.content;
|
|
324
352
|
yield { type: "reasoning_delta", content: chunk.content };
|
|
325
353
|
break;
|
|
@@ -533,7 +561,7 @@ export class Agent {
|
|
|
533
561
|
},
|
|
534
562
|
});
|
|
535
563
|
flushGovernorReminders();
|
|
536
|
-
yield { type: "turn_end", usage: turnUsage };
|
|
564
|
+
yield { type: "turn_end", usage: turnUsage, willContinue: true };
|
|
537
565
|
// Auto-continue: if we have tool results, the LLM needs to respond to them.
|
|
538
566
|
// Emitting the turn boundary keeps UI renderers aligned with the persisted
|
|
539
567
|
// assistant/tool message sequence instead of merging the next answer into
|
|
@@ -549,8 +577,9 @@ export class Agent {
|
|
|
549
577
|
flushReminders: flushGovernorReminders,
|
|
550
578
|
});
|
|
551
579
|
flushGovernorReminders();
|
|
552
|
-
|
|
553
|
-
|
|
580
|
+
const willContinue = !!hookState.forceContinuationReason;
|
|
581
|
+
yield { type: "turn_end", usage: turnUsage, willContinue };
|
|
582
|
+
if (willContinue) {
|
|
554
583
|
delete hookState.forceContinuationReason;
|
|
555
584
|
continue;
|
|
556
585
|
}
|
|
@@ -569,6 +598,7 @@ export class Agent {
|
|
|
569
598
|
if (afterTokens < beforeTokens) {
|
|
570
599
|
this.lastInputTokens = null;
|
|
571
600
|
this.lastAnchorMessageCount = null;
|
|
601
|
+
this.fileStateTracker?.invalidateReadHistory();
|
|
572
602
|
return before - this.messages.length;
|
|
573
603
|
}
|
|
574
604
|
}
|
|
@@ -583,6 +613,7 @@ export class Agent {
|
|
|
583
613
|
this.messages = llmResult.messages;
|
|
584
614
|
this.lastInputTokens = null;
|
|
585
615
|
this.lastAnchorMessageCount = null;
|
|
616
|
+
this.fileStateTracker?.invalidateReadHistory();
|
|
586
617
|
return before - this.messages.length;
|
|
587
618
|
}
|
|
588
619
|
const fallback = compactMessages(this.messages, { keepRecentTurns });
|
|
@@ -590,6 +621,7 @@ export class Agent {
|
|
|
590
621
|
this.messages = fallback.messages;
|
|
591
622
|
this.lastInputTokens = null;
|
|
592
623
|
this.lastAnchorMessageCount = null;
|
|
624
|
+
this.fileStateTracker?.invalidateReadHistory();
|
|
593
625
|
return before - this.messages.length;
|
|
594
626
|
}
|
|
595
627
|
return 0;
|
|
@@ -1056,6 +1088,7 @@ export class Agent {
|
|
|
1056
1088
|
this.messages = candidate;
|
|
1057
1089
|
this.lastInputTokens = null;
|
|
1058
1090
|
this.lastAnchorMessageCount = null;
|
|
1091
|
+
this.fileStateTracker?.invalidateReadHistory();
|
|
1059
1092
|
}
|
|
1060
1093
|
}
|
|
1061
1094
|
appendMessage(message) {
|
package/dist/context/budget.d.ts
CHANGED
|
@@ -19,3 +19,4 @@ export interface ContextBudgetOptions {
|
|
|
19
19
|
export declare function estimateMessageTokens(message: Message): number;
|
|
20
20
|
export declare function estimateContextTokens(messages: Message[]): number;
|
|
21
21
|
export declare function getContextBudget(providerId: string, modelId: string, messages: Message[], options?: ContextBudgetOptions): ContextBudget;
|
|
22
|
+
export declare function estimateTextTokens(text: string): number;
|
package/dist/context/budget.js
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SkillSummary } from "../skills/types.js";
|
|
2
|
+
import type { Message, ToolRegistryEntry } from "../types.js";
|
|
3
|
+
export interface ContextUsageBucket {
|
|
4
|
+
label: string;
|
|
5
|
+
tokens: number;
|
|
6
|
+
detail?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ContextUsageSnapshot {
|
|
9
|
+
providerId: string;
|
|
10
|
+
modelId: string;
|
|
11
|
+
contextWindow?: number;
|
|
12
|
+
usedTokens: number;
|
|
13
|
+
freeTokens?: number;
|
|
14
|
+
buckets: {
|
|
15
|
+
systemPrompt: ContextUsageBucket;
|
|
16
|
+
tools: ContextUsageBucket;
|
|
17
|
+
skills: ContextUsageBucket;
|
|
18
|
+
deferredTools: ContextUsageBucket;
|
|
19
|
+
other: ContextUsageBucket;
|
|
20
|
+
};
|
|
21
|
+
toolCount: number;
|
|
22
|
+
deferredToolCount: number;
|
|
23
|
+
skillCount: number;
|
|
24
|
+
messageCount: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function buildContextUsageSnapshot(input: {
|
|
27
|
+
providerId: string;
|
|
28
|
+
modelId: string;
|
|
29
|
+
messages: Message[];
|
|
30
|
+
toolEntries: ToolRegistryEntry[];
|
|
31
|
+
deferredToolEntries?: ToolRegistryEntry[];
|
|
32
|
+
skills: SkillSummary[];
|
|
33
|
+
}): ContextUsageSnapshot;
|
|
34
|
+
export declare function formatContextUsage(snapshot: ContextUsageSnapshot): string;
|