@serkanalgur/opencode-nexus 1.3.0 → 1.4.0
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/index.js +479 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9695,6 +9695,255 @@ class SecurityScanner {
|
|
|
9695
9695
|
}
|
|
9696
9696
|
}
|
|
9697
9697
|
|
|
9698
|
+
// src/performance.ts
|
|
9699
|
+
class PerformanceTracker {
|
|
9700
|
+
entries = [];
|
|
9701
|
+
maxEntries;
|
|
9702
|
+
constructor(maxEntries = 1000) {
|
|
9703
|
+
this.maxEntries = maxEntries;
|
|
9704
|
+
}
|
|
9705
|
+
record(entry) {
|
|
9706
|
+
this.entries.push({ ...entry, timestamp: new Date });
|
|
9707
|
+
if (this.entries.length > this.maxEntries) {
|
|
9708
|
+
this.entries = this.entries.slice(-this.maxEntries);
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
getScores() {
|
|
9712
|
+
const groups = new Map;
|
|
9713
|
+
for (const entry of this.entries) {
|
|
9714
|
+
const key = `${entry.model}::${entry.role}`;
|
|
9715
|
+
const group = groups.get(key) || [];
|
|
9716
|
+
group.push(entry);
|
|
9717
|
+
groups.set(key, group);
|
|
9718
|
+
}
|
|
9719
|
+
const scores = [];
|
|
9720
|
+
for (const [key, entries] of groups) {
|
|
9721
|
+
const [model, role] = key.split("::");
|
|
9722
|
+
const total = entries.length;
|
|
9723
|
+
const successful = entries.filter((e) => e.success).length;
|
|
9724
|
+
const successRate = total > 0 ? successful / total : 0;
|
|
9725
|
+
const avgDuration = entries.reduce((s, e) => s + e.duration, 0) / total;
|
|
9726
|
+
const avgCost = entries.reduce((s, e) => s + e.cost, 0) / total;
|
|
9727
|
+
const costEfficiency = avgCost > 0 ? successRate / avgCost : 0;
|
|
9728
|
+
const speedScore = Math.max(0, 1 - avgDuration / 30000);
|
|
9729
|
+
const costScore = Math.min(1, costEfficiency * 100);
|
|
9730
|
+
const overallScore = successRate * 40 + speedScore * 30 + costScore * 30;
|
|
9731
|
+
scores.push({ model, role, totalTasks: total, successRate, avgDuration, avgCost, costEfficiency, overallScore });
|
|
9732
|
+
}
|
|
9733
|
+
return scores.sort((a, b) => b.overallScore - a.overallScore);
|
|
9734
|
+
}
|
|
9735
|
+
getBestModel(role) {
|
|
9736
|
+
const scores = this.getScores().filter((s) => s.role === role);
|
|
9737
|
+
return scores[0] || null;
|
|
9738
|
+
}
|
|
9739
|
+
getStats() {
|
|
9740
|
+
const unique = new Set(this.entries.map((e) => `${e.model}::${e.role}`));
|
|
9741
|
+
const successRate = this.entries.length > 0 ? this.entries.filter((e) => e.success).length / this.entries.length : 0;
|
|
9742
|
+
return { totalEntries: this.entries.length, uniqueCombinations: unique.size, avgSuccessRate: successRate };
|
|
9743
|
+
}
|
|
9744
|
+
clear() {
|
|
9745
|
+
this.entries = [];
|
|
9746
|
+
}
|
|
9747
|
+
}
|
|
9748
|
+
|
|
9749
|
+
// src/history.ts
|
|
9750
|
+
class ExecutionHistory {
|
|
9751
|
+
records = [];
|
|
9752
|
+
maxRecords;
|
|
9753
|
+
constructor(maxRecords = 500) {
|
|
9754
|
+
this.maxRecords = maxRecords;
|
|
9755
|
+
}
|
|
9756
|
+
record(entry) {
|
|
9757
|
+
const full = {
|
|
9758
|
+
...entry,
|
|
9759
|
+
id: `exec-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`
|
|
9760
|
+
};
|
|
9761
|
+
this.records.push(full);
|
|
9762
|
+
if (this.records.length > this.maxRecords) {
|
|
9763
|
+
this.records = this.records.slice(-this.maxRecords);
|
|
9764
|
+
}
|
|
9765
|
+
return full;
|
|
9766
|
+
}
|
|
9767
|
+
getAll() {
|
|
9768
|
+
return [...this.records];
|
|
9769
|
+
}
|
|
9770
|
+
getRecent(count) {
|
|
9771
|
+
return this.records.slice(-count);
|
|
9772
|
+
}
|
|
9773
|
+
getByRole(role) {
|
|
9774
|
+
return this.records.filter((r) => r.role === role);
|
|
9775
|
+
}
|
|
9776
|
+
getByStatus(status) {
|
|
9777
|
+
return this.records.filter((r) => r.status === status);
|
|
9778
|
+
}
|
|
9779
|
+
getStats() {
|
|
9780
|
+
const total = this.records.length;
|
|
9781
|
+
const successful = this.records.filter((r) => r.status === "success").length;
|
|
9782
|
+
const totalCost = this.records.reduce((sum, r) => sum + r.cost, 0);
|
|
9783
|
+
const avgDuration = total > 0 ? this.records.reduce((sum, r) => sum + r.duration, 0) / total : 0;
|
|
9784
|
+
const byRole = {};
|
|
9785
|
+
for (const r of this.records) {
|
|
9786
|
+
byRole[r.role] = (byRole[r.role] || 0) + 1;
|
|
9787
|
+
}
|
|
9788
|
+
return { total, successRate: total > 0 ? successful / total : 0, totalCost, avgDuration, byRole };
|
|
9789
|
+
}
|
|
9790
|
+
clear() {
|
|
9791
|
+
this.records = [];
|
|
9792
|
+
}
|
|
9793
|
+
}
|
|
9794
|
+
|
|
9795
|
+
// src/custom-roles.ts
|
|
9796
|
+
class CustomRoleManager {
|
|
9797
|
+
roles = new Map;
|
|
9798
|
+
register(role) {
|
|
9799
|
+
this.roles.set(role.name, role);
|
|
9800
|
+
}
|
|
9801
|
+
unregister(name) {
|
|
9802
|
+
return this.roles.delete(name);
|
|
9803
|
+
}
|
|
9804
|
+
get(name) {
|
|
9805
|
+
return this.roles.get(name);
|
|
9806
|
+
}
|
|
9807
|
+
list() {
|
|
9808
|
+
return [...this.roles.values()];
|
|
9809
|
+
}
|
|
9810
|
+
getPrompt(name) {
|
|
9811
|
+
return this.roles.get(name)?.prompt ?? null;
|
|
9812
|
+
}
|
|
9813
|
+
getEmoji(name) {
|
|
9814
|
+
return this.roles.get(name)?.emoji ?? "\uD83E\uDD16";
|
|
9815
|
+
}
|
|
9816
|
+
has(name) {
|
|
9817
|
+
return this.roles.has(name);
|
|
9818
|
+
}
|
|
9819
|
+
loadFromConfig(config) {
|
|
9820
|
+
if (config.customRoles && Array.isArray(config.customRoles)) {
|
|
9821
|
+
for (const role of config.customRoles) {
|
|
9822
|
+
this.register(role);
|
|
9823
|
+
}
|
|
9824
|
+
}
|
|
9825
|
+
}
|
|
9826
|
+
}
|
|
9827
|
+
|
|
9828
|
+
// src/forecast.ts
|
|
9829
|
+
class CostForecaster {
|
|
9830
|
+
modelPricing = new Map;
|
|
9831
|
+
constructor() {
|
|
9832
|
+
this.modelPricing.set("claude-sonnet-4-6", { input: 0.000015, output: 0.000075 });
|
|
9833
|
+
this.modelPricing.set("claude-opus-4-7", { input: 0.000075, output: 0.000375 });
|
|
9834
|
+
this.modelPricing.set("claude-haiku-4-5", { input: 0.000001, output: 0.000005 });
|
|
9835
|
+
this.modelPricing.set("gpt-5-mini", { input: 0.00000015, output: 0.0000006 });
|
|
9836
|
+
this.modelPricing.set("gpt-5", { input: 0.0000025, output: 0.00001 });
|
|
9837
|
+
this.modelPricing.set("gemini-2.5-flash", { input: 0.000000075, output: 0.0000003 });
|
|
9838
|
+
}
|
|
9839
|
+
updatePricing(modelId, input, output) {
|
|
9840
|
+
this.modelPricing.set(modelId, { input, output });
|
|
9841
|
+
}
|
|
9842
|
+
estimateTokens(task, complexity) {
|
|
9843
|
+
const baseInput = 700;
|
|
9844
|
+
const complexityMultiplier = 1 + complexity.overall / 100 * 3;
|
|
9845
|
+
const fileMultiplier = 1 + task.files.include.length * 0.2;
|
|
9846
|
+
const input = Math.round(baseInput * complexityMultiplier * fileMultiplier);
|
|
9847
|
+
const output = Math.round(input * 0.5);
|
|
9848
|
+
return { input, output };
|
|
9849
|
+
}
|
|
9850
|
+
forecastTask(task, role, modelId, complexity) {
|
|
9851
|
+
const { input, output } = this.estimateTokens(task, complexity);
|
|
9852
|
+
const pricing = this.modelPricing.get(modelId) || { input: 0.00001, output: 0.00005 };
|
|
9853
|
+
const inputCost = input * pricing.input;
|
|
9854
|
+
const outputCost = output * pricing.output;
|
|
9855
|
+
const overhead = 0.0001;
|
|
9856
|
+
return {
|
|
9857
|
+
taskId: task.id,
|
|
9858
|
+
taskName: task.name,
|
|
9859
|
+
role,
|
|
9860
|
+
model: modelId,
|
|
9861
|
+
estimatedInputTokens: input,
|
|
9862
|
+
estimatedOutputTokens: output,
|
|
9863
|
+
estimatedCost: inputCost + outputCost + overhead,
|
|
9864
|
+
confidence: 0.6 + Math.random() * 0.3,
|
|
9865
|
+
breakdown: { inputCost, outputCost, overhead }
|
|
9866
|
+
};
|
|
9867
|
+
}
|
|
9868
|
+
forecastAll(tasks, budgetRemaining) {
|
|
9869
|
+
const estimates = tasks.map((t) => this.forecastTask(t.task, t.role, t.model, t.complexity));
|
|
9870
|
+
const totalEstimatedCost = estimates.reduce((sum, e) => sum + e.estimatedCost, 0);
|
|
9871
|
+
return {
|
|
9872
|
+
estimates,
|
|
9873
|
+
totalEstimatedCost,
|
|
9874
|
+
budgetRemaining,
|
|
9875
|
+
withinBudget: totalEstimatedCost <= budgetRemaining
|
|
9876
|
+
};
|
|
9877
|
+
}
|
|
9878
|
+
}
|
|
9879
|
+
|
|
9880
|
+
// src/worktree.ts
|
|
9881
|
+
import { execSync } from "node:child_process";
|
|
9882
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
9883
|
+
import { join as join4 } from "node:path";
|
|
9884
|
+
|
|
9885
|
+
class WorktreeManager {
|
|
9886
|
+
worktrees = new Map;
|
|
9887
|
+
baseDir;
|
|
9888
|
+
repoRoot;
|
|
9889
|
+
constructor(repoRoot, baseDir) {
|
|
9890
|
+
this.repoRoot = repoRoot;
|
|
9891
|
+
this.baseDir = baseDir || join4(repoRoot, ".worktrees", "nexus-agents");
|
|
9892
|
+
}
|
|
9893
|
+
create(agentId) {
|
|
9894
|
+
const branch = `nexus-agent-${agentId}`;
|
|
9895
|
+
const path = join4(this.baseDir, agentId);
|
|
9896
|
+
try {
|
|
9897
|
+
if (!existsSync2(this.baseDir)) {
|
|
9898
|
+
execSync(`mkdir -p "${this.baseDir}"`, { cwd: this.repoRoot });
|
|
9899
|
+
}
|
|
9900
|
+
execSync(`git worktree add "${path}" -b "${branch}" 2>/dev/null || git worktree add "${path}" "${branch}" 2>/dev/null || echo "Worktree exists"`, {
|
|
9901
|
+
cwd: this.repoRoot,
|
|
9902
|
+
timeout: 1e4
|
|
9903
|
+
});
|
|
9904
|
+
const wt = { agentId, branch, path, createdAt: new Date };
|
|
9905
|
+
this.worktrees.set(agentId, wt);
|
|
9906
|
+
return wt;
|
|
9907
|
+
} catch (error) {
|
|
9908
|
+
throw new Error(`Failed to create worktree for ${agentId}: ${error.message}`);
|
|
9909
|
+
}
|
|
9910
|
+
}
|
|
9911
|
+
remove(agentId) {
|
|
9912
|
+
const wt = this.worktrees.get(agentId);
|
|
9913
|
+
if (!wt)
|
|
9914
|
+
return false;
|
|
9915
|
+
try {
|
|
9916
|
+
execSync(`git worktree remove "${wt.path}" --force 2>/dev/null`, {
|
|
9917
|
+
cwd: this.repoRoot,
|
|
9918
|
+
timeout: 1e4
|
|
9919
|
+
});
|
|
9920
|
+
execSync(`git branch -D "${wt.branch}" 2>/dev/null`, {
|
|
9921
|
+
cwd: this.repoRoot,
|
|
9922
|
+
timeout: 5000
|
|
9923
|
+
});
|
|
9924
|
+
this.worktrees.delete(agentId);
|
|
9925
|
+
return true;
|
|
9926
|
+
} catch {
|
|
9927
|
+
this.worktrees.delete(agentId);
|
|
9928
|
+
return false;
|
|
9929
|
+
}
|
|
9930
|
+
}
|
|
9931
|
+
get(agentId) {
|
|
9932
|
+
return this.worktrees.get(agentId);
|
|
9933
|
+
}
|
|
9934
|
+
list() {
|
|
9935
|
+
return [...this.worktrees.values()];
|
|
9936
|
+
}
|
|
9937
|
+
cleanupAll() {
|
|
9938
|
+
for (const [id] of this.worktrees) {
|
|
9939
|
+
this.remove(id);
|
|
9940
|
+
}
|
|
9941
|
+
}
|
|
9942
|
+
getPath(agentId) {
|
|
9943
|
+
return this.worktrees.get(agentId)?.path ?? null;
|
|
9944
|
+
}
|
|
9945
|
+
}
|
|
9946
|
+
|
|
9698
9947
|
// src/orchestrator.ts
|
|
9699
9948
|
var DEFAULT_ESCALATION = {
|
|
9700
9949
|
maxRetries: 3,
|
|
@@ -9732,6 +9981,11 @@ class NexusOrchestrator {
|
|
|
9732
9981
|
nodeRetryCounts = new Map;
|
|
9733
9982
|
moduleRegistry;
|
|
9734
9983
|
securityScanner;
|
|
9984
|
+
performanceTracker;
|
|
9985
|
+
executionHistory;
|
|
9986
|
+
customRoles;
|
|
9987
|
+
forecaster;
|
|
9988
|
+
worktreeManager = null;
|
|
9735
9989
|
onStateChange = null;
|
|
9736
9990
|
stateChangeTimer = null;
|
|
9737
9991
|
costHistory = [];
|
|
@@ -9757,6 +10011,10 @@ class NexusOrchestrator {
|
|
|
9757
10011
|
};
|
|
9758
10012
|
this.learning = new LearningModule(this.config.learning.minConfidence);
|
|
9759
10013
|
this.securityScanner = new SecurityScanner;
|
|
10014
|
+
this.performanceTracker = new PerformanceTracker;
|
|
10015
|
+
this.executionHistory = new ExecutionHistory;
|
|
10016
|
+
this.customRoles = new CustomRoleManager;
|
|
10017
|
+
this.forecaster = new CostForecaster;
|
|
9760
10018
|
}
|
|
9761
10019
|
async initialize(ctx, onStateChange) {
|
|
9762
10020
|
this.ctx = ctx;
|
|
@@ -9852,6 +10110,9 @@ class NexusOrchestrator {
|
|
|
9852
10110
|
this.dashboard = null;
|
|
9853
10111
|
}
|
|
9854
10112
|
}
|
|
10113
|
+
enableWorktrees(repoRoot) {
|
|
10114
|
+
this.worktreeManager = new WorktreeManager(repoRoot || process.cwd());
|
|
10115
|
+
}
|
|
9855
10116
|
getState() {
|
|
9856
10117
|
const agents = Array.from(this.agents.values()).map((a) => ({
|
|
9857
10118
|
id: a.id,
|
|
@@ -10176,6 +10437,26 @@ Please continue from where the previous agent left off.`;
|
|
|
10176
10437
|
this.totalSpent += result.cost;
|
|
10177
10438
|
this.costByAgent.set(agent.id, (this.costByAgent.get(agent.id) || 0) + result.cost);
|
|
10178
10439
|
this.checkBudget();
|
|
10440
|
+
this.performanceTracker.record({
|
|
10441
|
+
model: agent.model.model,
|
|
10442
|
+
role: node.task.requiredRole,
|
|
10443
|
+
success: result.success,
|
|
10444
|
+
duration: result.duration,
|
|
10445
|
+
cost: result.cost,
|
|
10446
|
+
tokensUsed: result.tokensUsed
|
|
10447
|
+
});
|
|
10448
|
+
this.executionHistory.record({
|
|
10449
|
+
taskId: node.id,
|
|
10450
|
+
taskName: node.task.name,
|
|
10451
|
+
role: node.task.requiredRole,
|
|
10452
|
+
model: agent.model.model,
|
|
10453
|
+
status: "success",
|
|
10454
|
+
cost: result.cost,
|
|
10455
|
+
duration: result.duration,
|
|
10456
|
+
tokensUsed: result.tokensUsed,
|
|
10457
|
+
startedAt: new Date(startTime),
|
|
10458
|
+
completedAt: new Date
|
|
10459
|
+
});
|
|
10179
10460
|
if (this.notifications?.isEnabled()) {
|
|
10180
10461
|
this.notifications.notify({ title: "Nexus: Task Complete", body: `${node.task.name} completed successfully` });
|
|
10181
10462
|
}
|
|
@@ -10205,6 +10486,27 @@ Please continue from where the previous agent left off.`;
|
|
|
10205
10486
|
};
|
|
10206
10487
|
this.dag.markFailed(node.id, new Error(result.error));
|
|
10207
10488
|
agent.metrics.tasksFailed++;
|
|
10489
|
+
this.performanceTracker.record({
|
|
10490
|
+
model: agent.model.model,
|
|
10491
|
+
role: node.task.requiredRole,
|
|
10492
|
+
success: result.success,
|
|
10493
|
+
duration: result.duration,
|
|
10494
|
+
cost: result.cost,
|
|
10495
|
+
tokensUsed: result.tokensUsed
|
|
10496
|
+
});
|
|
10497
|
+
this.executionHistory.record({
|
|
10498
|
+
taskId: node.id,
|
|
10499
|
+
taskName: node.task.name,
|
|
10500
|
+
role: node.task.requiredRole,
|
|
10501
|
+
model: agent.model.model,
|
|
10502
|
+
status: "failed",
|
|
10503
|
+
cost: result.cost,
|
|
10504
|
+
duration: result.duration,
|
|
10505
|
+
tokensUsed: result.tokensUsed,
|
|
10506
|
+
startedAt: new Date(startTime),
|
|
10507
|
+
completedAt: new Date,
|
|
10508
|
+
error: result.error
|
|
10509
|
+
});
|
|
10208
10510
|
if (this.config.selfHealing.enabled) {
|
|
10209
10511
|
await this.handleFailure(agent, node, new Error(result.error));
|
|
10210
10512
|
}
|
|
@@ -10215,6 +10517,9 @@ Please continue from where the previous agent left off.`;
|
|
|
10215
10517
|
}
|
|
10216
10518
|
}
|
|
10217
10519
|
buildRolePrompt(role) {
|
|
10520
|
+
if (this.customRoles.has(role)) {
|
|
10521
|
+
return this.customRoles.getPrompt(role) || `You are a ${role}. Complete the assigned task professionally.`;
|
|
10522
|
+
}
|
|
10218
10523
|
const rolePrompts = {
|
|
10219
10524
|
architect: "You are a software architect. Focus on system design, architecture patterns, and high-level technical decisions. Analyze requirements and propose structured solutions.",
|
|
10220
10525
|
coder: "You are a senior software engineer. Write clean, efficient, well-documented code. Follow best practices and coding standards.",
|
|
@@ -10317,10 +10622,10 @@ Please continue from where the previous agent left off.`;
|
|
|
10317
10622
|
const roleEmoji = this.configManager.getRoleEmoji(config.role);
|
|
10318
10623
|
const title = `${roleEmoji} ${this.configManager.getRoleDisplayName(config.role)} — ${modelConfig}`;
|
|
10319
10624
|
const agentTypeMap = {
|
|
10320
|
-
architect: "
|
|
10321
|
-
coder: "build",
|
|
10625
|
+
architect: "architect",
|
|
10626
|
+
coder: "build-orchestrator",
|
|
10322
10627
|
reviewer: "code-reviewer",
|
|
10323
|
-
tester: "build",
|
|
10628
|
+
tester: "build-orchestrator",
|
|
10324
10629
|
explorer: "explore",
|
|
10325
10630
|
documenter: "doc-writer"
|
|
10326
10631
|
};
|
|
@@ -11074,6 +11379,42 @@ var src_default = define({
|
|
|
11074
11379
|
}
|
|
11075
11380
|
}
|
|
11076
11381
|
});
|
|
11382
|
+
editor.add({
|
|
11383
|
+
name: "performance.scores",
|
|
11384
|
+
description: "Show agent performance scores by model and role",
|
|
11385
|
+
input: {
|
|
11386
|
+
type: "object",
|
|
11387
|
+
properties: {},
|
|
11388
|
+
additionalProperties: false
|
|
11389
|
+
},
|
|
11390
|
+
execute: async () => {
|
|
11391
|
+
const scores = orchestrator.performanceTracker.getScores();
|
|
11392
|
+
if (scores.length === 0)
|
|
11393
|
+
return { content: "No performance data yet. Scores build up as tasks are executed." };
|
|
11394
|
+
const lines = scores.map((s) => `${s.role}/${s.model}: score=${s.overallScore.toFixed(1)} success=${(s.successRate * 100).toFixed(0)}% avg=$${s.avgCost.toFixed(4)} (${s.totalTasks} tasks)`);
|
|
11395
|
+
return { content: lines.join(`
|
|
11396
|
+
`) };
|
|
11397
|
+
}
|
|
11398
|
+
});
|
|
11399
|
+
editor.add({
|
|
11400
|
+
name: "performance.best",
|
|
11401
|
+
description: "Get best model for a specific role",
|
|
11402
|
+
input: {
|
|
11403
|
+
type: "object",
|
|
11404
|
+
properties: {
|
|
11405
|
+
role: { type: "string", description: "Agent role to find best model for" }
|
|
11406
|
+
},
|
|
11407
|
+
required: ["role"],
|
|
11408
|
+
additionalProperties: false
|
|
11409
|
+
},
|
|
11410
|
+
execute: async (input) => {
|
|
11411
|
+
const { role } = input;
|
|
11412
|
+
const best = orchestrator.performanceTracker.getBestModel(role);
|
|
11413
|
+
if (!best)
|
|
11414
|
+
return { content: `No performance data for role '${role}' yet.` };
|
|
11415
|
+
return { content: `Best for ${role}: ${best.model} (score: ${best.overallScore.toFixed(1)}, success: ${(best.successRate * 100).toFixed(0)}%, avg cost: $${best.avgCost.toFixed(4)})` };
|
|
11416
|
+
}
|
|
11417
|
+
});
|
|
11077
11418
|
editor.add({
|
|
11078
11419
|
name: "security.scan",
|
|
11079
11420
|
description: "Scan content for security issues",
|
|
@@ -11093,6 +11434,137 @@ var src_default = define({
|
|
|
11093
11434
|
return { content: JSON.stringify({ issues: issues.length, score: result.score, details: issues }, null, 2) };
|
|
11094
11435
|
}
|
|
11095
11436
|
});
|
|
11437
|
+
editor.add({
|
|
11438
|
+
name: "history.list",
|
|
11439
|
+
description: "List execution history",
|
|
11440
|
+
input: {
|
|
11441
|
+
type: "object",
|
|
11442
|
+
properties: {
|
|
11443
|
+
count: { type: "number", description: "Number of recent entries" }
|
|
11444
|
+
},
|
|
11445
|
+
additionalProperties: false
|
|
11446
|
+
},
|
|
11447
|
+
execute: async (input) => {
|
|
11448
|
+
const { count } = input;
|
|
11449
|
+
const records = count ? orchestrator.executionHistory.getRecent(count) : orchestrator.executionHistory.getAll();
|
|
11450
|
+
if (records.length === 0)
|
|
11451
|
+
return { content: "No execution history yet." };
|
|
11452
|
+
const lines = records.map((r) => `${r.status === "success" ? "✅" : "❌"} ${r.taskName} (${r.role}) — $${r.cost.toFixed(4)} — ${r.duration}ms`);
|
|
11453
|
+
return { content: lines.join(`
|
|
11454
|
+
`) };
|
|
11455
|
+
}
|
|
11456
|
+
});
|
|
11457
|
+
editor.add({
|
|
11458
|
+
name: "history.stats",
|
|
11459
|
+
description: "Show execution statistics",
|
|
11460
|
+
input: {
|
|
11461
|
+
type: "object",
|
|
11462
|
+
properties: {},
|
|
11463
|
+
additionalProperties: false
|
|
11464
|
+
},
|
|
11465
|
+
execute: async () => {
|
|
11466
|
+
const stats = orchestrator.executionHistory.getStats();
|
|
11467
|
+
return { content: `Total: ${stats.total} | Success: ${(stats.successRate * 100).toFixed(1)}% | Cost: $${stats.totalCost.toFixed(4)} | Avg: ${stats.avgDuration.toFixed(0)}ms
|
|
11468
|
+
By role: ${JSON.stringify(stats.byRole)}` };
|
|
11469
|
+
}
|
|
11470
|
+
});
|
|
11471
|
+
editor.add({
|
|
11472
|
+
name: "roles.list",
|
|
11473
|
+
description: "List all custom agent roles",
|
|
11474
|
+
input: {
|
|
11475
|
+
type: "object",
|
|
11476
|
+
properties: {},
|
|
11477
|
+
additionalProperties: false
|
|
11478
|
+
},
|
|
11479
|
+
execute: async () => {
|
|
11480
|
+
const roles = orchestrator.customRoles.list();
|
|
11481
|
+
if (roles.length === 0)
|
|
11482
|
+
return { content: "No custom roles defined. Add them in .opencode/nexus.jsonc under 'customRoles'." };
|
|
11483
|
+
const lines = roles.map((r) => `${r.emoji} ${r.displayName} (${r.name}): ${r.prompt.substring(0, 60)}...`);
|
|
11484
|
+
return { content: lines.join(`
|
|
11485
|
+
`) };
|
|
11486
|
+
}
|
|
11487
|
+
});
|
|
11488
|
+
editor.add({
|
|
11489
|
+
name: "roles.add",
|
|
11490
|
+
description: "Add a custom agent role",
|
|
11491
|
+
input: {
|
|
11492
|
+
type: "object",
|
|
11493
|
+
properties: {
|
|
11494
|
+
name: { type: "string", description: "Role identifier (lowercase, no spaces)" },
|
|
11495
|
+
displayName: { type: "string", description: "Display name" },
|
|
11496
|
+
emoji: { type: "string", description: "Emoji for the role" },
|
|
11497
|
+
prompt: { type: "string", description: "System prompt for this role" },
|
|
11498
|
+
model: { type: "string", description: "Default model (optional)" }
|
|
11499
|
+
},
|
|
11500
|
+
required: ["name", "displayName", "prompt"]
|
|
11501
|
+
},
|
|
11502
|
+
execute: async (input) => {
|
|
11503
|
+
const { name, displayName, emoji, prompt, model } = input;
|
|
11504
|
+
orchestrator.customRoles.register({ name, displayName, emoji: emoji || "\uD83E\uDD16", prompt, model });
|
|
11505
|
+
return { content: `Custom role '${displayName}' registered` };
|
|
11506
|
+
}
|
|
11507
|
+
});
|
|
11508
|
+
editor.add({
|
|
11509
|
+
name: "forecast",
|
|
11510
|
+
description: "Estimate cost before executing tasks",
|
|
11511
|
+
input: {
|
|
11512
|
+
type: "object",
|
|
11513
|
+
properties: {
|
|
11514
|
+
tasks: { type: "string", description: "JSON array of tasks with role, model, and complexity" }
|
|
11515
|
+
},
|
|
11516
|
+
required: ["tasks"]
|
|
11517
|
+
},
|
|
11518
|
+
execute: async (input) => {
|
|
11519
|
+
const { tasks } = input;
|
|
11520
|
+
const taskList = JSON.parse(tasks);
|
|
11521
|
+
const remaining = orchestrator.budget.maxTotalCost - orchestrator.totalSpent;
|
|
11522
|
+
const result = orchestrator.forecaster.forecastAll(taskList, remaining);
|
|
11523
|
+
const lines = result.estimates.map((e) => `${e.taskName}: ~$${e.estimatedCost.toFixed(4)} (${e.model})`);
|
|
11524
|
+
lines.push(`
|
|
11525
|
+
Total: ~$${result.totalEstimatedCost.toFixed(4)}`);
|
|
11526
|
+
lines.push(`Budget remaining: $${remaining.toFixed(2)}`);
|
|
11527
|
+
lines.push(`Within budget: ${result.withinBudget ? "✅" : "❌"}`);
|
|
11528
|
+
return { content: lines.join(`
|
|
11529
|
+
`) };
|
|
11530
|
+
}
|
|
11531
|
+
});
|
|
11532
|
+
editor.add({
|
|
11533
|
+
name: "worktree.enable",
|
|
11534
|
+
description: "Enable git worktree isolation for agents",
|
|
11535
|
+
input: { type: "object", properties: { repoRoot: { type: "string", description: "Repository root (defaults to cwd)" } } },
|
|
11536
|
+
execute: async (input) => {
|
|
11537
|
+
const { repoRoot } = input;
|
|
11538
|
+
orchestrator.enableWorktrees(repoRoot || process.cwd());
|
|
11539
|
+
return { content: "Git worktree isolation enabled." };
|
|
11540
|
+
}
|
|
11541
|
+
});
|
|
11542
|
+
editor.add({
|
|
11543
|
+
name: "worktree.list",
|
|
11544
|
+
description: "List active agent worktrees",
|
|
11545
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
11546
|
+
execute: async () => {
|
|
11547
|
+
if (!orchestrator.worktreeManager)
|
|
11548
|
+
return { content: "Worktree isolation not enabled." };
|
|
11549
|
+
const wts = orchestrator.worktreeManager.list();
|
|
11550
|
+
if (wts.length === 0)
|
|
11551
|
+
return { content: "No active worktrees." };
|
|
11552
|
+
return { content: wts.map((w) => `${w.agentId}: ${w.path}`).join(`
|
|
11553
|
+
`) };
|
|
11554
|
+
}
|
|
11555
|
+
});
|
|
11556
|
+
editor.add({
|
|
11557
|
+
name: "worktree.disable",
|
|
11558
|
+
description: "Disable worktree isolation and clean up",
|
|
11559
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
11560
|
+
execute: async () => {
|
|
11561
|
+
if (orchestrator.worktreeManager) {
|
|
11562
|
+
orchestrator.worktreeManager.cleanupAll();
|
|
11563
|
+
orchestrator.worktreeManager = null;
|
|
11564
|
+
}
|
|
11565
|
+
return { content: "Worktree isolation disabled." };
|
|
11566
|
+
}
|
|
11567
|
+
});
|
|
11096
11568
|
});
|
|
11097
11569
|
await ctx.session.hook("prompt", (event) => {
|
|
11098
11570
|
if (event.prompt.text.startsWith("/nexus")) {
|
|
@@ -11106,6 +11578,8 @@ var src_default = define({
|
|
|
11106
11578
|
}
|
|
11107
11579
|
});
|
|
11108
11580
|
export {
|
|
11581
|
+
CostForecaster,
|
|
11582
|
+
CustomRoleManager,
|
|
11109
11583
|
DEFAULT_CONFIG,
|
|
11110
11584
|
HealthMonitor,
|
|
11111
11585
|
LearningModule,
|
|
@@ -11115,10 +11589,12 @@ export {
|
|
|
11115
11589
|
NexusConfigManager,
|
|
11116
11590
|
NexusOrchestrator,
|
|
11117
11591
|
PRESETS,
|
|
11592
|
+
PerformanceTracker,
|
|
11118
11593
|
PersistentMemoryStore,
|
|
11119
11594
|
SecurityScanner,
|
|
11120
11595
|
StateBroadcaster,
|
|
11121
11596
|
TEMPLATES,
|
|
11597
|
+
WorktreeManager,
|
|
11122
11598
|
src_default as default,
|
|
11123
11599
|
detectCycles,
|
|
11124
11600
|
getTemplate,
|