xibecode 1.0.2 → 1.0.6
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/commands/chat.d.ts +0 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +10 -7
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +5 -3
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/diagnostics.js +1 -1
- package/dist/commands/diagnostics.js.map +1 -1
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/resume.js +1 -1
- package/dist/commands/resume.js.map +1 -1
- package/dist/commands/run-pr.d.ts.map +1 -1
- package/dist/commands/run-pr.js +13 -10
- package/dist/commands/run-pr.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +17 -14
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +3 -2
- package/dist/commands/skills.js.map +1 -1
- package/dist/components/AssistantMarkdown.js +1 -1
- package/dist/components/AssistantMarkdown.js.map +1 -1
- package/dist/index.js +2 -39
- package/dist/index.js.map +1 -1
- package/dist/ui/claude-style-chat.d.ts.map +1 -1
- package/dist/ui/claude-style-chat.js +15 -11
- package/dist/ui/claude-style-chat.js.map +1 -1
- package/dist/utils/built-in-skills-dir.d.ts +7 -0
- package/dist/utils/built-in-skills-dir.d.ts.map +1 -0
- package/dist/utils/built-in-skills-dir.js +11 -0
- package/dist/utils/built-in-skills-dir.js.map +1 -0
- package/dist/utils/config.d.ts +2 -119
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +3 -88
- package/dist/utils/config.js.map +1 -1
- package/package.json +11 -26
- package/dist/commands/punycode.d.ts +0 -5
- package/dist/commands/punycode.d.ts.map +0 -1
- package/dist/commands/punycode.js +0 -48
- package/dist/commands/punycode.js.map +0 -1
- package/dist/commands/tui.d.ts +0 -9
- package/dist/commands/tui.d.ts.map +0 -1
- package/dist/commands/tui.js +0 -83
- package/dist/commands/tui.js.map +0 -1
- package/dist/core/agent-tool-policies.d.ts +0 -5
- package/dist/core/agent-tool-policies.d.ts.map +0 -1
- package/dist/core/agent-tool-policies.js +0 -18
- package/dist/core/agent-tool-policies.js.map +0 -1
- package/dist/core/agent.d.ts +0 -181
- package/dist/core/agent.d.ts.map +0 -1
- package/dist/core/agent.js +0 -1777
- package/dist/core/agent.js.map +0 -1
- package/dist/core/background-agent.d.ts +0 -23
- package/dist/core/background-agent.d.ts.map +0 -1
- package/dist/core/background-agent.js +0 -175
- package/dist/core/background-agent.js.map +0 -1
- package/dist/core/code-graph.d.ts +0 -18
- package/dist/core/code-graph.d.ts.map +0 -1
- package/dist/core/code-graph.js +0 -105
- package/dist/core/code-graph.js.map +0 -1
- package/dist/core/conflict-solver.d.ts +0 -26
- package/dist/core/conflict-solver.d.ts.map +0 -1
- package/dist/core/conflict-solver.js +0 -108
- package/dist/core/conflict-solver.js.map +0 -1
- package/dist/core/context-compactor.d.ts +0 -10
- package/dist/core/context-compactor.d.ts.map +0 -1
- package/dist/core/context-compactor.js +0 -158
- package/dist/core/context-compactor.js.map +0 -1
- package/dist/core/context-pruner.d.ts +0 -19
- package/dist/core/context-pruner.d.ts.map +0 -1
- package/dist/core/context-pruner.js +0 -103
- package/dist/core/context-pruner.js.map +0 -1
- package/dist/core/context.d.ts +0 -82
- package/dist/core/context.d.ts.map +0 -1
- package/dist/core/context.js +0 -273
- package/dist/core/context.js.map +0 -1
- package/dist/core/conversation-recovery.d.ts +0 -9
- package/dist/core/conversation-recovery.d.ts.map +0 -1
- package/dist/core/conversation-recovery.js +0 -15
- package/dist/core/conversation-recovery.js.map +0 -1
- package/dist/core/debug-workflow.d.ts +0 -9
- package/dist/core/debug-workflow.d.ts.map +0 -1
- package/dist/core/debug-workflow.js +0 -19
- package/dist/core/debug-workflow.js.map +0 -1
- package/dist/core/docs-scraper.d.ts +0 -40
- package/dist/core/docs-scraper.d.ts.map +0 -1
- package/dist/core/docs-scraper.js +0 -386
- package/dist/core/docs-scraper.js.map +0 -1
- package/dist/core/editor.d.ts +0 -87
- package/dist/core/editor.d.ts.map +0 -1
- package/dist/core/editor.js +0 -377
- package/dist/core/editor.js.map +0 -1
- package/dist/core/export.d.ts +0 -11
- package/dist/core/export.d.ts.map +0 -1
- package/dist/core/export.js +0 -54
- package/dist/core/export.js.map +0 -1
- package/dist/core/history-manager.d.ts +0 -75
- package/dist/core/history-manager.d.ts.map +0 -1
- package/dist/core/history-manager.js +0 -146
- package/dist/core/history-manager.js.map +0 -1
- package/dist/core/marketplace-client.d.ts +0 -52
- package/dist/core/marketplace-client.d.ts.map +0 -1
- package/dist/core/marketplace-client.js +0 -71
- package/dist/core/marketplace-client.js.map +0 -1
- package/dist/core/mcp/mcp-config.d.ts +0 -10
- package/dist/core/mcp/mcp-config.d.ts.map +0 -1
- package/dist/core/mcp/mcp-config.js +0 -70
- package/dist/core/mcp/mcp-config.js.map +0 -1
- package/dist/core/mcp/mcp-policy.d.ts +0 -17
- package/dist/core/mcp/mcp-policy.d.ts.map +0 -1
- package/dist/core/mcp/mcp-policy.js +0 -56
- package/dist/core/mcp/mcp-policy.js.map +0 -1
- package/dist/core/mcp/oauth-flow.d.ts +0 -30
- package/dist/core/mcp/oauth-flow.d.ts.map +0 -1
- package/dist/core/mcp/oauth-flow.js +0 -230
- package/dist/core/mcp/oauth-flow.js.map +0 -1
- package/dist/core/mcp/oauth-store.d.ts +0 -13
- package/dist/core/mcp/oauth-store.d.ts.map +0 -1
- package/dist/core/mcp/oauth-store.js +0 -68
- package/dist/core/mcp/oauth-store.js.map +0 -1
- package/dist/core/mcp/resolve-mcp-servers.d.ts +0 -16
- package/dist/core/mcp/resolve-mcp-servers.d.ts.map +0 -1
- package/dist/core/mcp/resolve-mcp-servers.js +0 -83
- package/dist/core/mcp/resolve-mcp-servers.js.map +0 -1
- package/dist/core/mcp-client.d.ts +0 -99
- package/dist/core/mcp-client.d.ts.map +0 -1
- package/dist/core/mcp-client.js +0 -315
- package/dist/core/mcp-client.js.map +0 -1
- package/dist/core/memory-promotions.d.ts +0 -15
- package/dist/core/memory-promotions.d.ts.map +0 -1
- package/dist/core/memory-promotions.js +0 -38
- package/dist/core/memory-promotions.js.map +0 -1
- package/dist/core/memory.d.ts +0 -32
- package/dist/core/memory.d.ts.map +0 -1
- package/dist/core/memory.js +0 -121
- package/dist/core/memory.js.map +0 -1
- package/dist/core/modes.d.ts +0 -432
- package/dist/core/modes.d.ts.map +0 -1
- package/dist/core/modes.js +0 -1088
- package/dist/core/modes.js.map +0 -1
- package/dist/core/pattern-miner.d.ts +0 -43
- package/dist/core/pattern-miner.d.ts.map +0 -1
- package/dist/core/pattern-miner.js +0 -123
- package/dist/core/pattern-miner.js.map +0 -1
- package/dist/core/permission-store.d.ts +0 -15
- package/dist/core/permission-store.d.ts.map +0 -1
- package/dist/core/permission-store.js +0 -30
- package/dist/core/permission-store.js.map +0 -1
- package/dist/core/permissions.d.ts +0 -33
- package/dist/core/permissions.d.ts.map +0 -1
- package/dist/core/permissions.js +0 -141
- package/dist/core/permissions.js.map +0 -1
- package/dist/core/plan-artifacts.d.ts +0 -10
- package/dist/core/plan-artifacts.d.ts.map +0 -1
- package/dist/core/plan-artifacts.js +0 -60
- package/dist/core/plan-artifacts.js.map +0 -1
- package/dist/core/plan-session.d.ts +0 -25
- package/dist/core/plan-session.d.ts.map +0 -1
- package/dist/core/plan-session.js +0 -99
- package/dist/core/plan-session.js.map +0 -1
- package/dist/core/planMode.d.ts +0 -51
- package/dist/core/planMode.d.ts.map +0 -1
- package/dist/core/planMode.js +0 -245
- package/dist/core/planMode.js.map +0 -1
- package/dist/core/plugins.d.ts +0 -96
- package/dist/core/plugins.d.ts.map +0 -1
- package/dist/core/plugins.js +0 -202
- package/dist/core/plugins.js.map +0 -1
- package/dist/core/session-bridge.d.ts +0 -128
- package/dist/core/session-bridge.d.ts.map +0 -1
- package/dist/core/session-bridge.js +0 -328
- package/dist/core/session-bridge.js.map +0 -1
- package/dist/core/session-manager.d.ts +0 -80
- package/dist/core/session-manager.d.ts.map +0 -1
- package/dist/core/session-manager.js +0 -166
- package/dist/core/session-manager.js.map +0 -1
- package/dist/core/session-memory.d.ts +0 -45
- package/dist/core/session-memory.d.ts.map +0 -1
- package/dist/core/session-memory.js +0 -103
- package/dist/core/session-memory.js.map +0 -1
- package/dist/core/skill-selection.d.ts +0 -36
- package/dist/core/skill-selection.d.ts.map +0 -1
- package/dist/core/skill-selection.js +0 -172
- package/dist/core/skill-selection.js.map +0 -1
- package/dist/core/skills-sh-client.d.ts +0 -19
- package/dist/core/skills-sh-client.d.ts.map +0 -1
- package/dist/core/skills-sh-client.js +0 -75
- package/dist/core/skills-sh-client.js.map +0 -1
- package/dist/core/skills.d.ts +0 -97
- package/dist/core/skills.d.ts.map +0 -1
- package/dist/core/skills.js +0 -339
- package/dist/core/skills.js.map +0 -1
- package/dist/core/swarm.d.ts +0 -34
- package/dist/core/swarm.d.ts.map +0 -1
- package/dist/core/swarm.js +0 -111
- package/dist/core/swarm.js.map +0 -1
- package/dist/core/task-status.d.ts +0 -13
- package/dist/core/task-status.d.ts.map +0 -1
- package/dist/core/task-status.js +0 -17
- package/dist/core/task-status.js.map +0 -1
- package/dist/core/tool-orchestrator.d.ts +0 -30
- package/dist/core/tool-orchestrator.d.ts.map +0 -1
- package/dist/core/tool-orchestrator.js +0 -89
- package/dist/core/tool-orchestrator.js.map +0 -1
- package/dist/core/tools.d.ts +0 -462
- package/dist/core/tools.d.ts.map +0 -1
- package/dist/core/tools.js +0 -2916
- package/dist/core/tools.js.map +0 -1
- package/dist/core/transcript-cleanup.d.ts +0 -8
- package/dist/core/transcript-cleanup.d.ts.map +0 -1
- package/dist/core/transcript-cleanup.js +0 -52
- package/dist/core/transcript-cleanup.js.map +0 -1
- package/dist/core/visual-feedback.d.ts +0 -20
- package/dist/core/visual-feedback.d.ts.map +0 -1
- package/dist/core/visual-feedback.js +0 -117
- package/dist/core/visual-feedback.js.map +0 -1
- package/dist/tools/browser.d.ts +0 -120
- package/dist/tools/browser.d.ts.map +0 -1
- package/dist/tools/browser.js +0 -439
- package/dist/tools/browser.js.map +0 -1
- package/dist/tools/test-generator.d.ts +0 -157
- package/dist/tools/test-generator.d.ts.map +0 -1
- package/dist/tools/test-generator.js +0 -893
- package/dist/tools/test-generator.js.map +0 -1
- package/dist/tui/InkApp.d.ts +0 -21
- package/dist/tui/InkApp.d.ts.map +0 -1
- package/dist/tui/InkApp.js +0 -146
- package/dist/tui/InkApp.js.map +0 -1
- package/dist/tui/MarkdownMessage.d.ts +0 -16
- package/dist/tui/MarkdownMessage.d.ts.map +0 -1
- package/dist/tui/MarkdownMessage.js +0 -63
- package/dist/tui/MarkdownMessage.js.map +0 -1
- package/dist/tui/blessed-chat.d.ts +0 -9
- package/dist/tui/blessed-chat.d.ts.map +0 -1
- package/dist/tui/blessed-chat.js +0 -887
- package/dist/tui/blessed-chat.js.map +0 -1
- package/dist/tui/markdown-to-blessed.d.ts +0 -6
- package/dist/tui/markdown-to-blessed.d.ts.map +0 -1
- package/dist/tui/markdown-to-blessed.js +0 -26
- package/dist/tui/markdown-to-blessed.js.map +0 -1
- package/dist/ui/ink/App.d.ts +0 -25
- package/dist/ui/ink/App.d.ts.map +0 -1
- package/dist/ui/ink/App.js +0 -372
- package/dist/ui/ink/App.js.map +0 -1
- package/dist/utils/at-references.d.ts +0 -14
- package/dist/utils/at-references.d.ts.map +0 -1
- package/dist/utils/at-references.js +0 -47
- package/dist/utils/at-references.js.map +0 -1
- package/dist/utils/auto-memory.d.ts +0 -24
- package/dist/utils/auto-memory.d.ts.map +0 -1
- package/dist/utils/auto-memory.js +0 -153
- package/dist/utils/auto-memory.js.map +0 -1
- package/dist/utils/git.d.ts +0 -89
- package/dist/utils/git.d.ts.map +0 -1
- package/dist/utils/git.js +0 -444
- package/dist/utils/git.js.map +0 -1
- package/dist/utils/mcp-servers-file.d.ts +0 -46
- package/dist/utils/mcp-servers-file.d.ts.map +0 -1
- package/dist/utils/mcp-servers-file.js +0 -212
- package/dist/utils/mcp-servers-file.js.map +0 -1
- package/dist/utils/safety.d.ts +0 -60
- package/dist/utils/safety.d.ts.map +0 -1
- package/dist/utils/safety.js +0 -254
- package/dist/utils/safety.js.map +0 -1
- package/dist/utils/smithery.d.ts +0 -25
- package/dist/utils/smithery.d.ts.map +0 -1
- package/dist/utils/smithery.js +0 -50
- package/dist/utils/smithery.js.map +0 -1
- package/dist/utils/testRunner.d.ts +0 -44
- package/dist/utils/testRunner.d.ts.map +0 -1
- package/dist/utils/testRunner.js +0 -270
- package/dist/utils/testRunner.js.map +0 -1
- package/dist/webui/server.d.ts +0 -99
- package/dist/webui/server.d.ts.map +0 -1
- package/dist/webui/server.js +0 -2619
- package/dist/webui/server.js.map +0 -1
- package/webui-dist/assets/index-CSla6Lzy.css +0 -32
- package/webui-dist/assets/index-G_Z4gzPy.js +0 -457
- package/webui-dist/assets/index-G_Z4gzPy.js.map +0 -1
- package/webui-dist/assets/xterm-Da5jL1MD.js +0 -10
- package/webui-dist/assets/xterm-Da5jL1MD.js.map +0 -1
- package/webui-dist/assets/xterm-addon-fit-CMeqLIvm.js +0 -2
- package/webui-dist/assets/xterm-addon-fit-CMeqLIvm.js.map +0 -1
- package/webui-dist/assets/xterm-addon-web-links-D6m8jNVE.js +0 -2
- package/webui-dist/assets/xterm-addon-web-links-D6m8jNVE.js.map +0 -1
- package/webui-dist/index.html +0 -15
package/dist/core/agent.js
DELETED
|
@@ -1,1777 +0,0 @@
|
|
|
1
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
-
import fetch from 'node-fetch';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import { readFile } from 'fs/promises';
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { EventEmitter } from 'events';
|
|
7
|
-
import { MODE_CONFIG, createModeState, transitionMode, ModeOrchestrator, parseModeRequest, stripModeRequests, parseTaskComplete, stripTaskComplete } from './modes.js';
|
|
8
|
-
import { NeuralMemory } from './memory.js';
|
|
9
|
-
import { PROVIDER_CONFIGS } from '../utils/config.js';
|
|
10
|
-
import { PermissionManager } from './permissions.js';
|
|
11
|
-
import { ToolOrchestrator } from './tool-orchestrator.js';
|
|
12
|
-
import { compactConversation } from './context-compactor.js';
|
|
13
|
-
import { autoLoadProjectMemories, formatMemoriesForContext, isAutoMemoryLoadEnabled, } from '../utils/auto-memory.js';
|
|
14
|
-
export class LoopDetector {
|
|
15
|
-
history = [];
|
|
16
|
-
maxRepeats = 3;
|
|
17
|
-
timeWindow = 10000;
|
|
18
|
-
check(toolName, toolInput) {
|
|
19
|
-
const signature = this.makeSignature(toolName, toolInput);
|
|
20
|
-
const coarse = this.makeCoarseSignature(toolName, toolInput);
|
|
21
|
-
const now = Date.now();
|
|
22
|
-
this.history.push({ tool: toolName, signature, coarse, timestamp: now });
|
|
23
|
-
this.history = this.history.filter(h => now - h.timestamp < this.timeWindow);
|
|
24
|
-
const recentDuplicates = this.history.filter(h => h.signature === signature);
|
|
25
|
-
if (recentDuplicates.length >= this.maxRepeats) {
|
|
26
|
-
return {
|
|
27
|
-
allowed: false,
|
|
28
|
-
reason: `CRITICAL ERROR: Loop detected! You called ${toolName} ${this.maxRepeats}+ times with the exact same parameters. YOU MUST STOP AND RE-EVALUATE YOUR STRATEGY. Do not use this tool again right now. Change your mindset. Use 'search_files' or 'get_context' to gather new facts before proceeding.`,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
const sameTool = this.history.filter(h => h.tool === toolName);
|
|
32
|
-
const sameCoarse = this.history.filter(h => h.coarse === coarse);
|
|
33
|
-
// Aggressive coarse match rejection
|
|
34
|
-
if (sameCoarse.length >= this.maxRepeats + 1) {
|
|
35
|
-
return {
|
|
36
|
-
allowed: false,
|
|
37
|
-
reason: `CRITICAL ERROR: Repeated ${toolName} attempts with near-identical patterns. YOUR ASSUMPTIONS ARE WRONG. Step back, verify file paths with 'search_files', and try a completely different approach.`,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
// Lower threshold for repeated tool failures
|
|
41
|
-
if (sameTool.length >= this.maxRepeats + 2) {
|
|
42
|
-
const uniquePatterns = new Set(sameTool.map(h => h.coarse)).size;
|
|
43
|
-
if (uniquePatterns <= 3) {
|
|
44
|
-
return {
|
|
45
|
-
allowed: false,
|
|
46
|
-
reason: `CRITICAL ERROR: ${toolName} repeated ${sameTool.length} times with little variation. STOP guessing. Use 'read_file' or 'grep_code' to find the actual code structure, or use 'web_search' if you lack documentation.`,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
allowed: true,
|
|
51
|
-
reason: `Warning: ${toolName} called ${sameTool.length} times recently. Ensure you are making progress.`,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
return { allowed: true };
|
|
55
|
-
}
|
|
56
|
-
reset() {
|
|
57
|
-
this.history = [];
|
|
58
|
-
}
|
|
59
|
-
makeSignature(toolName, input) {
|
|
60
|
-
return JSON.stringify({
|
|
61
|
-
tool: toolName,
|
|
62
|
-
input: this.canonicalize(input),
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
makeCoarseSignature(toolName, input) {
|
|
66
|
-
const canonical = this.canonicalize(input);
|
|
67
|
-
if (!canonical || typeof canonical !== 'object' || Array.isArray(canonical)) {
|
|
68
|
-
return `${toolName}:primitive`;
|
|
69
|
-
}
|
|
70
|
-
const keys = Object.keys(canonical).sort();
|
|
71
|
-
return `${toolName}:${keys.join(',')}`;
|
|
72
|
-
}
|
|
73
|
-
canonicalize(value) {
|
|
74
|
-
if (Array.isArray(value)) {
|
|
75
|
-
return value.map((item) => this.canonicalize(item));
|
|
76
|
-
}
|
|
77
|
-
if (value && typeof value === 'object') {
|
|
78
|
-
const inObj = value;
|
|
79
|
-
const out = {};
|
|
80
|
-
for (const key of Object.keys(inObj).sort()) {
|
|
81
|
-
out[key] = this.canonicalize(inObj[key]);
|
|
82
|
-
}
|
|
83
|
-
return out;
|
|
84
|
-
}
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// ─── Think-tag streaming filter ───────────────────────────────
|
|
89
|
-
class ThinkTagFilter {
|
|
90
|
-
insideThink = false;
|
|
91
|
-
buffer = '';
|
|
92
|
-
reset() {
|
|
93
|
-
this.insideThink = false;
|
|
94
|
-
this.buffer = '';
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Process a streaming text chunk. Returns the text that should be shown
|
|
98
|
-
* to the user (with <think>...</think> blocks removed in real-time).
|
|
99
|
-
*/
|
|
100
|
-
push(chunk) {
|
|
101
|
-
const combined = this.buffer + chunk;
|
|
102
|
-
this.buffer = '';
|
|
103
|
-
let output = '';
|
|
104
|
-
let i = 0;
|
|
105
|
-
while (i < combined.length) {
|
|
106
|
-
if (this.insideThink) {
|
|
107
|
-
const closeIdx = combined.indexOf('</think>', i);
|
|
108
|
-
if (closeIdx !== -1) {
|
|
109
|
-
this.insideThink = false;
|
|
110
|
-
i = closeIdx + 8;
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
// Still inside think block, consume everything
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
const openIdx = combined.indexOf('<think>', i);
|
|
119
|
-
if (openIdx !== -1) {
|
|
120
|
-
output += combined.substring(i, openIdx);
|
|
121
|
-
this.insideThink = true;
|
|
122
|
-
i = openIdx + 7;
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
// Check for partial '<think' or '</think' at the end
|
|
126
|
-
const remaining = combined.substring(i);
|
|
127
|
-
const partialTag = this.findPartialTag(remaining);
|
|
128
|
-
if (partialTag > 0) {
|
|
129
|
-
output += remaining.substring(0, remaining.length - partialTag);
|
|
130
|
-
this.buffer = remaining.substring(remaining.length - partialTag);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
output += remaining;
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return output;
|
|
140
|
-
}
|
|
141
|
-
/** Flush any remaining buffered text */
|
|
142
|
-
flush() {
|
|
143
|
-
const leftover = this.buffer;
|
|
144
|
-
this.buffer = '';
|
|
145
|
-
if (this.insideThink)
|
|
146
|
-
return '';
|
|
147
|
-
return leftover;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Strip all think tags from a complete string (non-streaming).
|
|
151
|
-
*/
|
|
152
|
-
static strip(text) {
|
|
153
|
-
return text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
154
|
-
}
|
|
155
|
-
findPartialTag(text) {
|
|
156
|
-
const tags = ['<think>', '</think>'];
|
|
157
|
-
for (const tag of tags) {
|
|
158
|
-
for (let len = tag.length - 1; len >= 1; len--) {
|
|
159
|
-
if (text.endsWith(tag.substring(0, len))) {
|
|
160
|
-
return len;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return 0;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
const MAX_TOOL_RESULT_CHARS = 30000;
|
|
168
|
-
export function compactToolResultPayload(result, maxChars = MAX_TOOL_RESULT_CHARS) {
|
|
169
|
-
if (typeof result === 'string') {
|
|
170
|
-
return compactLargeString(result, maxChars);
|
|
171
|
-
}
|
|
172
|
-
if (Array.isArray(result)) {
|
|
173
|
-
return result.map((item) => compactToolResultPayload(item, maxChars));
|
|
174
|
-
}
|
|
175
|
-
if (!result || typeof result !== 'object') {
|
|
176
|
-
return result;
|
|
177
|
-
}
|
|
178
|
-
const compacted = {};
|
|
179
|
-
for (const [key, value] of Object.entries(result)) {
|
|
180
|
-
if (typeof value === 'string' && ['stdout', 'stderr', 'content', 'output', 'message'].includes(key)) {
|
|
181
|
-
compacted[key] = compactLargeString(value, maxChars);
|
|
182
|
-
if (compacted[key] !== value) {
|
|
183
|
-
compacted[`${key}Truncated`] = true;
|
|
184
|
-
compacted[`${key}OriginalLength`] = value.length;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
compacted[key] = compactToolResultPayload(value, Math.max(4000, Math.floor(maxChars / 2)));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return compacted;
|
|
192
|
-
}
|
|
193
|
-
function compactLargeString(value, maxChars) {
|
|
194
|
-
if (value.length <= maxChars)
|
|
195
|
-
return value;
|
|
196
|
-
const marker = `\n\n[tool result truncated: original length ${value.length} chars]\n\n`;
|
|
197
|
-
const available = Math.max(0, maxChars - marker.length);
|
|
198
|
-
const headLength = Math.ceil(available * 0.6);
|
|
199
|
-
const tailLength = Math.floor(available * 0.4);
|
|
200
|
-
return `${value.slice(0, headLength)}${marker}${value.slice(value.length - tailLength)}`;
|
|
201
|
-
}
|
|
202
|
-
// ─── Agent ────────────────────────────────────────────────────
|
|
203
|
-
export class EnhancedAgent extends EventEmitter {
|
|
204
|
-
client;
|
|
205
|
-
messages = [];
|
|
206
|
-
loopDetector = new LoopDetector();
|
|
207
|
-
thinkFilter = new ThinkTagFilter();
|
|
208
|
-
config;
|
|
209
|
-
iterationCount = 0;
|
|
210
|
-
toolCallCount = 0;
|
|
211
|
-
filesChanged = new Set();
|
|
212
|
-
modeState;
|
|
213
|
-
modeOrchestrator;
|
|
214
|
-
provider;
|
|
215
|
-
/** Ranked markdown memories (user + project + .xibecode/memories); injected in getSystemPrompt */
|
|
216
|
-
autoMemoryMarkdownSection = '';
|
|
217
|
-
totalInputTokens = 0;
|
|
218
|
-
totalOutputTokens = 0;
|
|
219
|
-
sessionCost = 0;
|
|
220
|
-
activeSkill = null;
|
|
221
|
-
defaultSkillsPrompt = '';
|
|
222
|
-
memory;
|
|
223
|
-
injectedMessages = [];
|
|
224
|
-
/** Current reasoning tier (AX-lite): strategic = plan, tactical = step decisions, operational = tools. */
|
|
225
|
-
currentTier = 'tactical';
|
|
226
|
-
/** When plan-first was used, the initial strategic plan text (for context). */
|
|
227
|
-
strategicPlanText = '';
|
|
228
|
-
sessionMemory = null;
|
|
229
|
-
contextHintFiles = [];
|
|
230
|
-
mindsetAdaptive = false;
|
|
231
|
-
currentMindset = 'convergent';
|
|
232
|
-
permissionManager;
|
|
233
|
-
toolOrchestrator;
|
|
234
|
-
evidenceTrail = [];
|
|
235
|
-
isAbortError(err) {
|
|
236
|
-
if (!err || typeof err !== 'object')
|
|
237
|
-
return false;
|
|
238
|
-
const anyErr = err;
|
|
239
|
-
return (anyErr.name === 'AbortError' ||
|
|
240
|
-
anyErr.type === 'aborted' ||
|
|
241
|
-
String(anyErr.message || '').toLowerCase().includes('aborted'));
|
|
242
|
-
}
|
|
243
|
-
injectMessage(message) {
|
|
244
|
-
this.injectedMessages.push(message);
|
|
245
|
-
}
|
|
246
|
-
getInjectedMessages() {
|
|
247
|
-
return this.injectedMessages;
|
|
248
|
-
}
|
|
249
|
-
clearInjectedMessages() {
|
|
250
|
-
this.injectedMessages = [];
|
|
251
|
-
}
|
|
252
|
-
recordEvidence(kind, detail) {
|
|
253
|
-
this.evidenceTrail.push({ kind, detail, ts: Date.now() });
|
|
254
|
-
if (this.evidenceTrail.length > 80) {
|
|
255
|
-
this.evidenceTrail = this.evidenceTrail.slice(-80);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
estimateMessageTokens(message) {
|
|
259
|
-
const text = (() => {
|
|
260
|
-
if (typeof message.content === 'string')
|
|
261
|
-
return message.content;
|
|
262
|
-
if (!Array.isArray(message.content))
|
|
263
|
-
return '';
|
|
264
|
-
return message.content
|
|
265
|
-
.map((block) => {
|
|
266
|
-
if (block?.type === 'text')
|
|
267
|
-
return String(block.text || '');
|
|
268
|
-
if (block?.type === 'tool_use') {
|
|
269
|
-
const input = block.input ? JSON.stringify(block.input) : '{}';
|
|
270
|
-
return `[tool_use:${String(block.name || 'unknown')}] ${input}`;
|
|
271
|
-
}
|
|
272
|
-
if (block?.type === 'tool_result') {
|
|
273
|
-
return `[tool_result:${String(block.tool_use_id || 'unknown')}] ${String(block.content || '')}`;
|
|
274
|
-
}
|
|
275
|
-
return String(block?.content ?? '');
|
|
276
|
-
})
|
|
277
|
-
.join('\n');
|
|
278
|
-
})();
|
|
279
|
-
// Rough estimate for modern tokenizers across mixed text/code.
|
|
280
|
-
return Math.ceil(text.length / 4);
|
|
281
|
-
}
|
|
282
|
-
estimateConversationTokens() {
|
|
283
|
-
return this.messages.reduce((sum, message) => sum + this.estimateMessageTokens(message), 0);
|
|
284
|
-
}
|
|
285
|
-
hasRecentGroundedEvidence(windowMs = 5 * 60 * 1000) {
|
|
286
|
-
const threshold = Date.now() - windowMs;
|
|
287
|
-
return this.evidenceTrail.some((entry) => entry.ts >= threshold);
|
|
288
|
-
}
|
|
289
|
-
shouldEnforceCompletionEvidence() {
|
|
290
|
-
if (this.config.completionEvidenceMode === 'off')
|
|
291
|
-
return false;
|
|
292
|
-
// Allow single-turn informational answers without tools.
|
|
293
|
-
if (this.toolCallCount === 0 && this.filesChanged.size === 0 && this.iterationCount <= 1) {
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
async postEditVerify(toolExecutor, toolUse, result) {
|
|
299
|
-
if (this.config.postEditVerification === 'off') {
|
|
300
|
-
return { ok: true, message: 'disabled' };
|
|
301
|
-
}
|
|
302
|
-
if (!['write_file', 'edit_file', 'edit_lines', 'verified_edit'].includes(toolUse.name)) {
|
|
303
|
-
return { ok: true, message: 'not-applicable' };
|
|
304
|
-
}
|
|
305
|
-
const input = (toolUse.input ?? {});
|
|
306
|
-
const path = (typeof result?.path === 'string' && result.path) ||
|
|
307
|
-
(typeof input.path === 'string' && input.path) ||
|
|
308
|
-
'';
|
|
309
|
-
if (!path) {
|
|
310
|
-
const strict = this.config.postEditVerification === 'strict';
|
|
311
|
-
return {
|
|
312
|
-
ok: !strict,
|
|
313
|
-
message: strict ? 'post-edit verification failed: missing path' : 'post-edit verification skipped: missing path',
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
const readArgs = { path };
|
|
317
|
-
if (typeof input.start_line === 'number' && typeof input.end_line === 'number') {
|
|
318
|
-
readArgs.start_line = input.start_line;
|
|
319
|
-
readArgs.end_line = input.end_line;
|
|
320
|
-
}
|
|
321
|
-
const check = await toolExecutor.execute('read_file', readArgs);
|
|
322
|
-
if (check?.error || check?.success === false) {
|
|
323
|
-
return {
|
|
324
|
-
ok: false,
|
|
325
|
-
message: `post-edit verification failed: could not read ${path}`,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
if (typeof input.new_content === 'string' && input.new_content.trim().length > 0) {
|
|
329
|
-
const observed = typeof check?.content === 'string' ? check.content : '';
|
|
330
|
-
const expected = input.new_content.trim();
|
|
331
|
-
const normalize = (v) => v.replace(/\s+/g, ' ').trim();
|
|
332
|
-
const expectedNeedle = normalize(expected).slice(0, 180);
|
|
333
|
-
if (expectedNeedle && !normalize(observed).includes(expectedNeedle)) {
|
|
334
|
-
return {
|
|
335
|
-
ok: this.config.postEditVerification !== 'strict',
|
|
336
|
-
message: `post-edit verification warning: updated content was not confidently observed in ${path}`,
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
this.recordEvidence('post_edit_verify', path);
|
|
341
|
-
return { ok: true, message: `verified ${path}` };
|
|
342
|
-
}
|
|
343
|
-
// Pricing per 1M tokens (input/output) — Claude models
|
|
344
|
-
static PRICING = {
|
|
345
|
-
'claude-sonnet-4-5-20250929': { input: 3, output: 15 },
|
|
346
|
-
'claude-opus-4-5-20251101': { input: 15, output: 75 },
|
|
347
|
-
'claude-haiku-4-5-20251015': { input: 1, output: 5 },
|
|
348
|
-
'claude-opus-4-6-20251101': { input: 5, output: 25 },
|
|
349
|
-
};
|
|
350
|
-
constructor(config, providerOverride) {
|
|
351
|
-
super();
|
|
352
|
-
const clientConfig = { apiKey: config.apiKey };
|
|
353
|
-
if (config.baseUrl) {
|
|
354
|
-
clientConfig.baseURL = config.baseUrl;
|
|
355
|
-
}
|
|
356
|
-
this.client = new Anthropic(clientConfig);
|
|
357
|
-
this.config = {
|
|
358
|
-
apiKey: config.apiKey,
|
|
359
|
-
baseUrl: config.baseUrl ?? '',
|
|
360
|
-
model: config.model,
|
|
361
|
-
maxIterations: config.maxIterations ?? 150,
|
|
362
|
-
verbose: config.verbose ?? false,
|
|
363
|
-
mode: config.mode ?? 'agent',
|
|
364
|
-
provider: config.provider ?? this.detectProvider(config.model),
|
|
365
|
-
customProviderFormat: config.customProviderFormat ?? 'openai',
|
|
366
|
-
requestFormat: config.requestFormat ?? 'auto',
|
|
367
|
-
planFirst: config.planFirst ?? false,
|
|
368
|
-
sessionMemory: config.sessionMemory,
|
|
369
|
-
contextHintFiles: config.contextHintFiles ?? [],
|
|
370
|
-
planningModel: config.planningModel,
|
|
371
|
-
executionModel: config.executionModel,
|
|
372
|
-
mindsetAdaptive: config.mindsetAdaptive ?? false,
|
|
373
|
-
strictTextOnlyCompletion: config.strictTextOnlyCompletion ?? false,
|
|
374
|
-
completionEvidenceMode: config.completionEvidenceMode ?? 'balanced',
|
|
375
|
-
postEditVerification: config.postEditVerification ?? 'balanced',
|
|
376
|
-
memoryRecallMinScore: config.memoryRecallMinScore ?? 2,
|
|
377
|
-
};
|
|
378
|
-
this.defaultSkillsPrompt = config.defaultSkillsPrompt ?? '';
|
|
379
|
-
this.mindsetAdaptive = this.config.mindsetAdaptive ?? false;
|
|
380
|
-
// Initialize mode state and orchestrator
|
|
381
|
-
this.modeState = createModeState(this.config.mode);
|
|
382
|
-
this.permissionManager = new PermissionManager(this.config.mode);
|
|
383
|
-
this.toolOrchestrator = new ToolOrchestrator();
|
|
384
|
-
this.modeOrchestrator = new ModeOrchestrator({
|
|
385
|
-
autoApprovalPolicy: 'prompt-only',
|
|
386
|
-
allowAutoEscalation: true,
|
|
387
|
-
});
|
|
388
|
-
// Prefer explicit provider override from config, otherwise auto-detect
|
|
389
|
-
this.provider = providerOverride ?? config.provider ?? this.detectProvider(this.config.model);
|
|
390
|
-
// Load project memory if it exists
|
|
391
|
-
this.sessionMemory = config.sessionMemory ?? null;
|
|
392
|
-
this.contextHintFiles = config.contextHintFiles ?? [];
|
|
393
|
-
this.memory = new NeuralMemory();
|
|
394
|
-
this.memory.init().catch(console.error);
|
|
395
|
-
}
|
|
396
|
-
detectProvider(model) {
|
|
397
|
-
const m = model.toLowerCase();
|
|
398
|
-
if (m.startsWith('claude'))
|
|
399
|
-
return 'anthropic';
|
|
400
|
-
if (m.startsWith('gpt') || m.startsWith('o1') || m.startsWith('o3'))
|
|
401
|
-
return 'openai';
|
|
402
|
-
if (m.startsWith('glm'))
|
|
403
|
-
return 'zai';
|
|
404
|
-
if (m.startsWith('qwen'))
|
|
405
|
-
return 'alibaba';
|
|
406
|
-
if (m.startsWith('kimi') || m.startsWith('moonshot'))
|
|
407
|
-
return 'kimi';
|
|
408
|
-
if (m.startsWith('grok'))
|
|
409
|
-
return 'grok';
|
|
410
|
-
if (m.startsWith('deepseek'))
|
|
411
|
-
return 'deepseek';
|
|
412
|
-
if (m.startsWith('llama') || m.startsWith('mixtral'))
|
|
413
|
-
return 'groq';
|
|
414
|
-
if (m.includes('/'))
|
|
415
|
-
return 'openrouter';
|
|
416
|
-
return 'openai';
|
|
417
|
-
}
|
|
418
|
-
/** Multi-model routing: use planning model for strategic tier, execution model for tactical/operational when set. */
|
|
419
|
-
getModelForTier() {
|
|
420
|
-
if (this.currentTier === 'strategic' && this.config.planningModel)
|
|
421
|
-
return this.config.planningModel;
|
|
422
|
-
if ((this.currentTier === 'tactical' || this.currentTier === 'operational') && this.config.executionModel)
|
|
423
|
-
return this.config.executionModel;
|
|
424
|
-
return this.config.model;
|
|
425
|
-
}
|
|
426
|
-
emit(event, data) {
|
|
427
|
-
return super.emit('event', { type: event, data });
|
|
428
|
-
}
|
|
429
|
-
async run(initialPrompt, tools, toolExecutor, opts) {
|
|
430
|
-
// Reset per-turn state (keeps conversation history in this.messages)
|
|
431
|
-
this.iterationCount = 0;
|
|
432
|
-
this.toolCallCount = 0;
|
|
433
|
-
this.loopDetector.reset();
|
|
434
|
-
this.evidenceTrail = [];
|
|
435
|
-
this.autoMemoryMarkdownSection = '';
|
|
436
|
-
if (isAutoMemoryLoadEnabled()) {
|
|
437
|
-
try {
|
|
438
|
-
const ranked = await autoLoadProjectMemories(process.cwd(), initialPrompt, []);
|
|
439
|
-
this.autoMemoryMarkdownSection = formatMemoriesForContext(ranked);
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
/* non-fatal */
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (!this.autoMemoryMarkdownSection.trim()) {
|
|
446
|
-
const fallbackMd = join(process.cwd(), '.xibecode', 'memory.md');
|
|
447
|
-
if (existsSync(fallbackMd)) {
|
|
448
|
-
try {
|
|
449
|
-
// ⚡ Bolt: Use asynchronous readFile to prevent blocking the Node.js event loop
|
|
450
|
-
// Performance impact: Keeps the agent and web UI responsive while loading fallback memory
|
|
451
|
-
const content = await readFile(fallbackMd, 'utf-8');
|
|
452
|
-
this.autoMemoryMarkdownSection = `\n\n## Project Memory\n\n${content.trim()}`;
|
|
453
|
-
}
|
|
454
|
-
catch {
|
|
455
|
-
/* ignore */
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
if (opts?.images && opts.images.length > 0) {
|
|
460
|
-
const blocks = [{ type: 'text', text: initialPrompt }];
|
|
461
|
-
for (const img of opts.images) {
|
|
462
|
-
blocks.push({
|
|
463
|
-
type: 'image_url',
|
|
464
|
-
image_url: { url: `data:${img.mime};base64,${img.dataBase64}` },
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
this.messages.push({ role: 'user', content: blocks });
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
this.messages.push({
|
|
471
|
-
role: 'user',
|
|
472
|
-
content: initialPrompt,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
// ─── Neural Memory Recall ───
|
|
476
|
-
try {
|
|
477
|
-
const memories = await this.memory.retrieve(initialPrompt, 5, this.config.memoryRecallMinScore);
|
|
478
|
-
if (memories.length > 0) {
|
|
479
|
-
const memoryContext = memories.map(m => `- [${new Date(m.timestamp).toISOString().split('T')[0]}] ${m.trigger} -> ${m.action} (${m.outcome})`).join('\n');
|
|
480
|
-
this.messages.push({
|
|
481
|
-
role: 'user',
|
|
482
|
-
content: `\n\n[Neural Memory Recall — UNVERIFIED HINTS]\n` +
|
|
483
|
-
`These are recall hints, not guaranteed facts. Verify with read_file / grep_code / tests before relying on them.\n` +
|
|
484
|
-
`${memoryContext}\n\nUser Prompt: ${initialPrompt}`
|
|
485
|
-
});
|
|
486
|
-
this.emit('thinking', { message: `Recalled ${memories.length} relevant memories` });
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
catch (err) {
|
|
490
|
-
// Ignore memory errors to not block execution
|
|
491
|
-
}
|
|
492
|
-
this.emit('thinking', { message: 'Starting agent...' });
|
|
493
|
-
// ─── Plan-first (AX-lite strategic tier): one-shot plan before execution ───
|
|
494
|
-
if (this.config.planFirst) {
|
|
495
|
-
this.currentTier = 'strategic';
|
|
496
|
-
this.emit('thinking', { message: 'Strategic planning (plan-first mode)...' });
|
|
497
|
-
try {
|
|
498
|
-
const planResult = await this.callModel([], opts?.signal);
|
|
499
|
-
const planContent = planResult.message?.content;
|
|
500
|
-
let planText = '';
|
|
501
|
-
if (Array.isArray(planContent)) {
|
|
502
|
-
for (const block of planContent) {
|
|
503
|
-
if (block.type === 'text' && typeof block.text === 'string') {
|
|
504
|
-
planText += block.text;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
else if (typeof planContent === 'string') {
|
|
509
|
-
planText = planContent;
|
|
510
|
-
}
|
|
511
|
-
planText = planText.trim() || 'Proceed step by step.';
|
|
512
|
-
this.strategicPlanText = planText;
|
|
513
|
-
this.messages.push({
|
|
514
|
-
role: 'assistant',
|
|
515
|
-
content: planResult.message?.content ?? planText,
|
|
516
|
-
});
|
|
517
|
-
this.messages.push({
|
|
518
|
-
role: 'user',
|
|
519
|
-
content: `[Strategic plan you created]\n\n${planText}\n\nNow execute this plan step by step. Use the available tools to implement each part.`,
|
|
520
|
-
});
|
|
521
|
-
this.currentTier = 'tactical';
|
|
522
|
-
this.emit('thinking', { message: 'Strategic plan complete; starting execution.' });
|
|
523
|
-
}
|
|
524
|
-
catch (err) {
|
|
525
|
-
this.emit('warning', { message: `Plan-first planning failed: ${err?.message ?? err}. Continuing without plan.` });
|
|
526
|
-
this.currentTier = 'tactical';
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
while (this.iterationCount < this.config.maxIterations) {
|
|
530
|
-
this.iterationCount++;
|
|
531
|
-
this.emit('iteration', {
|
|
532
|
-
current: this.iterationCount,
|
|
533
|
-
total: this.config.maxIterations,
|
|
534
|
-
});
|
|
535
|
-
this.emit('thinking', { message: 'AI is thinking...' });
|
|
536
|
-
const maxApiRetries = 5;
|
|
537
|
-
const retryDelayMs = 2000;
|
|
538
|
-
let response;
|
|
539
|
-
let streamed = false;
|
|
540
|
-
try {
|
|
541
|
-
// Tools are supported for both Anthropic and OpenAI-format models.
|
|
542
|
-
const effectiveTools = tools;
|
|
543
|
-
for (let attempt = 1; attempt <= maxApiRetries; attempt++) {
|
|
544
|
-
try {
|
|
545
|
-
const result = await this.callModel(effectiveTools, opts?.signal);
|
|
546
|
-
response = result.message;
|
|
547
|
-
streamed = result.streamed;
|
|
548
|
-
const persona = result.persona;
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
catch (apiError) {
|
|
552
|
-
if (opts?.signal?.aborted || this.isAbortError(apiError)) {
|
|
553
|
-
throw apiError;
|
|
554
|
-
}
|
|
555
|
-
this.emit('error', { message: 'API Error', error: apiError.message });
|
|
556
|
-
if (attempt < maxApiRetries) {
|
|
557
|
-
this.emit('warning', {
|
|
558
|
-
message: `Retrying in ${retryDelayMs / 1000}s (attempt ${attempt}/${maxApiRetries})...`,
|
|
559
|
-
});
|
|
560
|
-
await new Promise((r) => setTimeout(r, retryDelayMs));
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
throw apiError;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
if (!response) {
|
|
568
|
-
throw new Error('API call failed after retries');
|
|
569
|
-
}
|
|
570
|
-
// Add assistant response
|
|
571
|
-
this.messages.push({
|
|
572
|
-
role: 'assistant',
|
|
573
|
-
content: response.content,
|
|
574
|
-
});
|
|
575
|
-
// Track token usage for cost estimation
|
|
576
|
-
if (response.usage) {
|
|
577
|
-
const inputTokens = response.usage.input_tokens || 0;
|
|
578
|
-
const outputTokens = response.usage.output_tokens || 0;
|
|
579
|
-
this.totalInputTokens += inputTokens;
|
|
580
|
-
this.totalOutputTokens += outputTokens;
|
|
581
|
-
// Calculate cost
|
|
582
|
-
const pricing = EnhancedAgent.PRICING[this.config.model];
|
|
583
|
-
if (pricing) {
|
|
584
|
-
this.sessionCost += (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
// Auto-compact by estimated token budget (OpenClaude-style), not raw message count.
|
|
588
|
-
const compactionThreshold = 120_000;
|
|
589
|
-
const compactionTarget = 90_000;
|
|
590
|
-
let estimatedTokens = this.estimateConversationTokens();
|
|
591
|
-
if (estimatedTokens > compactionThreshold) {
|
|
592
|
-
const beforeCount = this.messages.length;
|
|
593
|
-
const compacted = compactConversation(this.messages, 24);
|
|
594
|
-
this.messages = compacted.messages;
|
|
595
|
-
estimatedTokens = this.estimateConversationTokens();
|
|
596
|
-
this.emit('warning', {
|
|
597
|
-
message: `${compacted.summaryNotice || 'Auto-compacted conversation to save context'} ` +
|
|
598
|
-
`(estimated tokens: ${estimatedTokens})`,
|
|
599
|
-
});
|
|
600
|
-
// If still above target, compact once more with tighter window.
|
|
601
|
-
if (estimatedTokens > compactionTarget && this.messages.length < beforeCount) {
|
|
602
|
-
const compactedAgain = compactConversation(this.messages, 16);
|
|
603
|
-
this.messages = compactedAgain.messages;
|
|
604
|
-
estimatedTokens = this.estimateConversationTokens();
|
|
605
|
-
this.emit('warning', {
|
|
606
|
-
message: `${compactedAgain.summaryNotice || 'Second compaction pass applied'} ` +
|
|
607
|
-
`(estimated tokens: ${estimatedTokens})`,
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
// Process response
|
|
612
|
-
const content = response.content;
|
|
613
|
-
const textBlocks = content.filter((block) => block.type === 'text');
|
|
614
|
-
const toolUseBlocks = content.filter((block) => block.type === 'tool_use');
|
|
615
|
-
// Check for mode change requests in text blocks
|
|
616
|
-
for (const block of textBlocks) {
|
|
617
|
-
if (this.mindsetAdaptive) {
|
|
618
|
-
const mindsetMatch = block.text.match(/\[\[SET_MINDSET:\s*(\w+)\]\]/i);
|
|
619
|
-
if (mindsetMatch) {
|
|
620
|
-
const m = mindsetMatch[1].toLowerCase();
|
|
621
|
-
if (m === 'convergent' || m === 'divergent' || m === 'algorithmic') {
|
|
622
|
-
this.currentMindset = m;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
const modeRequest = parseModeRequest(block.text);
|
|
627
|
-
if (modeRequest) {
|
|
628
|
-
this.modeState = this.modeOrchestrator.requestModeChange(this.modeState, modeRequest.mode, modeRequest.reason, 'model');
|
|
629
|
-
// Evaluate the request
|
|
630
|
-
const evaluation = this.modeOrchestrator.evaluateModeChangeRequest(this.modeState);
|
|
631
|
-
const permissionEvaluation = this.permissionManager.evaluateModeTransition(this.modeState.current, modeRequest.mode);
|
|
632
|
-
const approvedByPermission = permissionEvaluation.approved;
|
|
633
|
-
if (evaluation.approved && approvedByPermission) {
|
|
634
|
-
// Auto-approved - switch immediately
|
|
635
|
-
const oldMode = this.modeState.current;
|
|
636
|
-
this.modeState = transitionMode(this.modeState, modeRequest.mode, modeRequest.reason);
|
|
637
|
-
this.permissionManager.setMode(modeRequest.mode);
|
|
638
|
-
// Update tool executor mode
|
|
639
|
-
if (toolExecutor.setMode) {
|
|
640
|
-
toolExecutor.setMode(modeRequest.mode);
|
|
641
|
-
}
|
|
642
|
-
this.emit('mode_changed', {
|
|
643
|
-
from: oldMode,
|
|
644
|
-
to: modeRequest.mode,
|
|
645
|
-
reason: modeRequest.reason,
|
|
646
|
-
auto: true,
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
else {
|
|
650
|
-
// Requires confirmation
|
|
651
|
-
this.emit('mode_change_requested', {
|
|
652
|
-
from: this.modeState.current,
|
|
653
|
-
to: modeRequest.mode,
|
|
654
|
-
reason: modeRequest.reason,
|
|
655
|
-
requiresConfirmation: evaluation.requiresConfirmation,
|
|
656
|
-
message: permissionEvaluation.reason ?? evaluation.reason,
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
// Check for task completion
|
|
661
|
-
const taskComplete = parseTaskComplete(block.text);
|
|
662
|
-
if (taskComplete) {
|
|
663
|
-
// Switch to review mode (team_leader temporarily disabled)
|
|
664
|
-
this.modeState = transitionMode(this.modeState, 'review', 'Task completed: ' + taskComplete.summary);
|
|
665
|
-
this.permissionManager.setMode('review');
|
|
666
|
-
this.emit('mode_changed', {
|
|
667
|
-
from: this.modeState.previous,
|
|
668
|
-
to: 'review',
|
|
669
|
-
reason: 'Task completion reported',
|
|
670
|
-
auto: true
|
|
671
|
-
});
|
|
672
|
-
// Update tool executor mode if applicable
|
|
673
|
-
if (toolExecutor.setMode) {
|
|
674
|
-
toolExecutor.setMode('review');
|
|
675
|
-
}
|
|
676
|
-
// Add a system note to help reviewer mode understand what happened
|
|
677
|
-
this.messages.push({
|
|
678
|
-
role: 'user',
|
|
679
|
-
content: `[SYSTEM] Agent reported task completion:\nSummary: ${taskComplete.summary}\n\nSwitching to review mode. Review the summary and decide the next step.`
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
// Show text responses (only if not already streamed)
|
|
684
|
-
if (!streamed) {
|
|
685
|
-
for (const block of textBlocks) {
|
|
686
|
-
const cleanText = ThinkTagFilter.strip(block.text);
|
|
687
|
-
// Remove mode request and task complete tags for display
|
|
688
|
-
const displayText = stripTaskComplete(stripModeRequests(cleanText));
|
|
689
|
-
if (displayText) {
|
|
690
|
-
this.emit('response', { text: displayText, persona: response.persona });
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
// If no tools, we're done (unless run-pr-style strict completion requires TASK_COMPLETE)
|
|
695
|
-
if (toolUseBlocks.length === 0) {
|
|
696
|
-
const hasTaskComplete = textBlocks.some((b) => parseTaskComplete(b.text) != null);
|
|
697
|
-
const hasEvidence = this.hasRecentGroundedEvidence();
|
|
698
|
-
if (this.shouldEnforceCompletionEvidence()) {
|
|
699
|
-
const needsTaskComplete = this.config.completionEvidenceMode === 'strict' || this.toolCallCount > 0;
|
|
700
|
-
if ((needsTaskComplete && !hasTaskComplete) || !hasEvidence) {
|
|
701
|
-
const reason = !hasEvidence
|
|
702
|
-
? 'no recent grounded evidence'
|
|
703
|
-
: 'missing [[TASK_COMPLETE | summary=...]]';
|
|
704
|
-
this.emit('warning', {
|
|
705
|
-
message: `Completion evidence gate blocked finalize: ${reason}.`,
|
|
706
|
-
});
|
|
707
|
-
this.messages.push({
|
|
708
|
-
role: 'user',
|
|
709
|
-
content: '[SYSTEM] Completion gate: before finishing, provide grounded evidence from tool results/tests and then emit ' +
|
|
710
|
-
'[[TASK_COMPLETE | summary=<brief summary> | evidence=<paths/tests/tool proof>]]. Continue working until this is satisfied.',
|
|
711
|
-
});
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
if (this.config.strictTextOnlyCompletion &&
|
|
716
|
-
this.iterationCount < this.config.maxIterations) {
|
|
717
|
-
if (!hasTaskComplete) {
|
|
718
|
-
this.emit('warning', {
|
|
719
|
-
message: 'Assistant returned no tool calls without [[TASK_COMPLETE | summary=...]]; nudging to continue.',
|
|
720
|
-
});
|
|
721
|
-
this.messages.push({
|
|
722
|
-
role: 'user',
|
|
723
|
-
content: '[SYSTEM] You ended this turn without tool calls and without [[TASK_COMPLETE | summary=...]]. Continue using tools until the task is fully done (all files edited, checks run), or emit [[TASK_COMPLETE | summary=<brief summary>]] only when finished.',
|
|
724
|
-
});
|
|
725
|
-
continue;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
this.emit('complete', {
|
|
729
|
-
iterations: this.iterationCount,
|
|
730
|
-
toolCalls: this.toolCallCount,
|
|
731
|
-
filesChanged: this.filesChanged.size,
|
|
732
|
-
});
|
|
733
|
-
break;
|
|
734
|
-
}
|
|
735
|
-
// Execute tools
|
|
736
|
-
const toolResults = [];
|
|
737
|
-
const updates = await this.toolOrchestrator.executeBatches(toolUseBlocks, async (toolUse, i) => this.executeSingleToolUse(toolExecutor, toolUse, i));
|
|
738
|
-
for (const update of updates) {
|
|
739
|
-
const compactedResult = compactToolResultPayload(update.result);
|
|
740
|
-
const payload = typeof compactedResult === 'string'
|
|
741
|
-
? compactedResult
|
|
742
|
-
: JSON.stringify(compactedResult, null, 2);
|
|
743
|
-
toolResults.push({
|
|
744
|
-
type: 'tool_result',
|
|
745
|
-
tool_use_id: update.toolUse.id,
|
|
746
|
-
content: payload,
|
|
747
|
-
...(update.success ? {} : { is_error: true }),
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
// Add injected messages if any exist
|
|
751
|
-
if (this.injectedMessages.length > 0) {
|
|
752
|
-
const combinedMsg = this.injectedMessages.join('\n');
|
|
753
|
-
toolResults.push({
|
|
754
|
-
type: 'text',
|
|
755
|
-
text: `[USER INTERRUPT/UPDATE]:\n${combinedMsg}`
|
|
756
|
-
});
|
|
757
|
-
this.emit('warning', { message: `Injected ${this.injectedMessages.length} user message(s) into context.` });
|
|
758
|
-
this.injectedMessages = [];
|
|
759
|
-
}
|
|
760
|
-
// Add results to conversation
|
|
761
|
-
this.messages.push({
|
|
762
|
-
role: 'user',
|
|
763
|
-
content: toolResults,
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
catch (error) {
|
|
767
|
-
if (opts?.signal?.aborted || this.isAbortError(error)) {
|
|
768
|
-
throw error;
|
|
769
|
-
}
|
|
770
|
-
this.emit('error', {
|
|
771
|
-
message: 'API Error',
|
|
772
|
-
error: error.message,
|
|
773
|
-
});
|
|
774
|
-
throw error;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
if (this.iterationCount >= this.config.maxIterations) {
|
|
778
|
-
this.emit('warning', {
|
|
779
|
-
message: `Reached maximum iterations (${this.config.maxIterations})`,
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Call the model with streaming (fallback to non-streaming).
|
|
785
|
-
*/
|
|
786
|
-
async executeSingleToolUse(toolExecutor, toolUse, index) {
|
|
787
|
-
this.toolCallCount++;
|
|
788
|
-
const loopCheck = this.loopDetector.check(toolUse.name, toolUse.input);
|
|
789
|
-
if (!loopCheck.allowed) {
|
|
790
|
-
this.emit('warning', { message: loopCheck.reason });
|
|
791
|
-
return {
|
|
792
|
-
toolUse,
|
|
793
|
-
index,
|
|
794
|
-
result: `Error: ${loopCheck.reason}. Try a different approach.`,
|
|
795
|
-
success: false,
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
if (loopCheck.reason) {
|
|
799
|
-
this.emit('warning', { message: loopCheck.reason });
|
|
800
|
-
}
|
|
801
|
-
this.emit('tool_call', {
|
|
802
|
-
name: toolUse.name,
|
|
803
|
-
input: toolUse.input,
|
|
804
|
-
index: index + 1,
|
|
805
|
-
});
|
|
806
|
-
this.recordEvidence('tool_call', toolUse.name);
|
|
807
|
-
try {
|
|
808
|
-
let result = await toolExecutor.execute(toolUse.name, toolUse.input);
|
|
809
|
-
if (['write_file', 'edit_file', 'edit_lines', 'verified_edit'].includes(toolUse.name)) {
|
|
810
|
-
const input = toolUse.input;
|
|
811
|
-
if (typeof input?.path === 'string')
|
|
812
|
-
this.filesChanged.add(input.path);
|
|
813
|
-
}
|
|
814
|
-
const verification = await this.postEditVerify(toolExecutor, toolUse, result);
|
|
815
|
-
if (!verification.ok) {
|
|
816
|
-
const currentMessage = typeof result?.message === 'string' && result.message
|
|
817
|
-
? `${result.message}; ${verification.message}`
|
|
818
|
-
: verification.message;
|
|
819
|
-
if (result && typeof result === 'object') {
|
|
820
|
-
result = { ...result, message: currentMessage, success: false, error: true, postVerify: verification.message };
|
|
821
|
-
}
|
|
822
|
-
else {
|
|
823
|
-
result = { success: false, error: true, message: currentMessage, result };
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
else if (result && typeof result === 'object') {
|
|
827
|
-
result = { ...result, postVerify: verification.message };
|
|
828
|
-
}
|
|
829
|
-
const success = !result?.error && result?.success !== false;
|
|
830
|
-
this.emit('tool_result', {
|
|
831
|
-
name: toolUse.name,
|
|
832
|
-
result,
|
|
833
|
-
success,
|
|
834
|
-
});
|
|
835
|
-
this.recordEvidence(success ? 'tool_result_ok' : 'tool_result_error', `${toolUse.name}:${success ? 'ok' : 'error'}`);
|
|
836
|
-
if (this.sessionMemory) {
|
|
837
|
-
const msg = typeof result === 'object' && result?.message != null ? String(result.message) : undefined;
|
|
838
|
-
this.sessionMemory.recordAttempt(toolUse.name, success, msg);
|
|
839
|
-
}
|
|
840
|
-
return {
|
|
841
|
-
toolUse,
|
|
842
|
-
index,
|
|
843
|
-
result,
|
|
844
|
-
success,
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
catch (error) {
|
|
848
|
-
this.emit('error', {
|
|
849
|
-
tool: toolUse.name,
|
|
850
|
-
error: error.message,
|
|
851
|
-
});
|
|
852
|
-
return {
|
|
853
|
-
toolUse,
|
|
854
|
-
index,
|
|
855
|
-
result: `Error: ${error.message}`,
|
|
856
|
-
success: false,
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
/**
|
|
861
|
-
* Call the model with streaming (fallback to non-streaming).
|
|
862
|
-
*/
|
|
863
|
-
async callModel(tools, signal) {
|
|
864
|
-
// Route to provider-specific implementation
|
|
865
|
-
// Check if the provider uses the Anthropic format or OpenAI format
|
|
866
|
-
let isAnthropicFormat = false;
|
|
867
|
-
const rf = this.config.requestFormat ?? 'auto';
|
|
868
|
-
if (rf === 'openai') {
|
|
869
|
-
isAnthropicFormat = false;
|
|
870
|
-
}
|
|
871
|
-
else if (rf === 'anthropic') {
|
|
872
|
-
isAnthropicFormat = true;
|
|
873
|
-
}
|
|
874
|
-
else if (this.provider !== 'custom') {
|
|
875
|
-
isAnthropicFormat =
|
|
876
|
-
PROVIDER_CONFIGS[this.provider]?.format ===
|
|
877
|
-
'anthropic';
|
|
878
|
-
}
|
|
879
|
-
else {
|
|
880
|
-
isAnthropicFormat = this.config.customProviderFormat === 'anthropic';
|
|
881
|
-
}
|
|
882
|
-
// If it's NOT Anthropic format, use the OpenAI-compatible client
|
|
883
|
-
if (!isAnthropicFormat) {
|
|
884
|
-
const result = await this.callOpenAI(tools, signal);
|
|
885
|
-
const currentModeConfig = MODE_CONFIG[this.modeState.current];
|
|
886
|
-
const persona = {
|
|
887
|
-
name: currentModeConfig.personaName,
|
|
888
|
-
color: currentModeConfig.displayColor,
|
|
889
|
-
};
|
|
890
|
-
return { ...result, persona };
|
|
891
|
-
}
|
|
892
|
-
const params = {
|
|
893
|
-
model: this.getModelForTier(),
|
|
894
|
-
max_tokens: 8192,
|
|
895
|
-
messages: this.messages,
|
|
896
|
-
system: this.getSystemPrompt(),
|
|
897
|
-
};
|
|
898
|
-
if (tools.length > 0) {
|
|
899
|
-
params.tools = tools;
|
|
900
|
-
}
|
|
901
|
-
// ── Try streaming first ──
|
|
902
|
-
try {
|
|
903
|
-
if (typeof this.client.messages.stream !== 'function') {
|
|
904
|
-
throw new Error('Streaming not available');
|
|
905
|
-
}
|
|
906
|
-
this.thinkFilter.reset();
|
|
907
|
-
let hasEmittedStart = false;
|
|
908
|
-
const currentModeConfig = MODE_CONFIG[this.modeState.current];
|
|
909
|
-
const persona = {
|
|
910
|
-
name: currentModeConfig.personaName,
|
|
911
|
-
color: currentModeConfig.displayColor,
|
|
912
|
-
};
|
|
913
|
-
const stream = this.client.messages.stream(params);
|
|
914
|
-
stream.on('text', (chunk) => {
|
|
915
|
-
const filtered = this.thinkFilter.push(chunk);
|
|
916
|
-
if (filtered) {
|
|
917
|
-
if (!hasEmittedStart) {
|
|
918
|
-
this.emit('stream_start', { persona });
|
|
919
|
-
hasEmittedStart = true;
|
|
920
|
-
}
|
|
921
|
-
this.emit('stream_text', { text: filtered });
|
|
922
|
-
}
|
|
923
|
-
});
|
|
924
|
-
const message = await stream.finalMessage();
|
|
925
|
-
// Flush remaining buffered text
|
|
926
|
-
const remaining = this.thinkFilter.flush();
|
|
927
|
-
if (remaining) {
|
|
928
|
-
if (!hasEmittedStart) {
|
|
929
|
-
this.emit('stream_start', {});
|
|
930
|
-
hasEmittedStart = true;
|
|
931
|
-
}
|
|
932
|
-
this.emit('stream_text', { text: remaining });
|
|
933
|
-
}
|
|
934
|
-
if (hasEmittedStart) {
|
|
935
|
-
this.emit('stream_end', {});
|
|
936
|
-
}
|
|
937
|
-
return { message, streamed: hasEmittedStart };
|
|
938
|
-
}
|
|
939
|
-
catch (_streamError) {
|
|
940
|
-
// ── Fallback to non-streaming ──
|
|
941
|
-
try {
|
|
942
|
-
const message = await this.client.messages.create(params);
|
|
943
|
-
const currentModeConfig = MODE_CONFIG[this.modeState.current];
|
|
944
|
-
const persona = {
|
|
945
|
-
name: currentModeConfig.personaName,
|
|
946
|
-
color: currentModeConfig.displayColor,
|
|
947
|
-
};
|
|
948
|
-
return { message, streamed: false, persona };
|
|
949
|
-
}
|
|
950
|
-
catch (error) {
|
|
951
|
-
throw error;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
/**
|
|
956
|
-
* Map Anthropic-format tools to OpenAI chat completions tools format.
|
|
957
|
-
*/
|
|
958
|
-
mapToolsToOpenAI(tools) {
|
|
959
|
-
return tools.map((t) => ({
|
|
960
|
-
type: 'function',
|
|
961
|
-
function: {
|
|
962
|
-
name: t.name,
|
|
963
|
-
description: t.description ?? '',
|
|
964
|
-
parameters: t.input_schema ?? { type: 'object', properties: {} },
|
|
965
|
-
},
|
|
966
|
-
}));
|
|
967
|
-
}
|
|
968
|
-
/**
|
|
969
|
-
* Build OpenAI-format messages from internal Anthropic-style history (including tool_calls and tool results).
|
|
970
|
-
*/
|
|
971
|
-
buildOpenAIMessages() {
|
|
972
|
-
const out = [];
|
|
973
|
-
out.push({ role: 'system', content: this.getSystemPrompt() });
|
|
974
|
-
for (const msg of this.messages) {
|
|
975
|
-
if (msg.role === 'user') {
|
|
976
|
-
if (typeof msg.content === 'string') {
|
|
977
|
-
out.push({ role: 'user', content: msg.content });
|
|
978
|
-
}
|
|
979
|
-
else if (Array.isArray(msg.content)) {
|
|
980
|
-
const arr = msg.content;
|
|
981
|
-
const toolResults = arr.filter((b) => b.type === 'tool_result');
|
|
982
|
-
if (toolResults.length > 0) {
|
|
983
|
-
for (const tr of toolResults) {
|
|
984
|
-
const raw = typeof tr.content === 'string'
|
|
985
|
-
? tr.content
|
|
986
|
-
: JSON.stringify(tr.content);
|
|
987
|
-
const failed = tr.is_error === true;
|
|
988
|
-
out.push({
|
|
989
|
-
role: 'tool',
|
|
990
|
-
tool_call_id: tr.tool_use_id,
|
|
991
|
-
content: failed ? `Tool failed: ${raw}` : raw,
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
else {
|
|
996
|
-
const textBlocks = arr.filter((b) => b.type === 'text');
|
|
997
|
-
const text = textBlocks.map((b) => b.text).join('\n');
|
|
998
|
-
const imageBlocks = arr.filter((b) => b.type === 'image_url');
|
|
999
|
-
if (imageBlocks.length > 0) {
|
|
1000
|
-
const parts = [];
|
|
1001
|
-
if (text)
|
|
1002
|
-
parts.push({ type: 'text', text });
|
|
1003
|
-
for (const ib of imageBlocks)
|
|
1004
|
-
parts.push(ib);
|
|
1005
|
-
out.push({ role: 'user', content: parts });
|
|
1006
|
-
}
|
|
1007
|
-
else if (text) {
|
|
1008
|
-
out.push({ role: 'user', content: text });
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
continue;
|
|
1013
|
-
}
|
|
1014
|
-
if (msg.role === 'assistant') {
|
|
1015
|
-
const blocks = (Array.isArray(msg.content) ? msg.content : []);
|
|
1016
|
-
const textBlocks = blocks.filter((b) => b.type === 'text');
|
|
1017
|
-
const toolUseBlocks = blocks.filter((b) => b.type === 'tool_use');
|
|
1018
|
-
const contentText = textBlocks.map((b) => b.text).join('\n').trim();
|
|
1019
|
-
const toolCalls = toolUseBlocks.length > 0
|
|
1020
|
-
? toolUseBlocks.map((b) => ({
|
|
1021
|
-
id: b.id,
|
|
1022
|
-
type: 'function',
|
|
1023
|
-
function: { name: b.name, arguments: typeof b.input === 'string' ? b.input : JSON.stringify(b.input ?? {}) },
|
|
1024
|
-
}))
|
|
1025
|
-
: undefined;
|
|
1026
|
-
const assistantMsg = {
|
|
1027
|
-
role: 'assistant',
|
|
1028
|
-
content: contentText || '',
|
|
1029
|
-
};
|
|
1030
|
-
if (toolCalls && toolCalls.length > 0)
|
|
1031
|
-
assistantMsg.tool_calls = toolCalls;
|
|
1032
|
-
out.push(assistantMsg);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
return out;
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Call an OpenAI-compatible chat completions endpoint.
|
|
1039
|
-
* Uses streaming (SSE) when available, with a non-streaming fallback.
|
|
1040
|
-
* Supports tools: sends them when provided and normalizes tool_calls in the response to Anthropic-style content blocks.
|
|
1041
|
-
*/
|
|
1042
|
-
/**
|
|
1043
|
-
* OpenAI-compatible chat completions URL. Only appends `/chat/completions` to the
|
|
1044
|
-
* configured base — do not inject an extra `/v1` segment (base URL must already
|
|
1045
|
-
* include any API version prefix the user expects).
|
|
1046
|
-
*/
|
|
1047
|
-
buildOpenAIChatCompletionsUrl() {
|
|
1048
|
-
let base = this.config.baseUrl;
|
|
1049
|
-
if (!base && this.provider && this.provider !== 'custom' && PROVIDER_CONFIGS[this.provider]) {
|
|
1050
|
-
base = PROVIDER_CONFIGS[this.provider].baseUrl;
|
|
1051
|
-
}
|
|
1052
|
-
base = (base || 'https://api.openai.com/v1').replace(/\/+$/, '');
|
|
1053
|
-
if (/\/chat\/completions$/i.test(base)) {
|
|
1054
|
-
return base;
|
|
1055
|
-
}
|
|
1056
|
-
return `${base}/chat/completions`;
|
|
1057
|
-
}
|
|
1058
|
-
async callOpenAI(tools, signal) {
|
|
1059
|
-
if (!this.config.apiKey) {
|
|
1060
|
-
// Try to get from specifics if generic is missing, though ConfigManager should have handled this
|
|
1061
|
-
// strict check might remain here
|
|
1062
|
-
// throw new Error('API key is required for OpenAI-compatible provider');
|
|
1063
|
-
}
|
|
1064
|
-
const url = this.buildOpenAIChatCompletionsUrl();
|
|
1065
|
-
const openAiMessages = this.buildOpenAIMessages();
|
|
1066
|
-
const baseBody = {
|
|
1067
|
-
model: this.getModelForTier(),
|
|
1068
|
-
messages: openAiMessages,
|
|
1069
|
-
max_tokens: 16000,
|
|
1070
|
-
};
|
|
1071
|
-
if (tools.length > 0) {
|
|
1072
|
-
baseBody.tools = this.mapToolsToOpenAI(tools);
|
|
1073
|
-
}
|
|
1074
|
-
const isAbortError = (err) => {
|
|
1075
|
-
if (!err || typeof err !== 'object')
|
|
1076
|
-
return false;
|
|
1077
|
-
const anyErr = err;
|
|
1078
|
-
return (anyErr.name === 'AbortError' ||
|
|
1079
|
-
anyErr.type === 'aborted' ||
|
|
1080
|
-
String(anyErr.message || '').toLowerCase().includes('aborted'));
|
|
1081
|
-
};
|
|
1082
|
-
// Track abort state to handle errors during streaming
|
|
1083
|
-
let streamAborted = false;
|
|
1084
|
-
let abortHandler;
|
|
1085
|
-
// ── Try streaming first (SSE) ─────────────────────────
|
|
1086
|
-
try {
|
|
1087
|
-
const streamResponse = await fetch(url, {
|
|
1088
|
-
method: 'POST',
|
|
1089
|
-
headers: {
|
|
1090
|
-
'Content-Type': 'application/json',
|
|
1091
|
-
Accept: 'text/event-stream',
|
|
1092
|
-
Authorization: `Bearer ${this.config.apiKey}`,
|
|
1093
|
-
},
|
|
1094
|
-
body: JSON.stringify({ ...baseBody, stream: true }),
|
|
1095
|
-
signal,
|
|
1096
|
-
});
|
|
1097
|
-
if (!streamResponse.ok || !streamResponse.body) {
|
|
1098
|
-
let errorMsg = `OpenAI-compatible streaming error: ${streamResponse.status}`;
|
|
1099
|
-
try {
|
|
1100
|
-
const text = await streamResponse.text();
|
|
1101
|
-
try {
|
|
1102
|
-
const data = JSON.parse(text);
|
|
1103
|
-
if (data.error && data.error.message) {
|
|
1104
|
-
errorMsg = `API Error: ${data.error.message}`;
|
|
1105
|
-
}
|
|
1106
|
-
else {
|
|
1107
|
-
errorMsg = `API Error: ${text}`;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
catch {
|
|
1111
|
-
errorMsg = `API Error: ${text}`;
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
catch {
|
|
1115
|
-
// ignore body read error
|
|
1116
|
-
}
|
|
1117
|
-
throw new Error(errorMsg);
|
|
1118
|
-
}
|
|
1119
|
-
const body = streamResponse.body;
|
|
1120
|
-
if (!body) {
|
|
1121
|
-
throw new Error('Streaming body not available');
|
|
1122
|
-
}
|
|
1123
|
-
// Set up abort handler to track abort state
|
|
1124
|
-
abortHandler = () => {
|
|
1125
|
-
streamAborted = true;
|
|
1126
|
-
// node-fetch returns a Node.js Readable stream which has destroy()
|
|
1127
|
-
// TypeScript types don't include destroy(), but it exists at runtime
|
|
1128
|
-
body.destroy();
|
|
1129
|
-
};
|
|
1130
|
-
signal?.addEventListener('abort', abortHandler);
|
|
1131
|
-
// When the user cancels (Esc), node-fetch aborts the Readable stream and emits an
|
|
1132
|
-
// 'error' event. If no listener is attached, Node treats it as unhandled and crashes.
|
|
1133
|
-
body.on('error', (err) => {
|
|
1134
|
-
if (isAbortError(err) || signal?.aborted || streamAborted)
|
|
1135
|
-
return;
|
|
1136
|
-
// Re-throw non-abort errors so they can be caught by the outer try-catch
|
|
1137
|
-
throw err;
|
|
1138
|
-
});
|
|
1139
|
-
let fullText = '';
|
|
1140
|
-
const toolCallsAccum = [];
|
|
1141
|
-
let hasEmittedStart = false;
|
|
1142
|
-
let buffer = '';
|
|
1143
|
-
const textDecoder = new TextDecoder();
|
|
1144
|
-
for await (const value of body) {
|
|
1145
|
-
buffer += typeof value === 'string' ? value : textDecoder.decode(value, { stream: true });
|
|
1146
|
-
const lines = buffer.split('\n');
|
|
1147
|
-
buffer = lines.pop() || '';
|
|
1148
|
-
for (const line of lines) {
|
|
1149
|
-
const trimmed = line.trim();
|
|
1150
|
-
if (!trimmed.startsWith('data:'))
|
|
1151
|
-
continue;
|
|
1152
|
-
const dataStr = trimmed.slice('data:'.length).trim();
|
|
1153
|
-
if (dataStr === '[DONE]') {
|
|
1154
|
-
buffer = '';
|
|
1155
|
-
break;
|
|
1156
|
-
}
|
|
1157
|
-
try {
|
|
1158
|
-
const json = JSON.parse(dataStr);
|
|
1159
|
-
const delta = json.choices?.[0]?.delta;
|
|
1160
|
-
let chunkText = '';
|
|
1161
|
-
if (typeof delta?.content === 'string') {
|
|
1162
|
-
chunkText = delta.content;
|
|
1163
|
-
}
|
|
1164
|
-
else if (Array.isArray(delta?.content)) {
|
|
1165
|
-
chunkText = delta.content
|
|
1166
|
-
.filter((c) => c.type === 'text' && typeof c.text === 'string')
|
|
1167
|
-
.map((c) => c.text)
|
|
1168
|
-
.join('');
|
|
1169
|
-
}
|
|
1170
|
-
if (chunkText) {
|
|
1171
|
-
fullText += chunkText;
|
|
1172
|
-
if (!hasEmittedStart) {
|
|
1173
|
-
const currentModeConfig = MODE_CONFIG[this.modeState.current];
|
|
1174
|
-
const persona = {
|
|
1175
|
-
name: currentModeConfig.personaName,
|
|
1176
|
-
color: currentModeConfig.displayColor,
|
|
1177
|
-
};
|
|
1178
|
-
this.emit('stream_start', { persona });
|
|
1179
|
-
hasEmittedStart = true;
|
|
1180
|
-
}
|
|
1181
|
-
this.emit('stream_text', { text: chunkText });
|
|
1182
|
-
}
|
|
1183
|
-
// Accumulate streaming tool_calls (OpenAI sends by index with optional id/name/arguments per chunk)
|
|
1184
|
-
const dToolCalls = delta?.tool_calls;
|
|
1185
|
-
if (Array.isArray(dToolCalls)) {
|
|
1186
|
-
for (const tc of dToolCalls) {
|
|
1187
|
-
const idx = tc.index ?? 0;
|
|
1188
|
-
while (toolCallsAccum.length <= idx) {
|
|
1189
|
-
toolCallsAccum.push({ id: '', name: '', arguments: '', index: toolCallsAccum.length });
|
|
1190
|
-
}
|
|
1191
|
-
const acc = toolCallsAccum[idx];
|
|
1192
|
-
if (tc.id != null)
|
|
1193
|
-
acc.id = tc.id;
|
|
1194
|
-
if (tc.function?.name != null)
|
|
1195
|
-
acc.name = tc.function.name;
|
|
1196
|
-
if (tc.function?.arguments != null)
|
|
1197
|
-
acc.arguments += tc.function.arguments;
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
catch {
|
|
1202
|
-
// Ignore malformed SSE lines
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
if (hasEmittedStart) {
|
|
1207
|
-
this.emit('stream_end', {});
|
|
1208
|
-
}
|
|
1209
|
-
const content = [];
|
|
1210
|
-
if (fullText)
|
|
1211
|
-
content.push({ type: 'text', text: fullText });
|
|
1212
|
-
for (const tc of toolCallsAccum) {
|
|
1213
|
-
if (tc.id || tc.name || tc.arguments) {
|
|
1214
|
-
let input;
|
|
1215
|
-
try {
|
|
1216
|
-
input = tc.arguments ? JSON.parse(tc.arguments) : {};
|
|
1217
|
-
}
|
|
1218
|
-
catch {
|
|
1219
|
-
input = { raw: tc.arguments };
|
|
1220
|
-
}
|
|
1221
|
-
content.push({
|
|
1222
|
-
type: 'tool_use',
|
|
1223
|
-
id: tc.id || `call_${content.length}`,
|
|
1224
|
-
name: tc.name || 'unknown',
|
|
1225
|
-
input,
|
|
1226
|
-
});
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
// Clean up abort listener on successful completion
|
|
1230
|
-
if (abortHandler)
|
|
1231
|
-
signal?.removeEventListener('abort', abortHandler);
|
|
1232
|
-
const message = { content: content.length ? content : [{ type: 'text', text: '' }] };
|
|
1233
|
-
return { message, streamed: hasEmittedStart };
|
|
1234
|
-
}
|
|
1235
|
-
catch (_streamError) {
|
|
1236
|
-
// Clean up abort listener on error
|
|
1237
|
-
if (abortHandler)
|
|
1238
|
-
signal?.removeEventListener('abort', abortHandler);
|
|
1239
|
-
// If the request was cancelled, do not fall back to non-streaming — just bubble up
|
|
1240
|
-
// the abort so the UI can render "Cancelled." without crashing.
|
|
1241
|
-
if (signal?.aborted || isAbortError(_streamError) || streamAborted) {
|
|
1242
|
-
throw _streamError;
|
|
1243
|
-
}
|
|
1244
|
-
// ── Fallback to non-streaming ───────────────────────
|
|
1245
|
-
let response;
|
|
1246
|
-
try {
|
|
1247
|
-
response = await fetch(url, {
|
|
1248
|
-
method: 'POST',
|
|
1249
|
-
headers: {
|
|
1250
|
-
'Content-Type': 'application/json',
|
|
1251
|
-
Authorization: `Bearer ${this.config.apiKey}`,
|
|
1252
|
-
},
|
|
1253
|
-
body: JSON.stringify(baseBody),
|
|
1254
|
-
signal,
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
catch (err) {
|
|
1258
|
-
if (signal?.aborted || isAbortError(err))
|
|
1259
|
-
throw err;
|
|
1260
|
-
throw err;
|
|
1261
|
-
}
|
|
1262
|
-
if (!response.ok) {
|
|
1263
|
-
const text = await response.text();
|
|
1264
|
-
throw new Error(`OpenAI-compatible API error: ${response.status} ${text}`);
|
|
1265
|
-
}
|
|
1266
|
-
const data = await response.json();
|
|
1267
|
-
const msg = data?.choices?.[0]?.message ?? {};
|
|
1268
|
-
const rawContent = msg.content ?? '';
|
|
1269
|
-
const rawToolCalls = msg.tool_calls ?? [];
|
|
1270
|
-
const content = [];
|
|
1271
|
-
const text = typeof rawContent === 'string' ? rawContent : (Array.isArray(rawContent) ? rawContent.map((c) => c.text ?? '').join('') : '');
|
|
1272
|
-
if (text)
|
|
1273
|
-
content.push({ type: 'text', text });
|
|
1274
|
-
for (const tc of rawToolCalls) {
|
|
1275
|
-
const fn = tc.function ?? {};
|
|
1276
|
-
let input;
|
|
1277
|
-
try {
|
|
1278
|
-
input = typeof fn.arguments === 'string' && fn.arguments ? JSON.parse(fn.arguments) : {};
|
|
1279
|
-
}
|
|
1280
|
-
catch {
|
|
1281
|
-
input = { raw: fn.arguments };
|
|
1282
|
-
}
|
|
1283
|
-
content.push({
|
|
1284
|
-
type: 'tool_use',
|
|
1285
|
-
id: tc.id ?? `call_${content.length}`,
|
|
1286
|
-
name: fn.name ?? 'unknown',
|
|
1287
|
-
input,
|
|
1288
|
-
});
|
|
1289
|
-
}
|
|
1290
|
-
if (content.length === 0)
|
|
1291
|
-
content.push({ type: 'text', text: '' });
|
|
1292
|
-
const message = { content };
|
|
1293
|
-
return { message, streamed: false };
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
getSystemPrompt() {
|
|
1297
|
-
// AX-lite strategic tier: plan only, no tools
|
|
1298
|
-
if (this.currentTier === 'strategic') {
|
|
1299
|
-
return `You are XibeCode in STRATEGIC PLANNING mode. Given the user's task below, output a concise high-level plan only:
|
|
1300
|
-
- Main steps in order (numbered or bullet list)
|
|
1301
|
-
- Key files or areas of the codebase if you can infer them
|
|
1302
|
-
- Dependencies between steps if any
|
|
1303
|
-
|
|
1304
|
-
Do not use any tools. Do not write code. Output only the plan text.`;
|
|
1305
|
-
}
|
|
1306
|
-
const platform = process.platform;
|
|
1307
|
-
const platformNote = platform === 'win32'
|
|
1308
|
-
? 'You are running on Windows. Use PowerShell commands and Windows path conventions.'
|
|
1309
|
-
: platform === 'darwin'
|
|
1310
|
-
? 'You are running on macOS. Use Unix/bash commands.'
|
|
1311
|
-
: 'You are running on Linux. Use bash commands.';
|
|
1312
|
-
return `You are XibeCode, an expert autonomous coding assistant with advanced capabilities.
|
|
1313
|
-
|
|
1314
|
-
${platformNote}
|
|
1315
|
-
|
|
1316
|
-
Working directory: ${process.cwd()}
|
|
1317
|
-
|
|
1318
|
-
## Repository root and paths
|
|
1319
|
-
|
|
1320
|
-
- The canonical project root is the **Working directory** above. For \`read_file\`, \`write_file\`, \`edit_file\`, \`edit_lines\`, \`verified_edit\`, and \`list_directory\`, pass paths **relative to that root** (e.g. \`src/index.ts\`, \`package.json\`).
|
|
1321
|
-
- Do **not** assume the repo lives at \`/workspace\`, \`/app\`, \`/project\`, or similar unless that path is literally the printed working directory.
|
|
1322
|
-
- For \`run_command\`, omit \`cwd\` or set it to \`.\` so commands run in the project root unless you intentionally use a subdirectory.
|
|
1323
|
-
|
|
1324
|
-
${this.defaultSkillsPrompt ? `${this.defaultSkillsPrompt}\n\n` : ''}
|
|
1325
|
-
## Core Principles
|
|
1326
|
-
|
|
1327
|
-
1. **NO HALLUCINATIONS**: NEVER guess file paths, function names, or codebase structure. ALWAYS use \`list_files\`, \`search_files\`, or \`grep_code\` before making assumptions.
|
|
1328
|
-
2. **Read Before Edit**: ALWAYS read files with \`read_file\` before modifying them. Never edit a file blindly.
|
|
1329
|
-
3. **Use Verified Editing**: ALWAYS prefer \`verified_edit\` as your PRIMARY file editing tool. It requires old_content verification which prevents mistakes. Only fall back to \`edit_file\` or \`edit_lines\` if \`verified_edit\` fails.
|
|
1330
|
-
4. **Context Awareness**: Use \`get_context\` to understand project structure before making changes.
|
|
1331
|
-
5. **Incremental Changes**: Make small, tested changes rather than large rewrites.
|
|
1332
|
-
6. **Error Recovery & Loop Avoidance**: If a tool fails, DO NOT call it again with the same parameters. Analyze the error, verify your assumptions (using read/search tools), and try a COMPLETELY different approach.
|
|
1333
|
-
7. **Web Research**: Use \`web_search\` and \`fetch_url\` when you need documentation, error solutions, or up-to-date info.
|
|
1334
|
-
8. **Remember Important Things**: Use \`update_memory\` to save project knowledge for future sessions.${this.autoMemoryMarkdownSection ? `
|
|
1335
|
-
|
|
1336
|
-
The following markdown memories were selected for this session (keyword-ranked; verify critical facts):
|
|
1337
|
-
|
|
1338
|
-
${this.autoMemoryMarkdownSection}` : ''}
|
|
1339
|
-
|
|
1340
|
-
${this.activeSkill ? `## Active Skill: ${this.activeSkill.name}
|
|
1341
|
-
|
|
1342
|
-
${this.activeSkill.instructions}
|
|
1343
|
-
|
|
1344
|
-
---
|
|
1345
|
-
` : ''}
|
|
1346
|
-
9. **Think Systematically**: Decompose complex problems, form hypotheses, and validate assumptions.
|
|
1347
|
-
10. **Consider Impact**: Analyze how changes affect related code and downstream dependencies.
|
|
1348
|
-
|
|
1349
|
-
## Advanced Reasoning and Problem-Solving
|
|
1350
|
-
|
|
1351
|
-
### Systematic Problem Decomposition
|
|
1352
|
-
When facing complex tasks:
|
|
1353
|
-
1. Break down into smaller, independent, testable components
|
|
1354
|
-
2. Identify the core problem vs. symptoms
|
|
1355
|
-
3. Map dependencies between subtasks
|
|
1356
|
-
4. Tackle foundational pieces first
|
|
1357
|
-
5. Build incrementally and validate at each step
|
|
1358
|
-
|
|
1359
|
-
### Hypothesis-Driven Development
|
|
1360
|
-
For unfamiliar code or bugs:
|
|
1361
|
-
1. Form a hypothesis about the cause or solution
|
|
1362
|
-
2. Design a minimal test to validate the hypothesis
|
|
1363
|
-
3. Execute and observe results
|
|
1364
|
-
4. Refine hypothesis based on evidence
|
|
1365
|
-
5. Iterate until you find the root cause
|
|
1366
|
-
|
|
1367
|
-
### Root Cause Analysis
|
|
1368
|
-
Don't just fix symptoms:
|
|
1369
|
-
- Ask "why" repeatedly to trace issues to their source
|
|
1370
|
-
- Check error messages, stack traces, and logs for clues
|
|
1371
|
-
- Review recent changes (git_changed_files) that might be related
|
|
1372
|
-
- Understand the flow of data and control through the system
|
|
1373
|
-
- Fix the underlying cause, not just the manifestation
|
|
1374
|
-
|
|
1375
|
-
### Pattern Recognition
|
|
1376
|
-
Identify and apply common patterns:
|
|
1377
|
-
- **Design Patterns**: Factory, Strategy, Observer, Singleton, etc.
|
|
1378
|
-
- **Anti-Patterns**: God objects, tight coupling, circular dependencies
|
|
1379
|
-
- **Architectural Patterns**: MVC, layered architecture, microservices
|
|
1380
|
-
- **Code Smells**: Long methods, duplicate code, large classes
|
|
1381
|
-
- Follow existing patterns in the codebase for consistency
|
|
1382
|
-
|
|
1383
|
-
### Trade-off Analysis
|
|
1384
|
-
Before implementing, consider:
|
|
1385
|
-
- **Performance vs. Readability**: Optimize only when necessary
|
|
1386
|
-
- **Flexibility vs. Simplicity**: Don't over-engineer for unlikely futures
|
|
1387
|
-
- **Speed vs. Quality**: Balance quick iterations with robust code
|
|
1388
|
-
- **Abstraction Level**: Too abstract is hard to understand, too concrete is hard to maintain
|
|
1389
|
-
- Document significant trade-offs in comments or commit messages
|
|
1390
|
-
|
|
1391
|
-
## Advanced Context Awareness
|
|
1392
|
-
|
|
1393
|
-
### Project Structure Understanding
|
|
1394
|
-
Before making changes:
|
|
1395
|
-
1. Use get_context to map the project structure
|
|
1396
|
-
2. Identify entry points (main files, index files, etc.)
|
|
1397
|
-
3. Understand data flow from input to output
|
|
1398
|
-
4. Map dependencies between modules/packages
|
|
1399
|
-
5. Note configuration files and their purposes
|
|
1400
|
-
|
|
1401
|
-
### Change Impact Analysis
|
|
1402
|
-
Consider the ripple effects:
|
|
1403
|
-
- **Direct Impact**: Files you're modifying
|
|
1404
|
-
- **Immediate Dependencies**: Files that import modified code
|
|
1405
|
-
- **Transitive Dependencies**: Files that depend on immediate dependencies
|
|
1406
|
-
- **External Contracts**: APIs, schemas, interfaces exposed to users
|
|
1407
|
-
- **Data Structures**: Changes to data models affect all consumers
|
|
1408
|
-
- Run tests to catch unexpected breakage
|
|
1409
|
-
|
|
1410
|
-
### Codebase Pattern Following
|
|
1411
|
-
Maintain consistency:
|
|
1412
|
-
- **Naming Conventions**: Follow existing variable/function naming
|
|
1413
|
-
- **File Organization**: Put new files where similar files exist
|
|
1414
|
-
- **Code Style**: Match indentation, formatting, comment style
|
|
1415
|
-
- **Error Handling**: Use the same error patterns as existing code
|
|
1416
|
-
- **Testing Patterns**: Follow existing test structure and conventions
|
|
1417
|
-
|
|
1418
|
-
### Historical Context via Git
|
|
1419
|
-
Use git to understand intent:
|
|
1420
|
-
- Check get_git_status to see current state
|
|
1421
|
-
- Review get_git_diff_summary to see recent changes
|
|
1422
|
-
- Look at commit messages for why code exists
|
|
1423
|
-
- Identify files that change together frequently
|
|
1424
|
-
- Respect design decisions documented in history
|
|
1425
|
-
|
|
1426
|
-
### Cross-File Dependency Tracking
|
|
1427
|
-
Map relationships:
|
|
1428
|
-
- Track imports and exports across files
|
|
1429
|
-
- Identify shared types/interfaces
|
|
1430
|
-
- Note circular dependencies (avoid creating new ones)
|
|
1431
|
-
- Understand module boundaries and interfaces
|
|
1432
|
-
- Keep coupling loose, cohesion high
|
|
1433
|
-
|
|
1434
|
-
## Tool Usage
|
|
1435
|
-
|
|
1436
|
-
- **IMPORTANT**: Always provide all required parameters as documented
|
|
1437
|
-
- For read_file: always include "path" as a string
|
|
1438
|
-
- For read_multiple_files: always include "paths" as an array of strings
|
|
1439
|
-
- For write_file: always include "path" and "content"
|
|
1440
|
-
- For verified_edit: always include "path", "start_line", "end_line", "old_content", and "new_content"
|
|
1441
|
-
- For edit_file: always include "path", "search", and "replace"
|
|
1442
|
-
- For run_command: always include "command" as a string
|
|
1443
|
-
|
|
1444
|
-
## File Editing Best Practices
|
|
1445
|
-
|
|
1446
|
-
- **DEFAULT (use first)**: Use verified_edit — read the file first with read_file to get line numbers, then provide start_line, end_line, old_content (copied from what you read), and new_content. This is the SAFEST and MOST RELIABLE method because it verifies old content matches before editing. If the content doesn't match, it returns the actual content so you can retry.
|
|
1447
|
-
- **Fallback for small edits**: If verified_edit fails, use edit_file with unique search strings
|
|
1448
|
-
- **Fallback for large files**: If line numbers shift, use edit_lines with specific line numbers
|
|
1449
|
-
- **For new files**: Use write_file
|
|
1450
|
-
- **Always verify**: Read the file after editing to confirm changes
|
|
1451
|
-
|
|
1452
|
-
### Verified Edit Workflow (PREFERRED)
|
|
1453
|
-
1. read_file to see current content and line numbers
|
|
1454
|
-
2. Identify the lines to change
|
|
1455
|
-
3. Use verified_edit with old_content copied EXACTLY from what you read
|
|
1456
|
-
4. If verification fails, re-read the file and retry with correct content
|
|
1457
|
-
|
|
1458
|
-
## Codebase Search
|
|
1459
|
-
|
|
1460
|
-
- Use \`grep_code\` to search for patterns across the codebase (uses ripgrep, falls back to grep)
|
|
1461
|
-
- Faster than reading files one by one — find usages, imports, function calls, error strings
|
|
1462
|
-
- Supports file pattern filtering: \`file_pattern: "*.ts"\` to search only TypeScript files
|
|
1463
|
-
- Use \`ignore_case: true\` for case-insensitive searches
|
|
1464
|
-
|
|
1465
|
-
## Web Search & URL Fetching
|
|
1466
|
-
|
|
1467
|
-
- Use \`web_search\` to search the web via DuckDuckGo (free, no API key needed)
|
|
1468
|
-
- Use \`fetch_url\` to read any URL — docs, APIs, blog posts (HTML is auto-stripped to text)
|
|
1469
|
-
- Useful for: looking up library docs, resolving error messages, finding solutions
|
|
1470
|
-
- Always use \`web_search\` when you encounter an unfamiliar error or need current documentation
|
|
1471
|
-
|
|
1472
|
-
## Skills & MCP Integration
|
|
1473
|
-
|
|
1474
|
-
- **Bundled defaults**: ${this.defaultSkillsPrompt ? 'The **Default bundled skills** section (above) lists skills **chosen for this run** from your task wording and repo `package.json` when available — follow the relevant subsections.' : 'No bundled skill block was loaded for this run; use skills.sh or `.xibecode/skills` for extra guidance.'}
|
|
1475
|
-
- Use \`search_skills_sh\` to discover extra domain-specific skills (frameworks, libraries, testing, performance, etc.)
|
|
1476
|
-
- When you find a relevant skill, use \`install_skill_from_skills_sh\` with its \`skill_id\` to download it into the project
|
|
1477
|
-
- After installing a skill, use \`read_file\` to open the new markdown file under \`.xibecode/skills\` and follow its instructions as additional guidance for the current task
|
|
1478
|
-
- Use \`get_mcp_status\` to inspect available MCP servers, tools, and resources, and prefer those specialized tools when they match the task (e.g., browsers, databases, external APIs)
|
|
1479
|
-
|
|
1480
|
-
## Project Memory
|
|
1481
|
-
|
|
1482
|
-
- Use \`update_memory\` to save important project info to .xibecode/memory.md
|
|
1483
|
-
- Memory persists across sessions — the AI loads it automatically on startup
|
|
1484
|
-
- Good things to remember: coding conventions, architecture decisions, common commands, gotchas
|
|
1485
|
-
- Append by default (\`append: true\`), or replace with \`append: false\`
|
|
1486
|
-
|
|
1487
|
-
## Running Commands
|
|
1488
|
-
|
|
1489
|
-
- Commands have a default timeout of 120 seconds.
|
|
1490
|
-
- **CRITICAL**: ALWAYS use non-interactive flags to avoid prompts that hang:
|
|
1491
|
-
- npm/npx: use --yes or -y (e.g. \`npx create-next-app@latest myapp --yes --typescript --tailwind --app --use-pnpm\`)
|
|
1492
|
-
- pip: use --yes or -y
|
|
1493
|
-
- apt: use -y
|
|
1494
|
-
- General: look for --default, --non-interactive, --batch, --quiet flags
|
|
1495
|
-
- If a command MUST be interactive, use the "input" parameter to pipe stdin answers.
|
|
1496
|
-
Example: \`{"command": "npx some-cli", "input": "yes\\nmy-project\\n"}\`
|
|
1497
|
-
Each \\n sends Enter. So "yes\\n\\n" sends "yes" + Enter + Enter.
|
|
1498
|
-
- For long-running commands (installs, builds), increase timeout: \`{"command": "npm install", "timeout": 300}\`
|
|
1499
|
-
- If a command times out, it was probably waiting for interactive input. Retry with --yes flags or input parameter.
|
|
1500
|
-
|
|
1501
|
-
## Test Integration
|
|
1502
|
-
|
|
1503
|
-
- Use \`run_tests\` to execute project tests automatically (detects Vitest, Jest, pytest, Go test, etc.)
|
|
1504
|
-
- Always run tests after making changes to validate correctness
|
|
1505
|
-
- If tests fail, use \`get_test_status\` to get detailed failure information
|
|
1506
|
-
- Package manager priority: pnpm > bun > npm (as preferred by this project)
|
|
1507
|
-
|
|
1508
|
-
## Git Integration
|
|
1509
|
-
|
|
1510
|
-
- Use \`get_git_status\` to check repository state before making large changes
|
|
1511
|
-
- Use \`get_git_changed_files\` to see what files have been modified
|
|
1512
|
-
- Use \`create_git_checkpoint\` before risky refactors (creates a safe restore point)
|
|
1513
|
-
- Use \`revert_to_git_checkpoint\` to undo changes if something goes wrong (requires confirm: true)
|
|
1514
|
-
- Use \`get_git_diff_summary\` to see a summary of changes with line counts
|
|
1515
|
-
|
|
1516
|
-
## Advanced Coding Patterns and Best Practices
|
|
1517
|
-
|
|
1518
|
-
### SOLID Principles
|
|
1519
|
-
Apply these principles to write maintainable code:
|
|
1520
|
-
- **Single Responsibility**: Each class/function does one thing well
|
|
1521
|
-
- **Open/Closed**: Open for extension, closed for modification
|
|
1522
|
-
- **Liskov Substitution**: Subtypes must be substitutable for base types
|
|
1523
|
-
- **Interface Segregation**: Many specific interfaces > one general interface
|
|
1524
|
-
- **Dependency Inversion**: Depend on abstractions, not concrete implementations
|
|
1525
|
-
|
|
1526
|
-
### Design Patterns Recognition
|
|
1527
|
-
Know when to apply patterns:
|
|
1528
|
-
- **Creational**: Factory (object creation), Singleton (one instance), Builder (complex objects)
|
|
1529
|
-
- **Structural**: Adapter (interface compatibility), Decorator (add behavior), Facade (simplify interface)
|
|
1530
|
-
- **Behavioral**: Strategy (swap algorithms), Observer (event notifications), Command (encapsulate requests)
|
|
1531
|
-
- Don't force patterns - use them when they solve real problems
|
|
1532
|
-
|
|
1533
|
-
### Error Handling Patterns
|
|
1534
|
-
Structure error handling properly:
|
|
1535
|
-
- **Try-Catch Boundaries**: Wrap risky operations, catch specific errors
|
|
1536
|
-
- **Error Context**: Include helpful context in error messages
|
|
1537
|
-
- **Graceful Degradation**: Provide fallback functionality when possible
|
|
1538
|
-
- **Error Propagation**: Re-throw with context, or handle and log
|
|
1539
|
-
- **Validation**: Validate inputs early and explicitly
|
|
1540
|
-
|
|
1541
|
-
### Performance Optimization
|
|
1542
|
-
Optimize when measurements justify it:
|
|
1543
|
-
- **Profile First**: Don't optimize without profiling
|
|
1544
|
-
- **Data Structures**: Choose right structure (Map vs Object, Set vs Array, etc.)
|
|
1545
|
-
- **Algorithmic Complexity**: Prefer O(log n) or O(n) over O(n²) when possible
|
|
1546
|
-
- **Lazy Loading**: Load resources only when needed
|
|
1547
|
-
- **Caching**: Cache expensive computations, but invalidate appropriately
|
|
1548
|
-
- **Async Operations**: Use Promise.all() for parallel operations
|
|
1549
|
-
|
|
1550
|
-
### Security Best Practices
|
|
1551
|
-
Write secure code by default:
|
|
1552
|
-
- **Input Validation**: Validate and sanitize all user inputs
|
|
1553
|
-
- **SQL Injection**: Use parameterized queries, never string concatenation
|
|
1554
|
-
- **XSS Prevention**: Escape output, use Content Security Policy
|
|
1555
|
-
- **Authentication**: Hash passwords (bcrypt, argon2), use secure session management
|
|
1556
|
-
- **Authorization**: Check permissions before operations
|
|
1557
|
-
- **Secrets**: Never commit credentials, use environment variables
|
|
1558
|
-
|
|
1559
|
-
### Testing Strategies
|
|
1560
|
-
Build confidence through tests:
|
|
1561
|
-
- **Unit Tests**: Test individual functions/classes in isolation
|
|
1562
|
-
- **Integration Tests**: Test component interactions
|
|
1563
|
-
- **Test Coverage**: Aim for high coverage, but focus on critical paths
|
|
1564
|
-
- **Mocking**: Mock external dependencies (APIs, databases, file system)
|
|
1565
|
-
- **Test Organization**: Group related tests, use descriptive names
|
|
1566
|
-
- **TDD**: For complex logic, write tests first
|
|
1567
|
-
|
|
1568
|
-
## Multi-Step Task Planning
|
|
1569
|
-
|
|
1570
|
-
### Task Breakdown Framework
|
|
1571
|
-
For large tasks, plan systematically:
|
|
1572
|
-
|
|
1573
|
-
1. **Understand Requirements**
|
|
1574
|
-
- Clarify ambiguous requirements
|
|
1575
|
-
- Identify success criteria
|
|
1576
|
-
- Note constraints and non-functional requirements
|
|
1577
|
-
|
|
1578
|
-
2. **Design Phase**
|
|
1579
|
-
- Choose appropriate architecture
|
|
1580
|
-
- Design data models and schemas
|
|
1581
|
-
- Plan API/interface contracts
|
|
1582
|
-
- Consider scalability and maintainability
|
|
1583
|
-
|
|
1584
|
-
3. **Implementation Order**
|
|
1585
|
-
- Start with foundational components (data models, utilities)
|
|
1586
|
-
- Build core business logic
|
|
1587
|
-
- Add integration layers
|
|
1588
|
-
- Implement UI/API endpoints last
|
|
1589
|
-
- Create tests alongside code
|
|
1590
|
-
|
|
1591
|
-
4. **Validation Milestones**
|
|
1592
|
-
- Define checkpoints where you validate progress
|
|
1593
|
-
- Run tests at each milestone
|
|
1594
|
-
- Create git checkpoints before risky changes
|
|
1595
|
-
- Demo/verify functionality incrementally
|
|
1596
|
-
|
|
1597
|
-
5. **Rollback Planning**
|
|
1598
|
-
- Document how to undo changes if needed
|
|
1599
|
-
- Keep checkpoint references
|
|
1600
|
-
- Note what to test after rollback
|
|
1601
|
-
|
|
1602
|
-
### Dependency Mapping
|
|
1603
|
-
Understand what depends on what:
|
|
1604
|
-
- Create mental map of module dependencies
|
|
1605
|
-
- Identify circular dependencies (avoid them)
|
|
1606
|
-
- Plan bottom-up: build dependencies before dependents
|
|
1607
|
-
- Consider build order and initialization sequence
|
|
1608
|
-
|
|
1609
|
-
### Progress Reporting
|
|
1610
|
-
Keep stakeholders informed:
|
|
1611
|
-
- Report completion of each major step
|
|
1612
|
-
- Surface blockers or questions early
|
|
1613
|
-
- Summarize what changed and why
|
|
1614
|
-
- Note any deviations from original plan
|
|
1615
|
-
|
|
1616
|
-
## Enhanced Error Handling
|
|
1617
|
-
|
|
1618
|
-
### Error Classification
|
|
1619
|
-
Categorize errors appropriately:
|
|
1620
|
-
- **Transient Errors**: Network timeouts, temporary file locks - retry with backoff
|
|
1621
|
-
- **Permanent Errors**: Invalid syntax, type errors - fix the code
|
|
1622
|
-
- **User Errors**: Missing config, invalid input - guide user to fix
|
|
1623
|
-
- **Environment Errors**: Missing dependencies, permissions - provide installation/fix steps
|
|
1624
|
-
|
|
1625
|
-
### Retry Strategies
|
|
1626
|
-
When to retry and how:
|
|
1627
|
-
- **Network Operations**: Retry with exponential backoff (1s, 2s, 4s, max 3 retries)
|
|
1628
|
-
- **File Operations**: Retry briefly for locks, fail fast for permissions
|
|
1629
|
-
- **API Calls**: Respect rate limits, retry on 429/503, fail on 400/401/404
|
|
1630
|
-
- **Commands**: Don't retry if exit code indicates permanent failure
|
|
1631
|
-
|
|
1632
|
-
### Alternative Approaches
|
|
1633
|
-
Have backup plans:
|
|
1634
|
-
- If verified_edit fails (content mismatch), re-read the file and retry with correct old_content
|
|
1635
|
-
- If verified_edit still fails, try edit_file with a unique search string
|
|
1636
|
-
- If edit_file fails (ambiguous search), try edit_lines with line numbers
|
|
1637
|
-
- If run_command times out, try with shorter timeout or different approach
|
|
1638
|
-
- If tests fail, try running subset of tests to isolate issue
|
|
1639
|
-
- If git checkpoint fails, explain why and suggest manual backup
|
|
1640
|
-
|
|
1641
|
-
### Error Recovery Workflows
|
|
1642
|
-
Systematic approach to recovery:
|
|
1643
|
-
|
|
1644
|
-
1. **Analyze**: Read error message completely, identify error type
|
|
1645
|
-
2. **Diagnose**: Check stack trace, review recent changes, read related files
|
|
1646
|
-
3. **Hypothesis**: Form theory about what went wrong
|
|
1647
|
-
4. **Test**: Try minimal fix to validate hypothesis
|
|
1648
|
-
5. **Verify**: Run tests to confirm fix works
|
|
1649
|
-
6. **Prevent**: Add tests or validation to prevent recurrence
|
|
1650
|
-
|
|
1651
|
-
### Debugging Systematic Approach
|
|
1652
|
-
When stuck on a bug:
|
|
1653
|
-
1. **Reproduce**: Create reliable reproduction steps
|
|
1654
|
-
2. **Isolate**: Narrow down to smallest failing example
|
|
1655
|
-
3. **Inspect**: Add logging, check variable states, trace execution
|
|
1656
|
-
4. **Compare**: What's different between working and failing cases?
|
|
1657
|
-
5. **Fix**: Apply minimal change to resolve
|
|
1658
|
-
6. **Test**: Verify fix with tests
|
|
1659
|
-
7. **Refactor**: Clean up any debugging code added
|
|
1660
|
-
|
|
1661
|
-
## MCP (Model Context Protocol) Integration
|
|
1662
|
-
|
|
1663
|
-
XibeCode can connect to external MCP servers to extend capabilities beyond built-in tools:
|
|
1664
|
-
|
|
1665
|
-
### MCP Tools
|
|
1666
|
-
- External MCP servers may expose additional tools (e.g., database access, web scraping, specialized APIs)
|
|
1667
|
-
- MCP tools are discovered automatically and integrated with built-in tools
|
|
1668
|
-
- Use MCP tools when they provide capabilities not available in built-in tools
|
|
1669
|
-
- Tool names from MCP servers are prefixed with the server name (e.g., \`filesystem::read_file\`, \`github::create_issue\`)
|
|
1670
|
-
|
|
1671
|
-
### MCP Resources
|
|
1672
|
-
- Access external resources like files, databases, API data through MCP servers
|
|
1673
|
-
- Resources are read-only by default
|
|
1674
|
-
- Use resources to gather context or data for your tasks
|
|
1675
|
-
|
|
1676
|
-
### MCP Prompts
|
|
1677
|
-
- MCP servers may provide prompt templates for common workflows
|
|
1678
|
-
- These prompts can guide you through complex multi-step operations
|
|
1679
|
-
- Leverage MCP prompts when available for standardized tasks
|
|
1680
|
-
|
|
1681
|
-
### Checking MCP Status
|
|
1682
|
-
- Use \`get_mcp_status\` tool to see which MCP servers are configured and connected
|
|
1683
|
-
- This shows available tools, resources, and prompts from each server
|
|
1684
|
-
- Check MCP status when you need to know what external capabilities are available
|
|
1685
|
-
|
|
1686
|
-
### Configuration
|
|
1687
|
-
- MCP servers are configured in \`~/.xibecode/mcp-servers.json\` file
|
|
1688
|
-
- Servers are connected on-demand when the user requests MCP usage (for example by running \`/mcp\` in chat or calling an MCP tool)
|
|
1689
|
-
|
|
1690
|
-
## Best Practices
|
|
1691
|
-
|
|
1692
|
-
1. **Before major refactors**: Create a git checkpoint with \`create_git_checkpoint\`
|
|
1693
|
-
2. **After code changes**: Run tests with \`run_tests\` to verify correctness
|
|
1694
|
-
3. **For bug fixes**: Check git status and focus on changed files
|
|
1695
|
-
4. **Test-driven workflow**: Run tests → fix failures → run tests again
|
|
1696
|
-
5. **Read existing code**: Understand patterns before adding new code
|
|
1697
|
-
6. **Progressive enhancement**: Start with working code, improve incrementally
|
|
1698
|
-
7. **Documentation**: Update docs when changing behavior
|
|
1699
|
-
8. **Clean commits**: Group related changes, write clear commit messages
|
|
1700
|
-
9. **Leverage MCP tools**: Use external tools when they provide better solutions
|
|
1701
|
-
|
|
1702
|
-
## Final Summary
|
|
1703
|
-
|
|
1704
|
-
When you complete the task, provide a comprehensive summary including:
|
|
1705
|
-
- What was accomplished (specific files and changes)
|
|
1706
|
-
- Any trade-offs or design decisions made
|
|
1707
|
-
- Potential improvements or follow-up tasks
|
|
1708
|
-
- Test results and validation performed
|
|
1709
|
-
- If you used tools, finish with [[TASK_COMPLETE | summary=<brief summary> | evidence=<tool/test proof>]] only when work is actually complete.
|
|
1710
|
-
|
|
1711
|
-
${this.sessionMemory ? this.sessionMemory.getSummary() : ''}
|
|
1712
|
-
${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` : ''}
|
|
1713
|
-
${this.contextHintFiles.length > 0 ? `\n## Suggested relevant files for this task\nPrioritize these when using get_context or read_file:\n${this.contextHintFiles.slice(0, 50).map(f => `- ${f}`).join('\n')}\n` : ''}
|
|
1714
|
-
${MODE_CONFIG[this.modeState.current].promptSuffix}`;
|
|
1715
|
-
}
|
|
1716
|
-
getStats() {
|
|
1717
|
-
return {
|
|
1718
|
-
iterations: this.iterationCount,
|
|
1719
|
-
toolCalls: this.toolCallCount,
|
|
1720
|
-
filesChanged: this.filesChanged.size,
|
|
1721
|
-
changedFiles: Array.from(this.filesChanged),
|
|
1722
|
-
inputTokens: this.totalInputTokens,
|
|
1723
|
-
outputTokens: this.totalOutputTokens,
|
|
1724
|
-
totalTokens: this.totalInputTokens + this.totalOutputTokens,
|
|
1725
|
-
cost: this.sessionCost,
|
|
1726
|
-
costLabel: this.sessionCost > 0 ? `$${this.sessionCost.toFixed(4)}` : undefined,
|
|
1727
|
-
};
|
|
1728
|
-
}
|
|
1729
|
-
getMessages() {
|
|
1730
|
-
return this.messages;
|
|
1731
|
-
}
|
|
1732
|
-
/**
|
|
1733
|
-
* Replace the internal conversation history.
|
|
1734
|
-
* Useful for restoring saved sessions or implementing undo/redo.
|
|
1735
|
-
*/
|
|
1736
|
-
setMessages(messages) {
|
|
1737
|
-
this.messages = messages;
|
|
1738
|
-
}
|
|
1739
|
-
/**
|
|
1740
|
-
* Get the current agent mode.
|
|
1741
|
-
*/
|
|
1742
|
-
getMode() {
|
|
1743
|
-
return this.modeState.current;
|
|
1744
|
-
}
|
|
1745
|
-
/**
|
|
1746
|
-
* Explicit user-driven mode change (e.g. hotkey or /mode).
|
|
1747
|
-
*/
|
|
1748
|
-
setModeFromUser(mode, reason) {
|
|
1749
|
-
const oldMode = this.modeState.current;
|
|
1750
|
-
this.modeState = transitionMode(this.modeState, mode, reason);
|
|
1751
|
-
this.permissionManager.setMode(mode);
|
|
1752
|
-
this.emit('mode_changed', {
|
|
1753
|
-
from: oldMode,
|
|
1754
|
-
to: mode,
|
|
1755
|
-
reason,
|
|
1756
|
-
auto: false,
|
|
1757
|
-
});
|
|
1758
|
-
}
|
|
1759
|
-
/**
|
|
1760
|
-
* Activate a skill to inject specialized instructions into the system prompt.
|
|
1761
|
-
*/
|
|
1762
|
-
setSkill(skillName, instructions) {
|
|
1763
|
-
if (skillName && instructions) {
|
|
1764
|
-
this.activeSkill = { name: skillName, instructions };
|
|
1765
|
-
}
|
|
1766
|
-
else {
|
|
1767
|
-
this.activeSkill = null;
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
/**
|
|
1771
|
-
* Get the currently active skill name, if any.
|
|
1772
|
-
*/
|
|
1773
|
-
getActiveSkill() {
|
|
1774
|
-
return this.activeSkill?.name || null;
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
//# sourceMappingURL=agent.js.map
|