xibecode 0.7.6 → 0.9.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 +20 -44
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +9 -1462
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/run-pr.d.ts.map +1 -1
- package/dist/commands/run-pr.js +9 -1
- package/dist/commands/run-pr.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +46 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/components/AssistantMarkdown.d.ts +10 -0
- package/dist/components/AssistantMarkdown.d.ts.map +1 -0
- package/dist/components/AssistantMarkdown.js +25 -0
- package/dist/components/AssistantMarkdown.js.map +1 -0
- package/dist/components/design-system/ThemeProvider.d.ts +13 -0
- package/dist/components/design-system/ThemeProvider.d.ts.map +1 -0
- package/dist/components/design-system/ThemeProvider.js +21 -0
- package/dist/components/design-system/ThemeProvider.js.map +1 -0
- package/dist/components/design-system/ThemedBox.d.ts +18 -0
- package/dist/components/design-system/ThemedBox.d.ts.map +1 -0
- package/dist/components/design-system/ThemedBox.js +27 -0
- package/dist/components/design-system/ThemedBox.js.map +1 -0
- package/dist/components/design-system/ThemedText.d.ts +11 -0
- package/dist/components/design-system/ThemedText.d.ts.map +1 -0
- package/dist/components/design-system/ThemedText.js +23 -0
- package/dist/components/design-system/ThemedText.js.map +1 -0
- package/dist/core/agent-tool-policies.d.ts +5 -0
- package/dist/core/agent-tool-policies.d.ts.map +1 -0
- package/dist/core/agent-tool-policies.js +18 -0
- package/dist/core/agent-tool-policies.js.map +1 -0
- package/dist/core/agent.d.ts +30 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +320 -100
- package/dist/core/agent.js.map +1 -1
- package/dist/core/background-agent.d.ts.map +1 -1
- package/dist/core/background-agent.js +4 -0
- package/dist/core/background-agent.js.map +1 -1
- package/dist/core/context-compactor.d.ts +10 -0
- package/dist/core/context-compactor.d.ts.map +1 -0
- package/dist/core/context-compactor.js +158 -0
- package/dist/core/context-compactor.js.map +1 -0
- package/dist/core/conversation-recovery.d.ts +9 -0
- package/dist/core/conversation-recovery.d.ts.map +1 -0
- package/dist/core/conversation-recovery.js +15 -0
- package/dist/core/conversation-recovery.js.map +1 -0
- package/dist/core/debug-workflow.d.ts +9 -0
- package/dist/core/debug-workflow.d.ts.map +1 -0
- package/dist/core/debug-workflow.js +19 -0
- package/dist/core/debug-workflow.js.map +1 -0
- package/dist/core/memory-promotions.d.ts +15 -0
- package/dist/core/memory-promotions.d.ts.map +1 -0
- package/dist/core/memory-promotions.js +38 -0
- package/dist/core/memory-promotions.js.map +1 -0
- package/dist/core/memory.d.ts +3 -1
- package/dist/core/memory.d.ts.map +1 -1
- package/dist/core/memory.js +27 -3
- package/dist/core/memory.js.map +1 -1
- package/dist/core/modes.d.ts +1 -0
- package/dist/core/modes.d.ts.map +1 -1
- package/dist/core/modes.js +94 -5
- package/dist/core/modes.js.map +1 -1
- package/dist/core/permission-store.d.ts +15 -0
- package/dist/core/permission-store.d.ts.map +1 -0
- package/dist/core/permission-store.js +30 -0
- package/dist/core/permission-store.js.map +1 -0
- package/dist/core/permissions.d.ts +33 -0
- package/dist/core/permissions.d.ts.map +1 -0
- package/dist/core/permissions.js +139 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/plan-artifacts.d.ts +10 -0
- package/dist/core/plan-artifacts.d.ts.map +1 -0
- package/dist/core/plan-artifacts.js +53 -0
- package/dist/core/plan-artifacts.js.map +1 -0
- package/dist/core/plan-session.d.ts +25 -0
- package/dist/core/plan-session.d.ts.map +1 -0
- package/dist/core/plan-session.js +95 -0
- package/dist/core/plan-session.js.map +1 -0
- package/dist/core/planMode.d.ts +5 -0
- package/dist/core/planMode.d.ts.map +1 -1
- package/dist/core/planMode.js +52 -1
- package/dist/core/planMode.js.map +1 -1
- package/dist/core/session-bridge.d.ts +12 -1
- package/dist/core/session-bridge.d.ts.map +1 -1
- package/dist/core/session-bridge.js +46 -0
- package/dist/core/session-bridge.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +3 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/swarm.d.ts.map +1 -1
- package/dist/core/swarm.js +2 -0
- package/dist/core/swarm.js.map +1 -1
- package/dist/core/task-status.d.ts +13 -0
- package/dist/core/task-status.d.ts.map +1 -0
- package/dist/core/task-status.js +17 -0
- package/dist/core/task-status.js.map +1 -0
- package/dist/core/tool-orchestrator.d.ts +22 -0
- package/dist/core/tool-orchestrator.d.ts.map +1 -0
- package/dist/core/tool-orchestrator.js +56 -0
- package/dist/core/tool-orchestrator.js.map +1 -0
- package/dist/core/tools.d.ts +6 -0
- package/dist/core/tools.d.ts.map +1 -1
- package/dist/core/tools.js +30 -1
- package/dist/core/tools.js.map +1 -1
- package/dist/core/transcript-cleanup.d.ts +8 -0
- package/dist/core/transcript-cleanup.d.ts.map +1 -0
- package/dist/core/transcript-cleanup.js +52 -0
- package/dist/core/transcript-cleanup.js.map +1 -0
- package/dist/ink.d.ts +24 -0
- package/dist/ink.d.ts.map +1 -0
- package/dist/ink.js +56 -0
- package/dist/ink.js.map +1 -0
- package/dist/interactiveHelpers.d.ts +9 -0
- package/dist/interactiveHelpers.d.ts.map +1 -0
- package/dist/interactiveHelpers.js +20 -0
- package/dist/interactiveHelpers.js.map +1 -0
- package/dist/types/command.d.ts +24 -0
- package/dist/types/command.d.ts.map +1 -0
- package/dist/types/command.js +2 -0
- package/dist/types/command.js.map +1 -0
- package/dist/ui/claude-style-chat.d.ts +11 -0
- package/dist/ui/claude-style-chat.d.ts.map +1 -0
- package/dist/ui/claude-style-chat.js +492 -0
- package/dist/ui/claude-style-chat.js.map +1 -0
- package/dist/utils/config.d.ts +6 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +1 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/renderOptions.d.ts +7 -0
- package/dist/utils/renderOptions.d.ts.map +1 -0
- package/dist/utils/renderOptions.js +60 -0
- package/dist/utils/renderOptions.js.map +1 -0
- package/dist/utils/tool-display.d.ts +8 -0
- package/dist/utils/tool-display.d.ts.map +1 -0
- package/dist/utils/tool-display.js +213 -0
- package/dist/utils/tool-display.js.map +1 -0
- package/dist/utils/tui-theme.d.ts +78 -0
- package/dist/utils/tui-theme.d.ts.map +1 -0
- package/dist/utils/tui-theme.js +76 -0
- package/dist/utils/tui-theme.js.map +1 -0
- package/dist/webui/server.d.ts.map +1 -1
- package/dist/webui/server.js +2 -1
- package/dist/webui/server.js.map +1 -1
- package/package.json +18 -13
package/dist/core/agent.js
CHANGED
|
@@ -5,16 +5,20 @@ import { EventEmitter } from 'events';
|
|
|
5
5
|
import { MODE_CONFIG, createModeState, transitionMode, ModeOrchestrator, parseModeRequest, stripModeRequests, parseTaskComplete, stripTaskComplete } from './modes.js';
|
|
6
6
|
import { NeuralMemory } from './memory.js';
|
|
7
7
|
import { PROVIDER_CONFIGS } from '../utils/config.js';
|
|
8
|
+
import { PermissionManager } from './permissions.js';
|
|
9
|
+
import { ToolOrchestrator } from './tool-orchestrator.js';
|
|
10
|
+
import { compactConversation } from './context-compactor.js';
|
|
8
11
|
export class LoopDetector {
|
|
9
12
|
history = [];
|
|
10
13
|
maxRepeats = 3;
|
|
11
14
|
timeWindow = 10000;
|
|
12
15
|
check(toolName, toolInput) {
|
|
13
|
-
const signature =
|
|
16
|
+
const signature = this.makeSignature(toolName, toolInput);
|
|
17
|
+
const coarse = this.makeCoarseSignature(toolName, toolInput);
|
|
14
18
|
const now = Date.now();
|
|
15
|
-
this.history.push({ tool: toolName,
|
|
19
|
+
this.history.push({ tool: toolName, signature, coarse, timestamp: now });
|
|
16
20
|
this.history = this.history.filter(h => now - h.timestamp < this.timeWindow);
|
|
17
|
-
const recentDuplicates = this.history.filter(h => h.
|
|
21
|
+
const recentDuplicates = this.history.filter(h => h.signature === signature);
|
|
18
22
|
if (recentDuplicates.length >= this.maxRepeats) {
|
|
19
23
|
return {
|
|
20
24
|
allowed: false,
|
|
@@ -22,7 +26,21 @@ export class LoopDetector {
|
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
const sameTool = this.history.filter(h => h.tool === toolName);
|
|
29
|
+
const sameCoarse = this.history.filter(h => h.coarse === coarse);
|
|
30
|
+
if (sameCoarse.length >= this.maxRepeats + 2) {
|
|
31
|
+
return {
|
|
32
|
+
allowed: false,
|
|
33
|
+
reason: `Loop detected: repeated ${toolName} attempts with near-identical input patterns`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
25
36
|
if (sameTool.length >= this.maxRepeats * 2) {
|
|
37
|
+
const uniquePatterns = new Set(sameTool.map(h => h.coarse)).size;
|
|
38
|
+
if (uniquePatterns <= 2) {
|
|
39
|
+
return {
|
|
40
|
+
allowed: false,
|
|
41
|
+
reason: `Loop detected: ${toolName} repeated ${sameTool.length} times with low-variation inputs`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
26
44
|
return {
|
|
27
45
|
allowed: true,
|
|
28
46
|
reason: `Warning: ${toolName} called ${sameTool.length} times recently`,
|
|
@@ -33,6 +51,34 @@ export class LoopDetector {
|
|
|
33
51
|
reset() {
|
|
34
52
|
this.history = [];
|
|
35
53
|
}
|
|
54
|
+
makeSignature(toolName, input) {
|
|
55
|
+
return JSON.stringify({
|
|
56
|
+
tool: toolName,
|
|
57
|
+
input: this.canonicalize(input),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
makeCoarseSignature(toolName, input) {
|
|
61
|
+
const canonical = this.canonicalize(input);
|
|
62
|
+
if (!canonical || typeof canonical !== 'object' || Array.isArray(canonical)) {
|
|
63
|
+
return `${toolName}:primitive`;
|
|
64
|
+
}
|
|
65
|
+
const keys = Object.keys(canonical).sort();
|
|
66
|
+
return `${toolName}:${keys.join(',')}`;
|
|
67
|
+
}
|
|
68
|
+
canonicalize(value) {
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
return value.map((item) => this.canonicalize(item));
|
|
71
|
+
}
|
|
72
|
+
if (value && typeof value === 'object') {
|
|
73
|
+
const inObj = value;
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const key of Object.keys(inObj).sort()) {
|
|
76
|
+
out[key] = this.canonicalize(inObj[key]);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
36
82
|
}
|
|
37
83
|
// ─── Think-tag streaming filter ───────────────────────────────
|
|
38
84
|
class ThinkTagFilter {
|
|
@@ -142,6 +188,9 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
142
188
|
contextHintFiles = [];
|
|
143
189
|
mindsetAdaptive = false;
|
|
144
190
|
currentMindset = 'convergent';
|
|
191
|
+
permissionManager;
|
|
192
|
+
toolOrchestrator;
|
|
193
|
+
evidenceTrail = [];
|
|
145
194
|
injectMessage(message) {
|
|
146
195
|
this.injectedMessages.push(message);
|
|
147
196
|
}
|
|
@@ -151,6 +200,97 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
151
200
|
clearInjectedMessages() {
|
|
152
201
|
this.injectedMessages = [];
|
|
153
202
|
}
|
|
203
|
+
recordEvidence(kind, detail) {
|
|
204
|
+
this.evidenceTrail.push({ kind, detail, ts: Date.now() });
|
|
205
|
+
if (this.evidenceTrail.length > 80) {
|
|
206
|
+
this.evidenceTrail = this.evidenceTrail.slice(-80);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
estimateMessageTokens(message) {
|
|
210
|
+
const text = (() => {
|
|
211
|
+
if (typeof message.content === 'string')
|
|
212
|
+
return message.content;
|
|
213
|
+
if (!Array.isArray(message.content))
|
|
214
|
+
return '';
|
|
215
|
+
return message.content
|
|
216
|
+
.map((block) => {
|
|
217
|
+
if (block?.type === 'text')
|
|
218
|
+
return String(block.text || '');
|
|
219
|
+
if (block?.type === 'tool_use') {
|
|
220
|
+
const input = block.input ? JSON.stringify(block.input) : '{}';
|
|
221
|
+
return `[tool_use:${String(block.name || 'unknown')}] ${input}`;
|
|
222
|
+
}
|
|
223
|
+
if (block?.type === 'tool_result') {
|
|
224
|
+
return `[tool_result:${String(block.tool_use_id || 'unknown')}] ${String(block.content || '')}`;
|
|
225
|
+
}
|
|
226
|
+
return String(block?.content ?? '');
|
|
227
|
+
})
|
|
228
|
+
.join('\n');
|
|
229
|
+
})();
|
|
230
|
+
// Rough estimate for modern tokenizers across mixed text/code.
|
|
231
|
+
return Math.ceil(text.length / 4);
|
|
232
|
+
}
|
|
233
|
+
estimateConversationTokens() {
|
|
234
|
+
return this.messages.reduce((sum, message) => sum + this.estimateMessageTokens(message), 0);
|
|
235
|
+
}
|
|
236
|
+
hasRecentGroundedEvidence(windowMs = 5 * 60 * 1000) {
|
|
237
|
+
const threshold = Date.now() - windowMs;
|
|
238
|
+
return this.evidenceTrail.some((entry) => entry.ts >= threshold);
|
|
239
|
+
}
|
|
240
|
+
shouldEnforceCompletionEvidence() {
|
|
241
|
+
if (this.config.completionEvidenceMode === 'off')
|
|
242
|
+
return false;
|
|
243
|
+
// Allow single-turn informational answers without tools.
|
|
244
|
+
if (this.toolCallCount === 0 && this.filesChanged.size === 0 && this.iterationCount <= 1) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
async postEditVerify(toolExecutor, toolUse, result) {
|
|
250
|
+
if (this.config.postEditVerification === 'off') {
|
|
251
|
+
return { ok: true, message: 'disabled' };
|
|
252
|
+
}
|
|
253
|
+
if (!['write_file', 'edit_file', 'edit_lines', 'verified_edit'].includes(toolUse.name)) {
|
|
254
|
+
return { ok: true, message: 'not-applicable' };
|
|
255
|
+
}
|
|
256
|
+
const input = (toolUse.input ?? {});
|
|
257
|
+
const path = (typeof result?.path === 'string' && result.path) ||
|
|
258
|
+
(typeof input.path === 'string' && input.path) ||
|
|
259
|
+
'';
|
|
260
|
+
if (!path) {
|
|
261
|
+
const strict = this.config.postEditVerification === 'strict';
|
|
262
|
+
return {
|
|
263
|
+
ok: !strict,
|
|
264
|
+
message: strict ? 'post-edit verification failed: missing path' : 'post-edit verification skipped: missing path',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const readArgs = { path };
|
|
268
|
+
if (typeof input.start_line === 'number' && typeof input.end_line === 'number') {
|
|
269
|
+
readArgs.start_line = input.start_line;
|
|
270
|
+
readArgs.end_line = input.end_line;
|
|
271
|
+
}
|
|
272
|
+
const check = await toolExecutor.execute('read_file', readArgs);
|
|
273
|
+
if (check?.error || check?.success === false) {
|
|
274
|
+
return {
|
|
275
|
+
ok: false,
|
|
276
|
+
message: `post-edit verification failed: could not read ${path}`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (typeof input.new_content === 'string' && input.new_content.trim().length > 0) {
|
|
280
|
+
const observed = typeof check?.content === 'string' ? check.content : '';
|
|
281
|
+
const expected = input.new_content.trim();
|
|
282
|
+
const normalize = (v) => v.replace(/\s+/g, ' ').trim();
|
|
283
|
+
const expectedNeedle = normalize(expected).slice(0, 180);
|
|
284
|
+
if (expectedNeedle && !normalize(observed).includes(expectedNeedle)) {
|
|
285
|
+
return {
|
|
286
|
+
ok: this.config.postEditVerification !== 'strict',
|
|
287
|
+
message: `post-edit verification warning: updated content was not confidently observed in ${path}`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
this.recordEvidence('post_edit_verify', path);
|
|
292
|
+
return { ok: true, message: `verified ${path}` };
|
|
293
|
+
}
|
|
154
294
|
// Pricing per 1M tokens (input/output) — Claude models
|
|
155
295
|
static PRICING = {
|
|
156
296
|
'claude-sonnet-4-5-20250929': { input: 3, output: 15 },
|
|
@@ -174,6 +314,7 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
174
314
|
mode: config.mode ?? 'agent',
|
|
175
315
|
provider: config.provider ?? this.detectProvider(config.model),
|
|
176
316
|
customProviderFormat: config.customProviderFormat ?? 'openai',
|
|
317
|
+
requestFormat: config.requestFormat ?? 'auto',
|
|
177
318
|
planFirst: config.planFirst ?? false,
|
|
178
319
|
sessionMemory: config.sessionMemory,
|
|
179
320
|
contextHintFiles: config.contextHintFiles ?? [],
|
|
@@ -181,13 +322,18 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
181
322
|
executionModel: config.executionModel,
|
|
182
323
|
mindsetAdaptive: config.mindsetAdaptive ?? false,
|
|
183
324
|
strictTextOnlyCompletion: config.strictTextOnlyCompletion ?? false,
|
|
325
|
+
completionEvidenceMode: config.completionEvidenceMode ?? 'balanced',
|
|
326
|
+
postEditVerification: config.postEditVerification ?? 'balanced',
|
|
327
|
+
memoryRecallMinScore: config.memoryRecallMinScore ?? 2,
|
|
184
328
|
};
|
|
185
329
|
this.defaultSkillsPrompt = config.defaultSkillsPrompt ?? '';
|
|
186
330
|
this.mindsetAdaptive = this.config.mindsetAdaptive ?? false;
|
|
187
331
|
// Initialize mode state and orchestrator
|
|
188
332
|
this.modeState = createModeState(this.config.mode);
|
|
333
|
+
this.permissionManager = new PermissionManager(this.config.mode);
|
|
334
|
+
this.toolOrchestrator = new ToolOrchestrator();
|
|
189
335
|
this.modeOrchestrator = new ModeOrchestrator({
|
|
190
|
-
autoApprovalPolicy: '
|
|
336
|
+
autoApprovalPolicy: 'prompt-only',
|
|
191
337
|
allowAutoEscalation: true,
|
|
192
338
|
});
|
|
193
339
|
// Prefer explicit provider override from config, otherwise auto-detect
|
|
@@ -243,18 +389,21 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
243
389
|
this.iterationCount = 0;
|
|
244
390
|
this.toolCallCount = 0;
|
|
245
391
|
this.loopDetector.reset();
|
|
392
|
+
this.evidenceTrail = [];
|
|
246
393
|
this.messages.push({
|
|
247
394
|
role: 'user',
|
|
248
395
|
content: initialPrompt,
|
|
249
396
|
});
|
|
250
397
|
// ─── Neural Memory Recall ───
|
|
251
398
|
try {
|
|
252
|
-
const memories = await this.memory.retrieve(initialPrompt);
|
|
399
|
+
const memories = await this.memory.retrieve(initialPrompt, 5, this.config.memoryRecallMinScore);
|
|
253
400
|
if (memories.length > 0) {
|
|
254
401
|
const memoryContext = memories.map(m => `- [${new Date(m.timestamp).toISOString().split('T')[0]}] ${m.trigger} -> ${m.action} (${m.outcome})`).join('\n');
|
|
255
402
|
this.messages.push({
|
|
256
403
|
role: 'user',
|
|
257
|
-
content: `\n\n[Neural Memory Recall
|
|
404
|
+
content: `\n\n[Neural Memory Recall — UNVERIFIED HINTS]\n` +
|
|
405
|
+
`These are recall hints, not guaranteed facts. Verify with read_file / grep_code / tests before relying on them.\n` +
|
|
406
|
+
`${memoryContext}\n\nUser Prompt: ${initialPrompt}`
|
|
258
407
|
});
|
|
259
408
|
this.emit('thinking', { message: `Recalled ${memories.length} relevant memories` });
|
|
260
409
|
}
|
|
@@ -354,15 +503,29 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
354
503
|
this.sessionCost += (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000;
|
|
355
504
|
}
|
|
356
505
|
}
|
|
357
|
-
// Auto-compact
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.messages =
|
|
365
|
-
this.
|
|
506
|
+
// Auto-compact by estimated token budget (OpenClaude-style), not raw message count.
|
|
507
|
+
const compactionThreshold = 120_000;
|
|
508
|
+
const compactionTarget = 90_000;
|
|
509
|
+
let estimatedTokens = this.estimateConversationTokens();
|
|
510
|
+
if (estimatedTokens > compactionThreshold) {
|
|
511
|
+
const beforeCount = this.messages.length;
|
|
512
|
+
const compacted = compactConversation(this.messages, 24);
|
|
513
|
+
this.messages = compacted.messages;
|
|
514
|
+
estimatedTokens = this.estimateConversationTokens();
|
|
515
|
+
this.emit('warning', {
|
|
516
|
+
message: `${compacted.summaryNotice || 'Auto-compacted conversation to save context'} ` +
|
|
517
|
+
`(estimated tokens: ${estimatedTokens})`,
|
|
518
|
+
});
|
|
519
|
+
// If still above target, compact once more with tighter window.
|
|
520
|
+
if (estimatedTokens > compactionTarget && this.messages.length < beforeCount) {
|
|
521
|
+
const compactedAgain = compactConversation(this.messages, 16);
|
|
522
|
+
this.messages = compactedAgain.messages;
|
|
523
|
+
estimatedTokens = this.estimateConversationTokens();
|
|
524
|
+
this.emit('warning', {
|
|
525
|
+
message: `${compactedAgain.summaryNotice || 'Second compaction pass applied'} ` +
|
|
526
|
+
`(estimated tokens: ${estimatedTokens})`,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
366
529
|
}
|
|
367
530
|
// Process response
|
|
368
531
|
const content = response.content;
|
|
@@ -384,10 +547,13 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
384
547
|
this.modeState = this.modeOrchestrator.requestModeChange(this.modeState, modeRequest.mode, modeRequest.reason, 'model');
|
|
385
548
|
// Evaluate the request
|
|
386
549
|
const evaluation = this.modeOrchestrator.evaluateModeChangeRequest(this.modeState);
|
|
387
|
-
|
|
550
|
+
const permissionEvaluation = this.permissionManager.evaluateModeTransition(this.modeState.current, modeRequest.mode);
|
|
551
|
+
const approvedByPermission = permissionEvaluation.approved;
|
|
552
|
+
if (evaluation.approved && approvedByPermission) {
|
|
388
553
|
// Auto-approved - switch immediately
|
|
389
554
|
const oldMode = this.modeState.current;
|
|
390
555
|
this.modeState = transitionMode(this.modeState, modeRequest.mode, modeRequest.reason);
|
|
556
|
+
this.permissionManager.setMode(modeRequest.mode);
|
|
391
557
|
// Update tool executor mode
|
|
392
558
|
if (toolExecutor.setMode) {
|
|
393
559
|
toolExecutor.setMode(modeRequest.mode);
|
|
@@ -406,7 +572,7 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
406
572
|
to: modeRequest.mode,
|
|
407
573
|
reason: modeRequest.reason,
|
|
408
574
|
requiresConfirmation: evaluation.requiresConfirmation,
|
|
409
|
-
message: evaluation.reason,
|
|
575
|
+
message: permissionEvaluation.reason ?? evaluation.reason,
|
|
410
576
|
});
|
|
411
577
|
}
|
|
412
578
|
}
|
|
@@ -415,6 +581,7 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
415
581
|
if (taskComplete) {
|
|
416
582
|
// Switch back to team_leader mode
|
|
417
583
|
this.modeState = transitionMode(this.modeState, 'team_leader', 'Task completed: ' + taskComplete.summary);
|
|
584
|
+
this.permissionManager.setMode('team_leader');
|
|
418
585
|
this.emit('mode_changed', {
|
|
419
586
|
from: this.modeState.previous,
|
|
420
587
|
to: 'team_leader',
|
|
@@ -445,9 +612,27 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
445
612
|
}
|
|
446
613
|
// If no tools, we're done (unless run-pr-style strict completion requires TASK_COMPLETE)
|
|
447
614
|
if (toolUseBlocks.length === 0) {
|
|
615
|
+
const hasTaskComplete = textBlocks.some((b) => parseTaskComplete(b.text) != null);
|
|
616
|
+
const hasEvidence = this.hasRecentGroundedEvidence();
|
|
617
|
+
if (this.shouldEnforceCompletionEvidence()) {
|
|
618
|
+
const needsTaskComplete = this.config.completionEvidenceMode === 'strict' || this.toolCallCount > 0;
|
|
619
|
+
if ((needsTaskComplete && !hasTaskComplete) || !hasEvidence) {
|
|
620
|
+
const reason = !hasEvidence
|
|
621
|
+
? 'no recent grounded evidence'
|
|
622
|
+
: 'missing [[TASK_COMPLETE | summary=...]]';
|
|
623
|
+
this.emit('warning', {
|
|
624
|
+
message: `Completion evidence gate blocked finalize: ${reason}.`,
|
|
625
|
+
});
|
|
626
|
+
this.messages.push({
|
|
627
|
+
role: 'user',
|
|
628
|
+
content: '[SYSTEM] Completion gate: before finishing, provide grounded evidence from tool results/tests and then emit ' +
|
|
629
|
+
'[[TASK_COMPLETE | summary=<brief summary> | evidence=<paths/tests/tool proof>]]. Continue working until this is satisfied.',
|
|
630
|
+
});
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
448
634
|
if (this.config.strictTextOnlyCompletion &&
|
|
449
635
|
this.iterationCount < this.config.maxIterations) {
|
|
450
|
-
const hasTaskComplete = textBlocks.some((b) => parseTaskComplete(b.text) != null);
|
|
451
636
|
if (!hasTaskComplete) {
|
|
452
637
|
this.emit('warning', {
|
|
453
638
|
message: 'Assistant returned no tool calls without [[TASK_COMPLETE | summary=...]]; nudging to continue.',
|
|
@@ -468,69 +653,17 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
468
653
|
}
|
|
469
654
|
// Execute tools
|
|
470
655
|
const toolResults = [];
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
content: `Error: ${loopCheck.reason}. Try a different approach.`,
|
|
482
|
-
is_error: true,
|
|
483
|
-
});
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
if (loopCheck.reason) {
|
|
487
|
-
this.emit('warning', { message: loopCheck.reason });
|
|
488
|
-
}
|
|
489
|
-
// Emit tool call
|
|
490
|
-
this.emit('tool_call', {
|
|
491
|
-
name: toolUse.name,
|
|
492
|
-
input: toolUse.input,
|
|
493
|
-
index: i + 1,
|
|
656
|
+
const updates = await this.toolOrchestrator.executeBatches(toolUseBlocks, async (toolUse, i) => this.executeSingleToolUse(toolExecutor, toolUse, i));
|
|
657
|
+
for (const update of updates) {
|
|
658
|
+
const payload = typeof update.result === 'string'
|
|
659
|
+
? update.result
|
|
660
|
+
: JSON.stringify(update.result, null, 2);
|
|
661
|
+
toolResults.push({
|
|
662
|
+
type: 'tool_result',
|
|
663
|
+
tool_use_id: update.toolUse.id,
|
|
664
|
+
content: payload,
|
|
665
|
+
...(update.success ? {} : { is_error: true }),
|
|
494
666
|
});
|
|
495
|
-
// Execute
|
|
496
|
-
try {
|
|
497
|
-
const result = await toolExecutor.execute(toolUse.name, toolUse.input);
|
|
498
|
-
// Track file changes
|
|
499
|
-
if (['write_file', 'edit_file', 'edit_lines', 'verified_edit'].includes(toolUse.name)) {
|
|
500
|
-
const input = toolUse.input;
|
|
501
|
-
if (typeof input?.path === 'string')
|
|
502
|
-
this.filesChanged.add(input.path);
|
|
503
|
-
}
|
|
504
|
-
const success = !result.error && result.success !== false;
|
|
505
|
-
this.emit('tool_result', {
|
|
506
|
-
name: toolUse.name,
|
|
507
|
-
result,
|
|
508
|
-
success,
|
|
509
|
-
});
|
|
510
|
-
if (this.sessionMemory) {
|
|
511
|
-
const msg = typeof result === 'object' && result?.message != null ? String(result.message) : undefined;
|
|
512
|
-
this.sessionMemory.recordAttempt(toolUse.name, success, msg);
|
|
513
|
-
}
|
|
514
|
-
const payload = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
515
|
-
toolResults.push({
|
|
516
|
-
type: 'tool_result',
|
|
517
|
-
tool_use_id: toolUse.id,
|
|
518
|
-
content: payload,
|
|
519
|
-
...(success ? {} : { is_error: true }),
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
catch (error) {
|
|
523
|
-
this.emit('error', {
|
|
524
|
-
tool: toolUse.name,
|
|
525
|
-
error: error.message,
|
|
526
|
-
});
|
|
527
|
-
toolResults.push({
|
|
528
|
-
type: 'tool_result',
|
|
529
|
-
tool_use_id: toolUse.id,
|
|
530
|
-
content: `Error: ${error.message}`,
|
|
531
|
-
is_error: true,
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
667
|
}
|
|
535
668
|
// Add injected messages if any exist
|
|
536
669
|
if (this.injectedMessages.length > 0) {
|
|
@@ -562,19 +695,103 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
562
695
|
});
|
|
563
696
|
}
|
|
564
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* Call the model with streaming (fallback to non-streaming).
|
|
700
|
+
*/
|
|
701
|
+
async executeSingleToolUse(toolExecutor, toolUse, index) {
|
|
702
|
+
this.toolCallCount++;
|
|
703
|
+
const loopCheck = this.loopDetector.check(toolUse.name, toolUse.input);
|
|
704
|
+
if (!loopCheck.allowed) {
|
|
705
|
+
this.emit('warning', { message: loopCheck.reason });
|
|
706
|
+
return {
|
|
707
|
+
toolUse,
|
|
708
|
+
index,
|
|
709
|
+
result: `Error: ${loopCheck.reason}. Try a different approach.`,
|
|
710
|
+
success: false,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
if (loopCheck.reason) {
|
|
714
|
+
this.emit('warning', { message: loopCheck.reason });
|
|
715
|
+
}
|
|
716
|
+
this.emit('tool_call', {
|
|
717
|
+
name: toolUse.name,
|
|
718
|
+
input: toolUse.input,
|
|
719
|
+
index: index + 1,
|
|
720
|
+
});
|
|
721
|
+
this.recordEvidence('tool_call', toolUse.name);
|
|
722
|
+
try {
|
|
723
|
+
let result = await toolExecutor.execute(toolUse.name, toolUse.input);
|
|
724
|
+
if (['write_file', 'edit_file', 'edit_lines', 'verified_edit'].includes(toolUse.name)) {
|
|
725
|
+
const input = toolUse.input;
|
|
726
|
+
if (typeof input?.path === 'string')
|
|
727
|
+
this.filesChanged.add(input.path);
|
|
728
|
+
}
|
|
729
|
+
const verification = await this.postEditVerify(toolExecutor, toolUse, result);
|
|
730
|
+
if (!verification.ok) {
|
|
731
|
+
const currentMessage = typeof result?.message === 'string' && result.message
|
|
732
|
+
? `${result.message}; ${verification.message}`
|
|
733
|
+
: verification.message;
|
|
734
|
+
if (result && typeof result === 'object') {
|
|
735
|
+
result = { ...result, message: currentMessage, success: false, error: true, postVerify: verification.message };
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
result = { success: false, error: true, message: currentMessage, result };
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
else if (result && typeof result === 'object') {
|
|
742
|
+
result = { ...result, postVerify: verification.message };
|
|
743
|
+
}
|
|
744
|
+
const success = !result?.error && result?.success !== false;
|
|
745
|
+
this.emit('tool_result', {
|
|
746
|
+
name: toolUse.name,
|
|
747
|
+
result,
|
|
748
|
+
success,
|
|
749
|
+
});
|
|
750
|
+
this.recordEvidence(success ? 'tool_result_ok' : 'tool_result_error', `${toolUse.name}:${success ? 'ok' : 'error'}`);
|
|
751
|
+
if (this.sessionMemory) {
|
|
752
|
+
const msg = typeof result === 'object' && result?.message != null ? String(result.message) : undefined;
|
|
753
|
+
this.sessionMemory.recordAttempt(toolUse.name, success, msg);
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
toolUse,
|
|
757
|
+
index,
|
|
758
|
+
result,
|
|
759
|
+
success,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
this.emit('error', {
|
|
764
|
+
tool: toolUse.name,
|
|
765
|
+
error: error.message,
|
|
766
|
+
});
|
|
767
|
+
return {
|
|
768
|
+
toolUse,
|
|
769
|
+
index,
|
|
770
|
+
result: `Error: ${error.message}`,
|
|
771
|
+
success: false,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
}
|
|
565
775
|
/**
|
|
566
776
|
* Call the model with streaming (fallback to non-streaming).
|
|
567
777
|
*/
|
|
568
778
|
async callModel(tools) {
|
|
569
779
|
// Route to provider-specific implementation
|
|
570
780
|
// Check if the provider uses the Anthropic format or OpenAI format
|
|
571
|
-
// Check if the provider uses the Anthropic format or OpenAI format
|
|
572
781
|
let isAnthropicFormat = false;
|
|
573
|
-
|
|
574
|
-
|
|
782
|
+
const rf = this.config.requestFormat ?? 'auto';
|
|
783
|
+
if (rf === 'openai') {
|
|
784
|
+
isAnthropicFormat = false;
|
|
785
|
+
}
|
|
786
|
+
else if (rf === 'anthropic') {
|
|
787
|
+
isAnthropicFormat = true;
|
|
788
|
+
}
|
|
789
|
+
else if (this.provider !== 'custom') {
|
|
790
|
+
isAnthropicFormat =
|
|
791
|
+
PROVIDER_CONFIGS[this.provider]?.format ===
|
|
792
|
+
'anthropic';
|
|
575
793
|
}
|
|
576
|
-
|
|
577
|
-
if (this.provider === 'custom') {
|
|
794
|
+
else {
|
|
578
795
|
isAnthropicFormat = this.config.customProviderFormat === 'anthropic';
|
|
579
796
|
}
|
|
580
797
|
// If it's NOT Anthropic format, use the OpenAI-compatible client
|
|
@@ -727,27 +944,29 @@ export class EnhancedAgent extends EventEmitter {
|
|
|
727
944
|
* Uses streaming (SSE) when available, with a non-streaming fallback.
|
|
728
945
|
* Supports tools: sends them when provided and normalizes tool_calls in the response to Anthropic-style content blocks.
|
|
729
946
|
*/
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
// Determine Base URL: Config -> Provider Default -> OpenAI Default
|
|
947
|
+
/**
|
|
948
|
+
* OpenAI-compatible chat completions URL. Only appends `/chat/completions` to the
|
|
949
|
+
* configured base — do not inject an extra `/v1` segment (base URL must already
|
|
950
|
+
* include any API version prefix the user expects).
|
|
951
|
+
*/
|
|
952
|
+
buildOpenAIChatCompletionsUrl() {
|
|
737
953
|
let base = this.config.baseUrl;
|
|
738
954
|
if (!base && this.provider && this.provider !== 'custom' && PROVIDER_CONFIGS[this.provider]) {
|
|
739
955
|
base = PROVIDER_CONFIGS[this.provider].baseUrl;
|
|
740
956
|
}
|
|
741
|
-
base = (base || 'https://api.openai.com').replace(/\/+$/, '');
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (base.endsWith('/v1') || base.endsWith('/v4')) {
|
|
745
|
-
// Z.ai ends in /v4, others /v1. If already included, just append chat/completions
|
|
746
|
-
url = `${base}/chat/completions`;
|
|
957
|
+
base = (base || 'https://api.openai.com/v1').replace(/\/+$/, '');
|
|
958
|
+
if (/\/chat\/completions$/i.test(base)) {
|
|
959
|
+
return base;
|
|
747
960
|
}
|
|
748
|
-
|
|
749
|
-
|
|
961
|
+
return `${base}/chat/completions`;
|
|
962
|
+
}
|
|
963
|
+
async callOpenAI(tools) {
|
|
964
|
+
if (!this.config.apiKey) {
|
|
965
|
+
// Try to get from specifics if generic is missing, though ConfigManager should have handled this
|
|
966
|
+
// strict check might remain here
|
|
967
|
+
// throw new Error('API key is required for OpenAI-compatible provider');
|
|
750
968
|
}
|
|
969
|
+
const url = this.buildOpenAIChatCompletionsUrl();
|
|
751
970
|
const openAiMessages = this.buildOpenAIMessages();
|
|
752
971
|
const baseBody = {
|
|
753
972
|
model: this.getModelForTier(),
|
|
@@ -1346,6 +1565,7 @@ When you complete the task, provide a comprehensive summary including:
|
|
|
1346
1565
|
- Any trade-offs or design decisions made
|
|
1347
1566
|
- Potential improvements or follow-up tasks
|
|
1348
1567
|
- Test results and validation performed
|
|
1568
|
+
- If you used tools, finish with [[TASK_COMPLETE | summary=<brief summary> | evidence=<tool/test proof>]] only when work is actually complete.
|
|
1349
1569
|
|
|
1350
1570
|
${this.sessionMemory ? this.sessionMemory.getSummary() : ''}
|
|
1351
1571
|
${this.mindsetAdaptive ? `\n## Current reasoning mindset: ${this.currentMindset.toUpperCase()}\n${this.currentMindset === 'convergent' ? 'Focus on one solution; narrow options and commit. Use [[SET_MINDSET: divergent]] to explore alternatives, or [[SET_MINDSET: algorithmic]] for step-by-step.' : this.currentMindset === 'divergent' ? 'Explore alternatives; brainstorm. Use [[SET_MINDSET: convergent]] to narrow, or [[SET_MINDSET: algorithmic]] for step-by-step.' : 'Reason step-by-step; formal. Use [[SET_MINDSET: convergent]] to commit, or [[SET_MINDSET: divergent]] to explore.'}\n` : ''}
|