@serkanalgur/opencode-nexus 1.2.7 → 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 +572 -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 = [];
|
|
@@ -9740,6 +9994,7 @@ class NexusOrchestrator {
|
|
|
9740
9994
|
return this._healthMonitor;
|
|
9741
9995
|
}
|
|
9742
9996
|
notifications = null;
|
|
9997
|
+
modelCosts = new Map;
|
|
9743
9998
|
constructor(config, messageStoreConfig, memoryStoreConfig) {
|
|
9744
9999
|
this.config = this.mergeConfig(config);
|
|
9745
10000
|
this.budget = this.config.budget;
|
|
@@ -9756,11 +10011,16 @@ class NexusOrchestrator {
|
|
|
9756
10011
|
};
|
|
9757
10012
|
this.learning = new LearningModule(this.config.learning.minConfidence);
|
|
9758
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;
|
|
9759
10018
|
}
|
|
9760
10019
|
async initialize(ctx, onStateChange) {
|
|
9761
10020
|
this.ctx = ctx;
|
|
9762
10021
|
this.onStateChange = onStateChange ?? null;
|
|
9763
10022
|
this.configManager.loadFromPath(process.cwd());
|
|
10023
|
+
await this.loadModelCosts();
|
|
9764
10024
|
this.cleanupInterval = setInterval(() => this.cleanupStaleData(), 300000);
|
|
9765
10025
|
this._healthMonitor = new HealthMonitor({
|
|
9766
10026
|
checkInterval: this.config.agents.healthCheckInterval
|
|
@@ -9776,6 +10036,56 @@ class NexusOrchestrator {
|
|
|
9776
10036
|
};
|
|
9777
10037
|
await this.moduleRegistry.setupAll(moduleCtx);
|
|
9778
10038
|
}
|
|
10039
|
+
async loadModelCosts() {
|
|
10040
|
+
try {
|
|
10041
|
+
if (!this.ctx)
|
|
10042
|
+
return;
|
|
10043
|
+
if (this.ctx.client?.model?.list) {
|
|
10044
|
+
const result = await this.ctx.client.model.list();
|
|
10045
|
+
const models = result?.data?.data ?? result?.data ?? [];
|
|
10046
|
+
if (Array.isArray(models)) {
|
|
10047
|
+
for (const model of models) {
|
|
10048
|
+
if (model.cost && Array.isArray(model.cost) && model.cost.length > 0) {
|
|
10049
|
+
const baseCost = model.cost[0];
|
|
10050
|
+
this.modelCosts.set(model.id, {
|
|
10051
|
+
input: baseCost.input || 0,
|
|
10052
|
+
output: baseCost.output || 0,
|
|
10053
|
+
cacheRead: baseCost.cache?.read || 0,
|
|
10054
|
+
cacheWrite: baseCost.cache?.write || 0
|
|
10055
|
+
});
|
|
10056
|
+
}
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
10059
|
+
return;
|
|
10060
|
+
}
|
|
10061
|
+
const location = this.ctx.location ?? this.ctx.data?.location?.default();
|
|
10062
|
+
if (location && this.ctx.data?.location?.model) {
|
|
10063
|
+
await this.ctx.data.location.model.sync(location);
|
|
10064
|
+
const models = this.ctx.data.location.model.list(location) ?? [];
|
|
10065
|
+
for (const model of models) {
|
|
10066
|
+
if (model.cost && Array.isArray(model.cost) && model.cost.length > 0) {
|
|
10067
|
+
const baseCost = model.cost[0];
|
|
10068
|
+
this.modelCosts.set(model.id, {
|
|
10069
|
+
input: baseCost.input || 0,
|
|
10070
|
+
output: baseCost.output || 0,
|
|
10071
|
+
cacheRead: baseCost.cache?.read || 0,
|
|
10072
|
+
cacheWrite: baseCost.cache?.write || 0
|
|
10073
|
+
});
|
|
10074
|
+
}
|
|
10075
|
+
}
|
|
10076
|
+
}
|
|
10077
|
+
} catch {}
|
|
10078
|
+
}
|
|
10079
|
+
setModelCosts(costs) {
|
|
10080
|
+
for (const [model, cost] of Object.entries(costs)) {
|
|
10081
|
+
this.modelCosts.set(model, {
|
|
10082
|
+
input: cost.input,
|
|
10083
|
+
output: cost.output,
|
|
10084
|
+
cacheRead: cost.cacheRead || 0,
|
|
10085
|
+
cacheWrite: cost.cacheWrite || 0
|
|
10086
|
+
});
|
|
10087
|
+
}
|
|
10088
|
+
}
|
|
9779
10089
|
initBroadcaster(opts) {
|
|
9780
10090
|
this.broadcaster = new StateBroadcaster(this, opts);
|
|
9781
10091
|
const previousOnStateChange = this.onStateChange;
|
|
@@ -9800,6 +10110,9 @@ class NexusOrchestrator {
|
|
|
9800
10110
|
this.dashboard = null;
|
|
9801
10111
|
}
|
|
9802
10112
|
}
|
|
10113
|
+
enableWorktrees(repoRoot) {
|
|
10114
|
+
this.worktreeManager = new WorktreeManager(repoRoot || process.cwd());
|
|
10115
|
+
}
|
|
9803
10116
|
getState() {
|
|
9804
10117
|
const agents = Array.from(this.agents.values()).map((a) => ({
|
|
9805
10118
|
id: a.id,
|
|
@@ -10124,6 +10437,26 @@ Please continue from where the previous agent left off.`;
|
|
|
10124
10437
|
this.totalSpent += result.cost;
|
|
10125
10438
|
this.costByAgent.set(agent.id, (this.costByAgent.get(agent.id) || 0) + result.cost);
|
|
10126
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
|
+
});
|
|
10127
10460
|
if (this.notifications?.isEnabled()) {
|
|
10128
10461
|
this.notifications.notify({ title: "Nexus: Task Complete", body: `${node.task.name} completed successfully` });
|
|
10129
10462
|
}
|
|
@@ -10153,6 +10486,27 @@ Please continue from where the previous agent left off.`;
|
|
|
10153
10486
|
};
|
|
10154
10487
|
this.dag.markFailed(node.id, new Error(result.error));
|
|
10155
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
|
+
});
|
|
10156
10510
|
if (this.config.selfHealing.enabled) {
|
|
10157
10511
|
await this.handleFailure(agent, node, new Error(result.error));
|
|
10158
10512
|
}
|
|
@@ -10163,6 +10517,9 @@ Please continue from where the previous agent left off.`;
|
|
|
10163
10517
|
}
|
|
10164
10518
|
}
|
|
10165
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
|
+
}
|
|
10166
10523
|
const rolePrompts = {
|
|
10167
10524
|
architect: "You are a software architect. Focus on system design, architecture patterns, and high-level technical decisions. Analyze requirements and propose structured solutions.",
|
|
10168
10525
|
coder: "You are a senior software engineer. Write clean, efficient, well-documented code. Follow best practices and coding standards.",
|
|
@@ -10265,10 +10622,10 @@ Please continue from where the previous agent left off.`;
|
|
|
10265
10622
|
const roleEmoji = this.configManager.getRoleEmoji(config.role);
|
|
10266
10623
|
const title = `${roleEmoji} ${this.configManager.getRoleDisplayName(config.role)} — ${modelConfig}`;
|
|
10267
10624
|
const agentTypeMap = {
|
|
10268
|
-
architect: "
|
|
10269
|
-
coder: "build",
|
|
10625
|
+
architect: "architect",
|
|
10626
|
+
coder: "build-orchestrator",
|
|
10270
10627
|
reviewer: "code-reviewer",
|
|
10271
|
-
tester: "build",
|
|
10628
|
+
tester: "build-orchestrator",
|
|
10272
10629
|
explorer: "explore",
|
|
10273
10630
|
documenter: "doc-writer"
|
|
10274
10631
|
};
|
|
@@ -10354,6 +10711,10 @@ Please continue from where the previous agent left off.`;
|
|
|
10354
10711
|
return this.selectBestModel(role, complexity);
|
|
10355
10712
|
}
|
|
10356
10713
|
estimateModelCost(model) {
|
|
10714
|
+
const realCost = this.modelCosts.get(model);
|
|
10715
|
+
if (realCost) {
|
|
10716
|
+
return (realCost.input * 1000 + realCost.output * 1000) / 2;
|
|
10717
|
+
}
|
|
10357
10718
|
const costs = {
|
|
10358
10719
|
"claude-sonnet-4-6": 0.15,
|
|
10359
10720
|
"claude-opus-4-7": 15,
|
|
@@ -10905,6 +11266,43 @@ var src_default = define({
|
|
|
10905
11266
|
return { content: "Dashboard stopped" };
|
|
10906
11267
|
}
|
|
10907
11268
|
});
|
|
11269
|
+
editor.add({
|
|
11270
|
+
name: "model.costs",
|
|
11271
|
+
description: "Show real model pricing from OpenCode or set custom costs",
|
|
11272
|
+
input: {
|
|
11273
|
+
type: "object",
|
|
11274
|
+
properties: {
|
|
11275
|
+
model: { type: "string", description: "Model ID to show cost for (optional, shows all if omitted)" },
|
|
11276
|
+
setInput: { type: "number", description: "Set input cost per token for a model" },
|
|
11277
|
+
setOutput: { type: "number", description: "Set output cost per token for a model" }
|
|
11278
|
+
},
|
|
11279
|
+
additionalProperties: false
|
|
11280
|
+
},
|
|
11281
|
+
execute: async (input) => {
|
|
11282
|
+
const { model, setInput, setOutput } = input;
|
|
11283
|
+
if (model && setInput !== undefined && setOutput !== undefined) {
|
|
11284
|
+
orchestrator.setModelCosts({ [model]: { input: setInput, output: setOutput } });
|
|
11285
|
+
return { content: `Set ${model}: input=$${setInput}/token, output=$${setOutput}/token` };
|
|
11286
|
+
}
|
|
11287
|
+
if (model) {
|
|
11288
|
+
const cost = orchestrator.modelCosts.get(model);
|
|
11289
|
+
if (cost) {
|
|
11290
|
+
return { content: `${model}: input=$${cost.input}/token, output=$${cost.output}/token, cache_read=$${cost.cacheRead}/token, cache_write=$${cost.cacheWrite}/token` };
|
|
11291
|
+
}
|
|
11292
|
+
const estimate = orchestrator["estimateModelCost"](model);
|
|
11293
|
+
return { content: `${model}: no real pricing data (estimated $${estimate}/1K tokens)` };
|
|
11294
|
+
}
|
|
11295
|
+
if (orchestrator.modelCosts.size > 0) {
|
|
11296
|
+
const lines = ["\uD83D\uDCCA Model Pricing (from OpenCode):"];
|
|
11297
|
+
for (const [id, cost] of orchestrator.modelCosts) {
|
|
11298
|
+
lines.push(` ${id}: $${cost.input}/token in, $${cost.output}/token out`);
|
|
11299
|
+
}
|
|
11300
|
+
return { content: lines.join(`
|
|
11301
|
+
`) };
|
|
11302
|
+
}
|
|
11303
|
+
return { content: "No real pricing data loaded. Using hardcoded estimates." };
|
|
11304
|
+
}
|
|
11305
|
+
});
|
|
10908
11306
|
editor.add({
|
|
10909
11307
|
name: "spawn",
|
|
10910
11308
|
description: "Spawn a sub-agent for a task",
|
|
@@ -10981,6 +11379,42 @@ var src_default = define({
|
|
|
10981
11379
|
}
|
|
10982
11380
|
}
|
|
10983
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
|
+
});
|
|
10984
11418
|
editor.add({
|
|
10985
11419
|
name: "security.scan",
|
|
10986
11420
|
description: "Scan content for security issues",
|
|
@@ -11000,6 +11434,137 @@ var src_default = define({
|
|
|
11000
11434
|
return { content: JSON.stringify({ issues: issues.length, score: result.score, details: issues }, null, 2) };
|
|
11001
11435
|
}
|
|
11002
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
|
+
});
|
|
11003
11568
|
});
|
|
11004
11569
|
await ctx.session.hook("prompt", (event) => {
|
|
11005
11570
|
if (event.prompt.text.startsWith("/nexus")) {
|
|
@@ -11013,6 +11578,8 @@ var src_default = define({
|
|
|
11013
11578
|
}
|
|
11014
11579
|
});
|
|
11015
11580
|
export {
|
|
11581
|
+
CostForecaster,
|
|
11582
|
+
CustomRoleManager,
|
|
11016
11583
|
DEFAULT_CONFIG,
|
|
11017
11584
|
HealthMonitor,
|
|
11018
11585
|
LearningModule,
|
|
@@ -11022,10 +11589,12 @@ export {
|
|
|
11022
11589
|
NexusConfigManager,
|
|
11023
11590
|
NexusOrchestrator,
|
|
11024
11591
|
PRESETS,
|
|
11592
|
+
PerformanceTracker,
|
|
11025
11593
|
PersistentMemoryStore,
|
|
11026
11594
|
SecurityScanner,
|
|
11027
11595
|
StateBroadcaster,
|
|
11028
11596
|
TEMPLATES,
|
|
11597
|
+
WorktreeManager,
|
|
11029
11598
|
src_default as default,
|
|
11030
11599
|
detectCycles,
|
|
11031
11600
|
getTemplate,
|