@serkanalgur/opencode-nexus 1.7.0 → 1.8.1
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/README.md +254 -330
- package/dist/index.js +1183 -50
- package/dist/tui.js +177 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9962,6 +9962,116 @@ class WorktreeManager {
|
|
|
9962
9962
|
}
|
|
9963
9963
|
}
|
|
9964
9964
|
|
|
9965
|
+
// src/todo.ts
|
|
9966
|
+
class TodoEnforcer {
|
|
9967
|
+
items = new Map;
|
|
9968
|
+
maxItems;
|
|
9969
|
+
constructor(maxItems = 50) {
|
|
9970
|
+
this.maxItems = maxItems;
|
|
9971
|
+
}
|
|
9972
|
+
add(description, assignedTo) {
|
|
9973
|
+
if (this.items.size >= this.maxItems) {
|
|
9974
|
+
const completedItems = [...this.items.values()].filter((i) => i.status === "completed").sort((a, b) => (a.completedAt?.getTime() || 0) - (b.completedAt?.getTime() || 0));
|
|
9975
|
+
if (completedItems.length > 0) {
|
|
9976
|
+
this.items.delete(completedItems[0].id);
|
|
9977
|
+
} else {
|
|
9978
|
+
const oldestPending = [...this.items.values()].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
9979
|
+
if (oldestPending.length > 0) {
|
|
9980
|
+
this.items.delete(oldestPending[0].id);
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
const item = {
|
|
9985
|
+
id: `todo-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
|
|
9986
|
+
description,
|
|
9987
|
+
status: "pending",
|
|
9988
|
+
assignedTo,
|
|
9989
|
+
createdAt: new Date
|
|
9990
|
+
};
|
|
9991
|
+
this.items.set(item.id, item);
|
|
9992
|
+
return item;
|
|
9993
|
+
}
|
|
9994
|
+
start(id) {
|
|
9995
|
+
const item = this.items.get(id);
|
|
9996
|
+
if (item)
|
|
9997
|
+
item.status = "in_progress";
|
|
9998
|
+
}
|
|
9999
|
+
complete(id) {
|
|
10000
|
+
const item = this.items.get(id);
|
|
10001
|
+
if (item) {
|
|
10002
|
+
item.status = "completed";
|
|
10003
|
+
item.completedAt = new Date;
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
10006
|
+
block(id) {
|
|
10007
|
+
const item = this.items.get(id);
|
|
10008
|
+
if (item)
|
|
10009
|
+
item.status = "blocked";
|
|
10010
|
+
}
|
|
10011
|
+
get(id) {
|
|
10012
|
+
return this.items.get(id);
|
|
10013
|
+
}
|
|
10014
|
+
getPending() {
|
|
10015
|
+
return [...this.items.values()].filter((i) => i.status === "pending");
|
|
10016
|
+
}
|
|
10017
|
+
getInProgress() {
|
|
10018
|
+
return [...this.items.values()].filter((i) => i.status === "in_progress");
|
|
10019
|
+
}
|
|
10020
|
+
getCompleted() {
|
|
10021
|
+
return [...this.items.values()].filter((i) => i.status === "completed");
|
|
10022
|
+
}
|
|
10023
|
+
getAll() {
|
|
10024
|
+
return [...this.items.values()];
|
|
10025
|
+
}
|
|
10026
|
+
getStats() {
|
|
10027
|
+
const all = [...this.items.values()];
|
|
10028
|
+
return {
|
|
10029
|
+
total: all.length,
|
|
10030
|
+
pending: all.filter((i) => i.status === "pending").length,
|
|
10031
|
+
inProgress: all.filter((i) => i.status === "in_progress").length,
|
|
10032
|
+
completed: all.filter((i) => i.status === "completed").length,
|
|
10033
|
+
blocked: all.filter((i) => i.status === "blocked").length
|
|
10034
|
+
};
|
|
10035
|
+
}
|
|
10036
|
+
clear() {
|
|
10037
|
+
this.items.clear();
|
|
10038
|
+
}
|
|
10039
|
+
trackTask(taskId, description, role) {
|
|
10040
|
+
return this.add(`[Task: ${taskId}] ${description}`, role);
|
|
10041
|
+
}
|
|
10042
|
+
completeTask(taskId) {
|
|
10043
|
+
for (const item of this.items.values()) {
|
|
10044
|
+
if (item.description.startsWith(`[Task: ${taskId}]`)) {
|
|
10045
|
+
this.complete(item.id);
|
|
10046
|
+
break;
|
|
10047
|
+
}
|
|
10048
|
+
}
|
|
10049
|
+
}
|
|
10050
|
+
formatAll() {
|
|
10051
|
+
const all = this.getAll();
|
|
10052
|
+
if (all.length === 0)
|
|
10053
|
+
return "\uD83D\uDCCB No todo items.";
|
|
10054
|
+
const statusIcon = {
|
|
10055
|
+
pending: "⏳",
|
|
10056
|
+
in_progress: "\uD83D\uDD04",
|
|
10057
|
+
completed: "✅",
|
|
10058
|
+
blocked: "\uD83D\uDEAB"
|
|
10059
|
+
};
|
|
10060
|
+
const lines = all.map((item) => {
|
|
10061
|
+
const icon = statusIcon[item.status] || "❓";
|
|
10062
|
+
const assigned = item.assignedTo ? ` (${item.assignedTo})` : "";
|
|
10063
|
+
const time = item.completedAt ? `completed ${item.completedAt.toISOString()}` : `created ${item.createdAt.toISOString()}`;
|
|
10064
|
+
return ` ${icon} ${item.id}: ${item.description}${assigned} — ${time}`;
|
|
10065
|
+
});
|
|
10066
|
+
const stats = this.getStats();
|
|
10067
|
+
const summary = `\uD83D\uDCCB Todo: ${stats.total} total | ⏳ ${stats.pending} pending | \uD83D\uDD04 ${stats.inProgress} in progress | ✅ ${stats.completed} completed | \uD83D\uDEAB ${stats.blocked} blocked`;
|
|
10068
|
+
return `${summary}
|
|
10069
|
+
|
|
10070
|
+
${lines.join(`
|
|
10071
|
+
`)}`;
|
|
10072
|
+
}
|
|
10073
|
+
}
|
|
10074
|
+
|
|
9965
10075
|
// src/orchestrator.ts
|
|
9966
10076
|
var DEFAULT_ESCALATION = {
|
|
9967
10077
|
maxRetries: 3,
|
|
@@ -10004,6 +10114,7 @@ class NexusOrchestrator {
|
|
|
10004
10114
|
customRoles;
|
|
10005
10115
|
forecaster;
|
|
10006
10116
|
worktreeManager = null;
|
|
10117
|
+
todoEnforcer = new TodoEnforcer;
|
|
10007
10118
|
onStateChange = null;
|
|
10008
10119
|
stateChangeTimer = null;
|
|
10009
10120
|
costHistory = [];
|
|
@@ -10451,6 +10562,7 @@ Please continue from where the previous agent left off.`;
|
|
|
10451
10562
|
cost: this.estimateModelCost(agent.model.model)
|
|
10452
10563
|
};
|
|
10453
10564
|
this.dag.markComplete(node.id, result);
|
|
10565
|
+
this.todoEnforcer.completeTask(agent.id);
|
|
10454
10566
|
agent.metrics.tasksCompleted++;
|
|
10455
10567
|
agent.metrics.totalCost += result.cost;
|
|
10456
10568
|
this.totalSpent += result.cost;
|
|
@@ -10496,15 +10608,28 @@ Please continue from where the previous agent left off.`;
|
|
|
10496
10608
|
}
|
|
10497
10609
|
} catch (error) {
|
|
10498
10610
|
const duration = Date.now() - startTime;
|
|
10611
|
+
const errorMessage = error.message || "Task failed";
|
|
10499
10612
|
const result = {
|
|
10500
10613
|
success: false,
|
|
10501
|
-
error:
|
|
10614
|
+
error: errorMessage,
|
|
10502
10615
|
duration,
|
|
10503
10616
|
tokensUsed: 0,
|
|
10504
10617
|
cost: 0
|
|
10505
10618
|
};
|
|
10506
|
-
this.dag.markFailed(node.id, new Error(
|
|
10619
|
+
this.dag.markFailed(node.id, new Error(errorMessage));
|
|
10620
|
+
this.todoEnforcer.completeTask(agent.id);
|
|
10507
10621
|
agent.metrics.tasksFailed++;
|
|
10622
|
+
agent.status = "failed";
|
|
10623
|
+
this.emit("task:failed", {
|
|
10624
|
+
taskId: node.id,
|
|
10625
|
+
taskName: node.task.name,
|
|
10626
|
+
agentId: agent.id,
|
|
10627
|
+
role: node.task.requiredRole,
|
|
10628
|
+
model: agent.model.model,
|
|
10629
|
+
error: errorMessage,
|
|
10630
|
+
duration,
|
|
10631
|
+
sessionID: agent.sessionID
|
|
10632
|
+
});
|
|
10508
10633
|
this.performanceTracker.record({
|
|
10509
10634
|
model: agent.model.model,
|
|
10510
10635
|
role: node.task.requiredRole,
|
|
@@ -10524,13 +10649,18 @@ Please continue from where the previous agent left off.`;
|
|
|
10524
10649
|
tokensUsed: result.tokensUsed,
|
|
10525
10650
|
startedAt: new Date(startTime),
|
|
10526
10651
|
completedAt: new Date,
|
|
10527
|
-
error:
|
|
10652
|
+
error: errorMessage
|
|
10528
10653
|
});
|
|
10654
|
+
if (this.notifications?.isEnabled()) {
|
|
10655
|
+
this.notifications.notify({ title: "Nexus: Task Failed", body: `${node.task.name} failed: ${errorMessage}`, sound: true });
|
|
10656
|
+
}
|
|
10529
10657
|
if (this.config.selfHealing.enabled) {
|
|
10530
|
-
await this.handleFailure(agent, node, new Error(
|
|
10658
|
+
await this.handleFailure(agent, node, new Error(errorMessage));
|
|
10531
10659
|
}
|
|
10532
10660
|
} finally {
|
|
10533
|
-
agent.status
|
|
10661
|
+
if (agent.status !== "terminated" && agent.status !== "failed") {
|
|
10662
|
+
agent.status = "idle";
|
|
10663
|
+
}
|
|
10534
10664
|
node.task.status = node.status === "completed" ? "completed" : "failed";
|
|
10535
10665
|
this.notifyStateChange();
|
|
10536
10666
|
}
|
|
@@ -10653,7 +10783,13 @@ Please continue from where the previous agent left off.`;
|
|
|
10653
10783
|
title,
|
|
10654
10784
|
agent: agentType,
|
|
10655
10785
|
model: modelName ? { providerID: provider, id: modelName } : undefined,
|
|
10656
|
-
parentID: this.parentSessionID || undefined
|
|
10786
|
+
parentID: this.parentSessionID || undefined,
|
|
10787
|
+
metadata: {
|
|
10788
|
+
nexusRole: config.role,
|
|
10789
|
+
nexusTask: config.task?.name || "direct-spawn",
|
|
10790
|
+
nexusAgentId: agentId,
|
|
10791
|
+
nexusModel: modelConfig
|
|
10792
|
+
}
|
|
10657
10793
|
});
|
|
10658
10794
|
const agent = {
|
|
10659
10795
|
id: agentId,
|
|
@@ -10680,6 +10816,8 @@ Please continue from where the previous agent left off.`;
|
|
|
10680
10816
|
sessionID: session.id
|
|
10681
10817
|
};
|
|
10682
10818
|
this.agents.set(agentId, agent);
|
|
10819
|
+
const taskDesc = config.task?.name || `Agent ${config.role} task`;
|
|
10820
|
+
this.todoEnforcer.trackTask(agentId, taskDesc, config.role);
|
|
10683
10821
|
this.emit("agent:spawned", agent);
|
|
10684
10822
|
this.notifyStateChange();
|
|
10685
10823
|
this.enforceAgentLimit();
|
|
@@ -11102,6 +11240,203 @@ function getTemplate(name) {
|
|
|
11102
11240
|
return TEMPLATES[name];
|
|
11103
11241
|
}
|
|
11104
11242
|
|
|
11243
|
+
// src/goal.ts
|
|
11244
|
+
var goalCounter = 0;
|
|
11245
|
+
|
|
11246
|
+
class GoalManager {
|
|
11247
|
+
goals = new Map;
|
|
11248
|
+
activeGoalId = null;
|
|
11249
|
+
set(description, autoContinue = true) {
|
|
11250
|
+
if (this.activeGoalId) {
|
|
11251
|
+
const prev = this.goals.get(this.activeGoalId);
|
|
11252
|
+
if (prev && prev.status === "active") {
|
|
11253
|
+
prev.status = "completed";
|
|
11254
|
+
prev.completedAt = new Date;
|
|
11255
|
+
}
|
|
11256
|
+
}
|
|
11257
|
+
const goal = {
|
|
11258
|
+
id: `goal-${Date.now()}-${++goalCounter}`,
|
|
11259
|
+
description,
|
|
11260
|
+
status: "active",
|
|
11261
|
+
tasks: [],
|
|
11262
|
+
createdAt: new Date,
|
|
11263
|
+
autoContinue
|
|
11264
|
+
};
|
|
11265
|
+
this.goals.set(goal.id, goal);
|
|
11266
|
+
this.activeGoalId = goal.id;
|
|
11267
|
+
return goal;
|
|
11268
|
+
}
|
|
11269
|
+
addTask(goalId, taskId) {
|
|
11270
|
+
const goal = this.goals.get(goalId);
|
|
11271
|
+
if (goal)
|
|
11272
|
+
goal.tasks.push(taskId);
|
|
11273
|
+
}
|
|
11274
|
+
complete(goalId) {
|
|
11275
|
+
const goal = this.goals.get(goalId);
|
|
11276
|
+
if (goal) {
|
|
11277
|
+
goal.status = "completed";
|
|
11278
|
+
goal.completedAt = new Date;
|
|
11279
|
+
if (this.activeGoalId === goalId) {
|
|
11280
|
+
this.activeGoalId = null;
|
|
11281
|
+
}
|
|
11282
|
+
}
|
|
11283
|
+
}
|
|
11284
|
+
pause(goalId) {
|
|
11285
|
+
const goal = this.goals.get(goalId);
|
|
11286
|
+
if (goal)
|
|
11287
|
+
goal.status = "paused";
|
|
11288
|
+
}
|
|
11289
|
+
resume(goalId) {
|
|
11290
|
+
const goal = this.goals.get(goalId);
|
|
11291
|
+
if (goal)
|
|
11292
|
+
goal.status = "active";
|
|
11293
|
+
}
|
|
11294
|
+
getActive() {
|
|
11295
|
+
if (!this.activeGoalId)
|
|
11296
|
+
return null;
|
|
11297
|
+
const goal = this.goals.get(this.activeGoalId);
|
|
11298
|
+
if (!goal || goal.status !== "active")
|
|
11299
|
+
return null;
|
|
11300
|
+
return goal;
|
|
11301
|
+
}
|
|
11302
|
+
getAll() {
|
|
11303
|
+
return [...this.goals.values()];
|
|
11304
|
+
}
|
|
11305
|
+
shouldContinue() {
|
|
11306
|
+
const active = this.getActive();
|
|
11307
|
+
return active?.autoContinue === true && active.status === "active";
|
|
11308
|
+
}
|
|
11309
|
+
get(goalId) {
|
|
11310
|
+
return this.goals.get(goalId);
|
|
11311
|
+
}
|
|
11312
|
+
}
|
|
11313
|
+
|
|
11314
|
+
// src/team.ts
|
|
11315
|
+
class TeamManager {
|
|
11316
|
+
teams = new Map;
|
|
11317
|
+
teamCounter = 0;
|
|
11318
|
+
memberCounter = 0;
|
|
11319
|
+
create(name, leadRole) {
|
|
11320
|
+
this.teamCounter++;
|
|
11321
|
+
const team = {
|
|
11322
|
+
id: `team-${Date.now()}-${this.teamCounter}`,
|
|
11323
|
+
name,
|
|
11324
|
+
lead: leadRole,
|
|
11325
|
+
members: [],
|
|
11326
|
+
status: "forming",
|
|
11327
|
+
createdAt: new Date
|
|
11328
|
+
};
|
|
11329
|
+
this.teams.set(team.id, team);
|
|
11330
|
+
return team;
|
|
11331
|
+
}
|
|
11332
|
+
addMember(teamId, role, model) {
|
|
11333
|
+
const team = this.teams.get(teamId);
|
|
11334
|
+
if (!team)
|
|
11335
|
+
return null;
|
|
11336
|
+
this.memberCounter++;
|
|
11337
|
+
const member = {
|
|
11338
|
+
id: `member-${Date.now()}-${this.memberCounter}`,
|
|
11339
|
+
role,
|
|
11340
|
+
status: "idle",
|
|
11341
|
+
model
|
|
11342
|
+
};
|
|
11343
|
+
team.members.push(member);
|
|
11344
|
+
return member;
|
|
11345
|
+
}
|
|
11346
|
+
activate(teamId) {
|
|
11347
|
+
const team = this.teams.get(teamId);
|
|
11348
|
+
if (team)
|
|
11349
|
+
team.status = "active";
|
|
11350
|
+
}
|
|
11351
|
+
complete(teamId) {
|
|
11352
|
+
const team = this.teams.get(teamId);
|
|
11353
|
+
if (team)
|
|
11354
|
+
team.status = "completed";
|
|
11355
|
+
}
|
|
11356
|
+
get(teamId) {
|
|
11357
|
+
return this.teams.get(teamId);
|
|
11358
|
+
}
|
|
11359
|
+
getAll() {
|
|
11360
|
+
return [...this.teams.values()];
|
|
11361
|
+
}
|
|
11362
|
+
getActive() {
|
|
11363
|
+
return [...this.teams.values()].find((t) => t.status === "active") || null;
|
|
11364
|
+
}
|
|
11365
|
+
}
|
|
11366
|
+
|
|
11367
|
+
// src/astgrep.ts
|
|
11368
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
11369
|
+
|
|
11370
|
+
class AstGrep {
|
|
11371
|
+
_available = null;
|
|
11372
|
+
search(pattern, language, directory) {
|
|
11373
|
+
if (!this.isAvailable()) {
|
|
11374
|
+
return [];
|
|
11375
|
+
}
|
|
11376
|
+
try {
|
|
11377
|
+
const cmd = `sg run --pattern "${pattern}" --lang ${language} --json ${directory}`;
|
|
11378
|
+
const output = execSync2(cmd, { encoding: "utf-8", timeout: 30000 });
|
|
11379
|
+
const results = JSON.parse(output);
|
|
11380
|
+
if (!Array.isArray(results))
|
|
11381
|
+
return [];
|
|
11382
|
+
return results.map((r) => ({
|
|
11383
|
+
file: r.file?.path || r.file || "",
|
|
11384
|
+
line: r.range?.start?.line || 0,
|
|
11385
|
+
column: r.range?.start?.column || 0,
|
|
11386
|
+
match: r.text || r.source || ""
|
|
11387
|
+
}));
|
|
11388
|
+
} catch {
|
|
11389
|
+
return [];
|
|
11390
|
+
}
|
|
11391
|
+
}
|
|
11392
|
+
rewrite(pattern, rewrite, language, directory) {
|
|
11393
|
+
if (!this.isAvailable()) {
|
|
11394
|
+
return { count: 0, results: [] };
|
|
11395
|
+
}
|
|
11396
|
+
try {
|
|
11397
|
+
const cmd = `sg run --pattern "${pattern}" --rewrite "${rewrite}" --lang ${language} --json ${directory}`;
|
|
11398
|
+
const output = execSync2(cmd, { encoding: "utf-8", timeout: 30000 });
|
|
11399
|
+
const results = JSON.parse(output);
|
|
11400
|
+
if (!Array.isArray(results)) {
|
|
11401
|
+
return { count: 0, results: [] };
|
|
11402
|
+
}
|
|
11403
|
+
const mapped = results.map((r) => ({
|
|
11404
|
+
file: r.file?.path || r.file || "",
|
|
11405
|
+
line: r.range?.start?.line || 0,
|
|
11406
|
+
column: r.range?.start?.column || 0,
|
|
11407
|
+
match: r.text || r.source || "",
|
|
11408
|
+
replacement: r.replacement || undefined
|
|
11409
|
+
}));
|
|
11410
|
+
return { count: mapped.length, results: mapped };
|
|
11411
|
+
} catch {
|
|
11412
|
+
return { count: 0, results: [] };
|
|
11413
|
+
}
|
|
11414
|
+
}
|
|
11415
|
+
isAvailable() {
|
|
11416
|
+
if (this._available !== null)
|
|
11417
|
+
return this._available;
|
|
11418
|
+
try {
|
|
11419
|
+
execSync2("sg --version", { encoding: "utf-8", timeout: 5000 });
|
|
11420
|
+
this._available = true;
|
|
11421
|
+
} catch {
|
|
11422
|
+
this._available = false;
|
|
11423
|
+
}
|
|
11424
|
+
return this._available;
|
|
11425
|
+
}
|
|
11426
|
+
status() {
|
|
11427
|
+
const available = this.isAvailable();
|
|
11428
|
+
if (available) {
|
|
11429
|
+
try {
|
|
11430
|
+
const version = execSync2("sg --version", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
11431
|
+
return `✅ ast-grep is installed (${version})`;
|
|
11432
|
+
} catch {
|
|
11433
|
+
return "✅ ast-grep is installed";
|
|
11434
|
+
}
|
|
11435
|
+
}
|
|
11436
|
+
return "❌ ast-grep is not installed. Install with: brew install ast-grep";
|
|
11437
|
+
}
|
|
11438
|
+
}
|
|
11439
|
+
|
|
11105
11440
|
// src/index.ts
|
|
11106
11441
|
import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync3 } from "node:fs";
|
|
11107
11442
|
import { join as join6 } from "node:path";
|
|
@@ -11113,18 +11448,28 @@ mode: primary
|
|
|
11113
11448
|
|
|
11114
11449
|
# Nexus Orchestrator Agent
|
|
11115
11450
|
|
|
11116
|
-
You are the Nexus Orchestrator — an intelligent multi-agent coordinator. You manage parallel sub-agents
|
|
11451
|
+
You are the Nexus Orchestrator — an intelligent multi-agent coordinator with cost awareness, performance tracking, and security scanning. You manage parallel sub-agents through a disciplined 5-phase execution model, routing tasks to optimal models, reviewing every delivery, and integrating with conflict recovery.
|
|
11452
|
+
|
|
11453
|
+
The currency of the whole orchestration is the **validated commit**: a delivery exists only as an immutable commit SHA that passed review and tests. Never treat a working tree as a delivery.
|
|
11117
11454
|
|
|
11118
|
-
|
|
11455
|
+
Execute the five phases in order. Never skip the quality gate.
|
|
11119
11456
|
|
|
11120
|
-
|
|
11121
|
-
- **Wait for completion** — get results back from agents
|
|
11122
|
-
- **Background execution** — run agents in background while you continue
|
|
11123
|
-
- **Cost-aware routing** — automatically selects optimal models based on task complexity and budget
|
|
11124
|
-
- **Self-healing** — retries failed tasks, transfers context to new agents, escalates if needed
|
|
11125
|
-
- **Performance tracking** — learns which model/role combinations work best
|
|
11457
|
+
## Model configuration
|
|
11126
11458
|
|
|
11127
|
-
|
|
11459
|
+
Read your model pools from the first file that exists: \`.opencode/nexus.jsonc\` in the project, then \`~/.config/opencode/nexus.jsonc\`. The configuration maps complexity levels to model pools:
|
|
11460
|
+
|
|
11461
|
+
\`\`\`json
|
|
11462
|
+
{
|
|
11463
|
+
"models": {
|
|
11464
|
+
"complex": "provider/model-a, provider/model-b",
|
|
11465
|
+
"normal": "provider/model-c"
|
|
11466
|
+
}
|
|
11467
|
+
}
|
|
11468
|
+
### Delegating Tasks (Recommended — spawn + wait + result)
|
|
11469
|
+
\`\`\`
|
|
11470
|
+
nexus.delegate(role="coder", task="Implement JWT auth")
|
|
11471
|
+
nexus.delegate(role="reviewer", task="Review the implementation", timeout=180000)
|
|
11472
|
+
\`\`\`
|
|
11128
11473
|
|
|
11129
11474
|
### Spawning Agents (Wait for Result)
|
|
11130
11475
|
\`\`\`
|
|
@@ -11132,57 +11477,306 @@ nexus.spawn(role="coder", task="Implement JWT auth", wait=true)
|
|
|
11132
11477
|
nexus.spawn(role="reviewer", task="Review implementation", wait=true, timeout=60000)
|
|
11133
11478
|
\`\`\`
|
|
11134
11479
|
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
# Both run in parallel
|
|
11140
|
-
nexus.sessions() # Check progress
|
|
11141
|
-
nexus.background() # Move to background
|
|
11142
|
-
\`\`\`
|
|
11480
|
+
- A model reference is \`provider/model\`, optionally suffixed with a variant: \`provider/model#max\`, \`provider/model#xhigh\`.
|
|
11481
|
+
- Each level maps to a pool of models (comma-separated).
|
|
11482
|
+
- \`complex\` and \`normal\` are task complexity levels for implementation, research, and verification tasks.
|
|
11483
|
+
- The \`reviewer\` and \`simplifier\` sub-agents always run with your own session model — no configuration needed.
|
|
11143
11484
|
|
|
11144
|
-
|
|
11145
|
-
\`\`\`
|
|
11146
|
-
nexus.result(sessionID="ses_xxx") # Get output from completed agent
|
|
11147
|
-
\`\`\`
|
|
11485
|
+
**Rotation rule:** when a pool contains several models, assign them round-robin across tasks. Comparison tasks are the exception: they use the whole pool at once.
|
|
11148
11486
|
|
|
11487
|
+
**Fallback rules:**
|
|
11488
|
+
- Missing \`complex\` → use \`normal\`.
|
|
11489
|
+
- Missing \`normal\`, or no config file → use the session default model everywhere (do not pass an explicit \`model\`).
|
|
11149
11490
|
### Other Tools
|
|
11150
11491
|
\`\`\`
|
|
11151
|
-
nexus.status(detailed=true)
|
|
11152
|
-
nexus.costs()
|
|
11153
|
-
nexus.forecast(tasks='[...]')
|
|
11154
|
-
nexus.performance.scores()
|
|
11155
|
-
nexus.history.list(count=10)
|
|
11156
|
-
nexus.security.scan(content="
|
|
11492
|
+
nexus.status(detailed=true) # Full state
|
|
11493
|
+
nexus.costs() # Cost report
|
|
11494
|
+
nexus.forecast(tasks='[...]') # Predict costs
|
|
11495
|
+
nexus.performance.scores() # Performance data
|
|
11496
|
+
nexus.history.list(count=10) # Execution history
|
|
11497
|
+
nexus.security.scan(content="..", filename="app.ts") # Security scan
|
|
11498
|
+
nexus.dashboard.start(port=4747) # Start web dashboard
|
|
11499
|
+
nexus.dashboard.stop() # Stop web dashboard
|
|
11157
11500
|
\`\`\`
|
|
11158
11501
|
|
|
11159
|
-
|
|
11502
|
+
**Cost-aware selection:** before dispatching, use \`nexus.forecast()\` to estimate task costs. If a complex model exceeds budget thresholds, downgrade to normal. Use \`nexus.performance.best(role)\` to prefer models with proven success rates.
|
|
11160
11503
|
|
|
11504
|
+
## Phase 1 — Decompose
|
|
11161
11505
|
When given a development request:
|
|
11162
11506
|
1. **Analyze** — Break into discrete tasks
|
|
11163
11507
|
2. **Plan** — Determine parallel vs sequential
|
|
11164
11508
|
3. **Estimate** — Use nexus.forecast for costs
|
|
11165
|
-
4. **
|
|
11166
|
-
5. **Spawn** —
|
|
11509
|
+
4. **Delegate** — Sequential tasks: use nexus.delegate (recommended)
|
|
11510
|
+
5. **Spawn** — Parallel tasks: spawn without wait
|
|
11167
11511
|
6. **Monitor** — nexus.sessions() to track progress
|
|
11168
|
-
7. **Review** — nexus.
|
|
11512
|
+
7. **Review** — nexus.delegate(role="reviewer")
|
|
11169
11513
|
8. **Report** — Summarize outcomes
|
|
11170
11514
|
|
|
11515
|
+
1. Analyze the request and the codebase; delegate exploration to an \`explorer\` sub-agent for large codebases, and the decomposition itself to an \`architect\` sub-agent when the plan is genuinely hard.
|
|
11516
|
+
2. Each task must define:
|
|
11517
|
+
- \`id\`: short kebab-case slug
|
|
11518
|
+
- \`kind\`: \`implementation\` | \`research\` | \`verification\`
|
|
11519
|
+
- \`objective\`: what to build, find, or verify, with acceptance criteria
|
|
11520
|
+
- \`scope\`: files or directories it may touch (empty for research and verification)
|
|
11521
|
+
- \`complexity\`: \`complex\` or \`normal\`
|
|
11522
|
+
- \`compare\`: optional, \`true\` to run the task on every model of the pool and keep the best delivery
|
|
11523
|
+
- \`depends_on\`: ids of tasks that must be validated before this one starts
|
|
11524
|
+
3. **Isolation rule:** implementation tasks running in parallel must have disjoint scopes; overlapping scopes are serialized through \`depends_on\`. Research and verification tasks touch no code but still wait for their prerequisites.
|
|
11525
|
+
4. If the request is ambiguous, ask the user before decomposing. Then present the plan (tasks, kinds, models, parallel groups) and get the user's approval before launching any task sub-agent. Read-only preparatory sub-agents (\`explorer\`, \`architect\`) may run before approval — they build the plan, not code.
|
|
11526
|
+
## Web Dashboard
|
|
11527
|
+
|
|
11528
|
+
Start the web dashboard to monitor agents, costs, and history in real-time:
|
|
11529
|
+
\`\`\`
|
|
11530
|
+
nexus.dashboard.start(port=4747, host="127.0.0.1")
|
|
11531
|
+
\`\`\`
|
|
11532
|
+
Then open http://127.0.0.1:4747 in your browser.
|
|
11533
|
+
|
|
11534
|
+
Stop it when done:
|
|
11535
|
+
\`\`\`
|
|
11536
|
+
nexus.dashboard.stop()
|
|
11537
|
+
\`\`\`
|
|
11538
|
+
|
|
11539
|
+
## Git Workflow & Environment Detection
|
|
11540
|
+
|
|
11541
|
+
Before starting any work, detect the git environment and follow proper workflow:
|
|
11542
|
+
|
|
11543
|
+
### Detection
|
|
11544
|
+
- Run \`git status\` to verify it's a git repo
|
|
11545
|
+
- Check \`.github/\` or \`.gitlab-ci.yml\` for CI/CD
|
|
11546
|
+
- Check \`git remote -v\` for GitHub/GitLab URLs
|
|
11547
|
+
|
|
11548
|
+
### Rules
|
|
11549
|
+
1. **NEVER push directly to main** — always create a feature branch
|
|
11550
|
+
2. **Use conventional commits**: \`feat:\`, \`fix:\`, \`docs:\`, \`chore:\`
|
|
11551
|
+
3. **Create PR** for review before merging
|
|
11552
|
+
4. **Ask the user** about branching strategy if unclear
|
|
11553
|
+
|
|
11554
|
+
### Memory
|
|
11555
|
+
Store git workflow preferences in memory for future reference.
|
|
11556
|
+
|
|
11171
11557
|
## Quality Gates
|
|
11172
11558
|
|
|
11173
|
-
|
|
11559
|
+
## Phase 2 — Worktrees and snapshots
|
|
11560
|
+
|
|
11561
|
+
1. Enable worktree isolation: \`nexus.worktree.enable()\`. Verify the repository is clean; if not, stop and report. Record the base branch and its commit — the immovable anchor for the whole run.
|
|
11562
|
+
2. Every implementation task gets its own worktree and branch, following the environment's conventions; the main checkout stays untouched. Verification tasks also get their own worktree at the prepared snapshot. Comparison candidates use separate worktrees.
|
|
11563
|
+
3. A task's worktree is created only once all its prerequisites are validated, from this snapshot rule (transitive implementation ancestors count, even through research or verification prerequisites):
|
|
11564
|
+
- No implementation ancestor → start from the base commit
|
|
11565
|
+
- Exactly one implementation ancestor → fast-path: branch directly from that ancestor's validated commit
|
|
11566
|
+
- Several implementation ancestors → start from the base commit and merge each ancestor's validated commit in \`depends_on\` order
|
|
11567
|
+
4. Research prerequisites contribute no commits: pass their findings explicitly in the dependent's prompt. Verification reports must identify the verified commit SHA, the checks performed, and their results — they produce findings, not implementation deliveries. A verification report only validates the snapshot it ran against: if an ancestor's validated revision later changes, re-run the verification on the new snapshot.
|
|
11568
|
+
5. Comparison candidates must all start from the same snapshot.
|
|
11569
|
+
6. A conflict while assembling a multi-ancestor snapshot is delegated to the involved ancestor's implementer; the assembled snapshot must pass build and tests before the dependent is dispatched.
|
|
11570
|
+
|
|
11571
|
+
## Phase 3 — Dispatch
|
|
11572
|
+
|
|
11573
|
+
1. Spawn one sub-agent per ready task (all prerequisites validated), in the background with \`nexus.spawn(role=..., task=..., wait=false)\`, with the \`model\` picked from the pool matching its complexity.
|
|
11574
|
+
2. Each prompt must include:
|
|
11575
|
+
- the kind, objective, and acceptance criteria
|
|
11576
|
+
- the task scope, with the instruction to never touch files outside it
|
|
11577
|
+
- findings from research and verification prerequisites, when any
|
|
11578
|
+
- for implementation tasks: work inside your worktree (move your session there so every read, edit, and command targets it) and run the project build and tests before reporting — the final delivery commit is produced during the quality gate
|
|
11579
|
+
- for verification tasks: work inside your snapshot worktree (move your session there) and report the verified commit SHA, the checks performed, and their results
|
|
11580
|
+
3. **Comparison tasks:** spawn one sub-agent per model in the pool, in parallel, each in its own worktree.
|
|
11581
|
+
4. Monitor progress: use \`nexus.sessions()\` to track active agents. When a task completes, use \`nexus.result(sessionID=...)\` to retrieve its output.
|
|
11582
|
+
5. When a task is validated, prepare its dependents' worktrees (Phase 2) and dispatch them.
|
|
11583
|
+
|
|
11584
|
+
## Phase 4 — Quality gate (every implementation delivery)
|
|
11585
|
+
|
|
11586
|
+
Research and verification reports are assessed directly against their acceptance criteria.
|
|
11587
|
+
|
|
11588
|
+
1. **Review:** spawn a \`reviewer\` sub-agent with \`nexus.spawn(role="reviewer", wait=true)\` to review the delivered code in the worktree — committed, staged, unstaged, and untracked alike.
|
|
11589
|
+
2. **Security scan:** run \`nexus.security.scan(content=..., filename=...)\` on changed files. Flag any security findings as blocking issues.
|
|
11590
|
+
3. **Rework loop:** blocking issues go back to the session that produced the delivery (resume it, full context kept), optionally under a different model — escalate when stuck, downgrade when slow or costly. Each round repeats review until no blocking issues remain.
|
|
11591
|
+
4. **Commit:** the implementer commits the complete delivery — exactly what was reviewed; any further change goes through the rework loop.
|
|
11592
|
+
5. **Validation:** build and tests pass at that commit and the worktree is clean → record the commit SHA as the task's \`validated_commit\`.
|
|
11593
|
+
6. **Comparison tasks:** once every candidate passed the gate (or after one review round), keep the best delivery against the acceptance criteria and discard the others. Use \`nexus.performance.scores()\` to inform which model delivered the best result.
|
|
11594
|
+
7. **Limits:** at most 2 rework rounds per task, then \`failed\`: remove its worktree, keep its branch for possible later recovery. When a task fails, transitively mark its pending dependents \`failed\` (recording the failing prerequisite) and continue independent tasks so the final barrier stays reachable.
|
|
11595
|
+
8. **Cost tracking:** after each quality gate round, call \`nexus.costs()\` to check budget status. If budget is exhausted, halt remaining tasks and report.
|
|
11596
|
+
|
|
11597
|
+
## Phase 5 — Final integration and simplification
|
|
11598
|
+
|
|
11599
|
+
1. Wait until every task is validated or marked failed. Never merge mid-flight.
|
|
11600
|
+
2. Merge each validated implementation task's \`validated_commit\` into the base branch, in \`depends_on\` topological order; research and verification tasks produce findings, not commits. Once a task is integrated, clean up after it immediately: remove its worktree, delete its merged branch, and delete temporary artifacts it created outside the repository (build outputs, logs, captures) once they are no longer needed.
|
|
11601
|
+
3. **Conflict recovery** — never force:
|
|
11602
|
+
- Abort the conflicting merge in the base checkout first; never leave an unfinished merge behind.
|
|
11603
|
+
- Delegate to the implementer: merge the current integration commit into its task branch in its worktree and resolve.
|
|
11604
|
+
- The resolution changes the delivery: commit it, re-run the quality gate, record the replacement \`validated_commit\`, then retry.
|
|
11605
|
+
- When a prerequisite's validated revision changes, revalidate its affected dependents — bounded to one cascade per integration; further churn marks the task \`failed\`.
|
|
11606
|
+
- If a task ultimately fails here, exclude its unmerged descendants — even previously validated ones — and confirm the base checkout is clean before continuing.
|
|
11607
|
+
4. **Simplification pass:** once the final merge is done, spawn a \`simplifier\` sub-agent with \`nexus.spawn(role="documenter", task="Simplify the integrated changes between BASE and HEAD", wait=true)\`. Its edits get a focused \`reviewer\` review, then build and tests re-run, and you commit the result on the base branch.
|
|
11608
|
+
5. Remove every remaining worktree (research, verification, failed, excluded) via \`nexus.worktree.disable()\` and delete stray temporary artifacts.
|
|
11609
|
+
6. **Final report:** summarize per-task status, model(s) used, review round-trips, costs incurred (\`nexus.costs()\`), performance insights (\`nexus.performance.scores()\`), and the overall outcome with follow-ups.
|
|
11610
|
+
|
|
11611
|
+
## Rules
|
|
11612
|
+
|
|
11613
|
+
- Never modify code directly; all code changes go through sub-agents. The only commits you create are technical ones: snapshot assembly merges, final integration merges, and the post-simplification commit. Validated delivery commits always come from sub-agents.
|
|
11614
|
+
- Never force a merge, rewrite history, or discard uncommitted user work.
|
|
11615
|
+
- Rework always resumes the session that produced the delivery, possibly under a different model.
|
|
11616
|
+
- Never leave the base checkout in an unfinished merge state.
|
|
11617
|
+
- Always identify a validated task by its immutable \`validated_commit\`: use recorded SHAs, never branch names or working trees, when creating dependents and integrating deliveries.
|
|
11618
|
+
- Report progress after each phase. Keep reports concise.
|
|
11619
|
+
|
|
11620
|
+
## Nexus-Specific Advantages
|
|
11621
|
+
|
|
11622
|
+
These capabilities differentiate Nexus from a plain orchestrator:
|
|
11623
|
+
|
|
11624
|
+
### Cost Intelligence
|
|
11625
|
+
- \`nexus.forecast(tasks=...)\` — estimate costs before dispatching, avoid budget surprises
|
|
11626
|
+
- \`nexus.costs()\` — real-time budget status, halt if exhausted
|
|
11627
|
+
- \`nexus.model.costs(model=...)\` — inspect per-model pricing to make informed routing decisions
|
|
11628
|
+
|
|
11629
|
+
### Performance Learning
|
|
11630
|
+
- \`nexus.performance.scores()\` — see which model/role combos succeed most
|
|
11631
|
+
- \`nexus.performance.best(role=...)\` — pick the proven winner for a role
|
|
11632
|
+
- Scores improve routing over time: complex tasks go to high-performers, simple tasks use cheaper models
|
|
11633
|
+
|
|
11634
|
+
### Security Scanning
|
|
11635
|
+
- \`nexus.security.scan(content=..., filename=...)\` — automated security review at the quality gate
|
|
11636
|
+
- Findings become blocking issues in the rework loop
|
|
11637
|
+
- No code ships without a security pass
|
|
11638
|
+
|
|
11639
|
+
### Observability
|
|
11640
|
+
- \`nexus.dashboard.start()\` — live web dashboard for monitoring orchestrator state
|
|
11641
|
+
- \`nexus.status(detailed=true)\` — full metrics on demand
|
|
11642
|
+
- \`nexus.history.list(count=N)\` — execution history for post-mortem analysis
|
|
11643
|
+
- \`nexus.history.stats()\` — aggregate success rates and cost trends
|
|
11644
|
+
|
|
11645
|
+
### Extensibility
|
|
11646
|
+
- \`nexus.roles.add(...)\` — define custom agent roles with specialized prompts
|
|
11647
|
+
- \`nexus.template(name=...)\` — reusable task templates for common workflows
|
|
11648
|
+
- \`nexus.worktree.enable()\` — git worktree isolation for parallel safety
|
|
11649
|
+
- Every code change → nexus.delegate(role="reviewer")
|
|
11174
11650
|
- Security check → nexus.security.scan()
|
|
11175
|
-
- Testing → nexus.
|
|
11651
|
+
- Testing → nexus.delegate(role="tester")
|
|
11176
11652
|
`;
|
|
11177
11653
|
var src_default = define({
|
|
11178
11654
|
id: "nexus",
|
|
11179
11655
|
async setup(ctx) {
|
|
11180
11656
|
try {
|
|
11181
11657
|
const agentDir = join6(homedir2(), ".config", "opencode", "agents");
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
writeFileSync2(
|
|
11658
|
+
mkdirSync4(agentDir, { recursive: true });
|
|
11659
|
+
const orchestratorFile = join6(agentDir, "nexus-orchestrator.md");
|
|
11660
|
+
if (!existsSync3(orchestratorFile)) {
|
|
11661
|
+
writeFileSync2(orchestratorFile, NEXUS_AGENT_CONTENT, "utf-8");
|
|
11662
|
+
}
|
|
11663
|
+
const subagents = {
|
|
11664
|
+
"nexus-architect.md": `---
|
|
11665
|
+
description: Nexus Architect agent — designs system architecture with cost-aware model selection
|
|
11666
|
+
mode: subagent
|
|
11667
|
+
permissions:
|
|
11668
|
+
- action: edit
|
|
11669
|
+
resource: "*"
|
|
11670
|
+
- action: shell
|
|
11671
|
+
resource: "*"
|
|
11672
|
+
---
|
|
11673
|
+
|
|
11674
|
+
# Nexus Architect Agent
|
|
11675
|
+
|
|
11676
|
+
You are a Nexus Architect sub-agent. Design system architecture.
|
|
11677
|
+
|
|
11678
|
+
- System design and architecture patterns
|
|
11679
|
+
- High-level technical decisions
|
|
11680
|
+
- API design and interface definitions
|
|
11681
|
+
- Consider cost implications
|
|
11682
|
+
- Document decisions and trade-offs`,
|
|
11683
|
+
"nexus-coder.md": `---
|
|
11684
|
+
description: Nexus Coder agent — implements code with cost-aware model selection
|
|
11685
|
+
mode: subagent
|
|
11686
|
+
permissions:
|
|
11687
|
+
- action: edit
|
|
11688
|
+
resource: "*"
|
|
11689
|
+
- action: shell
|
|
11690
|
+
resource: "*"
|
|
11691
|
+
---
|
|
11692
|
+
|
|
11693
|
+
# Nexus Coder Agent
|
|
11694
|
+
|
|
11695
|
+
You are a Nexus Coder sub-agent. Implement code tasks.
|
|
11696
|
+
|
|
11697
|
+
- Write clean, efficient TypeScript/JavaScript code
|
|
11698
|
+
- Follow existing code patterns
|
|
11699
|
+
- Add tests for functionality
|
|
11700
|
+
- Use model from nexus config for your role
|
|
11701
|
+
- Commit with conventional commit messages`,
|
|
11702
|
+
"nexus-explorer.md": `---
|
|
11703
|
+
description: Nexus Explorer agent — explores codebases and provides architecture analysis
|
|
11704
|
+
mode: subagent
|
|
11705
|
+
permissions:
|
|
11706
|
+
- action: edit
|
|
11707
|
+
resource: "*"
|
|
11708
|
+
effect: deny
|
|
11709
|
+
---
|
|
11710
|
+
|
|
11711
|
+
# Nexus Explorer Agent
|
|
11712
|
+
|
|
11713
|
+
You are a Nexus Explorer sub-agent. Explore codebases.
|
|
11714
|
+
|
|
11715
|
+
- Read-only exploration
|
|
11716
|
+
- Find files by patterns
|
|
11717
|
+
- Analyze dependencies
|
|
11718
|
+
- Report architecture findings`,
|
|
11719
|
+
"nexus-tester.md": `---
|
|
11720
|
+
description: Nexus Tester agent — writes and runs tests for quality assurance
|
|
11721
|
+
mode: subagent
|
|
11722
|
+
permissions:
|
|
11723
|
+
- action: edit
|
|
11724
|
+
resource: "*"
|
|
11725
|
+
- action: shell
|
|
11726
|
+
resource: "*"
|
|
11727
|
+
---
|
|
11728
|
+
|
|
11729
|
+
# Nexus Tester Agent
|
|
11730
|
+
|
|
11731
|
+
You are a Nexus Tester sub-agent. Write and run tests.
|
|
11732
|
+
|
|
11733
|
+
- Unit tests for new functions
|
|
11734
|
+
- Integration tests for features
|
|
11735
|
+
- Test edge cases and errors
|
|
11736
|
+
- Follow existing test patterns`,
|
|
11737
|
+
"nexus-reviewer.md": `---
|
|
11738
|
+
description: Nexus Reviewer agent — reviews code for quality, security, and correctness
|
|
11739
|
+
mode: subagent
|
|
11740
|
+
permissions:
|
|
11741
|
+
- action: edit
|
|
11742
|
+
resource: "*"
|
|
11743
|
+
effect: deny
|
|
11744
|
+
---
|
|
11745
|
+
|
|
11746
|
+
# Nexus Reviewer Agent
|
|
11747
|
+
|
|
11748
|
+
You are a Nexus Reviewer sub-agent. Review code changes.
|
|
11749
|
+
|
|
11750
|
+
- Code correctness and logic
|
|
11751
|
+
- Security vulnerabilities
|
|
11752
|
+
- Performance implications
|
|
11753
|
+
- Test coverage
|
|
11754
|
+
- Provide APPROVED / CHANGES REQUESTED assessment`,
|
|
11755
|
+
"nexus-documenter.md": `---
|
|
11756
|
+
description: Nexus Documenter agent — writes clear, comprehensive technical documentation
|
|
11757
|
+
mode: subagent
|
|
11758
|
+
permissions:
|
|
11759
|
+
- action: edit
|
|
11760
|
+
resource: "*"
|
|
11761
|
+
- action: shell
|
|
11762
|
+
resource: "*"
|
|
11763
|
+
---
|
|
11764
|
+
|
|
11765
|
+
# Nexus Documenter Agent
|
|
11766
|
+
|
|
11767
|
+
You are a Nexus Documenter sub-agent. Write documentation.
|
|
11768
|
+
|
|
11769
|
+
- API documentation
|
|
11770
|
+
- README files
|
|
11771
|
+
- Architecture docs
|
|
11772
|
+
- Include code examples
|
|
11773
|
+
- Keep docs close to code`
|
|
11774
|
+
};
|
|
11775
|
+
for (const [filename, content] of Object.entries(subagents)) {
|
|
11776
|
+
const filepath = join6(agentDir, filename);
|
|
11777
|
+
if (!existsSync3(filepath)) {
|
|
11778
|
+
writeFileSync2(filepath, content, "utf-8");
|
|
11779
|
+
}
|
|
11186
11780
|
}
|
|
11187
11781
|
} catch {}
|
|
11188
11782
|
try {
|
|
@@ -11197,12 +11791,49 @@ var src_default = define({
|
|
|
11197
11791
|
}
|
|
11198
11792
|
}
|
|
11199
11793
|
} catch {}
|
|
11794
|
+
try {
|
|
11795
|
+
const nexusConfigPath = join6(process.cwd(), ".opencode", "nexus.jsonc");
|
|
11796
|
+
if (existsSync3(nexusConfigPath)) {
|
|
11797
|
+
const nexusContent = readFileSync3(nexusConfigPath, "utf-8");
|
|
11798
|
+
const stripped = nexusContent.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
11799
|
+
const nexusConfig = JSON.parse(stripped);
|
|
11800
|
+
if (nexusConfig.models) {
|
|
11801
|
+
const configPath = join6(homedir2(), ".config", "opencode", "opencode.jsonc");
|
|
11802
|
+
if (existsSync3(configPath)) {
|
|
11803
|
+
let configContent = readFileSync3(configPath, "utf-8");
|
|
11804
|
+
const configObj = JSON.parse(configContent);
|
|
11805
|
+
if (!configObj.agents)
|
|
11806
|
+
configObj.agents = {};
|
|
11807
|
+
if (!configObj.agents["nexus-orchestrator"])
|
|
11808
|
+
configObj.agents["nexus-orchestrator"] = {};
|
|
11809
|
+
if (!configObj.agents["nexus-orchestrator"].model) {
|
|
11810
|
+
configObj.agents["nexus-orchestrator"].model = nexusConfig.models.coder || "opencode-go/mimo-v2.5";
|
|
11811
|
+
writeFileSync2(configPath, JSON.stringify(configObj, null, 2) + `
|
|
11812
|
+
`, "utf-8");
|
|
11813
|
+
}
|
|
11814
|
+
}
|
|
11815
|
+
}
|
|
11816
|
+
}
|
|
11817
|
+
} catch {}
|
|
11200
11818
|
const orchestrator = new NexusOrchestrator;
|
|
11819
|
+
const goalManager = new GoalManager;
|
|
11201
11820
|
await orchestrator.initialize(ctx, () => {
|
|
11202
11821
|
const state = orchestrator.getState();
|
|
11203
11822
|
ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(state))).catch(() => {});
|
|
11823
|
+
const sidebarState = {
|
|
11824
|
+
agents: state.agents,
|
|
11825
|
+
totalCost: state.totalSpent,
|
|
11826
|
+
budgetRemaining: state.budgetRemaining
|
|
11827
|
+
};
|
|
11828
|
+
ctx.storage.set("nexus-sidebar-state", JSON.parse(JSON.stringify(sidebarState))).catch(() => {});
|
|
11204
11829
|
});
|
|
11205
11830
|
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
11831
|
+
const initialState = orchestrator.getState();
|
|
11832
|
+
await ctx.storage.set("nexus-sidebar-state", JSON.parse(JSON.stringify({
|
|
11833
|
+
agents: initialState.agents,
|
|
11834
|
+
totalCost: initialState.totalSpent,
|
|
11835
|
+
budgetRemaining: initialState.budgetRemaining
|
|
11836
|
+
})));
|
|
11206
11837
|
await ctx.tool.transform((editor) => {
|
|
11207
11838
|
editor.namespace({
|
|
11208
11839
|
name: "nexus",
|
|
@@ -11422,7 +12053,7 @@ var src_default = define({
|
|
|
11422
12053
|
properties: {
|
|
11423
12054
|
role: { type: "string", description: "Agent role (architect, coder, reviewer, tester, explorer, documenter)" },
|
|
11424
12055
|
task: { type: "string", description: "Task description" },
|
|
11425
|
-
model: { type: "string", description: "Model override (optional)" },
|
|
12056
|
+
model: { type: "string", description: "Model override (optional, e.g. 'anthropic/claude-sonnet-4-6')" },
|
|
11426
12057
|
wait: { type: "boolean", description: "Wait for completion (default: false)" },
|
|
11427
12058
|
timeout: { type: "number", description: "Timeout in ms when waiting (default: 120000)" }
|
|
11428
12059
|
},
|
|
@@ -11432,17 +12063,62 @@ var src_default = define({
|
|
|
11432
12063
|
execute: async (input) => {
|
|
11433
12064
|
const { role, task, model, wait, timeout } = input;
|
|
11434
12065
|
try {
|
|
11435
|
-
orchestrator.parentSessionID
|
|
12066
|
+
if (!orchestrator.parentSessionID) {
|
|
12067
|
+
try {
|
|
12068
|
+
const currentSession = ctx.session?.current?.();
|
|
12069
|
+
if (currentSession?.id) {
|
|
12070
|
+
orchestrator.parentSessionID = currentSession.id;
|
|
12071
|
+
}
|
|
12072
|
+
} catch {}
|
|
12073
|
+
}
|
|
11436
12074
|
const agent = await orchestrator.spawnAgent({ role, model });
|
|
11437
12075
|
await orchestrator.ctx.session.prompt({
|
|
11438
12076
|
sessionID: agent.sessionID,
|
|
11439
12077
|
text: task
|
|
11440
12078
|
});
|
|
12079
|
+
agent.status = "working";
|
|
12080
|
+
orchestrator.notifyStateChange();
|
|
11441
12081
|
if (wait) {
|
|
11442
12082
|
const waitTimeout = timeout || 120000;
|
|
12083
|
+
const waitPromise = orchestrator.ctx.session.wait({ sessionID: agent.sessionID });
|
|
12084
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out after ${waitTimeout}ms`)), waitTimeout));
|
|
11443
12085
|
try {
|
|
11444
|
-
await
|
|
11445
|
-
} catch {
|
|
12086
|
+
await Promise.race([waitPromise, timeoutPromise]);
|
|
12087
|
+
} catch (waitError) {
|
|
12088
|
+
agent.status = "working";
|
|
12089
|
+
orchestrator.notifyStateChange();
|
|
12090
|
+
try {
|
|
12091
|
+
const messages = await orchestrator.ctx.session.context({ sessionID: agent.sessionID });
|
|
12092
|
+
const lastMsg = messages.filter((m) => m.role === "assistant").pop();
|
|
12093
|
+
if (lastMsg) {
|
|
12094
|
+
agent.status = "completed";
|
|
12095
|
+
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
12096
|
+
const taskPreview = task.length > 80 ? task.substring(0, 77) + "..." : task;
|
|
12097
|
+
return {
|
|
12098
|
+
content: [
|
|
12099
|
+
`${agent.name}`,
|
|
12100
|
+
`\uD83D\uDCCB Task: ${taskPreview}`,
|
|
12101
|
+
`⏱️ Status: ${waitError.message || "timeout"}`,
|
|
12102
|
+
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
12103
|
+
`
|
|
12104
|
+
--- Partial Result ---`,
|
|
12105
|
+
typeof lastMsg.content === "string" ? lastMsg.content : JSON.stringify(lastMsg.content)
|
|
12106
|
+
].join(`
|
|
12107
|
+
`)
|
|
12108
|
+
};
|
|
12109
|
+
}
|
|
12110
|
+
} catch {}
|
|
12111
|
+
return {
|
|
12112
|
+
content: [
|
|
12113
|
+
`${agent.name}`,
|
|
12114
|
+
`\uD83D\uDCCB Task: ${task.length > 80 ? task.substring(0, 77) + "..." : task}`,
|
|
12115
|
+
`⏱️ Status: ${waitError.message || "timeout"}`,
|
|
12116
|
+
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
12117
|
+
`\uD83D\uDCA1 Use nexus.result(sessionID="${agent.sessionID}") to check later`
|
|
12118
|
+
].join(`
|
|
12119
|
+
`)
|
|
12120
|
+
};
|
|
12121
|
+
}
|
|
11446
12122
|
try {
|
|
11447
12123
|
const messages = await orchestrator.ctx.session.context({ sessionID: agent.sessionID });
|
|
11448
12124
|
const lastMsg = messages.filter((m) => m.role === "assistant").pop();
|
|
@@ -11458,11 +12134,23 @@ var src_default = define({
|
|
|
11458
12134
|
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
11459
12135
|
`
|
|
11460
12136
|
--- Result ---`,
|
|
11461
|
-
result
|
|
12137
|
+
typeof result === "string" ? result : JSON.stringify(result)
|
|
11462
12138
|
].join(`
|
|
11463
12139
|
`)
|
|
11464
12140
|
};
|
|
11465
|
-
} catch {
|
|
12141
|
+
} catch {
|
|
12142
|
+
agent.status = "completed";
|
|
12143
|
+
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
12144
|
+
return {
|
|
12145
|
+
content: [
|
|
12146
|
+
`${agent.name}`,
|
|
12147
|
+
`✅ Status: completed`,
|
|
12148
|
+
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
12149
|
+
`⚠️ Could not read result output`
|
|
12150
|
+
].join(`
|
|
12151
|
+
`)
|
|
12152
|
+
};
|
|
12153
|
+
}
|
|
11466
12154
|
}
|
|
11467
12155
|
const complexity = orchestrator.analyzeComplexity({
|
|
11468
12156
|
id: `spawn-${Date.now()}`,
|
|
@@ -11485,7 +12173,7 @@ var src_default = define({
|
|
|
11485
12173
|
`\uD83D\uDCCA Complexity: ${complexity.overall}/100 (${complexity.factors.riskLevel} risk)`,
|
|
11486
12174
|
`\uD83E\uDD16 Model reasoning: ${modelSelection.reasoning}`,
|
|
11487
12175
|
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
11488
|
-
`\uD83D\uDCA1 Use wait=true to wait for completion`
|
|
12176
|
+
`\uD83D\uDCA1 Use wait=true to wait for completion, or nexus.result() to fetch later`
|
|
11489
12177
|
].join(`
|
|
11490
12178
|
`);
|
|
11491
12179
|
return { content: output };
|
|
@@ -11494,6 +12182,72 @@ var src_default = define({
|
|
|
11494
12182
|
}
|
|
11495
12183
|
}
|
|
11496
12184
|
});
|
|
12185
|
+
editor.add({
|
|
12186
|
+
name: "delegate",
|
|
12187
|
+
description: "Delegate a task to a sub-agent and wait for result (convenience wrapper around spawn+wait)",
|
|
12188
|
+
input: {
|
|
12189
|
+
type: "object",
|
|
12190
|
+
properties: {
|
|
12191
|
+
role: { type: "string", description: "Agent role (architect, coder, reviewer, tester, explorer, documenter)" },
|
|
12192
|
+
task: { type: "string", description: "Task description" },
|
|
12193
|
+
model: { type: "string", description: "Model override (optional)" },
|
|
12194
|
+
timeout: { type: "number", description: "Timeout in ms (default: 120000)" }
|
|
12195
|
+
},
|
|
12196
|
+
required: ["role", "task"],
|
|
12197
|
+
additionalProperties: false
|
|
12198
|
+
},
|
|
12199
|
+
execute: async (input) => {
|
|
12200
|
+
const { role, task, model, timeout } = input;
|
|
12201
|
+
try {
|
|
12202
|
+
const agent = await orchestrator.spawnAgent({ role, model });
|
|
12203
|
+
await orchestrator.ctx.session.prompt({
|
|
12204
|
+
sessionID: agent.sessionID,
|
|
12205
|
+
text: task
|
|
12206
|
+
});
|
|
12207
|
+
agent.status = "working";
|
|
12208
|
+
orchestrator.notifyStateChange();
|
|
12209
|
+
const waitTimeout = timeout || 120000;
|
|
12210
|
+
const waitPromise = orchestrator.ctx.session.wait({ sessionID: agent.sessionID });
|
|
12211
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve("timeout"), waitTimeout));
|
|
12212
|
+
const outcome = await Promise.race([waitPromise.then(() => "completed"), timeoutPromise]);
|
|
12213
|
+
try {
|
|
12214
|
+
const messages = await orchestrator.ctx.session.context({ sessionID: agent.sessionID });
|
|
12215
|
+
const lastMsg = messages.filter((m) => m.role === "assistant").pop();
|
|
12216
|
+
const resultContent = lastMsg?.content || (outcome === "timeout" ? "Timed out — agent may still be running" : "Completed with no output");
|
|
12217
|
+
agent.status = outcome === "timeout" ? "working" : "completed";
|
|
12218
|
+
orchestrator.notifyStateChange();
|
|
12219
|
+
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
12220
|
+
const statusIcon = outcome === "timeout" ? "⏱️" : "✅";
|
|
12221
|
+
return {
|
|
12222
|
+
content: [
|
|
12223
|
+
`${agent.name}`,
|
|
12224
|
+
`\uD83D\uDCCB Task: ${task.length > 80 ? task.substring(0, 77) + "..." : task}`,
|
|
12225
|
+
`${statusIcon} Status: ${outcome}`,
|
|
12226
|
+
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
12227
|
+
`
|
|
12228
|
+
--- Result ---`,
|
|
12229
|
+
typeof resultContent === "string" ? resultContent : JSON.stringify(resultContent)
|
|
12230
|
+
].join(`
|
|
12231
|
+
`)
|
|
12232
|
+
};
|
|
12233
|
+
} catch {
|
|
12234
|
+
agent.status = outcome === "timeout" ? "working" : "completed";
|
|
12235
|
+
orchestrator.notifyStateChange();
|
|
12236
|
+
return {
|
|
12237
|
+
content: [
|
|
12238
|
+
`${agent.name}`,
|
|
12239
|
+
`${outcome === "timeout" ? "⏱️" : "✅"} Status: ${outcome}`,
|
|
12240
|
+
`\uD83D\uDCCE Session: ${agent.sessionID}`,
|
|
12241
|
+
`⚠️ Could not read result output`
|
|
12242
|
+
].join(`
|
|
12243
|
+
`)
|
|
12244
|
+
};
|
|
12245
|
+
}
|
|
12246
|
+
} catch (error) {
|
|
12247
|
+
return { content: `Delegate failed: ${error.message}` };
|
|
12248
|
+
}
|
|
12249
|
+
}
|
|
12250
|
+
});
|
|
11497
12251
|
editor.add({
|
|
11498
12252
|
name: "template",
|
|
11499
12253
|
description: "List or instantiate task templates",
|
|
@@ -11764,6 +12518,381 @@ ${lastMsg.content}` };
|
|
|
11764
12518
|
}
|
|
11765
12519
|
}
|
|
11766
12520
|
});
|
|
12521
|
+
editor.add({
|
|
12522
|
+
name: "clarify",
|
|
12523
|
+
description: "Ask clarifying question before proceeding with ambiguous task",
|
|
12524
|
+
input: {
|
|
12525
|
+
type: "object",
|
|
12526
|
+
properties: {
|
|
12527
|
+
question: { type: "string", description: "The clarifying question to ask" },
|
|
12528
|
+
options: { type: "string", description: "Comma-separated options to present" },
|
|
12529
|
+
assumption: { type: "string", description: "Default assumption if no response" }
|
|
12530
|
+
},
|
|
12531
|
+
required: ["question"],
|
|
12532
|
+
additionalProperties: false
|
|
12533
|
+
},
|
|
12534
|
+
execute: async (input) => {
|
|
12535
|
+
const { question, options, assumption } = input;
|
|
12536
|
+
const optionList = options ? options.split(",").map((o) => o.trim()) : [];
|
|
12537
|
+
let response = `❓ ${question}`;
|
|
12538
|
+
if (optionList.length > 0) {
|
|
12539
|
+
response += `
|
|
12540
|
+
Options: ${optionList.map((o, i) => `${i + 1}. ${o}`).join(", ")}`;
|
|
12541
|
+
}
|
|
12542
|
+
if (assumption) {
|
|
12543
|
+
response += `
|
|
12544
|
+
\uD83D\uDCA1 Default: ${assumption}`;
|
|
12545
|
+
}
|
|
12546
|
+
return { content: response };
|
|
12547
|
+
}
|
|
12548
|
+
});
|
|
12549
|
+
editor.add({
|
|
12550
|
+
name: "todo.add",
|
|
12551
|
+
description: "Add a todo item to track work",
|
|
12552
|
+
input: {
|
|
12553
|
+
type: "object",
|
|
12554
|
+
properties: {
|
|
12555
|
+
description: { type: "string", description: "Todo description" },
|
|
12556
|
+
assignedTo: { type: "string", description: "Agent or role to assign (optional)" }
|
|
12557
|
+
},
|
|
12558
|
+
required: ["description"],
|
|
12559
|
+
additionalProperties: false
|
|
12560
|
+
},
|
|
12561
|
+
execute: async (input) => {
|
|
12562
|
+
const { description, assignedTo } = input;
|
|
12563
|
+
const item = orchestrator.todoEnforcer.add(description, assignedTo);
|
|
12564
|
+
return { content: `\uD83D\uDCDD Todo added: ${item.id}: ${item.description} (${item.status})` };
|
|
12565
|
+
}
|
|
12566
|
+
});
|
|
12567
|
+
editor.add({
|
|
12568
|
+
name: "todo.list",
|
|
12569
|
+
description: "List all todo items",
|
|
12570
|
+
input: {
|
|
12571
|
+
type: "object",
|
|
12572
|
+
properties: {},
|
|
12573
|
+
additionalProperties: false
|
|
12574
|
+
},
|
|
12575
|
+
execute: async () => {
|
|
12576
|
+
return { content: orchestrator.todoEnforcer.formatAll() };
|
|
12577
|
+
}
|
|
12578
|
+
});
|
|
12579
|
+
editor.add({
|
|
12580
|
+
name: "todo.complete",
|
|
12581
|
+
description: "Mark a todo item as completed",
|
|
12582
|
+
input: {
|
|
12583
|
+
type: "object",
|
|
12584
|
+
properties: {
|
|
12585
|
+
id: { type: "string", description: "Todo item ID" }
|
|
12586
|
+
},
|
|
12587
|
+
required: ["id"],
|
|
12588
|
+
additionalProperties: false
|
|
12589
|
+
},
|
|
12590
|
+
execute: async (input) => {
|
|
12591
|
+
const { id } = input;
|
|
12592
|
+
const item = orchestrator.todoEnforcer.get(id);
|
|
12593
|
+
if (!item)
|
|
12594
|
+
return { content: `❌ Todo ${id} not found.` };
|
|
12595
|
+
orchestrator.todoEnforcer.complete(id);
|
|
12596
|
+
return { content: `✅ Todo completed: ${id}: ${item.description}` };
|
|
12597
|
+
}
|
|
12598
|
+
});
|
|
12599
|
+
editor.add({
|
|
12600
|
+
name: "todo.stats",
|
|
12601
|
+
description: "Get todo statistics",
|
|
12602
|
+
input: {
|
|
12603
|
+
type: "object",
|
|
12604
|
+
properties: {},
|
|
12605
|
+
additionalProperties: false
|
|
12606
|
+
},
|
|
12607
|
+
execute: async () => {
|
|
12608
|
+
const stats = orchestrator.todoEnforcer.getStats();
|
|
12609
|
+
return {
|
|
12610
|
+
content: `\uD83D\uDCCA Todo Statistics:
|
|
12611
|
+
Total: ${stats.total}
|
|
12612
|
+
⏳ Pending: ${stats.pending}
|
|
12613
|
+
\uD83D\uDD04 In Progress: ${stats.inProgress}
|
|
12614
|
+
✅ Completed: ${stats.completed}
|
|
12615
|
+
\uD83D\uDEAB Blocked: ${stats.blocked}`
|
|
12616
|
+
};
|
|
12617
|
+
}
|
|
12618
|
+
});
|
|
12619
|
+
editor.add({
|
|
12620
|
+
name: "goal.set",
|
|
12621
|
+
description: "Set a new persistent objective",
|
|
12622
|
+
input: {
|
|
12623
|
+
type: "object",
|
|
12624
|
+
properties: {
|
|
12625
|
+
description: { type: "string", description: "Goal description" },
|
|
12626
|
+
autoContinue: { type: "boolean", description: "Auto-continue (default: true)" }
|
|
12627
|
+
},
|
|
12628
|
+
required: ["description"],
|
|
12629
|
+
additionalProperties: false
|
|
12630
|
+
},
|
|
12631
|
+
execute: async (input) => {
|
|
12632
|
+
const { description, autoContinue } = input;
|
|
12633
|
+
const goal = goalManager.set(description, autoContinue ?? true);
|
|
12634
|
+
return { content: `\uD83C\uDFAF Goal set: ${goal.description}` };
|
|
12635
|
+
}
|
|
12636
|
+
});
|
|
12637
|
+
editor.add({
|
|
12638
|
+
name: "goal.status",
|
|
12639
|
+
description: "Show current goal status",
|
|
12640
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
12641
|
+
execute: async () => {
|
|
12642
|
+
const goal = goalManager.getActive();
|
|
12643
|
+
if (!goal)
|
|
12644
|
+
return { content: "No active goal. Use nexus.goal.set() to create one." };
|
|
12645
|
+
return { content: `\uD83C\uDFAF ${goal.description}
|
|
12646
|
+
Status: ${goal.status}
|
|
12647
|
+
Tasks: ${goal.tasks.length}` };
|
|
12648
|
+
}
|
|
12649
|
+
});
|
|
12650
|
+
editor.add({
|
|
12651
|
+
name: "goal.complete",
|
|
12652
|
+
description: "Complete current goal",
|
|
12653
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
12654
|
+
execute: async () => {
|
|
12655
|
+
const goal = goalManager.getActive();
|
|
12656
|
+
if (!goal)
|
|
12657
|
+
return { content: "No active goal." };
|
|
12658
|
+
goalManager.complete(goal.id);
|
|
12659
|
+
return { content: `✅ Goal completed: ${goal.description}` };
|
|
12660
|
+
}
|
|
12661
|
+
});
|
|
12662
|
+
editor.add({
|
|
12663
|
+
name: "goal.list",
|
|
12664
|
+
description: "List all goals",
|
|
12665
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
12666
|
+
execute: async () => {
|
|
12667
|
+
const goals = goalManager.getAll();
|
|
12668
|
+
if (goals.length === 0)
|
|
12669
|
+
return { content: "No goals yet." };
|
|
12670
|
+
const lines = goals.map((g) => `${g.status === "active" ? "\uD83C\uDFAF" : "✅"} ${g.description}`);
|
|
12671
|
+
return { content: lines.join(`
|
|
12672
|
+
`) };
|
|
12673
|
+
}
|
|
12674
|
+
});
|
|
12675
|
+
editor.add({
|
|
12676
|
+
name: "goal.status",
|
|
12677
|
+
description: "Show current active goal status",
|
|
12678
|
+
input: {
|
|
12679
|
+
type: "object",
|
|
12680
|
+
properties: {},
|
|
12681
|
+
additionalProperties: false
|
|
12682
|
+
},
|
|
12683
|
+
execute: async () => {
|
|
12684
|
+
const active = goalManager.getActive();
|
|
12685
|
+
if (!active)
|
|
12686
|
+
return { content: "No active goal. Use nexus.goal.set() to set one." };
|
|
12687
|
+
return {
|
|
12688
|
+
content: [
|
|
12689
|
+
`\uD83C\uDFAF Active Goal: ${active.description}`,
|
|
12690
|
+
`\uD83D\uDCCB ID: ${active.id}`,
|
|
12691
|
+
`\uD83D\uDD04 Auto-continue: ${active.autoContinue ? "enabled" : "disabled"}`,
|
|
12692
|
+
`\uD83D\uDCCA Status: ${active.status}`,
|
|
12693
|
+
`\uD83D\uDCCE Tasks: ${active.tasks.length > 0 ? active.tasks.join(", ") : "none yet"}`,
|
|
12694
|
+
`\uD83D\uDCC5 Created: ${active.createdAt.toISOString()}`,
|
|
12695
|
+
`⏱️ Should continue: ${goalManager.shouldContinue() ? "yes" : "no"}`
|
|
12696
|
+
].join(`
|
|
12697
|
+
`)
|
|
12698
|
+
};
|
|
12699
|
+
}
|
|
12700
|
+
});
|
|
12701
|
+
editor.add({
|
|
12702
|
+
name: "goal.complete",
|
|
12703
|
+
description: "Mark current active goal as completed",
|
|
12704
|
+
input: {
|
|
12705
|
+
type: "object",
|
|
12706
|
+
properties: {},
|
|
12707
|
+
additionalProperties: false
|
|
12708
|
+
},
|
|
12709
|
+
execute: async () => {
|
|
12710
|
+
const active = goalManager.getActive();
|
|
12711
|
+
if (!active)
|
|
12712
|
+
return { content: "No active goal to complete." };
|
|
12713
|
+
goalManager.complete(active.id);
|
|
12714
|
+
await ctx.storage.set("nexus-goal", JSON.parse(JSON.stringify(goalManager.getAll())));
|
|
12715
|
+
return {
|
|
12716
|
+
content: [
|
|
12717
|
+
`✅ Goal completed: ${active.description}`,
|
|
12718
|
+
`\uD83D\uDCCB ID: ${active.id}`,
|
|
12719
|
+
`\uD83D\uDCCE Tasks tracked: ${active.tasks.length}`
|
|
12720
|
+
].join(`
|
|
12721
|
+
`)
|
|
12722
|
+
};
|
|
12723
|
+
}
|
|
12724
|
+
});
|
|
12725
|
+
editor.add({
|
|
12726
|
+
name: "goal.list",
|
|
12727
|
+
description: "List all goals",
|
|
12728
|
+
input: {
|
|
12729
|
+
type: "object",
|
|
12730
|
+
properties: {},
|
|
12731
|
+
additionalProperties: false
|
|
12732
|
+
},
|
|
12733
|
+
execute: async () => {
|
|
12734
|
+
const goals = goalManager.getAll();
|
|
12735
|
+
if (goals.length === 0)
|
|
12736
|
+
return { content: "No goals set yet. Use nexus.goal.set() to create one." };
|
|
12737
|
+
const lines = goals.map((g) => {
|
|
12738
|
+
const icon = g.status === "active" ? "\uD83C\uDFAF" : g.status === "completed" ? "✅" : g.status === "paused" ? "⏸️" : "❌";
|
|
12739
|
+
return `${icon} [${g.status}] ${g.description} (tasks: ${g.tasks.length})`;
|
|
12740
|
+
});
|
|
12741
|
+
return { content: `Goals (${goals.length}):
|
|
12742
|
+
${lines.join(`
|
|
12743
|
+
`)}` };
|
|
12744
|
+
}
|
|
12745
|
+
});
|
|
12746
|
+
const teamManager = new TeamManager;
|
|
12747
|
+
const astGrep = new AstGrep;
|
|
12748
|
+
editor.add({
|
|
12749
|
+
name: "team.create",
|
|
12750
|
+
description: "Create a new team with a lead role",
|
|
12751
|
+
input: {
|
|
12752
|
+
type: "object",
|
|
12753
|
+
properties: {
|
|
12754
|
+
name: { type: "string", description: "Team name" },
|
|
12755
|
+
leadRole: { type: "string", description: "Role of the team lead" }
|
|
12756
|
+
},
|
|
12757
|
+
required: ["name", "leadRole"],
|
|
12758
|
+
additionalProperties: false
|
|
12759
|
+
},
|
|
12760
|
+
execute: async (input) => {
|
|
12761
|
+
const { name, leadRole } = input;
|
|
12762
|
+
const team = teamManager.create(name, leadRole);
|
|
12763
|
+
return { content: `Team '${team.name}' created with ID: ${team.id}
|
|
12764
|
+
Lead: ${leadRole}
|
|
12765
|
+
Status: ${team.status}
|
|
12766
|
+
|
|
12767
|
+
Add members with nexus.team.addMember(teamId="${team.id}", role="...", model="...")` };
|
|
12768
|
+
}
|
|
12769
|
+
});
|
|
12770
|
+
editor.add({
|
|
12771
|
+
name: "team.addMember",
|
|
12772
|
+
description: "Add a member to a team",
|
|
12773
|
+
input: {
|
|
12774
|
+
type: "object",
|
|
12775
|
+
properties: {
|
|
12776
|
+
teamId: { type: "string", description: "Team ID" },
|
|
12777
|
+
role: { type: "string", description: "Role for this member" },
|
|
12778
|
+
model: { type: "string", description: "Model for this member" }
|
|
12779
|
+
},
|
|
12780
|
+
required: ["teamId", "role", "model"],
|
|
12781
|
+
additionalProperties: false
|
|
12782
|
+
},
|
|
12783
|
+
execute: async (input) => {
|
|
12784
|
+
const { teamId, role, model } = input;
|
|
12785
|
+
const member = teamManager.addMember(teamId, role, model);
|
|
12786
|
+
if (!member) {
|
|
12787
|
+
return { content: `Team ${teamId} not found.` };
|
|
12788
|
+
}
|
|
12789
|
+
const team = teamManager.get(teamId);
|
|
12790
|
+
return { content: `Member added to team '${team?.name}':
|
|
12791
|
+
ID: ${member.id}
|
|
12792
|
+
Role: ${member.role}
|
|
12793
|
+
Model: ${member.model}
|
|
12794
|
+
Status: ${member.status}
|
|
12795
|
+
|
|
12796
|
+
Total members: ${team?.members.length || 0}` };
|
|
12797
|
+
}
|
|
12798
|
+
});
|
|
12799
|
+
editor.add({
|
|
12800
|
+
name: "team.status",
|
|
12801
|
+
description: "Show team status",
|
|
12802
|
+
input: {
|
|
12803
|
+
type: "object",
|
|
12804
|
+
properties: {
|
|
12805
|
+
teamId: { type: "string", description: "Team ID (optional, shows all if omitted)" }
|
|
12806
|
+
},
|
|
12807
|
+
additionalProperties: false
|
|
12808
|
+
},
|
|
12809
|
+
execute: async (input) => {
|
|
12810
|
+
const { teamId } = input;
|
|
12811
|
+
if (teamId) {
|
|
12812
|
+
const team = teamManager.get(teamId);
|
|
12813
|
+
if (!team) {
|
|
12814
|
+
return { content: `Team ${teamId} not found.` };
|
|
12815
|
+
}
|
|
12816
|
+
const memberLines = team.members.map((m) => ` - ${m.role} (${m.model}): ${m.status}`).join(`
|
|
12817
|
+
`);
|
|
12818
|
+
return { content: `Team: ${team.name} (${team.id})
|
|
12819
|
+
Lead: ${team.lead}
|
|
12820
|
+
Status: ${team.status}
|
|
12821
|
+
Created: ${team.createdAt.toISOString()}
|
|
12822
|
+
Members (${team.members.length}):
|
|
12823
|
+
${memberLines || " No members yet"}` };
|
|
12824
|
+
}
|
|
12825
|
+
const teams = teamManager.getAll();
|
|
12826
|
+
if (teams.length === 0) {
|
|
12827
|
+
return { content: "No teams created yet." };
|
|
12828
|
+
}
|
|
12829
|
+
const lines = teams.map((t) => `${t.status === "active" ? "\uD83D\uDFE2" : t.status === "completed" ? "✅" : "\uD83D\uDD35"} ${t.name} (${t.id}) - Lead: ${t.lead} - Members: ${t.members.length}`);
|
|
12830
|
+
return { content: `Teams (${teams.length}):
|
|
12831
|
+
${lines.join(`
|
|
12832
|
+
`)}` };
|
|
12833
|
+
}
|
|
12834
|
+
});
|
|
12835
|
+
editor.add({
|
|
12836
|
+
name: "team.activate",
|
|
12837
|
+
description: "Activate a team to start execution",
|
|
12838
|
+
input: {
|
|
12839
|
+
type: "object",
|
|
12840
|
+
properties: {
|
|
12841
|
+
teamId: { type: "string", description: "Team ID" }
|
|
12842
|
+
},
|
|
12843
|
+
required: ["teamId"],
|
|
12844
|
+
additionalProperties: false
|
|
12845
|
+
},
|
|
12846
|
+
execute: async (input) => {
|
|
12847
|
+
const { teamId } = input;
|
|
12848
|
+
const team = teamManager.get(teamId);
|
|
12849
|
+
if (!team) {
|
|
12850
|
+
return { content: `Team ${teamId} not found.` };
|
|
12851
|
+
}
|
|
12852
|
+
if (team.members.length === 0) {
|
|
12853
|
+
return { content: `Team '${team.name}' has no members. Add members before activating.` };
|
|
12854
|
+
}
|
|
12855
|
+
teamManager.activate(teamId);
|
|
12856
|
+
return { content: `Team '${team.name}' activated!
|
|
12857
|
+
|
|
12858
|
+
Team is now ready for parallel execution with ${team.members.length} members:
|
|
12859
|
+
${team.members.map((m) => ` - ${m.role}: ${m.model}`).join(`
|
|
12860
|
+
`)}` };
|
|
12861
|
+
}
|
|
12862
|
+
});
|
|
12863
|
+
editor.add({
|
|
12864
|
+
name: "astgrep.search",
|
|
12865
|
+
description: "Search for AST patterns in codebase",
|
|
12866
|
+
input: {
|
|
12867
|
+
type: "object",
|
|
12868
|
+
properties: {
|
|
12869
|
+
pattern: { type: "string", description: "AST pattern to search for" },
|
|
12870
|
+
language: { type: "string", description: "Programming language (typescript, python, etc.)" },
|
|
12871
|
+
directory: { type: "string", description: "Directory to search in" }
|
|
12872
|
+
},
|
|
12873
|
+
required: ["pattern", "language", "directory"],
|
|
12874
|
+
additionalProperties: false
|
|
12875
|
+
},
|
|
12876
|
+
execute: async (input) => {
|
|
12877
|
+
const { pattern, language, directory } = input;
|
|
12878
|
+
const results = astGrep.search(pattern, language, directory);
|
|
12879
|
+
if (results.length === 0)
|
|
12880
|
+
return { content: `No matches found for "${pattern}" in ${language}` };
|
|
12881
|
+
const lines = results.map((r) => `${r.file}:${r.line} — ${r.match}`);
|
|
12882
|
+
return { content: `Found ${results.length} matches:
|
|
12883
|
+
${lines.join(`
|
|
12884
|
+
`)}` };
|
|
12885
|
+
}
|
|
12886
|
+
});
|
|
12887
|
+
editor.add({
|
|
12888
|
+
name: "astgrep.status",
|
|
12889
|
+
description: "Check if ast-grep is installed",
|
|
12890
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
12891
|
+
execute: async () => {
|
|
12892
|
+
const available = astGrep.isAvailable();
|
|
12893
|
+
return { content: available ? "✅ ast-grep is installed" : "❌ ast-grep is not installed. Install with: cargo install ast-grep" };
|
|
12894
|
+
}
|
|
12895
|
+
});
|
|
11767
12896
|
});
|
|
11768
12897
|
await ctx.session.hook("prompt", (event) => {
|
|
11769
12898
|
if (event.prompt.text.startsWith("/nexus")) {
|
|
@@ -11777,9 +12906,11 @@ ${lastMsg.content}` };
|
|
|
11777
12906
|
}
|
|
11778
12907
|
});
|
|
11779
12908
|
export {
|
|
12909
|
+
AstGrep,
|
|
11780
12910
|
CostForecaster,
|
|
11781
12911
|
CustomRoleManager,
|
|
11782
12912
|
DEFAULT_CONFIG,
|
|
12913
|
+
GoalManager,
|
|
11783
12914
|
HealthMonitor,
|
|
11784
12915
|
LearningModule,
|
|
11785
12916
|
MessageRouter,
|
|
@@ -11793,6 +12924,8 @@ export {
|
|
|
11793
12924
|
SecurityScanner,
|
|
11794
12925
|
StateBroadcaster,
|
|
11795
12926
|
TEMPLATES,
|
|
12927
|
+
TeamManager,
|
|
12928
|
+
TodoEnforcer,
|
|
11796
12929
|
WorktreeManager,
|
|
11797
12930
|
src_default as default,
|
|
11798
12931
|
detectCycles,
|