@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.
Files changed (2) hide show
  1. package/dist/index.js +572 -3
  2. 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: "build",
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serkanalgur/opencode-nexus",
3
- "version": "1.2.7",
3
+ "version": "1.4.0",
4
4
  "description": "Adaptive Multi-Agent Orchestration with Cost Intelligence for OpenCode V2",
5
5
  "keywords": [
6
6
  "opencode",