xibecode 1.0.3 → 1.0.7

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.
Files changed (268) hide show
  1. package/dist/commands/chat.d.ts.map +1 -1
  2. package/dist/commands/chat.js +10 -7
  3. package/dist/commands/chat.js.map +1 -1
  4. package/dist/commands/diagnostics.js +1 -1
  5. package/dist/commands/diagnostics.js.map +1 -1
  6. package/dist/commands/mcp.js +1 -1
  7. package/dist/commands/mcp.js.map +1 -1
  8. package/dist/commands/resume.js +1 -1
  9. package/dist/commands/resume.js.map +1 -1
  10. package/dist/commands/run-pr.d.ts.map +1 -1
  11. package/dist/commands/run-pr.js +10 -9
  12. package/dist/commands/run-pr.js.map +1 -1
  13. package/dist/commands/run.d.ts.map +1 -1
  14. package/dist/commands/run.js +14 -13
  15. package/dist/commands/run.js.map +1 -1
  16. package/dist/commands/skills.d.ts.map +1 -1
  17. package/dist/commands/skills.js +3 -2
  18. package/dist/commands/skills.js.map +1 -1
  19. package/dist/components/AssistantMarkdown.js +1 -1
  20. package/dist/components/AssistantMarkdown.js.map +1 -1
  21. package/dist/ui/claude-style-chat.d.ts.map +1 -1
  22. package/dist/ui/claude-style-chat.js +12 -10
  23. package/dist/ui/claude-style-chat.js.map +1 -1
  24. package/dist/utils/built-in-skills-dir.d.ts +7 -0
  25. package/dist/utils/built-in-skills-dir.d.ts.map +1 -0
  26. package/dist/utils/built-in-skills-dir.js +11 -0
  27. package/dist/utils/built-in-skills-dir.js.map +1 -0
  28. package/dist/utils/config.d.ts +2 -119
  29. package/dist/utils/config.d.ts.map +1 -1
  30. package/dist/utils/config.js +3 -88
  31. package/dist/utils/config.js.map +1 -1
  32. package/package.json +11 -17
  33. package/dist/commands/punycode.d.ts +0 -5
  34. package/dist/commands/punycode.d.ts.map +0 -1
  35. package/dist/commands/punycode.js +0 -48
  36. package/dist/commands/punycode.js.map +0 -1
  37. package/dist/commands/tui.d.ts +0 -9
  38. package/dist/commands/tui.d.ts.map +0 -1
  39. package/dist/commands/tui.js +0 -83
  40. package/dist/commands/tui.js.map +0 -1
  41. package/dist/core/agent-tool-policies.d.ts +0 -5
  42. package/dist/core/agent-tool-policies.d.ts.map +0 -1
  43. package/dist/core/agent-tool-policies.js +0 -18
  44. package/dist/core/agent-tool-policies.js.map +0 -1
  45. package/dist/core/agent.d.ts +0 -181
  46. package/dist/core/agent.d.ts.map +0 -1
  47. package/dist/core/agent.js +0 -1777
  48. package/dist/core/agent.js.map +0 -1
  49. package/dist/core/background-agent.d.ts +0 -23
  50. package/dist/core/background-agent.d.ts.map +0 -1
  51. package/dist/core/background-agent.js +0 -175
  52. package/dist/core/background-agent.js.map +0 -1
  53. package/dist/core/code-graph.d.ts +0 -18
  54. package/dist/core/code-graph.d.ts.map +0 -1
  55. package/dist/core/code-graph.js +0 -105
  56. package/dist/core/code-graph.js.map +0 -1
  57. package/dist/core/conflict-solver.d.ts +0 -26
  58. package/dist/core/conflict-solver.d.ts.map +0 -1
  59. package/dist/core/conflict-solver.js +0 -108
  60. package/dist/core/conflict-solver.js.map +0 -1
  61. package/dist/core/context-compactor.d.ts +0 -10
  62. package/dist/core/context-compactor.d.ts.map +0 -1
  63. package/dist/core/context-compactor.js +0 -158
  64. package/dist/core/context-compactor.js.map +0 -1
  65. package/dist/core/context-pruner.d.ts +0 -19
  66. package/dist/core/context-pruner.d.ts.map +0 -1
  67. package/dist/core/context-pruner.js +0 -103
  68. package/dist/core/context-pruner.js.map +0 -1
  69. package/dist/core/context.d.ts +0 -82
  70. package/dist/core/context.d.ts.map +0 -1
  71. package/dist/core/context.js +0 -273
  72. package/dist/core/context.js.map +0 -1
  73. package/dist/core/conversation-recovery.d.ts +0 -9
  74. package/dist/core/conversation-recovery.d.ts.map +0 -1
  75. package/dist/core/conversation-recovery.js +0 -15
  76. package/dist/core/conversation-recovery.js.map +0 -1
  77. package/dist/core/debug-workflow.d.ts +0 -9
  78. package/dist/core/debug-workflow.d.ts.map +0 -1
  79. package/dist/core/debug-workflow.js +0 -19
  80. package/dist/core/debug-workflow.js.map +0 -1
  81. package/dist/core/docs-scraper.d.ts +0 -40
  82. package/dist/core/docs-scraper.d.ts.map +0 -1
  83. package/dist/core/docs-scraper.js +0 -386
  84. package/dist/core/docs-scraper.js.map +0 -1
  85. package/dist/core/editor.d.ts +0 -87
  86. package/dist/core/editor.d.ts.map +0 -1
  87. package/dist/core/editor.js +0 -377
  88. package/dist/core/editor.js.map +0 -1
  89. package/dist/core/export.d.ts +0 -11
  90. package/dist/core/export.d.ts.map +0 -1
  91. package/dist/core/export.js +0 -54
  92. package/dist/core/export.js.map +0 -1
  93. package/dist/core/history-manager.d.ts +0 -75
  94. package/dist/core/history-manager.d.ts.map +0 -1
  95. package/dist/core/history-manager.js +0 -146
  96. package/dist/core/history-manager.js.map +0 -1
  97. package/dist/core/marketplace-client.d.ts +0 -52
  98. package/dist/core/marketplace-client.d.ts.map +0 -1
  99. package/dist/core/marketplace-client.js +0 -71
  100. package/dist/core/marketplace-client.js.map +0 -1
  101. package/dist/core/mcp/mcp-config.d.ts +0 -10
  102. package/dist/core/mcp/mcp-config.d.ts.map +0 -1
  103. package/dist/core/mcp/mcp-config.js +0 -70
  104. package/dist/core/mcp/mcp-config.js.map +0 -1
  105. package/dist/core/mcp/mcp-policy.d.ts +0 -17
  106. package/dist/core/mcp/mcp-policy.d.ts.map +0 -1
  107. package/dist/core/mcp/mcp-policy.js +0 -56
  108. package/dist/core/mcp/mcp-policy.js.map +0 -1
  109. package/dist/core/mcp/oauth-flow.d.ts +0 -30
  110. package/dist/core/mcp/oauth-flow.d.ts.map +0 -1
  111. package/dist/core/mcp/oauth-flow.js +0 -230
  112. package/dist/core/mcp/oauth-flow.js.map +0 -1
  113. package/dist/core/mcp/oauth-store.d.ts +0 -13
  114. package/dist/core/mcp/oauth-store.d.ts.map +0 -1
  115. package/dist/core/mcp/oauth-store.js +0 -68
  116. package/dist/core/mcp/oauth-store.js.map +0 -1
  117. package/dist/core/mcp/resolve-mcp-servers.d.ts +0 -16
  118. package/dist/core/mcp/resolve-mcp-servers.d.ts.map +0 -1
  119. package/dist/core/mcp/resolve-mcp-servers.js +0 -83
  120. package/dist/core/mcp/resolve-mcp-servers.js.map +0 -1
  121. package/dist/core/mcp-client.d.ts +0 -99
  122. package/dist/core/mcp-client.d.ts.map +0 -1
  123. package/dist/core/mcp-client.js +0 -317
  124. package/dist/core/mcp-client.js.map +0 -1
  125. package/dist/core/memory-promotions.d.ts +0 -15
  126. package/dist/core/memory-promotions.d.ts.map +0 -1
  127. package/dist/core/memory-promotions.js +0 -38
  128. package/dist/core/memory-promotions.js.map +0 -1
  129. package/dist/core/memory.d.ts +0 -32
  130. package/dist/core/memory.d.ts.map +0 -1
  131. package/dist/core/memory.js +0 -121
  132. package/dist/core/memory.js.map +0 -1
  133. package/dist/core/modes.d.ts +0 -432
  134. package/dist/core/modes.d.ts.map +0 -1
  135. package/dist/core/modes.js +0 -1094
  136. package/dist/core/modes.js.map +0 -1
  137. package/dist/core/pattern-miner.d.ts +0 -43
  138. package/dist/core/pattern-miner.d.ts.map +0 -1
  139. package/dist/core/pattern-miner.js +0 -123
  140. package/dist/core/pattern-miner.js.map +0 -1
  141. package/dist/core/permission-store.d.ts +0 -15
  142. package/dist/core/permission-store.d.ts.map +0 -1
  143. package/dist/core/permission-store.js +0 -30
  144. package/dist/core/permission-store.js.map +0 -1
  145. package/dist/core/permissions.d.ts +0 -33
  146. package/dist/core/permissions.d.ts.map +0 -1
  147. package/dist/core/permissions.js +0 -141
  148. package/dist/core/permissions.js.map +0 -1
  149. package/dist/core/plan-artifacts.d.ts +0 -10
  150. package/dist/core/plan-artifacts.d.ts.map +0 -1
  151. package/dist/core/plan-artifacts.js +0 -60
  152. package/dist/core/plan-artifacts.js.map +0 -1
  153. package/dist/core/plan-session.d.ts +0 -25
  154. package/dist/core/plan-session.d.ts.map +0 -1
  155. package/dist/core/plan-session.js +0 -99
  156. package/dist/core/plan-session.js.map +0 -1
  157. package/dist/core/planMode.d.ts +0 -51
  158. package/dist/core/planMode.d.ts.map +0 -1
  159. package/dist/core/planMode.js +0 -245
  160. package/dist/core/planMode.js.map +0 -1
  161. package/dist/core/plugins.d.ts +0 -96
  162. package/dist/core/plugins.d.ts.map +0 -1
  163. package/dist/core/plugins.js +0 -204
  164. package/dist/core/plugins.js.map +0 -1
  165. package/dist/core/session-bridge.d.ts +0 -128
  166. package/dist/core/session-bridge.d.ts.map +0 -1
  167. package/dist/core/session-bridge.js +0 -328
  168. package/dist/core/session-bridge.js.map +0 -1
  169. package/dist/core/session-manager.d.ts +0 -80
  170. package/dist/core/session-manager.d.ts.map +0 -1
  171. package/dist/core/session-manager.js +0 -166
  172. package/dist/core/session-manager.js.map +0 -1
  173. package/dist/core/session-memory.d.ts +0 -45
  174. package/dist/core/session-memory.d.ts.map +0 -1
  175. package/dist/core/session-memory.js +0 -103
  176. package/dist/core/session-memory.js.map +0 -1
  177. package/dist/core/skill-selection.d.ts +0 -36
  178. package/dist/core/skill-selection.d.ts.map +0 -1
  179. package/dist/core/skill-selection.js +0 -172
  180. package/dist/core/skill-selection.js.map +0 -1
  181. package/dist/core/skills-sh-client.d.ts +0 -19
  182. package/dist/core/skills-sh-client.d.ts.map +0 -1
  183. package/dist/core/skills-sh-client.js +0 -75
  184. package/dist/core/skills-sh-client.js.map +0 -1
  185. package/dist/core/skills.d.ts +0 -97
  186. package/dist/core/skills.d.ts.map +0 -1
  187. package/dist/core/skills.js +0 -339
  188. package/dist/core/skills.js.map +0 -1
  189. package/dist/core/swarm.d.ts +0 -34
  190. package/dist/core/swarm.d.ts.map +0 -1
  191. package/dist/core/swarm.js +0 -111
  192. package/dist/core/swarm.js.map +0 -1
  193. package/dist/core/task-status.d.ts +0 -13
  194. package/dist/core/task-status.d.ts.map +0 -1
  195. package/dist/core/task-status.js +0 -17
  196. package/dist/core/task-status.js.map +0 -1
  197. package/dist/core/tool-orchestrator.d.ts +0 -30
  198. package/dist/core/tool-orchestrator.d.ts.map +0 -1
  199. package/dist/core/tool-orchestrator.js +0 -89
  200. package/dist/core/tool-orchestrator.js.map +0 -1
  201. package/dist/core/tools.d.ts +0 -462
  202. package/dist/core/tools.d.ts.map +0 -1
  203. package/dist/core/tools.js +0 -2916
  204. package/dist/core/tools.js.map +0 -1
  205. package/dist/core/transcript-cleanup.d.ts +0 -8
  206. package/dist/core/transcript-cleanup.d.ts.map +0 -1
  207. package/dist/core/transcript-cleanup.js +0 -52
  208. package/dist/core/transcript-cleanup.js.map +0 -1
  209. package/dist/core/visual-feedback.d.ts +0 -20
  210. package/dist/core/visual-feedback.d.ts.map +0 -1
  211. package/dist/core/visual-feedback.js +0 -117
  212. package/dist/core/visual-feedback.js.map +0 -1
  213. package/dist/tools/browser.d.ts +0 -120
  214. package/dist/tools/browser.d.ts.map +0 -1
  215. package/dist/tools/browser.js +0 -439
  216. package/dist/tools/browser.js.map +0 -1
  217. package/dist/tools/test-generator.d.ts +0 -157
  218. package/dist/tools/test-generator.d.ts.map +0 -1
  219. package/dist/tools/test-generator.js +0 -893
  220. package/dist/tools/test-generator.js.map +0 -1
  221. package/dist/tui/InkApp.d.ts +0 -21
  222. package/dist/tui/InkApp.d.ts.map +0 -1
  223. package/dist/tui/InkApp.js +0 -146
  224. package/dist/tui/InkApp.js.map +0 -1
  225. package/dist/tui/MarkdownMessage.d.ts +0 -16
  226. package/dist/tui/MarkdownMessage.d.ts.map +0 -1
  227. package/dist/tui/MarkdownMessage.js +0 -63
  228. package/dist/tui/MarkdownMessage.js.map +0 -1
  229. package/dist/tui/blessed-chat.d.ts +0 -9
  230. package/dist/tui/blessed-chat.d.ts.map +0 -1
  231. package/dist/tui/blessed-chat.js +0 -887
  232. package/dist/tui/blessed-chat.js.map +0 -1
  233. package/dist/tui/markdown-to-blessed.d.ts +0 -6
  234. package/dist/tui/markdown-to-blessed.d.ts.map +0 -1
  235. package/dist/tui/markdown-to-blessed.js +0 -26
  236. package/dist/tui/markdown-to-blessed.js.map +0 -1
  237. package/dist/ui/ink/App.d.ts +0 -25
  238. package/dist/ui/ink/App.d.ts.map +0 -1
  239. package/dist/ui/ink/App.js +0 -372
  240. package/dist/ui/ink/App.js.map +0 -1
  241. package/dist/utils/at-references.d.ts +0 -14
  242. package/dist/utils/at-references.d.ts.map +0 -1
  243. package/dist/utils/at-references.js +0 -47
  244. package/dist/utils/at-references.js.map +0 -1
  245. package/dist/utils/auto-memory.d.ts +0 -24
  246. package/dist/utils/auto-memory.d.ts.map +0 -1
  247. package/dist/utils/auto-memory.js +0 -153
  248. package/dist/utils/auto-memory.js.map +0 -1
  249. package/dist/utils/git.d.ts +0 -89
  250. package/dist/utils/git.d.ts.map +0 -1
  251. package/dist/utils/git.js +0 -444
  252. package/dist/utils/git.js.map +0 -1
  253. package/dist/utils/mcp-servers-file.d.ts +0 -46
  254. package/dist/utils/mcp-servers-file.d.ts.map +0 -1
  255. package/dist/utils/mcp-servers-file.js +0 -212
  256. package/dist/utils/mcp-servers-file.js.map +0 -1
  257. package/dist/utils/safety.d.ts +0 -60
  258. package/dist/utils/safety.d.ts.map +0 -1
  259. package/dist/utils/safety.js +0 -254
  260. package/dist/utils/safety.js.map +0 -1
  261. package/dist/utils/smithery.d.ts +0 -25
  262. package/dist/utils/smithery.d.ts.map +0 -1
  263. package/dist/utils/smithery.js +0 -50
  264. package/dist/utils/smithery.js.map +0 -1
  265. package/dist/utils/testRunner.d.ts +0 -44
  266. package/dist/utils/testRunner.d.ts.map +0 -1
  267. package/dist/utils/testRunner.js +0 -270
  268. package/dist/utils/testRunner.js.map +0 -1
@@ -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