@serkanalgur/opencode-nexus 1.0.5 → 1.0.7
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 +269 -170
- package/dist/tui.js +59 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8514,11 +8514,53 @@ class NexusOrchestrator {
|
|
|
8514
8514
|
memory = new Map;
|
|
8515
8515
|
eventHandlers = new Map;
|
|
8516
8516
|
configManager;
|
|
8517
|
+
ctx = null;
|
|
8518
|
+
onStateChange = null;
|
|
8517
8519
|
constructor(config) {
|
|
8518
8520
|
this.config = this.mergeConfig(config);
|
|
8519
8521
|
this.budget = this.config.budget;
|
|
8520
8522
|
this.configManager = new NexusConfigManager;
|
|
8521
8523
|
}
|
|
8524
|
+
initialize(ctx, onStateChange) {
|
|
8525
|
+
this.ctx = ctx;
|
|
8526
|
+
this.onStateChange = onStateChange ?? null;
|
|
8527
|
+
}
|
|
8528
|
+
getState() {
|
|
8529
|
+
const agents = Array.from(this.agents.values()).map((a) => ({
|
|
8530
|
+
id: a.id,
|
|
8531
|
+
name: a.name,
|
|
8532
|
+
role: a.role,
|
|
8533
|
+
status: a.status,
|
|
8534
|
+
model: `${a.model.provider}/${a.model.model}`,
|
|
8535
|
+
sessionID: a.sessionID,
|
|
8536
|
+
spawnedAt: a.spawnedAt.toISOString(),
|
|
8537
|
+
tasksCompleted: a.metrics.tasksCompleted,
|
|
8538
|
+
tasksFailed: a.metrics.tasksFailed,
|
|
8539
|
+
totalCost: a.metrics.totalCost
|
|
8540
|
+
}));
|
|
8541
|
+
const tasks = Array.from(this.tasks.values()).map((t) => ({
|
|
8542
|
+
id: t.id,
|
|
8543
|
+
name: t.name,
|
|
8544
|
+
role: t.requiredRole,
|
|
8545
|
+
status: t.status,
|
|
8546
|
+
assignedAgent: t.assignedAgent,
|
|
8547
|
+
result: t.result ? {
|
|
8548
|
+
success: t.result.success,
|
|
8549
|
+
output: t.result.output?.slice(0, 500),
|
|
8550
|
+
error: t.result.error,
|
|
8551
|
+
duration: t.result.duration
|
|
8552
|
+
} : undefined
|
|
8553
|
+
}));
|
|
8554
|
+
return {
|
|
8555
|
+
running: this.running,
|
|
8556
|
+
paused: this.paused,
|
|
8557
|
+
agents,
|
|
8558
|
+
tasks,
|
|
8559
|
+
totalSpent: this.totalSpent,
|
|
8560
|
+
budgetRemaining: this.budget.maxTotalCost - this.totalSpent,
|
|
8561
|
+
lastUpdated: new Date().toISOString()
|
|
8562
|
+
};
|
|
8563
|
+
}
|
|
8522
8564
|
mergeConfig(partial) {
|
|
8523
8565
|
const defaults = {
|
|
8524
8566
|
maxConcurrency: 5,
|
|
@@ -8556,7 +8598,7 @@ class NexusOrchestrator {
|
|
|
8556
8598
|
syncInterval: 5000
|
|
8557
8599
|
},
|
|
8558
8600
|
dashboard: {
|
|
8559
|
-
enabled:
|
|
8601
|
+
enabled: true,
|
|
8560
8602
|
port: 4747,
|
|
8561
8603
|
host: "127.0.0.1"
|
|
8562
8604
|
},
|
|
@@ -8584,12 +8626,19 @@ class NexusOrchestrator {
|
|
|
8584
8626
|
learning: { ...defaults.learning, ...partial?.learning }
|
|
8585
8627
|
};
|
|
8586
8628
|
}
|
|
8629
|
+
notifyStateChange() {
|
|
8630
|
+
this.onStateChange?.();
|
|
8631
|
+
}
|
|
8587
8632
|
async execute(request) {
|
|
8588
8633
|
if (this.running) {
|
|
8589
8634
|
throw new Error("Orchestrator is already running");
|
|
8590
8635
|
}
|
|
8636
|
+
if (!this.ctx) {
|
|
8637
|
+
throw new Error("Orchestrator not initialized - call initialize(ctx) first");
|
|
8638
|
+
}
|
|
8591
8639
|
this.running = true;
|
|
8592
8640
|
const startTime = Date.now();
|
|
8641
|
+
this.notifyStateChange();
|
|
8593
8642
|
try {
|
|
8594
8643
|
this.dag = this.buildDAG(request.tasks);
|
|
8595
8644
|
if (request.budget) {
|
|
@@ -8615,6 +8664,7 @@ class NexusOrchestrator {
|
|
|
8615
8664
|
};
|
|
8616
8665
|
} finally {
|
|
8617
8666
|
this.running = false;
|
|
8667
|
+
this.notifyStateChange();
|
|
8618
8668
|
}
|
|
8619
8669
|
}
|
|
8620
8670
|
buildDAG(tasks) {
|
|
@@ -8692,11 +8742,13 @@ class NexusOrchestrator {
|
|
|
8692
8742
|
async executeDAG() {
|
|
8693
8743
|
while (!this.dag.isComplete() && !this.paused) {
|
|
8694
8744
|
const readyNodes = this.dag.getReadyNodes();
|
|
8745
|
+
const spawnPromises = [];
|
|
8695
8746
|
for (const node of readyNodes) {
|
|
8696
8747
|
if (this.agents.size < this.config.maxConcurrency) {
|
|
8697
|
-
|
|
8748
|
+
spawnPromises.push(this.spawnAndExecute(node));
|
|
8698
8749
|
}
|
|
8699
8750
|
}
|
|
8751
|
+
await Promise.all(spawnPromises);
|
|
8700
8752
|
await this.sleep(this.config.schedulerInterval);
|
|
8701
8753
|
}
|
|
8702
8754
|
}
|
|
@@ -8710,86 +8762,150 @@ class NexusOrchestrator {
|
|
|
8710
8762
|
});
|
|
8711
8763
|
node.spawnedAgent = agent;
|
|
8712
8764
|
node.status = "running";
|
|
8713
|
-
|
|
8765
|
+
node.task.assignedAgent = agent.id;
|
|
8766
|
+
this.notifyStateChange();
|
|
8767
|
+
await this.executeTask(agent, node);
|
|
8714
8768
|
}
|
|
8715
|
-
async
|
|
8716
|
-
|
|
8717
|
-
setTimeout(() => {
|
|
8718
|
-
if (node.status === "running") {
|
|
8719
|
-
this.handleAgentTimeout(agent, node);
|
|
8720
|
-
}
|
|
8721
|
-
}, timeout);
|
|
8722
|
-
}
|
|
8723
|
-
handleAgentTimeout(agent, node) {
|
|
8724
|
-
if (this.config.selfHealing.enabled) {
|
|
8725
|
-
this.handleFailure(agent, node, new Error("Agent timed out"));
|
|
8726
|
-
} else {
|
|
8769
|
+
async executeTask(agent, node) {
|
|
8770
|
+
if (!this.ctx || !agent.sessionID) {
|
|
8727
8771
|
node.status = "failed";
|
|
8728
8772
|
node.result = {
|
|
8729
8773
|
success: false,
|
|
8730
|
-
error: "
|
|
8731
|
-
duration:
|
|
8774
|
+
error: "No session available for agent",
|
|
8775
|
+
duration: 0,
|
|
8732
8776
|
tokensUsed: 0,
|
|
8733
8777
|
cost: 0
|
|
8734
8778
|
};
|
|
8779
|
+
this.notifyStateChange();
|
|
8780
|
+
return;
|
|
8735
8781
|
}
|
|
8782
|
+
const startTime = Date.now();
|
|
8783
|
+
const timeout = node.task.timeout || this.config.defaultTimeout;
|
|
8784
|
+
try {
|
|
8785
|
+
const rolePrompt = this.buildRolePrompt(node.task.requiredRole);
|
|
8786
|
+
const taskPrompt = `${rolePrompt}
|
|
8787
|
+
|
|
8788
|
+
## Task
|
|
8789
|
+
${node.task.name}
|
|
8790
|
+
|
|
8791
|
+
${node.task.description}
|
|
8792
|
+
|
|
8793
|
+
## Scope
|
|
8794
|
+
Files: ${node.task.files.include.join(", ")}`;
|
|
8795
|
+
await this.ctx.session.prompt({
|
|
8796
|
+
sessionID: agent.sessionID,
|
|
8797
|
+
text: taskPrompt
|
|
8798
|
+
});
|
|
8799
|
+
const waitPromise = this.ctx.session.wait({ sessionID: agent.sessionID });
|
|
8800
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Task timed out")), timeout));
|
|
8801
|
+
await Promise.race([waitPromise, timeoutPromise]);
|
|
8802
|
+
const messages = await this.ctx.session.context({ sessionID: agent.sessionID });
|
|
8803
|
+
const lastAssistantMsg = messages.filter((m) => m.role === "assistant").pop();
|
|
8804
|
+
const output = lastAssistantMsg?.content || "Task completed";
|
|
8805
|
+
const duration = Date.now() - startTime;
|
|
8806
|
+
const result = {
|
|
8807
|
+
success: true,
|
|
8808
|
+
output: typeof output === "string" ? output : JSON.stringify(output),
|
|
8809
|
+
duration,
|
|
8810
|
+
tokensUsed: 0,
|
|
8811
|
+
cost: this.estimateModelCost(agent.model.model)
|
|
8812
|
+
};
|
|
8813
|
+
this.dag.markComplete(node.id, result);
|
|
8814
|
+
agent.metrics.tasksCompleted++;
|
|
8815
|
+
agent.metrics.totalCost += result.cost;
|
|
8816
|
+
this.totalSpent += result.cost;
|
|
8817
|
+
this.costByAgent.set(agent.id, (this.costByAgent.get(agent.id) || 0) + result.cost);
|
|
8818
|
+
this.checkBudget();
|
|
8819
|
+
} catch (error) {
|
|
8820
|
+
const duration = Date.now() - startTime;
|
|
8821
|
+
const result = {
|
|
8822
|
+
success: false,
|
|
8823
|
+
error: error.message || "Task failed",
|
|
8824
|
+
duration,
|
|
8825
|
+
tokensUsed: 0,
|
|
8826
|
+
cost: 0
|
|
8827
|
+
};
|
|
8828
|
+
this.dag.markFailed(node.id, new Error(result.error));
|
|
8829
|
+
agent.metrics.tasksFailed++;
|
|
8830
|
+
if (this.config.selfHealing.enabled) {
|
|
8831
|
+
await this.handleFailure(agent, node, new Error(result.error));
|
|
8832
|
+
}
|
|
8833
|
+
} finally {
|
|
8834
|
+
agent.status = "idle";
|
|
8835
|
+
node.task.status = node.status === "completed" ? "completed" : "failed";
|
|
8836
|
+
this.notifyStateChange();
|
|
8837
|
+
}
|
|
8838
|
+
}
|
|
8839
|
+
buildRolePrompt(role) {
|
|
8840
|
+
const rolePrompts = {
|
|
8841
|
+
architect: "You are a software architect. Focus on system design, architecture patterns, and high-level technical decisions. Analyze requirements and propose structured solutions.",
|
|
8842
|
+
coder: "You are a senior software engineer. Write clean, efficient, well-documented code. Follow best practices and coding standards.",
|
|
8843
|
+
reviewer: "You are a code reviewer. Review code for correctness, security, performance, and maintainability. Provide constructive feedback.",
|
|
8844
|
+
tester: "You are a QA engineer. Write comprehensive tests, identify edge cases, and ensure code quality.",
|
|
8845
|
+
explorer: "You are a code explorer. Navigate and analyze codebases, understand architecture, and provide detailed reports.",
|
|
8846
|
+
documenter: "You are a technical writer. Create clear, comprehensive documentation for code and APIs."
|
|
8847
|
+
};
|
|
8848
|
+
return rolePrompts[role] || `You are a ${role}. Complete the assigned task professionally.`;
|
|
8736
8849
|
}
|
|
8737
8850
|
async handleFailure(agent, node, error) {
|
|
8738
8851
|
const failureCount = agent.metrics.tasksFailed + 1;
|
|
8739
|
-
agent.metrics.tasksFailed++;
|
|
8740
8852
|
if (failureCount < this.config.selfHealing.maxRetries) {
|
|
8741
8853
|
const delay = this.config.selfHealing.retryDelay * Math.pow(this.config.selfHealing.backoffMultiplier, failureCount - 1);
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8854
|
+
await this.sleep(delay);
|
|
8855
|
+
if (agent.sessionID) {
|
|
8856
|
+
node.status = "running";
|
|
8857
|
+
this.notifyStateChange();
|
|
8858
|
+
await this.executeTask(agent, node);
|
|
8859
|
+
}
|
|
8747
8860
|
} else {
|
|
8748
8861
|
node.status = "failed";
|
|
8749
8862
|
node.result = {
|
|
8750
8863
|
success: false,
|
|
8751
|
-
error: error.message
|
|
8864
|
+
error: `Max retries (${this.config.selfHealing.maxRetries}) exceeded: ${error.message}`,
|
|
8752
8865
|
duration: 0,
|
|
8753
8866
|
tokensUsed: 0,
|
|
8754
8867
|
cost: 0
|
|
8755
8868
|
};
|
|
8869
|
+
this.notifyStateChange();
|
|
8756
8870
|
}
|
|
8757
8871
|
}
|
|
8758
|
-
async retryAgent(agent, node) {
|
|
8759
|
-
agent.status = "working";
|
|
8760
|
-
node.status = "running";
|
|
8761
|
-
this.emit("agent:retry", { agent, node });
|
|
8762
|
-
}
|
|
8763
|
-
async respawnAgent(oldAgent, node) {
|
|
8764
|
-
const newAgent = await this.spawnAgent({
|
|
8765
|
-
role: oldAgent.role,
|
|
8766
|
-
task: node.task,
|
|
8767
|
-
model: oldAgent.model.model
|
|
8768
|
-
});
|
|
8769
|
-
if (this.config.selfHealing.contextTransfer) {
|
|
8770
|
-
await this.transferContext(oldAgent, newAgent);
|
|
8771
|
-
}
|
|
8772
|
-
await this.terminateAgent(oldAgent.id);
|
|
8773
|
-
node.spawnedAgent = newAgent;
|
|
8774
|
-
node.status = "running";
|
|
8775
|
-
this.monitorAgent(newAgent, node);
|
|
8776
|
-
}
|
|
8777
|
-
async transferContext(oldAgent, newAgent) {
|
|
8778
|
-
this.emit("context:transfer", { from: oldAgent, to: newAgent });
|
|
8779
|
-
}
|
|
8780
8872
|
async spawnAgent(config) {
|
|
8873
|
+
if (!this.ctx) {
|
|
8874
|
+
throw new Error("Orchestrator not initialized");
|
|
8875
|
+
}
|
|
8781
8876
|
const agentId = `agent-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
8877
|
+
const displayName = `${config.role}-${agentId.slice(0, 8)}`;
|
|
8878
|
+
const session = await this.ctx.session.create({
|
|
8879
|
+
title: `[Nexus] ${displayName}`
|
|
8880
|
+
});
|
|
8881
|
+
const modelConfig = this.configManager.getModelForRole(config.role);
|
|
8882
|
+
const [provider, ...modelParts] = modelConfig.split("/");
|
|
8883
|
+
const modelName = modelParts.join("/");
|
|
8884
|
+
try {
|
|
8885
|
+
await this.ctx.session.switchAgent({
|
|
8886
|
+
sessionID: session.id,
|
|
8887
|
+
agent: config.role === "architect" ? "build" : config.role === "reviewer" ? "build" : "build"
|
|
8888
|
+
});
|
|
8889
|
+
} catch {}
|
|
8890
|
+
if (modelName) {
|
|
8891
|
+
try {
|
|
8892
|
+
await this.ctx.session.switchModel({
|
|
8893
|
+
sessionID: session.id,
|
|
8894
|
+
model: { providerID: provider, id: modelName }
|
|
8895
|
+
});
|
|
8896
|
+
} catch {}
|
|
8897
|
+
}
|
|
8782
8898
|
const agent = {
|
|
8783
8899
|
id: agentId,
|
|
8784
|
-
name:
|
|
8900
|
+
name: displayName,
|
|
8785
8901
|
role: config.role,
|
|
8786
|
-
status: "
|
|
8902
|
+
status: "idle",
|
|
8787
8903
|
model: {
|
|
8788
|
-
provider
|
|
8789
|
-
model:
|
|
8904
|
+
provider,
|
|
8905
|
+
model: modelName || "default",
|
|
8790
8906
|
estimatedCost: 0,
|
|
8791
8907
|
estimatedQuality: 0.5,
|
|
8792
|
-
reasoning:
|
|
8908
|
+
reasoning: `Configured for ${config.role}`
|
|
8793
8909
|
},
|
|
8794
8910
|
spawnedAt: new Date,
|
|
8795
8911
|
lastActivity: new Date,
|
|
@@ -8800,12 +8916,12 @@ class NexusOrchestrator {
|
|
|
8800
8916
|
totalCost: 0,
|
|
8801
8917
|
averageResponseTime: 0,
|
|
8802
8918
|
errorRate: 0
|
|
8803
|
-
}
|
|
8919
|
+
},
|
|
8920
|
+
sessionID: session.id
|
|
8804
8921
|
};
|
|
8805
8922
|
this.agents.set(agentId, agent);
|
|
8806
8923
|
this.emit("agent:spawned", agent);
|
|
8807
|
-
|
|
8808
|
-
agent.status = "idle";
|
|
8924
|
+
this.notifyStateChange();
|
|
8809
8925
|
return agent;
|
|
8810
8926
|
}
|
|
8811
8927
|
async terminateAgent(agentId) {
|
|
@@ -8814,53 +8930,34 @@ class NexusOrchestrator {
|
|
|
8814
8930
|
agent.status = "terminated";
|
|
8815
8931
|
this.agents.delete(agentId);
|
|
8816
8932
|
this.emit("agent:terminated", agent);
|
|
8933
|
+
this.notifyStateChange();
|
|
8817
8934
|
}
|
|
8818
8935
|
}
|
|
8819
8936
|
analyzeComplexity(task) {
|
|
8820
8937
|
const fileCount = task.files.include.length;
|
|
8821
|
-
const codeLines =
|
|
8938
|
+
const codeLines = task.files.include.length * 50;
|
|
8822
8939
|
const dependencyDepth = task.dependencies.length;
|
|
8823
|
-
|
|
8824
|
-
const riskLevel = this.estimateRiskLevel(task);
|
|
8825
|
-
const overall = Math.min(100, fileCount * 10 + codeLines / 10 + dependencyDepth * 15 + domainKnowledge * 20 + (riskLevel === "high" ? 30 : riskLevel === "medium" ? 15 : 0));
|
|
8826
|
-
return {
|
|
8827
|
-
overall,
|
|
8828
|
-
factors: {
|
|
8829
|
-
fileCount,
|
|
8830
|
-
codeLines,
|
|
8831
|
-
dependencyDepth,
|
|
8832
|
-
domainKnowledge,
|
|
8833
|
-
riskLevel
|
|
8834
|
-
}
|
|
8835
|
-
};
|
|
8836
|
-
}
|
|
8837
|
-
estimateCodeLines(task) {
|
|
8838
|
-
return task.files.include.length * 50;
|
|
8839
|
-
}
|
|
8840
|
-
estimateDomainKnowledge(task) {
|
|
8940
|
+
let domainKnowledge = 0;
|
|
8841
8941
|
const keywords = ["security", "auth", "payment", "crypto", "database"];
|
|
8842
|
-
let score = 0;
|
|
8843
8942
|
for (const keyword of keywords) {
|
|
8844
8943
|
if (task.description.toLowerCase().includes(keyword)) {
|
|
8845
|
-
|
|
8944
|
+
domainKnowledge += 20;
|
|
8846
8945
|
}
|
|
8847
8946
|
}
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
estimateRiskLevel(task) {
|
|
8947
|
+
domainKnowledge = Math.min(100, domainKnowledge);
|
|
8948
|
+
let riskLevel = "low";
|
|
8851
8949
|
const highRiskKeywords = ["migration", "production", "security", "payment"];
|
|
8852
|
-
const mediumRiskKeywords = ["refactor", "update", "modify"];
|
|
8853
8950
|
for (const keyword of highRiskKeywords) {
|
|
8854
8951
|
if (task.description.toLowerCase().includes(keyword)) {
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
}
|
|
8858
|
-
for (const keyword of mediumRiskKeywords) {
|
|
8859
|
-
if (task.description.toLowerCase().includes(keyword)) {
|
|
8860
|
-
return "medium";
|
|
8952
|
+
riskLevel = "high";
|
|
8953
|
+
break;
|
|
8861
8954
|
}
|
|
8862
8955
|
}
|
|
8863
|
-
|
|
8956
|
+
const overall = Math.min(100, fileCount * 10 + codeLines / 10 + dependencyDepth * 15 + domainKnowledge * 20 + (riskLevel === "high" ? 30 : riskLevel === "medium" ? 15 : 0));
|
|
8957
|
+
return {
|
|
8958
|
+
overall,
|
|
8959
|
+
factors: { fileCount, codeLines, dependencyDepth, domainKnowledge, riskLevel }
|
|
8960
|
+
};
|
|
8864
8961
|
}
|
|
8865
8962
|
selectModel(role, complexity) {
|
|
8866
8963
|
const configModel = this.configManager.getModelForRole(role);
|
|
@@ -8915,6 +9012,7 @@ class NexusOrchestrator {
|
|
|
8915
9012
|
const modelCost = this.costByModel.get(model) || 0;
|
|
8916
9013
|
this.costByModel.set(model, modelCost + cost);
|
|
8917
9014
|
this.checkBudget();
|
|
9015
|
+
this.notifyStateChange();
|
|
8918
9016
|
}
|
|
8919
9017
|
checkBudget() {
|
|
8920
9018
|
const remaining = this.budget.maxTotalCost - this.totalSpent;
|
|
@@ -8943,21 +9041,10 @@ class NexusOrchestrator {
|
|
|
8943
9041
|
this.subscribers.set(topic, handlers);
|
|
8944
9042
|
return () => {
|
|
8945
9043
|
const idx = handlers.indexOf(handler);
|
|
8946
|
-
if (idx > -1)
|
|
9044
|
+
if (idx > -1)
|
|
8947
9045
|
handlers.splice(idx, 1);
|
|
8948
|
-
}
|
|
8949
9046
|
};
|
|
8950
9047
|
}
|
|
8951
|
-
send(agentId, message) {
|
|
8952
|
-
const fullMessage = {
|
|
8953
|
-
...message,
|
|
8954
|
-
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
8955
|
-
to: agentId,
|
|
8956
|
-
timestamp: new Date
|
|
8957
|
-
};
|
|
8958
|
-
this.messageQueue.push(fullMessage);
|
|
8959
|
-
this.emit("message:sent", fullMessage);
|
|
8960
|
-
}
|
|
8961
9048
|
setMemory(scope, key, value, author) {
|
|
8962
9049
|
const entry = {
|
|
8963
9050
|
id: `mem-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
@@ -8975,71 +9062,42 @@ class NexusOrchestrator {
|
|
|
8975
9062
|
getMemory(scope, key) {
|
|
8976
9063
|
return this.memory.get(`${scope}:${key}`);
|
|
8977
9064
|
}
|
|
8978
|
-
searchMemory(query, scope) {
|
|
8979
|
-
const results = [];
|
|
8980
|
-
this.memory.forEach((entry) => {
|
|
8981
|
-
if (scope && entry.scope !== scope)
|
|
8982
|
-
return;
|
|
8983
|
-
if (entry.key.includes(query) || JSON.stringify(entry.value).includes(query)) {
|
|
8984
|
-
results.push(entry);
|
|
8985
|
-
}
|
|
8986
|
-
});
|
|
8987
|
-
return results;
|
|
8988
|
-
}
|
|
8989
9065
|
getStatus(detailed) {
|
|
8990
|
-
const
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
agentsByStatus: this.getAgentsByStatus(),
|
|
9002
|
-
costByAgent: Object.fromEntries(this.costByAgent),
|
|
9003
|
-
costByModel: Object.fromEntries(this.costByModel)
|
|
9004
|
-
}, null, 2);
|
|
9005
|
-
}
|
|
9006
|
-
return JSON.stringify(status, null, 2);
|
|
9066
|
+
const state = this.getState();
|
|
9067
|
+
if (detailed)
|
|
9068
|
+
return JSON.stringify(state, null, 2);
|
|
9069
|
+
return JSON.stringify({
|
|
9070
|
+
running: state.running,
|
|
9071
|
+
paused: state.paused,
|
|
9072
|
+
agents: state.agents.length,
|
|
9073
|
+
tasks: state.tasks.length,
|
|
9074
|
+
totalCost: state.totalSpent,
|
|
9075
|
+
budgetRemaining: state.budgetRemaining
|
|
9076
|
+
}, null, 2);
|
|
9007
9077
|
}
|
|
9008
9078
|
listAgents(filter) {
|
|
9009
|
-
const
|
|
9010
|
-
const filtered = filter ? agents.filter((a) => a.status === filter) : agents;
|
|
9079
|
+
const state = this.getState();
|
|
9080
|
+
const filtered = filter ? state.agents.filter((a) => a.status === filter) : state.agents;
|
|
9011
9081
|
return JSON.stringify(filtered, null, 2);
|
|
9012
9082
|
}
|
|
9013
9083
|
getCostReport() {
|
|
9014
|
-
const
|
|
9015
|
-
totalSpent: this.totalSpent,
|
|
9016
|
-
budgetRemaining: this.budget.maxTotalCost - this.totalSpent,
|
|
9017
|
-
byAgent: new Map,
|
|
9018
|
-
byModel: new Map,
|
|
9019
|
-
timeline: []
|
|
9020
|
-
};
|
|
9021
|
-
this.agents.forEach((agent) => {
|
|
9022
|
-
report.byAgent.set(agent.id, {
|
|
9023
|
-
agentId: agent.id,
|
|
9024
|
-
agentName: agent.name,
|
|
9025
|
-
totalCost: agent.metrics.totalCost,
|
|
9026
|
-
tokenCount: agent.metrics.totalTokens,
|
|
9027
|
-
taskCount: agent.metrics.tasksCompleted
|
|
9028
|
-
});
|
|
9029
|
-
});
|
|
9084
|
+
const state = this.getState();
|
|
9030
9085
|
return JSON.stringify({
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9086
|
+
totalSpent: state.totalSpent,
|
|
9087
|
+
budgetRemaining: state.budgetRemaining,
|
|
9088
|
+
byAgent: Object.fromEntries(this.costByAgent),
|
|
9089
|
+
byModel: Object.fromEntries(this.costByModel)
|
|
9034
9090
|
}, null, 2);
|
|
9035
9091
|
}
|
|
9036
9092
|
pause() {
|
|
9037
9093
|
this.paused = true;
|
|
9038
9094
|
this.emit("orchestrator:paused", {});
|
|
9095
|
+
this.notifyStateChange();
|
|
9039
9096
|
}
|
|
9040
9097
|
resume() {
|
|
9041
9098
|
this.paused = false;
|
|
9042
9099
|
this.emit("orchestrator:resumed", {});
|
|
9100
|
+
this.notifyStateChange();
|
|
9043
9101
|
}
|
|
9044
9102
|
shutdown() {
|
|
9045
9103
|
this.agents.forEach((agent) => {
|
|
@@ -9048,25 +9106,18 @@ class NexusOrchestrator {
|
|
|
9048
9106
|
this.agents.clear();
|
|
9049
9107
|
this.running = false;
|
|
9050
9108
|
this.emit("orchestrator:shutdown", {});
|
|
9109
|
+
this.notifyStateChange();
|
|
9051
9110
|
}
|
|
9052
9111
|
collectResults() {
|
|
9053
9112
|
const results = [];
|
|
9054
9113
|
if (this.dag) {
|
|
9055
9114
|
this.dag.nodes.forEach((node) => {
|
|
9056
|
-
if (node.result)
|
|
9115
|
+
if (node.result)
|
|
9057
9116
|
results.push(node.result);
|
|
9058
|
-
}
|
|
9059
9117
|
});
|
|
9060
9118
|
}
|
|
9061
9119
|
return results;
|
|
9062
9120
|
}
|
|
9063
|
-
getAgentsByStatus() {
|
|
9064
|
-
const counts = {};
|
|
9065
|
-
this.agents.forEach((agent) => {
|
|
9066
|
-
counts[agent.status] = (counts[agent.status] || 0) + 1;
|
|
9067
|
-
});
|
|
9068
|
-
return counts;
|
|
9069
|
-
}
|
|
9070
9121
|
sleep(ms) {
|
|
9071
9122
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9072
9123
|
}
|
|
@@ -9076,9 +9127,8 @@ class NexusOrchestrator {
|
|
|
9076
9127
|
this.eventHandlers.set(event, handlers);
|
|
9077
9128
|
return () => {
|
|
9078
9129
|
const idx = handlers.indexOf(handler);
|
|
9079
|
-
if (idx > -1)
|
|
9130
|
+
if (idx > -1)
|
|
9080
9131
|
handlers.splice(idx, 1);
|
|
9081
|
-
}
|
|
9082
9132
|
};
|
|
9083
9133
|
}
|
|
9084
9134
|
emit(event, data) {
|
|
@@ -9090,22 +9140,21 @@ class NexusOrchestrator {
|
|
|
9090
9140
|
const command = parts[1];
|
|
9091
9141
|
switch (command) {
|
|
9092
9142
|
case "status":
|
|
9093
|
-
|
|
9094
|
-
break;
|
|
9143
|
+
return this.getStatus(true);
|
|
9095
9144
|
case "agents":
|
|
9096
|
-
|
|
9097
|
-
break;
|
|
9145
|
+
return this.listAgents(parts[2]);
|
|
9098
9146
|
case "costs":
|
|
9099
|
-
|
|
9100
|
-
break;
|
|
9147
|
+
return this.getCostReport();
|
|
9101
9148
|
case "pause":
|
|
9102
9149
|
this.pause();
|
|
9103
|
-
|
|
9150
|
+
return "Orchestrator paused";
|
|
9104
9151
|
case "resume":
|
|
9105
9152
|
this.resume();
|
|
9106
|
-
|
|
9153
|
+
return "Orchestrator resumed";
|
|
9154
|
+
case "dashboard":
|
|
9155
|
+
return JSON.stringify(this.getState(), null, 2);
|
|
9107
9156
|
default:
|
|
9108
|
-
|
|
9157
|
+
return "Unknown command. Available: status, agents, costs, pause, resume, dashboard";
|
|
9109
9158
|
}
|
|
9110
9159
|
}
|
|
9111
9160
|
}
|
|
@@ -9115,6 +9164,11 @@ var src_default = define({
|
|
|
9115
9164
|
id: "nexus",
|
|
9116
9165
|
async setup(ctx) {
|
|
9117
9166
|
const orchestrator = new NexusOrchestrator;
|
|
9167
|
+
orchestrator.initialize(ctx, () => {
|
|
9168
|
+
const state = orchestrator.getState();
|
|
9169
|
+
ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(state))).catch(() => {});
|
|
9170
|
+
});
|
|
9171
|
+
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
9118
9172
|
await ctx.tool.transform((editor) => {
|
|
9119
9173
|
editor.namespace({
|
|
9120
9174
|
name: "nexus",
|
|
@@ -9132,7 +9186,9 @@ var src_default = define({
|
|
|
9132
9186
|
},
|
|
9133
9187
|
execute: async (input) => {
|
|
9134
9188
|
const { detailed } = input;
|
|
9135
|
-
|
|
9189
|
+
const status = orchestrator.getStatus(detailed);
|
|
9190
|
+
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
9191
|
+
return { content: status };
|
|
9136
9192
|
}
|
|
9137
9193
|
});
|
|
9138
9194
|
editor.add({
|
|
@@ -9162,10 +9218,53 @@ var src_default = define({
|
|
|
9162
9218
|
return { content: orchestrator.getCostReport() };
|
|
9163
9219
|
}
|
|
9164
9220
|
});
|
|
9221
|
+
editor.add({
|
|
9222
|
+
name: "dashboard",
|
|
9223
|
+
description: "Get full orchestrator state for dashboard display",
|
|
9224
|
+
input: {
|
|
9225
|
+
type: "object",
|
|
9226
|
+
properties: {},
|
|
9227
|
+
additionalProperties: false
|
|
9228
|
+
},
|
|
9229
|
+
execute: async () => {
|
|
9230
|
+
const state = orchestrator.getState();
|
|
9231
|
+
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(state)));
|
|
9232
|
+
return { content: JSON.stringify(state, null, 2) };
|
|
9233
|
+
}
|
|
9234
|
+
});
|
|
9235
|
+
editor.add({
|
|
9236
|
+
name: "spawn",
|
|
9237
|
+
description: "Spawn a sub-agent for a task",
|
|
9238
|
+
input: {
|
|
9239
|
+
type: "object",
|
|
9240
|
+
properties: {
|
|
9241
|
+
role: { type: "string", description: "Agent role (architect, coder, reviewer, tester, explorer, documenter)" },
|
|
9242
|
+
task: { type: "string", description: "Task description" },
|
|
9243
|
+
model: { type: "string", description: "Model override (optional)" }
|
|
9244
|
+
},
|
|
9245
|
+
required: ["role", "task"],
|
|
9246
|
+
additionalProperties: false
|
|
9247
|
+
},
|
|
9248
|
+
execute: async (input) => {
|
|
9249
|
+
const { role, task, model } = input;
|
|
9250
|
+
try {
|
|
9251
|
+
const agent = await orchestrator.spawnAgent({ role, model });
|
|
9252
|
+
await orchestrator.ctx.session.prompt({
|
|
9253
|
+
sessionID: agent.sessionID,
|
|
9254
|
+
text: task
|
|
9255
|
+
});
|
|
9256
|
+
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
9257
|
+
return { content: `Spawned ${role} agent (${agent.id}) on session ${agent.sessionID}. Task sent.` };
|
|
9258
|
+
} catch (error) {
|
|
9259
|
+
return { content: `Failed to spawn agent: ${error.message}` };
|
|
9260
|
+
}
|
|
9261
|
+
}
|
|
9262
|
+
});
|
|
9165
9263
|
});
|
|
9166
9264
|
await ctx.session.hook("prompt", (event) => {
|
|
9167
|
-
if (event.prompt.text.
|
|
9168
|
-
orchestrator.handleCommand(event.prompt.text);
|
|
9265
|
+
if (event.prompt.text.startsWith("/nexus")) {
|
|
9266
|
+
const result = orchestrator.handleCommand(event.prompt.text);
|
|
9267
|
+
event.metadata = { ...event.metadata, nexusResult: result };
|
|
9169
9268
|
}
|
|
9170
9269
|
});
|
|
9171
9270
|
return () => {
|
package/dist/tui.js
CHANGED
|
@@ -34207,6 +34207,48 @@ var tui_default = define({
|
|
|
34207
34207
|
variant: "success"
|
|
34208
34208
|
});
|
|
34209
34209
|
};
|
|
34210
|
+
const handleDashboard = async () => {
|
|
34211
|
+
const config = configManager.getConfig();
|
|
34212
|
+
const lines = [
|
|
34213
|
+
"⚡ Nexus Dashboard",
|
|
34214
|
+
"═══════════════════════════════════",
|
|
34215
|
+
"",
|
|
34216
|
+
"\uD83E\uDD16 Agent Models:"
|
|
34217
|
+
];
|
|
34218
|
+
for (const role of configManager.getRoles()) {
|
|
34219
|
+
const model = config.models[role] || "(not set)";
|
|
34220
|
+
const displayName = configManager.getRoleDisplayName(role);
|
|
34221
|
+
lines.push(` ${displayName}: ${model}`);
|
|
34222
|
+
}
|
|
34223
|
+
lines.push("");
|
|
34224
|
+
lines.push("\uD83D\uDCB0 Budget:");
|
|
34225
|
+
lines.push(` Max Total: $${config.budget.maxTotalCost}`);
|
|
34226
|
+
lines.push(` Max Per Task: $${config.budget.maxCostPerTask}`);
|
|
34227
|
+
lines.push(` Alert Threshold: ${config.budget.alertThreshold * 100}%`);
|
|
34228
|
+
lines.push("");
|
|
34229
|
+
lines.push("\uD83D\uDEE1️ Self-Healing:");
|
|
34230
|
+
lines.push(` Enabled: ${config.selfHealing.enabled ? "✅" : "❌"}`);
|
|
34231
|
+
lines.push(` Max Retries: ${config.selfHealing.maxRetries}`);
|
|
34232
|
+
lines.push("");
|
|
34233
|
+
lines.push("\uD83D\uDCA1 Commands:");
|
|
34234
|
+
lines.push(" /nexus status - Show config summary");
|
|
34235
|
+
lines.push(" /nexus config - Configure models & budget");
|
|
34236
|
+
lines.push(" /nexus model - Select model for role");
|
|
34237
|
+
lines.push(" /nexus reset - Reset to defaults");
|
|
34238
|
+
lines.push("");
|
|
34239
|
+
lines.push("\uD83D\uDD27 Tools (use in agent prompt):");
|
|
34240
|
+
lines.push(" nexus.status - Orchestrator status");
|
|
34241
|
+
lines.push(" nexus.agents - List spawned agents");
|
|
34242
|
+
lines.push(" nexus.costs - Cost report");
|
|
34243
|
+
lines.push(" nexus.spawn - Spawn a sub-agent");
|
|
34244
|
+
context.ui.toast.show({
|
|
34245
|
+
title: "Nexus Dashboard",
|
|
34246
|
+
message: lines.join(`
|
|
34247
|
+
`),
|
|
34248
|
+
variant: "info",
|
|
34249
|
+
duration: 15000
|
|
34250
|
+
});
|
|
34251
|
+
};
|
|
34210
34252
|
context.ui.slot({
|
|
34211
34253
|
append: "app",
|
|
34212
34254
|
render: () => {
|
|
@@ -34241,6 +34283,10 @@ var tui_default = define({
|
|
|
34241
34283
|
duration: 8000
|
|
34242
34284
|
});
|
|
34243
34285
|
break;
|
|
34286
|
+
case "dashboard":
|
|
34287
|
+
case "d":
|
|
34288
|
+
await handleDashboard();
|
|
34289
|
+
break;
|
|
34244
34290
|
case "model":
|
|
34245
34291
|
case "m":
|
|
34246
34292
|
if (parts[1]) {
|
|
@@ -34277,7 +34323,7 @@ var tui_default = define({
|
|
|
34277
34323
|
default:
|
|
34278
34324
|
context.ui.toast.show({
|
|
34279
34325
|
title: "Nexus",
|
|
34280
|
-
message: "Commands: config, status, model <role>, reset",
|
|
34326
|
+
message: "Commands: config, status, dashboard, model <role>, reset",
|
|
34281
34327
|
variant: "info"
|
|
34282
34328
|
});
|
|
34283
34329
|
}
|
|
@@ -34298,6 +34344,18 @@ var tui_default = define({
|
|
|
34298
34344
|
await handleFullConfig();
|
|
34299
34345
|
}
|
|
34300
34346
|
},
|
|
34347
|
+
{
|
|
34348
|
+
id: "nexus.dashboard",
|
|
34349
|
+
title: "Nexus Dashboard",
|
|
34350
|
+
group: "Nexus",
|
|
34351
|
+
palette: true,
|
|
34352
|
+
slash: { name: "nexus-dashboard", aliases: ["nd"], arguments: true },
|
|
34353
|
+
enabled: () => true,
|
|
34354
|
+
suggested: true,
|
|
34355
|
+
run: async () => {
|
|
34356
|
+
await handleDashboard();
|
|
34357
|
+
}
|
|
34358
|
+
},
|
|
34301
34359
|
{
|
|
34302
34360
|
id: "nexus.model",
|
|
34303
34361
|
title: "Select Agent Model",
|