@serkanalgur/opencode-nexus 1.0.7 → 1.2.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/README.md +296 -248
- package/dist/index.js +2060 -297
- package/dist/tui.js +269 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
+
|
|
1
4
|
// node_modules/@opencode/plugin/dist/promise/plugin.js
|
|
2
5
|
function define(plugin) {
|
|
3
6
|
return plugin;
|
|
@@ -3457,7 +3460,7 @@ function hasCheck(checks, id) {
|
|
|
3457
3460
|
}
|
|
3458
3461
|
var number2 = /* @__PURE__ */ new Number4;
|
|
3459
3462
|
|
|
3460
|
-
class
|
|
3463
|
+
class Boolean2 extends Base2 {
|
|
3461
3464
|
_tag = "Boolean";
|
|
3462
3465
|
getParser() {
|
|
3463
3466
|
return fromRefinement(this, isBoolean);
|
|
@@ -3466,7 +3469,7 @@ class Boolean extends Base2 {
|
|
|
3466
3469
|
return "boolean";
|
|
3467
3470
|
}
|
|
3468
3471
|
}
|
|
3469
|
-
var boolean = /* @__PURE__ */ new
|
|
3472
|
+
var boolean = /* @__PURE__ */ new Boolean2;
|
|
3470
3473
|
class Arrays extends Base2 {
|
|
3471
3474
|
_tag = "Arrays";
|
|
3472
3475
|
isMutable;
|
|
@@ -5372,7 +5375,7 @@ var Null2 = /* @__PURE__ */ make9(null_);
|
|
|
5372
5375
|
var Undefined2 = /* @__PURE__ */ make9(undefined_2);
|
|
5373
5376
|
var String5 = /* @__PURE__ */ make9(string2);
|
|
5374
5377
|
var Number5 = /* @__PURE__ */ make9(number2);
|
|
5375
|
-
var
|
|
5378
|
+
var Boolean3 = /* @__PURE__ */ make9(boolean);
|
|
5376
5379
|
function makeStruct(ast, fields) {
|
|
5377
5380
|
return make9(ast, {
|
|
5378
5381
|
fields,
|
|
@@ -6406,14 +6409,14 @@ var Option = Struct({
|
|
|
6406
6409
|
var When = Struct({
|
|
6407
6410
|
key: String5,
|
|
6408
6411
|
op: Literals(["eq", "neq"]),
|
|
6409
|
-
value: Union2([String5, Number5,
|
|
6412
|
+
value: Union2([String5, Number5, Boolean3])
|
|
6410
6413
|
}).annotate({ identifier: "Form.When" });
|
|
6411
6414
|
var FieldBase = {
|
|
6412
6415
|
key: String5,
|
|
6413
6416
|
title: String5.pipe(optional3),
|
|
6414
6417
|
description: String5.pipe(optional3),
|
|
6415
|
-
required:
|
|
6416
|
-
hidden:
|
|
6418
|
+
required: Boolean3.pipe(optional3),
|
|
6419
|
+
hidden: Boolean3.pipe(optional3).annotate({
|
|
6417
6420
|
description: "Skip the interactive authentication prompt and use the default unless an answer is supplied"
|
|
6418
6421
|
}),
|
|
6419
6422
|
when: ArraySchema(When).pipe(optional3)
|
|
@@ -6428,7 +6431,7 @@ var StringField = Struct({
|
|
|
6428
6431
|
placeholder: String5.pipe(optional3),
|
|
6429
6432
|
default: String5.pipe(optional3),
|
|
6430
6433
|
options: ArraySchema(Option).pipe(optional3),
|
|
6431
|
-
custom:
|
|
6434
|
+
custom: Boolean3.pipe(optional3)
|
|
6432
6435
|
}).annotate({ identifier: "Form.StringField" });
|
|
6433
6436
|
var NumberField = Struct({
|
|
6434
6437
|
...FieldBase,
|
|
@@ -6447,7 +6450,7 @@ var IntegerField = Struct({
|
|
|
6447
6450
|
var BooleanField = Struct({
|
|
6448
6451
|
...FieldBase,
|
|
6449
6452
|
type: Literal2("boolean"),
|
|
6450
|
-
default:
|
|
6453
|
+
default: Boolean3.pipe(optional3)
|
|
6451
6454
|
}).annotate({ identifier: "Form.BooleanField" });
|
|
6452
6455
|
var MultiselectField = Struct({
|
|
6453
6456
|
...FieldBase,
|
|
@@ -6455,7 +6458,7 @@ var MultiselectField = Struct({
|
|
|
6455
6458
|
options: ArraySchema(Option),
|
|
6456
6459
|
minItems: NonNegativeInt.pipe(optional3),
|
|
6457
6460
|
maxItems: NonNegativeInt.pipe(optional3),
|
|
6458
|
-
custom:
|
|
6461
|
+
custom: Boolean3.pipe(optional3),
|
|
6459
6462
|
default: ArraySchema(String5).pipe(optional3)
|
|
6460
6463
|
}).annotate({ identifier: "Form.MultiselectField" });
|
|
6461
6464
|
var ExternalField = Struct({
|
|
@@ -6484,7 +6487,7 @@ var Info2 = Struct({
|
|
|
6484
6487
|
...InfoBase,
|
|
6485
6488
|
fields: Fields
|
|
6486
6489
|
}).annotate({ identifier: "Form.Info" });
|
|
6487
|
-
var Value = Union2([String5, Number5,
|
|
6490
|
+
var Value = Union2([String5, Number5, Boolean3, ArraySchema(String5)]).annotate({
|
|
6488
6491
|
identifier: "Form.Value"
|
|
6489
6492
|
});
|
|
6490
6493
|
var Answer = Record(String5, Value).annotate({ identifier: "Form.Answer" });
|
|
@@ -6720,14 +6723,14 @@ var Overlays2 = {
|
|
|
6720
6723
|
};
|
|
6721
6724
|
var Compatibility = Struct({
|
|
6722
6725
|
reasoningField: ReasoningField.pipe(optional3),
|
|
6723
|
-
requireReasoning:
|
|
6726
|
+
requireReasoning: Boolean3.pipe(optional3),
|
|
6724
6727
|
maxTokensField: MaxTokensField.pipe(optional3),
|
|
6725
|
-
requireFinishReason:
|
|
6726
|
-
requireAssistantAfterTool:
|
|
6727
|
-
supportsPromptCacheKey:
|
|
6728
|
+
requireFinishReason: Boolean3.pipe(optional3),
|
|
6729
|
+
requireAssistantAfterTool: Boolean3.pipe(optional3),
|
|
6730
|
+
supportsPromptCacheKey: Boolean3.pipe(optional3)
|
|
6728
6731
|
}).annotate({ identifier: "Model.Compatibility" });
|
|
6729
6732
|
var Capabilities = Struct({
|
|
6730
|
-
tools:
|
|
6733
|
+
tools: Boolean3,
|
|
6731
6734
|
input: ArraySchema(String5),
|
|
6732
6735
|
output: ArraySchema(String5)
|
|
6733
6736
|
}).annotate({ identifier: "Model.Capabilities" }).pipe(statics(() => ({
|
|
@@ -6766,7 +6769,7 @@ var Info6 = Struct({
|
|
|
6766
6769
|
}),
|
|
6767
6770
|
cost: ArraySchema(Cost),
|
|
6768
6771
|
status: Literals(["alpha", "beta", "deprecated", "active"]),
|
|
6769
|
-
enabled:
|
|
6772
|
+
enabled: Boolean3,
|
|
6770
6773
|
limit: Struct({
|
|
6771
6774
|
context: Int,
|
|
6772
6775
|
input: Int.pipe(optional3),
|
|
@@ -6850,7 +6853,7 @@ var Info7 = Struct({
|
|
|
6850
6853
|
system: String5.pipe(optional3),
|
|
6851
6854
|
description: String5.pipe(optional3),
|
|
6852
6855
|
mode: Literals(["subagent", "primary", "all"]),
|
|
6853
|
-
hidden:
|
|
6856
|
+
hidden: Boolean3,
|
|
6854
6857
|
color: Color.pipe(optional3),
|
|
6855
6858
|
steps: PositiveInt.pipe(optional3),
|
|
6856
6859
|
permissions: Ruleset
|
|
@@ -6909,8 +6912,8 @@ class LocalConfig extends Class3("Mcp.LocalConfig")({
|
|
|
6909
6912
|
description: "Working directory for the MCP server process. Relative paths resolve from the workspace directory."
|
|
6910
6913
|
}),
|
|
6911
6914
|
environment: Record(String5, String5).pipe(optional3),
|
|
6912
|
-
disabled:
|
|
6913
|
-
codemode:
|
|
6915
|
+
disabled: Boolean3.pipe(optional3),
|
|
6916
|
+
codemode: Boolean3.pipe(optional3).annotate({
|
|
6914
6917
|
description: "Expose this server's tools through Code Mode. Defaults to true."
|
|
6915
6918
|
}),
|
|
6916
6919
|
timeout: TimeoutConfig.pipe(optional3),
|
|
@@ -6935,8 +6938,8 @@ class RemoteConfig extends Class3("Mcp.RemoteConfig")({
|
|
|
6935
6938
|
url: String5,
|
|
6936
6939
|
headers: Record(String5, String5).pipe(optional3),
|
|
6937
6940
|
oauth: Union2([OAuthConfig, Literal2(false)]).pipe(optional3),
|
|
6938
|
-
disabled:
|
|
6939
|
-
codemode:
|
|
6941
|
+
disabled: Boolean3.pipe(optional3),
|
|
6942
|
+
codemode: Boolean3.pipe(optional3).annotate({
|
|
6940
6943
|
description: "Expose this server's tools through Code Mode. Defaults to true."
|
|
6941
6944
|
}),
|
|
6942
6945
|
timeout: TimeoutConfig.pipe(optional3),
|
|
@@ -7118,7 +7121,7 @@ var Info11 = Struct({
|
|
|
7118
7121
|
id: ID11,
|
|
7119
7122
|
name: Name2,
|
|
7120
7123
|
description: String5.pipe(optional3),
|
|
7121
|
-
autoinvoke:
|
|
7124
|
+
autoinvoke: Boolean3.pipe(optional3),
|
|
7122
7125
|
path: AbsolutePath,
|
|
7123
7126
|
content: String5
|
|
7124
7127
|
}).annotate({ identifier: "Skill.Info" });
|
|
@@ -7261,7 +7264,7 @@ var Output = Struct({
|
|
|
7261
7264
|
output: String5,
|
|
7262
7265
|
cursor: NonNegativeInt,
|
|
7263
7266
|
size: NonNegativeInt,
|
|
7264
|
-
truncated:
|
|
7267
|
+
truncated: Boolean3
|
|
7265
7268
|
});
|
|
7266
7269
|
// node_modules/@opencode/schema/dist/session-error.js
|
|
7267
7270
|
var Error5 = Struct({
|
|
@@ -7386,7 +7389,7 @@ var AssistantTool = Struct({
|
|
|
7386
7389
|
type: tag("tool"),
|
|
7387
7390
|
id: String5,
|
|
7388
7391
|
name: String5,
|
|
7389
|
-
executed:
|
|
7392
|
+
executed: Boolean3.pipe(optional3),
|
|
7390
7393
|
providerState: ProviderState.pipe(optional3),
|
|
7391
7394
|
providerResultState: ProviderState.pipe(optional3),
|
|
7392
7395
|
state: ToolState,
|
|
@@ -7583,7 +7586,7 @@ var Info17 = Struct({
|
|
|
7583
7586
|
}).annotate({ identifier: "InstructionEntry.Info" });
|
|
7584
7587
|
var Snapshot = ArraySchema(Struct({
|
|
7585
7588
|
...Info17.fields,
|
|
7586
|
-
removed:
|
|
7589
|
+
removed: Boolean3
|
|
7587
7590
|
})).annotate({ identifier: "InstructionEntry.Snapshot" });
|
|
7588
7591
|
var MaxValueBytes = 256 * 1024;
|
|
7589
7592
|
|
|
@@ -8028,7 +8031,7 @@ var Tool;
|
|
|
8028
8031
|
schema: {
|
|
8029
8032
|
...ToolBase,
|
|
8030
8033
|
input: Record(String5, Unknown2),
|
|
8031
|
-
executed:
|
|
8034
|
+
executed: Boolean3,
|
|
8032
8035
|
state: ProviderState.pipe(optional3)
|
|
8033
8036
|
}
|
|
8034
8037
|
});
|
|
@@ -8049,7 +8052,7 @@ var Tool;
|
|
|
8049
8052
|
...ToolBase,
|
|
8050
8053
|
content: NonEmptyArray(Content),
|
|
8051
8054
|
metadata: Record(String5, Json2).pipe(optional3),
|
|
8052
|
-
executed:
|
|
8055
|
+
executed: Boolean3,
|
|
8053
8056
|
resultState: ProviderState.pipe(optional3)
|
|
8054
8057
|
}
|
|
8055
8058
|
});
|
|
@@ -8064,7 +8067,7 @@ var Tool;
|
|
|
8064
8067
|
error: Error5,
|
|
8065
8068
|
content: NonEmptyArray(Content).pipe(optional3),
|
|
8066
8069
|
metadata: Record(String5, Json2).pipe(optional3),
|
|
8067
|
-
executed:
|
|
8070
|
+
executed: Boolean3,
|
|
8068
8071
|
resultState: ProviderState.pipe(optional3)
|
|
8069
8072
|
}
|
|
8070
8073
|
});
|
|
@@ -8241,21 +8244,21 @@ var LocalSource = Struct({
|
|
|
8241
8244
|
type: Literal2("local"),
|
|
8242
8245
|
path: AbsolutePath,
|
|
8243
8246
|
description: String5.pipe(optional3),
|
|
8244
|
-
hidden:
|
|
8247
|
+
hidden: Boolean3.pipe(optional3)
|
|
8245
8248
|
}).annotate({ identifier: "Reference.LocalSource" });
|
|
8246
8249
|
var GitSource = Struct({
|
|
8247
8250
|
type: Literal2("git"),
|
|
8248
8251
|
repository: String5,
|
|
8249
8252
|
branch: String5.pipe(optional3),
|
|
8250
8253
|
description: String5.pipe(optional3),
|
|
8251
|
-
hidden:
|
|
8254
|
+
hidden: Boolean3.pipe(optional3)
|
|
8252
8255
|
}).annotate({ identifier: "Reference.GitSource" });
|
|
8253
8256
|
var Source4 = Union2([LocalSource, GitSource]).pipe(toTaggedUnion("type")).annotate({ identifier: "Reference.Source" });
|
|
8254
8257
|
var Info21 = Struct({
|
|
8255
8258
|
name: String5,
|
|
8256
8259
|
path: AbsolutePath,
|
|
8257
8260
|
description: String5.pipe(optional3),
|
|
8258
|
-
hidden:
|
|
8261
|
+
hidden: Boolean3.pipe(optional3),
|
|
8259
8262
|
source: Source4
|
|
8260
8263
|
}).annotate({ identifier: "Reference.Info" });
|
|
8261
8264
|
// node_modules/@opencode/schema/dist/vcs.js
|
|
@@ -8301,7 +8304,7 @@ var Result2 = Struct({
|
|
|
8301
8304
|
})
|
|
8302
8305
|
}).annotate({ identifier: "WebSearch.Result" });
|
|
8303
8306
|
|
|
8304
|
-
class
|
|
8307
|
+
class Response2 extends Class3("WebSearch.Response")({
|
|
8305
8308
|
providerID: ID16,
|
|
8306
8309
|
results: ArraySchema(Result2)
|
|
8307
8310
|
}) {
|
|
@@ -8325,7 +8328,7 @@ var CreateInput4 = Struct({
|
|
|
8325
8328
|
var RemoveInput = Struct({
|
|
8326
8329
|
projectID: ID10,
|
|
8327
8330
|
directory: AbsolutePath,
|
|
8328
|
-
force:
|
|
8331
|
+
force: Boolean3
|
|
8329
8332
|
}).annotate({ identifier: "Worktree.RemoveInput" });
|
|
8330
8333
|
var Info23 = Struct({
|
|
8331
8334
|
directory: AbsolutePath
|
|
@@ -8341,7 +8344,7 @@ var ListEntry = Struct({
|
|
|
8341
8344
|
|
|
8342
8345
|
class OperationError extends TaggedError3()("Worktree.OperationError", {
|
|
8343
8346
|
message: String5,
|
|
8344
|
-
forceRequired: optional3(
|
|
8347
|
+
forceRequired: optional3(Boolean3)
|
|
8345
8348
|
}) {
|
|
8346
8349
|
}
|
|
8347
8350
|
var List = ArraySchema(Directory).annotate({ identifier: "Worktree.List" });
|
|
@@ -8361,6 +8364,64 @@ var Resolved = durable({
|
|
|
8361
8364
|
});
|
|
8362
8365
|
var Event16 = { Updated: Updated12, Resolved, Definitions: inventory(Updated12, Resolved) };
|
|
8363
8366
|
// src/config.ts
|
|
8367
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
8368
|
+
import { homedir } from "node:os";
|
|
8369
|
+
import { join, dirname } from "node:path";
|
|
8370
|
+
function stripJsonComments(jsonc) {
|
|
8371
|
+
const result = [];
|
|
8372
|
+
let i = 0;
|
|
8373
|
+
const len = jsonc.length;
|
|
8374
|
+
while (i < len) {
|
|
8375
|
+
const ch = jsonc[i];
|
|
8376
|
+
if (ch === '"') {
|
|
8377
|
+
result.push(ch);
|
|
8378
|
+
i++;
|
|
8379
|
+
while (i < len && jsonc[i] !== '"') {
|
|
8380
|
+
if (jsonc[i] === "\\") {
|
|
8381
|
+
result.push(jsonc[i], jsonc[i + 1] ?? "");
|
|
8382
|
+
i += 2;
|
|
8383
|
+
} else {
|
|
8384
|
+
result.push(jsonc[i]);
|
|
8385
|
+
i++;
|
|
8386
|
+
}
|
|
8387
|
+
}
|
|
8388
|
+
if (i < len) {
|
|
8389
|
+
result.push(jsonc[i]);
|
|
8390
|
+
i++;
|
|
8391
|
+
}
|
|
8392
|
+
continue;
|
|
8393
|
+
}
|
|
8394
|
+
if (ch === "/" && jsonc[i + 1] === "/") {
|
|
8395
|
+
while (i < len && jsonc[i] !== `
|
|
8396
|
+
`)
|
|
8397
|
+
i++;
|
|
8398
|
+
continue;
|
|
8399
|
+
}
|
|
8400
|
+
if (ch === "/" && jsonc[i + 1] === "*") {
|
|
8401
|
+
i += 2;
|
|
8402
|
+
while (i < len && !(jsonc[i] === "*" && jsonc[i + 1] === "/"))
|
|
8403
|
+
i++;
|
|
8404
|
+
i += 2;
|
|
8405
|
+
continue;
|
|
8406
|
+
}
|
|
8407
|
+
result.push(ch);
|
|
8408
|
+
i++;
|
|
8409
|
+
}
|
|
8410
|
+
return result.join("");
|
|
8411
|
+
}
|
|
8412
|
+
function readJsoncFile(filePath) {
|
|
8413
|
+
try {
|
|
8414
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
8415
|
+
const stripped = stripJsonComments(raw);
|
|
8416
|
+
const parsed = JSON.parse(stripped);
|
|
8417
|
+
return parsed;
|
|
8418
|
+
} catch (err) {
|
|
8419
|
+
if (err.code !== "ENOENT") {
|
|
8420
|
+
console.warn(`[nexus] Failed to load config from ${filePath}: ${err.message}`);
|
|
8421
|
+
}
|
|
8422
|
+
return null;
|
|
8423
|
+
}
|
|
8424
|
+
}
|
|
8364
8425
|
var DEFAULT_CONFIG = {
|
|
8365
8426
|
models: {
|
|
8366
8427
|
architect: "anthropic/claude-sonnet-4-6",
|
|
@@ -8388,12 +8449,18 @@ class NexusConfigManager {
|
|
|
8388
8449
|
globalConfig = null;
|
|
8389
8450
|
storageConfig = null;
|
|
8390
8451
|
constructor() {
|
|
8391
|
-
this.loadConfigs();
|
|
8392
|
-
}
|
|
8393
|
-
loadConfigs() {
|
|
8394
8452
|
this.projectConfig = null;
|
|
8395
8453
|
this.globalConfig = null;
|
|
8396
8454
|
}
|
|
8455
|
+
loadConfigs(basePath) {
|
|
8456
|
+
const projectPath = join(basePath, ".opencode", "nexus.jsonc");
|
|
8457
|
+
this.projectConfig = readJsoncFile(projectPath);
|
|
8458
|
+
const globalPath = join(homedir(), ".config", "opencode", "nexus.jsonc");
|
|
8459
|
+
this.globalConfig = readJsoncFile(globalPath);
|
|
8460
|
+
}
|
|
8461
|
+
loadFromPath(basePath) {
|
|
8462
|
+
this.loadConfigs(basePath);
|
|
8463
|
+
}
|
|
8397
8464
|
getConfig() {
|
|
8398
8465
|
return {
|
|
8399
8466
|
models: {
|
|
@@ -8451,24 +8518,108 @@ class NexusConfigManager {
|
|
|
8451
8518
|
}
|
|
8452
8519
|
getRoleDisplayName(role) {
|
|
8453
8520
|
const names = {
|
|
8454
|
-
architect: "
|
|
8455
|
-
coder: "
|
|
8456
|
-
reviewer: "
|
|
8457
|
-
tester: "
|
|
8458
|
-
explorer: "
|
|
8459
|
-
documenter: "
|
|
8521
|
+
architect: "Architect",
|
|
8522
|
+
coder: "Coder",
|
|
8523
|
+
reviewer: "Reviewer",
|
|
8524
|
+
tester: "Tester",
|
|
8525
|
+
explorer: "Explorer",
|
|
8526
|
+
documenter: "Documenter"
|
|
8460
8527
|
};
|
|
8461
8528
|
return names[role] || role;
|
|
8462
8529
|
}
|
|
8530
|
+
getRoleEmoji(role) {
|
|
8531
|
+
const emojis = {
|
|
8532
|
+
architect: "\uD83C\uDFD7️",
|
|
8533
|
+
coder: "\uD83D\uDCBB",
|
|
8534
|
+
reviewer: "\uD83D\uDD0D",
|
|
8535
|
+
tester: "\uD83E\uDDEA",
|
|
8536
|
+
explorer: "\uD83D\uDD2C",
|
|
8537
|
+
documenter: "\uD83D\uDCDD"
|
|
8538
|
+
};
|
|
8539
|
+
return emojis[role] || "\uD83E\uDD16";
|
|
8540
|
+
}
|
|
8463
8541
|
exportConfig() {
|
|
8464
8542
|
return this.getConfig();
|
|
8465
8543
|
}
|
|
8466
8544
|
importConfig(config) {
|
|
8467
8545
|
this.storageConfig = { ...config };
|
|
8468
8546
|
}
|
|
8547
|
+
writeJsoncFile(filePath, config) {
|
|
8548
|
+
const dir = dirname(filePath);
|
|
8549
|
+
mkdirSync(dir, { recursive: true });
|
|
8550
|
+
const jsonc = [
|
|
8551
|
+
"// Nexus Configuration — https://github.com/serkanalgur/opencode-nexus",
|
|
8552
|
+
"// Precedence: this file > global config > TUI settings > defaults",
|
|
8553
|
+
"",
|
|
8554
|
+
JSON.stringify(config, null, 2)
|
|
8555
|
+
].join(`
|
|
8556
|
+
`);
|
|
8557
|
+
writeFileSync(filePath, jsonc + `
|
|
8558
|
+
`, "utf-8");
|
|
8559
|
+
}
|
|
8560
|
+
saveProjectConfig(basePath) {
|
|
8561
|
+
const projectPath = join(basePath, ".opencode", "nexus.jsonc");
|
|
8562
|
+
const config = this.getNonDefaultConfig();
|
|
8563
|
+
this.writeJsoncFile(projectPath, config);
|
|
8564
|
+
}
|
|
8565
|
+
saveGlobalConfig() {
|
|
8566
|
+
const globalPath = join(homedir(), ".config", "opencode", "nexus.jsonc");
|
|
8567
|
+
const config = this.getNonDefaultConfig();
|
|
8568
|
+
this.writeJsoncFile(globalPath, config);
|
|
8569
|
+
}
|
|
8570
|
+
saveConfig(level, basePath) {
|
|
8571
|
+
if (level === "project") {
|
|
8572
|
+
this.saveProjectConfig(basePath || process.cwd());
|
|
8573
|
+
} else {
|
|
8574
|
+
this.saveGlobalConfig();
|
|
8575
|
+
}
|
|
8576
|
+
}
|
|
8577
|
+
getNonDefaultConfig() {
|
|
8578
|
+
const current = this.getConfig();
|
|
8579
|
+
const result = {};
|
|
8580
|
+
const models = {};
|
|
8581
|
+
for (const role of this.getRoles()) {
|
|
8582
|
+
if (current.models[role] !== DEFAULT_CONFIG.models[role]) {
|
|
8583
|
+
models[role] = current.models[role];
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
8586
|
+
if (Object.keys(models).length > 0) {
|
|
8587
|
+
result.models = models;
|
|
8588
|
+
}
|
|
8589
|
+
const budget = {};
|
|
8590
|
+
const budgetKeys = ["maxTotalCost", "maxCostPerTask", "maxCostPerAgent", "alertThreshold"];
|
|
8591
|
+
for (const key of budgetKeys) {
|
|
8592
|
+
if (current.budget[key] !== DEFAULT_CONFIG.budget[key]) {
|
|
8593
|
+
budget[key] = current.budget[key];
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
if (Object.keys(budget).length > 0) {
|
|
8597
|
+
result.budget = budget;
|
|
8598
|
+
}
|
|
8599
|
+
const selfHealing = {};
|
|
8600
|
+
const shKeys = ["enabled", "maxRetries", "contextTransfer"];
|
|
8601
|
+
for (const key of shKeys) {
|
|
8602
|
+
if (current.selfHealing[key] !== DEFAULT_CONFIG.selfHealing[key]) {
|
|
8603
|
+
selfHealing[key] = current.selfHealing[key];
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
if (Object.keys(selfHealing).length > 0) {
|
|
8607
|
+
result.selfHealing = selfHealing;
|
|
8608
|
+
}
|
|
8609
|
+
return result;
|
|
8610
|
+
}
|
|
8469
8611
|
resetToDefaults() {
|
|
8470
8612
|
this.storageConfig = null;
|
|
8471
8613
|
}
|
|
8614
|
+
applyPreset(name) {
|
|
8615
|
+
const preset = PRESETS[name];
|
|
8616
|
+
if (!preset)
|
|
8617
|
+
throw new Error(`Unknown preset: ${name}. Available: ${Object.keys(PRESETS).join(", ")}`);
|
|
8618
|
+
this.storageConfig = { ...this.storageConfig, ...preset.config };
|
|
8619
|
+
}
|
|
8620
|
+
listPresets() {
|
|
8621
|
+
return Object.keys(PRESETS);
|
|
8622
|
+
}
|
|
8472
8623
|
getSummary() {
|
|
8473
8624
|
const config = this.getConfig();
|
|
8474
8625
|
const lines = [
|
|
@@ -8496,210 +8647,1369 @@ class NexusConfigManager {
|
|
|
8496
8647
|
`);
|
|
8497
8648
|
}
|
|
8498
8649
|
}
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
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
|
-
}
|
|
8564
|
-
mergeConfig(partial) {
|
|
8565
|
-
const defaults = {
|
|
8566
|
-
maxConcurrency: 5,
|
|
8567
|
-
schedulerInterval: 1000,
|
|
8568
|
-
defaultTimeout: 300000,
|
|
8650
|
+
var PRESETS = {
|
|
8651
|
+
minimal: {
|
|
8652
|
+
name: "Minimal",
|
|
8653
|
+
description: "Low-cost setup with free/cheap models, minimal budget",
|
|
8654
|
+
config: {
|
|
8655
|
+
models: {
|
|
8656
|
+
architect: "google/gemini-2.5-flash",
|
|
8657
|
+
coder: "google/gemini-2.5-flash",
|
|
8658
|
+
reviewer: "google/gemini-2.5-flash",
|
|
8659
|
+
tester: "google/gemini-2.5-flash",
|
|
8660
|
+
explorer: "google/gemini-2.5-flash",
|
|
8661
|
+
documenter: "google/gemini-2.5-flash"
|
|
8662
|
+
},
|
|
8663
|
+
budget: {
|
|
8664
|
+
maxTotalCost: 1,
|
|
8665
|
+
maxCostPerTask: 0.1,
|
|
8666
|
+
maxCostPerAgent: 0.5,
|
|
8667
|
+
alertThreshold: 0.5
|
|
8668
|
+
},
|
|
8669
|
+
selfHealing: {
|
|
8670
|
+
enabled: false,
|
|
8671
|
+
maxRetries: 1,
|
|
8672
|
+
contextTransfer: false
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
},
|
|
8676
|
+
balanced: {
|
|
8677
|
+
name: "Balanced",
|
|
8678
|
+
description: "Good quality models with moderate budget",
|
|
8679
|
+
config: {
|
|
8680
|
+
models: {
|
|
8681
|
+
architect: "anthropic/claude-sonnet-4-6",
|
|
8682
|
+
coder: "anthropic/claude-sonnet-4-6",
|
|
8683
|
+
reviewer: "openai/gpt-5-mini",
|
|
8684
|
+
tester: "anthropic/claude-haiku-4-5",
|
|
8685
|
+
explorer: "google/gemini-2.5-flash",
|
|
8686
|
+
documenter: "anthropic/claude-haiku-4-5"
|
|
8687
|
+
},
|
|
8569
8688
|
budget: {
|
|
8570
8689
|
maxTotalCost: 10,
|
|
8571
8690
|
maxCostPerTask: 1,
|
|
8572
8691
|
maxCostPerAgent: 2,
|
|
8573
|
-
alertThreshold: 0.2
|
|
8574
|
-
hardLimit: false
|
|
8575
|
-
},
|
|
8576
|
-
agents: {
|
|
8577
|
-
defaultRole: "coder",
|
|
8578
|
-
spawnDelay: 100,
|
|
8579
|
-
healthCheckInterval: 30000
|
|
8692
|
+
alertThreshold: 0.2
|
|
8580
8693
|
},
|
|
8581
8694
|
selfHealing: {
|
|
8582
8695
|
enabled: true,
|
|
8583
8696
|
maxRetries: 3,
|
|
8584
|
-
retryDelay: 1000,
|
|
8585
|
-
backoffMultiplier: 2,
|
|
8586
8697
|
contextTransfer: true
|
|
8698
|
+
}
|
|
8699
|
+
}
|
|
8700
|
+
},
|
|
8701
|
+
enterprise: {
|
|
8702
|
+
name: "Enterprise",
|
|
8703
|
+
description: "High-quality models with generous budget and full self-healing",
|
|
8704
|
+
config: {
|
|
8705
|
+
models: {
|
|
8706
|
+
architect: "anthropic/claude-sonnet-4-6",
|
|
8707
|
+
coder: "anthropic/claude-sonnet-4-6",
|
|
8708
|
+
reviewer: "openai/gpt-5",
|
|
8709
|
+
tester: "anthropic/claude-sonnet-4-6",
|
|
8710
|
+
explorer: "anthropic/claude-sonnet-4-6",
|
|
8711
|
+
documenter: "anthropic/claude-haiku-4-5"
|
|
8587
8712
|
},
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
},
|
|
8594
|
-
memory: {
|
|
8595
|
-
enabled: true,
|
|
8596
|
-
storage: "memory",
|
|
8597
|
-
maxEntriesPerScope: 1000,
|
|
8598
|
-
syncInterval: 5000
|
|
8713
|
+
budget: {
|
|
8714
|
+
maxTotalCost: 50,
|
|
8715
|
+
maxCostPerTask: 5,
|
|
8716
|
+
maxCostPerAgent: 10,
|
|
8717
|
+
alertThreshold: 0.1
|
|
8599
8718
|
},
|
|
8600
|
-
|
|
8719
|
+
selfHealing: {
|
|
8601
8720
|
enabled: true,
|
|
8602
|
-
|
|
8603
|
-
|
|
8721
|
+
maxRetries: 5,
|
|
8722
|
+
contextTransfer: true
|
|
8723
|
+
}
|
|
8724
|
+
}
|
|
8725
|
+
},
|
|
8726
|
+
"cost-optimized": {
|
|
8727
|
+
name: "Cost-Optimized",
|
|
8728
|
+
description: "Minimize costs while maintaining reasonable quality",
|
|
8729
|
+
config: {
|
|
8730
|
+
models: {
|
|
8731
|
+
architect: "anthropic/claude-haiku-4-5",
|
|
8732
|
+
coder: "google/gemini-2.5-flash",
|
|
8733
|
+
reviewer: "openai/gpt-5-mini",
|
|
8734
|
+
tester: "google/gemini-2.5-flash",
|
|
8735
|
+
explorer: "google/gemini-2.5-flash",
|
|
8736
|
+
documenter: "google/gemini-2.5-flash"
|
|
8604
8737
|
},
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8738
|
+
budget: {
|
|
8739
|
+
maxTotalCost: 3,
|
|
8740
|
+
maxCostPerTask: 0.3,
|
|
8741
|
+
maxCostPerAgent: 1,
|
|
8742
|
+
alertThreshold: 0.3
|
|
8609
8743
|
},
|
|
8610
|
-
|
|
8744
|
+
selfHealing: {
|
|
8611
8745
|
enabled: true,
|
|
8612
|
-
|
|
8613
|
-
|
|
8746
|
+
maxRetries: 2,
|
|
8747
|
+
contextTransfer: false
|
|
8614
8748
|
}
|
|
8615
|
-
}
|
|
8616
|
-
return {
|
|
8617
|
-
...defaults,
|
|
8618
|
-
...partial,
|
|
8619
|
-
budget: { ...defaults.budget, ...partial?.budget },
|
|
8620
|
-
agents: { ...defaults.agents, ...partial?.agents },
|
|
8621
|
-
selfHealing: { ...defaults.selfHealing, ...partial?.selfHealing },
|
|
8622
|
-
communication: { ...defaults.communication, ...partial?.communication },
|
|
8623
|
-
memory: { ...defaults.memory, ...partial?.memory },
|
|
8624
|
-
dashboard: { ...defaults.dashboard, ...partial?.dashboard },
|
|
8625
|
-
security: { ...defaults.security, ...partial?.security },
|
|
8626
|
-
learning: { ...defaults.learning, ...partial?.learning }
|
|
8627
|
-
};
|
|
8749
|
+
}
|
|
8628
8750
|
}
|
|
8629
|
-
|
|
8630
|
-
|
|
8751
|
+
};
|
|
8752
|
+
|
|
8753
|
+
// src/broadcast.ts
|
|
8754
|
+
class StateBroadcaster {
|
|
8755
|
+
clients = new Set;
|
|
8756
|
+
orchestrator;
|
|
8757
|
+
broadcastTimer = null;
|
|
8758
|
+
throttleMs;
|
|
8759
|
+
unsubscribers = [];
|
|
8760
|
+
constructor(orchestrator, opts) {
|
|
8761
|
+
this.orchestrator = orchestrator;
|
|
8762
|
+
this.throttleMs = opts?.throttleMs ?? 1000;
|
|
8763
|
+
this.setupEventListeners();
|
|
8764
|
+
}
|
|
8765
|
+
addClient(ws) {
|
|
8766
|
+
this.clients.add(ws);
|
|
8767
|
+
this.sendTo(ws, {
|
|
8768
|
+
type: "orchestrator:state",
|
|
8769
|
+
data: this.orchestrator.getState(),
|
|
8770
|
+
timestamp: new Date().toISOString()
|
|
8771
|
+
});
|
|
8631
8772
|
}
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8773
|
+
removeClient(ws) {
|
|
8774
|
+
this.clients.delete(ws);
|
|
8775
|
+
}
|
|
8776
|
+
setupEventListeners() {
|
|
8777
|
+
const orch = this.orchestrator;
|
|
8778
|
+
const eventNames = [
|
|
8779
|
+
"agent:spawned",
|
|
8780
|
+
"agent:terminated",
|
|
8781
|
+
"budget:alert",
|
|
8782
|
+
"budget:exceeded"
|
|
8783
|
+
];
|
|
8784
|
+
for (const eventName of eventNames) {
|
|
8785
|
+
const unsub = orch.on?.(eventName, (data) => {
|
|
8786
|
+
this.broadcast(eventName, data);
|
|
8787
|
+
});
|
|
8788
|
+
if (unsub)
|
|
8789
|
+
this.unsubscribers.push(unsub);
|
|
8635
8790
|
}
|
|
8636
|
-
|
|
8637
|
-
|
|
8791
|
+
}
|
|
8792
|
+
broadcastState() {
|
|
8793
|
+
if (this.broadcastTimer)
|
|
8794
|
+
return;
|
|
8795
|
+
this.broadcastTimer = setTimeout(() => {
|
|
8796
|
+
this.broadcastTimer = null;
|
|
8797
|
+
this.broadcast("orchestrator:state", this.orchestrator.getState());
|
|
8798
|
+
}, this.throttleMs);
|
|
8799
|
+
}
|
|
8800
|
+
broadcast(event, data) {
|
|
8801
|
+
const message = JSON.stringify({
|
|
8802
|
+
type: event,
|
|
8803
|
+
data,
|
|
8804
|
+
timestamp: new Date().toISOString()
|
|
8805
|
+
});
|
|
8806
|
+
for (const client of this.clients) {
|
|
8807
|
+
try {
|
|
8808
|
+
client.send(message);
|
|
8809
|
+
} catch {
|
|
8810
|
+
this.clients.delete(client);
|
|
8811
|
+
}
|
|
8638
8812
|
}
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
this.notifyStateChange();
|
|
8813
|
+
}
|
|
8814
|
+
sendTo(ws, data) {
|
|
8642
8815
|
try {
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
}
|
|
8647
|
-
await this.executeDAG();
|
|
8648
|
-
const results = this.collectResults();
|
|
8649
|
-
const totalDuration = Date.now() - startTime;
|
|
8650
|
-
return {
|
|
8651
|
-
success: true,
|
|
8652
|
-
tasks: results,
|
|
8653
|
-
totalCost: this.totalSpent,
|
|
8654
|
-
totalDuration,
|
|
8655
|
-
agentsUsed: this.agents.size
|
|
8656
|
-
};
|
|
8657
|
-
} catch (error) {
|
|
8658
|
-
return {
|
|
8659
|
-
success: false,
|
|
8660
|
-
tasks: [],
|
|
8661
|
-
totalCost: this.totalSpent,
|
|
8662
|
-
totalDuration: Date.now() - startTime,
|
|
8663
|
-
agentsUsed: this.agents.size
|
|
8664
|
-
};
|
|
8665
|
-
} finally {
|
|
8666
|
-
this.running = false;
|
|
8667
|
-
this.notifyStateChange();
|
|
8816
|
+
ws.send(JSON.stringify(data));
|
|
8817
|
+
} catch {
|
|
8818
|
+
this.clients.delete(ws);
|
|
8668
8819
|
}
|
|
8669
8820
|
}
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8821
|
+
getClientCount() {
|
|
8822
|
+
return this.clients.size;
|
|
8823
|
+
}
|
|
8824
|
+
destroy() {
|
|
8825
|
+
if (this.broadcastTimer) {
|
|
8826
|
+
clearTimeout(this.broadcastTimer);
|
|
8827
|
+
this.broadcastTimer = null;
|
|
8828
|
+
}
|
|
8829
|
+
for (const unsub of this.unsubscribers) {
|
|
8830
|
+
unsub();
|
|
8831
|
+
}
|
|
8832
|
+
this.unsubscribers = [];
|
|
8833
|
+
this.clients.clear();
|
|
8834
|
+
}
|
|
8835
|
+
}
|
|
8836
|
+
|
|
8837
|
+
// src/dashboard.ts
|
|
8838
|
+
var CORS_HEADERS = {
|
|
8839
|
+
"Access-Control-Allow-Origin": "*",
|
|
8840
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
8841
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
8842
|
+
};
|
|
8843
|
+
function jsonResponse(data, status = 200) {
|
|
8844
|
+
return new Response(JSON.stringify(data), {
|
|
8845
|
+
status,
|
|
8846
|
+
headers: {
|
|
8847
|
+
"Content-Type": "application/json",
|
|
8848
|
+
...CORS_HEADERS
|
|
8849
|
+
}
|
|
8850
|
+
});
|
|
8851
|
+
}
|
|
8852
|
+
|
|
8853
|
+
class DashboardModule {
|
|
8854
|
+
server = null;
|
|
8855
|
+
clients = new Set;
|
|
8856
|
+
orchestrator;
|
|
8857
|
+
constructor(orchestrator) {
|
|
8858
|
+
this.orchestrator = orchestrator;
|
|
8859
|
+
}
|
|
8860
|
+
start(port, host) {
|
|
8861
|
+
const self = this;
|
|
8862
|
+
try {
|
|
8863
|
+
this.server = Bun.serve({
|
|
8864
|
+
port,
|
|
8865
|
+
hostname: host,
|
|
8866
|
+
fetch(req, server) {
|
|
8867
|
+
const url = new URL(req.url);
|
|
8868
|
+
if (req.method === "OPTIONS") {
|
|
8869
|
+
return new Response(null, { headers: CORS_HEADERS });
|
|
8870
|
+
}
|
|
8871
|
+
if (url.pathname === "/ws/events") {
|
|
8872
|
+
if (server.upgrade(req, { data: {} }))
|
|
8873
|
+
return new Response(null);
|
|
8874
|
+
return new Response("WebSocket upgrade failed", { status: 500 });
|
|
8875
|
+
}
|
|
8876
|
+
if (url.pathname === "/api/state") {
|
|
8877
|
+
return jsonResponse(self.orchestrator.getState());
|
|
8878
|
+
}
|
|
8879
|
+
if (url.pathname === "/api/config") {
|
|
8880
|
+
return jsonResponse(self.orchestrator.configManager.exportConfig());
|
|
8881
|
+
}
|
|
8882
|
+
if (url.pathname === "/api/agents") {
|
|
8883
|
+
const state = self.orchestrator.getState();
|
|
8884
|
+
return jsonResponse(state.agents);
|
|
8885
|
+
}
|
|
8886
|
+
if (url.pathname === "/api/costs") {
|
|
8887
|
+
return jsonResponse(self.orchestrator.getCostReport());
|
|
8888
|
+
}
|
|
8889
|
+
if (url.pathname === "/api/health") {
|
|
8890
|
+
return jsonResponse({ ok: true, uptime: process.uptime() });
|
|
8891
|
+
}
|
|
8892
|
+
return new Response("Nexus Dashboard API", {
|
|
8893
|
+
headers: {
|
|
8894
|
+
"Content-Type": "text/plain",
|
|
8895
|
+
...CORS_HEADERS
|
|
8896
|
+
}
|
|
8897
|
+
});
|
|
8898
|
+
},
|
|
8899
|
+
websocket: {
|
|
8900
|
+
open(ws) {
|
|
8901
|
+
self.clients.add(ws);
|
|
8902
|
+
ws.send(JSON.stringify({
|
|
8903
|
+
type: "orchestrator:state",
|
|
8904
|
+
data: self.orchestrator.getState(),
|
|
8905
|
+
timestamp: new Date().toISOString()
|
|
8906
|
+
}));
|
|
8907
|
+
},
|
|
8908
|
+
message(ws, message) {
|
|
8909
|
+
try {
|
|
8910
|
+
const msg = JSON.parse(typeof message === "string" ? message : message.toString());
|
|
8911
|
+
if (msg.type === "ping") {
|
|
8912
|
+
ws.send(JSON.stringify({
|
|
8913
|
+
type: "pong",
|
|
8914
|
+
timestamp: new Date().toISOString()
|
|
8915
|
+
}));
|
|
8916
|
+
}
|
|
8917
|
+
} catch {}
|
|
8918
|
+
},
|
|
8919
|
+
close(ws) {
|
|
8920
|
+
self.clients.delete(ws);
|
|
8921
|
+
}
|
|
8922
|
+
}
|
|
8923
|
+
});
|
|
8924
|
+
console.log(`[nexus] Dashboard server running at http://${host}:${port}`);
|
|
8925
|
+
} catch (err) {
|
|
8926
|
+
this.server = null;
|
|
8927
|
+
console.error(`[nexus] Failed to start dashboard on ${host}:${port}: ${err.message}`);
|
|
8928
|
+
throw new Error(`Dashboard failed to start on port ${port}: ${err.message}`);
|
|
8929
|
+
}
|
|
8930
|
+
}
|
|
8931
|
+
broadcast(event, data) {
|
|
8932
|
+
const message = JSON.stringify({
|
|
8933
|
+
type: event,
|
|
8934
|
+
data,
|
|
8935
|
+
timestamp: new Date().toISOString()
|
|
8936
|
+
});
|
|
8937
|
+
for (const client of this.clients) {
|
|
8938
|
+
try {
|
|
8939
|
+
client.send(message);
|
|
8940
|
+
} catch {
|
|
8941
|
+
this.clients.delete(client);
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
stop() {
|
|
8946
|
+
if (this.server) {
|
|
8947
|
+
this.server.stop();
|
|
8948
|
+
this.server = null;
|
|
8949
|
+
}
|
|
8950
|
+
this.clients.clear();
|
|
8951
|
+
}
|
|
8952
|
+
getClientCount() {
|
|
8953
|
+
return this.clients.size;
|
|
8954
|
+
}
|
|
8955
|
+
isRunning() {
|
|
8956
|
+
return this.server !== null;
|
|
8957
|
+
}
|
|
8958
|
+
}
|
|
8959
|
+
|
|
8960
|
+
// src/dag.ts
|
|
8961
|
+
function detectCycles(nodes) {
|
|
8962
|
+
const cycles = [];
|
|
8963
|
+
const visited = new Set;
|
|
8964
|
+
const inStack = new Set;
|
|
8965
|
+
const path = [];
|
|
8966
|
+
function dfs(nodeId) {
|
|
8967
|
+
if (inStack.has(nodeId)) {
|
|
8968
|
+
const cycleStart = path.indexOf(nodeId);
|
|
8969
|
+
cycles.push([...path.slice(cycleStart), nodeId]);
|
|
8970
|
+
return;
|
|
8971
|
+
}
|
|
8972
|
+
if (visited.has(nodeId))
|
|
8973
|
+
return;
|
|
8974
|
+
visited.add(nodeId);
|
|
8975
|
+
inStack.add(nodeId);
|
|
8976
|
+
path.push(nodeId);
|
|
8977
|
+
const node = nodes.find((n) => n.id === nodeId);
|
|
8978
|
+
if (node) {
|
|
8979
|
+
for (const depId of node.dependencies) {
|
|
8980
|
+
dfs(depId);
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8983
|
+
path.pop();
|
|
8984
|
+
inStack.delete(nodeId);
|
|
8985
|
+
}
|
|
8986
|
+
for (const node of nodes) {
|
|
8987
|
+
dfs(node.id);
|
|
8988
|
+
}
|
|
8989
|
+
return cycles;
|
|
8990
|
+
}
|
|
8991
|
+
|
|
8992
|
+
// src/message-store.ts
|
|
8993
|
+
import { appendFileSync, readFileSync as readFileSync2, existsSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
8994
|
+
import { join as join2, dirname as dirname2 } from "node:path";
|
|
8995
|
+
var DEFAULT_CONFIG2 = {
|
|
8996
|
+
storagePath: join2(process.env.HOME || "~", ".local", "share", "opencode-nexus", "messages.jsonl"),
|
|
8997
|
+
maxMessages: 1e4,
|
|
8998
|
+
rotationSize: 1000
|
|
8999
|
+
};
|
|
9000
|
+
|
|
9001
|
+
class MessageStore {
|
|
9002
|
+
messages = [];
|
|
9003
|
+
config;
|
|
9004
|
+
writeCount = 0;
|
|
9005
|
+
constructor(config) {
|
|
9006
|
+
this.config = { ...DEFAULT_CONFIG2, ...config };
|
|
9007
|
+
this.loadFromDisk();
|
|
9008
|
+
}
|
|
9009
|
+
loadFromDisk() {
|
|
9010
|
+
try {
|
|
9011
|
+
if (existsSync(this.config.storagePath)) {
|
|
9012
|
+
const raw = readFileSync2(this.config.storagePath, "utf-8");
|
|
9013
|
+
const lines = raw.split(`
|
|
9014
|
+
`).filter((l) => l.trim());
|
|
9015
|
+
this.messages = lines.map((l) => JSON.parse(l)).slice(-this.config.maxMessages);
|
|
9016
|
+
}
|
|
9017
|
+
} catch {
|
|
9018
|
+
this.messages = [];
|
|
9019
|
+
}
|
|
9020
|
+
}
|
|
9021
|
+
ensureDir() {
|
|
9022
|
+
try {
|
|
9023
|
+
mkdirSync2(dirname2(this.config.storagePath), { recursive: true });
|
|
9024
|
+
} catch {}
|
|
9025
|
+
}
|
|
9026
|
+
add(message) {
|
|
9027
|
+
this.messages.push(message);
|
|
9028
|
+
if (this.messages.length > this.config.maxMessages) {
|
|
9029
|
+
this.messages = this.messages.slice(-this.config.maxMessages);
|
|
9030
|
+
}
|
|
9031
|
+
try {
|
|
9032
|
+
this.ensureDir();
|
|
9033
|
+
appendFileSync(this.config.storagePath, JSON.stringify(message) + `
|
|
9034
|
+
`);
|
|
9035
|
+
this.writeCount++;
|
|
9036
|
+
if (this.writeCount % this.config.rotationSize === 0) {
|
|
9037
|
+
this.rotate();
|
|
9038
|
+
}
|
|
9039
|
+
} catch {}
|
|
9040
|
+
}
|
|
9041
|
+
rotate() {
|
|
9042
|
+
try {
|
|
9043
|
+
const content = this.messages.map((m) => JSON.stringify(m)).join(`
|
|
9044
|
+
`) + `
|
|
9045
|
+
`;
|
|
9046
|
+
const { writeFileSync } = __require("node:fs");
|
|
9047
|
+
writeFileSync(this.config.storagePath, content);
|
|
9048
|
+
} catch {}
|
|
9049
|
+
}
|
|
9050
|
+
getAll() {
|
|
9051
|
+
return [...this.messages];
|
|
9052
|
+
}
|
|
9053
|
+
getByTopic(topic) {
|
|
9054
|
+
return this.messages.filter((m) => m.topic === topic);
|
|
9055
|
+
}
|
|
9056
|
+
getByAgent(agentId) {
|
|
9057
|
+
return this.messages.filter((m) => m.from === agentId || m.to === agentId);
|
|
9058
|
+
}
|
|
9059
|
+
getRecent(count) {
|
|
9060
|
+
return this.messages.slice(-count);
|
|
9061
|
+
}
|
|
9062
|
+
clear() {
|
|
9063
|
+
this.messages = [];
|
|
9064
|
+
this.writeCount = 0;
|
|
9065
|
+
}
|
|
9066
|
+
getStats() {
|
|
9067
|
+
let fileSize = 0;
|
|
9068
|
+
try {
|
|
9069
|
+
if (existsSync(this.config.storagePath)) {
|
|
9070
|
+
const { statSync } = __require("node:fs");
|
|
9071
|
+
fileSize = statSync(this.config.storagePath).size;
|
|
9072
|
+
}
|
|
9073
|
+
} catch {}
|
|
9074
|
+
return {
|
|
9075
|
+
total: this.messages.length,
|
|
9076
|
+
writeCount: this.writeCount,
|
|
9077
|
+
fileSize
|
|
9078
|
+
};
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
|
|
9082
|
+
// src/memory-store.ts
|
|
9083
|
+
import { Database } from "bun:sqlite";
|
|
9084
|
+
import { join as join3, dirname as dirname3 } from "node:path";
|
|
9085
|
+
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
9086
|
+
var DEFAULT_CONFIG3 = {
|
|
9087
|
+
dbPath: join3(process.env.HOME || "~", ".local", "share", "opencode-nexus", "memory.db"),
|
|
9088
|
+
defaultTTL: 0,
|
|
9089
|
+
maxEntries: 1e4
|
|
9090
|
+
};
|
|
9091
|
+
|
|
9092
|
+
class PersistentMemoryStore {
|
|
9093
|
+
db;
|
|
9094
|
+
config;
|
|
9095
|
+
constructor(config) {
|
|
9096
|
+
this.config = { ...DEFAULT_CONFIG3, ...config };
|
|
9097
|
+
try {
|
|
9098
|
+
mkdirSync3(dirname3(this.config.dbPath), { recursive: true });
|
|
9099
|
+
} catch {}
|
|
9100
|
+
this.db = new Database(this.config.dbPath);
|
|
9101
|
+
this.init();
|
|
9102
|
+
}
|
|
9103
|
+
init() {
|
|
9104
|
+
this.db.exec(`
|
|
9105
|
+
CREATE TABLE IF NOT EXISTS memory (
|
|
9106
|
+
id TEXT PRIMARY KEY,
|
|
9107
|
+
key TEXT NOT NULL,
|
|
9108
|
+
value TEXT NOT NULL,
|
|
9109
|
+
scope TEXT NOT NULL,
|
|
9110
|
+
author TEXT NOT NULL,
|
|
9111
|
+
timestamp INTEGER NOT NULL,
|
|
9112
|
+
confidence REAL DEFAULT 1.0,
|
|
9113
|
+
tags TEXT DEFAULT '[]',
|
|
9114
|
+
ttl INTEGER DEFAULT 0,
|
|
9115
|
+
expires_at INTEGER
|
|
9116
|
+
);
|
|
9117
|
+
CREATE INDEX IF NOT EXISTS idx_memory_scope ON memory(scope);
|
|
9118
|
+
CREATE INDEX IF NOT EXISTS idx_memory_key ON memory(key);
|
|
9119
|
+
CREATE INDEX IF NOT EXISTS idx_memory_author ON memory(author);
|
|
9120
|
+
`);
|
|
9121
|
+
}
|
|
9122
|
+
set(entry) {
|
|
9123
|
+
const id = `mem-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
9124
|
+
const timestamp = Date.now();
|
|
9125
|
+
const expiresAt = entry.ttl ? timestamp + entry.ttl : null;
|
|
9126
|
+
const stmt = this.db.prepare(`
|
|
9127
|
+
INSERT OR REPLACE INTO memory (id, key, value, scope, author, timestamp, confidence, tags, ttl, expires_at)
|
|
9128
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9129
|
+
`);
|
|
9130
|
+
stmt.run(id, entry.key, JSON.stringify(entry.value), entry.scope, entry.author, timestamp, entry.confidence, JSON.stringify(entry.tags || []), entry.ttl || 0, expiresAt);
|
|
9131
|
+
return { ...entry, id, timestamp: new Date(timestamp) };
|
|
9132
|
+
}
|
|
9133
|
+
get(key, scope) {
|
|
9134
|
+
const entries = this.getByKey(key, scope);
|
|
9135
|
+
return entries[entries.length - 1] || null;
|
|
9136
|
+
}
|
|
9137
|
+
getByKey(key, scope) {
|
|
9138
|
+
let query = "SELECT * FROM memory WHERE key = ?";
|
|
9139
|
+
const params = [key];
|
|
9140
|
+
if (scope) {
|
|
9141
|
+
query += " AND scope = ?";
|
|
9142
|
+
params.push(scope);
|
|
9143
|
+
}
|
|
9144
|
+
query += " AND (expires_at IS NULL OR expires_at > ?)";
|
|
9145
|
+
params.push(Date.now());
|
|
9146
|
+
const rows = this.db.prepare(query).all(...params);
|
|
9147
|
+
return rows.map(this.rowToEntry);
|
|
9148
|
+
}
|
|
9149
|
+
getByScope(scope) {
|
|
9150
|
+
const rows = this.db.prepare("SELECT * FROM memory WHERE scope = ? AND (expires_at IS NULL OR expires_at > ?) ORDER BY timestamp DESC").all(scope, Date.now());
|
|
9151
|
+
return rows.map(this.rowToEntry);
|
|
9152
|
+
}
|
|
9153
|
+
search(query) {
|
|
9154
|
+
const rows = this.db.prepare(`SELECT * FROM memory WHERE (key LIKE ? OR value LIKE ?) AND (expires_at IS NULL OR expires_at > ?) ORDER BY timestamp DESC`).all(`%${query}%`, `%${query}%`, Date.now());
|
|
9155
|
+
return rows.map(this.rowToEntry);
|
|
9156
|
+
}
|
|
9157
|
+
getRecent(count) {
|
|
9158
|
+
const rows = this.db.prepare("SELECT * FROM memory WHERE (expires_at IS NULL OR expires_at > ?) ORDER BY timestamp DESC LIMIT ?").all(Date.now(), count);
|
|
9159
|
+
return rows.map(this.rowToEntry);
|
|
9160
|
+
}
|
|
9161
|
+
getByAuthor(author) {
|
|
9162
|
+
const rows = this.db.prepare("SELECT * FROM memory WHERE author = ? AND (expires_at IS NULL OR expires_at > ?) ORDER BY timestamp DESC").all(author, Date.now());
|
|
9163
|
+
return rows.map(this.rowToEntry);
|
|
9164
|
+
}
|
|
9165
|
+
delete(key, scope) {
|
|
9166
|
+
let query = "DELETE FROM memory WHERE key = ?";
|
|
9167
|
+
const params = [key];
|
|
9168
|
+
if (scope) {
|
|
9169
|
+
query += " AND scope = ?";
|
|
9170
|
+
params.push(scope);
|
|
9171
|
+
}
|
|
9172
|
+
const result = this.db.prepare(query).run(...params);
|
|
9173
|
+
return result.changes > 0;
|
|
9174
|
+
}
|
|
9175
|
+
clear(scope) {
|
|
9176
|
+
let query = "DELETE FROM memory";
|
|
9177
|
+
const params = [];
|
|
9178
|
+
if (scope) {
|
|
9179
|
+
query += " WHERE scope = ?";
|
|
9180
|
+
params.push(scope);
|
|
9181
|
+
}
|
|
9182
|
+
const result = this.db.prepare(query).run(...params);
|
|
9183
|
+
return result.changes;
|
|
9184
|
+
}
|
|
9185
|
+
getStats() {
|
|
9186
|
+
const total = this.db.prepare("SELECT COUNT(*) as count FROM memory").get().count;
|
|
9187
|
+
const byScope = {};
|
|
9188
|
+
const scopes = this.db.prepare("SELECT DISTINCT scope FROM memory").all();
|
|
9189
|
+
for (const { scope } of scopes) {
|
|
9190
|
+
byScope[scope] = this.db.prepare("SELECT COUNT(*) as count FROM memory WHERE scope = ?").get(scope).count;
|
|
9191
|
+
}
|
|
9192
|
+
const expired = this.db.prepare("SELECT COUNT(*) as count FROM memory WHERE expires_at IS NOT NULL AND expires_at <= ?").get(Date.now()).count;
|
|
9193
|
+
return { total, byScope, expired };
|
|
9194
|
+
}
|
|
9195
|
+
rowToEntry(row) {
|
|
9196
|
+
let value;
|
|
9197
|
+
try {
|
|
9198
|
+
value = JSON.parse(row.value);
|
|
9199
|
+
} catch {
|
|
9200
|
+
value = row.value;
|
|
9201
|
+
}
|
|
9202
|
+
let tags = [];
|
|
9203
|
+
try {
|
|
9204
|
+
tags = JSON.parse(row.tags || "[]");
|
|
9205
|
+
} catch {
|
|
9206
|
+
tags = [];
|
|
9207
|
+
}
|
|
9208
|
+
return {
|
|
9209
|
+
id: row.id,
|
|
9210
|
+
key: row.key,
|
|
9211
|
+
value,
|
|
9212
|
+
scope: row.scope,
|
|
9213
|
+
author: row.author,
|
|
9214
|
+
timestamp: new Date(row.timestamp),
|
|
9215
|
+
confidence: row.confidence,
|
|
9216
|
+
tags,
|
|
9217
|
+
ttl: row.ttl || undefined
|
|
9218
|
+
};
|
|
9219
|
+
}
|
|
9220
|
+
close() {
|
|
9221
|
+
this.db.close();
|
|
9222
|
+
}
|
|
9223
|
+
}
|
|
9224
|
+
|
|
9225
|
+
// src/health.ts
|
|
9226
|
+
var DEFAULT_HEALTH_CONFIG = {
|
|
9227
|
+
checkInterval: 30000,
|
|
9228
|
+
unhealthyThreshold: 0.5,
|
|
9229
|
+
degradedThreshold: 1e4
|
|
9230
|
+
};
|
|
9231
|
+
|
|
9232
|
+
class HealthMonitor {
|
|
9233
|
+
checks = new Map;
|
|
9234
|
+
interval = null;
|
|
9235
|
+
config;
|
|
9236
|
+
constructor(config) {
|
|
9237
|
+
this.config = { ...DEFAULT_HEALTH_CONFIG, ...config };
|
|
9238
|
+
}
|
|
9239
|
+
start(getAgents) {
|
|
9240
|
+
this.stop();
|
|
9241
|
+
this.interval = setInterval(() => {
|
|
9242
|
+
const agents = getAgents();
|
|
9243
|
+
for (const agent of agents) {
|
|
9244
|
+
if (agent.status === "working" || agent.status === "idle") {
|
|
9245
|
+
this.checkAgent(agent);
|
|
9246
|
+
}
|
|
9247
|
+
}
|
|
9248
|
+
}, this.config.checkInterval);
|
|
9249
|
+
}
|
|
9250
|
+
stop() {
|
|
9251
|
+
if (this.interval) {
|
|
9252
|
+
clearInterval(this.interval);
|
|
9253
|
+
this.interval = null;
|
|
9254
|
+
}
|
|
9255
|
+
}
|
|
9256
|
+
checkAgent(agent) {
|
|
9257
|
+
const errorRate = agent.metrics.tasksFailed / Math.max(1, agent.metrics.tasksCompleted + agent.metrics.tasksFailed);
|
|
9258
|
+
const avgResponseTime = agent.metrics.averageResponseTime || 0;
|
|
9259
|
+
let status = "healthy";
|
|
9260
|
+
if (errorRate > this.config.unhealthyThreshold)
|
|
9261
|
+
status = "unhealthy";
|
|
9262
|
+
else if (avgResponseTime > this.config.degradedThreshold)
|
|
9263
|
+
status = "degraded";
|
|
9264
|
+
const check = {
|
|
9265
|
+
agentId: agent.id,
|
|
9266
|
+
timestamp: new Date,
|
|
9267
|
+
responseTime: avgResponseTime,
|
|
9268
|
+
errorRate,
|
|
9269
|
+
tokensPerSecond: 0,
|
|
9270
|
+
memoryUsage: 0,
|
|
9271
|
+
status
|
|
9272
|
+
};
|
|
9273
|
+
const history = this.checks.get(agent.id) || [];
|
|
9274
|
+
history.push(check);
|
|
9275
|
+
if (history.length > 100)
|
|
9276
|
+
history.shift();
|
|
9277
|
+
this.checks.set(agent.id, history);
|
|
9278
|
+
return check;
|
|
9279
|
+
}
|
|
9280
|
+
getHealth(agentId) {
|
|
9281
|
+
const history = this.checks.get(agentId);
|
|
9282
|
+
return history?.[history.length - 1] || null;
|
|
9283
|
+
}
|
|
9284
|
+
getHistory(agentId) {
|
|
9285
|
+
return this.checks.get(agentId) || [];
|
|
9286
|
+
}
|
|
9287
|
+
getUnhealthyAgents() {
|
|
9288
|
+
const unhealthy = [];
|
|
9289
|
+
for (const [agentId, history] of this.checks) {
|
|
9290
|
+
const latest = history[history.length - 1];
|
|
9291
|
+
if (latest?.status === "unhealthy")
|
|
9292
|
+
unhealthy.push(agentId);
|
|
9293
|
+
}
|
|
9294
|
+
return unhealthy;
|
|
9295
|
+
}
|
|
9296
|
+
getConfig() {
|
|
9297
|
+
return { ...this.config };
|
|
9298
|
+
}
|
|
9299
|
+
isActive() {
|
|
9300
|
+
return this.interval !== null;
|
|
9301
|
+
}
|
|
9302
|
+
}
|
|
9303
|
+
|
|
9304
|
+
// src/fanout.ts
|
|
9305
|
+
class MessageRouter {
|
|
9306
|
+
routes = new Map;
|
|
9307
|
+
wildcardSubscribers = new Set;
|
|
9308
|
+
messageHandlers = new Map;
|
|
9309
|
+
subscribe(subscriberId, topic, handler) {
|
|
9310
|
+
this.messageHandlers.set(subscriberId, handler);
|
|
9311
|
+
if (topic === "*") {
|
|
9312
|
+
this.wildcardSubscribers.add(subscriberId);
|
|
9313
|
+
} else {
|
|
9314
|
+
const subs = this.routes.get(topic) || new Set;
|
|
9315
|
+
subs.add(subscriberId);
|
|
9316
|
+
this.routes.set(topic, subs);
|
|
9317
|
+
}
|
|
9318
|
+
}
|
|
9319
|
+
unsubscribe(subscriberId) {
|
|
9320
|
+
this.messageHandlers.delete(subscriberId);
|
|
9321
|
+
this.wildcardSubscribers.delete(subscriberId);
|
|
9322
|
+
for (const subs of this.routes.values()) {
|
|
9323
|
+
subs.delete(subscriberId);
|
|
9324
|
+
}
|
|
9325
|
+
}
|
|
9326
|
+
route(message) {
|
|
9327
|
+
const deliveredTo = [];
|
|
9328
|
+
const exactSubs = this.routes.get(message.topic);
|
|
9329
|
+
if (exactSubs) {
|
|
9330
|
+
for (const subId of exactSubs) {
|
|
9331
|
+
const handler = this.messageHandlers.get(subId);
|
|
9332
|
+
if (handler) {
|
|
9333
|
+
handler(message);
|
|
9334
|
+
deliveredTo.push(subId);
|
|
9335
|
+
}
|
|
9336
|
+
}
|
|
9337
|
+
}
|
|
9338
|
+
for (const subId of this.wildcardSubscribers) {
|
|
9339
|
+
if (!deliveredTo.includes(subId)) {
|
|
9340
|
+
const handler = this.messageHandlers.get(subId);
|
|
9341
|
+
if (handler) {
|
|
9342
|
+
handler(message);
|
|
9343
|
+
deliveredTo.push(subId);
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
return deliveredTo;
|
|
9348
|
+
}
|
|
9349
|
+
broadcast(message) {
|
|
9350
|
+
for (const handler of this.messageHandlers.values()) {
|
|
9351
|
+
handler(message);
|
|
9352
|
+
}
|
|
9353
|
+
}
|
|
9354
|
+
getSubscribers(topic) {
|
|
9355
|
+
const subs = this.routes.get(topic);
|
|
9356
|
+
return subs ? [...subs] : [];
|
|
9357
|
+
}
|
|
9358
|
+
getWildcardSubscribers() {
|
|
9359
|
+
return [...this.wildcardSubscribers];
|
|
9360
|
+
}
|
|
9361
|
+
getStats() {
|
|
9362
|
+
return {
|
|
9363
|
+
topics: this.routes.size,
|
|
9364
|
+
totalSubscribers: this.messageHandlers.size,
|
|
9365
|
+
wildcardSubscribers: this.wildcardSubscribers.size
|
|
9366
|
+
};
|
|
9367
|
+
}
|
|
9368
|
+
}
|
|
9369
|
+
|
|
9370
|
+
// src/notifications.ts
|
|
9371
|
+
import { spawn } from "node:child_process";
|
|
9372
|
+
|
|
9373
|
+
class NotificationManager {
|
|
9374
|
+
enabled;
|
|
9375
|
+
platform;
|
|
9376
|
+
constructor(enabled = true) {
|
|
9377
|
+
this.enabled = enabled;
|
|
9378
|
+
this.platform = process.platform;
|
|
9379
|
+
}
|
|
9380
|
+
async notify(options) {
|
|
9381
|
+
if (!this.enabled)
|
|
9382
|
+
return false;
|
|
9383
|
+
try {
|
|
9384
|
+
if (this.platform === "darwin") {
|
|
9385
|
+
return await this.notifyMacOS(options);
|
|
9386
|
+
} else if (this.platform === "linux") {
|
|
9387
|
+
return await this.notifyLinux(options);
|
|
9388
|
+
} else if (this.platform === "win32") {
|
|
9389
|
+
return await this.notifyWindows(options);
|
|
9390
|
+
}
|
|
9391
|
+
} catch {}
|
|
9392
|
+
return false;
|
|
9393
|
+
}
|
|
9394
|
+
async notifyMacOS(options) {
|
|
9395
|
+
const soundFlag = options.silent ? "" : options.sound !== false ? 'sound name "Glass"' : "";
|
|
9396
|
+
const script = `display notification "${options.body}" with title "${options.title}" ${soundFlag}`;
|
|
9397
|
+
return new Promise((resolve) => {
|
|
9398
|
+
const proc = spawn("osascript", ["-e", script]);
|
|
9399
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
9400
|
+
proc.on("error", () => resolve(false));
|
|
9401
|
+
setTimeout(() => {
|
|
9402
|
+
proc.kill();
|
|
9403
|
+
resolve(false);
|
|
9404
|
+
}, 3000);
|
|
9405
|
+
});
|
|
9406
|
+
}
|
|
9407
|
+
async notifyLinux(options) {
|
|
9408
|
+
return new Promise((resolve) => {
|
|
9409
|
+
const proc = spawn("notify-send", [options.title, options.body]);
|
|
9410
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
9411
|
+
proc.on("error", () => resolve(false));
|
|
9412
|
+
setTimeout(() => {
|
|
9413
|
+
proc.kill();
|
|
9414
|
+
resolve(false);
|
|
9415
|
+
}, 3000);
|
|
9416
|
+
});
|
|
9417
|
+
}
|
|
9418
|
+
async notifyWindows(options) {
|
|
9419
|
+
return new Promise((resolve) => {
|
|
9420
|
+
const proc = spawn("powershell", ["-Command", `New-BurntToastNotification -Text '${options.title}','${options.body}'`]);
|
|
9421
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
9422
|
+
proc.on("error", () => resolve(false));
|
|
9423
|
+
setTimeout(() => {
|
|
9424
|
+
proc.kill();
|
|
9425
|
+
resolve(false);
|
|
9426
|
+
}, 3000);
|
|
9427
|
+
});
|
|
9428
|
+
}
|
|
9429
|
+
setEnabled(enabled) {
|
|
9430
|
+
this.enabled = enabled;
|
|
9431
|
+
}
|
|
9432
|
+
isEnabled() {
|
|
9433
|
+
return this.enabled;
|
|
9434
|
+
}
|
|
9435
|
+
}
|
|
9436
|
+
|
|
9437
|
+
// src/learning.ts
|
|
9438
|
+
class LearningModule {
|
|
9439
|
+
entries = new Map;
|
|
9440
|
+
minConfidence;
|
|
9441
|
+
constructor(minConfidence = 0.5) {
|
|
9442
|
+
this.minConfidence = minConfidence;
|
|
9443
|
+
}
|
|
9444
|
+
recordFailure(pattern, solution, context, tags = []) {
|
|
9445
|
+
const existing = this.findByPattern(pattern);
|
|
9446
|
+
if (existing) {
|
|
9447
|
+
existing.failCount++;
|
|
9448
|
+
existing.lastSeen = new Date;
|
|
9449
|
+
existing.confidence = Math.max(0, existing.confidence - 0.1);
|
|
9450
|
+
return existing;
|
|
9451
|
+
}
|
|
9452
|
+
const entry = {
|
|
9453
|
+
id: `learn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
9454
|
+
pattern,
|
|
9455
|
+
solution,
|
|
9456
|
+
context,
|
|
9457
|
+
confidence: 0.3,
|
|
9458
|
+
successCount: 0,
|
|
9459
|
+
failCount: 1,
|
|
9460
|
+
lastSeen: new Date,
|
|
9461
|
+
tags
|
|
9462
|
+
};
|
|
9463
|
+
this.entries.set(entry.id, entry);
|
|
9464
|
+
return entry;
|
|
9465
|
+
}
|
|
9466
|
+
recordSuccess(entryId) {
|
|
9467
|
+
const entry = this.entries.get(entryId);
|
|
9468
|
+
if (entry) {
|
|
9469
|
+
entry.successCount++;
|
|
9470
|
+
entry.confidence = Math.min(1, entry.confidence + 0.15);
|
|
9471
|
+
entry.lastSeen = new Date;
|
|
9472
|
+
}
|
|
9473
|
+
}
|
|
9474
|
+
findSolutions(errorPattern) {
|
|
9475
|
+
const matches = [];
|
|
9476
|
+
for (const entry of this.entries.values()) {
|
|
9477
|
+
if (entry.confidence < this.minConfidence)
|
|
9478
|
+
continue;
|
|
9479
|
+
const similarity = this.calculateSimilarity(errorPattern, entry.pattern);
|
|
9480
|
+
if (similarity > 0.3) {
|
|
9481
|
+
matches.push({ entry, similarity });
|
|
9482
|
+
}
|
|
9483
|
+
}
|
|
9484
|
+
return matches.sort((a, b) => b.similarity - a.similarity);
|
|
9485
|
+
}
|
|
9486
|
+
getReliable() {
|
|
9487
|
+
return [...this.entries.values()].filter((e) => e.confidence >= this.minConfidence && e.successCount > 0).sort((a, b) => b.confidence - a.confidence);
|
|
9488
|
+
}
|
|
9489
|
+
getStats() {
|
|
9490
|
+
const all = [...this.entries.values()];
|
|
9491
|
+
const reliable = all.filter((e) => e.confidence >= this.minConfidence && e.successCount > 0);
|
|
9492
|
+
const avgConfidence = all.length > 0 ? all.reduce((sum, e) => sum + e.confidence, 0) / all.length : 0;
|
|
9493
|
+
return { total: all.length, reliable: reliable.length, avgConfidence };
|
|
9494
|
+
}
|
|
9495
|
+
getEntries() {
|
|
9496
|
+
return [...this.entries.values()];
|
|
9497
|
+
}
|
|
9498
|
+
findByPattern(pattern) {
|
|
9499
|
+
for (const entry of this.entries.values()) {
|
|
9500
|
+
if (entry.pattern === pattern)
|
|
9501
|
+
return entry;
|
|
9502
|
+
}
|
|
9503
|
+
return;
|
|
9504
|
+
}
|
|
9505
|
+
calculateSimilarity(a, b) {
|
|
9506
|
+
const wordsA = new Set(a.toLowerCase().split(/\s+/));
|
|
9507
|
+
const wordsB = new Set(b.toLowerCase().split(/\s+/));
|
|
9508
|
+
const intersection = new Set([...wordsA].filter((w) => wordsB.has(w)));
|
|
9509
|
+
const union = new Set([...wordsA, ...wordsB]);
|
|
9510
|
+
return union.size > 0 ? intersection.size / union.size : 0;
|
|
9511
|
+
}
|
|
9512
|
+
clear() {
|
|
9513
|
+
this.entries.clear();
|
|
9514
|
+
}
|
|
9515
|
+
}
|
|
9516
|
+
|
|
9517
|
+
// src/modules.ts
|
|
9518
|
+
class ModuleRegistry {
|
|
9519
|
+
modules = new Map;
|
|
9520
|
+
contexts = new Map;
|
|
9521
|
+
register(module) {
|
|
9522
|
+
if (this.modules.has(module.name)) {
|
|
9523
|
+
throw new Error(`Module '${module.name}' is already registered`);
|
|
9524
|
+
}
|
|
9525
|
+
this.modules.set(module.name, module);
|
|
9526
|
+
}
|
|
9527
|
+
async setupAll(ctx) {
|
|
9528
|
+
for (const [name, module] of this.modules) {
|
|
9529
|
+
try {
|
|
9530
|
+
if (module.setup) {
|
|
9531
|
+
await module.setup(ctx);
|
|
9532
|
+
this.contexts.set(name, ctx);
|
|
9533
|
+
}
|
|
9534
|
+
if (module.hooks) {
|
|
9535
|
+
for (const hook of module.hooks) {
|
|
9536
|
+
ctx.on(hook.event, hook.handler);
|
|
9537
|
+
}
|
|
9538
|
+
}
|
|
9539
|
+
} catch (err) {
|
|
9540
|
+
console.error(`[nexus] Failed to setup module '${name}':`, err);
|
|
9541
|
+
}
|
|
9542
|
+
}
|
|
9543
|
+
}
|
|
9544
|
+
async teardownAll() {
|
|
9545
|
+
for (const [name, module] of this.modules) {
|
|
9546
|
+
try {
|
|
9547
|
+
if (module.teardown) {
|
|
9548
|
+
await module.teardown();
|
|
9549
|
+
}
|
|
9550
|
+
} catch (err) {
|
|
9551
|
+
console.error(`[nexus] Failed to teardown module '${name}':`, err);
|
|
9552
|
+
}
|
|
9553
|
+
}
|
|
9554
|
+
this.contexts.clear();
|
|
9555
|
+
}
|
|
9556
|
+
get(name) {
|
|
9557
|
+
return this.modules.get(name);
|
|
9558
|
+
}
|
|
9559
|
+
list() {
|
|
9560
|
+
return [...this.modules.values()];
|
|
9561
|
+
}
|
|
9562
|
+
getTools() {
|
|
9563
|
+
const tools = [];
|
|
9564
|
+
for (const module of this.modules.values()) {
|
|
9565
|
+
if (module.tools) {
|
|
9566
|
+
tools.push(...module.tools);
|
|
9567
|
+
}
|
|
9568
|
+
}
|
|
9569
|
+
return tools;
|
|
9570
|
+
}
|
|
9571
|
+
isRegistered(name) {
|
|
9572
|
+
return this.modules.has(name);
|
|
9573
|
+
}
|
|
9574
|
+
unregister(name) {
|
|
9575
|
+
this.contexts.delete(name);
|
|
9576
|
+
return this.modules.delete(name);
|
|
9577
|
+
}
|
|
9578
|
+
}
|
|
9579
|
+
|
|
9580
|
+
// src/security.ts
|
|
9581
|
+
var DEFAULT_SECURITY_CONFIG = {
|
|
9582
|
+
enabled: true,
|
|
9583
|
+
scanSecrets: true,
|
|
9584
|
+
scanPatterns: true,
|
|
9585
|
+
customPatterns: [],
|
|
9586
|
+
excludeFiles: ["node_modules", ".git", "dist", "*.test.ts", "*.spec.ts"]
|
|
9587
|
+
};
|
|
9588
|
+
var SECRET_PATTERNS = [
|
|
9589
|
+
{ pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"][^'"]+['"]/gi, category: "secrets", message: "Possible API key found" },
|
|
9590
|
+
{ pattern: /(?:secret|password|passwd|pwd)\s*[:=]\s*['"][^'"]+['"]/gi, category: "secrets", message: "Possible password/secret found" },
|
|
9591
|
+
{ pattern: /(?:token|access_token|auth_token)\s*[:=]\s*['"][^'"]+['"]/gi, category: "secrets", message: "Possible token found" },
|
|
9592
|
+
{ pattern: /(?:private[_-]?key)\s*[:=]\s*['"][^'"]+['"]/gi, category: "secrets", message: "Possible private key found" },
|
|
9593
|
+
{ pattern: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/g, category: "secrets", message: "Private key block found" },
|
|
9594
|
+
{ pattern: /(?:AWS|aws)(?:_ACCESS_KEY_ID|_SECRET_ACCESS_KEY)\s*[:=]\s*['"][^'"]+['"]/gi, category: "secrets", message: "AWS credentials found" }
|
|
9595
|
+
];
|
|
9596
|
+
var DANGEROUS_PATTERNS = [
|
|
9597
|
+
{ pattern: /eval\s*\(/g, category: "injection", message: "eval() usage detected", suggestion: "Use safer alternatives like JSON.parse() or Function constructor" },
|
|
9598
|
+
{ pattern: /exec\s*\(/g, category: "injection", message: "exec() usage detected", suggestion: "Use execFile() or spawn() with argument arrays" },
|
|
9599
|
+
{ pattern: /child_process/g, category: "injection", message: "child_process usage detected", suggestion: "Ensure user input is sanitized before shell execution" },
|
|
9600
|
+
{ pattern: /innerHTML\s*=/g, category: "xss", message: "innerHTML assignment detected", suggestion: "Use textContent or sanitize input before setting innerHTML" },
|
|
9601
|
+
{ pattern: /document\.write\s*\(/g, category: "xss", message: "document.write() usage detected", suggestion: "Use DOM manipulation methods instead" },
|
|
9602
|
+
{ pattern: /new Function\s*\(/g, category: "injection", message: "Function constructor usage detected", suggestion: "Avoid dynamic code execution" },
|
|
9603
|
+
{ pattern: /__proto__\s*=/g, category: "prototype-pollution", message: "__proto__ assignment detected", suggestion: "Use Object.create() or Object.assign() instead" },
|
|
9604
|
+
{ pattern: /process\.env\[?[\"'][^\"']+[\"']\]?(?!\.replace)/g, category: "data-exposure", message: "Direct process.env access", suggestion: "Use a config module with validation" }
|
|
9605
|
+
];
|
|
9606
|
+
|
|
9607
|
+
class SecurityScanner {
|
|
9608
|
+
config;
|
|
9609
|
+
issues = [];
|
|
9610
|
+
constructor(config) {
|
|
9611
|
+
this.config = { ...DEFAULT_SECURITY_CONFIG, ...config };
|
|
9612
|
+
}
|
|
9613
|
+
scanContent(content, filename) {
|
|
9614
|
+
if (!this.config.enabled)
|
|
9615
|
+
return [];
|
|
9616
|
+
if (this.isExcluded(filename))
|
|
9617
|
+
return [];
|
|
9618
|
+
const issues = [];
|
|
9619
|
+
if (this.config.scanSecrets) {
|
|
9620
|
+
for (const { pattern, category, message } of SECRET_PATTERNS) {
|
|
9621
|
+
const matches = content.matchAll(new RegExp(pattern.source, pattern.flags));
|
|
9622
|
+
for (const match of matches) {
|
|
9623
|
+
const line = this.getLineNumber(content, match.index || 0);
|
|
9624
|
+
issues.push({
|
|
9625
|
+
id: `sec-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
|
|
9626
|
+
severity: "critical",
|
|
9627
|
+
category,
|
|
9628
|
+
message,
|
|
9629
|
+
file: filename,
|
|
9630
|
+
line,
|
|
9631
|
+
suggestion: "Remove hardcoded secrets and use environment variables or a secrets manager"
|
|
9632
|
+
});
|
|
9633
|
+
}
|
|
9634
|
+
}
|
|
9635
|
+
}
|
|
9636
|
+
if (this.config.scanPatterns) {
|
|
9637
|
+
for (const { pattern, category, message, suggestion } of DANGEROUS_PATTERNS) {
|
|
9638
|
+
const matches = content.matchAll(new RegExp(pattern.source, pattern.flags));
|
|
9639
|
+
for (const match of matches) {
|
|
9640
|
+
const line = this.getLineNumber(content, match.index || 0);
|
|
9641
|
+
issues.push({
|
|
9642
|
+
id: `sec-${Date.now()}-${Math.random().toString(36).substr(6, 6)}`,
|
|
9643
|
+
severity: "medium",
|
|
9644
|
+
category,
|
|
9645
|
+
message,
|
|
9646
|
+
file: filename,
|
|
9647
|
+
line,
|
|
9648
|
+
suggestion
|
|
9649
|
+
});
|
|
9650
|
+
}
|
|
9651
|
+
}
|
|
9652
|
+
}
|
|
9653
|
+
for (const pattern of this.config.customPatterns) {
|
|
9654
|
+
const matches = content.matchAll(new RegExp(pattern.source, pattern.flags));
|
|
9655
|
+
for (const match of matches) {
|
|
9656
|
+
const line = this.getLineNumber(content, match.index || 0);
|
|
9657
|
+
issues.push({
|
|
9658
|
+
id: `sec-${Date.now()}-${Math.random().toString(36).substr(12, 6)}`,
|
|
9659
|
+
severity: "medium",
|
|
9660
|
+
category: "custom",
|
|
9661
|
+
message: `Custom pattern match in ${filename}`,
|
|
9662
|
+
file: filename,
|
|
9663
|
+
line
|
|
9664
|
+
});
|
|
9665
|
+
}
|
|
9666
|
+
}
|
|
9667
|
+
this.issues.push(...issues);
|
|
9668
|
+
return issues;
|
|
9669
|
+
}
|
|
9670
|
+
getResult() {
|
|
9671
|
+
const severityScores = { critical: 0, high: 10, medium: 25, low: 50 };
|
|
9672
|
+
let deductions = 0;
|
|
9673
|
+
for (const issue of this.issues) {
|
|
9674
|
+
deductions += severityScores[issue.severity] || 25;
|
|
9675
|
+
}
|
|
9676
|
+
const score = Math.max(0, 100 - deductions);
|
|
9677
|
+
return {
|
|
9678
|
+
issues: [...this.issues],
|
|
9679
|
+
score,
|
|
9680
|
+
scannedAt: new Date,
|
|
9681
|
+
filesScanned: new Set(this.issues.map((i) => i.file).filter(Boolean)).size || 0
|
|
9682
|
+
};
|
|
9683
|
+
}
|
|
9684
|
+
getBySeverity(severity) {
|
|
9685
|
+
return this.issues.filter((i) => i.severity === severity);
|
|
9686
|
+
}
|
|
9687
|
+
getByCategory(category) {
|
|
9688
|
+
return this.issues.filter((i) => i.category === category);
|
|
9689
|
+
}
|
|
9690
|
+
isExcluded(filename) {
|
|
9691
|
+
return this.config.excludeFiles.some((exclusion) => {
|
|
9692
|
+
if (exclusion.startsWith("*")) {
|
|
9693
|
+
return filename.endsWith(exclusion.slice(1));
|
|
9694
|
+
}
|
|
9695
|
+
return filename.includes(exclusion);
|
|
9696
|
+
});
|
|
9697
|
+
}
|
|
9698
|
+
getLineNumber(content, index) {
|
|
9699
|
+
return content.substring(0, index).split(`
|
|
9700
|
+
`).length;
|
|
9701
|
+
}
|
|
9702
|
+
clear() {
|
|
9703
|
+
this.issues = [];
|
|
9704
|
+
}
|
|
9705
|
+
getStats() {
|
|
9706
|
+
return {
|
|
9707
|
+
total: this.issues.length,
|
|
9708
|
+
critical: this.getBySeverity("critical").length,
|
|
9709
|
+
high: this.getBySeverity("high").length,
|
|
9710
|
+
medium: this.getBySeverity("medium").length,
|
|
9711
|
+
low: this.getBySeverity("low").length
|
|
9712
|
+
};
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
|
|
9716
|
+
// src/orchestrator.ts
|
|
9717
|
+
var DEFAULT_ESCALATION = {
|
|
9718
|
+
maxRetries: 3,
|
|
9719
|
+
retryDelay: 1000,
|
|
9720
|
+
enableRespawn: true,
|
|
9721
|
+
fallbackModels: ["google/gemini-2.5-flash", "anthropic/claude-haiku-4-5"],
|
|
9722
|
+
alertOnFailure: true
|
|
9723
|
+
};
|
|
9724
|
+
|
|
9725
|
+
class NexusOrchestrator {
|
|
9726
|
+
agents = new Map;
|
|
9727
|
+
tasks = new Map;
|
|
9728
|
+
dag = null;
|
|
9729
|
+
config;
|
|
9730
|
+
budget;
|
|
9731
|
+
running = false;
|
|
9732
|
+
paused = false;
|
|
9733
|
+
budgetExceeded = false;
|
|
9734
|
+
totalSpent = 0;
|
|
9735
|
+
costByAgent = new Map;
|
|
9736
|
+
costByModel = new Map;
|
|
9737
|
+
messageQueue = [];
|
|
9738
|
+
subscribers = new Map;
|
|
9739
|
+
messageStore;
|
|
9740
|
+
memoryStore;
|
|
9741
|
+
messageRouter;
|
|
9742
|
+
eventHandlers = new Map;
|
|
9743
|
+
configManager;
|
|
9744
|
+
ctx = null;
|
|
9745
|
+
broadcaster = null;
|
|
9746
|
+
dashboard = null;
|
|
9747
|
+
_healthMonitor = null;
|
|
9748
|
+
learning;
|
|
9749
|
+
escalationPolicy;
|
|
9750
|
+
nodeRetryCounts = new Map;
|
|
9751
|
+
moduleRegistry;
|
|
9752
|
+
securityScanner;
|
|
9753
|
+
onStateChange = null;
|
|
9754
|
+
stateChangeTimer = null;
|
|
9755
|
+
costHistory = [];
|
|
9756
|
+
cleanupInterval = null;
|
|
9757
|
+
get healthMonitor() {
|
|
9758
|
+
return this._healthMonitor;
|
|
9759
|
+
}
|
|
9760
|
+
notifications = null;
|
|
9761
|
+
constructor(config, messageStoreConfig, memoryStoreConfig) {
|
|
9762
|
+
this.config = this.mergeConfig(config);
|
|
9763
|
+
this.budget = this.config.budget;
|
|
9764
|
+
this.configManager = new NexusConfigManager;
|
|
9765
|
+
this.moduleRegistry = new ModuleRegistry;
|
|
9766
|
+
this.messageStore = new MessageStore(messageStoreConfig);
|
|
9767
|
+
this.memoryStore = new PersistentMemoryStore(memoryStoreConfig);
|
|
9768
|
+
this.messageRouter = new MessageRouter;
|
|
9769
|
+
this.escalationPolicy = {
|
|
9770
|
+
...DEFAULT_ESCALATION,
|
|
9771
|
+
maxRetries: this.config.selfHealing.maxRetries,
|
|
9772
|
+
retryDelay: this.config.selfHealing.retryDelay,
|
|
9773
|
+
enableRespawn: this.config.selfHealing.contextTransfer
|
|
9774
|
+
};
|
|
9775
|
+
this.learning = new LearningModule(this.config.learning.minConfidence);
|
|
9776
|
+
this.securityScanner = new SecurityScanner;
|
|
9777
|
+
}
|
|
9778
|
+
async initialize(ctx, onStateChange) {
|
|
9779
|
+
this.ctx = ctx;
|
|
9780
|
+
this.onStateChange = onStateChange ?? null;
|
|
9781
|
+
this.configManager.loadFromPath(process.cwd());
|
|
9782
|
+
this.cleanupInterval = setInterval(() => this.cleanupStaleData(), 300000);
|
|
9783
|
+
this._healthMonitor = new HealthMonitor({
|
|
9784
|
+
checkInterval: this.config.agents.healthCheckInterval
|
|
9785
|
+
});
|
|
9786
|
+
this.notifications = new NotificationManager(true);
|
|
9787
|
+
const moduleCtx = {
|
|
9788
|
+
orchestrator: this,
|
|
9789
|
+
config: this.config,
|
|
9790
|
+
emit: (event, data) => this.emit(event, data),
|
|
9791
|
+
on: (event, handler) => {
|
|
9792
|
+
this.on(event, handler);
|
|
9793
|
+
}
|
|
9794
|
+
};
|
|
9795
|
+
await this.moduleRegistry.setupAll(moduleCtx);
|
|
9796
|
+
}
|
|
9797
|
+
initBroadcaster(opts) {
|
|
9798
|
+
this.broadcaster = new StateBroadcaster(this, opts);
|
|
9799
|
+
const previousOnStateChange = this.onStateChange;
|
|
9800
|
+
this.onStateChange = () => {
|
|
9801
|
+
previousOnStateChange?.();
|
|
9802
|
+
this.broadcaster?.broadcastState();
|
|
9803
|
+
};
|
|
9804
|
+
}
|
|
9805
|
+
startDashboard(port, host) {
|
|
9806
|
+
const dashPort = port || this.config.dashboard.port;
|
|
9807
|
+
const dashHost = host || this.config.dashboard.host;
|
|
9808
|
+
this.dashboard = new DashboardModule(this);
|
|
9809
|
+
this.dashboard.start(dashPort, dashHost);
|
|
9810
|
+
this.on("agent:spawned", (agent) => this.dashboard.broadcast("agent:spawned", agent));
|
|
9811
|
+
this.on("agent:terminated", (agent) => this.dashboard.broadcast("agent:terminated", agent));
|
|
9812
|
+
this.on("budget:alert", (data) => this.dashboard.broadcast("budget:alert", data));
|
|
9813
|
+
this.on("budget:exceeded", (data) => this.dashboard.broadcast("budget:exceeded", data));
|
|
9814
|
+
}
|
|
9815
|
+
stopDashboard() {
|
|
9816
|
+
if (this.dashboard) {
|
|
9817
|
+
this.dashboard.stop();
|
|
9818
|
+
this.dashboard = null;
|
|
9819
|
+
}
|
|
9820
|
+
}
|
|
9821
|
+
getState() {
|
|
9822
|
+
const agents = Array.from(this.agents.values()).map((a) => ({
|
|
9823
|
+
id: a.id,
|
|
9824
|
+
name: a.name,
|
|
9825
|
+
role: a.role,
|
|
9826
|
+
status: a.status,
|
|
9827
|
+
model: `${a.model.provider}/${a.model.model}`,
|
|
9828
|
+
sessionID: a.sessionID,
|
|
9829
|
+
spawnedAt: a.spawnedAt.toISOString(),
|
|
9830
|
+
tasksCompleted: a.metrics.tasksCompleted,
|
|
9831
|
+
tasksFailed: a.metrics.tasksFailed,
|
|
9832
|
+
totalCost: a.metrics.totalCost
|
|
9833
|
+
}));
|
|
9834
|
+
const tasks = Array.from(this.tasks.values()).map((t) => ({
|
|
9835
|
+
id: t.id,
|
|
9836
|
+
name: t.name,
|
|
9837
|
+
role: t.requiredRole,
|
|
9838
|
+
priority: t.priority || "normal",
|
|
9839
|
+
status: t.status,
|
|
9840
|
+
assignedAgent: t.assignedAgent,
|
|
9841
|
+
result: t.result ? {
|
|
9842
|
+
success: t.result.success,
|
|
9843
|
+
output: t.result.output?.slice(0, 500),
|
|
9844
|
+
error: t.result.error,
|
|
9845
|
+
duration: t.result.duration
|
|
9846
|
+
} : undefined
|
|
9847
|
+
}));
|
|
9848
|
+
return {
|
|
9849
|
+
running: this.running,
|
|
9850
|
+
paused: this.paused,
|
|
9851
|
+
agents,
|
|
9852
|
+
tasks,
|
|
9853
|
+
totalSpent: this.totalSpent,
|
|
9854
|
+
budgetRemaining: this.budget.maxTotalCost - this.totalSpent,
|
|
9855
|
+
lastUpdated: new Date().toISOString()
|
|
9856
|
+
};
|
|
9857
|
+
}
|
|
9858
|
+
mergeConfig(partial) {
|
|
9859
|
+
const defaults = {
|
|
9860
|
+
maxConcurrency: 5,
|
|
9861
|
+
schedulerInterval: 1000,
|
|
9862
|
+
defaultTimeout: 300000,
|
|
9863
|
+
budget: {
|
|
9864
|
+
maxTotalCost: 10,
|
|
9865
|
+
maxCostPerTask: 1,
|
|
9866
|
+
maxCostPerAgent: 2,
|
|
9867
|
+
alertThreshold: 0.2,
|
|
9868
|
+
hardLimit: false
|
|
9869
|
+
},
|
|
9870
|
+
agents: {
|
|
9871
|
+
defaultRole: "coder",
|
|
9872
|
+
spawnDelay: 100,
|
|
9873
|
+
healthCheckInterval: 30000
|
|
9874
|
+
},
|
|
9875
|
+
selfHealing: {
|
|
9876
|
+
enabled: true,
|
|
9877
|
+
maxRetries: 3,
|
|
9878
|
+
retryDelay: 1000,
|
|
9879
|
+
backoffMultiplier: 2,
|
|
9880
|
+
contextTransfer: true
|
|
9881
|
+
},
|
|
9882
|
+
communication: {
|
|
9883
|
+
mode: "pubsub",
|
|
9884
|
+
maxQueueSize: 100,
|
|
9885
|
+
messageTTL: 60000,
|
|
9886
|
+
persistence: false
|
|
9887
|
+
},
|
|
9888
|
+
memory: {
|
|
9889
|
+
enabled: true,
|
|
9890
|
+
storage: "memory",
|
|
9891
|
+
maxEntriesPerScope: 1000,
|
|
9892
|
+
syncInterval: 5000
|
|
9893
|
+
},
|
|
9894
|
+
dashboard: {
|
|
9895
|
+
enabled: true,
|
|
9896
|
+
port: 4747,
|
|
9897
|
+
host: "127.0.0.1"
|
|
9898
|
+
},
|
|
9899
|
+
security: {
|
|
9900
|
+
sastEnabled: true,
|
|
9901
|
+
secretsScanning: true,
|
|
9902
|
+
scopeEnforcement: true
|
|
9903
|
+
},
|
|
9904
|
+
learning: {
|
|
9905
|
+
enabled: true,
|
|
9906
|
+
patternStorage: "memory",
|
|
9907
|
+
minConfidence: 0.7
|
|
9908
|
+
}
|
|
9909
|
+
};
|
|
9910
|
+
return {
|
|
9911
|
+
...defaults,
|
|
9912
|
+
...partial,
|
|
9913
|
+
budget: { ...defaults.budget, ...partial?.budget },
|
|
9914
|
+
agents: { ...defaults.agents, ...partial?.agents },
|
|
9915
|
+
selfHealing: { ...defaults.selfHealing, ...partial?.selfHealing },
|
|
9916
|
+
communication: { ...defaults.communication, ...partial?.communication },
|
|
9917
|
+
memory: { ...defaults.memory, ...partial?.memory },
|
|
9918
|
+
dashboard: { ...defaults.dashboard, ...partial?.dashboard },
|
|
9919
|
+
security: { ...defaults.security, ...partial?.security },
|
|
9920
|
+
learning: { ...defaults.learning, ...partial?.learning }
|
|
9921
|
+
};
|
|
9922
|
+
}
|
|
9923
|
+
notifyStateChange() {
|
|
9924
|
+
if (this.stateChangeTimer)
|
|
9925
|
+
return;
|
|
9926
|
+
this.stateChangeTimer = setTimeout(() => {
|
|
9927
|
+
this.stateChangeTimer = null;
|
|
9928
|
+
this.onStateChange?.();
|
|
9929
|
+
}, 100);
|
|
9930
|
+
}
|
|
9931
|
+
async execute(request) {
|
|
9932
|
+
if (this.running) {
|
|
9933
|
+
throw new Error("Orchestrator is already running");
|
|
9934
|
+
}
|
|
9935
|
+
if (!this.ctx) {
|
|
9936
|
+
throw new Error("Orchestrator not initialized - call initialize(ctx) first");
|
|
9937
|
+
}
|
|
9938
|
+
this.running = true;
|
|
9939
|
+
const startTime = Date.now();
|
|
9940
|
+
this.notifyStateChange();
|
|
9941
|
+
try {
|
|
9942
|
+
this.dag = this.buildDAG(request.tasks);
|
|
9943
|
+
const dagNodes = Array.from(this.dag.nodes.values());
|
|
9944
|
+
const cycles = detectCycles(dagNodes);
|
|
9945
|
+
if (cycles.length > 0) {
|
|
9946
|
+
const cycleDescriptions = cycles.map((c) => c.join(" → ")).join(", ");
|
|
9947
|
+
throw new Error(`Circular dependency detected: ${cycleDescriptions}`);
|
|
9948
|
+
}
|
|
9949
|
+
if (request.budget) {
|
|
9950
|
+
this.budget = request.budget;
|
|
9951
|
+
}
|
|
9952
|
+
await this.executeDAG();
|
|
9953
|
+
const results = this.collectResults();
|
|
9954
|
+
const totalDuration = Date.now() - startTime;
|
|
9955
|
+
return {
|
|
9956
|
+
success: true,
|
|
9957
|
+
tasks: results,
|
|
9958
|
+
totalCost: this.totalSpent,
|
|
9959
|
+
totalDuration,
|
|
9960
|
+
agentsUsed: this.agents.size
|
|
9961
|
+
};
|
|
9962
|
+
} catch (error) {
|
|
9963
|
+
return {
|
|
9964
|
+
success: false,
|
|
9965
|
+
tasks: [],
|
|
9966
|
+
totalCost: this.totalSpent,
|
|
9967
|
+
totalDuration: Date.now() - startTime,
|
|
9968
|
+
agentsUsed: this.agents.size
|
|
9969
|
+
};
|
|
9970
|
+
} finally {
|
|
9971
|
+
this.running = false;
|
|
9972
|
+
this.notifyStateChange();
|
|
9973
|
+
}
|
|
9974
|
+
}
|
|
9975
|
+
buildDAG(tasks) {
|
|
9976
|
+
const dag = {
|
|
9977
|
+
nodes: new Map,
|
|
9978
|
+
addNode: (node) => {
|
|
9979
|
+
dag.nodes.set(node.id, node);
|
|
9980
|
+
},
|
|
9981
|
+
addDependency: (nodeId, dependsOn) => {
|
|
9982
|
+
const node = dag.nodes.get(nodeId);
|
|
9983
|
+
if (node) {
|
|
9984
|
+
node.dependencies.push(dependsOn);
|
|
9985
|
+
}
|
|
9986
|
+
},
|
|
9987
|
+
removeNode: (nodeId) => {
|
|
9988
|
+
dag.nodes.delete(nodeId);
|
|
9989
|
+
},
|
|
9990
|
+
getReadyNodes: () => {
|
|
9991
|
+
const ready = [];
|
|
9992
|
+
dag.nodes.forEach((node) => {
|
|
9993
|
+
if (node.status === "pending") {
|
|
9994
|
+
const depsComplete = node.dependencies.every((dep) => {
|
|
9995
|
+
const depNode = dag.nodes.get(dep);
|
|
9996
|
+
return depNode?.status === "completed";
|
|
9997
|
+
});
|
|
9998
|
+
if (depsComplete) {
|
|
9999
|
+
ready.push(node);
|
|
10000
|
+
}
|
|
10001
|
+
}
|
|
10002
|
+
});
|
|
10003
|
+
const priorityOrder = { critical: 0, high: 1, normal: 2, low: 3 };
|
|
10004
|
+
return ready.sort((a, b) => {
|
|
10005
|
+
const pa = priorityOrder[a.task.priority] ?? 2;
|
|
10006
|
+
const pb = priorityOrder[b.task.priority] ?? 2;
|
|
10007
|
+
return pa - pb;
|
|
10008
|
+
});
|
|
10009
|
+
},
|
|
10010
|
+
markComplete: (nodeId, result) => {
|
|
10011
|
+
const node = dag.nodes.get(nodeId);
|
|
10012
|
+
if (node) {
|
|
8703
10013
|
node.status = "completed";
|
|
8704
10014
|
node.result = result;
|
|
8705
10015
|
}
|
|
@@ -8752,7 +10062,7 @@ class NexusOrchestrator {
|
|
|
8752
10062
|
await this.sleep(this.config.schedulerInterval);
|
|
8753
10063
|
}
|
|
8754
10064
|
}
|
|
8755
|
-
async spawnAndExecute(node) {
|
|
10065
|
+
async spawnAndExecute(node, transferContext) {
|
|
8756
10066
|
const complexity = this.analyzeComplexity(node.task);
|
|
8757
10067
|
const model = this.selectModel(node.task.requiredRole, complexity);
|
|
8758
10068
|
const agent = await this.spawnAgent({
|
|
@@ -8764,9 +10074,9 @@ class NexusOrchestrator {
|
|
|
8764
10074
|
node.status = "running";
|
|
8765
10075
|
node.task.assignedAgent = agent.id;
|
|
8766
10076
|
this.notifyStateChange();
|
|
8767
|
-
await this.executeTask(agent, node);
|
|
10077
|
+
await this.executeTask(agent, node, transferContext);
|
|
8768
10078
|
}
|
|
8769
|
-
async executeTask(agent, node) {
|
|
10079
|
+
async executeTask(agent, node, transferContext) {
|
|
8770
10080
|
if (!this.ctx || !agent.sessionID) {
|
|
8771
10081
|
node.status = "failed";
|
|
8772
10082
|
node.result = {
|
|
@@ -8783,7 +10093,7 @@ class NexusOrchestrator {
|
|
|
8783
10093
|
const timeout = node.task.timeout || this.config.defaultTimeout;
|
|
8784
10094
|
try {
|
|
8785
10095
|
const rolePrompt = this.buildRolePrompt(node.task.requiredRole);
|
|
8786
|
-
|
|
10096
|
+
let taskPrompt = `${rolePrompt}
|
|
8787
10097
|
|
|
8788
10098
|
## Task
|
|
8789
10099
|
${node.task.name}
|
|
@@ -8792,6 +10102,22 @@ ${node.task.description}
|
|
|
8792
10102
|
|
|
8793
10103
|
## Scope
|
|
8794
10104
|
Files: ${node.task.files.include.join(", ")}`;
|
|
10105
|
+
if (transferContext) {
|
|
10106
|
+
taskPrompt += `
|
|
10107
|
+
|
|
10108
|
+
## Previous Agent Context (from failed agent ${transferContext.previousAgentId})`;
|
|
10109
|
+
taskPrompt += `
|
|
10110
|
+
Partial results: ${transferContext.partialResults.join(", ") || "None"}`;
|
|
10111
|
+
taskPrompt += `
|
|
10112
|
+
Decisions made: ${transferContext.decisions.join(", ") || "None"}`;
|
|
10113
|
+
taskPrompt += `
|
|
10114
|
+
Progress: ${transferContext.taskProgress}%`;
|
|
10115
|
+
taskPrompt += `
|
|
10116
|
+
Errors encountered: ${transferContext.errorLog.join(", ") || "None"}`;
|
|
10117
|
+
taskPrompt += `
|
|
10118
|
+
|
|
10119
|
+
Please continue from where the previous agent left off.`;
|
|
10120
|
+
}
|
|
8795
10121
|
await this.ctx.session.prompt({
|
|
8796
10122
|
sessionID: agent.sessionID,
|
|
8797
10123
|
text: taskPrompt
|
|
@@ -8816,6 +10142,24 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
8816
10142
|
this.totalSpent += result.cost;
|
|
8817
10143
|
this.costByAgent.set(agent.id, (this.costByAgent.get(agent.id) || 0) + result.cost);
|
|
8818
10144
|
this.checkBudget();
|
|
10145
|
+
if (this.notifications?.isEnabled()) {
|
|
10146
|
+
this.notifications.notify({ title: "Nexus: Task Complete", body: `${node.task.name} completed successfully` });
|
|
10147
|
+
}
|
|
10148
|
+
const priorPattern = this.learning.findSolutions(`task ${node.id} failed`);
|
|
10149
|
+
if (priorPattern.length > 0) {
|
|
10150
|
+
this.learning.recordSuccess(priorPattern[0].entry.id);
|
|
10151
|
+
}
|
|
10152
|
+
if (typeof output === "string" && output.length > 0) {
|
|
10153
|
+
const securityIssues = this.securityScanner.scanContent(output, node.task.name);
|
|
10154
|
+
if (securityIssues.length > 0) {
|
|
10155
|
+
this.emit("security:issues-found", {
|
|
10156
|
+
taskId: node.id,
|
|
10157
|
+
taskName: node.task.name,
|
|
10158
|
+
issues: securityIssues,
|
|
10159
|
+
totalIssues: securityIssues.length
|
|
10160
|
+
});
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
8819
10163
|
} catch (error) {
|
|
8820
10164
|
const duration = Date.now() - startTime;
|
|
8821
10165
|
const result = {
|
|
@@ -8847,57 +10191,105 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
8847
10191
|
};
|
|
8848
10192
|
return rolePrompts[role] || `You are a ${role}. Complete the assigned task professionally.`;
|
|
8849
10193
|
}
|
|
10194
|
+
collectContext(agent) {
|
|
10195
|
+
const partialResults = [];
|
|
10196
|
+
const decisions = [];
|
|
10197
|
+
const errorLog = [];
|
|
10198
|
+
if (agent.sessionID && this.ctx) {
|
|
10199
|
+
if (agent.metrics.tasksCompleted > 0) {
|
|
10200
|
+
partialResults.push(`${agent.metrics.tasksCompleted} task(s) completed before failure`);
|
|
10201
|
+
}
|
|
10202
|
+
if (agent.metrics.tasksFailed > 0) {
|
|
10203
|
+
errorLog.push(`${agent.metrics.tasksFailed} task(s) failed`);
|
|
10204
|
+
}
|
|
10205
|
+
}
|
|
10206
|
+
const memoryEntries = [];
|
|
10207
|
+
const byAgentId = this.memoryStore.getByAuthor(agent.id);
|
|
10208
|
+
const byRole = this.memoryStore.getByAuthor(agent.role);
|
|
10209
|
+
memoryEntries.push(...byAgentId, ...byRole);
|
|
10210
|
+
const taskProgress = agent.metrics.tasksCompleted > 0 ? Math.min(50, agent.metrics.tasksCompleted * 25) : 0;
|
|
10211
|
+
return {
|
|
10212
|
+
previousAgentId: agent.id,
|
|
10213
|
+
partialResults,
|
|
10214
|
+
decisions,
|
|
10215
|
+
memoryEntries,
|
|
10216
|
+
taskProgress,
|
|
10217
|
+
errorLog
|
|
10218
|
+
};
|
|
10219
|
+
}
|
|
8850
10220
|
async handleFailure(agent, node, error) {
|
|
8851
|
-
const
|
|
8852
|
-
|
|
8853
|
-
|
|
10221
|
+
const policy = this.escalationPolicy;
|
|
10222
|
+
const retryCount = this.nodeRetryCounts.get(node.id) || 0;
|
|
10223
|
+
const pattern = error.message || "Unknown error";
|
|
10224
|
+
const solution = `Retry (attempt ${retryCount + 1}/${policy.maxRetries})`;
|
|
10225
|
+
const context = `during ${node.task.requiredRole} task "${node.task.name}"`;
|
|
10226
|
+
this.learning.recordFailure(pattern, solution, context, [node.task.requiredRole]);
|
|
10227
|
+
if (retryCount < policy.maxRetries) {
|
|
10228
|
+
this.nodeRetryCounts.set(node.id, retryCount + 1);
|
|
10229
|
+
const delay = policy.retryDelay * Math.pow(2, retryCount);
|
|
8854
10230
|
await this.sleep(delay);
|
|
8855
|
-
|
|
8856
|
-
|
|
10231
|
+
node.status = "pending";
|
|
10232
|
+
this.notifyStateChange();
|
|
10233
|
+
await this.spawnAndExecute(node);
|
|
10234
|
+
return;
|
|
10235
|
+
}
|
|
10236
|
+
if (policy.enableRespawn && this.config.selfHealing.contextTransfer) {
|
|
10237
|
+
const context = this.collectContext(agent);
|
|
10238
|
+
this.setMemory("session", `context:${agent.id}`, context, agent.id);
|
|
10239
|
+
await this.terminateAgent(agent.id);
|
|
10240
|
+
node.status = "pending";
|
|
10241
|
+
this.notifyStateChange();
|
|
10242
|
+
await this.spawnAndExecute(node, context);
|
|
10243
|
+
return;
|
|
10244
|
+
}
|
|
10245
|
+
if (policy.fallbackModels.length > 0) {
|
|
10246
|
+
const fallbackModel = policy.fallbackModels.shift();
|
|
10247
|
+
if (fallbackModel) {
|
|
10248
|
+
await this.terminateAgent(agent.id);
|
|
10249
|
+
node.status = "pending";
|
|
8857
10250
|
this.notifyStateChange();
|
|
8858
|
-
await this.
|
|
10251
|
+
await this.spawnAgent({ role: node.task.requiredRole, model: fallbackModel });
|
|
10252
|
+
return;
|
|
10253
|
+
}
|
|
10254
|
+
}
|
|
10255
|
+
if (policy.alertOnFailure) {
|
|
10256
|
+
this.emit("agent:escalation", { agentId: agent.id, taskId: node.id, error: error.message });
|
|
10257
|
+
if (this.notifications?.isEnabled()) {
|
|
10258
|
+
this.notifications.notify({ title: "Nexus: Task Failed", body: `${node.task.name} failed: ${error.message}`, sound: true });
|
|
8859
10259
|
}
|
|
8860
|
-
} else {
|
|
8861
|
-
node.status = "failed";
|
|
8862
|
-
node.result = {
|
|
8863
|
-
success: false,
|
|
8864
|
-
error: `Max retries (${this.config.selfHealing.maxRetries}) exceeded: ${error.message}`,
|
|
8865
|
-
duration: 0,
|
|
8866
|
-
tokensUsed: 0,
|
|
8867
|
-
cost: 0
|
|
8868
|
-
};
|
|
8869
|
-
this.notifyStateChange();
|
|
8870
10260
|
}
|
|
10261
|
+
node.status = "failed";
|
|
10262
|
+
node.result = {
|
|
10263
|
+
success: false,
|
|
10264
|
+
error: error.message,
|
|
10265
|
+
duration: 0,
|
|
10266
|
+
tokensUsed: 0,
|
|
10267
|
+
cost: 0
|
|
10268
|
+
};
|
|
10269
|
+
this.notifyStateChange();
|
|
8871
10270
|
}
|
|
8872
10271
|
async spawnAgent(config) {
|
|
8873
10272
|
if (!this.ctx) {
|
|
8874
10273
|
throw new Error("Orchestrator not initialized");
|
|
8875
10274
|
}
|
|
10275
|
+
if (this.budgetExceeded) {
|
|
10276
|
+
throw new Error("Budget exceeded — cannot spawn new agents");
|
|
10277
|
+
}
|
|
8876
10278
|
const agentId = `agent-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
8877
|
-
const
|
|
10279
|
+
const modelConfig = config.model || this.configManager.getModelForRole(config.role);
|
|
10280
|
+
const slashIndex = modelConfig.indexOf("/");
|
|
10281
|
+
const provider = slashIndex > -1 ? modelConfig.slice(0, slashIndex) : modelConfig;
|
|
10282
|
+
const modelName = slashIndex > -1 ? modelConfig.slice(slashIndex + 1) : modelConfig;
|
|
10283
|
+
const roleEmoji = this.configManager.getRoleEmoji(config.role);
|
|
10284
|
+
const title = `${roleEmoji} ${this.configManager.getRoleDisplayName(config.role)} — ${modelConfig}`;
|
|
8878
10285
|
const session = await this.ctx.session.create({
|
|
8879
|
-
title
|
|
10286
|
+
title,
|
|
10287
|
+
agent: "build",
|
|
10288
|
+
model: modelName ? { providerID: provider, id: modelName } : undefined
|
|
8880
10289
|
});
|
|
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
|
-
}
|
|
8898
10290
|
const agent = {
|
|
8899
10291
|
id: agentId,
|
|
8900
|
-
name:
|
|
10292
|
+
name: title,
|
|
8901
10293
|
role: config.role,
|
|
8902
10294
|
status: "idle",
|
|
8903
10295
|
model: {
|
|
@@ -8922,6 +10314,11 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
8922
10314
|
this.agents.set(agentId, agent);
|
|
8923
10315
|
this.emit("agent:spawned", agent);
|
|
8924
10316
|
this.notifyStateChange();
|
|
10317
|
+
this.enforceAgentLimit();
|
|
10318
|
+
const monitor = this.getOrCreateHealthMonitor();
|
|
10319
|
+
if (!monitor.isActive()) {
|
|
10320
|
+
monitor.start(() => Array.from(this.agents.values()));
|
|
10321
|
+
}
|
|
8925
10322
|
return agent;
|
|
8926
10323
|
}
|
|
8927
10324
|
async terminateAgent(agentId) {
|
|
@@ -8931,6 +10328,9 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
8931
10328
|
this.agents.delete(agentId);
|
|
8932
10329
|
this.emit("agent:terminated", agent);
|
|
8933
10330
|
this.notifyStateChange();
|
|
10331
|
+
if (this._healthMonitor && this.agents.size === 0) {
|
|
10332
|
+
this._healthMonitor.stop();
|
|
10333
|
+
}
|
|
8934
10334
|
}
|
|
8935
10335
|
}
|
|
8936
10336
|
analyzeComplexity(task) {
|
|
@@ -8960,26 +10360,7 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
8960
10360
|
};
|
|
8961
10361
|
}
|
|
8962
10362
|
selectModel(role, complexity) {
|
|
8963
|
-
|
|
8964
|
-
const [provider, ...modelParts] = configModel.split("/");
|
|
8965
|
-
const model = modelParts.join("/");
|
|
8966
|
-
const budgetRemaining = this.budget.maxTotalCost - this.totalSpent;
|
|
8967
|
-
if (budgetRemaining < 1 && configModel !== "opencode/minimax-m2.5-free") {
|
|
8968
|
-
return {
|
|
8969
|
-
provider: "opencode",
|
|
8970
|
-
model: "minimax-m2.5-free",
|
|
8971
|
-
estimatedCost: 0,
|
|
8972
|
-
estimatedQuality: 0.5,
|
|
8973
|
-
reasoning: "Budget constrained, using free tier"
|
|
8974
|
-
};
|
|
8975
|
-
}
|
|
8976
|
-
return {
|
|
8977
|
-
provider,
|
|
8978
|
-
model,
|
|
8979
|
-
estimatedCost: this.estimateModelCost(model),
|
|
8980
|
-
estimatedQuality: this.estimateModelQuality(model),
|
|
8981
|
-
reasoning: `Configured model for ${role}`
|
|
8982
|
-
};
|
|
10363
|
+
return this.selectBestModel(role, complexity);
|
|
8983
10364
|
}
|
|
8984
10365
|
estimateModelCost(model) {
|
|
8985
10366
|
const costs = {
|
|
@@ -9005,12 +10386,72 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
9005
10386
|
};
|
|
9006
10387
|
return quality[model] || 0.6;
|
|
9007
10388
|
}
|
|
10389
|
+
estimateModelSpeed(model) {
|
|
10390
|
+
const speeds = {
|
|
10391
|
+
"claude-opus-4-7": 0.4,
|
|
10392
|
+
"claude-sonnet-4-6": 0.7,
|
|
10393
|
+
"gpt-5": 0.6,
|
|
10394
|
+
"claude-haiku-4-5": 0.9,
|
|
10395
|
+
"gemini-2.5-flash": 0.95,
|
|
10396
|
+
"gpt-5-mini": 0.85,
|
|
10397
|
+
"minimax-m2.5-free": 0.8
|
|
10398
|
+
};
|
|
10399
|
+
return speeds[model] || 0.5;
|
|
10400
|
+
}
|
|
10401
|
+
scoreModel(modelId, role, complexity) {
|
|
10402
|
+
const [provider, ...parts] = modelId.split("/");
|
|
10403
|
+
const model = parts.join("/");
|
|
10404
|
+
const cost = this.estimateModelCost(model);
|
|
10405
|
+
const quality = this.estimateModelQuality(model);
|
|
10406
|
+
const maxCost = 15;
|
|
10407
|
+
const costScore = 1 - cost / maxCost;
|
|
10408
|
+
const speedScore = this.estimateModelSpeed(model);
|
|
10409
|
+
const qualityWeight = complexity.overall > 70 ? 0.6 : complexity.overall > 40 ? 0.4 : 0.2;
|
|
10410
|
+
const costWeight = 1 - qualityWeight;
|
|
10411
|
+
const overallScore = quality * qualityWeight + costScore * costWeight;
|
|
10412
|
+
return {
|
|
10413
|
+
model,
|
|
10414
|
+
provider,
|
|
10415
|
+
costScore,
|
|
10416
|
+
qualityScore: quality,
|
|
10417
|
+
speedScore,
|
|
10418
|
+
overallScore,
|
|
10419
|
+
reasoning: `Score: ${overallScore.toFixed(2)} (quality: ${quality.toFixed(2)}, cost: ${costScore.toFixed(2)}, speed: ${speedScore.toFixed(2)})`
|
|
10420
|
+
};
|
|
10421
|
+
}
|
|
10422
|
+
selectBestModel(role, complexity) {
|
|
10423
|
+
const configModel = this.configManager.getModelForRole(role);
|
|
10424
|
+
const candidates = [
|
|
10425
|
+
configModel,
|
|
10426
|
+
"anthropic/claude-sonnet-4-6",
|
|
10427
|
+
"anthropic/claude-haiku-4-5",
|
|
10428
|
+
"openai/gpt-5-mini",
|
|
10429
|
+
"google/gemini-2.5-flash",
|
|
10430
|
+
"opencode/minimax-m2.5-free"
|
|
10431
|
+
];
|
|
10432
|
+
const unique = [...new Set(candidates)];
|
|
10433
|
+
const scored = unique.map((m) => this.scoreModel(m, role, complexity));
|
|
10434
|
+
const budgetRemaining = this.budget.maxTotalCost - this.totalSpent;
|
|
10435
|
+
const affordable = scored.filter((s) => {
|
|
10436
|
+
const cost = this.estimateModelCost(s.model);
|
|
10437
|
+
return cost <= budgetRemaining || cost === 0;
|
|
10438
|
+
});
|
|
10439
|
+
const best = (affordable.length > 0 ? affordable : scored).sort((a, b) => b.overallScore - a.overallScore)[0];
|
|
10440
|
+
return {
|
|
10441
|
+
provider: best.provider,
|
|
10442
|
+
model: best.model,
|
|
10443
|
+
estimatedCost: this.estimateModelCost(best.model),
|
|
10444
|
+
estimatedQuality: best.qualityScore,
|
|
10445
|
+
reasoning: best.reasoning
|
|
10446
|
+
};
|
|
10447
|
+
}
|
|
9008
10448
|
trackCost(agentId, model, cost, tokens) {
|
|
9009
10449
|
this.totalSpent += cost;
|
|
9010
10450
|
const agentCost = this.costByAgent.get(agentId) || 0;
|
|
9011
10451
|
this.costByAgent.set(agentId, agentCost + cost);
|
|
9012
10452
|
const modelCost = this.costByModel.get(model) || 0;
|
|
9013
10453
|
this.costByModel.set(model, modelCost + cost);
|
|
10454
|
+
this.costHistory.push({ timestamp: Date.now(), cost, agentId });
|
|
9014
10455
|
this.checkBudget();
|
|
9015
10456
|
this.notifyStateChange();
|
|
9016
10457
|
}
|
|
@@ -9019,8 +10460,12 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
9019
10460
|
const remainingPercent = remaining / this.budget.maxTotalCost;
|
|
9020
10461
|
if (remainingPercent <= this.config.budget.alertThreshold) {
|
|
9021
10462
|
this.emit("budget:alert", { remaining, remainingPercent });
|
|
10463
|
+
if (this.notifications?.isEnabled()) {
|
|
10464
|
+
this.notifications.notify({ title: "Nexus: Budget Alert", body: `Budget low: $${remaining.toFixed(2)} remaining (${(remainingPercent * 100).toFixed(1)}%)`, sound: true });
|
|
10465
|
+
}
|
|
9022
10466
|
}
|
|
9023
|
-
if (this.budget.hardLimit && remaining <= 0) {
|
|
10467
|
+
if (this.budget.hardLimit && remaining <= 0 && !this.budgetExceeded) {
|
|
10468
|
+
this.budgetExceeded = true;
|
|
9024
10469
|
this.emit("budget:exceeded", { totalSpent: this.totalSpent });
|
|
9025
10470
|
this.pause();
|
|
9026
10471
|
}
|
|
@@ -9028,12 +10473,15 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
9028
10473
|
publish(topic, message) {
|
|
9029
10474
|
const fullMessage = {
|
|
9030
10475
|
...message,
|
|
10476
|
+
topic: message.topic ?? topic,
|
|
9031
10477
|
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
9032
10478
|
timestamp: new Date
|
|
9033
10479
|
};
|
|
9034
10480
|
this.messageQueue.push(fullMessage);
|
|
10481
|
+
this.messageStore.add(fullMessage);
|
|
9035
10482
|
const handlers = this.subscribers.get(topic) || [];
|
|
9036
10483
|
handlers.forEach((handler) => handler(fullMessage));
|
|
10484
|
+
this.messageRouter.route(fullMessage);
|
|
9037
10485
|
}
|
|
9038
10486
|
subscribe(topic, handler) {
|
|
9039
10487
|
const handlers = this.subscribers.get(topic) || [];
|
|
@@ -9046,29 +10494,27 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
9046
10494
|
};
|
|
9047
10495
|
}
|
|
9048
10496
|
setMemory(scope, key, value, author) {
|
|
9049
|
-
const entry = {
|
|
9050
|
-
id: `mem-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
10497
|
+
const entry = this.memoryStore.set({
|
|
9051
10498
|
key,
|
|
9052
10499
|
value,
|
|
9053
10500
|
scope,
|
|
9054
10501
|
author,
|
|
9055
|
-
timestamp: new Date,
|
|
9056
10502
|
confidence: 1,
|
|
9057
10503
|
tags: []
|
|
9058
|
-
};
|
|
9059
|
-
this.memory.set(`${scope}:${key}`, entry);
|
|
10504
|
+
});
|
|
9060
10505
|
this.emit("memory:set", entry);
|
|
9061
10506
|
}
|
|
9062
10507
|
getMemory(scope, key) {
|
|
9063
|
-
return this.
|
|
10508
|
+
return this.memoryStore.get(key, scope) ?? undefined;
|
|
9064
10509
|
}
|
|
9065
10510
|
getStatus(detailed) {
|
|
9066
10511
|
const state = this.getState();
|
|
9067
10512
|
if (detailed)
|
|
9068
|
-
return JSON.stringify(state, null, 2);
|
|
10513
|
+
return JSON.stringify({ ...state, budgetExceeded: this.budgetExceeded }, null, 2);
|
|
9069
10514
|
return JSON.stringify({
|
|
9070
10515
|
running: state.running,
|
|
9071
10516
|
paused: state.paused,
|
|
10517
|
+
budgetExceeded: this.budgetExceeded,
|
|
9072
10518
|
agents: state.agents.length,
|
|
9073
10519
|
tasks: state.tasks.length,
|
|
9074
10520
|
totalCost: state.totalSpent,
|
|
@@ -9099,14 +10545,61 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
9099
10545
|
this.emit("orchestrator:resumed", {});
|
|
9100
10546
|
this.notifyStateChange();
|
|
9101
10547
|
}
|
|
9102
|
-
|
|
10548
|
+
isBudgetExceeded() {
|
|
10549
|
+
return this.budgetExceeded;
|
|
10550
|
+
}
|
|
10551
|
+
resetBudgetExceeded() {
|
|
10552
|
+
this.budgetExceeded = false;
|
|
10553
|
+
}
|
|
10554
|
+
async shutdown() {
|
|
10555
|
+
await this.moduleRegistry.teardownAll();
|
|
10556
|
+
this._healthMonitor?.stop();
|
|
10557
|
+
this.stopDashboard();
|
|
10558
|
+
this.memoryStore.close();
|
|
10559
|
+
if (this.cleanupInterval) {
|
|
10560
|
+
clearInterval(this.cleanupInterval);
|
|
10561
|
+
this.cleanupInterval = null;
|
|
10562
|
+
}
|
|
10563
|
+
if (this.stateChangeTimer) {
|
|
10564
|
+
clearTimeout(this.stateChangeTimer);
|
|
10565
|
+
this.stateChangeTimer = null;
|
|
10566
|
+
}
|
|
9103
10567
|
this.agents.forEach((agent) => {
|
|
9104
10568
|
agent.status = "terminated";
|
|
9105
10569
|
});
|
|
9106
10570
|
this.agents.clear();
|
|
9107
10571
|
this.running = false;
|
|
9108
10572
|
this.emit("orchestrator:shutdown", {});
|
|
9109
|
-
this.
|
|
10573
|
+
this.onStateChange?.();
|
|
10574
|
+
}
|
|
10575
|
+
cleanupStaleData() {
|
|
10576
|
+
const now = Date.now();
|
|
10577
|
+
for (const [id, agent] of this.agents) {
|
|
10578
|
+
if (agent.status === "terminated" && now - agent.lastActivity.getTime() > 3600000) {
|
|
10579
|
+
this.agents.delete(id);
|
|
10580
|
+
}
|
|
10581
|
+
}
|
|
10582
|
+
if (this.costHistory.length > 1000) {
|
|
10583
|
+
this.costHistory = this.costHistory.slice(-500);
|
|
10584
|
+
}
|
|
10585
|
+
}
|
|
10586
|
+
enforceAgentLimit() {
|
|
10587
|
+
const MAX_AGENTS = this.config.maxConcurrency || 10;
|
|
10588
|
+
if (this.agents.size > MAX_AGENTS) {
|
|
10589
|
+
const terminated = [...this.agents.entries()].filter(([_, a]) => a.status === "terminated").sort((a, b) => a[1].lastActivity.getTime() - b[1].lastActivity.getTime());
|
|
10590
|
+
const toRemove = terminated.slice(0, this.agents.size - MAX_AGENTS);
|
|
10591
|
+
for (const [id] of toRemove) {
|
|
10592
|
+
this.agents.delete(id);
|
|
10593
|
+
}
|
|
10594
|
+
}
|
|
10595
|
+
}
|
|
10596
|
+
getOrCreateHealthMonitor() {
|
|
10597
|
+
if (!this._healthMonitor) {
|
|
10598
|
+
this._healthMonitor = new HealthMonitor({
|
|
10599
|
+
checkInterval: this.config.agents.healthCheckInterval
|
|
10600
|
+
});
|
|
10601
|
+
}
|
|
10602
|
+
return this._healthMonitor;
|
|
9110
10603
|
}
|
|
9111
10604
|
collectResults() {
|
|
9112
10605
|
const results = [];
|
|
@@ -9159,12 +10652,90 @@ Files: ${node.task.files.include.join(", ")}`;
|
|
|
9159
10652
|
}
|
|
9160
10653
|
}
|
|
9161
10654
|
|
|
10655
|
+
// src/templates.ts
|
|
10656
|
+
var TEMPLATES = {
|
|
10657
|
+
feature: {
|
|
10658
|
+
name: "Feature Development",
|
|
10659
|
+
description: "Full feature pipeline: architect → coder → reviewer → tester",
|
|
10660
|
+
steps: [
|
|
10661
|
+
{ role: "architect", name: "Design", description: "Design the feature architecture and API", files: { include: ["**/*.ts"], exclude: ["node_modules"] } },
|
|
10662
|
+
{ role: "coder", name: "Implement", description: "Implement the feature according to design", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10663
|
+
{ role: "reviewer", name: "Review", description: "Code review for quality, security, and correctness", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10664
|
+
{ role: "tester", name: "Test", description: "Write and run tests for the feature", files: { include: ["test/**/*.ts"], exclude: ["node_modules"] } }
|
|
10665
|
+
]
|
|
10666
|
+
},
|
|
10667
|
+
bugfix: {
|
|
10668
|
+
name: "Bug Fix",
|
|
10669
|
+
description: "Bug investigation and fix: explorer → coder → reviewer",
|
|
10670
|
+
steps: [
|
|
10671
|
+
{ role: "explorer", name: "Investigate", description: "Investigate the bug root cause", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10672
|
+
{ role: "coder", name: "Fix", description: "Implement the bug fix", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10673
|
+
{ role: "reviewer", name: "Review", description: "Review the fix for correctness", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } }
|
|
10674
|
+
]
|
|
10675
|
+
},
|
|
10676
|
+
refactor: {
|
|
10677
|
+
name: "Refactoring",
|
|
10678
|
+
description: "Code refactoring: architect → coder → reviewer → tester",
|
|
10679
|
+
steps: [
|
|
10680
|
+
{ role: "architect", name: "Plan", description: "Plan the refactoring approach", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10681
|
+
{ role: "coder", name: "Refactor", description: "Execute the refactoring", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10682
|
+
{ role: "reviewer", name: "Review", description: "Review refactored code", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10683
|
+
{ role: "tester", name: "Verify", description: "Run existing tests to ensure no regressions", files: { include: ["test/**/*.ts"], exclude: ["node_modules"] } }
|
|
10684
|
+
]
|
|
10685
|
+
},
|
|
10686
|
+
documentation: {
|
|
10687
|
+
name: "Documentation",
|
|
10688
|
+
description: "Documentation update: explorer → documenter → reviewer",
|
|
10689
|
+
steps: [
|
|
10690
|
+
{ role: "explorer", name: "Analyze", description: "Analyze codebase to understand what needs documentation", files: { include: ["src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10691
|
+
{ role: "documenter", name: "Write", description: "Write documentation", files: { include: ["**/*.md", "src/**/*.ts"], exclude: ["node_modules"] } },
|
|
10692
|
+
{ role: "reviewer", name: "Review", description: "Review documentation for accuracy", files: { include: ["**/*.md"], exclude: ["node_modules"] } }
|
|
10693
|
+
]
|
|
10694
|
+
}
|
|
10695
|
+
};
|
|
10696
|
+
var DEFAULT_COMPLEXITY = {
|
|
10697
|
+
overall: 50,
|
|
10698
|
+
factors: {
|
|
10699
|
+
fileCount: 1,
|
|
10700
|
+
codeLines: 100,
|
|
10701
|
+
dependencyDepth: 1,
|
|
10702
|
+
domainKnowledge: 1,
|
|
10703
|
+
riskLevel: "medium"
|
|
10704
|
+
}
|
|
10705
|
+
};
|
|
10706
|
+
function instantiateTemplate(templateName, baseDir) {
|
|
10707
|
+
const template = TEMPLATES[templateName];
|
|
10708
|
+
if (!template)
|
|
10709
|
+
throw new Error(`Unknown template: ${templateName}. Available: ${Object.keys(TEMPLATES).join(", ")}`);
|
|
10710
|
+
return template.steps.map((step, index) => ({
|
|
10711
|
+
id: `${templateName}-${index + 1}-${step.role}`,
|
|
10712
|
+
name: `${template.name}: ${step.name}`,
|
|
10713
|
+
description: step.description,
|
|
10714
|
+
requiredRole: step.role,
|
|
10715
|
+
complexity: { ...DEFAULT_COMPLEXITY },
|
|
10716
|
+
files: {
|
|
10717
|
+
include: step.files.include.map((f) => f.startsWith("/") ? f : `${baseDir}/${f}`),
|
|
10718
|
+
exclude: step.files.exclude
|
|
10719
|
+
},
|
|
10720
|
+
dependencies: index > 0 ? [`${templateName}-${index}-${template.steps[index - 1].role}`] : [],
|
|
10721
|
+
timeout: step.timeout,
|
|
10722
|
+
status: "pending",
|
|
10723
|
+
priority: "normal"
|
|
10724
|
+
}));
|
|
10725
|
+
}
|
|
10726
|
+
function listTemplates() {
|
|
10727
|
+
return Object.keys(TEMPLATES);
|
|
10728
|
+
}
|
|
10729
|
+
function getTemplate(name) {
|
|
10730
|
+
return TEMPLATES[name];
|
|
10731
|
+
}
|
|
10732
|
+
|
|
9162
10733
|
// src/index.ts
|
|
9163
10734
|
var src_default = define({
|
|
9164
10735
|
id: "nexus",
|
|
9165
10736
|
async setup(ctx) {
|
|
9166
10737
|
const orchestrator = new NexusOrchestrator;
|
|
9167
|
-
orchestrator.initialize(ctx, () => {
|
|
10738
|
+
await orchestrator.initialize(ctx, () => {
|
|
9168
10739
|
const state = orchestrator.getState();
|
|
9169
10740
|
ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(state))).catch(() => {});
|
|
9170
10741
|
});
|
|
@@ -9232,6 +10803,117 @@ var src_default = define({
|
|
|
9232
10803
|
return { content: JSON.stringify(state, null, 2) };
|
|
9233
10804
|
}
|
|
9234
10805
|
});
|
|
10806
|
+
editor.add({
|
|
10807
|
+
name: "queue",
|
|
10808
|
+
description: "Show current task queue with priorities",
|
|
10809
|
+
input: {
|
|
10810
|
+
type: "object",
|
|
10811
|
+
properties: {},
|
|
10812
|
+
additionalProperties: false
|
|
10813
|
+
},
|
|
10814
|
+
execute: async () => {
|
|
10815
|
+
const state = orchestrator.getState();
|
|
10816
|
+
const tasks = state.tasks || [];
|
|
10817
|
+
return { content: JSON.stringify(tasks, null, 2) };
|
|
10818
|
+
}
|
|
10819
|
+
});
|
|
10820
|
+
editor.add({
|
|
10821
|
+
name: "config.save",
|
|
10822
|
+
description: "Save Nexus config to disk (project or global)",
|
|
10823
|
+
input: {
|
|
10824
|
+
type: "object",
|
|
10825
|
+
properties: {
|
|
10826
|
+
level: { type: "string", enum: ["project", "global"], description: "Config level to save" },
|
|
10827
|
+
basePath: { type: "string", description: "Project root (for project-level, defaults to cwd)" }
|
|
10828
|
+
},
|
|
10829
|
+
required: ["level"],
|
|
10830
|
+
additionalProperties: false
|
|
10831
|
+
},
|
|
10832
|
+
execute: async (input) => {
|
|
10833
|
+
const { level, basePath } = input;
|
|
10834
|
+
orchestrator.configManager.saveConfig(level, basePath || process.cwd());
|
|
10835
|
+
const location = level === "project" ? `${basePath || process.cwd()}/.opencode/nexus.jsonc` : "~/.config/opencode/nexus.jsonc";
|
|
10836
|
+
return { content: `Config saved to ${level} level at ${location}` };
|
|
10837
|
+
}
|
|
10838
|
+
});
|
|
10839
|
+
editor.add({
|
|
10840
|
+
name: "config.init",
|
|
10841
|
+
description: "Initialize default config files for project and/or global",
|
|
10842
|
+
input: {
|
|
10843
|
+
type: "object",
|
|
10844
|
+
properties: {
|
|
10845
|
+
level: { type: "string", enum: ["project", "global", "both"], description: "Which config to initialize" },
|
|
10846
|
+
basePath: { type: "string", description: "Project root" }
|
|
10847
|
+
},
|
|
10848
|
+
required: ["level"],
|
|
10849
|
+
additionalProperties: false
|
|
10850
|
+
},
|
|
10851
|
+
execute: async (input) => {
|
|
10852
|
+
const { level, basePath } = input;
|
|
10853
|
+
const path = basePath || process.cwd();
|
|
10854
|
+
const locations = [];
|
|
10855
|
+
if (level === "project" || level === "both") {
|
|
10856
|
+
orchestrator.configManager.saveProjectConfig(path);
|
|
10857
|
+
locations.push(`${path}/.opencode/nexus.jsonc`);
|
|
10858
|
+
}
|
|
10859
|
+
if (level === "global" || level === "both") {
|
|
10860
|
+
orchestrator.configManager.saveGlobalConfig();
|
|
10861
|
+
locations.push("~/.config/opencode/nexus.jsonc");
|
|
10862
|
+
}
|
|
10863
|
+
return { content: `Config initialized at ${level} level(s): ${locations.join(", ")}` };
|
|
10864
|
+
}
|
|
10865
|
+
});
|
|
10866
|
+
editor.add({
|
|
10867
|
+
name: "preset",
|
|
10868
|
+
description: "Apply a preset configuration",
|
|
10869
|
+
input: {
|
|
10870
|
+
type: "object",
|
|
10871
|
+
properties: {
|
|
10872
|
+
name: { type: "string", description: "Preset name (minimal, balanced, enterprise, cost-optimized)" }
|
|
10873
|
+
},
|
|
10874
|
+
required: ["name"],
|
|
10875
|
+
additionalProperties: false
|
|
10876
|
+
},
|
|
10877
|
+
execute: async (input) => {
|
|
10878
|
+
const { name } = input;
|
|
10879
|
+
try {
|
|
10880
|
+
orchestrator.configManager.applyPreset(name);
|
|
10881
|
+
return { content: `Applied preset: ${PRESETS[name]?.name || name}` };
|
|
10882
|
+
} catch (error) {
|
|
10883
|
+
return { content: `Error: ${error.message}` };
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10886
|
+
});
|
|
10887
|
+
editor.add({
|
|
10888
|
+
name: "dashboard.start",
|
|
10889
|
+
description: "Start the web dashboard server",
|
|
10890
|
+
input: {
|
|
10891
|
+
type: "object",
|
|
10892
|
+
properties: {
|
|
10893
|
+
port: { type: "number", description: "Port (default: 4747)" },
|
|
10894
|
+
host: { type: "string", description: "Host (default: 127.0.0.1)" }
|
|
10895
|
+
},
|
|
10896
|
+
additionalProperties: false
|
|
10897
|
+
},
|
|
10898
|
+
execute: async (input) => {
|
|
10899
|
+
const { port, host } = input;
|
|
10900
|
+
orchestrator.startDashboard(port, host);
|
|
10901
|
+
return { content: `Dashboard started at http://${host || "127.0.0.1"}:${port || 4747}` };
|
|
10902
|
+
}
|
|
10903
|
+
});
|
|
10904
|
+
editor.add({
|
|
10905
|
+
name: "dashboard.stop",
|
|
10906
|
+
description: "Stop the web dashboard server",
|
|
10907
|
+
input: {
|
|
10908
|
+
type: "object",
|
|
10909
|
+
properties: {},
|
|
10910
|
+
additionalProperties: false
|
|
10911
|
+
},
|
|
10912
|
+
execute: async () => {
|
|
10913
|
+
orchestrator.stopDashboard();
|
|
10914
|
+
return { content: "Dashboard stopped" };
|
|
10915
|
+
}
|
|
10916
|
+
});
|
|
9235
10917
|
editor.add({
|
|
9236
10918
|
name: "spawn",
|
|
9237
10919
|
description: "Spawn a sub-agent for a task",
|
|
@@ -9253,13 +10935,80 @@ var src_default = define({
|
|
|
9253
10935
|
sessionID: agent.sessionID,
|
|
9254
10936
|
text: task
|
|
9255
10937
|
});
|
|
10938
|
+
const complexity = orchestrator.analyzeComplexity({
|
|
10939
|
+
id: `spawn-${Date.now()}`,
|
|
10940
|
+
name: task,
|
|
10941
|
+
description: task,
|
|
10942
|
+
files: { include: [] },
|
|
10943
|
+
dependencies: [],
|
|
10944
|
+
requiredRole: role,
|
|
10945
|
+
complexity: { overall: 0, factors: { fileCount: 0, codeLines: 0, dependencyDepth: 0, domainKnowledge: 0, riskLevel: "low" } },
|
|
10946
|
+
priority: "normal",
|
|
10947
|
+
status: "running"
|
|
10948
|
+
});
|
|
10949
|
+
agent.complexity = complexity;
|
|
10950
|
+
const modelSelection = orchestrator.selectModel(role, complexity);
|
|
9256
10951
|
await ctx.storage.set("orchestrator-state", JSON.parse(JSON.stringify(orchestrator.getState())));
|
|
9257
|
-
|
|
10952
|
+
const taskPreview = task.length > 80 ? task.substring(0, 77) + "..." : task;
|
|
10953
|
+
const output = [
|
|
10954
|
+
`${agent.name}`,
|
|
10955
|
+
`\uD83D\uDCCB Task: ${taskPreview}`,
|
|
10956
|
+
`\uD83D\uDCCA Complexity: ${complexity.overall}/100 (${complexity.factors.riskLevel} risk)`,
|
|
10957
|
+
`\uD83E\uDD16 Model reasoning: ${modelSelection.reasoning}`,
|
|
10958
|
+
`\uD83D\uDCCE Session: ${agent.sessionID}`
|
|
10959
|
+
].join(`
|
|
10960
|
+
`);
|
|
10961
|
+
return { content: output };
|
|
9258
10962
|
} catch (error) {
|
|
9259
10963
|
return { content: `Failed to spawn agent: ${error.message}` };
|
|
9260
10964
|
}
|
|
9261
10965
|
}
|
|
9262
10966
|
});
|
|
10967
|
+
editor.add({
|
|
10968
|
+
name: "template",
|
|
10969
|
+
description: "List or instantiate task templates",
|
|
10970
|
+
input: {
|
|
10971
|
+
type: "object",
|
|
10972
|
+
properties: {
|
|
10973
|
+
name: { type: "string", description: "Template name to instantiate (or 'list' to show all)" },
|
|
10974
|
+
baseDir: { type: "string", description: "Base directory for file paths" }
|
|
10975
|
+
},
|
|
10976
|
+
additionalProperties: false
|
|
10977
|
+
},
|
|
10978
|
+
execute: async (input) => {
|
|
10979
|
+
const { name, baseDir } = input;
|
|
10980
|
+
if (!name || name === "list") {
|
|
10981
|
+
const templates = listTemplates();
|
|
10982
|
+
return { content: templates.map((t) => `${t}: ${TEMPLATES[t].description}`).join(`
|
|
10983
|
+
`) };
|
|
10984
|
+
}
|
|
10985
|
+
try {
|
|
10986
|
+
const tasks = instantiateTemplate(name, baseDir || process.cwd());
|
|
10987
|
+
return { content: JSON.stringify(tasks, null, 2) };
|
|
10988
|
+
} catch (error) {
|
|
10989
|
+
return { content: `Error: ${error.message}` };
|
|
10990
|
+
}
|
|
10991
|
+
}
|
|
10992
|
+
});
|
|
10993
|
+
editor.add({
|
|
10994
|
+
name: "security.scan",
|
|
10995
|
+
description: "Scan content for security issues",
|
|
10996
|
+
input: {
|
|
10997
|
+
type: "object",
|
|
10998
|
+
properties: {
|
|
10999
|
+
content: { type: "string", description: "Code content to scan" },
|
|
11000
|
+
filename: { type: "string", description: "Filename for context" }
|
|
11001
|
+
},
|
|
11002
|
+
required: ["content"],
|
|
11003
|
+
additionalProperties: false
|
|
11004
|
+
},
|
|
11005
|
+
execute: async (input) => {
|
|
11006
|
+
const { content, filename } = input;
|
|
11007
|
+
const issues = orchestrator.securityScanner.scanContent(content, filename || "unknown");
|
|
11008
|
+
const result = orchestrator.securityScanner.getResult();
|
|
11009
|
+
return { content: JSON.stringify({ issues: issues.length, score: result.score, details: issues }, null, 2) };
|
|
11010
|
+
}
|
|
11011
|
+
});
|
|
9263
11012
|
});
|
|
9264
11013
|
await ctx.session.hook("prompt", (event) => {
|
|
9265
11014
|
if (event.prompt.text.startsWith("/nexus")) {
|
|
@@ -9274,7 +11023,21 @@ var src_default = define({
|
|
|
9274
11023
|
});
|
|
9275
11024
|
export {
|
|
9276
11025
|
DEFAULT_CONFIG,
|
|
11026
|
+
HealthMonitor,
|
|
11027
|
+
LearningModule,
|
|
11028
|
+
MessageRouter,
|
|
11029
|
+
MessageStore,
|
|
11030
|
+
ModuleRegistry,
|
|
9277
11031
|
NexusConfigManager,
|
|
9278
11032
|
NexusOrchestrator,
|
|
9279
|
-
|
|
11033
|
+
PRESETS,
|
|
11034
|
+
PersistentMemoryStore,
|
|
11035
|
+
SecurityScanner,
|
|
11036
|
+
StateBroadcaster,
|
|
11037
|
+
TEMPLATES,
|
|
11038
|
+
src_default as default,
|
|
11039
|
+
detectCycles,
|
|
11040
|
+
getTemplate,
|
|
11041
|
+
instantiateTemplate,
|
|
11042
|
+
listTemplates
|
|
9280
11043
|
};
|