apex-dev 3.10.4 → 3.10.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/entry.js ADDED
@@ -0,0 +1,3732 @@
1
+ // @bun
2
+ // entry.mjs
3
+ import {
4
+ TextAttributes,
5
+ createCliRenderer
6
+ } from "@opentui/core";
7
+ import { createRoot, useTerminalDimensions, useKeyboard } from "@opentui/react";
8
+ var __create = Object.create;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ function __accessProp(key) {
14
+ return this[key];
15
+ }
16
+ var __toESMCache_node;
17
+ var __toESMCache_esm;
18
+ var __toESM = (mod, isNodeMode, target) => {
19
+ var canCache = mod != null && typeof mod === "object";
20
+ if (canCache) {
21
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
22
+ var cached = cache.get(mod);
23
+ if (cached)
24
+ return cached;
25
+ }
26
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
27
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
28
+ for (let key of __getOwnPropNames(mod))
29
+ if (!__hasOwnProp.call(to, key))
30
+ __defProp(to, key, {
31
+ get: __accessProp.bind(mod, key),
32
+ enumerable: true
33
+ });
34
+ if (canCache)
35
+ cache.set(mod, to);
36
+ return to;
37
+ };
38
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
39
+ var __require = import.meta.require;
40
+ var require_react = __commonJS((exports, module) => {
41
+ module.exports = __require("react");
42
+ });
43
+ var require_jsx_runtime = __commonJS((exports, module) => {
44
+ module.exports = __require("react/jsx-runtime");
45
+ });
46
+ var require_openai = __commonJS((exports, module) => {
47
+ module.exports = __require("openai");
48
+ });
49
+ var require_store = __commonJS((exports, module2) => {
50
+ var state = {
51
+ messages: [],
52
+ streamingContent: "",
53
+ streamingThinking: "",
54
+ isProcessing: false,
55
+ showHelp: false,
56
+ showSummary: false
57
+ };
58
+ var nextId = 1;
59
+ var listeners = new Set;
60
+ var renderer = null;
61
+ function getSnapshot() {
62
+ return state;
63
+ }
64
+ function subscribe(listener) {
65
+ listeners.add(listener);
66
+ return () => listeners.delete(listener);
67
+ }
68
+ let renderRequested = false;
69
+ function notify() {
70
+ for (const fn of listeners)
71
+ fn();
72
+ if (renderer && !renderRequested) {
73
+ renderRequested = true;
74
+ setImmediate(() => {
75
+ renderRequested = false;
76
+ renderer.requestRender();
77
+ });
78
+ }
79
+ }
80
+ function setState(partial) {
81
+ state = { ...state, ...partial };
82
+ notify();
83
+ }
84
+ function addMessage(msg) {
85
+ const id = nextId++;
86
+ state = { ...state, messages: [...state.messages, { id, ...msg }] };
87
+ notify();
88
+ return id;
89
+ }
90
+ function updateMessage(id, updates) {
91
+ state = {
92
+ ...state,
93
+ messages: state.messages.map((m2) => m2.id === id ? { ...m2, ...updates } : m2)
94
+ };
95
+ notify();
96
+ }
97
+ function toggleMessageExpanded(id) {
98
+ state = {
99
+ ...state,
100
+ messages: state.messages.map((m2) => m2.id === id ? { ...m2, expanded: !m2.expanded } : m2)
101
+ };
102
+ notify();
103
+ }
104
+ function updateStreaming(content, thinking) {
105
+ state = { ...state, streamingContent: content || "", streamingThinking: thinking || "" };
106
+ notify();
107
+ }
108
+ function clearStreaming() {
109
+ state = { ...state, streamingContent: "", streamingThinking: "" };
110
+ notify();
111
+ }
112
+ function finishStreaming(msg) {
113
+ const id = nextId++;
114
+ state = {
115
+ ...state,
116
+ streamingContent: "",
117
+ streamingThinking: "",
118
+ messages: [...state.messages, { id, ...msg }]
119
+ };
120
+ notify();
121
+ return id;
122
+ }
123
+ function clearMessages() {
124
+ state = { ...state, messages: [] };
125
+ notify();
126
+ }
127
+ function setRenderer(r) {
128
+ renderer = r;
129
+ }
130
+ function getRenderer() {
131
+ return renderer;
132
+ }
133
+ module2.exports = {
134
+ getSnapshot,
135
+ subscribe,
136
+ setState,
137
+ addMessage,
138
+ updateMessage,
139
+ toggleMessageExpanded,
140
+ updateStreaming,
141
+ clearStreaming,
142
+ finishStreaming,
143
+ clearMessages,
144
+ setRenderer,
145
+ getRenderer
146
+ };
147
+ });
148
+ var require_theme = __commonJS((exports, module2) => {
149
+ var colors = {
150
+ primary: "#6366f1",
151
+ accent: "#818cf8",
152
+ dim: "#666666",
153
+ muted: "#888888",
154
+ text: "#e0e0e0",
155
+ white: "#ffffff",
156
+ green: "#22c55e",
157
+ yellow: "#eab308",
158
+ red: "#ef4444",
159
+ blue: "#3b82f6",
160
+ cyan: "#06b6d4",
161
+ surface: "#1e1e2e",
162
+ border: "#333355"
163
+ };
164
+ module2.exports = { colors };
165
+ });
166
+ var require_thinking = __commonJS((exports, module2) => {
167
+ function parseThinkBlocks(text) {
168
+ const thinkRegex = /<think>([\s\S]*?)(?:<\/think>|think>)/g;
169
+ const thoughts = [];
170
+ let match;
171
+ while ((match = thinkRegex.exec(text)) !== null) {
172
+ const content = match[1].trim();
173
+ if (content)
174
+ thoughts.push(content);
175
+ }
176
+ const cleaned = text.replace(/<think>[\s\S]*?(?:<\/think>|think>)/g, "").trim();
177
+ return { thoughts, content: cleaned };
178
+ }
179
+ function findThinkClose(text) {
180
+ const fullClose = text.indexOf("</think>");
181
+ if (fullClose !== -1)
182
+ return { pos: fullClose, len: 8 };
183
+ let searchFrom = 0;
184
+ while (searchFrom < text.length) {
185
+ const idx = text.indexOf("think>", searchFrom);
186
+ if (idx === -1)
187
+ break;
188
+ if (idx === 0 || text[idx - 1] !== "<")
189
+ return { pos: idx, len: 6 };
190
+ searchFrom = idx + 6;
191
+ }
192
+ return null;
193
+ }
194
+ function stripStrayCloseTag(text) {
195
+ return text.replace(/<\/think>/g, "").replace(/(?<!<)think>/g, "");
196
+ }
197
+ function splitAtPartialTag(text) {
198
+ const prefixes = [
199
+ "</think>",
200
+ "</think",
201
+ "</thin",
202
+ "</thi",
203
+ "</th",
204
+ "</t",
205
+ "</",
206
+ "<think>",
207
+ "<think",
208
+ "<thin",
209
+ "<thi",
210
+ "<th",
211
+ "<t",
212
+ "<"
213
+ ];
214
+ for (const prefix of prefixes) {
215
+ if (text.endsWith(prefix)) {
216
+ if (prefix === "</think>" || prefix === "think>") {
217
+ return { safe: text.slice(0, -prefix.length), pending: "" };
218
+ }
219
+ return { safe: text.slice(0, -prefix.length), pending: prefix };
220
+ }
221
+ }
222
+ return { safe: text, pending: "" };
223
+ }
224
+ module2.exports = {
225
+ parseThinkBlocks,
226
+ findThinkClose,
227
+ stripStrayCloseTag,
228
+ splitAtPartialTag
229
+ };
230
+ });
231
+ var require_utils3 = __commonJS((exports, module2) => {
232
+ function toolDetailStr(name, args) {
233
+ if (!args)
234
+ return "";
235
+ switch (name) {
236
+ case "Bash":
237
+ return args.command || "";
238
+ case "Grep":
239
+ return `"${args.pattern}"${args.path ? ` in ${args.path}` : ""}`;
240
+ case "Glob":
241
+ return args.pattern || "";
242
+ case "ListDir":
243
+ return args.path || ".";
244
+ case "Read": {
245
+ let d2 = args.path || "";
246
+ if (args.start_line)
247
+ d2 += `:${args.start_line}-${args.end_line || ""}`;
248
+ return d2;
249
+ }
250
+ case "Write":
251
+ return args.path || "";
252
+ case "Edit":
253
+ return args.path || "";
254
+ case "Patch":
255
+ return `${args.path} (${(args.edits || []).length} edits)`;
256
+ case "UndoEdit":
257
+ return args.path || "";
258
+ case "Task":
259
+ return args.description || "";
260
+ case "CodeReview":
261
+ return "reviewing changes";
262
+ case "CodeReviewMulti":
263
+ return `multi-review (${(args.perspectives || []).length} perspectives)`;
264
+ case "FilePickerMax":
265
+ return args.prompt ? args.prompt.slice(0, 40) : "";
266
+ case "Thinker":
267
+ return args.prompt ? args.prompt.slice(0, 40) : "reasoning";
268
+ case "ThinkerBestOfN":
269
+ return `best-of-${args.n || 3}: ${(args.prompt || "").slice(0, 30)}`;
270
+ case "EditorMultiPrompt":
271
+ return `${(args.strategies || []).length} strategies`;
272
+ case "Commander":
273
+ return args.prompt ? args.prompt.slice(0, 40) : "running commands";
274
+ case "ContextPruner":
275
+ return "pruning context";
276
+ case "ResearcherWeb":
277
+ return args.prompt ? args.prompt.slice(0, 40) : "web research";
278
+ case "ResearcherDocs":
279
+ return args.prompt ? `${args.library ? args.library + ": " : ""}${args.prompt.slice(0, 30)}` : "docs research";
280
+ case "GeneralAgent":
281
+ return args.prompt ? args.prompt.slice(0, 40) : "analyzing";
282
+ case "WebSearch":
283
+ return args.query ? args.query.slice(0, 40) : "searching";
284
+ case "TodoList":
285
+ return args.action || "";
286
+ default:
287
+ return JSON.stringify(args).slice(0, 60);
288
+ }
289
+ }
290
+ module2.exports = { toolDetailStr };
291
+ });
292
+ var require_config = __commonJS((exports, module2) => {
293
+ var OpenAI = require_openai();
294
+ var NVIDIA_MODEL = "z-ai/glm4.7";
295
+ var REVIEWER_MODEL = "nvidia/llama-3.3-nemotron-super-49b-v1.5";
296
+ var FILE_PICKER_MODEL = "qwen/qwen3-coder-480b-a35b-instruct";
297
+ var THINKER_MODEL = "z-ai/glm4.7";
298
+ var COMMANDER_MODEL = "nvidia/llama-3.3-nemotron-super-49b-v1.5";
299
+ var CONTEXT_PRUNER_MODEL = "nvidia/llama-3.3-nemotron-super-49b-v1.5";
300
+ var RESEARCHER_MODEL = "nvidia/llama-3.3-nemotron-super-49b-v1.5";
301
+ var GENERAL_AGENT_MODEL = "z-ai/glm4.7";
302
+ var MAX_TOOL_ITERATIONS = 50;
303
+ var MAX_OUTPUT_LEN = 12000;
304
+ var TOOL_TIMEOUT = 60000;
305
+ var PROJECT_ROOT = process.cwd();
306
+ var currentMode = "max";
307
+ var REVIEWER_SYSTEM_PROMPT = `You are a senior code reviewer. An AI coding assistant just made changes to a codebase. Your job is to review those changes thoroughly and report issues. Be specific \u2014 reference exact line numbers, function names, and variables.
308
+
309
+ The caller must always specify the exact files and changes to review. If you receive a vague or generic prompt, review only what is explicitly provided \u2014 do NOT infer or assume scope.
310
+
311
+ Focus on:
312
+ 1. **Bugs & logic errors** \u2014 incorrect conditions, off-by-one, null/undefined risks, race conditions
313
+ 2. **Security** \u2014 exposed secrets, injection risks, unsafe operations
314
+ 3. **Edge cases** \u2014 unhandled inputs, missing error handling at boundaries
315
+ 4. **Code quality** \u2014 naming, readability, dead code, unnecessary complexity
316
+ 5. **Correctness** \u2014 does the code actually fulfil the stated intent?
317
+
318
+ If everything looks good, say so briefly. If there are problems, list them clearly with severity (critical / warning / nit). You have no tools; your only output is this review.`;
319
+ var FILE_PICKER_SYSTEM_PROMPT = `You are a precision file-picker agent embedded inside a coding assistant. Your ONLY job is to identify the files in a codebase that are relevant to a given prompt.
320
+
321
+ You will receive:
322
+ 1. A full recursive directory tree of the project.
323
+ 2. A preview (first 8 lines) of every source file.
324
+ 3. A prompt specifying the exact type of files to find.
325
+
326
+ Your task:
327
+ - Analyze the directory tree and file previews carefully.
328
+ - Select ONLY the files that are directly relevant to the prompt.
329
+ - Rank them by relevance (most relevant first).
330
+ - Be precise \u2014 do NOT include files that are only tangentially related.
331
+ - If no files match, say so.
332
+ - The caller must always specify the exact type of files they need. If you receive a vague or generic prompt like "give me an overview of the codebase", respond with an empty array \u2014 do NOT guess.
333
+
334
+ Output format \u2014 return ONLY a JSON array of objects, nothing else:
335
+ [
336
+ { "path": "relative/path/to/file.js", "reason": "Brief explanation of why this file is relevant" }
337
+ ]
338
+
339
+ Do NOT wrap in markdown code fences. Output raw JSON only.`;
340
+ var THINKER_SYSTEM_PROMPT = `You are Theo the Theorizer, a deep reasoning and planning agent inside a coding assistant. Your job is to think carefully about coding tasks and produce clear, actionable plans.
341
+
342
+ You will receive the conversation history and a specific question or task to reason about.
343
+
344
+ Your process:
345
+ 1. Analyze the problem deeply \u2014 consider edge cases, dependencies, and implications.
346
+ 2. If it's a coding task, plan which files need changes and in what order.
347
+ 3. Consider multiple approaches and trade-offs.
348
+ 4. Output a clear, structured response with your reasoning and recommendations.
349
+
350
+ Be concise but thorough. Focus on actionable insights, not obvious observations. If you identify risks or potential issues, flag them clearly.`;
351
+ var COMMANDER_SYSTEM_PROMPT = `You are a terminal command specialist agent. Your job is to determine the right shell commands to accomplish a goal and explain what they do.
352
+
353
+ You will receive a task description. Output a JSON array of commands to execute:
354
+ [
355
+ { "command": "the shell command", "description": "what this does and why" }
356
+ ]
357
+
358
+ Rules:
359
+ - Only suggest safe, non-destructive commands unless explicitly asked for destructive operations.
360
+ - Never suggest commands that expose secrets or credentials.
361
+ - Prefer specific, targeted commands over broad ones.
362
+ - Include error handling where appropriate (e.g., using || or checking exit codes).
363
+ - Output raw JSON only, no markdown fences.`;
364
+ var CONTEXT_PRUNER_SYSTEM_PROMPT = `You are a context management agent. Your job is to summarize a long conversation history into a concise but complete summary that preserves all important information.
365
+
366
+ Preserve:
367
+ 1. All file paths that were read, modified, or created.
368
+ 2. Key decisions and their rationale.
369
+ 3. Errors encountered and how they were resolved.
370
+ 4. The current state of the task (what's done, what's remaining).
371
+ 5. Any important code snippets or patterns discussed.
372
+
373
+ Output a structured summary with sections:
374
+ - **Task**: What the user asked for
375
+ - **Progress**: What has been done so far
376
+ - **Files Modified**: List of files changed
377
+ - **Key Decisions**: Important choices made
378
+ - **Current State**: Where things stand now
379
+ - **Remaining**: What still needs to be done (if anything)
380
+
381
+ Be concise but lose no critical details. This summary replaces the full conversation.`;
382
+ var SELECTOR_SYSTEM_PROMPT = `You are a code implementation selector. You will receive multiple implementation proposals (labeled A, B, C, etc.) for the same coding task. Each proposal includes the strategy used and the resulting changes.
383
+
384
+ Your job:
385
+ 1. Analyze each implementation carefully for:
386
+ - **Correctness**: Does it actually solve the stated problem?
387
+ - **Code quality**: Is it clean, readable, and maintainable?
388
+ - **Simplicity**: Is it the simplest correct solution?
389
+ - **Edge cases**: Does it handle edge cases?
390
+ - **Consistency**: Does it match existing code patterns?
391
+ 2. Pick the best implementation.
392
+ 3. Note any good ideas from non-chosen implementations that could improve the winner.
393
+
394
+ Output JSON only, no markdown fences:
395
+ {
396
+ "chosen": "A",
397
+ "reason": "Brief explanation of why this is the best",
398
+ "improvements": "Any good ideas from other implementations to incorporate"
399
+ }`;
400
+ var RESEARCHER_WEB_SYSTEM_PROMPT = `You are a web research specialist embedded in a coding assistant. You receive web search results and synthesize them into a clear, accurate answer.
401
+
402
+ Rules:
403
+ 1. Extract the most relevant information from results. Cite sources with URLs.
404
+ 2. Be specific and actionable \u2014 code examples and exact details over generic advice.
405
+ 3. If results don't contain the answer, say so clearly and share what you know from training data.
406
+ 4. Prefer recent/authoritative sources. Note when information may be outdated.
407
+ 5. Keep answers concise but thorough \u2014 developers are your audience.
408
+ 6. Do NOT use <think> tags or internal reasoning blocks in your response. Output your answer directly.`;
409
+ var RESEARCHER_DOCS_SYSTEM_PROMPT = `You are a documentation research specialist embedded in a coding assistant. You receive documentation search results and synthesize them into a precise, practical answer.
410
+
411
+ Rules:
412
+ 1. Extract exact API signatures, parameter types, return values, and defaults.
413
+ 2. Include code examples that can be used directly \u2014 prefer showing code over describing it.
414
+ 3. Note version-specific behavior when relevant.
415
+ 4. Highlight common pitfalls, gotchas, and deprecation warnings.
416
+ 5. If the docs don't cover the question, say so and provide your best guidance from training data.
417
+ 6. Do NOT use <think> tags or internal reasoning blocks in your response. Output your answer directly.`;
418
+ var GENERAL_AGENT_SYSTEM_PROMPT = `You are a general-purpose coding agent. You receive file contents and conversation context, then produce a thorough, actionable response.
419
+
420
+ Your strengths:
421
+ 1. Deep analysis \u2014 read and reason about complex codebases, trace call chains, identify patterns.
422
+ 2. Problem solving \u2014 identify root causes, suggest fixes, plan multi-step implementations.
423
+ 3. Code generation \u2014 write complete, working code that matches existing project conventions.
424
+
425
+ Be direct and comprehensive. Provide actual solutions, not descriptions of what to do. If you identify issues or risks, flag them clearly with severity.`;
426
+ var nvidiaClient = new OpenAI({
427
+ apiKey: process.env.APEX_API_KEY || "local-proxy",
428
+ baseURL: process.env.APEX_API_URL || "https://apex-api-ten.vercel.app/v1"
429
+ });
430
+ var session = {
431
+ conversationHistory: [],
432
+ totalTokens: 0,
433
+ totalCost: 0,
434
+ toolCallCount: 0,
435
+ filesModified: new Set,
436
+ filesRead: new Set,
437
+ commandsRun: [],
438
+ editHistory: [],
439
+ startTime: Date.now(),
440
+ turnCount: 0
441
+ };
442
+ function truncateOutput(str) {
443
+ if (str.length > MAX_OUTPUT_LEN) {
444
+ return str.slice(0, MAX_OUTPUT_LEN) + `
445
+ ... (truncated, ${str.length} chars total)`;
446
+ }
447
+ return str;
448
+ }
449
+ var path2 = __require("path");
450
+ function resolvePath(p) {
451
+ if (!p)
452
+ return PROJECT_ROOT;
453
+ return path2.isAbsolute(p) ? p : path2.resolve(PROJECT_ROOT, p);
454
+ }
455
+ function timestamp() {
456
+ return new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
457
+ }
458
+ function sleep(ms) {
459
+ return new Promise((r) => setTimeout(r, ms));
460
+ }
461
+ function getMode() {
462
+ return currentMode;
463
+ }
464
+ module2.exports = {
465
+ NVIDIA_MODEL,
466
+ REVIEWER_MODEL,
467
+ THINKER_MODEL,
468
+ COMMANDER_MODEL,
469
+ CONTEXT_PRUNER_MODEL,
470
+ RESEARCHER_MODEL,
471
+ GENERAL_AGENT_MODEL,
472
+ MAX_TOOL_ITERATIONS,
473
+ MAX_OUTPUT_LEN,
474
+ TOOL_TIMEOUT,
475
+ PROJECT_ROOT,
476
+ FILE_PICKER_MODEL,
477
+ FILE_PICKER_SYSTEM_PROMPT,
478
+ REVIEWER_SYSTEM_PROMPT,
479
+ THINKER_SYSTEM_PROMPT,
480
+ COMMANDER_SYSTEM_PROMPT,
481
+ CONTEXT_PRUNER_SYSTEM_PROMPT,
482
+ SELECTOR_SYSTEM_PROMPT,
483
+ RESEARCHER_WEB_SYSTEM_PROMPT,
484
+ RESEARCHER_DOCS_SYSTEM_PROMPT,
485
+ GENERAL_AGENT_SYSTEM_PROMPT,
486
+ nvidiaClient,
487
+ session,
488
+ truncateOutput,
489
+ resolvePath,
490
+ timestamp,
491
+ sleep,
492
+ getMode
493
+ };
494
+ });
495
+ var require_tools = __commonJS((exports, module2) => {
496
+ var toolDefs = [
497
+ {
498
+ type: "function",
499
+ function: {
500
+ name: "Read",
501
+ description: "Read the contents of a file. Returns line-numbered content. Always read a file before editing it.",
502
+ parameters: {
503
+ type: "object",
504
+ properties: {
505
+ path: { type: "string", description: "File path to read (absolute or relative to project root)." },
506
+ start_line: { type: "number", description: "Start line (1-indexed). Omit to read from beginning." },
507
+ end_line: { type: "number", description: "End line (1-indexed). Omit to read to end (max 500 lines)." }
508
+ },
509
+ required: ["path"]
510
+ }
511
+ }
512
+ },
513
+ {
514
+ type: "function",
515
+ function: {
516
+ name: "Write",
517
+ description: "Create a new file or completely overwrite an existing file. For modifying existing files, prefer Edit instead.",
518
+ parameters: {
519
+ type: "object",
520
+ properties: {
521
+ path: { type: "string", description: "File path to write." },
522
+ content: { type: "string", description: "Full content to write." }
523
+ },
524
+ required: ["path", "content"]
525
+ }
526
+ }
527
+ },
528
+ {
529
+ type: "function",
530
+ function: {
531
+ name: "Edit",
532
+ description: "Replace an exact string in a file with new content. The old_str must match exactly (including whitespace). For existing files, this is preferred over Write.",
533
+ parameters: {
534
+ type: "object",
535
+ properties: {
536
+ path: { type: "string", description: "File path to edit." },
537
+ old_str: { type: "string", description: "Exact string to find (must be unique in the file)." },
538
+ new_str: { type: "string", description: "Replacement string." }
539
+ },
540
+ required: ["path", "old_str", "new_str"]
541
+ }
542
+ }
543
+ },
544
+ {
545
+ type: "function",
546
+ function: {
547
+ name: "Patch",
548
+ description: "Apply multiple find-and-replace edits to a single file atomically. Use when you need to make several changes to the same file.",
549
+ parameters: {
550
+ type: "object",
551
+ properties: {
552
+ path: { type: "string", description: "File path to patch." },
553
+ edits: {
554
+ type: "array",
555
+ description: "Array of edits to apply in order.",
556
+ items: {
557
+ type: "object",
558
+ properties: {
559
+ old_str: { type: "string", description: "Exact string to find." },
560
+ new_str: { type: "string", description: "Replacement string." }
561
+ },
562
+ required: ["old_str", "new_str"]
563
+ }
564
+ }
565
+ },
566
+ required: ["path", "edits"]
567
+ }
568
+ }
569
+ },
570
+ {
571
+ type: "function",
572
+ function: {
573
+ name: "Bash",
574
+ description: "Execute a shell command. Use for running tests, builds, git commands, installing packages, checking syntax, etc. Commands have a 60-second timeout.",
575
+ parameters: {
576
+ type: "object",
577
+ properties: {
578
+ command: { type: "string", description: "Shell command to execute." },
579
+ cwd: { type: "string", description: "Working directory (defaults to project root)." }
580
+ },
581
+ required: ["command"]
582
+ }
583
+ }
584
+ },
585
+ {
586
+ type: "function",
587
+ function: {
588
+ name: "Grep",
589
+ description: "Search for a pattern across files using regex. Returns matching lines with file paths and line numbers.",
590
+ parameters: {
591
+ type: "object",
592
+ properties: {
593
+ pattern: { type: "string", description: "Regex pattern to search for." },
594
+ path: { type: "string", description: "Directory or file to search in (defaults to project root)." },
595
+ include: { type: "string", description: 'File glob pattern to include, e.g. "*.js" or "*.ts"' },
596
+ case_sensitive: { type: "boolean", description: "Case-sensitive search (default: false)." }
597
+ },
598
+ required: ["pattern"]
599
+ }
600
+ }
601
+ },
602
+ {
603
+ type: "function",
604
+ function: {
605
+ name: "Glob",
606
+ description: "Find files matching a glob pattern. Returns file paths sorted by modification time.",
607
+ parameters: {
608
+ type: "object",
609
+ properties: {
610
+ pattern: { type: "string", description: 'Glob pattern like "**/*.js", "src/**/*.ts", "*.json"' },
611
+ cwd: { type: "string", description: "Base directory for the search (defaults to project root)." }
612
+ },
613
+ required: ["pattern"]
614
+ }
615
+ }
616
+ },
617
+ {
618
+ type: "function",
619
+ function: {
620
+ name: "ListDir",
621
+ description: "List the contents of a directory. Shows files and subdirectories with type indicators.",
622
+ parameters: {
623
+ type: "object",
624
+ properties: {
625
+ path: { type: "string", description: "Directory path to list (defaults to project root)." },
626
+ recursive: { type: "boolean", description: "If true, list recursively (max depth 3)." }
627
+ },
628
+ required: []
629
+ }
630
+ }
631
+ },
632
+ {
633
+ type: "function",
634
+ function: {
635
+ name: "UndoEdit",
636
+ description: "Undo the last edit made to a specific file, restoring its previous content.",
637
+ parameters: {
638
+ type: "object",
639
+ properties: {
640
+ path: { type: "string", description: "File path to undo the last edit for." }
641
+ },
642
+ required: ["path"]
643
+ }
644
+ }
645
+ },
646
+ {
647
+ type: "function",
648
+ function: {
649
+ name: "Task",
650
+ description: "Spawn a sub-task by executing a sequence of shell commands for a complex multi-step operation. Useful for build-test-fix cycles.",
651
+ parameters: {
652
+ type: "object",
653
+ properties: {
654
+ description: { type: "string", description: "Brief description of the task." },
655
+ commands: {
656
+ type: "array",
657
+ description: "Shell commands to execute in sequence. Stops on first failure.",
658
+ items: { type: "string" }
659
+ }
660
+ },
661
+ required: ["description", "commands"]
662
+ }
663
+ }
664
+ },
665
+ {
666
+ type: "function",
667
+ function: {
668
+ name: "WebSearch",
669
+ description: "Search the web using Exa AI. Returns relevant results with titles, URLs, and text snippets. Use this to find up-to-date information, documentation, or answers from the internet.",
670
+ parameters: {
671
+ type: "object",
672
+ properties: {
673
+ query: { type: "string", description: "The search query to execute." },
674
+ num_results: { type: "number", description: "Number of results to return (default: 5, max: 10)." },
675
+ type: { type: "string", description: 'Search type: "auto" (default), "neural", or "keyword".' },
676
+ include_domains: {
677
+ type: "array",
678
+ description: 'Only return results from these domains, e.g. ["github.com", "stackoverflow.com"].',
679
+ items: { type: "string" }
680
+ },
681
+ category: { type: "string", description: 'Filter by category: "news", "research paper", "tweet", "company", "personal site", etc.' }
682
+ },
683
+ required: ["query"]
684
+ }
685
+ }
686
+ },
687
+ {
688
+ type: "function",
689
+ function: {
690
+ name: "FilePickerMax",
691
+ description: 'Spawn a file-picker sub-agent that deeply explores the codebase to find files relevant to a prompt. It scans the full directory tree and previews every source file, then uses the most capable model to identify and rank the relevant files. Use this when you need to locate files related to a concept, feature, bug, or pattern. NEVER send generic prompts like "give me an overview of the codebase" \u2014 always specify the exact type of files you want.',
692
+ parameters: {
693
+ type: "object",
694
+ properties: {
695
+ prompt: { type: "string", description: 'Specify the exact type of files you need. NEVER ask for a generic overview. Be specific \u2014 e.g. "show me the main entry point and routing files", "files that handle user authentication", "all React components related to the dashboard", "where database migrations are defined".' }
696
+ },
697
+ required: ["prompt"]
698
+ }
699
+ }
700
+ },
701
+ {
702
+ type: "function",
703
+ function: {
704
+ name: "TodoList",
705
+ description: "Manage a persistent todo list for tracking tasks. Supports adding, listing, completing, and removing items. The list is saved to .apex-todos.json in the project root.",
706
+ parameters: {
707
+ type: "object",
708
+ properties: {
709
+ action: {
710
+ type: "string",
711
+ enum: ["add", "list", "done", "remove", "clear"],
712
+ description: 'Action to perform: "add" a new item, "list" all items, "done" to mark complete, "remove" to delete, "clear" to remove all completed.'
713
+ },
714
+ text: { type: "string", description: 'Text for the todo item (required for "add").' },
715
+ index: { type: "number", description: 'Item index (1-based, required for "done" and "remove").' }
716
+ },
717
+ required: ["action"]
718
+ }
719
+ }
720
+ },
721
+ {
722
+ type: "function",
723
+ function: {
724
+ name: "Thinker",
725
+ description: "Spawn a deep reasoning/planning sub-agent. It analyzes the problem, considers multiple approaches, and returns a structured plan. Use for complex tasks that benefit from careful planning before implementation.",
726
+ parameters: {
727
+ type: "object",
728
+ properties: {
729
+ prompt: { type: "string", description: "The question or task to reason about deeply." }
730
+ },
731
+ required: ["prompt"]
732
+ }
733
+ }
734
+ },
735
+ {
736
+ type: "function",
737
+ function: {
738
+ name: "ThinkerBestOfN",
739
+ description: "Spawn N parallel thinking agents that each independently reason about the same problem, then a selector picks the best response. Use for critical decisions that benefit from multiple perspectives.",
740
+ parameters: {
741
+ type: "object",
742
+ properties: {
743
+ prompt: { type: "string", description: "The question or task to reason about from multiple angles." },
744
+ n: { type: "number", description: "Number of parallel thinking passes (default: 3, max: 5)." }
745
+ },
746
+ required: ["prompt"]
747
+ }
748
+ }
749
+ },
750
+ {
751
+ type: "function",
752
+ function: {
753
+ name: "EditorMultiPrompt",
754
+ description: "Spawn multiple editor agents in parallel, each with a different implementation strategy, then a selector picks the best result and applies it. Use for important code changes where you want to explore multiple approaches.",
755
+ parameters: {
756
+ type: "object",
757
+ properties: {
758
+ prompt: { type: "string", description: "The coding task to implement." },
759
+ strategies: {
760
+ type: "array",
761
+ description: "Array of 2-3 different implementation strategies to try in parallel.",
762
+ items: { type: "string" }
763
+ },
764
+ files: {
765
+ type: "array",
766
+ description: "File paths and their contents that each editor will work with.",
767
+ items: {
768
+ type: "object",
769
+ properties: {
770
+ path: { type: "string", description: "File path." },
771
+ content: { type: "string", description: "Current file content." }
772
+ },
773
+ required: ["path", "content"]
774
+ }
775
+ }
776
+ },
777
+ required: ["prompt", "strategies", "files"]
778
+ }
779
+ }
780
+ },
781
+ {
782
+ type: "function",
783
+ function: {
784
+ name: "CodeReviewMulti",
785
+ description: "Spawn multiple code reviewers in parallel, each analyzing from a different perspective (correctness, security, performance, etc.).",
786
+ parameters: {
787
+ type: "object",
788
+ properties: {
789
+ prompt: { type: "string", description: "Description of the changes to review." },
790
+ perspectives: {
791
+ type: "array",
792
+ description: 'Review perspectives, e.g. ["correctness and logic", "security vulnerabilities", "performance and efficiency"].',
793
+ items: { type: "string" }
794
+ }
795
+ },
796
+ required: ["prompt"]
797
+ }
798
+ }
799
+ },
800
+ {
801
+ type: "function",
802
+ function: {
803
+ name: "Commander",
804
+ description: "Spawn a terminal command specialist agent that determines and executes the right shell commands for a task. It plans the commands, explains them, then executes them in sequence.",
805
+ parameters: {
806
+ type: "object",
807
+ properties: {
808
+ prompt: { type: "string", description: "Description of what needs to be accomplished via terminal commands." }
809
+ },
810
+ required: ["prompt"]
811
+ }
812
+ }
813
+ },
814
+ {
815
+ type: "function",
816
+ function: {
817
+ name: "ContextPruner",
818
+ description: "Summarize the current conversation history to free up context space. Automatically invoked in MAX mode but can be called manually. Replaces verbose conversation history with a concise summary preserving all critical information.",
819
+ parameters: {
820
+ type: "object",
821
+ properties: {},
822
+ required: []
823
+ }
824
+ }
825
+ },
826
+ {
827
+ type: "function",
828
+ function: {
829
+ name: "ResearcherWeb",
830
+ description: "Search the web and synthesize results into a clear answer using an LLM. Use when you need up-to-date information, best practices, or answers that may not be in your training data. Falls back to LLM knowledge if web search is unavailable.",
831
+ parameters: {
832
+ type: "object",
833
+ properties: {
834
+ prompt: { type: "string", description: "The question to research. Be specific for better results." },
835
+ domains: {
836
+ type: "array",
837
+ description: 'Optional list of domains to restrict search to (e.g. ["stackoverflow.com", "github.com"]).',
838
+ items: { type: "string" }
839
+ }
840
+ },
841
+ required: ["prompt"]
842
+ }
843
+ }
844
+ },
845
+ {
846
+ type: "function",
847
+ function: {
848
+ name: "ResearcherDocs",
849
+ description: "Search technical documentation for a library or framework and synthesize a precise answer with API details and code examples. Use when you need to verify API signatures, find usage patterns, or check library behavior.",
850
+ parameters: {
851
+ type: "object",
852
+ properties: {
853
+ prompt: { type: "string", description: "The documentation question. Include the library/framework name and version if relevant." },
854
+ library: { type: "string", description: 'The library or framework name (e.g. "React", "Express", "Prisma").' }
855
+ },
856
+ required: ["prompt"]
857
+ }
858
+ }
859
+ },
860
+ {
861
+ type: "function",
862
+ function: {
863
+ name: "GeneralAgent",
864
+ description: "Spawn an independent general-purpose agent that reads specified files and solves a problem. Use when you need deep independent analysis, complex reasoning with full file context, or a second opinion. More powerful than Thinker because it receives actual file contents.",
865
+ parameters: {
866
+ type: "object",
867
+ properties: {
868
+ prompt: { type: "string", description: "The problem to solve. Be specific about what analysis or output you need." },
869
+ filePaths: {
870
+ type: "array",
871
+ description: "File paths to read and provide as context. The agent will analyze these files to solve the problem.",
872
+ items: { type: "string" }
873
+ }
874
+ },
875
+ required: ["prompt"]
876
+ }
877
+ }
878
+ }
879
+ ];
880
+ module2.exports = { toolDefs };
881
+ });
882
+ var require_prompt = __commonJS((exports, module2) => {
883
+ var fs2 = __require("fs");
884
+ var path2 = __require("path");
885
+ var { execSync } = __require("child_process");
886
+ var { PROJECT_ROOT, MAX_TOOL_ITERATIONS } = require_config();
887
+ function buildSystemPrompt() {
888
+ let gitInfo = "";
889
+ try {
890
+ const branch = execSync("git rev-parse --abbrev-ref HEAD 2>/dev/null", { encoding: "utf-8", cwd: PROJECT_ROOT }).trim();
891
+ const status = execSync("git status --short 2>/dev/null", { encoding: "utf-8", cwd: PROJECT_ROOT }).trim();
892
+ const remoteUrl = execSync("git config --get remote.origin.url 2>/dev/null", { encoding: "utf-8", cwd: PROJECT_ROOT }).trim();
893
+ gitInfo = `
894
+ Git branch: ${branch}
895
+ Git remote: ${remoteUrl}
896
+ Git status:
897
+ ${status || "(clean)"}`;
898
+ } catch {}
899
+ let projectInfo = "";
900
+ try {
901
+ const pkg = JSON.parse(fs2.readFileSync(path2.join(PROJECT_ROOT, "package.json"), "utf-8"));
902
+ projectInfo = `
903
+ Project: ${pkg.name || "unknown"} v${pkg.version || "0.0.0"}`;
904
+ if (pkg.dependencies)
905
+ projectInfo += `
906
+ Dependencies: ${Object.keys(pkg.dependencies).join(", ")}`;
907
+ if (pkg.devDependencies)
908
+ projectInfo += `
909
+ Dev dependencies: ${Object.keys(pkg.devDependencies).join(", ")}`;
910
+ if (pkg.scripts)
911
+ projectInfo += `
912
+ Scripts: ${Object.keys(pkg.scripts).join(", ")}`;
913
+ } catch {}
914
+ return `You are Apex AI, a strategic coding assistant that orchestrates complex tasks through specialized sub-agents. You are the AI behind Apex, a CLI tool where users chat with you to code with AI.
915
+
916
+ # Core Mandates
917
+
918
+ - **Understand first, act second:** Always gather context and read relevant files BEFORE editing. Use sub-agents (FilePickerMax, Grep, Read) to verify assumptions before implementing.
919
+ - **Quality over speed:** Prioritize correctness over appearing productive. Fewer, well-informed sub-agent calls are better than many rushed ones.
920
+ - **Tone:** Professional, direct, and concise. Suitable for a CLI environment.
921
+ - **Validate assumptions:** Use FilePickerMax and Read to verify assumptions about libraries, APIs, and project structure before implementing.
922
+ - **Proactiveness:** Fulfill the user's request thoroughly, including reasonable, directly implied follow-up actions.
923
+ - **Confirm ambiguity:** Do not take significant actions beyond the clear scope of the request without confirming. If asked *how* to do something, explain first, don't just do it.
924
+ - **Do what the user asks:** If the user asks you to do something, even running a risky command, do it.
925
+ - **If a tool fails, try again or try a different tool.** Don't give up after one attempt.
926
+ - **Act on errors.** If the user pastes an error or stack trace, locate the source, identify root cause, and fix it. Never punt back with "try checking X."
927
+ - **Nothing is automatic.** The agent loop is a thin shell \u2014 it only executes tool calls you explicitly make. No code review, no context pruning, no validation happens unless YOU call the corresponding tool.
928
+ - **Use <think></think> tags for moderate reasoning.** Call the Thinker sub-agent for anything more complex.
929
+
930
+ # Output Style
931
+ - Default to short answers (\u22644 lines) unless the user asks for detail.
932
+ - No unnecessary preamble or postamble. Don't narrate obvious steps.
933
+ - After working on files, just stop \u2014 don't summarize what you did unless asked.
934
+ - No emojis unless the user uses them first.
935
+ - For casual conversation, greetings, or quick questions, respond naturally without tools.
936
+ - NEVER say "I don't have any tool to call" \u2014 just respond with what you know.
937
+
938
+ # Code Editing Mandates
939
+
940
+ - **Conventions:** Rigorously adhere to existing project conventions when reading or modifying code.
941
+ - **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project first (check package.json, neighboring files).
942
+ - **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code.
943
+ - **Simplicity & Minimalism:** Make as few changes as possible. When modifying existing code, assume every line has a purpose. Do not change behavior except in the most minimal way.
944
+ - **Code Reuse:** Always reuse helper functions, components, classes, etc., whenever possible.
945
+ - **Refactoring Awareness:** Whenever you modify an exported symbol, find and update all references to it.
946
+ - **Testing:** If you create a test, run it to see if it passes, and fix it if it doesn't.
947
+ - **Code Hygiene:** Add needed imports, remove unused variables/functions/files, remove replaced code. Do NOT add comments unless the user asks or correctness requires it.
948
+
949
+ # Safety & Side Effects
950
+ - Never expose secrets, API keys, tokens, or credentials.
951
+ - Be careful about terminal commands that could be destructive or hard to undo (e.g. \`git push\`, \`git commit\`, \`rm -rf\`, \`git reset --hard\`). Don't run these unless the user explicitly asks.
952
+ - Don't add new dependencies without confirming the user wants them.
953
+
954
+ # Sub-Agent Orchestration
955
+
956
+ You have specialized sub-agents available as tools. **Nothing happens automatically \u2014 you are responsible for orchestrating ALL sub-agent work through your own tool calls.** No code review, no context pruning, no validation runs unless YOU explicitly call the appropriate tool. Sub-agents are specialists \u2014 they produce better, more thorough results than you chaining basic tools manually.
957
+
958
+ ## Available Sub-Agents
959
+
960
+ **Context Gathering:**
961
+ - **FilePickerMax** \u2014 Scans the full codebase to find files relevant to a prompt. Use instead of manually chaining Glob/Grep/ListDir. Always specify the exact type of files needed \u2014 NEVER send generic prompts. Spawn 2-5 of these in parallel for different aspects of the codebase.
962
+ - **ResearcherWeb** \u2014 Searches the web and synthesizes results with an LLM. Use when you need up-to-date information, best practices, or answers that may not be in your training data. Falls back to LLM knowledge if web search is unavailable.
963
+ - **ResearcherDocs** \u2014 Searches technical documentation for a library/framework and synthesizes a precise answer. Use when you need to verify API signatures, find usage patterns, or check library behavior.
964
+
965
+ **Reasoning & Planning:**
966
+ - **Thinker** \u2014 Deep reasoning and planning. Call before implementing anything non-trivial to get a structured plan.
967
+ - **ThinkerBestOfN** \u2014 Multiple parallel reasoning passes, selects the best. Use for critical decisions that benefit from diverse perspectives.
968
+ - **GeneralAgent** \u2014 Independent agent that reads specified files and solves problems. More powerful than Thinker because it receives actual file contents. Use when you need deep independent analysis, complex reasoning with full file context, or a second opinion.
969
+
970
+ **Implementation:**
971
+ - **EditorMultiPrompt** \u2014 Tries multiple implementation strategies in parallel, selects the best, and **auto-applies the changes**. Use for important code changes where you want to explore multiple approaches.
972
+ - **Commander** \u2014 Terminal command specialist. Plans and executes shell commands for a goal. Use instead of calling Bash directly for multi-step operations.
973
+
974
+ **Review & Maintenance:**
975
+ - **CodeReview** / **CodeReviewMulti** \u2014 Reviews code changes for bugs, security, edge cases. You MUST call one of these yourself after making code changes.
976
+ - **ContextPruner** \u2014 Summarizes conversation history to free context space. Call when the conversation is getting long.
977
+
978
+ ## How to Orchestrate (use your judgment)
979
+
980
+ **Phase 1 \u2014 Context Gathering:**
981
+ - Spawn multiple FilePickerMax in parallel for different aspects of the codebase (e.g. one for "entry points and routing", one for "authentication files", one for "test files").
982
+ - Use Read to read all relevant files. For complex tasks, read 12-20 files to build a thorough understanding.
983
+ - Use ResearcherWeb/ResearcherDocs when you need external information about libraries or APIs.
984
+ - Bundle independent context-gathering calls in the same turn for parallel execution.
985
+
986
+ **Phase 2 \u2014 Planning:**
987
+ - For tasks requiring 3+ steps, use TodoList to write out a step-by-step plan.
988
+ - Call Thinker (or ThinkerBestOfN for critical decisions) to reason about the approach.
989
+ - Call GeneralAgent when you need independent deep analysis with file context.
990
+
991
+ **Phase 3 \u2014 Implementation:**
992
+ - Use EditorMultiPrompt for non-trivial code changes \u2014 it tries multiple strategies and auto-applies the best result.
993
+ - For trivially simple edits on already-read files, use Edit or Patch directly.
994
+ - Use Write only for creating new files.
995
+
996
+ **Phase 4 \u2014 Validation:**
997
+ - After code changes, run the most relevant checks: tests, lint, typecheck, or build.
998
+ - Use Commander for multi-step validation. Use Bash for single commands.
999
+ - If checks fail, fix and re-run. If blocked, clearly state what's failing.
1000
+
1001
+ **Phase 5 \u2014 Review:**
1002
+ - After making code changes, call CodeReview or CodeReviewMulti yourself to review the changes. Nothing runs automatically.
1003
+ - If the review finds issues, fix them and re-validate.
1004
+
1005
+ ## When to Skip Sub-Agents and Act Directly
1006
+ - Reading a single known file path (just use Read)
1007
+ - A single targeted grep for a known pattern (just use Grep)
1008
+ - A quick one-line bash command (just use Bash)
1009
+ - Answering a question from memory/context (just respond)
1010
+ - Trivially simple edits where the file is already read and understood
1011
+
1012
+ ## Parallel Execution Rules
1013
+ - Bundle independent tool calls in the same turn \u2014 this is critical for speed.
1014
+ - Spawn multiple FilePickerMax simultaneously for different aspects of the codebase.
1015
+ - Run independent Read calls in parallel.
1016
+ - **Don't spawn dependent agents in parallel** \u2014 e.g. don't spawn EditorMultiPrompt at the same time as FilePickerMax, since editing depends on context.
1017
+ - After implementation, run tests AND typechecks in parallel.
1018
+
1019
+ # Tool Usage (basic tools)
1020
+ - Use Read to understand files before modifying them. NEVER modify a file you haven't read.
1021
+ - Use Edit for surgical changes to existing files (preferred over Write).
1022
+ - Use Patch for multiple edits to the same file.
1023
+ - Use Write only for creating new files.
1024
+ - Use Bash for simple, single commands. Use Commander for multi-step operations.
1025
+ - Use Grep/Glob/ListDir for quick, targeted lookups. Use FilePickerMax for broad codebase discovery.
1026
+ - Use TodoList to track multi-step plans.
1027
+ - Don't ask for permission to use tools \u2014 just use them.
1028
+
1029
+ # Environment
1030
+ Working directory: ${PROJECT_ROOT}
1031
+ OS: ${process.platform}
1032
+ Node: ${process.version}${projectInfo}${gitInfo}
1033
+ Maximum tool iterations per turn: ${MAX_TOOL_ITERATIONS}`;
1034
+ }
1035
+ module2.exports = { buildSystemPrompt };
1036
+ });
1037
+ var require_server = __commonJS((exports, module2) => {
1038
+ var OpenAI = require_openai();
1039
+ var NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
1040
+ var PORT = process.env.APEX_SERVER_PORT || 3579;
1041
+ var serverInstance = null;
1042
+ async function startServer() {
1043
+ if (serverInstance)
1044
+ return serverInstance;
1045
+ const apiKey = process.env.NVIDIA_API_KEY || "";
1046
+ const upstream = new OpenAI({ apiKey, baseURL: NVIDIA_BASE_URL });
1047
+ serverInstance = Bun.serve({
1048
+ port: PORT,
1049
+ async fetch(req) {
1050
+ const url = new URL(req.url);
1051
+ if (url.pathname === "/health") {
1052
+ return new Response(JSON.stringify({ status: "ok" }), {
1053
+ headers: { "Content-Type": "application/json" }
1054
+ });
1055
+ }
1056
+ if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
1057
+ try {
1058
+ const body = await req.json();
1059
+ const isStream = body.stream === true;
1060
+ if (isStream) {
1061
+ const stream = await upstream.chat.completions.create(body);
1062
+ const encoder2 = new TextEncoder;
1063
+ const readable = new ReadableStream({
1064
+ async start(controller) {
1065
+ try {
1066
+ for await (const chunk of stream) {
1067
+ controller.enqueue(encoder2.encode(`data: ${JSON.stringify(chunk)}
1068
+
1069
+ `));
1070
+ }
1071
+ controller.enqueue(encoder2.encode(`data: [DONE]
1072
+
1073
+ `));
1074
+ controller.close();
1075
+ } catch (err) {
1076
+ controller.enqueue(encoder2.encode(`data: ${JSON.stringify({ error: err.message })}
1077
+
1078
+ `));
1079
+ controller.close();
1080
+ }
1081
+ }
1082
+ });
1083
+ return new Response(readable, {
1084
+ headers: {
1085
+ "Content-Type": "text/event-stream",
1086
+ "Cache-Control": "no-cache",
1087
+ Connection: "keep-alive"
1088
+ }
1089
+ });
1090
+ }
1091
+ const result = await upstream.chat.completions.create(body);
1092
+ return new Response(JSON.stringify(result), {
1093
+ headers: { "Content-Type": "application/json" }
1094
+ });
1095
+ } catch (err) {
1096
+ const status = err.status || 500;
1097
+ return new Response(JSON.stringify({ error: { message: err.message, status } }), {
1098
+ status,
1099
+ headers: { "Content-Type": "application/json" }
1100
+ });
1101
+ }
1102
+ }
1103
+ if (url.pathname === "/v1/models" && req.method === "GET") {
1104
+ try {
1105
+ const models = await upstream.models.list();
1106
+ return new Response(JSON.stringify(models), {
1107
+ headers: { "Content-Type": "application/json" }
1108
+ });
1109
+ } catch (err) {
1110
+ return new Response(JSON.stringify({ error: err.message }), {
1111
+ status: 500,
1112
+ headers: { "Content-Type": "application/json" }
1113
+ });
1114
+ }
1115
+ }
1116
+ return new Response("Not Found", { status: 404 });
1117
+ }
1118
+ });
1119
+ return serverInstance;
1120
+ }
1121
+ function getServerURL() {
1122
+ return `http://localhost:${PORT}/v1`;
1123
+ }
1124
+ function getPort() {
1125
+ return PORT;
1126
+ }
1127
+ module2.exports = { startServer, getServerURL, getPort };
1128
+ });
1129
+ var require_toolExecutors = __commonJS((exports, module2) => {
1130
+ var fs2 = __require("fs");
1131
+ var path2 = __require("path");
1132
+ var https = __require("https");
1133
+ var { execSync } = __require("child_process");
1134
+ var {
1135
+ PROJECT_ROOT,
1136
+ TOOL_TIMEOUT,
1137
+ REVIEWER_MODEL,
1138
+ REVIEWER_SYSTEM_PROMPT,
1139
+ FILE_PICKER_MODEL,
1140
+ FILE_PICKER_SYSTEM_PROMPT,
1141
+ THINKER_MODEL,
1142
+ THINKER_SYSTEM_PROMPT,
1143
+ COMMANDER_MODEL,
1144
+ COMMANDER_SYSTEM_PROMPT,
1145
+ CONTEXT_PRUNER_MODEL,
1146
+ CONTEXT_PRUNER_SYSTEM_PROMPT,
1147
+ SELECTOR_SYSTEM_PROMPT,
1148
+ RESEARCHER_MODEL,
1149
+ GENERAL_AGENT_MODEL,
1150
+ RESEARCHER_WEB_SYSTEM_PROMPT,
1151
+ RESEARCHER_DOCS_SYSTEM_PROMPT,
1152
+ GENERAL_AGENT_SYSTEM_PROMPT,
1153
+ NVIDIA_MODEL,
1154
+ nvidiaClient,
1155
+ session,
1156
+ truncateOutput,
1157
+ resolvePath,
1158
+ sleep
1159
+ } = require_config();
1160
+ var { parseThinkBlocks } = require_thinking();
1161
+ async function streamCompletion(params, onStream) {
1162
+ for (let attempt = 0;attempt <= 2; attempt++) {
1163
+ let content = "";
1164
+ let reasoning = "";
1165
+ try {
1166
+ if (onStream) {
1167
+ const stream = await nvidiaClient.chat.completions.create({ ...params, stream: true });
1168
+ let displayContent = "";
1169
+ for await (const chunk of stream) {
1170
+ const delta = chunk.choices?.[0]?.delta;
1171
+ if (delta?.content) {
1172
+ content += delta.content;
1173
+ const lastOpen = content.lastIndexOf("<think>");
1174
+ const lastClose = content.lastIndexOf("</think>");
1175
+ if (lastOpen <= lastClose || lastOpen === -1) {
1176
+ displayContent = parseThinkBlocks(content).content;
1177
+ }
1178
+ onStream(displayContent || reasoning);
1179
+ }
1180
+ if (delta?.reasoning_content) {
1181
+ reasoning += delta.reasoning_content;
1182
+ onStream(displayContent || reasoning);
1183
+ }
1184
+ }
1185
+ let { content: cleaned } = parseThinkBlocks(content);
1186
+ const unclosedIdx = cleaned.lastIndexOf("<think>");
1187
+ if (unclosedIdx !== -1 && cleaned.indexOf("</think>", unclosedIdx) === -1) {
1188
+ cleaned = cleaned.slice(0, unclosedIdx).trim();
1189
+ }
1190
+ return cleaned || reasoning || "";
1191
+ } else {
1192
+ const response = await nvidiaClient.chat.completions.create(params);
1193
+ const rawContent = response.choices[0]?.message?.content || "";
1194
+ const rawReasoning = response.choices[0]?.message?.reasoning_content || "";
1195
+ let { content: cleaned } = parseThinkBlocks(rawContent);
1196
+ const unclosedIdx = cleaned.lastIndexOf("<think>");
1197
+ if (unclosedIdx !== -1 && cleaned.indexOf("</think>", unclosedIdx) === -1) {
1198
+ cleaned = cleaned.slice(0, unclosedIdx).trim();
1199
+ }
1200
+ return cleaned || rawReasoning || "";
1201
+ }
1202
+ } catch (err) {
1203
+ if (err.status === 404 && params.model !== NVIDIA_MODEL && attempt < 2) {
1204
+ params = { ...params, model: NVIDIA_MODEL };
1205
+ continue;
1206
+ }
1207
+ if (attempt < 2 && (err.status === 429 || err.status >= 500)) {
1208
+ await sleep(1000 * Math.pow(2, attempt));
1209
+ continue;
1210
+ }
1211
+ throw err;
1212
+ }
1213
+ }
1214
+ }
1215
+ function parseEditorOps(text) {
1216
+ const ops = [];
1217
+ const editRe = /---\s*EDIT:\s*(.+?)\s*---[\s\S]*?OLD:\s*\n```[^\n]*\n([\s\S]*?)\n```[\s\S]*?NEW:\s*\n```[^\n]*\n([\s\S]*?)\n```/g;
1218
+ let m2;
1219
+ while ((m2 = editRe.exec(text)) !== null) {
1220
+ ops.push({ type: "edit", path: m2[1].trim(), old_str: m2[2], new_str: m2[3] });
1221
+ }
1222
+ const createRe = /---\s*CREATE:\s*(.+?)\s*---\s*\n```[^\n]*\n([\s\S]*?)\n```/g;
1223
+ while ((m2 = createRe.exec(text)) !== null) {
1224
+ const p = m2[1].trim();
1225
+ if (!ops.some((o) => o.path === p && o.type === "edit")) {
1226
+ ops.push({ type: "create", path: p, content: m2[2] });
1227
+ }
1228
+ }
1229
+ return ops;
1230
+ }
1231
+ async function applyEditorOps(ops, executeFn) {
1232
+ const results = [];
1233
+ for (const op of ops) {
1234
+ if (op.type === "edit") {
1235
+ const r = await executeFn("Edit", { path: op.path, old_str: op.old_str, new_str: op.new_str });
1236
+ results.push(r.startsWith("Error") ? `\u2717 Edit ${op.path}: ${r}` : `\u2713 Edit ${op.path}`);
1237
+ } else if (op.type === "create") {
1238
+ const r = await executeFn("Write", { path: op.path, content: op.content });
1239
+ results.push(r.startsWith("Error") ? `\u2717 Create ${op.path}: ${r}` : `\u2713 Create ${op.path}`);
1240
+ }
1241
+ }
1242
+ return results;
1243
+ }
1244
+ async function executeTool(name, args, onStream) {
1245
+ try {
1246
+ switch (name) {
1247
+ case "Read": {
1248
+ const filePath = resolvePath(args.path);
1249
+ const stat = fs2.statSync(filePath, { throwIfNoEntry: false });
1250
+ if (!stat)
1251
+ return `Error: File not found: ${filePath}`;
1252
+ if (stat.isDirectory())
1253
+ return `Error: ${filePath} is a directory. Use ListDir instead.`;
1254
+ const content = fs2.readFileSync(filePath, "utf-8");
1255
+ const lines = content.split(`
1256
+ `);
1257
+ const start = Math.max(0, (args.start_line || 1) - 1);
1258
+ const end = args.end_line ? Math.min(lines.length, args.end_line) : Math.min(lines.length, start + 500);
1259
+ const slice = lines.slice(start, end);
1260
+ const numbered = slice.map((l, i) => `${start + i + 1}: ${l}`).join(`
1261
+ `);
1262
+ session.filesRead.add(filePath);
1263
+ if (end < lines.length) {
1264
+ return truncateOutput(numbered) + `
1265
+ (showing lines ${start + 1}-${end} of ${lines.length})`;
1266
+ }
1267
+ return truncateOutput(numbered);
1268
+ }
1269
+ case "Write": {
1270
+ const filePath = resolvePath(args.path);
1271
+ const dir = path2.dirname(filePath);
1272
+ if (!fs2.existsSync(dir))
1273
+ fs2.mkdirSync(dir, { recursive: true });
1274
+ const existed = fs2.existsSync(filePath);
1275
+ const before = existed ? fs2.readFileSync(filePath, "utf-8") : null;
1276
+ fs2.writeFileSync(filePath, args.content, "utf-8");
1277
+ if (before !== null) {
1278
+ session.editHistory.push({ path: filePath, before, after: args.content, timestamp: Date.now() });
1279
+ }
1280
+ session.filesModified.add(filePath);
1281
+ const lines = args.content.split(`
1282
+ `).length;
1283
+ return `${existed ? "Overwritten" : "Created"}: ${filePath} (${lines} lines)`;
1284
+ }
1285
+ case "Edit": {
1286
+ const filePath = resolvePath(args.path);
1287
+ if (!fs2.existsSync(filePath))
1288
+ return `Error: File not found: ${filePath}`;
1289
+ const content = fs2.readFileSync(filePath, "utf-8");
1290
+ const count = content.split(args.old_str).length - 1;
1291
+ if (count === 0)
1292
+ return `Error: old_str not found in ${path2.basename(filePath)}. Make sure it matches exactly (including whitespace and indentation).`;
1293
+ if (count > 1)
1294
+ return `Error: old_str found ${count} times in ${path2.basename(filePath)}. It must be unique. Add more surrounding context to make it unique.`;
1295
+ const updated = content.replace(args.old_str, args.new_str);
1296
+ fs2.writeFileSync(filePath, updated, "utf-8");
1297
+ session.editHistory.push({ path: filePath, before: content, after: updated, timestamp: Date.now() });
1298
+ session.filesModified.add(filePath);
1299
+ const oldLines = args.old_str.split(`
1300
+ `);
1301
+ const newLines = args.new_str.split(`
1302
+ `);
1303
+ let diff = `Edited: ${filePath}
1304
+ `;
1305
+ oldLines.forEach((l) => diff += `- ${l}
1306
+ `);
1307
+ newLines.forEach((l) => diff += `+ ${l}
1308
+ `);
1309
+ return diff;
1310
+ }
1311
+ case "Patch": {
1312
+ const filePath = resolvePath(args.path);
1313
+ if (!fs2.existsSync(filePath))
1314
+ return `Error: File not found: ${filePath}`;
1315
+ let content = fs2.readFileSync(filePath, "utf-8");
1316
+ const before = content;
1317
+ const results = [];
1318
+ for (let i = 0;i < args.edits.length; i++) {
1319
+ const edit = args.edits[i];
1320
+ if (!content.includes(edit.old_str)) {
1321
+ results.push(`Edit ${i + 1}: FAILED - old_str not found`);
1322
+ continue;
1323
+ }
1324
+ content = content.replace(edit.old_str, edit.new_str);
1325
+ results.push(`Edit ${i + 1}: OK`);
1326
+ }
1327
+ fs2.writeFileSync(filePath, content, "utf-8");
1328
+ session.editHistory.push({ path: filePath, before, after: content, timestamp: Date.now() });
1329
+ session.filesModified.add(filePath);
1330
+ return `Patched: ${filePath}
1331
+ ${results.join(`
1332
+ `)}`;
1333
+ }
1334
+ case "Bash": {
1335
+ const cwd = args.cwd ? resolvePath(args.cwd) : PROJECT_ROOT;
1336
+ session.commandsRun.push(args.command);
1337
+ try {
1338
+ const output = execSync(args.command, {
1339
+ encoding: "utf-8",
1340
+ timeout: TOOL_TIMEOUT,
1341
+ cwd,
1342
+ maxBuffer: 1024 * 1024 * 5,
1343
+ stdio: ["pipe", "pipe", "pipe"]
1344
+ });
1345
+ return truncateOutput(output || "(no output)");
1346
+ } catch (err) {
1347
+ const stdout = err.stdout || "";
1348
+ const stderr = err.stderr || "";
1349
+ const exitCode = err.status || 1;
1350
+ return truncateOutput(`Exit code: ${exitCode}
1351
+ ${stdout}
1352
+ ${stderr}`.trim());
1353
+ }
1354
+ }
1355
+ case "Grep": {
1356
+ const searchPath = resolvePath(args.path);
1357
+ const flags = args.case_sensitive ? "" : "-i";
1358
+ const include = args.include ? `--include='${args.include}'` : "";
1359
+ try {
1360
+ const cmd = `grep -rn ${flags} ${include} --color=never "${args.pattern.replace(/"/g, "\\\"")}" "${searchPath}" 2>/dev/null | head -80`;
1361
+ const output = execSync(cmd, { encoding: "utf-8", timeout: 15000 });
1362
+ return truncateOutput(output || "No matches found.");
1363
+ } catch {
1364
+ return "No matches found.";
1365
+ }
1366
+ }
1367
+ case "Glob": {
1368
+ const cwd = args.cwd ? resolvePath(args.cwd) : PROJECT_ROOT;
1369
+ try {
1370
+ const pattern = args.pattern;
1371
+ let cmd;
1372
+ if (pattern.includes("**")) {
1373
+ const namePattern = pattern.replace(/\*\*\//g, "").replace(/\*/g, "*");
1374
+ cmd = `find "${cwd}" -name "${namePattern}" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -100`;
1375
+ } else {
1376
+ cmd = `find "${cwd}" -name "${pattern}" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -100`;
1377
+ }
1378
+ const output = execSync(cmd, { encoding: "utf-8", timeout: 1e4 });
1379
+ if (!output.trim())
1380
+ return "No files found matching pattern.";
1381
+ const files = output.trim().split(`
1382
+ `).map((f) => path2.relative(cwd, f)).sort();
1383
+ return files.join(`
1384
+ `);
1385
+ } catch {
1386
+ return "No files found matching pattern.";
1387
+ }
1388
+ }
1389
+ case "ListDir": {
1390
+ let listRecursive = function(dir, depth, maxDepth2) {
1391
+ const entries = fs2.readdirSync(dir, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").sort((a, b2) => {
1392
+ if (a.isDirectory() && !b2.isDirectory())
1393
+ return -1;
1394
+ if (!a.isDirectory() && b2.isDirectory())
1395
+ return 1;
1396
+ return a.name.localeCompare(b2.name);
1397
+ });
1398
+ const lines2 = [];
1399
+ for (const entry of entries) {
1400
+ const prefix = " ".repeat(depth);
1401
+ if (entry.isDirectory()) {
1402
+ lines2.push(`${prefix}${entry.name}/`);
1403
+ if (depth < maxDepth2) {
1404
+ lines2.push(...listRecursive(path2.join(dir, entry.name), depth + 1, maxDepth2));
1405
+ }
1406
+ } else {
1407
+ const size = fs2.statSync(path2.join(dir, entry.name)).size;
1408
+ const sizeStr = size < 1024 ? `${size}B` : size < 1024 * 1024 ? `${(size / 1024).toFixed(1)}K` : `${(size / (1024 * 1024)).toFixed(1)}M`;
1409
+ lines2.push(`${prefix}${entry.name} (${sizeStr})`);
1410
+ }
1411
+ }
1412
+ return lines2;
1413
+ };
1414
+ const dirPath = resolvePath(args.path);
1415
+ if (!fs2.existsSync(dirPath))
1416
+ return `Error: Directory not found: ${dirPath}`;
1417
+ const stat = fs2.statSync(dirPath);
1418
+ if (!stat.isDirectory())
1419
+ return `Error: ${dirPath} is not a directory.`;
1420
+ const maxDepth = args.recursive ? 3 : 0;
1421
+ const lines = listRecursive(dirPath, 0, maxDepth);
1422
+ return truncateOutput(lines.join(`
1423
+ `) || "(empty directory)");
1424
+ }
1425
+ case "UndoEdit": {
1426
+ const filePath = resolvePath(args.path);
1427
+ const lastEdit = [...session.editHistory].reverse().find((e) => e.path === filePath);
1428
+ if (!lastEdit)
1429
+ return `Error: No edit history for ${filePath}`;
1430
+ fs2.writeFileSync(filePath, lastEdit.before, "utf-8");
1431
+ session.editHistory = session.editHistory.filter((e) => e !== lastEdit);
1432
+ return `Undone last edit to ${filePath}`;
1433
+ }
1434
+ case "Task": {
1435
+ const results = [];
1436
+ for (const cmd of args.commands) {
1437
+ try {
1438
+ const output = execSync(cmd, {
1439
+ encoding: "utf-8",
1440
+ timeout: TOOL_TIMEOUT,
1441
+ cwd: PROJECT_ROOT,
1442
+ maxBuffer: 1024 * 1024 * 5,
1443
+ stdio: ["pipe", "pipe", "pipe"]
1444
+ });
1445
+ results.push(`\u2713 ${cmd}
1446
+ ${output.trim()}`);
1447
+ session.commandsRun.push(cmd);
1448
+ } catch (err) {
1449
+ results.push(`\u2717 ${cmd}
1450
+ Exit code: ${err.status}
1451
+ ${(err.stdout || "").trim()}
1452
+ ${(err.stderr || "").trim()}`);
1453
+ session.commandsRun.push(cmd);
1454
+ break;
1455
+ }
1456
+ }
1457
+ return truncateOutput(`Task: ${args.description}
1458
+ ${"\u2500".repeat(40)}
1459
+ ${results.join(`
1460
+
1461
+ `)}`);
1462
+ }
1463
+ case "WebSearch": {
1464
+ const apiKey = process.env.EXA_API_KEY;
1465
+ if (!apiKey)
1466
+ return "Error: EXA_API_KEY environment variable is not set. Get one at https://dashboard.exa.ai/api-keys";
1467
+ const body = JSON.stringify({
1468
+ query: args.query,
1469
+ numResults: Math.min(args.num_results || 5, 10),
1470
+ type: args.type || "auto",
1471
+ ...args.include_domains && { includeDomains: args.include_domains },
1472
+ ...args.category && { category: args.category },
1473
+ contents: { highlights: { maxCharacters: 300 }, text: { maxCharacters: 1000 } }
1474
+ });
1475
+ const result = await new Promise((resolve3) => {
1476
+ const req = https.request({
1477
+ hostname: "api.exa.ai",
1478
+ path: "/search",
1479
+ method: "POST",
1480
+ headers: {
1481
+ "Content-Type": "application/json",
1482
+ "x-api-key": apiKey
1483
+ }
1484
+ }, (res) => {
1485
+ let data = "";
1486
+ res.on("data", (chunk) => data += chunk);
1487
+ res.on("end", () => {
1488
+ if (res.statusCode !== 200) {
1489
+ resolve3(`Error: Exa API returned ${res.statusCode}: ${data}`);
1490
+ return;
1491
+ }
1492
+ try {
1493
+ const json = JSON.parse(data);
1494
+ if (!json.results || json.results.length === 0) {
1495
+ resolve3("No results found.");
1496
+ return;
1497
+ }
1498
+ const formatted = json.results.map((r, i) => {
1499
+ let entry = `${i + 1}. **${r.title || "Untitled"}**
1500
+ ${r.url}`;
1501
+ if (r.publishedDate)
1502
+ entry += `
1503
+ Published: ${r.publishedDate.split("T")[0]}`;
1504
+ if (r.author)
1505
+ entry += `
1506
+ Author: ${r.author}`;
1507
+ if (r.text)
1508
+ entry += `
1509
+ ${r.text.trim().slice(0, 500)}`;
1510
+ else if (r.highlights && r.highlights.length)
1511
+ entry += `
1512
+ ${r.highlights[0].trim().slice(0, 300)}`;
1513
+ return entry;
1514
+ }).join(`
1515
+
1516
+ `);
1517
+ resolve3(truncateOutput(`Web Search Results (${json.results.length}):
1518
+ ${"\u2500".repeat(40)}
1519
+ ${formatted}`));
1520
+ } catch (e) {
1521
+ resolve3(`Error: Failed to parse Exa response: ${e.message}`);
1522
+ }
1523
+ });
1524
+ });
1525
+ req.on("error", (e) => resolve3(`Error: Exa request failed: ${e.message}`));
1526
+ req.setTimeout(15000, () => {
1527
+ req.destroy();
1528
+ resolve3("Error: Exa search timed out.");
1529
+ });
1530
+ req.write(body);
1531
+ req.end();
1532
+ });
1533
+ return result;
1534
+ }
1535
+ case "FilePickerMax": {
1536
+ let tree = "";
1537
+ try {
1538
+ tree = execSync(`find "${PROJECT_ROOT}" -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/.cache/*" -not -path "*/.local/*" -not -path "*/.upm/*" -not -path "*/.config/*" 2>/dev/null | head -500`, { encoding: "utf-8", timeout: 15000 }).trim();
1539
+ tree = tree.split(`
1540
+ `).map((f) => path2.relative(PROJECT_ROOT, f) || ".").join(`
1541
+ `);
1542
+ } catch {
1543
+ tree = "(failed to scan directory tree)";
1544
+ }
1545
+ const sourceExts = /\.(js|ts|jsx|tsx|py|rb|go|rs|java|c|cpp|h|hpp|css|scss|html|svelte|vue|json|yaml|yml|toml|md|sql|sh|bash|env|cfg|ini|xml)$/i;
1546
+ const allFiles = tree.split(`
1547
+ `).filter((f) => sourceExts.test(f));
1548
+ const previews = [];
1549
+ for (const relFile of allFiles.slice(0, 200)) {
1550
+ const absFile = path2.resolve(PROJECT_ROOT, relFile);
1551
+ try {
1552
+ const stat = fs2.statSync(absFile, { throwIfNoEntry: false });
1553
+ if (!stat || stat.isDirectory() || stat.size > 512 * 1024)
1554
+ continue;
1555
+ const content = fs2.readFileSync(absFile, "utf-8");
1556
+ const first8 = content.split(`
1557
+ `).slice(0, 8).join(`
1558
+ `);
1559
+ previews.push(`--- ${relFile} ---
1560
+ ${first8}`);
1561
+ } catch {}
1562
+ }
1563
+ const pickerMessages = [
1564
+ { role: "system", content: FILE_PICKER_SYSTEM_PROMPT },
1565
+ {
1566
+ role: "user",
1567
+ content: `# Prompt
1568
+ ${args.prompt}
1569
+
1570
+ # Directory Tree
1571
+ ${tree}
1572
+
1573
+ # File Previews (first 8 lines each)
1574
+ ${previews.join(`
1575
+
1576
+ `)}`
1577
+ }
1578
+ ];
1579
+ try {
1580
+ const header = `FilePickerMax Results
1581
+ ${"\u2500".repeat(40)}
1582
+ `;
1583
+ const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
1584
+ const raw = await streamCompletion({
1585
+ model: FILE_PICKER_MODEL,
1586
+ messages: pickerMessages,
1587
+ max_tokens: 4096,
1588
+ temperature: 0.2
1589
+ }, streamCb) || "[]";
1590
+ return truncateOutput(header + raw);
1591
+ } catch (apiErr) {
1592
+ return `Error: FilePickerMax failed \u2014 ${apiErr.message}`;
1593
+ }
1594
+ }
1595
+ case "TodoList": {
1596
+ const todoFile = path2.join(PROJECT_ROOT, ".apex-todos.json");
1597
+ const loadTodos = () => {
1598
+ try {
1599
+ return JSON.parse(fs2.readFileSync(todoFile, "utf-8"));
1600
+ } catch {
1601
+ return [];
1602
+ }
1603
+ };
1604
+ const saveTodos = (todos2) => fs2.writeFileSync(todoFile, JSON.stringify(todos2, null, 2), "utf-8");
1605
+ const formatTodos = (todos2) => {
1606
+ if (todos2.length === 0)
1607
+ return "Todo list is empty.";
1608
+ return todos2.map((t2, i) => `${i + 1}. [${t2.done ? "x" : " "}] ${t2.text}${t2.done ? " \u2713" : ""}`).join(`
1609
+ `);
1610
+ };
1611
+ const todos = loadTodos();
1612
+ switch (args.action) {
1613
+ case "add": {
1614
+ if (!args.text)
1615
+ return 'Error: "text" is required for add action.';
1616
+ todos.push({ text: args.text, done: false, created: Date.now() });
1617
+ saveTodos(todos);
1618
+ return `Added item ${todos.length}: ${args.text}
1619
+
1620
+ ${formatTodos(todos)}`;
1621
+ }
1622
+ case "list":
1623
+ return formatTodos(todos);
1624
+ case "done": {
1625
+ const idx = (args.index || 0) - 1;
1626
+ if (idx < 0 || idx >= todos.length)
1627
+ return `Error: Invalid index. Use 1-${todos.length}.`;
1628
+ todos[idx].done = true;
1629
+ saveTodos(todos);
1630
+ return `Completed: ${todos[idx].text}
1631
+
1632
+ ${formatTodos(todos)}`;
1633
+ }
1634
+ case "remove": {
1635
+ const idx = (args.index || 0) - 1;
1636
+ if (idx < 0 || idx >= todos.length)
1637
+ return `Error: Invalid index. Use 1-${todos.length}.`;
1638
+ const removed = todos.splice(idx, 1)[0];
1639
+ saveTodos(todos);
1640
+ return `Removed: ${removed.text}
1641
+
1642
+ ${formatTodos(todos)}`;
1643
+ }
1644
+ case "clear": {
1645
+ const before = todos.length;
1646
+ const remaining = todos.filter((t2) => !t2.done);
1647
+ saveTodos(remaining);
1648
+ return `Cleared ${before - remaining.length} completed item(s).
1649
+
1650
+ ${formatTodos(remaining)}`;
1651
+ }
1652
+ default:
1653
+ return `Error: Unknown action "${args.action}". Use add, list, done, remove, or clear.`;
1654
+ }
1655
+ }
1656
+ case "CodeReview": {
1657
+ const allFiles = new Set([...session.filesModified]);
1658
+ if (args.files && args.files.length) {
1659
+ for (const f of args.files)
1660
+ allFiles.add(resolvePath(f));
1661
+ }
1662
+ if (allFiles.size === 0) {
1663
+ return "CodeReview skipped \u2014 no files were modified this session.";
1664
+ }
1665
+ const fileContents = [];
1666
+ for (const filePath of allFiles) {
1667
+ if (!fs2.existsSync(filePath)) {
1668
+ fileContents.push(`--- ${filePath} ---
1669
+ [File not found]`);
1670
+ continue;
1671
+ }
1672
+ const stat = fs2.statSync(filePath);
1673
+ if (stat.isDirectory())
1674
+ continue;
1675
+ const content = fs2.readFileSync(filePath, "utf-8");
1676
+ fileContents.push(`--- ${path2.relative(PROJECT_ROOT, filePath) || filePath} ---
1677
+ ${content}`);
1678
+ }
1679
+ let gitDiff = "";
1680
+ try {
1681
+ gitDiff = execSync("git diff 2>/dev/null", { encoding: "utf-8", cwd: PROJECT_ROOT, timeout: 1e4 }).trim();
1682
+ } catch {}
1683
+ const reviewMessages = [
1684
+ {
1685
+ role: "system",
1686
+ content: REVIEWER_SYSTEM_PROMPT
1687
+ },
1688
+ {
1689
+ role: "user",
1690
+ content: `# What was changed
1691
+ ${args.prompt}
1692
+
1693
+ # Modified files (${allFiles.size})
1694
+
1695
+ ${fileContents.join(`
1696
+
1697
+ `)}${gitDiff ? `
1698
+
1699
+ # Git diff
1700
+ \`\`\`diff
1701
+ ${gitDiff}
1702
+ \`\`\`` : ""}`
1703
+ }
1704
+ ];
1705
+ try {
1706
+ const header = `Code Review (${REVIEWER_MODEL}) \u2014 ${allFiles.size} file(s)
1707
+ ${"\u2500".repeat(40)}
1708
+ `;
1709
+ const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
1710
+ const reviewText = await streamCompletion({
1711
+ model: REVIEWER_MODEL,
1712
+ messages: reviewMessages,
1713
+ max_tokens: 4096,
1714
+ temperature: 0.3
1715
+ }, streamCb) || "(No response from reviewer)";
1716
+ return truncateOutput(header + reviewText);
1717
+ } catch (apiErr) {
1718
+ return `Error: Code review failed \u2014 ${apiErr.message}`;
1719
+ }
1720
+ }
1721
+ case "Thinker": {
1722
+ const historyContext = session.conversationHistory.slice(-10).map((m2) => `[${m2.role}]: ${(m2.content || "").slice(0, 500)}`).join(`
1723
+ `);
1724
+ const thinkerMessages = [
1725
+ { role: "system", content: THINKER_SYSTEM_PROMPT },
1726
+ {
1727
+ role: "user",
1728
+ content: `# Recent conversation context
1729
+ ${historyContext}
1730
+
1731
+ # Task to reason about
1732
+ ${args.prompt}`
1733
+ }
1734
+ ];
1735
+ try {
1736
+ const header = `Thinker (${THINKER_MODEL})
1737
+ ${"\u2500".repeat(40)}
1738
+ `;
1739
+ const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
1740
+ const result = await streamCompletion({
1741
+ model: THINKER_MODEL,
1742
+ messages: thinkerMessages,
1743
+ max_tokens: 4096,
1744
+ temperature: 0.4
1745
+ }, streamCb) || "(No response from thinker)";
1746
+ return truncateOutput(header + result);
1747
+ } catch (apiErr) {
1748
+ return `Error: Thinker failed \u2014 ${apiErr.message}`;
1749
+ }
1750
+ }
1751
+ case "ThinkerBestOfN": {
1752
+ const n = Math.min(5, Math.max(2, args.n || 3));
1753
+ const historyCtx = session.conversationHistory.slice(-10).map((m2) => `[${m2.role}]: ${(m2.content || "").slice(0, 500)}`).join(`
1754
+ `);
1755
+ const header = `Best-of-${n} Thinker (MAX mode)
1756
+ ${"\u2500".repeat(40)}
1757
+ `;
1758
+ if (onStream)
1759
+ onStream(header + `Spawning ${n} parallel thinking agents...`);
1760
+ const thinkPromises = [];
1761
+ for (let i = 0;i < n; i++) {
1762
+ const label = String.fromCharCode(65 + i);
1763
+ thinkPromises.push(streamCompletion({
1764
+ model: THINKER_MODEL,
1765
+ messages: [
1766
+ { role: "system", content: THINKER_SYSTEM_PROMPT + `
1767
+
1768
+ You are Thinker ${label}. Approach this from a unique angle. Be creative and thorough.` },
1769
+ {
1770
+ role: "user",
1771
+ content: `# Context
1772
+ ${historyCtx}
1773
+
1774
+ # Task
1775
+ ${args.prompt}`
1776
+ }
1777
+ ],
1778
+ max_tokens: 3072,
1779
+ temperature: 0.7 + i * 0.1
1780
+ }, null).then((result) => ({ label, result })));
1781
+ }
1782
+ let thoughts;
1783
+ try {
1784
+ thoughts = await Promise.all(thinkPromises);
1785
+ } catch (apiErr) {
1786
+ return `Error: ThinkerBestOfN failed \u2014 ${apiErr.message}`;
1787
+ }
1788
+ if (onStream)
1789
+ onStream(header + `All ${n} thinkers completed. Selecting best response...`);
1790
+ const thoughtsFormatted = thoughts.map((t2) => `## Thought ${t2.label}
1791
+ ${t2.result || "(empty)"}`).join(`
1792
+
1793
+ `);
1794
+ try {
1795
+ const selectorResult = await streamCompletion({
1796
+ model: REVIEWER_MODEL,
1797
+ messages: [
1798
+ {
1799
+ role: "system",
1800
+ content: `You are a thought selector. You will receive ${n} different reasoning responses to the same question. Pick the best one based on depth, correctness, clarity, and actionability. Output JSON only:
1801
+ { "chosen": "A", "reason": "why this is best" }`
1802
+ },
1803
+ { role: "user", content: `# Original question
1804
+ ${args.prompt}
1805
+
1806
+ ${thoughtsFormatted}` }
1807
+ ],
1808
+ max_tokens: 1024,
1809
+ temperature: 0.1
1810
+ }, null);
1811
+ let chosen = "A";
1812
+ let reason = "";
1813
+ try {
1814
+ const parsed = JSON.parse(selectorResult.replace(/```json?\n?/g, "").replace(/```/g, "").trim());
1815
+ chosen = parsed.chosen || "A";
1816
+ reason = parsed.reason || "";
1817
+ } catch {}
1818
+ const winningThought = thoughts.find((t2) => t2.label === chosen) || thoughts[0];
1819
+ const result = `${header}Selected: Thought ${chosen}${reason ? ` \u2014 ${reason}` : ""}
1820
+
1821
+ ${winningThought.result}`;
1822
+ if (onStream)
1823
+ onStream(truncateOutput(result));
1824
+ return truncateOutput(result);
1825
+ } catch (apiErr) {
1826
+ const result = `${header}Selector failed, using Thought A:
1827
+
1828
+ ${thoughts[0].result}`;
1829
+ return truncateOutput(result);
1830
+ }
1831
+ }
1832
+ case "EditorMultiPrompt": {
1833
+ const strategies = args.strategies || ["straightforward implementation", "alternative approach"];
1834
+ const filesCtx = (args.files || []).map((f) => `--- ${f.path} ---
1835
+ ${f.content}`).join(`
1836
+
1837
+ `);
1838
+ const header = `Multi-Prompt Editor (${strategies.length} strategies)
1839
+ ${"\u2500".repeat(40)}
1840
+ `;
1841
+ if (onStream)
1842
+ onStream(header + `Spawning ${strategies.length} parallel editor agents...`);
1843
+ const editorPromises = strategies.map((strategy, i) => {
1844
+ const label = String.fromCharCode(65 + i);
1845
+ return streamCompletion({
1846
+ model: NVIDIA_MODEL,
1847
+ messages: [
1848
+ {
1849
+ role: "system",
1850
+ content: `You are Code Editor ${label}. You implement code changes using a specific strategy. Output your implementation as a series of file edits.
1851
+
1852
+ For each file change, output:
1853
+ --- EDIT: path/to/file ---
1854
+ OLD:
1855
+ \`\`\`
1856
+ exact old code
1857
+ \`\`\`
1858
+ NEW:
1859
+ \`\`\`
1860
+ new replacement code
1861
+ \`\`\`
1862
+
1863
+ For new files, output:
1864
+ --- CREATE: path/to/file ---
1865
+ \`\`\`
1866
+ full file content
1867
+ \`\`\`
1868
+
1869
+ Be precise. Match existing code style.`
1870
+ },
1871
+ {
1872
+ role: "user",
1873
+ content: `# Task
1874
+ ${args.prompt}
1875
+
1876
+ # Strategy
1877
+ ${strategy}
1878
+
1879
+ # Current files
1880
+ ${filesCtx}`
1881
+ }
1882
+ ],
1883
+ max_tokens: 4096,
1884
+ temperature: 0.3
1885
+ }, null).then((result) => ({ label, strategy, result: result || "(empty)" }));
1886
+ });
1887
+ let implementations;
1888
+ try {
1889
+ implementations = await Promise.all(editorPromises);
1890
+ } catch (apiErr) {
1891
+ return `Error: EditorMultiPrompt failed \u2014 ${apiErr.message}`;
1892
+ }
1893
+ if (onStream)
1894
+ onStream(header + `All editors completed. Selecting best implementation...`);
1895
+ const implFormatted = implementations.map((impl) => `## Implementation ${impl.label} \u2014 Strategy: "${impl.strategy}"
1896
+ ${impl.result}`).join(`
1897
+
1898
+ `);
1899
+ try {
1900
+ const selectorResult = await streamCompletion({
1901
+ model: REVIEWER_MODEL,
1902
+ messages: [
1903
+ { role: "system", content: SELECTOR_SYSTEM_PROMPT },
1904
+ {
1905
+ role: "user",
1906
+ content: `# Original task
1907
+ ${args.prompt}
1908
+
1909
+ ${implFormatted}`
1910
+ }
1911
+ ],
1912
+ max_tokens: 1024,
1913
+ temperature: 0.1
1914
+ }, null);
1915
+ let chosen = "A";
1916
+ let reason = "";
1917
+ let improvements = "";
1918
+ try {
1919
+ const parsed = JSON.parse(selectorResult.replace(/```json?\n?/g, "").replace(/```/g, "").trim());
1920
+ chosen = parsed.chosen || "A";
1921
+ reason = parsed.reason || "";
1922
+ improvements = parsed.improvements || "";
1923
+ } catch {}
1924
+ const winning = implementations.find((impl) => impl.label === chosen) || implementations[0];
1925
+ let result = `${header}Selected: Implementation ${chosen} ("${winning.strategy}")`;
1926
+ if (reason)
1927
+ result += `
1928
+ Reason: ${reason}`;
1929
+ if (improvements)
1930
+ result += `
1931
+ Improvements to consider: ${improvements}`;
1932
+ const ops = parseEditorOps(winning.result);
1933
+ if (ops.length > 0) {
1934
+ if (onStream)
1935
+ onStream(truncateOutput(result + `
1936
+
1937
+ Applying ${ops.length} change(s)...`));
1938
+ const applyResults = await applyEditorOps(ops, executeTool);
1939
+ result += `
1940
+
1941
+ --- Applied Changes ---
1942
+ ${applyResults.join(`
1943
+ `)}`;
1944
+ } else {
1945
+ result += `
1946
+
1947
+ ${winning.result}`;
1948
+ }
1949
+ if (onStream)
1950
+ onStream(truncateOutput(result));
1951
+ return truncateOutput(result);
1952
+ } catch (apiErr) {
1953
+ const fallbackOps = parseEditorOps(implementations[0].result);
1954
+ if (fallbackOps.length > 0) {
1955
+ const applyResults = await applyEditorOps(fallbackOps, executeTool);
1956
+ return truncateOutput(`${header}Selector failed, applied Implementation A:
1957
+ ${applyResults.join(`
1958
+ `)}`);
1959
+ }
1960
+ return truncateOutput(`${header}Selector failed, using Implementation A:
1961
+
1962
+ ${implementations[0].result}`);
1963
+ }
1964
+ }
1965
+ case "CodeReviewMulti": {
1966
+ const perspectives = args.perspectives || [
1967
+ "correctness, logic errors, and edge cases",
1968
+ "security vulnerabilities and data safety",
1969
+ "performance, efficiency, and resource usage"
1970
+ ];
1971
+ const modFiles = new Set([...session.filesModified]);
1972
+ if (modFiles.size === 0)
1973
+ return "CodeReviewMulti skipped \u2014 no files were modified.";
1974
+ const modFileContents = [];
1975
+ for (const fp of modFiles) {
1976
+ if (!fs2.existsSync(fp))
1977
+ continue;
1978
+ const stat = fs2.statSync(fp);
1979
+ if (stat.isDirectory())
1980
+ continue;
1981
+ modFileContents.push(`--- ${path2.relative(PROJECT_ROOT, fp)} ---
1982
+ ${fs2.readFileSync(fp, "utf-8")}`);
1983
+ }
1984
+ let diffText = "";
1985
+ try {
1986
+ diffText = execSync("git diff 2>/dev/null", { encoding: "utf-8", cwd: PROJECT_ROOT, timeout: 1e4 }).trim();
1987
+ } catch {}
1988
+ const header = `Multi-Perspective Code Review (${perspectives.length} reviewers)
1989
+ ${"\u2500".repeat(40)}
1990
+ `;
1991
+ if (onStream)
1992
+ onStream(header + `Spawning ${perspectives.length} parallel reviewers...`);
1993
+ const reviewPromises = perspectives.map((perspective, i) => {
1994
+ const label = String.fromCharCode(65 + i);
1995
+ return streamCompletion({
1996
+ model: REVIEWER_MODEL,
1997
+ messages: [
1998
+ {
1999
+ role: "system",
2000
+ content: REVIEWER_SYSTEM_PROMPT + `
2001
+
2002
+ Focus specifically on: ${perspective}. You are Reviewer ${label}.`
2003
+ },
2004
+ {
2005
+ role: "user",
2006
+ content: `# Changes
2007
+ ${args.prompt}
2008
+
2009
+ # Files (${modFiles.size})
2010
+ ${modFileContents.join(`
2011
+
2012
+ `)}${diffText ? `
2013
+
2014
+ # Git diff
2015
+ \`\`\`diff
2016
+ ${diffText}
2017
+ \`\`\`` : ""}`
2018
+ }
2019
+ ],
2020
+ max_tokens: 3072,
2021
+ temperature: 0.3
2022
+ }, null).then((result2) => ({ label, perspective, result: result2 || "(no issues found)" }));
2023
+ });
2024
+ let reviews;
2025
+ try {
2026
+ reviews = await Promise.all(reviewPromises);
2027
+ } catch (apiErr) {
2028
+ return `Error: CodeReviewMulti failed \u2014 ${apiErr.message}`;
2029
+ }
2030
+ let result = header;
2031
+ for (const review of reviews) {
2032
+ result += `
2033
+ ## Reviewer ${review.label} \u2014 ${review.perspective}
2034
+ ${review.result}
2035
+ `;
2036
+ }
2037
+ if (onStream)
2038
+ onStream(truncateOutput(result));
2039
+ return truncateOutput(result);
2040
+ }
2041
+ case "Commander": {
2042
+ const header = `Commander (${COMMANDER_MODEL})
2043
+ ${"\u2500".repeat(40)}
2044
+ `;
2045
+ if (onStream)
2046
+ onStream(header + "Planning commands...");
2047
+ let commandPlan;
2048
+ try {
2049
+ commandPlan = await streamCompletion({
2050
+ model: COMMANDER_MODEL,
2051
+ messages: [
2052
+ { role: "system", content: COMMANDER_SYSTEM_PROMPT },
2053
+ { role: "user", content: args.prompt }
2054
+ ],
2055
+ max_tokens: 2048,
2056
+ temperature: 0.2
2057
+ }, null);
2058
+ } catch (apiErr) {
2059
+ return `Error: Commander failed \u2014 ${apiErr.message}`;
2060
+ }
2061
+ let commands;
2062
+ try {
2063
+ commands = JSON.parse(commandPlan.replace(/```json?\n?/g, "").replace(/```/g, "").trim());
2064
+ if (!Array.isArray(commands))
2065
+ commands = [commands];
2066
+ } catch {
2067
+ return truncateOutput(`${header}Failed to parse command plan:
2068
+ ${commandPlan}`);
2069
+ }
2070
+ const results = [];
2071
+ for (const cmd of commands) {
2072
+ const command = typeof cmd === "string" ? cmd : cmd.command;
2073
+ const description = typeof cmd === "string" ? "" : cmd.description || "";
2074
+ if (onStream)
2075
+ onStream(truncateOutput(`${header}Running: ${command}${description ? ` (${description})` : ""}...`));
2076
+ try {
2077
+ const output = execSync(command, {
2078
+ encoding: "utf-8",
2079
+ timeout: TOOL_TIMEOUT,
2080
+ cwd: PROJECT_ROOT,
2081
+ maxBuffer: 1024 * 1024 * 5,
2082
+ stdio: ["pipe", "pipe", "pipe"]
2083
+ });
2084
+ results.push(`\u2713 ${command}${description ? `
2085
+ (${description})` : ""}
2086
+ ${(output || "").trim()}`);
2087
+ session.commandsRun.push(command);
2088
+ } catch (err) {
2089
+ results.push(`\u2717 ${command}
2090
+ Exit code: ${err.status}
2091
+ ${(err.stdout || "").trim()}
2092
+ ${(err.stderr || "").trim()}`);
2093
+ session.commandsRun.push(command);
2094
+ break;
2095
+ }
2096
+ }
2097
+ const result = `${header}${results.join(`
2098
+
2099
+ `)}`;
2100
+ if (onStream)
2101
+ onStream(truncateOutput(result));
2102
+ return truncateOutput(result);
2103
+ }
2104
+ case "ContextPruner": {
2105
+ if (session.conversationHistory.length < 6) {
2106
+ return "Context pruning skipped \u2014 conversation is still short.";
2107
+ }
2108
+ const header = `Context Pruner
2109
+ ${"\u2500".repeat(40)}
2110
+ `;
2111
+ if (onStream)
2112
+ onStream(header + "Summarizing conversation...");
2113
+ const historyText = session.conversationHistory.map((m2) => `[${m2.role}]: ${(m2.content || "").slice(0, 1000)}`).join(`
2114
+ `);
2115
+ try {
2116
+ const summary = await streamCompletion({
2117
+ model: CONTEXT_PRUNER_MODEL,
2118
+ messages: [
2119
+ { role: "system", content: CONTEXT_PRUNER_SYSTEM_PROMPT },
2120
+ { role: "user", content: `# Conversation to summarize (${session.conversationHistory.length} messages)
2121
+
2122
+ ${historyText}` }
2123
+ ],
2124
+ max_tokens: 2048,
2125
+ temperature: 0.2
2126
+ }, null);
2127
+ const oldLen = session.conversationHistory.length;
2128
+ session.conversationHistory = [
2129
+ {
2130
+ role: "system",
2131
+ content: `[Context Summary \u2014 ${oldLen} messages condensed]
2132
+ ${summary}`
2133
+ }
2134
+ ];
2135
+ const result = `${header}Condensed ${oldLen} messages into summary.
2136
+
2137
+ ${summary}`;
2138
+ if (onStream)
2139
+ onStream(truncateOutput(result));
2140
+ return truncateOutput(result);
2141
+ } catch (apiErr) {
2142
+ return `Error: Context pruning failed \u2014 ${apiErr.message}`;
2143
+ }
2144
+ }
2145
+ case "ResearcherWeb": {
2146
+ const header = `Web Research (${RESEARCHER_MODEL})
2147
+ ${"\u2500".repeat(40)}
2148
+ `;
2149
+ if (onStream)
2150
+ onStream(header + "Searching the web...");
2151
+ let searchResults = "";
2152
+ const searchArgs = { query: args.prompt, num_results: 5 };
2153
+ if (args.domains && args.domains.length)
2154
+ searchArgs.include_domains = args.domains;
2155
+ try {
2156
+ searchResults = await executeTool("WebSearch", searchArgs);
2157
+ } catch {
2158
+ searchResults = "(Web search unavailable \u2014 answering from knowledge)";
2159
+ }
2160
+ if (searchResults.startsWith("Error")) {
2161
+ searchResults = `(Web search failed: ${searchResults.slice(0, 200)})
2162
+
2163
+ Please answer from your training data.`;
2164
+ }
2165
+ try {
2166
+ const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
2167
+ const result = await streamCompletion({
2168
+ model: RESEARCHER_MODEL,
2169
+ messages: [
2170
+ { role: "system", content: RESEARCHER_WEB_SYSTEM_PROMPT },
2171
+ { role: "user", content: `# Question
2172
+ ${args.prompt}
2173
+
2174
+ # Web Search Results
2175
+ ${searchResults}` }
2176
+ ],
2177
+ max_tokens: 4096,
2178
+ temperature: 0.3
2179
+ }, streamCb) || "(No response from researcher)";
2180
+ return truncateOutput(header + result);
2181
+ } catch (apiErr) {
2182
+ return `Error: ResearcherWeb failed \u2014 ${apiErr.message}`;
2183
+ }
2184
+ }
2185
+ case "ResearcherDocs": {
2186
+ const header = `Docs Research (${RESEARCHER_MODEL})
2187
+ ${"\u2500".repeat(40)}
2188
+ `;
2189
+ if (onStream)
2190
+ onStream(header + "Searching documentation...");
2191
+ const docDomains = [
2192
+ "developer.mozilla.org",
2193
+ "react.dev",
2194
+ "nodejs.org",
2195
+ "docs.python.org",
2196
+ "doc.rust-lang.org",
2197
+ "pkg.go.dev",
2198
+ "learn.microsoft.com",
2199
+ "typescriptlang.org",
2200
+ "expressjs.com",
2201
+ "nextjs.org",
2202
+ "vuejs.org",
2203
+ "angular.io",
2204
+ "svelte.dev",
2205
+ "docs.rs",
2206
+ "rubydoc.info",
2207
+ "docs.oracle.com",
2208
+ "npmjs.com"
2209
+ ];
2210
+ const query = args.library ? `${args.library} ${args.prompt}` : args.prompt;
2211
+ let searchResults = "";
2212
+ try {
2213
+ searchResults = await executeTool("WebSearch", {
2214
+ query: `${query} documentation`,
2215
+ num_results: 8,
2216
+ include_domains: docDomains
2217
+ });
2218
+ } catch {
2219
+ searchResults = "";
2220
+ }
2221
+ if (!searchResults || searchResults === "No results found.") {
2222
+ try {
2223
+ searchResults = await executeTool("WebSearch", {
2224
+ query: `${query} documentation API reference`,
2225
+ num_results: 5
2226
+ });
2227
+ } catch {
2228
+ searchResults = "(Documentation search unavailable \u2014 answering from knowledge)";
2229
+ }
2230
+ }
2231
+ if (!searchResults || searchResults.startsWith("Error")) {
2232
+ searchResults = "(No documentation results found \u2014 answering from knowledge)";
2233
+ }
2234
+ try {
2235
+ const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
2236
+ const result = await streamCompletion({
2237
+ model: RESEARCHER_MODEL,
2238
+ messages: [
2239
+ { role: "system", content: RESEARCHER_DOCS_SYSTEM_PROMPT },
2240
+ {
2241
+ role: "user",
2242
+ content: `# Question
2243
+ ${args.prompt}${args.library ? `
2244
+ Library: ${args.library}` : ""}
2245
+
2246
+ # Documentation Search Results
2247
+ ${searchResults}`
2248
+ }
2249
+ ],
2250
+ max_tokens: 4096,
2251
+ temperature: 0.2
2252
+ }, streamCb) || "(No response from researcher)";
2253
+ return truncateOutput(header + result);
2254
+ } catch (apiErr) {
2255
+ return `Error: ResearcherDocs failed \u2014 ${apiErr.message}`;
2256
+ }
2257
+ }
2258
+ case "GeneralAgent": {
2259
+ const header = `General Agent (${GENERAL_AGENT_MODEL})
2260
+ ${"\u2500".repeat(40)}
2261
+ `;
2262
+ if (onStream)
2263
+ onStream(header + "Reading files and analyzing...");
2264
+ const MAX_TOTAL_CHARS = 50000;
2265
+ let totalChars = 0;
2266
+ const fileContents = [];
2267
+ for (const fp of args.filePaths || []) {
2268
+ const absPath = resolvePath(fp);
2269
+ const stat = fs2.statSync(absPath, { throwIfNoEntry: false });
2270
+ if (!stat || stat.isDirectory()) {
2271
+ fileContents.push(`--- ${fp} ---
2272
+ [Not found or is a directory]`);
2273
+ continue;
2274
+ }
2275
+ if (stat.size > 256 * 1024) {
2276
+ fileContents.push(`--- ${fp} ---
2277
+ [File too large: ${(stat.size / 1024).toFixed(0)}KB \u2014 skipped]`);
2278
+ continue;
2279
+ }
2280
+ const content = fs2.readFileSync(absPath, "utf-8");
2281
+ if (totalChars + content.length > MAX_TOTAL_CHARS) {
2282
+ const remaining = MAX_TOTAL_CHARS - totalChars;
2283
+ if (remaining > 500) {
2284
+ fileContents.push(`--- ${fp} ---
2285
+ ${content.slice(0, remaining)}
2286
+ [Truncated \u2014 context limit reached]`);
2287
+ }
2288
+ totalChars = MAX_TOTAL_CHARS;
2289
+ break;
2290
+ }
2291
+ fileContents.push(`--- ${fp} ---
2292
+ ${content}`);
2293
+ totalChars += content.length;
2294
+ }
2295
+ const historyCtx = session.conversationHistory.slice(-8).map((m2) => `[${m2.role}]: ${(m2.content || "").slice(0, 400)}`).join(`
2296
+ `);
2297
+ const userContent = [
2298
+ `# Task
2299
+ ${args.prompt}`,
2300
+ fileContents.length > 0 ? `
2301
+ # Files (${fileContents.length})
2302
+ ${fileContents.join(`
2303
+
2304
+ `)}` : "",
2305
+ historyCtx ? `
2306
+ # Recent conversation
2307
+ ${historyCtx}` : ""
2308
+ ].filter(Boolean).join(`
2309
+ `);
2310
+ try {
2311
+ const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
2312
+ const result = await streamCompletion({
2313
+ model: GENERAL_AGENT_MODEL,
2314
+ messages: [
2315
+ { role: "system", content: GENERAL_AGENT_SYSTEM_PROMPT },
2316
+ { role: "user", content: userContent }
2317
+ ],
2318
+ max_tokens: 4096,
2319
+ temperature: 0.4
2320
+ }, streamCb) || "(No response from agent)";
2321
+ return truncateOutput(header + result);
2322
+ } catch (apiErr) {
2323
+ return `Error: GeneralAgent failed \u2014 ${apiErr.message}`;
2324
+ }
2325
+ }
2326
+ default:
2327
+ return `Unknown tool: ${name}`;
2328
+ }
2329
+ } catch (err) {
2330
+ return `Error executing ${name}: ${err.message}`;
2331
+ }
2332
+ }
2333
+ module2.exports = { executeTool };
2334
+ });
2335
+ var require_agent = __commonJS((exports, module2) => {
2336
+ var {
2337
+ NVIDIA_MODEL,
2338
+ MAX_TOOL_ITERATIONS,
2339
+ nvidiaClient,
2340
+ session,
2341
+ sleep
2342
+ } = require_config();
2343
+ var { buildSystemPrompt } = require_prompt();
2344
+ var { toolDefs } = require_tools();
2345
+ var { executeTool } = require_toolExecutors();
2346
+ var { toolDetailStr } = require_utils3();
2347
+ var store = require_store();
2348
+ var {
2349
+ parseThinkBlocks,
2350
+ findThinkClose,
2351
+ stripStrayCloseTag,
2352
+ splitAtPartialTag
2353
+ } = require_thinking();
2354
+ var isProcessing = false;
2355
+ function getIsProcessing() {
2356
+ return isProcessing;
2357
+ }
2358
+ async function handleUserInput(userInput) {
2359
+ isProcessing = true;
2360
+ store.setState({ isProcessing: true });
2361
+ session.turnCount++;
2362
+ store.addMessage({ role: "user", content: userInput });
2363
+ session.conversationHistory.push({ role: "user", content: userInput });
2364
+ let turnTokens = 0;
2365
+ try {
2366
+ store.addMessage({ role: "divider" });
2367
+ const systemPrompt = buildSystemPrompt();
2368
+ let messages = [
2369
+ { role: "system", content: systemPrompt },
2370
+ ...session.conversationHistory
2371
+ ];
2372
+ let iterations = 0;
2373
+ while (iterations < MAX_TOOL_ITERATIONS) {
2374
+ iterations++;
2375
+ let stream;
2376
+ const maxRetries = 3;
2377
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
2378
+ try {
2379
+ stream = await nvidiaClient.chat.completions.create({
2380
+ model: NVIDIA_MODEL,
2381
+ messages: messages.map((m2) => {
2382
+ const clean = { role: m2.role, content: m2.content };
2383
+ if (m2.tool_calls)
2384
+ clean.tool_calls = m2.tool_calls.map((tc) => ({
2385
+ id: tc.id,
2386
+ type: "function",
2387
+ function: { name: tc.function.name, arguments: tc.function.arguments }
2388
+ }));
2389
+ if (m2.tool_call_id)
2390
+ clean.tool_call_id = m2.tool_call_id;
2391
+ if (m2.role === "assistant" && !m2.content)
2392
+ clean.content = null;
2393
+ return clean;
2394
+ }),
2395
+ max_tokens: 4096,
2396
+ temperature: 0.6,
2397
+ top_p: 0.95,
2398
+ tools: toolDefs,
2399
+ tool_choice: "auto",
2400
+ stream: true
2401
+ });
2402
+ break;
2403
+ } catch (apiErr) {
2404
+ if (attempt < maxRetries && apiErr.status >= 400 && apiErr.status < 500) {
2405
+ await sleep(1000 * Math.pow(2, attempt));
2406
+ continue;
2407
+ }
2408
+ throw apiErr;
2409
+ }
2410
+ }
2411
+ let fullContent = "";
2412
+ const toolCallDeltas = {};
2413
+ const toolCallMsgIds = {};
2414
+ const seenToolCalls = new Set;
2415
+ let finishReason = null;
2416
+ let streamUsage = null;
2417
+ let reasoningText = "";
2418
+ let displayState = "buffering";
2419
+ let contentAccum = "";
2420
+ let thinkAccum = "";
2421
+ let displayContent = "";
2422
+ let thinkContent = "";
2423
+ let lastFlushTime = Date.now();
2424
+ for await (const chunk of stream) {
2425
+ if (chunk.usage)
2426
+ streamUsage = chunk.usage;
2427
+ const delta = chunk.choices?.[0]?.delta;
2428
+ if (!delta) {
2429
+ if (chunk.choices?.[0]?.finish_reason)
2430
+ finishReason = chunk.choices[0].finish_reason;
2431
+ continue;
2432
+ }
2433
+ if (chunk.choices[0].finish_reason)
2434
+ finishReason = chunk.choices[0].finish_reason;
2435
+ if (delta.tool_calls) {
2436
+ for (const tc of delta.tool_calls) {
2437
+ const idx = tc.index;
2438
+ if (!toolCallDeltas[idx]) {
2439
+ toolCallDeltas[idx] = { id: tc.id || "", name: tc.function?.name || "", arguments: "" };
2440
+ }
2441
+ if (tc.id)
2442
+ toolCallDeltas[idx].id = tc.id;
2443
+ if (tc.function?.name) {
2444
+ toolCallDeltas[idx].name = tc.function.name;
2445
+ if (!seenToolCalls.has(idx)) {
2446
+ seenToolCalls.add(idx);
2447
+ toolCallMsgIds[idx] = store.addMessage({
2448
+ role: "tool",
2449
+ name: tc.function.name,
2450
+ detail: "...",
2451
+ status: "pending"
2452
+ });
2453
+ }
2454
+ }
2455
+ if (tc.function?.arguments) {
2456
+ toolCallDeltas[idx].arguments += tc.function.arguments;
2457
+ }
2458
+ }
2459
+ }
2460
+ if (delta.reasoning_content) {
2461
+ reasoningText += delta.reasoning_content;
2462
+ store.updateStreaming(displayContent, reasoningText);
2463
+ }
2464
+ if (delta.content) {
2465
+ fullContent += delta.content;
2466
+ const hasTool = Object.keys(toolCallDeltas).length > 0;
2467
+ if (displayState === "streaming") {
2468
+ contentAccum += delta.content;
2469
+ contentAccum = stripStrayCloseTag(contentAccum);
2470
+ const openIdx = contentAccum.indexOf("<think>");
2471
+ if (openIdx !== -1) {
2472
+ if (openIdx > 0)
2473
+ displayContent += contentAccum.slice(0, openIdx);
2474
+ thinkAccum = contentAccum.slice(openIdx + 7);
2475
+ contentAccum = "";
2476
+ displayState = "thinking";
2477
+ const closeMatch = findThinkClose(thinkAccum);
2478
+ if (closeMatch) {
2479
+ const thought = thinkAccum.slice(0, closeMatch.pos).trim();
2480
+ const after = thinkAccum.slice(closeMatch.pos + closeMatch.len);
2481
+ thinkAccum = "";
2482
+ if (thought)
2483
+ store.addMessage({ role: "thinking", content: thought });
2484
+ displayState = "streaming";
2485
+ contentAccum = after;
2486
+ if (!hasTool && after)
2487
+ displayContent += after;
2488
+ contentAccum = "";
2489
+ thinkContent = "";
2490
+ } else {
2491
+ thinkContent = thinkAccum;
2492
+ }
2493
+ } else {
2494
+ const { safe, pending } = splitAtPartialTag(contentAccum);
2495
+ contentAccum = pending;
2496
+ if (!hasTool && safe)
2497
+ displayContent += safe;
2498
+ }
2499
+ store.updateStreaming(displayContent, thinkContent || reasoningText);
2500
+ } else if (displayState === "thinking") {
2501
+ thinkAccum += delta.content;
2502
+ const closeMatch = findThinkClose(thinkAccum);
2503
+ if (closeMatch) {
2504
+ const thought = thinkAccum.slice(0, closeMatch.pos).trim();
2505
+ const after = thinkAccum.slice(closeMatch.pos + closeMatch.len);
2506
+ thinkAccum = "";
2507
+ if (thought)
2508
+ store.addMessage({ role: "thinking", content: thought });
2509
+ displayState = "streaming";
2510
+ contentAccum = after;
2511
+ if (!hasTool && after)
2512
+ displayContent += after;
2513
+ contentAccum = "";
2514
+ thinkContent = "";
2515
+ store.updateStreaming(displayContent, reasoningText);
2516
+ } else {
2517
+ thinkContent = thinkAccum;
2518
+ store.updateStreaming(displayContent, thinkContent || reasoningText);
2519
+ }
2520
+ } else {
2521
+ contentAccum += delta.content;
2522
+ contentAccum = stripStrayCloseTag(contentAccum);
2523
+ const openIdx = contentAccum.indexOf("<think>");
2524
+ if (openIdx !== -1) {
2525
+ const before = contentAccum.slice(0, openIdx);
2526
+ thinkAccum = contentAccum.slice(openIdx + 7);
2527
+ contentAccum = "";
2528
+ if (!hasTool && before.trim())
2529
+ displayContent += before;
2530
+ displayState = "thinking";
2531
+ const closeMatch = findThinkClose(thinkAccum);
2532
+ if (closeMatch) {
2533
+ const thought = thinkAccum.slice(0, closeMatch.pos).trim();
2534
+ const after = thinkAccum.slice(closeMatch.pos + closeMatch.len);
2535
+ thinkAccum = "";
2536
+ if (thought)
2537
+ store.addMessage({ role: "thinking", content: thought });
2538
+ displayState = "streaming";
2539
+ contentAccum = after;
2540
+ if (!hasTool && after)
2541
+ displayContent += after;
2542
+ contentAccum = "";
2543
+ thinkContent = "";
2544
+ } else {
2545
+ thinkContent = thinkAccum;
2546
+ }
2547
+ store.updateStreaming(displayContent, thinkContent || reasoningText);
2548
+ } else {
2549
+ const { safe, pending } = splitAtPartialTag(contentAccum);
2550
+ if (safe.length > 0) {
2551
+ displayState = "streaming";
2552
+ if (!hasTool)
2553
+ displayContent += safe;
2554
+ contentAccum = pending;
2555
+ store.updateStreaming(displayContent, reasoningText);
2556
+ }
2557
+ }
2558
+ }
2559
+ }
2560
+ const now = Date.now();
2561
+ if (now - lastFlushTime > 16) {
2562
+ lastFlushTime = now;
2563
+ await new Promise((r) => setTimeout(r, 1));
2564
+ }
2565
+ }
2566
+ if (displayState === "thinking") {
2567
+ const thought = (thinkAccum + contentAccum).trim();
2568
+ if (thought)
2569
+ store.addMessage({ role: "thinking", content: thought });
2570
+ thinkAccum = "";
2571
+ contentAccum = "";
2572
+ } else if (displayState === "buffering") {
2573
+ const hasTool = Object.keys(toolCallDeltas).length > 0;
2574
+ if (!hasTool && contentAccum.trim())
2575
+ displayContent += contentAccum;
2576
+ contentAccum = "";
2577
+ } else if (contentAccum) {
2578
+ const hasTool = Object.keys(toolCallDeltas).length > 0;
2579
+ if (!hasTool)
2580
+ displayContent += contentAccum;
2581
+ contentAccum = "";
2582
+ }
2583
+ if (reasoningText.trim()) {
2584
+ store.addMessage({ role: "thinking", content: reasoningText.trim() });
2585
+ }
2586
+ const { content: parsedContent } = parseThinkBlocks(fullContent);
2587
+ turnTokens += streamUsage?.total_tokens || 0;
2588
+ const sortedIndices = Object.keys(toolCallDeltas).sort((a, b2) => a - b2);
2589
+ const toolCalls = sortedIndices.map((idx) => ({
2590
+ id: toolCallDeltas[idx].id,
2591
+ type: "function",
2592
+ function: { name: toolCallDeltas[idx].name, arguments: toolCallDeltas[idx].arguments }
2593
+ }));
2594
+ const msg = {
2595
+ role: "assistant",
2596
+ content: fullContent || null,
2597
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
2598
+ };
2599
+ if (toolCalls.length > 0) {
2600
+ store.clearStreaming();
2601
+ messages.push(msg);
2602
+ if (displayContent.trim()) {
2603
+ store.addMessage({ role: "assistant", content: displayContent.trim() });
2604
+ }
2605
+ sortedIndices.forEach((idx, i) => {
2606
+ const tc = toolCalls[i];
2607
+ let toolArgs;
2608
+ try {
2609
+ toolArgs = JSON.parse(tc.function.arguments);
2610
+ } catch {
2611
+ toolArgs = {};
2612
+ }
2613
+ const detail = toolDetailStr(tc.function.name, toolArgs);
2614
+ const msgId = toolCallMsgIds[idx];
2615
+ if (msgId)
2616
+ store.updateMessage(msgId, { detail, status: "running" });
2617
+ });
2618
+ const toolPromises = toolCalls.map(async (toolCall, i) => {
2619
+ const toolName = toolCall.function.name;
2620
+ let toolArgs;
2621
+ try {
2622
+ toolArgs = JSON.parse(toolCall.function.arguments);
2623
+ } catch {
2624
+ toolArgs = {};
2625
+ }
2626
+ const detail = toolDetailStr(toolName, toolArgs);
2627
+ const callStart = Date.now();
2628
+ const msgId = toolCallMsgIds[sortedIndices[i]];
2629
+ const result = await executeTool(toolName, toolArgs, (partial) => {
2630
+ if (msgId)
2631
+ store.updateMessage(msgId, { output: partial });
2632
+ });
2633
+ const success = !result.startsWith("Error");
2634
+ const elapsed = Date.now() - callStart;
2635
+ session.toolCallCount++;
2636
+ if (msgId) {
2637
+ store.updateMessage(msgId, {
2638
+ detail,
2639
+ status: success ? "done" : "error",
2640
+ success,
2641
+ elapsed,
2642
+ output: result
2643
+ });
2644
+ }
2645
+ if ((toolName === "Edit" || toolName === "Patch") && success) {
2646
+ store.addMessage({ role: "diff", filename: toolArgs.path, content: result });
2647
+ }
2648
+ return { id: toolCall.id, result };
2649
+ });
2650
+ const toolResults = await Promise.all(toolPromises);
2651
+ for (const { id, result } of toolResults) {
2652
+ messages.push({ role: "tool", tool_call_id: id, content: result });
2653
+ }
2654
+ if (finishReason === "stop")
2655
+ break;
2656
+ displayContent = "";
2657
+ continue;
2658
+ }
2659
+ if (fullContent) {
2660
+ const cleanedContent = parsedContent.trim() || fullContent.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
2661
+ if (cleanedContent) {
2662
+ store.finishStreaming({ role: "assistant", content: cleanedContent });
2663
+ } else {
2664
+ store.clearStreaming();
2665
+ }
2666
+ session.conversationHistory.push({ role: "assistant", content: cleanedContent || fullContent });
2667
+ } else {
2668
+ store.clearStreaming();
2669
+ }
2670
+ break;
2671
+ }
2672
+ if (iterations >= MAX_TOOL_ITERATIONS) {
2673
+ store.addMessage({ role: "system", content: `\u26A0 Reached maximum tool iterations (${MAX_TOOL_ITERATIONS}). Stopping.` });
2674
+ }
2675
+ session.totalTokens += turnTokens;
2676
+ } catch (err) {
2677
+ store.clearStreaming();
2678
+ let errorMsg = `Error: ${err.message}`;
2679
+ if (err.status) {
2680
+ errorMsg += `
2681
+ Status: ${err.status}`;
2682
+ }
2683
+ store.addMessage({ role: "system", content: errorMsg });
2684
+ }
2685
+ store.addMessage({ role: "divider" });
2686
+ isProcessing = false;
2687
+ store.setState({ isProcessing: false });
2688
+ }
2689
+ module2.exports = {
2690
+ handleUserInput,
2691
+ getIsProcessing
2692
+ };
2693
+ });
2694
+ var require_commands = __commonJS((exports, module2) => {
2695
+ var fs2 = __require("fs");
2696
+ var path2 = __require("path");
2697
+ var { execSync } = __require("child_process");
2698
+ var { PROJECT_ROOT, session, resolvePath } = require_config();
2699
+ var { executeTool } = require_toolExecutors();
2700
+ var store = require_store();
2701
+ async function handleSlashCommand(input) {
2702
+ const [cmd, ...rest] = input.split(" ");
2703
+ const arg = rest.join(" ");
2704
+ switch (cmd) {
2705
+ case "/help":
2706
+ store.setState({ showHelp: true });
2707
+ break;
2708
+ case "/clear":
2709
+ session.conversationHistory = [];
2710
+ store.clearMessages();
2711
+ store.addMessage({ role: "system", content: "Conversation cleared." });
2712
+ break;
2713
+ case "/files":
2714
+ case "/ls": {
2715
+ const dirPath = arg ? resolvePath(arg) : PROJECT_ROOT;
2716
+ store.addMessage({ role: "system", content: "Loading file tree...", label: "Project Files" });
2717
+ const result = await executeTool("ListDir", { path: dirPath, recursive: true });
2718
+ store.addMessage({ role: "system", content: result, label: "Project Files" });
2719
+ break;
2720
+ }
2721
+ case "/cost":
2722
+ case "/status": {
2723
+ const elapsed = ((Date.now() - session.startTime) / 1000 / 60).toFixed(1);
2724
+ const parts = [
2725
+ `Session: ${elapsed} min`,
2726
+ `Turns: ${session.turnCount}`,
2727
+ `Tools: ${session.toolCallCount}`,
2728
+ `Tokens: ${session.totalTokens.toLocaleString()}`,
2729
+ `Cost: $${session.totalCost.toFixed(4)}`
2730
+ ];
2731
+ if (session.filesModified.size > 0)
2732
+ parts.push(`Files modified: ${session.filesModified.size}`);
2733
+ if (session.commandsRun.length > 0)
2734
+ parts.push(`Commands: ${session.commandsRun.length}`);
2735
+ store.addMessage({ role: "system", content: parts.join(`
2736
+ `), label: "Session Stats" });
2737
+ break;
2738
+ }
2739
+ case "/undo": {
2740
+ if (session.editHistory.length === 0) {
2741
+ store.addMessage({ role: "system", content: "No edits to undo." });
2742
+ } else {
2743
+ const last = session.editHistory[session.editHistory.length - 1];
2744
+ fs2.writeFileSync(last.path, last.before, "utf-8");
2745
+ session.editHistory.pop();
2746
+ store.addMessage({ role: "system", content: `Undone last edit to ${path2.basename(last.path)}` });
2747
+ }
2748
+ break;
2749
+ }
2750
+ case "/diff": {
2751
+ try {
2752
+ const diff = execSync("git diff --stat 2>/dev/null", { encoding: "utf-8", cwd: PROJECT_ROOT });
2753
+ store.addMessage({ role: "system", content: diff || "(no changes)", label: "Git Diff" });
2754
+ } catch {
2755
+ store.addMessage({ role: "system", content: "Not a git repository." });
2756
+ }
2757
+ break;
2758
+ }
2759
+ case "/git": {
2760
+ if (!arg) {
2761
+ store.addMessage({ role: "system", content: "Usage: /git <command>" });
2762
+ break;
2763
+ }
2764
+ try {
2765
+ const output = execSync(`git ${arg}`, { encoding: "utf-8", cwd: PROJECT_ROOT });
2766
+ store.addMessage({ role: "system", content: output || "(no output)", label: `git ${arg}` });
2767
+ } catch (err) {
2768
+ store.addMessage({ role: "system", content: err.stderr || err.message });
2769
+ }
2770
+ break;
2771
+ }
2772
+ case "/compact": {
2773
+ const pruneId = store.addMessage({ role: "system", content: "Compacting conversation...", label: "Context Pruner" });
2774
+ try {
2775
+ const result = await executeTool("ContextPruner", {}, (partial) => {
2776
+ store.updateMessage(pruneId, { content: partial, label: "Context Pruner" });
2777
+ });
2778
+ store.updateMessage(pruneId, { content: result, label: "Context Pruner" });
2779
+ } catch (err) {
2780
+ store.updateMessage(pruneId, { content: `Compaction failed: ${err.message}` });
2781
+ }
2782
+ break;
2783
+ }
2784
+ case "/quit":
2785
+ return { action: "quit" };
2786
+ default:
2787
+ store.addMessage({ role: "system", content: `Unknown command: ${cmd}. Type /help for available commands.` });
2788
+ }
2789
+ return null;
2790
+ }
2791
+ module2.exports = { handleSlashCommand };
2792
+ });
2793
+ var NARROW_THRESHOLD = 60;
2794
+ function useLayout() {
2795
+ const { width } = useTerminalDimensions();
2796
+ const w2 = width || 80;
2797
+ const isNarrow = w2 < NARROW_THRESHOLD;
2798
+ return {
2799
+ width: w2,
2800
+ isNarrow,
2801
+ indent: isNarrow ? 2 : 4,
2802
+ smallIndent: isNarrow ? 1 : 2
2803
+ };
2804
+ }
2805
+ var import_react11 = __toESM(require_react(), 1);
2806
+ var import_store = __toESM(require_store(), 1);
2807
+ function useStore() {
2808
+ return import_react11.useSyncExternalStore(import_store.subscribe, import_store.getSnapshot);
2809
+ }
2810
+ var import_react13 = __toESM(require_react(), 1);
2811
+ var import_theme = __toESM(require_theme(), 1);
2812
+ var import_config = __toESM(require_config(), 1);
2813
+ var jsx_runtime = __toESM(require_jsx_runtime(), 1);
2814
+ var path2 = __require("path");
2815
+ var { execSync } = __require("child_process");
2816
+ function Header() {
2817
+ const [branch, setBranch] = import_react13.useState("");
2818
+ const { isNarrow } = useLayout();
2819
+ const cwd = path2.basename(import_config.PROJECT_ROOT);
2820
+ import_react13.useEffect(() => {
2821
+ try {
2822
+ const b2 = execSync("git rev-parse --abbrev-ref HEAD 2>/dev/null", {
2823
+ encoding: "utf-8",
2824
+ cwd: import_config.PROJECT_ROOT
2825
+ }).trim();
2826
+ setBranch(b2);
2827
+ } catch {}
2828
+ }, []);
2829
+ return /* @__PURE__ */ jsx_runtime.jsx("box", {
2830
+ style: { flexDirection: "row", paddingLeft: 1, paddingRight: 1 },
2831
+ children: /* @__PURE__ */ jsx_runtime.jsxs("text", {
2832
+ children: [
2833
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
2834
+ fg: import_theme.colors.primary,
2835
+ attributes: TextAttributes.BOLD,
2836
+ children: "\u26A1 Apex"
2837
+ }),
2838
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
2839
+ fg: import_theme.colors.dim,
2840
+ children: " "
2841
+ }),
2842
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
2843
+ fg: import_theme.colors.accent,
2844
+ children: "[max]"
2845
+ }),
2846
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
2847
+ fg: import_theme.colors.dim,
2848
+ children: " "
2849
+ }),
2850
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
2851
+ fg: import_theme.colors.muted,
2852
+ children: isNarrow && cwd.length > 12 ? cwd.slice(0, 12) + "\u2026" : cwd
2853
+ }),
2854
+ branch && !isNarrow ? /* @__PURE__ */ jsx_runtime.jsxs(jsx_runtime.Fragment, {
2855
+ children: [
2856
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
2857
+ fg: import_theme.colors.dim,
2858
+ children: " on "
2859
+ }),
2860
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
2861
+ fg: import_theme.colors.text,
2862
+ children: branch
2863
+ })
2864
+ ]
2865
+ }) : null
2866
+ ]
2867
+ })
2868
+ });
2869
+ }
2870
+ var import_theme2 = __toESM(require_theme(), 1);
2871
+ var jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
2872
+ function Divider() {
2873
+ const { width } = useLayout();
2874
+ const cols = Math.min(width, 120);
2875
+ return /* @__PURE__ */ jsx_runtime2.jsx("text", {
2876
+ fg: import_theme2.colors.dim,
2877
+ content: "\u2500".repeat(cols)
2878
+ });
2879
+ }
2880
+ var import_theme3 = __toESM(require_theme(), 1);
2881
+ var import_config2 = __toESM(require_config(), 1);
2882
+ var jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
2883
+ function Welcome() {
2884
+ const { isNarrow } = useLayout();
2885
+ return /* @__PURE__ */ jsx_runtime3.jsxs("box", {
2886
+ style: { flexDirection: "column", paddingLeft: 1, marginTop: 1 },
2887
+ children: [
2888
+ /* @__PURE__ */ jsx_runtime3.jsx("text", {
2889
+ fg: import_theme3.colors.white,
2890
+ attributes: TextAttributes.BOLD,
2891
+ content: "How can I help?"
2892
+ }),
2893
+ /* @__PURE__ */ jsx_runtime3.jsx("text", {
2894
+ fg: import_theme3.colors.dim,
2895
+ content: isNarrow ? `Max ${import_config2.MAX_TOOL_ITERATIONS} iterations` : `Tools available \xB7 Max ${import_config2.MAX_TOOL_ITERATIONS} iterations per turn`
2896
+ })
2897
+ ]
2898
+ });
2899
+ }
2900
+ var import_theme4 = __toESM(require_theme(), 1);
2901
+ var jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
2902
+ function UserMessage({ content }) {
2903
+ return /* @__PURE__ */ jsx_runtime4.jsxs("box", {
2904
+ style: { flexDirection: "column", paddingLeft: 1, marginTop: 1 },
2905
+ children: [
2906
+ /* @__PURE__ */ jsx_runtime4.jsx("text", {
2907
+ fg: import_theme4.colors.blue,
2908
+ attributes: TextAttributes.BOLD,
2909
+ content: "You"
2910
+ }),
2911
+ /* @__PURE__ */ jsx_runtime4.jsx("text", {
2912
+ fg: import_theme4.colors.text,
2913
+ content: content || ""
2914
+ })
2915
+ ]
2916
+ });
2917
+ }
2918
+ var import_theme5 = __toESM(require_theme(), 1);
2919
+ var jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
2920
+ function AssistantMessage({ content, isStreaming }) {
2921
+ const { indent, isNarrow, width } = useLayout();
2922
+ const codeIndent = isNarrow ? 1 : 2;
2923
+ const separatorWidth = Math.min(width - indent - codeIndent, isNarrow ? 40 : 60);
2924
+ if (!content)
2925
+ return null;
2926
+ const lines = content.split(`
2927
+ `);
2928
+ const rendered = [];
2929
+ let inCodeBlock = false;
2930
+ let codeLines = [];
2931
+ let codeLang = "";
2932
+ for (let i = 0;i < lines.length; i++) {
2933
+ const line = lines[i];
2934
+ if (line.startsWith("```") && !inCodeBlock) {
2935
+ inCodeBlock = true;
2936
+ codeLang = line.slice(3).trim() || "code";
2937
+ codeLines = [];
2938
+ } else if (line.startsWith("```") && inCodeBlock) {
2939
+ inCodeBlock = false;
2940
+ rendered.push(/* @__PURE__ */ jsx_runtime5.jsxs("box", {
2941
+ style: { flexDirection: "column", paddingLeft: codeIndent, marginTop: 0 },
2942
+ children: [
2943
+ /* @__PURE__ */ jsx_runtime5.jsx("text", {
2944
+ fg: import_theme5.colors.dim,
2945
+ content: `\u2500\u2500 ${codeLang} \u2500\u2500`
2946
+ }),
2947
+ codeLines.map((cl, j2) => /* @__PURE__ */ jsx_runtime5.jsxs("text", {
2948
+ children: [
2949
+ /* @__PURE__ */ jsx_runtime5.jsx("span", {
2950
+ fg: import_theme5.colors.dim,
2951
+ children: String(j2 + 1).padStart(isNarrow ? 2 : 3) + " \u2502 "
2952
+ }),
2953
+ /* @__PURE__ */ jsx_runtime5.jsx("span", {
2954
+ fg: import_theme5.colors.text,
2955
+ children: cl
2956
+ })
2957
+ ]
2958
+ }, j2)),
2959
+ /* @__PURE__ */ jsx_runtime5.jsx("text", {
2960
+ fg: import_theme5.colors.dim,
2961
+ content: "\u2500".repeat(Math.max(separatorWidth, 10))
2962
+ })
2963
+ ]
2964
+ }, `code-${i}`));
2965
+ } else if (inCodeBlock) {
2966
+ codeLines.push(line);
2967
+ } else {
2968
+ const processed = line.replace(/`([^`]+)`/g, "\xAB$1\xBB");
2969
+ if (processed.includes("\xAB")) {
2970
+ const parts = processed.split(/\u00AB|\u00BB/);
2971
+ rendered.push(/* @__PURE__ */ jsx_runtime5.jsx("text", {
2972
+ children: parts.map((part, j2) => j2 % 2 === 0 ? /* @__PURE__ */ jsx_runtime5.jsx("span", {
2973
+ fg: import_theme5.colors.text,
2974
+ children: part
2975
+ }, j2) : /* @__PURE__ */ jsx_runtime5.jsx("span", {
2976
+ fg: import_theme5.colors.cyan,
2977
+ children: part
2978
+ }, j2))
2979
+ }, `line-${i}`));
2980
+ } else {
2981
+ rendered.push(/* @__PURE__ */ jsx_runtime5.jsx("text", {
2982
+ children: /* @__PURE__ */ jsx_runtime5.jsx("span", {
2983
+ fg: import_theme5.colors.text,
2984
+ children: line
2985
+ })
2986
+ }, `line-${i}`));
2987
+ }
2988
+ }
2989
+ }
2990
+ if (inCodeBlock && codeLines.length > 0) {
2991
+ rendered.push(/* @__PURE__ */ jsx_runtime5.jsxs("box", {
2992
+ style: { flexDirection: "column", paddingLeft: codeIndent },
2993
+ children: [
2994
+ /* @__PURE__ */ jsx_runtime5.jsx("text", {
2995
+ fg: import_theme5.colors.dim,
2996
+ content: `\u2500\u2500 ${codeLang} \u2500\u2500`
2997
+ }),
2998
+ codeLines.map((cl, j2) => /* @__PURE__ */ jsx_runtime5.jsxs("text", {
2999
+ children: [
3000
+ /* @__PURE__ */ jsx_runtime5.jsx("span", {
3001
+ fg: import_theme5.colors.dim,
3002
+ children: String(j2 + 1).padStart(isNarrow ? 2 : 3) + " \u2502 "
3003
+ }),
3004
+ /* @__PURE__ */ jsx_runtime5.jsx("span", {
3005
+ fg: import_theme5.colors.text,
3006
+ children: cl
3007
+ })
3008
+ ]
3009
+ }, j2))
3010
+ ]
3011
+ }, "code-tail"));
3012
+ }
3013
+ return /* @__PURE__ */ jsx_runtime5.jsxs("box", {
3014
+ style: { flexDirection: "column", paddingLeft: indent },
3015
+ children: [
3016
+ rendered,
3017
+ isStreaming ? /* @__PURE__ */ jsx_runtime5.jsx("text", {
3018
+ fg: import_theme5.colors.accent,
3019
+ content: "\u258A"
3020
+ }) : null
3021
+ ]
3022
+ });
3023
+ }
3024
+ var import_theme6 = __toESM(require_theme(), 1);
3025
+ var jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
3026
+ function ThinkBlock({ content, expanded, onToggle }) {
3027
+ const { isNarrow, smallIndent } = useLayout();
3028
+ if (!content)
3029
+ return null;
3030
+ const lines = content.split(`
3031
+ `);
3032
+ const maxPreview = isNarrow ? 2 : 4;
3033
+ const displayLines = expanded ? lines : lines.slice(0, maxPreview);
3034
+ const isTruncated = !expanded && lines.length > maxPreview;
3035
+ return /* @__PURE__ */ jsx_runtime6.jsxs("box", {
3036
+ style: { flexDirection: "column", paddingLeft: smallIndent, marginTop: 0 },
3037
+ onMouseDown: onToggle,
3038
+ children: [
3039
+ /* @__PURE__ */ jsx_runtime6.jsx("text", {
3040
+ fg: import_theme6.colors.dim,
3041
+ attributes: TextAttributes.ITALIC,
3042
+ content: "\u25B8 Thinking"
3043
+ }),
3044
+ displayLines.map((line, i) => line.trim() ? /* @__PURE__ */ jsx_runtime6.jsx("text", {
3045
+ fg: import_theme6.colors.dim,
3046
+ attributes: TextAttributes.ITALIC,
3047
+ style: { paddingLeft: smallIndent },
3048
+ content: line
3049
+ }, i) : null),
3050
+ isTruncated ? /* @__PURE__ */ jsx_runtime6.jsx("text", {
3051
+ fg: import_theme6.colors.dim,
3052
+ attributes: TextAttributes.ITALIC,
3053
+ style: { paddingLeft: smallIndent },
3054
+ content: isNarrow ? `+${lines.length - maxPreview} more (tap)` : `... +${lines.length - maxPreview} more lines (click to expand)`
3055
+ }) : null
3056
+ ]
3057
+ });
3058
+ }
3059
+ var import_theme8 = __toESM(require_theme(), 1);
3060
+ var import_store2 = __toESM(require_store(), 1);
3061
+ var jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
3062
+ var SUBAGENT_TOOLS = new Set([
3063
+ "FilePickerMax",
3064
+ "Thinker",
3065
+ "ThinkerBestOfN",
3066
+ "EditorMultiPrompt",
3067
+ "CodeReview",
3068
+ "CodeReviewMulti",
3069
+ "Commander",
3070
+ "ContextPruner",
3071
+ "ResearcherWeb",
3072
+ "ResearcherDocs",
3073
+ "GeneralAgent"
3074
+ ]);
3075
+ function formatElapsed(ms) {
3076
+ return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
3077
+ }
3078
+ function truncate(str, len) {
3079
+ return str.length > len ? str.slice(0, len - 3) + "..." : str;
3080
+ }
3081
+ function ToolCallItem({ message }) {
3082
+ const { indent, isNarrow } = useLayout();
3083
+ const truncLen = isNarrow ? 30 : 50;
3084
+ const { id, name, detail, status, success, elapsed, output, expanded } = message;
3085
+ const isRunning = status === "running" || status === "pending";
3086
+ const isSubagent = SUBAGENT_TOOLS.has(name);
3087
+ return /* @__PURE__ */ jsx_runtime8.jsxs("box", {
3088
+ style: { flexDirection: "column", paddingLeft: indent },
3089
+ onMouseDown: () => import_store2.toggleMessageExpanded(id),
3090
+ children: [
3091
+ /* @__PURE__ */ jsx_runtime8.jsx("box", {
3092
+ style: { flexDirection: "row" },
3093
+ children: isRunning ? /* @__PURE__ */ jsx_runtime8.jsx(Spinner, {
3094
+ label: `[${name}] ${truncate(detail || "...", truncLen)}`
3095
+ }) : /* @__PURE__ */ jsx_runtime8.jsxs("text", {
3096
+ children: [
3097
+ /* @__PURE__ */ jsx_runtime8.jsx("span", {
3098
+ fg: success ? import_theme8.colors.green : import_theme8.colors.red,
3099
+ children: success ? "\u2713" : "\u2717"
3100
+ }),
3101
+ isSubagent ? /* @__PURE__ */ jsx_runtime8.jsx("span", {
3102
+ fg: import_theme8.colors.dim,
3103
+ children: expanded ? " \u25BE" : " \u25B8"
3104
+ }) : null,
3105
+ /* @__PURE__ */ jsx_runtime8.jsx("span", {
3106
+ fg: import_theme8.colors.dim,
3107
+ children: " ["
3108
+ }),
3109
+ /* @__PURE__ */ jsx_runtime8.jsx("span", {
3110
+ fg: import_theme8.colors.accent,
3111
+ children: name
3112
+ }),
3113
+ /* @__PURE__ */ jsx_runtime8.jsx("span", {
3114
+ fg: import_theme8.colors.dim,
3115
+ children: "] "
3116
+ }),
3117
+ /* @__PURE__ */ jsx_runtime8.jsx("span", {
3118
+ fg: import_theme8.colors.dim,
3119
+ children: truncate(detail || "", truncLen)
3120
+ }),
3121
+ elapsed != null ? /* @__PURE__ */ jsx_runtime8.jsx("span", {
3122
+ fg: import_theme8.colors.dim,
3123
+ children: " " + formatElapsed(elapsed)
3124
+ }) : null
3125
+ ]
3126
+ })
3127
+ }),
3128
+ expanded && output ? (() => {
3129
+ const lines = output.split(`
3130
+ `);
3131
+ const maxLines = 20;
3132
+ const isTruncated = lines.length > maxLines;
3133
+ const displayOutput = isTruncated ? lines.slice(0, maxLines).join(`
3134
+ `) + `
3135
+ ...` : output;
3136
+ return isSubagent ? /* @__PURE__ */ jsx_runtime8.jsxs("box", {
3137
+ style: { flexDirection: "column", paddingLeft: indent, marginTop: 0, borderStyle: "single", borderColor: import_theme8.colors.border, paddingRight: 1 },
3138
+ children: [
3139
+ /* @__PURE__ */ jsx_runtime8.jsx("text", {
3140
+ fg: import_theme8.colors.dim,
3141
+ attributes: TextAttributes.ITALIC,
3142
+ style: { marginBottom: 0 },
3143
+ children: `\u2500\u2500 ${name} output ${isTruncated ? `(${lines.length} lines, showing ${maxLines})` : ""} \u2500\u2500`
3144
+ }),
3145
+ /* @__PURE__ */ jsx_runtime8.jsx("text", {
3146
+ fg: import_theme8.colors.text,
3147
+ content: displayOutput,
3148
+ wrapMode: "char"
3149
+ })
3150
+ ]
3151
+ }) : /* @__PURE__ */ jsx_runtime8.jsx("box", {
3152
+ style: { paddingLeft: indent, marginTop: 0 },
3153
+ children: /* @__PURE__ */ jsx_runtime8.jsx("text", {
3154
+ fg: import_theme8.colors.dim,
3155
+ content: truncate(displayOutput, 1000),
3156
+ wrapMode: "char"
3157
+ })
3158
+ });
3159
+ })() : null
3160
+ ]
3161
+ });
3162
+ }
3163
+ var import_react14 = __toESM(require_react(), 1);
3164
+ var import_theme7 = __toESM(require_theme(), 1);
3165
+ var jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
3166
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3167
+ function Spinner({ label }) {
3168
+ const [frame, setFrame] = import_react14.useState(0);
3169
+ const timerRef = import_react14.useRef(null);
3170
+ import_react14.useEffect(() => {
3171
+ timerRef.current = setInterval(() => {
3172
+ setFrame((f) => (f + 1) % FRAMES.length);
3173
+ }, 80);
3174
+ return () => clearInterval(timerRef.current);
3175
+ }, []);
3176
+ return /* @__PURE__ */ jsx_runtime7.jsxs("text", {
3177
+ children: [
3178
+ /* @__PURE__ */ jsx_runtime7.jsx("span", {
3179
+ fg: import_theme7.colors.accent,
3180
+ children: FRAMES[frame]
3181
+ }),
3182
+ label ? /* @__PURE__ */ jsx_runtime7.jsx("span", {
3183
+ fg: import_theme7.colors.dim,
3184
+ children: " " + label
3185
+ }) : null
3186
+ ]
3187
+ });
3188
+ }
3189
+ var import_theme9 = __toESM(require_theme(), 1);
3190
+ var jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
3191
+ var path3 = __require("path");
3192
+ function DiffView({ filename, content }) {
3193
+ const { indent } = useLayout();
3194
+ if (!content)
3195
+ return null;
3196
+ const lines = content.split(`
3197
+ `);
3198
+ return /* @__PURE__ */ jsx_runtime9.jsxs("box", {
3199
+ style: { flexDirection: "column", paddingLeft: indent },
3200
+ children: [
3201
+ /* @__PURE__ */ jsx_runtime9.jsx("text", {
3202
+ fg: import_theme9.colors.text,
3203
+ attributes: TextAttributes.BOLD,
3204
+ content: path3.basename(filename || "")
3205
+ }),
3206
+ lines.map((line, i) => {
3207
+ if (line.startsWith("+")) {
3208
+ return /* @__PURE__ */ jsx_runtime9.jsx("text", {
3209
+ fg: import_theme9.colors.green,
3210
+ content: line
3211
+ }, i);
3212
+ }
3213
+ if (line.startsWith("-")) {
3214
+ return /* @__PURE__ */ jsx_runtime9.jsx("text", {
3215
+ fg: import_theme9.colors.red,
3216
+ content: line
3217
+ }, i);
3218
+ }
3219
+ return null;
3220
+ })
3221
+ ]
3222
+ });
3223
+ }
3224
+ var import_theme10 = __toESM(require_theme(), 1);
3225
+ var import_store3 = __toESM(require_store(), 1);
3226
+ var jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
3227
+ function SystemMessage({ message }) {
3228
+ const { isNarrow, smallIndent } = useLayout();
3229
+ const { id, content = "", label, expanded } = message;
3230
+ if (!content)
3231
+ return null;
3232
+ const lines = content.split(`
3233
+ `);
3234
+ const maxPreview = isNarrow ? 3 : 6;
3235
+ const displayLines = expanded ? lines : lines.slice(0, maxPreview);
3236
+ const isTruncated = !expanded && lines.length > maxPreview;
3237
+ return /* @__PURE__ */ jsx_runtime10.jsxs("box", {
3238
+ style: { flexDirection: "column", paddingLeft: smallIndent, marginTop: 1 },
3239
+ onMouseDown: () => import_store3.toggleMessageExpanded(id),
3240
+ children: [
3241
+ label ? /* @__PURE__ */ jsx_runtime10.jsx("text", {
3242
+ fg: import_theme10.colors.accent,
3243
+ attributes: TextAttributes.BOLD,
3244
+ content: label
3245
+ }) : null,
3246
+ displayLines.map((line, i) => /* @__PURE__ */ jsx_runtime10.jsx("text", {
3247
+ fg: import_theme10.colors.muted,
3248
+ content: line
3249
+ }, i)),
3250
+ isTruncated ? /* @__PURE__ */ jsx_runtime10.jsx("text", {
3251
+ fg: import_theme10.colors.dim,
3252
+ content: isNarrow ? `+${lines.length - maxPreview} more (tap)` : `... +${lines.length - maxPreview} more lines (click to expand)`
3253
+ }) : null
3254
+ ]
3255
+ });
3256
+ }
3257
+ var import_react2 = __toESM(require_react(), 1);
3258
+ var import_theme11 = __toESM(require_theme(), 1);
3259
+ var import_store4 = __toESM(require_store(), 1);
3260
+ var jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
3261
+ function MessageItem({ message }) {
3262
+ const { width } = useLayout();
3263
+ switch (message.role) {
3264
+ case "user":
3265
+ return /* @__PURE__ */ jsx_runtime11.jsx(UserMessage, {
3266
+ content: message.content
3267
+ });
3268
+ case "assistant":
3269
+ return /* @__PURE__ */ jsx_runtime11.jsxs("box", {
3270
+ style: { flexDirection: "column", marginTop: 1 },
3271
+ children: [
3272
+ /* @__PURE__ */ jsx_runtime11.jsx("text", {
3273
+ fg: import_theme11.colors.primary,
3274
+ attributes: TextAttributes.BOLD,
3275
+ style: { paddingLeft: 1 },
3276
+ content: "Apex"
3277
+ }),
3278
+ /* @__PURE__ */ jsx_runtime11.jsx(AssistantMessage, {
3279
+ content: message.content
3280
+ })
3281
+ ]
3282
+ });
3283
+ case "thinking":
3284
+ return /* @__PURE__ */ jsx_runtime11.jsx(ThinkBlock, {
3285
+ content: message.content,
3286
+ expanded: message.expanded,
3287
+ onToggle: () => import_store4.toggleMessageExpanded(message.id)
3288
+ });
3289
+ case "tool":
3290
+ return /* @__PURE__ */ jsx_runtime11.jsx(ToolCallItem, {
3291
+ message
3292
+ });
3293
+ case "diff":
3294
+ return /* @__PURE__ */ jsx_runtime11.jsx(DiffView, {
3295
+ filename: message.filename,
3296
+ content: message.content
3297
+ });
3298
+ case "system":
3299
+ return /* @__PURE__ */ jsx_runtime11.jsx(SystemMessage, {
3300
+ message
3301
+ });
3302
+ case "divider":
3303
+ return /* @__PURE__ */ jsx_runtime11.jsx("text", {
3304
+ fg: import_theme11.colors.dim,
3305
+ style: { paddingLeft: 1 },
3306
+ content: "\u2500".repeat(Math.max(width - 2, 10))
3307
+ });
3308
+ default:
3309
+ return null;
3310
+ }
3311
+ }
3312
+ function ChatArea({ messages, streamingContent, streamingThinking, isProcessing }) {
3313
+ const { indent } = useLayout();
3314
+ const renderedMessages = import_react2.useMemo(() => messages.map((msg) => /* @__PURE__ */ jsx_runtime11.jsx(MessageItem, {
3315
+ message: msg
3316
+ }, msg.id)), [messages]);
3317
+ return /* @__PURE__ */ jsx_runtime11.jsx("scrollbox", {
3318
+ style: { flexGrow: 1 },
3319
+ focused: true,
3320
+ stickyScroll: true,
3321
+ stickyStart: "bottom",
3322
+ scrollY: true,
3323
+ key: "main-chat-scroll",
3324
+ children: /* @__PURE__ */ jsx_runtime11.jsxs("box", {
3325
+ style: { flexDirection: "column" },
3326
+ children: [
3327
+ /* @__PURE__ */ jsx_runtime11.jsx(Welcome, {}),
3328
+ renderedMessages,
3329
+ streamingThinking ? /* @__PURE__ */ jsx_runtime11.jsx("box", {
3330
+ style: { paddingLeft: 2, marginTop: 0 },
3331
+ children: /* @__PURE__ */ jsx_runtime11.jsxs("text", {
3332
+ fg: import_theme11.colors.dim,
3333
+ attributes: TextAttributes.ITALIC,
3334
+ children: [
3335
+ /* @__PURE__ */ jsx_runtime11.jsx("span", {
3336
+ fg: import_theme11.colors.dim,
3337
+ children: "\u25B8 Thinking: "
3338
+ }),
3339
+ /* @__PURE__ */ jsx_runtime11.jsx("span", {
3340
+ fg: import_theme11.colors.dim,
3341
+ children: streamingThinking.slice(-100)
3342
+ })
3343
+ ]
3344
+ })
3345
+ }) : null,
3346
+ streamingContent ? /* @__PURE__ */ jsx_runtime11.jsx("box", {
3347
+ style: { flexDirection: "column", marginTop: 0 },
3348
+ children: /* @__PURE__ */ jsx_runtime11.jsx(AssistantMessage, {
3349
+ content: streamingContent,
3350
+ isStreaming: true
3351
+ })
3352
+ }) : null,
3353
+ isProcessing && !streamingContent && !streamingThinking ? /* @__PURE__ */ jsx_runtime11.jsx("box", {
3354
+ style: { paddingLeft: indent, marginTop: 1 },
3355
+ children: /* @__PURE__ */ jsx_runtime11.jsx(Spinner, {
3356
+ label: "Reasoning..."
3357
+ })
3358
+ }) : null,
3359
+ /* @__PURE__ */ jsx_runtime11.jsx("box", {
3360
+ style: { height: 1 }
3361
+ })
3362
+ ]
3363
+ })
3364
+ });
3365
+ }
3366
+ var import_react15 = __toESM(require_react(), 1);
3367
+ var import_theme12 = __toESM(require_theme(), 1);
3368
+ var jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
3369
+ function InputBar({ disabled, onSubmit }) {
3370
+ const inputRef = import_react15.useRef(null);
3371
+ const { isNarrow } = useLayout();
3372
+ const handleSubmit = (value) => {
3373
+ const trimmed = value.trim();
3374
+ if (!trimmed)
3375
+ return;
3376
+ if (inputRef.current)
3377
+ inputRef.current.value = "";
3378
+ onSubmit(trimmed);
3379
+ };
3380
+ return /* @__PURE__ */ jsx_runtime12.jsxs("box", {
3381
+ style: { flexDirection: "column" },
3382
+ children: [
3383
+ /* @__PURE__ */ jsx_runtime12.jsx("text", {
3384
+ fg: import_theme12.colors.dim,
3385
+ style: { paddingLeft: isNarrow ? 1 : 2 },
3386
+ content: isNarrow ? "^C exit \xB7 /help" : "Ctrl+C to exit \xB7 /help for commands"
3387
+ }),
3388
+ /* @__PURE__ */ jsx_runtime12.jsxs("box", {
3389
+ style: { flexDirection: "row", paddingLeft: 1 },
3390
+ children: [
3391
+ /* @__PURE__ */ jsx_runtime12.jsx("text", {
3392
+ fg: import_theme12.colors.primary,
3393
+ content: "\u276F "
3394
+ }),
3395
+ /* @__PURE__ */ jsx_runtime12.jsx("input", {
3396
+ ref: inputRef,
3397
+ focused: !disabled,
3398
+ placeholder: disabled ? "Processing..." : "Type a message...",
3399
+ onSubmit: handleSubmit,
3400
+ fg: import_theme12.colors.text,
3401
+ style: { flexGrow: 1 }
3402
+ })
3403
+ ]
3404
+ })
3405
+ ]
3406
+ });
3407
+ }
3408
+ var import_theme13 = __toESM(require_theme(), 1);
3409
+ var import_config3 = __toESM(require_config(), 1);
3410
+ var jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
3411
+ function StatusBar({ isProcessing }) {
3412
+ const { isNarrow } = useLayout();
3413
+ const elapsed = ((Date.now() - import_config3.session.startTime) / 1000 / 60).toFixed(1);
3414
+ return /* @__PURE__ */ jsx_runtime13.jsx("box", {
3415
+ style: { flexDirection: "row", paddingLeft: isNarrow ? 1 : 2, paddingRight: isNarrow ? 1 : 2 },
3416
+ children: /* @__PURE__ */ jsx_runtime13.jsxs("text", {
3417
+ children: [
3418
+ /* @__PURE__ */ jsx_runtime13.jsxs("span", {
3419
+ fg: import_theme13.colors.dim,
3420
+ children: [
3421
+ elapsed,
3422
+ "min"
3423
+ ]
3424
+ }),
3425
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
3426
+ fg: import_theme13.colors.dim,
3427
+ children: " \xB7 "
3428
+ }),
3429
+ /* @__PURE__ */ jsx_runtime13.jsxs("span", {
3430
+ fg: import_theme13.colors.dim,
3431
+ children: [
3432
+ import_config3.session.turnCount,
3433
+ " turns"
3434
+ ]
3435
+ }),
3436
+ !isNarrow ? /* @__PURE__ */ jsx_runtime13.jsxs(jsx_runtime13.Fragment, {
3437
+ children: [
3438
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
3439
+ fg: import_theme13.colors.dim,
3440
+ children: " \xB7 "
3441
+ }),
3442
+ /* @__PURE__ */ jsx_runtime13.jsxs("span", {
3443
+ fg: import_theme13.colors.dim,
3444
+ children: [
3445
+ import_config3.session.toolCallCount,
3446
+ " tools"
3447
+ ]
3448
+ })
3449
+ ]
3450
+ }) : null,
3451
+ !isNarrow ? /* @__PURE__ */ jsx_runtime13.jsxs(jsx_runtime13.Fragment, {
3452
+ children: [
3453
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
3454
+ fg: import_theme13.colors.dim,
3455
+ children: " \xB7 "
3456
+ }),
3457
+ /* @__PURE__ */ jsx_runtime13.jsxs("span", {
3458
+ fg: import_theme13.colors.dim,
3459
+ children: [
3460
+ import_config3.session.totalTokens.toLocaleString(),
3461
+ " tok"
3462
+ ]
3463
+ })
3464
+ ]
3465
+ }) : null,
3466
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
3467
+ fg: import_theme13.colors.dim,
3468
+ children: " \xB7 "
3469
+ }),
3470
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
3471
+ fg: import_theme13.colors.dim,
3472
+ children: "$" + import_config3.session.totalCost.toFixed(4)
3473
+ }),
3474
+ import_config3.session.filesModified.size > 0 && !isNarrow ? /* @__PURE__ */ jsx_runtime13.jsxs(jsx_runtime13.Fragment, {
3475
+ children: [
3476
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
3477
+ fg: import_theme13.colors.dim,
3478
+ children: " \xB7 "
3479
+ }),
3480
+ /* @__PURE__ */ jsx_runtime13.jsxs("span", {
3481
+ fg: import_theme13.colors.yellow,
3482
+ children: [
3483
+ import_config3.session.filesModified.size,
3484
+ " files modified"
3485
+ ]
3486
+ })
3487
+ ]
3488
+ }) : null,
3489
+ isProcessing ? /* @__PURE__ */ jsx_runtime13.jsxs(jsx_runtime13.Fragment, {
3490
+ children: [
3491
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
3492
+ fg: import_theme13.colors.dim,
3493
+ children: " \xB7 "
3494
+ }),
3495
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
3496
+ fg: import_theme13.colors.accent,
3497
+ children: isNarrow ? "..." : "processing"
3498
+ })
3499
+ ]
3500
+ }) : null
3501
+ ]
3502
+ })
3503
+ });
3504
+ }
3505
+ var import_theme14 = __toESM(require_theme(), 1);
3506
+ var jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
3507
+ var COMMANDS = [
3508
+ { cmd: "/help", desc: "Show this menu" },
3509
+ { cmd: "/compact", desc: "Compact/summarize conversation context" },
3510
+ { cmd: "/files", desc: "Show project file tree" },
3511
+ { cmd: "/clear", desc: "Clear conversation" },
3512
+ { cmd: "/cost", desc: "Show session stats" },
3513
+ { cmd: "/undo", desc: "Undo last edit" },
3514
+ { cmd: "/diff", desc: "Show git diff" },
3515
+ { cmd: "/git <cmd>", desc: "Run a git command" },
3516
+ { cmd: "/quit", desc: "Exit" }
3517
+ ];
3518
+ var TOOLS = [
3519
+ "Read",
3520
+ "Write",
3521
+ "Edit",
3522
+ "Patch",
3523
+ "Bash",
3524
+ "Grep",
3525
+ "Glob",
3526
+ "ListDir",
3527
+ "UndoEdit",
3528
+ "Task",
3529
+ "CodeReview"
3530
+ ];
3531
+ var SUBAGENTS = [
3532
+ "FilePickerMax",
3533
+ "Thinker",
3534
+ "ThinkerBestOfN*",
3535
+ "EditorMultiPrompt*",
3536
+ "CodeReviewMulti*",
3537
+ "Commander",
3538
+ "ContextPruner"
3539
+ ];
3540
+ function HelpModal({ onClose, onCommand }) {
3541
+ const { isNarrow } = useLayout();
3542
+ useKeyboard((key) => {
3543
+ if (key.name === "escape" || key.name === "q") {
3544
+ onClose();
3545
+ }
3546
+ });
3547
+ return /* @__PURE__ */ jsx_runtime14.jsxs("box", {
3548
+ zIndex: 100,
3549
+ border: true,
3550
+ borderColor: import_theme14.colors.primary,
3551
+ backgroundColor: "#0d0d1a",
3552
+ title: " Help ",
3553
+ titleAlignment: "center",
3554
+ style: {
3555
+ position: "absolute",
3556
+ top: 2,
3557
+ left: isNarrow ? 1 : 4,
3558
+ bottom: 2,
3559
+ right: isNarrow ? 1 : 4,
3560
+ padding: 1,
3561
+ flexDirection: "column"
3562
+ },
3563
+ children: [
3564
+ /* @__PURE__ */ jsx_runtime14.jsx("text", {
3565
+ fg: import_theme14.colors.white,
3566
+ attributes: TextAttributes.BOLD,
3567
+ content: "Commands"
3568
+ }),
3569
+ /* @__PURE__ */ jsx_runtime14.jsx("box", {
3570
+ style: { flexDirection: "column", marginTop: 0 },
3571
+ children: COMMANDS.map(({ cmd, desc }) => /* @__PURE__ */ jsx_runtime14.jsx("box", {
3572
+ style: { flexDirection: "row" },
3573
+ onMouseDown: () => {
3574
+ const slashCmd = cmd.split(" ")[0];
3575
+ if (onCommand && !cmd.includes("<"))
3576
+ onCommand(slashCmd);
3577
+ onClose();
3578
+ },
3579
+ children: /* @__PURE__ */ jsx_runtime14.jsxs("text", {
3580
+ children: [
3581
+ /* @__PURE__ */ jsx_runtime14.jsx("span", {
3582
+ fg: import_theme14.colors.accent,
3583
+ children: cmd.padEnd(isNarrow ? 10 : 14)
3584
+ }),
3585
+ /* @__PURE__ */ jsx_runtime14.jsx("span", {
3586
+ fg: import_theme14.colors.text,
3587
+ children: desc
3588
+ })
3589
+ ]
3590
+ })
3591
+ }, cmd))
3592
+ }),
3593
+ /* @__PURE__ */ jsx_runtime14.jsx("text", {
3594
+ fg: import_theme14.colors.white,
3595
+ attributes: TextAttributes.BOLD,
3596
+ style: { marginTop: 1 },
3597
+ content: "Tools"
3598
+ }),
3599
+ /* @__PURE__ */ jsx_runtime14.jsx("text", {
3600
+ fg: import_theme14.colors.dim,
3601
+ content: TOOLS.join(", ")
3602
+ }),
3603
+ /* @__PURE__ */ jsx_runtime14.jsx("text", {
3604
+ fg: import_theme14.colors.white,
3605
+ attributes: TextAttributes.BOLD,
3606
+ style: { marginTop: 1 },
3607
+ content: "Sub-Agents"
3608
+ }),
3609
+ /* @__PURE__ */ jsx_runtime14.jsx("text", {
3610
+ fg: import_theme14.colors.dim,
3611
+ content: SUBAGENTS.join(", ")
3612
+ }),
3613
+ /* @__PURE__ */ jsx_runtime14.jsx("text", {
3614
+ fg: import_theme14.colors.dim,
3615
+ content: " * = MAX mode only"
3616
+ }),
3617
+ /* @__PURE__ */ jsx_runtime14.jsx("text", {
3618
+ fg: import_theme14.colors.dim,
3619
+ style: { marginTop: 1 },
3620
+ content: "Press ESC or q to close"
3621
+ })
3622
+ ]
3623
+ });
3624
+ }
3625
+ var import_react17 = __toESM(require_react(), 1);
3626
+ var import_store5 = __toESM(require_store(), 1);
3627
+ var import_config4 = __toESM(require_config(), 1);
3628
+ var import_agent = __toESM(require_agent(), 1);
3629
+ var import_commands = __toESM(require_commands(), 1);
3630
+ var jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
3631
+ function exitApp() {
3632
+ const renderer = import_store5.getRenderer();
3633
+ if (renderer)
3634
+ renderer.destroy();
3635
+ const elapsed = ((Date.now() - import_config4.session.startTime) / 1000 / 60).toFixed(1);
3636
+ const parts = [
3637
+ `${elapsed} min`,
3638
+ `${import_config4.session.turnCount} turns`,
3639
+ `${import_config4.session.toolCallCount} tool calls`,
3640
+ `${import_config4.session.totalTokens.toLocaleString()} tokens`,
3641
+ `$${import_config4.session.totalCost.toFixed(4)}`
3642
+ ];
3643
+ if (import_config4.session.filesModified.size > 0)
3644
+ parts.push(`${import_config4.session.filesModified.size} files modified`);
3645
+ if (import_config4.session.commandsRun.length > 0)
3646
+ parts.push(`${import_config4.session.commandsRun.length} commands`);
3647
+ console.log(`
3648
+ Session: ${parts.join(" \xB7 ")}
3649
+ `);
3650
+ console.log(` Goodbye! \u2726
3651
+ `);
3652
+ process.exit(0);
3653
+ }
3654
+ function App() {
3655
+ const state = useStore();
3656
+ useKeyboard((key) => {
3657
+ if (key.ctrl && key.name === "c") {
3658
+ exitApp();
3659
+ }
3660
+ });
3661
+ const handleInput = import_react17.useCallback(async (value) => {
3662
+ if (value === "exit" || value === "quit") {
3663
+ exitApp();
3664
+ return;
3665
+ }
3666
+ if (value.startsWith("/")) {
3667
+ const result = await import_commands.handleSlashCommand(value);
3668
+ if (result?.action === "quit") {
3669
+ exitApp();
3670
+ }
3671
+ return;
3672
+ }
3673
+ import_agent.handleUserInput(value).catch((err) => {
3674
+ import_store5.addMessage({ role: "system", content: `Error: ${err.message}` });
3675
+ import_store5.setState({ isProcessing: false });
3676
+ });
3677
+ }, []);
3678
+ const handleHelpCommand = import_react17.useCallback((cmd) => {
3679
+ if (cmd) {
3680
+ import_commands.handleSlashCommand(cmd).then((result) => {
3681
+ if (result?.action === "quit")
3682
+ exitApp();
3683
+ });
3684
+ }
3685
+ }, []);
3686
+ return /* @__PURE__ */ jsx_runtime15.jsxs("box", {
3687
+ style: { flexDirection: "column", flexGrow: 1 },
3688
+ children: [
3689
+ /* @__PURE__ */ jsx_runtime15.jsx(Header, {}),
3690
+ /* @__PURE__ */ jsx_runtime15.jsx(Divider, {}),
3691
+ /* @__PURE__ */ jsx_runtime15.jsx(ChatArea, {
3692
+ messages: state.messages,
3693
+ streamingContent: state.streamingContent,
3694
+ streamingThinking: state.streamingThinking,
3695
+ isProcessing: state.isProcessing
3696
+ }),
3697
+ /* @__PURE__ */ jsx_runtime15.jsx(Divider, {}),
3698
+ /* @__PURE__ */ jsx_runtime15.jsx(StatusBar, {
3699
+ isProcessing: state.isProcessing
3700
+ }),
3701
+ /* @__PURE__ */ jsx_runtime15.jsx(InputBar, {
3702
+ disabled: state.isProcessing || state.showHelp,
3703
+ onSubmit: handleInput
3704
+ }),
3705
+ state.showHelp ? /* @__PURE__ */ jsx_runtime15.jsx(HelpModal, {
3706
+ onClose: () => import_store5.setState({ showHelp: false }),
3707
+ onCommand: handleHelpCommand
3708
+ }) : null
3709
+ ]
3710
+ });
3711
+ }
3712
+ var jsx_runtime_main = __toESM(require_jsx_runtime(), 1);
3713
+ var import_store_main = __toESM(require_store(), 1);
3714
+ async function main() {
3715
+ if (process.env.APEX_LOCAL_SERVER === "1") {
3716
+ const { startServer } = __toESM(require_server(), 1);
3717
+ await startServer();
3718
+ }
3719
+ const renderer = await createCliRenderer({
3720
+ useAlternateScreen: true,
3721
+ exitOnCtrlC: false,
3722
+ useMouse: true
3723
+ });
3724
+ import_store_main.setRenderer(renderer);
3725
+ const root = createRoot(renderer);
3726
+ root.render(jsx_runtime_main.jsx(App, {}));
3727
+ renderer.start();
3728
+ }
3729
+ main().catch((err) => {
3730
+ console.error("Failed to start Apex:", err);
3731
+ process.exit(1);
3732
+ });