@serkanalgur/opencode-nexus 1.4.1 → 1.6.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 +182 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8846,7 +8846,7 @@ class DashboardModule {
|
|
|
8846
8846
|
this.server = Bun.serve({
|
|
8847
8847
|
port,
|
|
8848
8848
|
hostname: host,
|
|
8849
|
-
fetch(req, server) {
|
|
8849
|
+
async fetch(req, server) {
|
|
8850
8850
|
const url = new URL(req.url);
|
|
8851
8851
|
if (req.method === "OPTIONS") {
|
|
8852
8852
|
return new Response(null, { headers: CORS_HEADERS });
|
|
@@ -8874,14 +8874,20 @@ class DashboardModule {
|
|
|
8874
8874
|
}
|
|
8875
8875
|
try {
|
|
8876
8876
|
const spaPath = join2(process.cwd(), "dashboard", "index.html");
|
|
8877
|
-
const
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
return new Response(spaFile, {
|
|
8877
|
+
const spaContent = await Bun.file(spaPath).text();
|
|
8878
|
+
if (spaContent.length > 100) {
|
|
8879
|
+
return new Response(spaContent, {
|
|
8881
8880
|
headers: { "Content-Type": "text/html; charset=utf-8", ...CORS_HEADERS }
|
|
8882
8881
|
});
|
|
8883
8882
|
}
|
|
8884
|
-
|
|
8883
|
+
return new Response(`SPA not found at ${spaPath} (content: ${spaContent.length} bytes)`, {
|
|
8884
|
+
headers: { "Content-Type": "text/plain", ...CORS_HEADERS }
|
|
8885
|
+
});
|
|
8886
|
+
} catch (e) {
|
|
8887
|
+
return new Response(`SPA error: ${e}`, {
|
|
8888
|
+
headers: { "Content-Type": "text/plain", ...CORS_HEADERS }
|
|
8889
|
+
});
|
|
8890
|
+
}
|
|
8885
8891
|
return new Response("Nexus Dashboard API", {
|
|
8886
8892
|
headers: {
|
|
8887
8893
|
"Content-Type": "text/plain",
|
|
@@ -10006,6 +10012,7 @@ class NexusOrchestrator {
|
|
|
10006
10012
|
}
|
|
10007
10013
|
notifications = null;
|
|
10008
10014
|
modelCosts = new Map;
|
|
10015
|
+
parentSessionID = null;
|
|
10009
10016
|
constructor(config, messageStoreConfig, memoryStoreConfig) {
|
|
10010
10017
|
this.config = this.mergeConfig(config);
|
|
10011
10018
|
this.budget = this.config.budget;
|
|
@@ -10644,7 +10651,8 @@ Please continue from where the previous agent left off.`;
|
|
|
10644
10651
|
const session = await this.ctx.session.create({
|
|
10645
10652
|
title,
|
|
10646
10653
|
agent: agentType,
|
|
10647
|
-
model: modelName ? { providerID: provider, id: modelName } : undefined
|
|
10654
|
+
model: modelName ? { providerID: provider, id: modelName } : undefined,
|
|
10655
|
+
parentID: this.parentSessionID || undefined
|
|
10648
10656
|
});
|
|
10649
10657
|
const agent = {
|
|
10650
10658
|
id: agentId,
|
|
@@ -11094,9 +11102,84 @@ function getTemplate(name) {
|
|
|
11094
11102
|
}
|
|
11095
11103
|
|
|
11096
11104
|
// src/index.ts
|
|
11105
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync4, existsSync as existsSync3 } from "node:fs";
|
|
11106
|
+
import { join as join6 } from "node:path";
|
|
11107
|
+
import { homedir as homedir2 } from "node:os";
|
|
11108
|
+
var NEXUS_AGENT_CONTENT = `---
|
|
11109
|
+
description: Nexus multi-agent orchestrator — manages parallel sub-agents with cost-aware routing, self-healing, and intelligent task decomposition.
|
|
11110
|
+
mode: primary
|
|
11111
|
+
---
|
|
11112
|
+
|
|
11113
|
+
# Nexus Orchestrator Agent
|
|
11114
|
+
|
|
11115
|
+
You are the Nexus Orchestrator — an intelligent multi-agent coordinator. You manage parallel sub-agents with cost-aware model selection, self-healing on failures, and DAG-based task execution.
|
|
11116
|
+
|
|
11117
|
+
## Core Capabilities
|
|
11118
|
+
|
|
11119
|
+
- **Spawn specialized sub-agents** for each task (architect, coder, reviewer, tester, explorer, documenter)
|
|
11120
|
+
- **Cost-aware routing** — automatically selects optimal models based on task complexity and budget
|
|
11121
|
+
- **Self-healing** — retries failed tasks, transfers context to new agents, escalates if needed
|
|
11122
|
+
- **Performance tracking** — learns which model/role combinations work best
|
|
11123
|
+
- **Execution history** — tracks all past executions for analysis
|
|
11124
|
+
|
|
11125
|
+
## How to Use Nexus Tools
|
|
11126
|
+
|
|
11127
|
+
### Spawning Agents
|
|
11128
|
+
Use the \`spawn\` tool to create specialized agents:
|
|
11129
|
+
\`\`\`
|
|
11130
|
+
nexus.spawn(role="coder", task="Implement JWT authentication middleware")
|
|
11131
|
+
nexus.spawn(role="reviewer", task="Review the auth implementation for security")
|
|
11132
|
+
\`\`\`
|
|
11133
|
+
|
|
11134
|
+
Available roles: architect, coder, reviewer, tester, explorer, documenter
|
|
11135
|
+
|
|
11136
|
+
### Checking Status
|
|
11137
|
+
\`\`\`
|
|
11138
|
+
nexus.status(detailed=true) — Full orchestrator state
|
|
11139
|
+
nexus.agents(filter="working") — List active agents
|
|
11140
|
+
nexus.costs() — Cost report and budget status
|
|
11141
|
+
\`\`\`
|
|
11142
|
+
|
|
11143
|
+
### Cost Management
|
|
11144
|
+
\`\`\`
|
|
11145
|
+
nexus.forecast(tasks='[...]') — Predict costs
|
|
11146
|
+
nexus.model.costs() — Show real model pricing
|
|
11147
|
+
nexus.preset(name="balanced") — Apply a cost preset
|
|
11148
|
+
\`\`\`
|
|
11149
|
+
|
|
11150
|
+
### Performance & History
|
|
11151
|
+
\`\`\`
|
|
11152
|
+
nexus.performance.scores() — Model/role performance scores
|
|
11153
|
+
nexus.history.list(count=10) — Recent execution history
|
|
11154
|
+
\`\`\`
|
|
11155
|
+
|
|
11156
|
+
## Task Decomposition Strategy
|
|
11157
|
+
|
|
11158
|
+
When given a development request:
|
|
11159
|
+
1. **Analyze** — Break the request into discrete tasks
|
|
11160
|
+
2. **Estimate** — Use nexus.forecast to predict costs
|
|
11161
|
+
3. **Execute** — Spawn agents in parallel where possible
|
|
11162
|
+
4. **Review** — Use nexus.spawn(role="reviewer") for each delivery
|
|
11163
|
+
5. **Report** — Summarize what was done, costs, and outcomes
|
|
11164
|
+
|
|
11165
|
+
## Quality Gates
|
|
11166
|
+
|
|
11167
|
+
Never skip these steps:
|
|
11168
|
+
- Every code change must be reviewed by nexus.spawn(role="reviewer")
|
|
11169
|
+
- Run nexus.security.scan() on new code
|
|
11170
|
+
- Verify with nexus.spawn(role="tester") before marking complete
|
|
11171
|
+
`;
|
|
11097
11172
|
var src_default = define({
|
|
11098
11173
|
id: "nexus",
|
|
11099
11174
|
async setup(ctx) {
|
|
11175
|
+
try {
|
|
11176
|
+
const agentDir = join6(homedir2(), ".config", "opencode", "agents");
|
|
11177
|
+
const agentFile = join6(agentDir, "nexus-orchestrator.md");
|
|
11178
|
+
if (!existsSync3(agentFile)) {
|
|
11179
|
+
mkdirSync4(agentDir, { recursive: true });
|
|
11180
|
+
writeFileSync2(agentFile, NEXUS_AGENT_CONTENT, "utf-8");
|
|
11181
|
+
}
|
|
11182
|
+
} catch {}
|
|
11100
11183
|
const orchestrator = new NexusOrchestrator;
|
|
11101
11184
|
await orchestrator.initialize(ctx, () => {
|
|
11102
11185
|
const state = orchestrator.getState();
|
|
@@ -11316,25 +11399,54 @@ var src_default = define({
|
|
|
11316
11399
|
});
|
|
11317
11400
|
editor.add({
|
|
11318
11401
|
name: "spawn",
|
|
11319
|
-
description: "Spawn a sub-agent for a task",
|
|
11402
|
+
description: "Spawn a sub-agent for a task. Use wait=true to wait for completion.",
|
|
11320
11403
|
input: {
|
|
11321
11404
|
type: "object",
|
|
11322
11405
|
properties: {
|
|
11323
11406
|
role: { type: "string", description: "Agent role (architect, coder, reviewer, tester, explorer, documenter)" },
|
|
11324
11407
|
task: { type: "string", description: "Task description" },
|
|
11325
|
-
model: { type: "string", description: "Model override (optional)" }
|
|
11408
|
+
model: { type: "string", description: "Model override (optional)" },
|
|
11409
|
+
wait: { type: "boolean", description: "Wait for completion (default: false)" },
|
|
11410
|
+
timeout: { type: "number", description: "Timeout in ms when waiting (default: 120000)" }
|
|
11326
11411
|
},
|
|
11327
11412
|
required: ["role", "task"],
|
|
11328
11413
|
additionalProperties: false
|
|
11329
11414
|
},
|
|
11330
11415
|
execute: async (input) => {
|
|
11331
|
-
const { role, task, model } = input;
|
|
11416
|
+
const { role, task, model, wait, timeout } = input;
|
|
11332
11417
|
try {
|
|
11418
|
+
orchestrator.parentSessionID = null;
|
|
11333
11419
|
const agent = await orchestrator.spawnAgent({ role, model });
|
|
11334
11420
|
await orchestrator.ctx.session.prompt({
|
|
11335
11421
|
sessionID: agent.sessionID,
|
|
11336
11422
|
text: task
|
|
11337
11423
|
});
|
|
11424
|
+
if (wait) {
|
|
11425
|
+
const waitTimeout = timeout || 120000;
|
|
11426
|
+
try {
|
|
11427
|
+
await orchestrator.ctx.session.wait({ sessionID: agent.sessionID });
|
|
11428
|
+
} catch {}
|
|
11429
|
+
try {
|
|
11430
|
+
const messages = await orchestrator.ctx.session.context({ sessionID: agent.sessionID });
|
|
11431
|
+
const lastMsg = messages.filter((m) => m.role === "assistant").pop();
|
|
11432
|
+
const result = lastMsg?.content || "Task completed (no output captured)";
|
|
11433
|
+
agent.status = "completed";
|
|
11434
|
+
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
11435
|
+
const taskPreview = task.length > 80 ? task.substring(0, 77) + "..." : task;
|
|
11436
|
+
return {
|
|
11437
|
+
content: [
|
|
11438
|
+
`${agent.name}`,
|
|
11439
|
+
`\uD83D\uDCCB Task: ${taskPreview}`,
|
|
11440
|
+
`✅ Status: completed`,
|
|
11441
|
+
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
11442
|
+
`
|
|
11443
|
+
--- Result ---`,
|
|
11444
|
+
result
|
|
11445
|
+
].join(`
|
|
11446
|
+
`)
|
|
11447
|
+
};
|
|
11448
|
+
} catch {}
|
|
11449
|
+
}
|
|
11338
11450
|
const complexity = orchestrator.analyzeComplexity({
|
|
11339
11451
|
id: `spawn-${Date.now()}`,
|
|
11340
11452
|
name: task,
|
|
@@ -11355,7 +11467,8 @@ var src_default = define({
|
|
|
11355
11467
|
`\uD83D\uDCCB Task: ${taskPreview}`,
|
|
11356
11468
|
`\uD83D\uDCCA Complexity: ${complexity.overall}/100 (${complexity.factors.riskLevel} risk)`,
|
|
11357
11469
|
`\uD83E\uDD16 Model reasoning: ${modelSelection.reasoning}`,
|
|
11358
|
-
`\uD83D\uDCCE Session: ${agent.sessionID}
|
|
11470
|
+
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
11471
|
+
`\uD83D\uDCA1 Use wait=true to wait for completion`
|
|
11359
11472
|
].join(`
|
|
11360
11473
|
`);
|
|
11361
11474
|
return { content: output };
|
|
@@ -11576,6 +11689,64 @@ Total: ~$${result.totalEstimatedCost.toFixed(4)}`);
|
|
|
11576
11689
|
return { content: "Worktree isolation disabled." };
|
|
11577
11690
|
}
|
|
11578
11691
|
});
|
|
11692
|
+
editor.add({
|
|
11693
|
+
name: "sessions",
|
|
11694
|
+
description: "List all active Nexus agent sessions",
|
|
11695
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
11696
|
+
execute: async () => {
|
|
11697
|
+
const agents = orchestrator.getState().agents;
|
|
11698
|
+
if (agents.length === 0)
|
|
11699
|
+
return { content: "No active agent sessions." };
|
|
11700
|
+
const lines = agents.map((a) => {
|
|
11701
|
+
const statusIcon = a.status === "working" ? "\uD83D\uDD04" : a.status === "idle" ? "⏸️" : a.status === "completed" ? "✅" : "❌";
|
|
11702
|
+
return `${statusIcon} ${a.name} (${a.role}) — Session: ${a.sessionID}`;
|
|
11703
|
+
});
|
|
11704
|
+
return { content: `Active Sessions (${agents.length}):
|
|
11705
|
+
${lines.join(`
|
|
11706
|
+
`)}` };
|
|
11707
|
+
}
|
|
11708
|
+
});
|
|
11709
|
+
editor.add({
|
|
11710
|
+
name: "background",
|
|
11711
|
+
description: "Move running agents to background (detach from current session)",
|
|
11712
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
11713
|
+
execute: async () => {
|
|
11714
|
+
const agents = orchestrator.getState().agents.filter((a) => a.status === "working" || a.status === "idle");
|
|
11715
|
+
if (agents.length === 0)
|
|
11716
|
+
return { content: "No running agents to move to background." };
|
|
11717
|
+
for (const agent of agents) {
|
|
11718
|
+
try {
|
|
11719
|
+
await orchestrator.ctx.session.background({ sessionID: agent.sessionID });
|
|
11720
|
+
} catch {}
|
|
11721
|
+
}
|
|
11722
|
+
return { content: `${agents.length} agent(s) moved to background. You can continue working while they run.` };
|
|
11723
|
+
}
|
|
11724
|
+
});
|
|
11725
|
+
editor.add({
|
|
11726
|
+
name: "result",
|
|
11727
|
+
description: "Get the result of a completed agent session",
|
|
11728
|
+
input: {
|
|
11729
|
+
type: "object",
|
|
11730
|
+
properties: {
|
|
11731
|
+
sessionID: { type: "string", description: "Session ID of the agent" }
|
|
11732
|
+
},
|
|
11733
|
+
required: ["sessionID"]
|
|
11734
|
+
},
|
|
11735
|
+
execute: async (input) => {
|
|
11736
|
+
const { sessionID } = input;
|
|
11737
|
+
try {
|
|
11738
|
+
const messages = await orchestrator.ctx.session.context({ sessionID });
|
|
11739
|
+
const lastMsg = messages.filter((m) => m.role === "assistant").pop();
|
|
11740
|
+
if (lastMsg) {
|
|
11741
|
+
return { content: `Session ${sessionID} result:
|
|
11742
|
+
${lastMsg.content}` };
|
|
11743
|
+
}
|
|
11744
|
+
return { content: `Session ${sessionID} has no assistant messages yet.` };
|
|
11745
|
+
} catch (error) {
|
|
11746
|
+
return { content: `Failed to get result: ${error.message}` };
|
|
11747
|
+
}
|
|
11748
|
+
}
|
|
11749
|
+
});
|
|
11579
11750
|
});
|
|
11580
11751
|
await ctx.session.hook("prompt", (event) => {
|
|
11581
11752
|
if (event.prompt.text.startsWith("/nexus")) {
|