@xagent/x-cli 1.1.74 → 1.1.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +1213 -214
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -16,12 +16,13 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
|
16
16
|
import axios from 'axios';
|
|
17
17
|
import { exec, execSync, spawn } from 'child_process';
|
|
18
18
|
import { promisify } from 'util';
|
|
19
|
-
import
|
|
19
|
+
import fs6, { writeFile } from 'fs/promises';
|
|
20
20
|
import * as ops6 from 'fs-extra';
|
|
21
21
|
import { parse } from '@typescript-eslint/typescript-estree';
|
|
22
22
|
import Fuse from 'fuse.js';
|
|
23
23
|
import { glob } from 'glob';
|
|
24
24
|
import { encoding_for_model, get_encoding } from 'tiktoken';
|
|
25
|
+
import * as readline from 'readline';
|
|
25
26
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
26
27
|
import { marked } from 'marked';
|
|
27
28
|
import TerminalRenderer from 'marked-terminal';
|
|
@@ -336,6 +337,131 @@ var init_config = __esm({
|
|
|
336
337
|
PREDEFINED_SERVERS = {};
|
|
337
338
|
}
|
|
338
339
|
});
|
|
340
|
+
|
|
341
|
+
// src/utils/context-loader.ts
|
|
342
|
+
var context_loader_exports = {};
|
|
343
|
+
__export(context_loader_exports, {
|
|
344
|
+
formatContextStatus: () => formatContextStatus,
|
|
345
|
+
loadContext: () => loadContext
|
|
346
|
+
});
|
|
347
|
+
function loadMarkdownDirectory(dirPath) {
|
|
348
|
+
if (!fs__default.existsSync(dirPath)) {
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
const files = fs__default.readdirSync(dirPath).filter((file) => file.endsWith(".md")).sort();
|
|
352
|
+
let content = "";
|
|
353
|
+
for (const file of files) {
|
|
354
|
+
const filePath = path7__default.join(dirPath, file);
|
|
355
|
+
try {
|
|
356
|
+
const fileContent = fs__default.readFileSync(filePath, "utf-8");
|
|
357
|
+
content += `
|
|
358
|
+
|
|
359
|
+
=== ${file} ===
|
|
360
|
+
|
|
361
|
+
${fileContent}`;
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.warn(`Failed to read ${filePath}:`, error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return content;
|
|
367
|
+
}
|
|
368
|
+
function extractDateFromFilename(filename) {
|
|
369
|
+
const match = filename.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
370
|
+
if (match) {
|
|
371
|
+
return new Date(match[1]);
|
|
372
|
+
}
|
|
373
|
+
return /* @__PURE__ */ new Date(0);
|
|
374
|
+
}
|
|
375
|
+
function summarizeContent(content, maxLength = MAX_SUMMARY_LENGTH) {
|
|
376
|
+
if (content.length <= maxLength) {
|
|
377
|
+
return content;
|
|
378
|
+
}
|
|
379
|
+
const truncated = content.substring(0, maxLength);
|
|
380
|
+
const lastNewline = truncated.lastIndexOf("\n\n");
|
|
381
|
+
if (lastNewline > maxLength * 0.8) {
|
|
382
|
+
return truncated.substring(0, lastNewline);
|
|
383
|
+
}
|
|
384
|
+
return truncated + "\n\n[...content truncated for context budget...]";
|
|
385
|
+
}
|
|
386
|
+
function loadTaskFiles(tasksDir, maxBudget) {
|
|
387
|
+
if (!fs__default.existsSync(tasksDir)) {
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
const files = fs__default.readdirSync(tasksDir).filter((file) => file.endsWith(".md")).map((filename) => {
|
|
391
|
+
const filePath = path7__default.join(tasksDir, filename);
|
|
392
|
+
const content = fs__default.readFileSync(filePath, "utf-8");
|
|
393
|
+
return {
|
|
394
|
+
filename,
|
|
395
|
+
content,
|
|
396
|
+
size: Buffer.byteLength(content, "utf-8"),
|
|
397
|
+
date: extractDateFromFilename(filename),
|
|
398
|
+
isSummarized: false
|
|
399
|
+
};
|
|
400
|
+
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
401
|
+
const result = [];
|
|
402
|
+
let usedBudget = 0;
|
|
403
|
+
for (const file of files) {
|
|
404
|
+
let finalContent = file.content;
|
|
405
|
+
let isSummarized = false;
|
|
406
|
+
if (usedBudget + file.size > maxBudget) {
|
|
407
|
+
finalContent = summarizeContent(file.content);
|
|
408
|
+
const summarizedSize = Buffer.byteLength(finalContent, "utf-8");
|
|
409
|
+
if (usedBudget + summarizedSize > maxBudget) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
usedBudget += summarizedSize;
|
|
413
|
+
isSummarized = true;
|
|
414
|
+
} else {
|
|
415
|
+
usedBudget += file.size;
|
|
416
|
+
}
|
|
417
|
+
result.push({
|
|
418
|
+
...file,
|
|
419
|
+
content: finalContent,
|
|
420
|
+
isSummarized
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
function loadContext(agentDir = ".agent") {
|
|
426
|
+
const systemContent = loadMarkdownDirectory(path7__default.join(agentDir, "system"));
|
|
427
|
+
const sopContent = loadMarkdownDirectory(path7__default.join(agentDir, "sop"));
|
|
428
|
+
const systemSize = Buffer.byteLength(systemContent, "utf-8");
|
|
429
|
+
const sopSize = Buffer.byteLength(sopContent, "utf-8");
|
|
430
|
+
const taskBudget = Math.max(0, CONTEXT_BUDGET_BYTES - systemSize - sopSize);
|
|
431
|
+
const tasks = loadTaskFiles(path7__default.join(agentDir, "tasks"), taskBudget);
|
|
432
|
+
const totalSize = systemSize + sopSize + tasks.reduce((sum, task) => sum + Buffer.byteLength(task.content, "utf-8"), 0);
|
|
433
|
+
const warnings = [];
|
|
434
|
+
if (totalSize > CONTEXT_BUDGET_BYTES) {
|
|
435
|
+
warnings.push(`Context size (${(totalSize / 1024).toFixed(1)}KB) exceeds budget (${CONTEXT_BUDGET_BYTES / 1024}KB)`);
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
system: systemContent,
|
|
439
|
+
sop: sopContent,
|
|
440
|
+
tasks,
|
|
441
|
+
totalSize,
|
|
442
|
+
warnings
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function formatContextStatus(pack) {
|
|
446
|
+
const taskCount = pack.tasks.length;
|
|
447
|
+
const summarizedCount = pack.tasks.filter((t) => t.isSummarized).length;
|
|
448
|
+
const sizeKB = (pack.totalSize / 1024).toFixed(1);
|
|
449
|
+
let status = `[x-cli] Context: loaded system docs, sop docs, ${taskCount} task docs (~${sizeKB} KB).`;
|
|
450
|
+
if (summarizedCount > 0) {
|
|
451
|
+
status += ` Summarized ${summarizedCount} older tasks for context budget.`;
|
|
452
|
+
}
|
|
453
|
+
if (pack.warnings.length > 0) {
|
|
454
|
+
status += ` Warnings: ${pack.warnings.join("; ")}`;
|
|
455
|
+
}
|
|
456
|
+
return status;
|
|
457
|
+
}
|
|
458
|
+
var CONTEXT_BUDGET_BYTES, MAX_SUMMARY_LENGTH;
|
|
459
|
+
var init_context_loader = __esm({
|
|
460
|
+
"src/utils/context-loader.ts"() {
|
|
461
|
+
CONTEXT_BUDGET_BYTES = 280 * 1024;
|
|
462
|
+
MAX_SUMMARY_LENGTH = 2e3;
|
|
463
|
+
}
|
|
464
|
+
});
|
|
339
465
|
var GrokClient = class {
|
|
340
466
|
constructor(apiKey, model, baseURL) {
|
|
341
467
|
this.currentModel = "grok-code-fast-1";
|
|
@@ -629,7 +755,7 @@ var MCPManager = class extends EventEmitter {
|
|
|
629
755
|
this.transports.set(config2.name, transport);
|
|
630
756
|
const client = new Client(
|
|
631
757
|
{
|
|
632
|
-
name: "
|
|
758
|
+
name: "x-cli",
|
|
633
759
|
version: "1.0.0"
|
|
634
760
|
},
|
|
635
761
|
{
|
|
@@ -5300,7 +5426,7 @@ var OperationHistoryTool = class {
|
|
|
5300
5426
|
files: fileSnapshots
|
|
5301
5427
|
},
|
|
5302
5428
|
metadata: {
|
|
5303
|
-
tool: "
|
|
5429
|
+
tool: "x-cli",
|
|
5304
5430
|
filesAffected: files,
|
|
5305
5431
|
operationSize: this.determineOperationSize(files, rollbackData),
|
|
5306
5432
|
...metadata
|
|
@@ -5585,7 +5711,7 @@ This action cannot be undone.`
|
|
|
5585
5711
|
}
|
|
5586
5712
|
}
|
|
5587
5713
|
snapshots.push(snapshot);
|
|
5588
|
-
} catch (
|
|
5714
|
+
} catch (_error) {
|
|
5589
5715
|
snapshots.push({
|
|
5590
5716
|
filePath: path7.resolve(filePath),
|
|
5591
5717
|
existed: false
|
|
@@ -5676,7 +5802,7 @@ This action cannot be undone.`
|
|
|
5676
5802
|
/**
|
|
5677
5803
|
* Perform redo operation
|
|
5678
5804
|
*/
|
|
5679
|
-
async performRedo(
|
|
5805
|
+
async performRedo(_entry) {
|
|
5680
5806
|
return {
|
|
5681
5807
|
success: false,
|
|
5682
5808
|
error: "Redo functionality requires storing forward changes - not yet implemented"
|
|
@@ -5735,7 +5861,7 @@ ${errors.join("\n")}`;
|
|
|
5735
5861
|
/**
|
|
5736
5862
|
* Undo refactor operation
|
|
5737
5863
|
*/
|
|
5738
|
-
async undoRefactorOperation(fileSnapshots,
|
|
5864
|
+
async undoRefactorOperation(fileSnapshots, _customData) {
|
|
5739
5865
|
return await this.undoFileOperations(fileSnapshots);
|
|
5740
5866
|
}
|
|
5741
5867
|
/**
|
|
@@ -5808,7 +5934,7 @@ ${errors.join("\n")}`;
|
|
|
5808
5934
|
/**
|
|
5809
5935
|
* Determine operation size
|
|
5810
5936
|
*/
|
|
5811
|
-
determineOperationSize(files,
|
|
5937
|
+
determineOperationSize(files, _rollbackData) {
|
|
5812
5938
|
if (files.length <= 3) return "small";
|
|
5813
5939
|
if (files.length <= 10) return "medium";
|
|
5814
5940
|
return "large";
|
|
@@ -5854,7 +5980,7 @@ ${errors.join("\n")}`;
|
|
|
5854
5980
|
}));
|
|
5855
5981
|
this.currentPosition = parsed.currentPosition || this.history.length - 1;
|
|
5856
5982
|
}
|
|
5857
|
-
} catch (
|
|
5983
|
+
} catch (_error) {
|
|
5858
5984
|
this.history = [];
|
|
5859
5985
|
this.currentPosition = -1;
|
|
5860
5986
|
}
|
|
@@ -5871,7 +5997,7 @@ ${errors.join("\n")}`;
|
|
|
5871
5997
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5872
5998
|
};
|
|
5873
5999
|
await ops6.promises.writeFile(this.historyFile, JSON.stringify(data, null, 2), "utf-8");
|
|
5874
|
-
} catch (
|
|
6000
|
+
} catch (_error) {
|
|
5875
6001
|
}
|
|
5876
6002
|
}
|
|
5877
6003
|
/**
|
|
@@ -7040,10 +7166,10 @@ var DependencyAnalyzerTool = class {
|
|
|
7040
7166
|
const circularDeps = [];
|
|
7041
7167
|
const visited = /* @__PURE__ */ new Set();
|
|
7042
7168
|
const visiting = /* @__PURE__ */ new Set();
|
|
7043
|
-
const dfs = (filePath,
|
|
7169
|
+
const dfs = (filePath, path32) => {
|
|
7044
7170
|
if (visiting.has(filePath)) {
|
|
7045
|
-
const cycleStart =
|
|
7046
|
-
const cycle =
|
|
7171
|
+
const cycleStart = path32.indexOf(filePath);
|
|
7172
|
+
const cycle = path32.slice(cycleStart).concat([filePath]);
|
|
7047
7173
|
circularDeps.push({
|
|
7048
7174
|
cycle: cycle.map((fp) => graph.nodes.get(fp)?.filePath || fp),
|
|
7049
7175
|
severity: cycle.length <= 2 ? "error" : "warning",
|
|
@@ -7059,7 +7185,7 @@ var DependencyAnalyzerTool = class {
|
|
|
7059
7185
|
if (node) {
|
|
7060
7186
|
for (const dependency of node.dependencies) {
|
|
7061
7187
|
if (graph.nodes.has(dependency)) {
|
|
7062
|
-
dfs(dependency, [...
|
|
7188
|
+
dfs(dependency, [...path32, filePath]);
|
|
7063
7189
|
}
|
|
7064
7190
|
}
|
|
7065
7191
|
}
|
|
@@ -8435,89 +8561,955 @@ ${body}
|
|
|
8435
8561
|
default: true
|
|
8436
8562
|
}
|
|
8437
8563
|
},
|
|
8438
|
-
required: ["operation"]
|
|
8564
|
+
required: ["operation"]
|
|
8565
|
+
};
|
|
8566
|
+
}
|
|
8567
|
+
};
|
|
8568
|
+
var TokenCounter = class {
|
|
8569
|
+
constructor(model = "gpt-4") {
|
|
8570
|
+
try {
|
|
8571
|
+
this.encoder = encoding_for_model(model);
|
|
8572
|
+
} catch {
|
|
8573
|
+
this.encoder = get_encoding("cl100k_base");
|
|
8574
|
+
}
|
|
8575
|
+
}
|
|
8576
|
+
/**
|
|
8577
|
+
* Count tokens in a string
|
|
8578
|
+
*/
|
|
8579
|
+
countTokens(text) {
|
|
8580
|
+
if (!text) return 0;
|
|
8581
|
+
return this.encoder.encode(text).length;
|
|
8582
|
+
}
|
|
8583
|
+
/**
|
|
8584
|
+
* Count tokens in messages array (for chat completions)
|
|
8585
|
+
*/
|
|
8586
|
+
countMessageTokens(messages) {
|
|
8587
|
+
let totalTokens = 0;
|
|
8588
|
+
for (const message of messages) {
|
|
8589
|
+
totalTokens += 3;
|
|
8590
|
+
if (message.content && typeof message.content === "string") {
|
|
8591
|
+
totalTokens += this.countTokens(message.content);
|
|
8592
|
+
}
|
|
8593
|
+
if (message.role) {
|
|
8594
|
+
totalTokens += this.countTokens(message.role);
|
|
8595
|
+
}
|
|
8596
|
+
if (message.tool_calls) {
|
|
8597
|
+
totalTokens += this.countTokens(JSON.stringify(message.tool_calls));
|
|
8598
|
+
}
|
|
8599
|
+
}
|
|
8600
|
+
totalTokens += 3;
|
|
8601
|
+
return totalTokens;
|
|
8602
|
+
}
|
|
8603
|
+
/**
|
|
8604
|
+
* Estimate tokens for streaming content
|
|
8605
|
+
* This is an approximation since we don't have the full response yet
|
|
8606
|
+
*/
|
|
8607
|
+
estimateStreamingTokens(accumulatedContent) {
|
|
8608
|
+
return this.countTokens(accumulatedContent);
|
|
8609
|
+
}
|
|
8610
|
+
/**
|
|
8611
|
+
* Clean up resources
|
|
8612
|
+
*/
|
|
8613
|
+
dispose() {
|
|
8614
|
+
this.encoder.free();
|
|
8615
|
+
}
|
|
8616
|
+
};
|
|
8617
|
+
function formatTokenCount(count) {
|
|
8618
|
+
if (count <= 999) {
|
|
8619
|
+
return count.toString();
|
|
8620
|
+
}
|
|
8621
|
+
if (count < 1e6) {
|
|
8622
|
+
const k = count / 1e3;
|
|
8623
|
+
return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`;
|
|
8624
|
+
}
|
|
8625
|
+
const m = count / 1e6;
|
|
8626
|
+
return m % 1 === 0 ? `${m}m` : `${m.toFixed(1)}m`;
|
|
8627
|
+
}
|
|
8628
|
+
function createTokenCounter(model) {
|
|
8629
|
+
return new TokenCounter(model);
|
|
8630
|
+
}
|
|
8631
|
+
function loadCustomInstructions(workingDirectory = process.cwd()) {
|
|
8632
|
+
try {
|
|
8633
|
+
const instructionsPath = path7.join(workingDirectory, ".grok", "GROK.md");
|
|
8634
|
+
if (!fs.existsSync(instructionsPath)) {
|
|
8635
|
+
return null;
|
|
8636
|
+
}
|
|
8637
|
+
const customInstructions = fs.readFileSync(instructionsPath, "utf-8");
|
|
8638
|
+
return customInstructions.trim();
|
|
8639
|
+
} catch (error) {
|
|
8640
|
+
console.warn("Failed to load custom instructions:", error);
|
|
8641
|
+
return null;
|
|
8642
|
+
}
|
|
8643
|
+
}
|
|
8644
|
+
|
|
8645
|
+
// src/agent/grok-agent.ts
|
|
8646
|
+
init_settings_manager();
|
|
8647
|
+
var DEFAULT_OPTIONS = {
|
|
8648
|
+
createPatches: true,
|
|
8649
|
+
createBackups: true,
|
|
8650
|
+
gitCommit: true,
|
|
8651
|
+
timeout: 3e5,
|
|
8652
|
+
// 5 minutes per step
|
|
8653
|
+
maxConcurrentSteps: 1
|
|
8654
|
+
};
|
|
8655
|
+
var ExecutionOrchestrator = class {
|
|
8656
|
+
constructor(agent, options = {}) {
|
|
8657
|
+
this.agent = agent;
|
|
8658
|
+
this.maxRecoveryAttempts = 3;
|
|
8659
|
+
this.recoveryAttempts = /* @__PURE__ */ new Map();
|
|
8660
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
8661
|
+
}
|
|
8662
|
+
/**
|
|
8663
|
+
* Execute a research plan's TODO items
|
|
8664
|
+
*/
|
|
8665
|
+
async executePlan(plan) {
|
|
8666
|
+
console.log(`\u{1F680} Starting execution of ${plan.todo.length} tasks...`);
|
|
8667
|
+
console.log(`Summary: ${plan.summary}`);
|
|
8668
|
+
const executionPlan = {
|
|
8669
|
+
steps: plan.todo.map((todo, index) => ({
|
|
8670
|
+
id: index + 1,
|
|
8671
|
+
description: todo,
|
|
8672
|
+
status: "pending"
|
|
8673
|
+
})),
|
|
8674
|
+
totalSteps: plan.todo.length,
|
|
8675
|
+
completedSteps: 0,
|
|
8676
|
+
failedSteps: 0,
|
|
8677
|
+
startTime: /* @__PURE__ */ new Date(),
|
|
8678
|
+
summary: plan.summary
|
|
8679
|
+
};
|
|
8680
|
+
try {
|
|
8681
|
+
for (const step of executionPlan.steps) {
|
|
8682
|
+
await this.executeStep(step, executionPlan);
|
|
8683
|
+
if (step.status === "failed") {
|
|
8684
|
+
executionPlan.failedSteps++;
|
|
8685
|
+
} else {
|
|
8686
|
+
executionPlan.completedSteps++;
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8689
|
+
executionPlan.endTime = /* @__PURE__ */ new Date();
|
|
8690
|
+
if (this.options.gitCommit && this.isGitRepository()) {
|
|
8691
|
+
try {
|
|
8692
|
+
executionPlan.gitCommitHash = await this.createGitCommit(executionPlan);
|
|
8693
|
+
} catch (error) {
|
|
8694
|
+
console.warn("[Execution] Failed to create git commit:", error);
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
const success = executionPlan.failedSteps === 0;
|
|
8698
|
+
console.log(`\u2705 Execution ${success ? "completed" : "finished with errors"}: ${executionPlan.completedSteps}/${executionPlan.totalSteps} steps successful`);
|
|
8699
|
+
return {
|
|
8700
|
+
success,
|
|
8701
|
+
executionPlan
|
|
8702
|
+
};
|
|
8703
|
+
} catch (error) {
|
|
8704
|
+
executionPlan.endTime = /* @__PURE__ */ new Date();
|
|
8705
|
+
console.error("[Execution] Orchestration failed:", error);
|
|
8706
|
+
return {
|
|
8707
|
+
success: false,
|
|
8708
|
+
executionPlan,
|
|
8709
|
+
error: error instanceof Error ? error.message : "Unknown execution error"
|
|
8710
|
+
};
|
|
8711
|
+
}
|
|
8712
|
+
}
|
|
8713
|
+
/**
|
|
8714
|
+
* Execute a single step
|
|
8715
|
+
*/
|
|
8716
|
+
async executeStep(step, _executionPlan) {
|
|
8717
|
+
step.status = "running";
|
|
8718
|
+
step.startTime = /* @__PURE__ */ new Date();
|
|
8719
|
+
console.log(`
|
|
8720
|
+
[x-cli] #${step.id} ${step.description} \u2026`);
|
|
8721
|
+
try {
|
|
8722
|
+
const beforeState = this.captureFileState();
|
|
8723
|
+
await this.agent.processUserMessage(step.description);
|
|
8724
|
+
await new Promise((resolve8) => setTimeout(resolve8, 1e3));
|
|
8725
|
+
const afterState = this.captureFileState();
|
|
8726
|
+
step.changes = this.calculateChanges(beforeState, afterState);
|
|
8727
|
+
await this.displayChanges(step);
|
|
8728
|
+
if (step.changes && step.changes.length > 0) {
|
|
8729
|
+
step.patchFile = await this.createPatchFile(step);
|
|
8730
|
+
await this.createBackups(step);
|
|
8731
|
+
}
|
|
8732
|
+
step.status = "completed";
|
|
8733
|
+
step.endTime = /* @__PURE__ */ new Date();
|
|
8734
|
+
console.log(`[x-cli] #${step.id} \u2713 Completed`);
|
|
8735
|
+
} catch (error) {
|
|
8736
|
+
step.status = "failed";
|
|
8737
|
+
step.endTime = /* @__PURE__ */ new Date();
|
|
8738
|
+
step.error = error instanceof Error ? error.message : "Unknown error";
|
|
8739
|
+
console.log(`[x-cli] #${step.id} \u2717 Failed: ${step.error}`);
|
|
8740
|
+
}
|
|
8741
|
+
}
|
|
8742
|
+
/**
|
|
8743
|
+
* Capture current file state (simplified - just track modification times)
|
|
8744
|
+
*/
|
|
8745
|
+
captureFileState() {
|
|
8746
|
+
const state = /* @__PURE__ */ new Map();
|
|
8747
|
+
try {
|
|
8748
|
+
const walkDir = (dir) => {
|
|
8749
|
+
const files = fs.readdirSync(dir);
|
|
8750
|
+
for (const file of files) {
|
|
8751
|
+
const filePath = path7.join(dir, file);
|
|
8752
|
+
const stat = fs.statSync(filePath);
|
|
8753
|
+
if (stat.isDirectory() && !file.startsWith(".") && file !== "node_modules") {
|
|
8754
|
+
walkDir(filePath);
|
|
8755
|
+
} else if (stat.isFile()) {
|
|
8756
|
+
state.set(filePath, stat.mtime.getTime());
|
|
8757
|
+
}
|
|
8758
|
+
}
|
|
8759
|
+
};
|
|
8760
|
+
walkDir(".");
|
|
8761
|
+
} catch (error) {
|
|
8762
|
+
console.warn("[Execution] Failed to capture file state:", error);
|
|
8763
|
+
}
|
|
8764
|
+
return state;
|
|
8765
|
+
}
|
|
8766
|
+
/**
|
|
8767
|
+
* Calculate file changes between states
|
|
8768
|
+
*/
|
|
8769
|
+
calculateChanges(before, after) {
|
|
8770
|
+
const changes = [];
|
|
8771
|
+
for (const [filePath, afterTime] of after) {
|
|
8772
|
+
const beforeTime = before.get(filePath);
|
|
8773
|
+
if (!beforeTime || beforeTime !== afterTime) {
|
|
8774
|
+
changes.push({
|
|
8775
|
+
filePath,
|
|
8776
|
+
changeType: beforeTime ? "modified" : "created"
|
|
8777
|
+
});
|
|
8778
|
+
}
|
|
8779
|
+
}
|
|
8780
|
+
for (const filePath of before.keys()) {
|
|
8781
|
+
if (!after.has(filePath)) {
|
|
8782
|
+
changes.push({
|
|
8783
|
+
filePath,
|
|
8784
|
+
changeType: "deleted"
|
|
8785
|
+
});
|
|
8786
|
+
}
|
|
8787
|
+
}
|
|
8788
|
+
return changes;
|
|
8789
|
+
}
|
|
8790
|
+
/**
|
|
8791
|
+
* Display changes with diffs
|
|
8792
|
+
*/
|
|
8793
|
+
async displayChanges(step) {
|
|
8794
|
+
if (!step.changes || step.changes.length === 0) {
|
|
8795
|
+
return;
|
|
8796
|
+
}
|
|
8797
|
+
console.log(`[x-cli] #${step.id} Changes detected:`);
|
|
8798
|
+
for (const change of step.changes) {
|
|
8799
|
+
console.log(` ${change.changeType.toUpperCase()}: ${change.filePath}`);
|
|
8800
|
+
if (change.changeType === "modified" && fs.existsSync(change.filePath)) {
|
|
8801
|
+
try {
|
|
8802
|
+
if (this.isGitRepository()) {
|
|
8803
|
+
const diff = execSync(`git diff --no-index /dev/null ${change.filePath} 2>/dev/null || git diff ${change.filePath}`, {
|
|
8804
|
+
encoding: "utf-8",
|
|
8805
|
+
timeout: 5e3
|
|
8806
|
+
}).trim();
|
|
8807
|
+
if (diff) {
|
|
8808
|
+
console.log(" Diff:");
|
|
8809
|
+
console.log(diff.split("\n").map((line) => ` ${line}`).join("\n"));
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8812
|
+
} catch (_error) {
|
|
8813
|
+
}
|
|
8814
|
+
}
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
/**
|
|
8818
|
+
* Create patch file for changes
|
|
8819
|
+
*/
|
|
8820
|
+
async createPatchFile(step) {
|
|
8821
|
+
if (!this.options.createPatches || !step.changes || step.changes.length === 0) {
|
|
8822
|
+
return void 0;
|
|
8823
|
+
}
|
|
8824
|
+
try {
|
|
8825
|
+
const patchesDir = path7.join(__require("os").homedir(), ".xcli", "patches");
|
|
8826
|
+
if (!fs.existsSync(patchesDir)) {
|
|
8827
|
+
fs.mkdirSync(patchesDir, { recursive: true });
|
|
8828
|
+
}
|
|
8829
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8830
|
+
const patchFile = path7.join(patchesDir, `step-${step.id}-${timestamp}.patch`);
|
|
8831
|
+
let patchContent = `# Patch for step #${step.id}: ${step.description}
|
|
8832
|
+
`;
|
|
8833
|
+
patchContent += `# Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
8834
|
+
|
|
8835
|
+
`;
|
|
8836
|
+
for (const change of step.changes) {
|
|
8837
|
+
if (change.changeType === "modified" && fs.existsSync(change.filePath)) {
|
|
8838
|
+
try {
|
|
8839
|
+
const diff = execSync(`git diff ${change.filePath}`, {
|
|
8840
|
+
encoding: "utf-8",
|
|
8841
|
+
timeout: 5e3
|
|
8842
|
+
});
|
|
8843
|
+
patchContent += `--- a/${change.filePath}
|
|
8844
|
+
+++ b/${change.filePath}
|
|
8845
|
+
${diff}
|
|
8846
|
+
`;
|
|
8847
|
+
} catch {
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8851
|
+
fs.writeFileSync(patchFile, patchContent);
|
|
8852
|
+
console.log(`[x-cli] #${step.id} Patch saved: ${patchFile}`);
|
|
8853
|
+
return patchFile;
|
|
8854
|
+
} catch (error) {
|
|
8855
|
+
console.warn(`[Execution] Failed to create patch for step ${step.id}:`, error);
|
|
8856
|
+
return void 0;
|
|
8857
|
+
}
|
|
8858
|
+
}
|
|
8859
|
+
/**
|
|
8860
|
+
* Create backup files
|
|
8861
|
+
*/
|
|
8862
|
+
async createBackups(step) {
|
|
8863
|
+
if (!this.options.createBackups || !step.changes) {
|
|
8864
|
+
return;
|
|
8865
|
+
}
|
|
8866
|
+
for (const change of step.changes) {
|
|
8867
|
+
if ((change.changeType === "modified" || change.changeType === "created") && fs.existsSync(change.filePath)) {
|
|
8868
|
+
try {
|
|
8869
|
+
const backupPath = `${change.filePath}.bak`;
|
|
8870
|
+
fs.copyFileSync(change.filePath, backupPath);
|
|
8871
|
+
change.backupPath = backupPath;
|
|
8872
|
+
console.log(`[x-cli] #${step.id} Backup created: ${backupPath}`);
|
|
8873
|
+
} catch (_error) {
|
|
8874
|
+
console.warn(`[Execution] Failed to create backup for ${change.filePath}:`, _error);
|
|
8875
|
+
}
|
|
8876
|
+
}
|
|
8877
|
+
}
|
|
8878
|
+
}
|
|
8879
|
+
/**
|
|
8880
|
+
* Check if current directory is a git repository
|
|
8881
|
+
*/
|
|
8882
|
+
isGitRepository() {
|
|
8883
|
+
try {
|
|
8884
|
+
execSync("git rev-parse --git-dir", { stdio: "ignore" });
|
|
8885
|
+
return true;
|
|
8886
|
+
} catch {
|
|
8887
|
+
return false;
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
/**
|
|
8891
|
+
* Create git commit for all changes
|
|
8892
|
+
*/
|
|
8893
|
+
async createGitCommit(executionPlan) {
|
|
8894
|
+
try {
|
|
8895
|
+
execSync("git add .", { stdio: "ignore" });
|
|
8896
|
+
const commitMessage = `feat: ${executionPlan.summary}
|
|
8897
|
+
|
|
8898
|
+
Executed ${executionPlan.totalSteps} tasks:
|
|
8899
|
+
${executionPlan.steps.map((step) => `- ${step.description}`).join("\n")}
|
|
8900
|
+
|
|
8901
|
+
Auto-generated by x-cli execution orchestrator`;
|
|
8902
|
+
execSync(`git commit -m "${commitMessage}"`, { stdio: "ignore" });
|
|
8903
|
+
const hash = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
8904
|
+
console.log(`\u2705 Git commit created: ${hash.substring(0, 8)}`);
|
|
8905
|
+
return hash;
|
|
8906
|
+
} catch (error) {
|
|
8907
|
+
throw new Error(`Git commit failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
8908
|
+
}
|
|
8909
|
+
}
|
|
8910
|
+
/**
|
|
8911
|
+
* Detect error patterns in step execution
|
|
8912
|
+
*/
|
|
8913
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8914
|
+
detectError(error) {
|
|
8915
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
8916
|
+
if (errorMessage.includes("test") && (errorMessage.includes("fail") || errorMessage.includes("error"))) {
|
|
8917
|
+
return {
|
|
8918
|
+
stepId: -1,
|
|
8919
|
+
// Will be set by caller
|
|
8920
|
+
errorType: "test_failure",
|
|
8921
|
+
errorMessage,
|
|
8922
|
+
stackTrace: error instanceof Error ? error.stack : void 0,
|
|
8923
|
+
affectedFiles: this.findTestFiles(),
|
|
8924
|
+
contextData: { pattern: "test_failure" }
|
|
8925
|
+
};
|
|
8926
|
+
}
|
|
8927
|
+
if (errorMessage.includes("build") && (errorMessage.includes("fail") || errorMessage.includes("error"))) {
|
|
8928
|
+
return {
|
|
8929
|
+
stepId: -1,
|
|
8930
|
+
errorType: "build_failure",
|
|
8931
|
+
errorMessage,
|
|
8932
|
+
stackTrace: error instanceof Error ? error.stack : void 0,
|
|
8933
|
+
affectedFiles: this.findBuildFiles(),
|
|
8934
|
+
contextData: { pattern: "build_failure" }
|
|
8935
|
+
};
|
|
8936
|
+
}
|
|
8937
|
+
if (errorMessage.includes("lint") && (errorMessage.includes("fail") || errorMessage.includes("error"))) {
|
|
8938
|
+
return {
|
|
8939
|
+
stepId: -1,
|
|
8940
|
+
errorType: "lint_failure",
|
|
8941
|
+
errorMessage,
|
|
8942
|
+
stackTrace: error instanceof Error ? error.stack : void 0,
|
|
8943
|
+
affectedFiles: this.findSourceFiles(),
|
|
8944
|
+
contextData: { pattern: "lint_failure" }
|
|
8945
|
+
};
|
|
8946
|
+
}
|
|
8947
|
+
return {
|
|
8948
|
+
stepId: -1,
|
|
8949
|
+
errorType: "runtime_error",
|
|
8950
|
+
errorMessage,
|
|
8951
|
+
stackTrace: error instanceof Error ? error.stack : void 0,
|
|
8952
|
+
affectedFiles: [],
|
|
8953
|
+
contextData: { pattern: "runtime_error" }
|
|
8954
|
+
};
|
|
8955
|
+
}
|
|
8956
|
+
/**
|
|
8957
|
+
* Find test files in the project
|
|
8958
|
+
*/
|
|
8959
|
+
findTestFiles() {
|
|
8960
|
+
try {
|
|
8961
|
+
const testFiles = [];
|
|
8962
|
+
const walkDir = (dir) => {
|
|
8963
|
+
const files = fs.readdirSync(dir);
|
|
8964
|
+
for (const file of files) {
|
|
8965
|
+
const filePath = path7.join(dir, file);
|
|
8966
|
+
const stat = fs.statSync(filePath);
|
|
8967
|
+
if (stat.isDirectory() && !file.startsWith(".") && file !== "node_modules") {
|
|
8968
|
+
walkDir(filePath);
|
|
8969
|
+
} else if (stat.isFile() && (file.includes("test") || file.includes("spec"))) {
|
|
8970
|
+
testFiles.push(filePath);
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
8973
|
+
};
|
|
8974
|
+
walkDir(".");
|
|
8975
|
+
return testFiles.slice(0, 10);
|
|
8976
|
+
} catch {
|
|
8977
|
+
return [];
|
|
8978
|
+
}
|
|
8979
|
+
}
|
|
8980
|
+
/**
|
|
8981
|
+
* Find build configuration files
|
|
8982
|
+
*/
|
|
8983
|
+
findBuildFiles() {
|
|
8984
|
+
const buildFiles = ["package.json", "tsconfig.json", "webpack.config.js", "babel.config.js"];
|
|
8985
|
+
return buildFiles.filter((file) => fs.existsSync(file));
|
|
8986
|
+
}
|
|
8987
|
+
/**
|
|
8988
|
+
* Find source files
|
|
8989
|
+
*/
|
|
8990
|
+
findSourceFiles() {
|
|
8991
|
+
try {
|
|
8992
|
+
const sourceFiles = [];
|
|
8993
|
+
const walkDir = (dir) => {
|
|
8994
|
+
const files = fs.readdirSync(dir);
|
|
8995
|
+
for (const file of files) {
|
|
8996
|
+
const filePath = path7.join(dir, file);
|
|
8997
|
+
const stat = fs.statSync(filePath);
|
|
8998
|
+
if (stat.isDirectory() && !file.startsWith(".") && file !== "node_modules") {
|
|
8999
|
+
walkDir(filePath);
|
|
9000
|
+
} else if (stat.isFile() && (file.endsWith(".ts") || file.endsWith(".js") || file.endsWith(".tsx") || file.endsWith(".jsx"))) {
|
|
9001
|
+
sourceFiles.push(filePath);
|
|
9002
|
+
}
|
|
9003
|
+
}
|
|
9004
|
+
};
|
|
9005
|
+
walkDir(".");
|
|
9006
|
+
return sourceFiles.slice(0, 20);
|
|
9007
|
+
} catch {
|
|
9008
|
+
return [];
|
|
9009
|
+
}
|
|
9010
|
+
}
|
|
9011
|
+
/**
|
|
9012
|
+
* Present error context to user
|
|
9013
|
+
*/
|
|
9014
|
+
presentErrorContext(errorContext, _step) {
|
|
9015
|
+
console.log("\n" + "=".repeat(60));
|
|
9016
|
+
console.log("\u{1F6A8} ISSUE ENCOUNTERED");
|
|
9017
|
+
console.log("=".repeat(60));
|
|
9018
|
+
console.log(`[x-cli] Issue encountered: ${errorContext.errorMessage}`);
|
|
9019
|
+
if (errorContext.affectedFiles.length > 0) {
|
|
9020
|
+
console.log(`Affected files: ${errorContext.affectedFiles.slice(0, 5).join(", ")}`);
|
|
9021
|
+
if (errorContext.affectedFiles.length > 5) {
|
|
9022
|
+
console.log(`... and ${errorContext.affectedFiles.length - 5} more`);
|
|
9023
|
+
}
|
|
9024
|
+
}
|
|
9025
|
+
console.log("\n\u{1F504} Initiating adaptive recovery...");
|
|
9026
|
+
}
|
|
9027
|
+
/**
|
|
9028
|
+
* Handle recovery flow
|
|
9029
|
+
*/
|
|
9030
|
+
async handleRecovery(originalRequest, errorContext, executionPlan, researchService) {
|
|
9031
|
+
const attempts = this.recoveryAttempts.get(errorContext.stepId) || 0;
|
|
9032
|
+
if (attempts >= this.maxRecoveryAttempts) {
|
|
9033
|
+
console.log(`\u274C Maximum recovery attempts (${this.maxRecoveryAttempts}) reached for step ${errorContext.stepId}`);
|
|
9034
|
+
return { approved: false, maxRetriesExceeded: true };
|
|
9035
|
+
}
|
|
9036
|
+
this.recoveryAttempts.set(errorContext.stepId, attempts + 1);
|
|
9037
|
+
const recoveryRequest = {
|
|
9038
|
+
userTask: `Recovery from execution error: ${errorContext.errorMessage}
|
|
9039
|
+
|
|
9040
|
+
Original task: ${originalRequest.userTask}
|
|
9041
|
+
|
|
9042
|
+
Error context:
|
|
9043
|
+
- Type: ${errorContext.errorType}
|
|
9044
|
+
- Message: ${errorContext.errorMessage}
|
|
9045
|
+
- Affected files: ${errorContext.affectedFiles.join(", ")}
|
|
9046
|
+
|
|
9047
|
+
Please provide a recovery plan to resolve this issue and continue execution.`,
|
|
9048
|
+
context: `This is a RECOVERY REQUEST for a failed execution step. The original task was part of a larger plan that encountered an error. Focus on fixing the specific issue and providing steps to resolve it.`,
|
|
9049
|
+
constraints: [
|
|
9050
|
+
"Focus on fixing the specific error encountered",
|
|
9051
|
+
"Provide actionable recovery steps",
|
|
9052
|
+
"Consider the broader execution context",
|
|
9053
|
+
"Ensure recovery steps are safe and reversible"
|
|
9054
|
+
]
|
|
9055
|
+
};
|
|
9056
|
+
try {
|
|
9057
|
+
console.log("\u{1F50D} Analyzing error and generating recovery plan...");
|
|
9058
|
+
const { output, approval } = await researchService.researchAndGetApproval(recoveryRequest);
|
|
9059
|
+
if (approval.approved) {
|
|
9060
|
+
console.log("\u2705 Recovery plan approved. Resuming execution...");
|
|
9061
|
+
return { approved: true, recoveryPlan: output };
|
|
9062
|
+
} else {
|
|
9063
|
+
console.log("\u274C Recovery plan rejected by user.");
|
|
9064
|
+
return { approved: false };
|
|
9065
|
+
}
|
|
9066
|
+
} catch (error) {
|
|
9067
|
+
console.error("[Recovery] Failed to generate recovery plan:", error);
|
|
9068
|
+
return { approved: false };
|
|
9069
|
+
}
|
|
9070
|
+
}
|
|
9071
|
+
/**
|
|
9072
|
+
* Execute with adaptive recovery
|
|
9073
|
+
*/
|
|
9074
|
+
async executeWithRecovery(plan, researchService, originalRequest) {
|
|
9075
|
+
console.log(`\u{1F680} Starting execution with adaptive recovery of ${plan.todo.length} tasks...`);
|
|
9076
|
+
console.log(`Summary: ${plan.summary}`);
|
|
9077
|
+
const executionPlan = {
|
|
9078
|
+
steps: plan.todo.map((todo, index) => ({
|
|
9079
|
+
id: index + 1,
|
|
9080
|
+
description: todo,
|
|
9081
|
+
status: "pending"
|
|
9082
|
+
})),
|
|
9083
|
+
totalSteps: plan.todo.length,
|
|
9084
|
+
completedSteps: 0,
|
|
9085
|
+
failedSteps: 0,
|
|
9086
|
+
startTime: /* @__PURE__ */ new Date(),
|
|
9087
|
+
summary: plan.summary
|
|
9088
|
+
};
|
|
9089
|
+
try {
|
|
9090
|
+
for (let i = 0; i < executionPlan.steps.length; i++) {
|
|
9091
|
+
const step = executionPlan.steps[i];
|
|
9092
|
+
try {
|
|
9093
|
+
await this.executeStep(step, executionPlan);
|
|
9094
|
+
if (step.status === "completed") {
|
|
9095
|
+
executionPlan.completedSteps++;
|
|
9096
|
+
} else {
|
|
9097
|
+
const errorContext = this.detectError(step.error);
|
|
9098
|
+
if (errorContext) {
|
|
9099
|
+
errorContext.stepId = step.id;
|
|
9100
|
+
this.presentErrorContext(errorContext, step);
|
|
9101
|
+
const recoveryResult = await this.handleRecovery(
|
|
9102
|
+
originalRequest,
|
|
9103
|
+
errorContext,
|
|
9104
|
+
executionPlan,
|
|
9105
|
+
researchService
|
|
9106
|
+
);
|
|
9107
|
+
if (recoveryResult.approved && recoveryResult.recoveryPlan) {
|
|
9108
|
+
const recoverySteps = recoveryResult.recoveryPlan.plan.todo.map((todo, idx) => ({
|
|
9109
|
+
id: executionPlan.steps.length + idx + 1,
|
|
9110
|
+
description: `[RECOVERY] ${todo}`,
|
|
9111
|
+
status: "pending"
|
|
9112
|
+
}));
|
|
9113
|
+
executionPlan.steps.splice(i + 1, 0, ...recoverySteps);
|
|
9114
|
+
executionPlan.totalSteps += recoverySteps.length;
|
|
9115
|
+
console.log(`\u{1F4CB} Added ${recoverySteps.length} recovery steps. Continuing execution...`);
|
|
9116
|
+
continue;
|
|
9117
|
+
}
|
|
9118
|
+
}
|
|
9119
|
+
executionPlan.failedSteps++;
|
|
9120
|
+
}
|
|
9121
|
+
} catch (error) {
|
|
9122
|
+
const errorContext = this.detectError(error);
|
|
9123
|
+
if (errorContext) {
|
|
9124
|
+
errorContext.stepId = step.id;
|
|
9125
|
+
step.status = "failed";
|
|
9126
|
+
step.error = errorContext.errorMessage;
|
|
9127
|
+
executionPlan.failedSteps++;
|
|
9128
|
+
console.log(`[x-cli] #${step.id} \u2717 Failed: ${errorContext.errorMessage}`);
|
|
9129
|
+
}
|
|
9130
|
+
}
|
|
9131
|
+
}
|
|
9132
|
+
executionPlan.endTime = /* @__PURE__ */ new Date();
|
|
9133
|
+
if (this.options.gitCommit && this.isGitRepository()) {
|
|
9134
|
+
try {
|
|
9135
|
+
executionPlan.gitCommitHash = await this.createGitCommit(executionPlan);
|
|
9136
|
+
} catch (error) {
|
|
9137
|
+
console.warn("[Execution] Failed to create git commit:", error);
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
const success = executionPlan.failedSteps === 0;
|
|
9141
|
+
console.log(`\u2705 Execution ${success ? "completed" : "finished with errors"}: ${executionPlan.completedSteps}/${executionPlan.totalSteps} steps successful`);
|
|
9142
|
+
return {
|
|
9143
|
+
success,
|
|
9144
|
+
executionPlan
|
|
9145
|
+
};
|
|
9146
|
+
} catch (error) {
|
|
9147
|
+
executionPlan.endTime = /* @__PURE__ */ new Date();
|
|
9148
|
+
console.error("[Execution] Orchestration failed:", error);
|
|
9149
|
+
return {
|
|
9150
|
+
success: false,
|
|
9151
|
+
executionPlan,
|
|
9152
|
+
error: error instanceof Error ? error.message : "Unknown execution error"
|
|
9153
|
+
};
|
|
9154
|
+
}
|
|
9155
|
+
}
|
|
9156
|
+
};
|
|
9157
|
+
var DEFAULT_CONFIG = {
|
|
9158
|
+
maxOptions: 3,
|
|
9159
|
+
includeContext: true,
|
|
9160
|
+
timeout: 6e4
|
|
9161
|
+
// 60 seconds
|
|
9162
|
+
};
|
|
9163
|
+
var ResearchRecommendService = class {
|
|
9164
|
+
constructor(agent, config2 = DEFAULT_CONFIG) {
|
|
9165
|
+
this.agent = agent;
|
|
9166
|
+
this.config = config2;
|
|
9167
|
+
}
|
|
9168
|
+
/**
|
|
9169
|
+
* Perform research and generate recommendation
|
|
9170
|
+
*/
|
|
9171
|
+
async researchAndRecommend(request, contextPack) {
|
|
9172
|
+
const prompt = this.buildResearchPrompt(request, contextPack);
|
|
9173
|
+
try {
|
|
9174
|
+
const response = await this.agent.processUserMessage(prompt);
|
|
9175
|
+
return this.parseResearchOutput(response);
|
|
9176
|
+
} catch (error) {
|
|
9177
|
+
console.error("[ResearchRecommend] Research failed:", error);
|
|
9178
|
+
throw new Error(`Research failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
9179
|
+
}
|
|
9180
|
+
}
|
|
9181
|
+
/**
|
|
9182
|
+
* Build the research prompt
|
|
9183
|
+
*/
|
|
9184
|
+
buildResearchPrompt(request, contextPack) {
|
|
9185
|
+
let prompt = `Analyze the following task and provide a structured research output in JSON format.
|
|
9186
|
+
|
|
9187
|
+
TASK: ${request.userTask}
|
|
9188
|
+
|
|
9189
|
+
`;
|
|
9190
|
+
if (request.constraints && request.constraints.length > 0) {
|
|
9191
|
+
prompt += `CONSTRAINTS:
|
|
9192
|
+
${request.constraints.map((c) => `- ${c}`).join("\n")}
|
|
9193
|
+
|
|
9194
|
+
`;
|
|
9195
|
+
}
|
|
9196
|
+
if (request.preferences && request.preferences.length > 0) {
|
|
9197
|
+
prompt += `PREFERENCES:
|
|
9198
|
+
${request.preferences.map((p) => `- ${p}`).join("\n")}
|
|
9199
|
+
|
|
9200
|
+
`;
|
|
9201
|
+
}
|
|
9202
|
+
if (this.config.includeContext && contextPack) {
|
|
9203
|
+
prompt += `CONTEXT INFORMATION:
|
|
9204
|
+
System Documentation:
|
|
9205
|
+
${contextPack.system}
|
|
9206
|
+
|
|
9207
|
+
SOP Documentation:
|
|
9208
|
+
${contextPack.sop}
|
|
9209
|
+
|
|
9210
|
+
Recent Task Documentation:
|
|
9211
|
+
${contextPack.tasks.slice(0, 5).map((t) => `${t.filename}:
|
|
9212
|
+
${t.content}`).join("\n\n")}
|
|
9213
|
+
|
|
9214
|
+
`;
|
|
9215
|
+
}
|
|
9216
|
+
prompt += `Please provide your analysis in the following JSON structure:
|
|
9217
|
+
{
|
|
9218
|
+
"issues": [
|
|
9219
|
+
{
|
|
9220
|
+
"type": "fact|gap|risk",
|
|
9221
|
+
"description": "Description of the issue",
|
|
9222
|
+
"severity": "low|medium|high",
|
|
9223
|
+
"impact": "Impact description (optional)"
|
|
9224
|
+
}
|
|
9225
|
+
],
|
|
9226
|
+
"options": [
|
|
9227
|
+
{
|
|
9228
|
+
"id": 1,
|
|
9229
|
+
"title": "Option title",
|
|
9230
|
+
"description": "Detailed description",
|
|
9231
|
+
"tradeoffs": {
|
|
9232
|
+
"pros": ["pro1", "pro2"],
|
|
9233
|
+
"cons": ["con1", "con2"]
|
|
9234
|
+
},
|
|
9235
|
+
"effort": "low|medium|high",
|
|
9236
|
+
"risk": "low|medium|high"
|
|
9237
|
+
}
|
|
9238
|
+
],
|
|
9239
|
+
"recommendation": {
|
|
9240
|
+
"optionId": 1,
|
|
9241
|
+
"reasoning": "Why this option is recommended",
|
|
9242
|
+
"justification": "Detailed justification",
|
|
9243
|
+
"confidence": "low|medium|high"
|
|
9244
|
+
},
|
|
9245
|
+
"plan": {
|
|
9246
|
+
"summary": "Brief summary of the plan",
|
|
9247
|
+
"approach": ["step1", "step2", "step3"],
|
|
9248
|
+
"todo": ["TODO item 1", "TODO item 2"],
|
|
9249
|
+
"estimatedEffort": "Time estimate",
|
|
9250
|
+
"keyConsiderations": ["consideration1", "consideration2"]
|
|
9251
|
+
}
|
|
9252
|
+
}
|
|
9253
|
+
|
|
9254
|
+
Provide exactly ${this.config.maxOptions} options. Focus on actionable, practical solutions. Be thorough but concise. Respond with ONLY the JSON.`;
|
|
9255
|
+
return prompt;
|
|
9256
|
+
}
|
|
9257
|
+
/**
|
|
9258
|
+
* Parse the AI response into structured output
|
|
9259
|
+
*/
|
|
9260
|
+
parseResearchOutput(response) {
|
|
9261
|
+
let jsonText = "";
|
|
9262
|
+
if (Array.isArray(response)) {
|
|
9263
|
+
for (const entry of response) {
|
|
9264
|
+
if (entry.type === "assistant" && entry.content) {
|
|
9265
|
+
jsonText = entry.content.trim();
|
|
9266
|
+
break;
|
|
9267
|
+
}
|
|
9268
|
+
}
|
|
9269
|
+
} else if (typeof response === "string") {
|
|
9270
|
+
jsonText = response;
|
|
9271
|
+
}
|
|
9272
|
+
const jsonMatch = jsonText.match(/\{[\s\S]*\}/);
|
|
9273
|
+
if (!jsonMatch) {
|
|
9274
|
+
throw new Error("No JSON found in response");
|
|
9275
|
+
}
|
|
9276
|
+
try {
|
|
9277
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
9278
|
+
return {
|
|
9279
|
+
issues: this.validateIssues(parsed.issues || []),
|
|
9280
|
+
options: this.validateOptions(parsed.options || []),
|
|
9281
|
+
recommendation: this.validateRecommendation(parsed.recommendation),
|
|
9282
|
+
plan: this.validatePlan(parsed.plan)
|
|
9283
|
+
};
|
|
9284
|
+
} catch (error) {
|
|
9285
|
+
console.error("[ResearchRecommend] JSON parse error:", error);
|
|
9286
|
+
console.error("Raw response:", jsonText);
|
|
9287
|
+
throw new Error("Failed to parse research output JSON");
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9291
|
+
validateIssues(issues) {
|
|
9292
|
+
return issues.map((issue) => ({
|
|
9293
|
+
type: ["fact", "gap", "risk"].includes(issue.type) ? issue.type : "fact",
|
|
9294
|
+
description: issue.description || "No description provided",
|
|
9295
|
+
severity: ["low", "medium", "high"].includes(issue.severity) ? issue.severity : "medium",
|
|
9296
|
+
impact: issue.impact
|
|
9297
|
+
}));
|
|
9298
|
+
}
|
|
9299
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9300
|
+
validateOptions(options) {
|
|
9301
|
+
return options.slice(0, this.config.maxOptions).map((option, index) => ({
|
|
9302
|
+
id: option.id || index + 1,
|
|
9303
|
+
title: option.title || `Option ${index + 1}`,
|
|
9304
|
+
description: option.description || "No description provided",
|
|
9305
|
+
tradeoffs: {
|
|
9306
|
+
pros: Array.isArray(option.tradeoffs?.pros) ? option.tradeoffs.pros : [],
|
|
9307
|
+
cons: Array.isArray(option.tradeoffs?.cons) ? option.tradeoffs.cons : []
|
|
9308
|
+
},
|
|
9309
|
+
effort: ["low", "medium", "high"].includes(option.effort) ? option.effort : "medium",
|
|
9310
|
+
risk: ["low", "medium", "high"].includes(option.risk) ? option.risk : "medium"
|
|
9311
|
+
}));
|
|
9312
|
+
}
|
|
9313
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9314
|
+
validateRecommendation(rec) {
|
|
9315
|
+
return {
|
|
9316
|
+
optionId: rec?.optionId || 1,
|
|
9317
|
+
reasoning: rec?.reasoning || "No reasoning provided",
|
|
9318
|
+
justification: rec?.justification || "No justification provided",
|
|
9319
|
+
confidence: ["low", "medium", "high"].includes(rec?.confidence) ? rec.confidence : "medium"
|
|
8439
9320
|
};
|
|
8440
9321
|
}
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
9322
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9323
|
+
validatePlan(plan) {
|
|
9324
|
+
return {
|
|
9325
|
+
summary: plan?.summary || "No summary provided",
|
|
9326
|
+
approach: Array.isArray(plan?.approach) ? plan.approach : [],
|
|
9327
|
+
todo: Array.isArray(plan?.todo) ? plan.todo : [],
|
|
9328
|
+
estimatedEffort: plan?.estimatedEffort || "Unknown",
|
|
9329
|
+
keyConsiderations: Array.isArray(plan?.keyConsiderations) ? plan.keyConsiderations : []
|
|
9330
|
+
};
|
|
9331
|
+
}
|
|
9332
|
+
/**
|
|
9333
|
+
* Render research output to console
|
|
9334
|
+
*/
|
|
9335
|
+
renderToConsole(output) {
|
|
9336
|
+
console.log("\n" + "=".repeat(50));
|
|
9337
|
+
console.log("\u{1F916} RESEARCH & RECOMMENDATION");
|
|
9338
|
+
console.log("=".repeat(50));
|
|
9339
|
+
this.renderIssues(output.issues);
|
|
9340
|
+
this.renderOptions(output.options);
|
|
9341
|
+
this.renderRecommendation(output.recommendation, output.options);
|
|
9342
|
+
this.renderPlan(output.plan);
|
|
9343
|
+
console.log("=".repeat(50));
|
|
9344
|
+
}
|
|
9345
|
+
renderIssues(issues) {
|
|
9346
|
+
console.log("\n\u{1F4CB} ISSUES");
|
|
9347
|
+
console.log("-".repeat(20));
|
|
9348
|
+
if (issues.length === 0) {
|
|
9349
|
+
console.log("No issues identified.");
|
|
9350
|
+
return;
|
|
9351
|
+
}
|
|
9352
|
+
for (const issue of issues) {
|
|
9353
|
+
const icon = issue.type === "fact" ? "\u{1F4CA}" : issue.type === "gap" ? "\u26A0\uFE0F" : "\u{1F6A8}";
|
|
9354
|
+
const severity = issue.severity ? ` (${issue.severity.toUpperCase()})` : "";
|
|
9355
|
+
console.log(`${icon} ${issue.type.toUpperCase()}${severity}: ${issue.description}`);
|
|
9356
|
+
if (issue.impact) {
|
|
9357
|
+
console.log(` Impact: ${issue.impact}`);
|
|
9358
|
+
}
|
|
9359
|
+
}
|
|
9360
|
+
}
|
|
9361
|
+
renderOptions(options) {
|
|
9362
|
+
console.log("\n\u{1F3AF} OPTIONS");
|
|
9363
|
+
console.log("-".repeat(20));
|
|
9364
|
+
for (const option of options) {
|
|
9365
|
+
console.log(`
|
|
9366
|
+
${option.id}) ${option.title}`);
|
|
9367
|
+
console.log(` ${option.description}`);
|
|
9368
|
+
console.log(` Effort: ${option.effort.toUpperCase()} | Risk: ${option.risk.toUpperCase()}`);
|
|
9369
|
+
if (option.tradeoffs.pros.length > 0) {
|
|
9370
|
+
console.log(` \u2705 Pros: ${option.tradeoffs.pros.join(", ")}`);
|
|
9371
|
+
}
|
|
9372
|
+
if (option.tradeoffs.cons.length > 0) {
|
|
9373
|
+
console.log(` \u274C Cons: ${option.tradeoffs.cons.join(", ")}`);
|
|
9374
|
+
}
|
|
9375
|
+
}
|
|
9376
|
+
}
|
|
9377
|
+
renderRecommendation(recommendation, options) {
|
|
9378
|
+
console.log("\n\u{1F3AF} RECOMMENDATION");
|
|
9379
|
+
console.log("-".repeat(20));
|
|
9380
|
+
const recommendedOption = options.find((o) => o.id === recommendation.optionId);
|
|
9381
|
+
const optionTitle = recommendedOption ? recommendedOption.title : `Option ${recommendation.optionId}`;
|
|
9382
|
+
console.log(`\u2192 ${optionTitle} (Confidence: ${recommendation.confidence.toUpperCase()})`);
|
|
9383
|
+
console.log(`Reasoning: ${recommendation.reasoning}`);
|
|
9384
|
+
console.log(`Justification: ${recommendation.justification}`);
|
|
9385
|
+
}
|
|
9386
|
+
renderPlan(plan) {
|
|
9387
|
+
console.log("\n\u{1F4DD} PLAN SUMMARY");
|
|
9388
|
+
console.log("-".repeat(20));
|
|
9389
|
+
console.log(`Summary: ${plan.summary}`);
|
|
9390
|
+
console.log(`Estimated Effort: ${plan.estimatedEffort}`);
|
|
9391
|
+
if (plan.approach.length > 0) {
|
|
9392
|
+
console.log("\nApproach:");
|
|
9393
|
+
plan.approach.forEach((step, index) => {
|
|
9394
|
+
console.log(` ${index + 1}. ${step}`);
|
|
9395
|
+
});
|
|
9396
|
+
}
|
|
9397
|
+
if (plan.todo.length > 0) {
|
|
9398
|
+
console.log("\nTODO:");
|
|
9399
|
+
plan.todo.forEach((item) => {
|
|
9400
|
+
console.log(` [ ] ${item}`);
|
|
9401
|
+
});
|
|
9402
|
+
}
|
|
9403
|
+
if (plan.keyConsiderations.length > 0) {
|
|
9404
|
+
console.log("\nKey Considerations:");
|
|
9405
|
+
plan.keyConsiderations.forEach((consideration) => {
|
|
9406
|
+
console.log(` \u2022 ${consideration}`);
|
|
9407
|
+
});
|
|
8448
9408
|
}
|
|
8449
9409
|
}
|
|
8450
9410
|
/**
|
|
8451
|
-
*
|
|
9411
|
+
* Prompt user for approval with Y/n/R options
|
|
8452
9412
|
*/
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
9413
|
+
async promptForApproval(_output) {
|
|
9414
|
+
return new Promise((resolve8) => {
|
|
9415
|
+
const rl = readline.createInterface({
|
|
9416
|
+
input: process.stdin,
|
|
9417
|
+
output: process.stdout
|
|
9418
|
+
});
|
|
9419
|
+
const promptUser = () => {
|
|
9420
|
+
console.log("\nProceed with recommendation? (Y/n) [R=revise]");
|
|
9421
|
+
rl.question("> ", (answer) => {
|
|
9422
|
+
const cleanAnswer = answer.trim().toLowerCase();
|
|
9423
|
+
if (cleanAnswer === "y" || cleanAnswer === "yes" || cleanAnswer === "") {
|
|
9424
|
+
rl.close();
|
|
9425
|
+
resolve8({ approved: true, revised: false });
|
|
9426
|
+
} else if (cleanAnswer === "n" || cleanAnswer === "no") {
|
|
9427
|
+
rl.close();
|
|
9428
|
+
resolve8({ approved: false, revised: false });
|
|
9429
|
+
} else if (cleanAnswer === "r" || cleanAnswer === "revise") {
|
|
9430
|
+
rl.question("Revision note (brief description of changes needed): ", (revisionNote) => {
|
|
9431
|
+
rl.close();
|
|
9432
|
+
resolve8({
|
|
9433
|
+
approved: false,
|
|
9434
|
+
revised: true,
|
|
9435
|
+
revisionNote: revisionNote.trim() || "User requested revision"
|
|
9436
|
+
});
|
|
9437
|
+
});
|
|
9438
|
+
} else {
|
|
9439
|
+
console.log("\u274C Invalid input. Please enter Y (yes), N (no), or R (revise).");
|
|
9440
|
+
promptUser();
|
|
9441
|
+
}
|
|
9442
|
+
});
|
|
9443
|
+
};
|
|
9444
|
+
promptUser();
|
|
9445
|
+
});
|
|
8456
9446
|
}
|
|
8457
9447
|
/**
|
|
8458
|
-
*
|
|
9448
|
+
* Handle revision flow with updated request
|
|
8459
9449
|
*/
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
totalTokens += this.countTokens(JSON.stringify(message.tool_calls));
|
|
8472
|
-
}
|
|
8473
|
-
}
|
|
8474
|
-
totalTokens += 3;
|
|
8475
|
-
return totalTokens;
|
|
9450
|
+
async handleRevision(originalRequest, revisionNote, contextPack) {
|
|
9451
|
+
console.log(`\u{1F504} Revising based on: "${revisionNote}"`);
|
|
9452
|
+
console.log("\u{1F50D} Re-researching with revision context...");
|
|
9453
|
+
const revisedRequest = {
|
|
9454
|
+
...originalRequest,
|
|
9455
|
+
constraints: [
|
|
9456
|
+
...originalRequest.constraints || [],
|
|
9457
|
+
`REVISION REQUEST: ${revisionNote}`
|
|
9458
|
+
]
|
|
9459
|
+
};
|
|
9460
|
+
return await this.researchAndRecommend(revisedRequest, contextPack);
|
|
8476
9461
|
}
|
|
8477
9462
|
/**
|
|
8478
|
-
*
|
|
8479
|
-
* This is an approximation since we don't have the full response yet
|
|
9463
|
+
* Full research and approval workflow with revision support
|
|
8480
9464
|
*/
|
|
8481
|
-
|
|
8482
|
-
|
|
9465
|
+
async researchAndGetApproval(request, contextPack, maxRevisions = 3) {
|
|
9466
|
+
let currentRequest = request;
|
|
9467
|
+
let revisions = 0;
|
|
9468
|
+
while (revisions <= maxRevisions) {
|
|
9469
|
+
console.log("\u{1F50D} Researching and analyzing...");
|
|
9470
|
+
const output = await this.researchAndRecommend(currentRequest, contextPack);
|
|
9471
|
+
this.renderToConsole(output);
|
|
9472
|
+
const approval = await this.promptForApproval(output);
|
|
9473
|
+
if (approval.approved || !approval.revised) {
|
|
9474
|
+
return { output, approval, revisions };
|
|
9475
|
+
}
|
|
9476
|
+
revisions++;
|
|
9477
|
+
if (revisions > maxRevisions) {
|
|
9478
|
+
console.log(`\u274C Maximum revisions (${maxRevisions}) reached.`);
|
|
9479
|
+
return { output, approval, revisions };
|
|
9480
|
+
}
|
|
9481
|
+
console.log(`\u{1F504} Revision ${revisions}/${maxRevisions}`);
|
|
9482
|
+
currentRequest = {
|
|
9483
|
+
...request,
|
|
9484
|
+
constraints: [
|
|
9485
|
+
...request.constraints || [],
|
|
9486
|
+
`REVISION ${revisions}: ${approval.revisionNote}`
|
|
9487
|
+
]
|
|
9488
|
+
};
|
|
9489
|
+
}
|
|
9490
|
+
throw new Error("Unexpected end of revision loop");
|
|
8483
9491
|
}
|
|
8484
9492
|
/**
|
|
8485
|
-
*
|
|
9493
|
+
* Complete workflow: Research → Recommend → Execute with Adaptive Recovery
|
|
8486
9494
|
*/
|
|
8487
|
-
|
|
8488
|
-
this.
|
|
9495
|
+
async researchRecommendExecute(request, contextPack, maxRevisions = 3) {
|
|
9496
|
+
const { output, approval, revisions } = await this.researchAndGetApproval(request, contextPack, maxRevisions);
|
|
9497
|
+
if (!approval.approved) {
|
|
9498
|
+
return { output, approval, revisions };
|
|
9499
|
+
}
|
|
9500
|
+
console.log("\n\u{1F680} Proceeding with execution (with adaptive recovery)...");
|
|
9501
|
+
const orchestrator = new ExecutionOrchestrator(this.agent);
|
|
9502
|
+
const execution = await orchestrator.executeWithRecovery(output.plan, this, request);
|
|
9503
|
+
return {
|
|
9504
|
+
output,
|
|
9505
|
+
approval,
|
|
9506
|
+
revisions,
|
|
9507
|
+
execution
|
|
9508
|
+
};
|
|
8489
9509
|
}
|
|
8490
9510
|
};
|
|
8491
|
-
function formatTokenCount(count) {
|
|
8492
|
-
if (count <= 999) {
|
|
8493
|
-
return count.toString();
|
|
8494
|
-
}
|
|
8495
|
-
if (count < 1e6) {
|
|
8496
|
-
const k = count / 1e3;
|
|
8497
|
-
return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`;
|
|
8498
|
-
}
|
|
8499
|
-
const m = count / 1e6;
|
|
8500
|
-
return m % 1 === 0 ? `${m}m` : `${m.toFixed(1)}m`;
|
|
8501
|
-
}
|
|
8502
|
-
function createTokenCounter(model) {
|
|
8503
|
-
return new TokenCounter(model);
|
|
8504
|
-
}
|
|
8505
|
-
function loadCustomInstructions(workingDirectory = process.cwd()) {
|
|
8506
|
-
try {
|
|
8507
|
-
const instructionsPath = path7.join(workingDirectory, ".grok", "GROK.md");
|
|
8508
|
-
if (!fs.existsSync(instructionsPath)) {
|
|
8509
|
-
return null;
|
|
8510
|
-
}
|
|
8511
|
-
const customInstructions = fs.readFileSync(instructionsPath, "utf-8");
|
|
8512
|
-
return customInstructions.trim();
|
|
8513
|
-
} catch (error) {
|
|
8514
|
-
console.warn("Failed to load custom instructions:", error);
|
|
8515
|
-
return null;
|
|
8516
|
-
}
|
|
8517
|
-
}
|
|
8518
9511
|
|
|
8519
9512
|
// src/agent/grok-agent.ts
|
|
8520
|
-
init_settings_manager();
|
|
8521
9513
|
var GrokAgent = class extends EventEmitter {
|
|
8522
9514
|
constructor(apiKey, baseURL, model, maxToolRounds, contextPack) {
|
|
8523
9515
|
super();
|
|
@@ -8688,7 +9680,83 @@ Current working directory: ${process.cwd()}`
|
|
|
8688
9680
|
if (/(20\d{2})/.test(q)) return true;
|
|
8689
9681
|
return false;
|
|
8690
9682
|
}
|
|
9683
|
+
// Detect if message should use the Research → Recommend → Execute workflow
|
|
9684
|
+
shouldUseWorkflow(message) {
|
|
9685
|
+
const q = message.toLowerCase();
|
|
9686
|
+
const complexityIndicators = [
|
|
9687
|
+
// Action verbs indicating multi-step tasks
|
|
9688
|
+
/\b(implement|build|create|refactor|optimize|add|update|modify|develop|design)\b/.test(q),
|
|
9689
|
+
/\b(system|feature|component|module|service|api|database)\b/.test(q),
|
|
9690
|
+
// Multi-step indicators
|
|
9691
|
+
/\b(and|then|after|finally|also|additionally)\b/.test(q),
|
|
9692
|
+
/\b(step|phase|stage|part|component)\b/.test(q),
|
|
9693
|
+
// Size/complexity indicators
|
|
9694
|
+
q.length > 150,
|
|
9695
|
+
// Long requests
|
|
9696
|
+
(q.match(/\b(and|or|but|however|therefore|consequently)\b/g) || []).length >= 2,
|
|
9697
|
+
// Complex logic
|
|
9698
|
+
// Technical complexity
|
|
9699
|
+
/\b(authentication|authorization|security|validation|testing|deployment|ci.cd|docker|kubernetes)\b/.test(q),
|
|
9700
|
+
/\b(multiple|several|various|different|complex|advanced)\b/.test(q)
|
|
9701
|
+
];
|
|
9702
|
+
const indicatorCount = complexityIndicators.filter(Boolean).length;
|
|
9703
|
+
return indicatorCount >= 2;
|
|
9704
|
+
}
|
|
8691
9705
|
async processUserMessage(message) {
|
|
9706
|
+
if (this.shouldUseWorkflow(message)) {
|
|
9707
|
+
return this.processWithWorkflow(message);
|
|
9708
|
+
}
|
|
9709
|
+
return this.processStandard(message);
|
|
9710
|
+
}
|
|
9711
|
+
/**
|
|
9712
|
+
* Process complex tasks using the Research → Recommend → Execute workflow
|
|
9713
|
+
*/
|
|
9714
|
+
async processWithWorkflow(message) {
|
|
9715
|
+
const userEntry = {
|
|
9716
|
+
type: "user",
|
|
9717
|
+
content: message,
|
|
9718
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
9719
|
+
};
|
|
9720
|
+
this.chatHistory.push(userEntry);
|
|
9721
|
+
this.logEntry(userEntry);
|
|
9722
|
+
this.messages.push({ role: "user", content: message });
|
|
9723
|
+
try {
|
|
9724
|
+
const contextPack = await this.loadContextPack();
|
|
9725
|
+
const workflowService = new ResearchRecommendService(this);
|
|
9726
|
+
const request = {
|
|
9727
|
+
userTask: message,
|
|
9728
|
+
context: contextPack ? "Project context loaded" : void 0
|
|
9729
|
+
};
|
|
9730
|
+
console.log("\u{1F50D} Researching and analyzing...");
|
|
9731
|
+
const { output, approval, revisions } = await workflowService.researchAndGetApproval(request, contextPack);
|
|
9732
|
+
if (!approval.approved) {
|
|
9733
|
+
const rejectionEntry = {
|
|
9734
|
+
type: "assistant",
|
|
9735
|
+
content: approval.revised ? `Plan revised ${revisions} time(s) but ultimately rejected by user.` : "Plan rejected by user.",
|
|
9736
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
9737
|
+
};
|
|
9738
|
+
this.chatHistory.push(rejectionEntry);
|
|
9739
|
+
return [userEntry, rejectionEntry];
|
|
9740
|
+
}
|
|
9741
|
+
console.log("\u2705 Plan approved. Executing...");
|
|
9742
|
+
const orchestrator = new ExecutionOrchestrator(this);
|
|
9743
|
+
const executionResult = await orchestrator.executeWithRecovery(output.plan, workflowService, request);
|
|
9744
|
+
return this.workflowResultToChatEntries(userEntry, output, approval, executionResult);
|
|
9745
|
+
} catch (error) {
|
|
9746
|
+
console.error("[Workflow] Failed:", error);
|
|
9747
|
+
const errorEntry = {
|
|
9748
|
+
type: "assistant",
|
|
9749
|
+
content: `Workflow failed: ${error.message}`,
|
|
9750
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
9751
|
+
};
|
|
9752
|
+
this.chatHistory.push(errorEntry);
|
|
9753
|
+
return [userEntry, errorEntry];
|
|
9754
|
+
}
|
|
9755
|
+
}
|
|
9756
|
+
/**
|
|
9757
|
+
* Standard processing for simple queries
|
|
9758
|
+
*/
|
|
9759
|
+
async processStandard(message) {
|
|
8692
9760
|
const userEntry = {
|
|
8693
9761
|
type: "user",
|
|
8694
9762
|
content: message,
|
|
@@ -9061,10 +10129,10 @@ Current working directory: ${process.cwd()}`
|
|
|
9061
10129
|
return await this.textEditor.view(args.path, range);
|
|
9062
10130
|
} catch (error) {
|
|
9063
10131
|
console.warn(`view_file tool failed, falling back to bash: ${error.message}`);
|
|
9064
|
-
const
|
|
9065
|
-
let command = `cat "${
|
|
10132
|
+
const path32 = args.path;
|
|
10133
|
+
let command = `cat "${path32}"`;
|
|
9066
10134
|
if (args.start_line && args.end_line) {
|
|
9067
|
-
command = `sed -n '${args.start_line},${args.end_line}p' "${
|
|
10135
|
+
command = `sed -n '${args.start_line},${args.end_line}p' "${path32}"`;
|
|
9068
10136
|
}
|
|
9069
10137
|
return await this.bash.execute(command);
|
|
9070
10138
|
}
|
|
@@ -9300,6 +10368,12 @@ EOF`;
|
|
|
9300
10368
|
this.abortController.abort();
|
|
9301
10369
|
}
|
|
9302
10370
|
}
|
|
10371
|
+
getMessageCount() {
|
|
10372
|
+
return this.chatHistory.length;
|
|
10373
|
+
}
|
|
10374
|
+
getSessionTokenCount() {
|
|
10375
|
+
return this.tokenCounter.countMessageTokens(this.messages);
|
|
10376
|
+
}
|
|
9303
10377
|
logEntry(entry) {
|
|
9304
10378
|
try {
|
|
9305
10379
|
const dir = path7__default.dirname(this.sessionLogPath);
|
|
@@ -9318,6 +10392,43 @@ EOF`;
|
|
|
9318
10392
|
console.warn("Failed to log session entry:", error);
|
|
9319
10393
|
}
|
|
9320
10394
|
}
|
|
10395
|
+
/**
|
|
10396
|
+
* Load .agent context pack for enhanced recommendations
|
|
10397
|
+
*/
|
|
10398
|
+
async loadContextPack() {
|
|
10399
|
+
try {
|
|
10400
|
+
const contextLoader = await Promise.resolve().then(() => (init_context_loader(), context_loader_exports));
|
|
10401
|
+
return await contextLoader.loadContext(".agent");
|
|
10402
|
+
} catch (error) {
|
|
10403
|
+
console.warn("[Workflow] Failed to load context pack:", error);
|
|
10404
|
+
return void 0;
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10407
|
+
/**
|
|
10408
|
+
* Convert workflow results to chat entries for display
|
|
10409
|
+
*/
|
|
10410
|
+
workflowResultToChatEntries(userEntry, output, approval, executionResult) {
|
|
10411
|
+
const entries = [userEntry];
|
|
10412
|
+
const summaryEntry = {
|
|
10413
|
+
type: "assistant",
|
|
10414
|
+
content: `Workflow completed: ${executionResult?.success ? "\u2705 Success" : "\u274C Failed"}
|
|
10415
|
+
|
|
10416
|
+
${output?.plan?.summary || "Task completed"}`,
|
|
10417
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
10418
|
+
};
|
|
10419
|
+
entries.push(summaryEntry);
|
|
10420
|
+
this.chatHistory.push(summaryEntry);
|
|
10421
|
+
if (executionResult?.executionPlan) {
|
|
10422
|
+
const detailsEntry = {
|
|
10423
|
+
type: "assistant",
|
|
10424
|
+
content: `Executed ${executionResult.executionPlan.completedSteps}/${executionResult.executionPlan.totalSteps} tasks successfully.`,
|
|
10425
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
10426
|
+
};
|
|
10427
|
+
entries.push(detailsEntry);
|
|
10428
|
+
this.chatHistory.push(detailsEntry);
|
|
10429
|
+
}
|
|
10430
|
+
return entries;
|
|
10431
|
+
}
|
|
9321
10432
|
};
|
|
9322
10433
|
|
|
9323
10434
|
// src/utils/text-utils.ts
|
|
@@ -9924,7 +11035,7 @@ var CodebaseExplorer = class {
|
|
|
9924
11035
|
return files;
|
|
9925
11036
|
}
|
|
9926
11037
|
try {
|
|
9927
|
-
const entries = await
|
|
11038
|
+
const entries = await fs6.readdir(dirPath, { withFileTypes: true });
|
|
9928
11039
|
for (const entry of entries) {
|
|
9929
11040
|
const fullPath = path7__default.join(dirPath, entry.name);
|
|
9930
11041
|
const relativePath = path7__default.relative(options.rootPath, fullPath);
|
|
@@ -9945,7 +11056,7 @@ var CodebaseExplorer = class {
|
|
|
9945
11056
|
files.push(...subFiles);
|
|
9946
11057
|
} else {
|
|
9947
11058
|
try {
|
|
9948
|
-
const stats = await
|
|
11059
|
+
const stats = await fs6.stat(fullPath);
|
|
9949
11060
|
fileInfo.size = stats.size;
|
|
9950
11061
|
if (fileInfo.size > this.settings.maxFileSize) {
|
|
9951
11062
|
continue;
|
|
@@ -10214,7 +11325,7 @@ var CodebaseExplorer = class {
|
|
|
10214
11325
|
async detectProjectType(rootPath, files) {
|
|
10215
11326
|
const packageJsonPath = path7__default.join(rootPath, "package.json");
|
|
10216
11327
|
try {
|
|
10217
|
-
const packageJson = await
|
|
11328
|
+
const packageJson = await fs6.readFile(packageJsonPath, "utf-8");
|
|
10218
11329
|
const pkg = JSON.parse(packageJson);
|
|
10219
11330
|
if (pkg.dependencies?.react || pkg.devDependencies?.react) return "react";
|
|
10220
11331
|
if (pkg.dependencies?.vue || pkg.devDependencies?.vue) return "vue";
|
|
@@ -13454,7 +14565,7 @@ var ChangelogGenerator = class {
|
|
|
13454
14565
|
}
|
|
13455
14566
|
}
|
|
13456
14567
|
async getGitCommits() {
|
|
13457
|
-
const { execSync:
|
|
14568
|
+
const { execSync: execSync3 } = __require("child_process");
|
|
13458
14569
|
try {
|
|
13459
14570
|
let gitCommand2 = 'git log --pretty=format:"%H|%ad|%an|%s|%b" --date=short';
|
|
13460
14571
|
if (this.config.sinceVersion) {
|
|
@@ -13464,7 +14575,7 @@ var ChangelogGenerator = class {
|
|
|
13464
14575
|
} else {
|
|
13465
14576
|
gitCommand2 += " -n 50";
|
|
13466
14577
|
}
|
|
13467
|
-
const output =
|
|
14578
|
+
const output = execSync3(gitCommand2, {
|
|
13468
14579
|
cwd: this.config.rootPath,
|
|
13469
14580
|
encoding: "utf-8"
|
|
13470
14581
|
});
|
|
@@ -13699,9 +14810,9 @@ var UpdateAgentDocs = class {
|
|
|
13699
14810
|
hasNewFeatures: false
|
|
13700
14811
|
};
|
|
13701
14812
|
try {
|
|
13702
|
-
const { execSync:
|
|
14813
|
+
const { execSync: execSync3 } = __require("child_process");
|
|
13703
14814
|
try {
|
|
13704
|
-
const commits =
|
|
14815
|
+
const commits = execSync3("git log --oneline -10", {
|
|
13705
14816
|
cwd: this.config.rootPath,
|
|
13706
14817
|
encoding: "utf-8"
|
|
13707
14818
|
});
|
|
@@ -13709,7 +14820,7 @@ var UpdateAgentDocs = class {
|
|
|
13709
14820
|
} catch (error) {
|
|
13710
14821
|
}
|
|
13711
14822
|
try {
|
|
13712
|
-
const changedFiles =
|
|
14823
|
+
const changedFiles = execSync3("git diff --name-only HEAD~5..HEAD", {
|
|
13713
14824
|
cwd: this.config.rootPath,
|
|
13714
14825
|
encoding: "utf-8"
|
|
13715
14826
|
});
|
|
@@ -14686,8 +15797,8 @@ ${guardrail.createdFrom ? `- Created from incident: ${guardrail.createdFrom}` :
|
|
|
14686
15797
|
var package_default = {
|
|
14687
15798
|
type: "module",
|
|
14688
15799
|
name: "@xagent/x-cli",
|
|
14689
|
-
version: "1.1.
|
|
14690
|
-
description: "An open-source AI agent that brings
|
|
15800
|
+
version: "1.1.75",
|
|
15801
|
+
description: "An open-source AI agent that brings advanced AI capabilities directly into your terminal.",
|
|
14691
15802
|
main: "dist/index.js",
|
|
14692
15803
|
module: "dist/index.js",
|
|
14693
15804
|
types: "dist/index.d.ts",
|
|
@@ -14717,7 +15828,7 @@ var package_default = {
|
|
|
14717
15828
|
local: "npm run build > /dev/null 2>&1 && npm link > /dev/null 2>&1 && node dist/index.js",
|
|
14718
15829
|
"test:workflow": "node scripts/test-workflow.js",
|
|
14719
15830
|
"start:bun": "bun run dist/index.js",
|
|
14720
|
-
lint: "eslint . --ext .js,.jsx,.ts,.tsx",
|
|
15831
|
+
lint: "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern 'dist/**'",
|
|
14721
15832
|
typecheck: "tsc --noEmit",
|
|
14722
15833
|
"install:bun": "bun install",
|
|
14723
15834
|
preinstall: "echo '\u{1F916} Installing X CLI...'",
|
|
@@ -14730,10 +15841,10 @@ var package_default = {
|
|
|
14730
15841
|
},
|
|
14731
15842
|
"lint-staged": {
|
|
14732
15843
|
"*.{ts,tsx}": [
|
|
14733
|
-
"eslint --fix"
|
|
15844
|
+
"eslint --fix --ignore-pattern 'dist/**'"
|
|
14734
15845
|
],
|
|
14735
15846
|
"*.{js,jsx,mjs}": [
|
|
14736
|
-
"eslint --fix"
|
|
15847
|
+
"eslint --fix --ignore-pattern 'dist/**'"
|
|
14737
15848
|
],
|
|
14738
15849
|
"*.{md,mdx}": [
|
|
14739
15850
|
"prettier --write"
|
|
@@ -14746,10 +15857,10 @@ var package_default = {
|
|
|
14746
15857
|
"cli",
|
|
14747
15858
|
"agent",
|
|
14748
15859
|
"text-editor",
|
|
14749
|
-
"
|
|
14750
|
-
"ai"
|
|
15860
|
+
"ai",
|
|
15861
|
+
"x-ai"
|
|
14751
15862
|
],
|
|
14752
|
-
author: "
|
|
15863
|
+
author: "x-cli-team",
|
|
14753
15864
|
license: "MIT",
|
|
14754
15865
|
dependencies: {
|
|
14755
15866
|
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
@@ -14804,7 +15915,7 @@ var package_default = {
|
|
|
14804
15915
|
bugs: {
|
|
14805
15916
|
url: "https://github.com/x-cli-team/x-cli/issues"
|
|
14806
15917
|
},
|
|
14807
|
-
homepage: "https://
|
|
15918
|
+
homepage: "https://x-cli.dev",
|
|
14808
15919
|
icon: "docs/assets/logos/x-cli-logo.svg",
|
|
14809
15920
|
publishConfig: {
|
|
14810
15921
|
access: "public"
|
|
@@ -17216,11 +18327,11 @@ function useContextInfo(agent) {
|
|
|
17216
18327
|
if (agent) {
|
|
17217
18328
|
const modelName = agent.getCurrentModel?.() || "grok-code-fast-1";
|
|
17218
18329
|
const maxTokens = getMaxTokensForModel(modelName);
|
|
17219
|
-
const
|
|
17220
|
-
messagesCount =
|
|
17221
|
-
const tokenPercent = Math.round(
|
|
18330
|
+
const sessionTokens = agent.getSessionTokenCount?.() || 0;
|
|
18331
|
+
messagesCount = agent.getMessageCount?.() || 0;
|
|
18332
|
+
const tokenPercent = Math.round(sessionTokens / maxTokens * 100);
|
|
17222
18333
|
tokenUsage = {
|
|
17223
|
-
current:
|
|
18334
|
+
current: sessionTokens,
|
|
17224
18335
|
max: maxTokens,
|
|
17225
18336
|
percent: tokenPercent
|
|
17226
18337
|
};
|
|
@@ -19792,121 +20903,9 @@ function createToggleConfirmationsCommand() {
|
|
|
19792
20903
|
});
|
|
19793
20904
|
return toggleCommand;
|
|
19794
20905
|
}
|
|
19795
|
-
var CONTEXT_BUDGET_BYTES = 280 * 1024;
|
|
19796
|
-
var MAX_SUMMARY_LENGTH = 2e3;
|
|
19797
|
-
function loadMarkdownDirectory(dirPath) {
|
|
19798
|
-
if (!fs__default.existsSync(dirPath)) {
|
|
19799
|
-
return "";
|
|
19800
|
-
}
|
|
19801
|
-
const files = fs__default.readdirSync(dirPath).filter((file) => file.endsWith(".md")).sort();
|
|
19802
|
-
let content = "";
|
|
19803
|
-
for (const file of files) {
|
|
19804
|
-
const filePath = path7__default.join(dirPath, file);
|
|
19805
|
-
try {
|
|
19806
|
-
const fileContent = fs__default.readFileSync(filePath, "utf-8");
|
|
19807
|
-
content += `
|
|
19808
|
-
|
|
19809
|
-
=== ${file} ===
|
|
19810
|
-
|
|
19811
|
-
${fileContent}`;
|
|
19812
|
-
} catch (error) {
|
|
19813
|
-
console.warn(`Failed to read ${filePath}:`, error);
|
|
19814
|
-
}
|
|
19815
|
-
}
|
|
19816
|
-
return content;
|
|
19817
|
-
}
|
|
19818
|
-
function extractDateFromFilename(filename) {
|
|
19819
|
-
const match = filename.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
19820
|
-
if (match) {
|
|
19821
|
-
return new Date(match[1]);
|
|
19822
|
-
}
|
|
19823
|
-
return /* @__PURE__ */ new Date(0);
|
|
19824
|
-
}
|
|
19825
|
-
function summarizeContent(content, maxLength = MAX_SUMMARY_LENGTH) {
|
|
19826
|
-
if (content.length <= maxLength) {
|
|
19827
|
-
return content;
|
|
19828
|
-
}
|
|
19829
|
-
const truncated = content.substring(0, maxLength);
|
|
19830
|
-
const lastNewline = truncated.lastIndexOf("\n\n");
|
|
19831
|
-
if (lastNewline > maxLength * 0.8) {
|
|
19832
|
-
return truncated.substring(0, lastNewline);
|
|
19833
|
-
}
|
|
19834
|
-
return truncated + "\n\n[...content truncated for context budget...]";
|
|
19835
|
-
}
|
|
19836
|
-
function loadTaskFiles(tasksDir, maxBudget) {
|
|
19837
|
-
if (!fs__default.existsSync(tasksDir)) {
|
|
19838
|
-
return [];
|
|
19839
|
-
}
|
|
19840
|
-
const files = fs__default.readdirSync(tasksDir).filter((file) => file.endsWith(".md")).map((filename) => {
|
|
19841
|
-
const filePath = path7__default.join(tasksDir, filename);
|
|
19842
|
-
const content = fs__default.readFileSync(filePath, "utf-8");
|
|
19843
|
-
return {
|
|
19844
|
-
filename,
|
|
19845
|
-
content,
|
|
19846
|
-
size: Buffer.byteLength(content, "utf-8"),
|
|
19847
|
-
date: extractDateFromFilename(filename),
|
|
19848
|
-
isSummarized: false
|
|
19849
|
-
};
|
|
19850
|
-
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
19851
|
-
const result = [];
|
|
19852
|
-
let usedBudget = 0;
|
|
19853
|
-
for (const file of files) {
|
|
19854
|
-
let finalContent = file.content;
|
|
19855
|
-
let isSummarized = false;
|
|
19856
|
-
if (usedBudget + file.size > maxBudget) {
|
|
19857
|
-
finalContent = summarizeContent(file.content);
|
|
19858
|
-
const summarizedSize = Buffer.byteLength(finalContent, "utf-8");
|
|
19859
|
-
if (usedBudget + summarizedSize > maxBudget) {
|
|
19860
|
-
continue;
|
|
19861
|
-
}
|
|
19862
|
-
usedBudget += summarizedSize;
|
|
19863
|
-
isSummarized = true;
|
|
19864
|
-
} else {
|
|
19865
|
-
usedBudget += file.size;
|
|
19866
|
-
}
|
|
19867
|
-
result.push({
|
|
19868
|
-
...file,
|
|
19869
|
-
content: finalContent,
|
|
19870
|
-
isSummarized
|
|
19871
|
-
});
|
|
19872
|
-
}
|
|
19873
|
-
return result;
|
|
19874
|
-
}
|
|
19875
|
-
function loadContext(agentDir = ".agent") {
|
|
19876
|
-
const systemContent = loadMarkdownDirectory(path7__default.join(agentDir, "system"));
|
|
19877
|
-
const sopContent = loadMarkdownDirectory(path7__default.join(agentDir, "sop"));
|
|
19878
|
-
const systemSize = Buffer.byteLength(systemContent, "utf-8");
|
|
19879
|
-
const sopSize = Buffer.byteLength(sopContent, "utf-8");
|
|
19880
|
-
const taskBudget = Math.max(0, CONTEXT_BUDGET_BYTES - systemSize - sopSize);
|
|
19881
|
-
const tasks = loadTaskFiles(path7__default.join(agentDir, "tasks"), taskBudget);
|
|
19882
|
-
const totalSize = systemSize + sopSize + tasks.reduce((sum, task) => sum + Buffer.byteLength(task.content, "utf-8"), 0);
|
|
19883
|
-
const warnings = [];
|
|
19884
|
-
if (totalSize > CONTEXT_BUDGET_BYTES) {
|
|
19885
|
-
warnings.push(`Context size (${(totalSize / 1024).toFixed(1)}KB) exceeds budget (${CONTEXT_BUDGET_BYTES / 1024}KB)`);
|
|
19886
|
-
}
|
|
19887
|
-
return {
|
|
19888
|
-
system: systemContent,
|
|
19889
|
-
sop: sopContent,
|
|
19890
|
-
tasks,
|
|
19891
|
-
totalSize,
|
|
19892
|
-
warnings
|
|
19893
|
-
};
|
|
19894
|
-
}
|
|
19895
|
-
function formatContextStatus(pack) {
|
|
19896
|
-
const taskCount = pack.tasks.length;
|
|
19897
|
-
const summarizedCount = pack.tasks.filter((t) => t.isSummarized).length;
|
|
19898
|
-
const sizeKB = (pack.totalSize / 1024).toFixed(1);
|
|
19899
|
-
let status = `[x-cli] Context: loaded system docs, sop docs, ${taskCount} task docs (~${sizeKB} KB).`;
|
|
19900
|
-
if (summarizedCount > 0) {
|
|
19901
|
-
status += ` Summarized ${summarizedCount} older tasks for context budget.`;
|
|
19902
|
-
}
|
|
19903
|
-
if (pack.warnings.length > 0) {
|
|
19904
|
-
status += ` Warnings: ${pack.warnings.join("; ")}`;
|
|
19905
|
-
}
|
|
19906
|
-
return status;
|
|
19907
|
-
}
|
|
19908
20906
|
|
|
19909
20907
|
// src/index.ts
|
|
20908
|
+
init_context_loader();
|
|
19910
20909
|
dotenv.config();
|
|
19911
20910
|
process.on("SIGTERM", () => {
|
|
19912
20911
|
if (process.stdin.isTTY && process.stdin.setRawMode) {
|