@hilbras/omninode 0.7.0 → 0.9.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 +48 -4
- package/dist/cli/index.js +826 -45
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +289 -14
- package/dist/index.js +741 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39,6 +39,11 @@ var ProtocolError = class extends OmniNodeError {
|
|
|
39
39
|
super("PROTOCOL_VIOLATION", message, options);
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
var PlannerError = class extends OmniNodeError {
|
|
43
|
+
constructor(code, message, options = {}) {
|
|
44
|
+
super(code, message, options);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
42
47
|
var MemoryError = class extends OmniNodeError {
|
|
43
48
|
constructor(message, options = {}) {
|
|
44
49
|
super("MEMORY_UNAVAILABLE", message, options);
|
|
@@ -154,13 +159,23 @@ var pipelineConfigSchema = z.object({
|
|
|
154
159
|
role: z.string().optional(),
|
|
155
160
|
steps: z.array(pipelineStepConfigSchema).min(1)
|
|
156
161
|
}).strict();
|
|
162
|
+
var plannerConfigSchema = z.object({
|
|
163
|
+
kind: z.enum(["model", "heuristic"]).default("model"),
|
|
164
|
+
model: z.string().optional(),
|
|
165
|
+
instruction: z.string().optional()
|
|
166
|
+
}).strict();
|
|
157
167
|
var projectConfigSchema = z.object({
|
|
158
168
|
name: z.string().min(1),
|
|
159
169
|
providers: z.array(providerConfigSchema).default([]),
|
|
160
170
|
agents: z.array(agentConfigSchema).default([]),
|
|
161
171
|
roles: z.array(roleConfigSchema).default([]),
|
|
162
172
|
pipelines: z.array(pipelineConfigSchema).default([]),
|
|
163
|
-
|
|
173
|
+
planner: plannerConfigSchema.optional(),
|
|
174
|
+
memory: z.object({
|
|
175
|
+
provider: z.string().min(1).default("local"),
|
|
176
|
+
base_url: z.string().url().optional(),
|
|
177
|
+
api_key_env_var: z.string().min(1).optional()
|
|
178
|
+
}).strict().optional()
|
|
164
179
|
}).strict();
|
|
165
180
|
var appConfigSchema = z.object({
|
|
166
181
|
project: projectConfigSchema,
|
|
@@ -230,7 +245,20 @@ function toAppConfig(yaml) {
|
|
|
230
245
|
agents: yaml.project.agents.map(toAgentConfig),
|
|
231
246
|
roles: yaml.project.roles.map(toRoleDefinition),
|
|
232
247
|
pipelines: yaml.project.pipelines.map(toPipelineDefinition),
|
|
233
|
-
...yaml.project.
|
|
248
|
+
...yaml.project.planner !== void 0 ? {
|
|
249
|
+
planner: {
|
|
250
|
+
kind: yaml.project.planner.kind,
|
|
251
|
+
model: yaml.project.planner.model,
|
|
252
|
+
instruction: yaml.project.planner.instruction
|
|
253
|
+
}
|
|
254
|
+
} : {},
|
|
255
|
+
...yaml.project.memory !== void 0 ? {
|
|
256
|
+
memory: {
|
|
257
|
+
provider: yaml.project.memory.provider,
|
|
258
|
+
baseUrl: yaml.project.memory.base_url,
|
|
259
|
+
apiKeyEnvVar: yaml.project.memory.api_key_env_var
|
|
260
|
+
}
|
|
261
|
+
} : {}
|
|
234
262
|
},
|
|
235
263
|
...yaml.logging !== void 0 ? { logging: yaml.logging } : {}
|
|
236
264
|
};
|
|
@@ -1079,13 +1107,24 @@ var TaskEngine = class {
|
|
|
1079
1107
|
const role = task.role ? this.options.roles.get(task.role) : void 0;
|
|
1080
1108
|
const running = await this.transition(task, "running");
|
|
1081
1109
|
this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
|
|
1110
|
+
let background = running.context?.background;
|
|
1111
|
+
if (this.options.memory) {
|
|
1112
|
+
const memoryContext = await this.options.memory.gatherContext({
|
|
1113
|
+
objective: running.objective,
|
|
1114
|
+
...running.role ? { role: running.role } : {},
|
|
1115
|
+
...running.project ? { project: running.project } : {}
|
|
1116
|
+
});
|
|
1117
|
+
if (memoryContext.length > 0) {
|
|
1118
|
+
background = [memoryContext, background].filter((part) => part && part.length > 0).join("\n\n");
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1082
1121
|
let output;
|
|
1083
1122
|
try {
|
|
1084
1123
|
output = await agent.run({
|
|
1085
1124
|
taskId: running.id,
|
|
1086
1125
|
objective: running.objective,
|
|
1087
1126
|
...role ? { role } : {},
|
|
1088
|
-
...
|
|
1127
|
+
...background !== void 0 ? { context: background } : {}
|
|
1089
1128
|
});
|
|
1090
1129
|
} catch (error) {
|
|
1091
1130
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1103,8 +1142,12 @@ var TaskEngine = class {
|
|
|
1103
1142
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1104
1143
|
};
|
|
1105
1144
|
const finalStatus = output.status === "completed" ? "completed" : "failed";
|
|
1145
|
+
const finished = await this.transition(running, finalStatus, { result });
|
|
1106
1146
|
this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
|
|
1107
|
-
|
|
1147
|
+
if (this.options.memory) {
|
|
1148
|
+
await this.options.memory.recordTaskOutcome(finished);
|
|
1149
|
+
}
|
|
1150
|
+
return finished;
|
|
1108
1151
|
}
|
|
1109
1152
|
async cancel(id) {
|
|
1110
1153
|
const task = await this.mustGet(id);
|
|
@@ -1149,18 +1192,536 @@ function generateTaskId() {
|
|
|
1149
1192
|
return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
1150
1193
|
}
|
|
1151
1194
|
|
|
1195
|
+
// src/memory/local.ts
|
|
1196
|
+
import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
1197
|
+
import { dirname as dirname2 } from "path";
|
|
1198
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
1199
|
+
"the",
|
|
1200
|
+
"and",
|
|
1201
|
+
"for",
|
|
1202
|
+
"with",
|
|
1203
|
+
"this",
|
|
1204
|
+
"that",
|
|
1205
|
+
"from",
|
|
1206
|
+
"into",
|
|
1207
|
+
"was",
|
|
1208
|
+
"were",
|
|
1209
|
+
"have",
|
|
1210
|
+
"has",
|
|
1211
|
+
"had",
|
|
1212
|
+
"are",
|
|
1213
|
+
"but",
|
|
1214
|
+
"not",
|
|
1215
|
+
"you",
|
|
1216
|
+
"his",
|
|
1217
|
+
"her",
|
|
1218
|
+
"its",
|
|
1219
|
+
"their",
|
|
1220
|
+
"them",
|
|
1221
|
+
"then",
|
|
1222
|
+
"than",
|
|
1223
|
+
"when",
|
|
1224
|
+
"what",
|
|
1225
|
+
"which",
|
|
1226
|
+
"will",
|
|
1227
|
+
"would",
|
|
1228
|
+
"been",
|
|
1229
|
+
"were",
|
|
1230
|
+
"also",
|
|
1231
|
+
"because",
|
|
1232
|
+
"about",
|
|
1233
|
+
"after",
|
|
1234
|
+
"before"
|
|
1235
|
+
]);
|
|
1236
|
+
function keywords(text) {
|
|
1237
|
+
return new Set(
|
|
1238
|
+
(text ?? "").toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 4 && !STOPWORDS.has(t))
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
function relevanceScore(queryKeywords, entry) {
|
|
1242
|
+
if (queryKeywords.size === 0) return 0;
|
|
1243
|
+
const entryKeywords = keywords(`${entry.content} ${entry.key}`);
|
|
1244
|
+
let overlap = 0;
|
|
1245
|
+
for (const keyword of queryKeywords) {
|
|
1246
|
+
if (entryKeywords.has(keyword)) overlap += 1;
|
|
1247
|
+
}
|
|
1248
|
+
let tagMatches = 0;
|
|
1249
|
+
for (const tag of entry.tags ?? []) {
|
|
1250
|
+
if (queryKeywords.has(tag.toLowerCase())) tagMatches += 1;
|
|
1251
|
+
}
|
|
1252
|
+
return overlap * 2 + tagMatches * 1.5;
|
|
1253
|
+
}
|
|
1254
|
+
var LocalMemoryProvider = class {
|
|
1255
|
+
name = "local";
|
|
1256
|
+
filePath;
|
|
1257
|
+
queue = Promise.resolve();
|
|
1258
|
+
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
1259
|
+
this.filePath = `${directory}/memory.json`;
|
|
1260
|
+
}
|
|
1261
|
+
async query(query) {
|
|
1262
|
+
return this.synchronized(async () => {
|
|
1263
|
+
const entries = await this.load();
|
|
1264
|
+
const filtered = entries.filter((entry) => query.scope ? entry.scope === query.scope : true).filter(
|
|
1265
|
+
(entry) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry.tags ?? []).includes(tag)) : true
|
|
1266
|
+
);
|
|
1267
|
+
if (!query.text || query.text.trim().length === 0) {
|
|
1268
|
+
return filtered.sort((a, b) => (b.updatedAt ?? b.createdAt ?? "").localeCompare(a.updatedAt ?? a.createdAt ?? "")).slice(0, query.limit ?? 10);
|
|
1269
|
+
}
|
|
1270
|
+
const queryKeywords = keywords(query.text);
|
|
1271
|
+
return filtered.map((entry) => ({ entry, score: relevanceScore(queryKeywords, entry) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10).map(({ entry }) => entry);
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
async write(entry) {
|
|
1275
|
+
return this.synchronized(async () => {
|
|
1276
|
+
const entries = await this.load();
|
|
1277
|
+
const index = entries.findIndex((e) => e.key === entry.key);
|
|
1278
|
+
const stamped = {
|
|
1279
|
+
...entry,
|
|
1280
|
+
createdAt: entry.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1281
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1282
|
+
};
|
|
1283
|
+
if (index >= 0) entries[index] = stamped;
|
|
1284
|
+
else entries.push(stamped);
|
|
1285
|
+
await this.saveAll(entries);
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
async count() {
|
|
1289
|
+
return (await this.load()).length;
|
|
1290
|
+
}
|
|
1291
|
+
synchronized(operation) {
|
|
1292
|
+
const result = this.queue.then(operation, operation);
|
|
1293
|
+
this.queue = result.then(
|
|
1294
|
+
() => void 0,
|
|
1295
|
+
() => void 0
|
|
1296
|
+
);
|
|
1297
|
+
return result;
|
|
1298
|
+
}
|
|
1299
|
+
async load() {
|
|
1300
|
+
try {
|
|
1301
|
+
const raw = await readFile2(this.filePath, "utf8");
|
|
1302
|
+
const parsed = JSON.parse(raw);
|
|
1303
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1304
|
+
} catch {
|
|
1305
|
+
return [];
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
async saveAll(entries) {
|
|
1309
|
+
const tmp = `${this.filePath}.tmp`;
|
|
1310
|
+
await mkdir2(dirname2(this.filePath), { recursive: true });
|
|
1311
|
+
await writeFile2(tmp, `${JSON.stringify(entries, null, 2)}
|
|
1312
|
+
`, "utf8");
|
|
1313
|
+
await rename2(tmp, this.filePath);
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
// src/memory/remembera.ts
|
|
1318
|
+
var RememberaMemoryProvider = class {
|
|
1319
|
+
name = "remembera";
|
|
1320
|
+
baseUrl;
|
|
1321
|
+
apiKeyEnvVar;
|
|
1322
|
+
env;
|
|
1323
|
+
log;
|
|
1324
|
+
constructor(config, log = logger) {
|
|
1325
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
1326
|
+
this.apiKeyEnvVar = config.apiKeyEnvVar;
|
|
1327
|
+
this.env = config.env ?? process.env;
|
|
1328
|
+
this.log = log.child({ component: "remembera" });
|
|
1329
|
+
}
|
|
1330
|
+
headers() {
|
|
1331
|
+
const key = this.apiKeyEnvVar ? resolveApiKey({ name: "remembera", apiKeyEnvVar: this.apiKeyEnvVar }, this.env) : void 0;
|
|
1332
|
+
return key ? { authorization: `Bearer ${key}` } : {};
|
|
1333
|
+
}
|
|
1334
|
+
async query(query) {
|
|
1335
|
+
const { status, body } = await this.request(`${this.baseUrl}/api/memory/query`, query);
|
|
1336
|
+
if (status !== 200) {
|
|
1337
|
+
throw new MemoryError(`Remembera query returned HTTP ${status}.`);
|
|
1338
|
+
}
|
|
1339
|
+
const entries = body.entries;
|
|
1340
|
+
if (!Array.isArray(entries)) {
|
|
1341
|
+
throw new MemoryError("Remembera query response had no entries array.");
|
|
1342
|
+
}
|
|
1343
|
+
return entries.map((entry) => this.toEntry(entry)).filter((entry) => entry !== void 0);
|
|
1344
|
+
}
|
|
1345
|
+
async write(entry) {
|
|
1346
|
+
const { status } = await this.request(`${this.baseUrl}/api/memory`, entry);
|
|
1347
|
+
if (status !== 200 && status !== 201) {
|
|
1348
|
+
throw new MemoryError(`Remembera write returned HTTP ${status}.`);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async request(url, body) {
|
|
1352
|
+
const headers = this.headers();
|
|
1353
|
+
try {
|
|
1354
|
+
return await requestJson(url, {
|
|
1355
|
+
method: "POST",
|
|
1356
|
+
headers,
|
|
1357
|
+
body
|
|
1358
|
+
});
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1361
|
+
throw new MemoryError(`Remembera request to ${url} failed: ${message}`, { cause: error });
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
toEntry(raw) {
|
|
1365
|
+
if (raw === null || typeof raw !== "object") {
|
|
1366
|
+
this.log.warn("Skipping malformed Remembera memory entry.");
|
|
1367
|
+
return void 0;
|
|
1368
|
+
}
|
|
1369
|
+
const entry = raw;
|
|
1370
|
+
if (typeof entry.key !== "string" || typeof entry.content !== "string") {
|
|
1371
|
+
this.log.warn("Skipping Remembera memory entry without key/content.");
|
|
1372
|
+
return void 0;
|
|
1373
|
+
}
|
|
1374
|
+
const scope = entry.scope;
|
|
1375
|
+
return {
|
|
1376
|
+
key: entry.key,
|
|
1377
|
+
content: entry.content,
|
|
1378
|
+
scope: scope === "project" || scope === "role" || scope === "task" || scope === "knowledge" ? scope : "knowledge",
|
|
1379
|
+
...Array.isArray(entry.tags) ? { tags: entry.tags.filter((t) => typeof t === "string") } : {},
|
|
1380
|
+
...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
|
|
1381
|
+
...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {}
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
// src/memory/factory.ts
|
|
1387
|
+
function createMemoryProvider(config) {
|
|
1388
|
+
if (!config || config.provider === "local") {
|
|
1389
|
+
return new LocalMemoryProvider();
|
|
1390
|
+
}
|
|
1391
|
+
if (config.provider === "remembera") {
|
|
1392
|
+
if (!config.baseUrl) {
|
|
1393
|
+
throw new ConfigError(
|
|
1394
|
+
"CONFIG_INVALID",
|
|
1395
|
+
`Memory provider "remembera" needs a base_url in omninode.yaml.`
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
return new RememberaMemoryProvider({
|
|
1399
|
+
baseUrl: config.baseUrl,
|
|
1400
|
+
apiKeyEnvVar: config.apiKeyEnvVar
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
throw new ConfigError(
|
|
1404
|
+
"CONFIG_INVALID",
|
|
1405
|
+
`Unknown memory provider "${config.provider}". Available: local, remembera.`
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// src/memory/service.ts
|
|
1410
|
+
var MAX_CONTEXT_CHARS = 4e3;
|
|
1411
|
+
var MemoryService = class {
|
|
1412
|
+
constructor(provider, log = logger) {
|
|
1413
|
+
this.provider = provider;
|
|
1414
|
+
this.log = log.child({ component: "memory", provider: provider.name });
|
|
1415
|
+
}
|
|
1416
|
+
provider;
|
|
1417
|
+
log;
|
|
1418
|
+
get providerRef() {
|
|
1419
|
+
return this.provider;
|
|
1420
|
+
}
|
|
1421
|
+
get providerName() {
|
|
1422
|
+
return this.provider.name;
|
|
1423
|
+
}
|
|
1424
|
+
/** Relevant context for a task objective; empty string when nothing matches. */
|
|
1425
|
+
async gatherContext(query) {
|
|
1426
|
+
try {
|
|
1427
|
+
const entries = await this.provider.query({
|
|
1428
|
+
text: query.objective,
|
|
1429
|
+
...query.role ? { tags: [query.role] } : {},
|
|
1430
|
+
limit: 8
|
|
1431
|
+
});
|
|
1432
|
+
if (entries.length === 0) return "";
|
|
1433
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
1434
|
+
for (const entry of entries) {
|
|
1435
|
+
byScope.set(entry.scope, [...byScope.get(entry.scope) ?? [], entry]);
|
|
1436
|
+
}
|
|
1437
|
+
const parts = [];
|
|
1438
|
+
for (const scope of ["project", "task", "role", "knowledge"]) {
|
|
1439
|
+
const scopeEntries = byScope.get(scope) ?? [];
|
|
1440
|
+
if (scopeEntries.length === 0) continue;
|
|
1441
|
+
const lines = scopeEntries.map((entry) => `- ${entry.content}`).join("\n");
|
|
1442
|
+
parts.push(`### Relevant memory (${scope})
|
|
1443
|
+
${lines}`);
|
|
1444
|
+
}
|
|
1445
|
+
const context = parts.join("\n\n");
|
|
1446
|
+
return context.length > MAX_CONTEXT_CHARS ? `${context.slice(0, MAX_CONTEXT_CHARS)}\u2026` : context;
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
this.log.warn(
|
|
1449
|
+
`Memory query failed (continuing without memory): ${error instanceof Error ? error.message : String(error)}`
|
|
1450
|
+
);
|
|
1451
|
+
return "";
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
/** Records a finished task's outcome — best-effort. */
|
|
1455
|
+
async recordTaskOutcome(task) {
|
|
1456
|
+
const outcome = task.result?.summary ?? task.result?.error ?? "(no output)";
|
|
1457
|
+
try {
|
|
1458
|
+
await this.provider.write({
|
|
1459
|
+
key: `task:${task.id}`,
|
|
1460
|
+
scope: "task",
|
|
1461
|
+
content: `Objective: ${task.objective}
|
|
1462
|
+
Status: ${task.status}
|
|
1463
|
+
Outcome: ${outcome}`,
|
|
1464
|
+
tags: ["task", ...task.agent ? [task.agent] : [], ...task.role ? [task.role] : []],
|
|
1465
|
+
createdAt: task.createdAt
|
|
1466
|
+
});
|
|
1467
|
+
const significant = (task.result?.reports ?? []).flatMap(
|
|
1468
|
+
(report) => report.findings.filter((finding) => finding.severity === "high" || finding.severity === "critical").map((finding) => ({ report, finding }))
|
|
1469
|
+
);
|
|
1470
|
+
for (const { report, finding } of significant) {
|
|
1471
|
+
await this.provider.write({
|
|
1472
|
+
key: `finding:${report.id}:${finding.id}`,
|
|
1473
|
+
scope: "project",
|
|
1474
|
+
content: `[${finding.severity}] ${finding.title} \u2014 ${finding.detail}`,
|
|
1475
|
+
tags: ["known-problem", report.agent]
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
this.log.warn(
|
|
1480
|
+
`Memory write failed (continuing): ${error instanceof Error ? error.message : String(error)}`
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1152
1486
|
// src/tasks/engine-factory.ts
|
|
1153
1487
|
function createTaskEngine(config, log = logger, directory) {
|
|
1488
|
+
const agents = buildAgentRegistry(config, log);
|
|
1489
|
+
const roles = buildRoleRegistry(config);
|
|
1154
1490
|
return new TaskEngine({
|
|
1155
|
-
agents
|
|
1156
|
-
roles
|
|
1491
|
+
agents,
|
|
1492
|
+
roles,
|
|
1157
1493
|
store: new FileTaskStore(directory),
|
|
1494
|
+
...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
|
|
1158
1495
|
log
|
|
1159
1496
|
});
|
|
1160
1497
|
}
|
|
1161
1498
|
|
|
1162
1499
|
// src/pipelines/engine.ts
|
|
1500
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
1501
|
+
|
|
1502
|
+
// src/planner/heuristic.ts
|
|
1163
1503
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1504
|
+
var SEVERITY_RANK = {
|
|
1505
|
+
info: 0,
|
|
1506
|
+
low: 1,
|
|
1507
|
+
medium: 2,
|
|
1508
|
+
high: 3,
|
|
1509
|
+
critical: 4
|
|
1510
|
+
};
|
|
1511
|
+
var HeuristicPlanner = class {
|
|
1512
|
+
name = "heuristic";
|
|
1513
|
+
async plan(request) {
|
|
1514
|
+
const steps = [];
|
|
1515
|
+
const findings = request.reports.flatMap(
|
|
1516
|
+
(report) => report.findings.map((finding) => ({ finding, agent: report.agent }))
|
|
1517
|
+
);
|
|
1518
|
+
const recommendations = dedupe(request.reports.flatMap((r) => r.recommendations));
|
|
1519
|
+
for (const [index, text] of recommendations.entries()) {
|
|
1520
|
+
steps.push({
|
|
1521
|
+
id: `step-${steps.length + 1}`,
|
|
1522
|
+
title: truncate(text, 120),
|
|
1523
|
+
order: steps.length + 1,
|
|
1524
|
+
...index > 0 ? { dependsOn: [`step-${steps.length}`] } : {}
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
const significant = findings.filter(({ finding }) => finding.severity ? SEVERITY_RANK[finding.severity] >= SEVERITY_RANK.high : false).sort((a, b) => SEVERITY_RANK[b.finding.severity ?? "info"] - SEVERITY_RANK[a.finding.severity ?? "info"]);
|
|
1528
|
+
for (const { finding } of significant) {
|
|
1529
|
+
steps.push(remediationStep(finding, steps.length + 1));
|
|
1530
|
+
}
|
|
1531
|
+
if (steps.length === 0) {
|
|
1532
|
+
steps.push({
|
|
1533
|
+
id: "step-1",
|
|
1534
|
+
title: "Gather additional information",
|
|
1535
|
+
description: "The research reports contained no actionable recommendations or significant findings. Investigate the objective further before planning implementation work.",
|
|
1536
|
+
order: 1
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
const risks = significant.filter(({ finding }) => finding.severity === "critical").map(({ finding }) => `Critical finding unaddressed until its step completes: ${finding.title}`);
|
|
1540
|
+
return {
|
|
1541
|
+
id: `plan-${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`,
|
|
1542
|
+
objective: request.objective,
|
|
1543
|
+
summary: `Derived from ${request.reports.length} report(s): ${recommendations.length} recommendation step(s), ${significant.length} remediation step(s).`,
|
|
1544
|
+
steps,
|
|
1545
|
+
...risks.length > 0 ? { risks } : {},
|
|
1546
|
+
generatedBy: "heuristic",
|
|
1547
|
+
...request.taskId !== void 0 ? { taskId: request.taskId } : {},
|
|
1548
|
+
...request.pipelineRunId !== void 0 ? { pipelineRunId: request.pipelineRunId } : {},
|
|
1549
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
function remediationStep(finding, order) {
|
|
1554
|
+
return {
|
|
1555
|
+
id: `step-${order}`,
|
|
1556
|
+
title: `Fix: ${truncate(finding.title, 100)}`,
|
|
1557
|
+
description: finding.detail,
|
|
1558
|
+
order,
|
|
1559
|
+
dependsOn: order > 1 ? [`step-${order - 1}`] : [],
|
|
1560
|
+
sourceFindings: [finding.id]
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
function dedupe(texts) {
|
|
1564
|
+
return [...new Set(texts.map((t) => t.trim()).filter((t) => t.length > 0))];
|
|
1565
|
+
}
|
|
1566
|
+
function truncate(text, max) {
|
|
1567
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// src/planner/model.ts
|
|
1571
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1572
|
+
import { z as z2 } from "zod";
|
|
1573
|
+
|
|
1574
|
+
// src/planner/context.ts
|
|
1575
|
+
function buildPlannerContext(request) {
|
|
1576
|
+
const parts = [`# Objective
|
|
1577
|
+
${request.objective}`];
|
|
1578
|
+
if (request.role) {
|
|
1579
|
+
const roleLines = [`# Role
|
|
1580
|
+
${request.role.name} (${request.role.id})`];
|
|
1581
|
+
if (request.role.responsibilities.length > 0) {
|
|
1582
|
+
roleLines.push("Responsibilities:", ...request.role.responsibilities.map((r) => `- ${r}`));
|
|
1583
|
+
}
|
|
1584
|
+
if (request.role.expectedOutputs.length > 0) {
|
|
1585
|
+
roleLines.push("Expected outputs:", ...request.role.expectedOutputs.map((o) => `- ${o}`));
|
|
1586
|
+
}
|
|
1587
|
+
parts.push(roleLines.join("\n"));
|
|
1588
|
+
}
|
|
1589
|
+
if (request.reports.length > 0) {
|
|
1590
|
+
const agents = [...new Set(request.reports.map((r) => r.agent))].join(", ");
|
|
1591
|
+
const findingLines = request.reports.flatMap(
|
|
1592
|
+
(report) => report.findings.map(
|
|
1593
|
+
(finding) => `- [${finding.severity ?? "info"}] ${finding.title} (source: ${report.agent}, finding: ${finding.id})` + (finding.detail ? ` \u2014 ${finding.detail}` : "")
|
|
1594
|
+
)
|
|
1595
|
+
);
|
|
1596
|
+
parts.push(
|
|
1597
|
+
`# Findings (${request.reports.length} report(s) by ${agents})
|
|
1598
|
+
` + (findingLines.length > 0 ? findingLines.join("\n") : "(no findings reported)")
|
|
1599
|
+
);
|
|
1600
|
+
const recommendationLines = request.reports.flatMap(
|
|
1601
|
+
(report) => report.recommendations.map((text) => `- ${text} (source: ${report.agent})`)
|
|
1602
|
+
);
|
|
1603
|
+
if (recommendationLines.length > 0) {
|
|
1604
|
+
parts.push(`# Recommendations
|
|
1605
|
+
${recommendationLines.join("\n")}`);
|
|
1606
|
+
}
|
|
1607
|
+
const summaryLines = request.reports.map((report) => `- [${report.agent}] ${report.summary}`);
|
|
1608
|
+
parts.push(`# Report summaries
|
|
1609
|
+
${summaryLines.join("\n")}`);
|
|
1610
|
+
}
|
|
1611
|
+
if (request.memory && request.memory.length > 0) {
|
|
1612
|
+
parts.push(
|
|
1613
|
+
`# Relevant memory
|
|
1614
|
+
` + request.memory.map((entry) => `- [${entry.scope}] ${entry.content}`).join("\n")
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
if (request.context && request.context.length > 0) {
|
|
1618
|
+
parts.push(`# Research context
|
|
1619
|
+
${request.context}`);
|
|
1620
|
+
}
|
|
1621
|
+
return parts.join("\n\n");
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// src/planner/model.ts
|
|
1625
|
+
var planStepJsonSchema = z2.object({
|
|
1626
|
+
id: z2.string().optional(),
|
|
1627
|
+
title: z2.string().min(1),
|
|
1628
|
+
description: z2.string().optional(),
|
|
1629
|
+
order: z2.number().int().optional(),
|
|
1630
|
+
targets: z2.array(z2.string()).optional(),
|
|
1631
|
+
acceptance_criteria: z2.array(z2.string()).optional(),
|
|
1632
|
+
depends_on: z2.array(z2.string()).optional(),
|
|
1633
|
+
source_findings: z2.array(z2.string()).optional()
|
|
1634
|
+
}).strict();
|
|
1635
|
+
var planJsonSchema = z2.object({
|
|
1636
|
+
summary: z2.string().min(1),
|
|
1637
|
+
steps: z2.array(planStepJsonSchema).min(1),
|
|
1638
|
+
risks: z2.array(z2.string()).optional(),
|
|
1639
|
+
notes: z2.array(z2.string()).optional()
|
|
1640
|
+
}).strict();
|
|
1641
|
+
var ModelPlanner = class {
|
|
1642
|
+
constructor(options) {
|
|
1643
|
+
this.options = options;
|
|
1644
|
+
this.log = (options.log ?? logger).child({ component: "planner", model: options.model });
|
|
1645
|
+
}
|
|
1646
|
+
options;
|
|
1647
|
+
name = "model";
|
|
1648
|
+
log;
|
|
1649
|
+
async plan(request) {
|
|
1650
|
+
const context = buildPlannerContext(request);
|
|
1651
|
+
const messages = [
|
|
1652
|
+
{
|
|
1653
|
+
role: "system",
|
|
1654
|
+
content: 'You are the OmniNode planner. Given the objective and the combined research context, produce a final analysis and a prioritized, actionable implementation plan. Respond with JSON ONLY (no prose, no code fences) matching exactly this shape: {"summary": string, "steps": [{"id": string, "title": string, "description": string, "order": number, "targets": string[], "acceptance_criteria": string[], "source_findings": string[]}], "risks": string[], "notes": string[]}. ' + (this.options.instruction ?? "")
|
|
1655
|
+
},
|
|
1656
|
+
{ role: "user", content: context }
|
|
1657
|
+
];
|
|
1658
|
+
let responseText;
|
|
1659
|
+
try {
|
|
1660
|
+
responseText = await this.options.chat(this.options.model, messages);
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
return this.degrade(request, `planner model call failed: ${describe(error)}`);
|
|
1663
|
+
}
|
|
1664
|
+
const parsed = parsePlanJson(responseText);
|
|
1665
|
+
if (!parsed) {
|
|
1666
|
+
return this.degrade(request, "planner model output was not valid plan JSON");
|
|
1667
|
+
}
|
|
1668
|
+
const plan = {
|
|
1669
|
+
id: `plan-${Date.now().toString(36)}-${randomBytes3(3).toString("hex")}`,
|
|
1670
|
+
objective: request.objective,
|
|
1671
|
+
summary: parsed.summary,
|
|
1672
|
+
steps: parsed.steps,
|
|
1673
|
+
...parsed.risks !== void 0 ? { risks: parsed.risks } : {},
|
|
1674
|
+
...parsed.notes !== void 0 ? { notes: parsed.notes } : {},
|
|
1675
|
+
generatedBy: `model:${this.options.model}`,
|
|
1676
|
+
model: this.options.model,
|
|
1677
|
+
...request.taskId !== void 0 ? { taskId: request.taskId } : {},
|
|
1678
|
+
...request.pipelineRunId !== void 0 ? { pipelineRunId: request.pipelineRunId } : {},
|
|
1679
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1680
|
+
};
|
|
1681
|
+
return plan;
|
|
1682
|
+
}
|
|
1683
|
+
async degrade(request, reason) {
|
|
1684
|
+
if (this.options.fallback) {
|
|
1685
|
+
this.log.warn(`${reason} \u2014 using the heuristic fallback planner.`);
|
|
1686
|
+
return this.options.fallback.plan(request);
|
|
1687
|
+
}
|
|
1688
|
+
throw new PlannerError("PLANNER_FAILED", `Planner failed: ${reason}.`);
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
function parsePlanJson(text) {
|
|
1692
|
+
const unfenced = text.replace(/```(?:json)?/gi, "");
|
|
1693
|
+
const start = unfenced.indexOf("{");
|
|
1694
|
+
const end = unfenced.lastIndexOf("}");
|
|
1695
|
+
if (start < 0 || end <= start) return void 0;
|
|
1696
|
+
try {
|
|
1697
|
+
const json = JSON.parse(unfenced.slice(start, end + 1));
|
|
1698
|
+
const parsed = planJsonSchema.safeParse(json);
|
|
1699
|
+
if (!parsed.success) return void 0;
|
|
1700
|
+
const steps = parsed.data.steps.map((step, index) => ({
|
|
1701
|
+
id: step.id ?? `step-${index + 1}`,
|
|
1702
|
+
title: step.title,
|
|
1703
|
+
description: step.description,
|
|
1704
|
+
order: step.order ?? index + 1,
|
|
1705
|
+
targets: step.targets,
|
|
1706
|
+
acceptanceCriteria: step.acceptance_criteria,
|
|
1707
|
+
dependsOn: step.depends_on,
|
|
1708
|
+
sourceFindings: step.source_findings
|
|
1709
|
+
}));
|
|
1710
|
+
return {
|
|
1711
|
+
summary: parsed.data.summary,
|
|
1712
|
+
steps: steps.sort((a, b) => a.order - b.order),
|
|
1713
|
+
risks: parsed.data.risks,
|
|
1714
|
+
notes: parsed.data.notes
|
|
1715
|
+
};
|
|
1716
|
+
} catch {
|
|
1717
|
+
return void 0;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
function describe(error) {
|
|
1721
|
+
return error instanceof Error ? error.message : String(error);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// src/pipelines/engine.ts
|
|
1164
1725
|
var PipelineEngine = class {
|
|
1165
1726
|
constructor(options) {
|
|
1166
1727
|
this.options = options;
|
|
@@ -1273,7 +1834,7 @@ var PipelineEngine = class {
|
|
|
1273
1834
|
}
|
|
1274
1835
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1275
1836
|
const run = {
|
|
1276
|
-
id: `run-${Date.now().toString(36)}-${
|
|
1837
|
+
id: `run-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
|
|
1277
1838
|
pipelineId: def.id,
|
|
1278
1839
|
status: "running",
|
|
1279
1840
|
startedAt: now,
|
|
@@ -1301,7 +1862,9 @@ var PipelineEngine = class {
|
|
|
1301
1862
|
const ctx = {
|
|
1302
1863
|
objective,
|
|
1303
1864
|
...role ? { role } : {},
|
|
1304
|
-
combinedContext: contextParts.join("\n\n")
|
|
1865
|
+
combinedContext: contextParts.join("\n\n"),
|
|
1866
|
+
reports: [...allReports],
|
|
1867
|
+
pipelineRunId: run.id
|
|
1305
1868
|
};
|
|
1306
1869
|
return this.executeStep(step, ctx);
|
|
1307
1870
|
})
|
|
@@ -1320,6 +1883,7 @@ ${contribution}`);
|
|
|
1320
1883
|
stepId: step.id,
|
|
1321
1884
|
status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : "completed",
|
|
1322
1885
|
...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
|
|
1886
|
+
...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
|
|
1323
1887
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
1324
1888
|
};
|
|
1325
1889
|
run.stepRuns.push(stepRun);
|
|
@@ -1330,6 +1894,7 @@ ${contribution}`);
|
|
|
1330
1894
|
}
|
|
1331
1895
|
const failed = [...outcomes.values()].some((o) => o.status === "failed");
|
|
1332
1896
|
const finalStatus = failed ? "failed" : "completed";
|
|
1897
|
+
const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
|
|
1333
1898
|
let combinedReportId;
|
|
1334
1899
|
if (this.options.reports) {
|
|
1335
1900
|
const taskIds = [...outcomes.values()].flatMap((o) => o.taskIds);
|
|
@@ -1351,7 +1916,8 @@ ${contribution}`);
|
|
|
1351
1916
|
status: finalStatus,
|
|
1352
1917
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1353
1918
|
...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
|
|
1354
|
-
...combinedReportId !== void 0 ? { combinedReportId } : {}
|
|
1919
|
+
...combinedReportId !== void 0 ? { combinedReportId } : {},
|
|
1920
|
+
...planId !== void 0 ? { planId } : {}
|
|
1355
1921
|
};
|
|
1356
1922
|
await this.options.store.save(finished);
|
|
1357
1923
|
this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
|
|
@@ -1461,33 +2027,34 @@ ${contribution}`);
|
|
|
1461
2027
|
};
|
|
1462
2028
|
}
|
|
1463
2029
|
async runPlan(step, ctx) {
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
{
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
try {
|
|
1483
|
-
const planText = await this.getChat()(step.model, messages);
|
|
2030
|
+
const request = {
|
|
2031
|
+
objective: ctx.objective,
|
|
2032
|
+
reports: ctx.reports,
|
|
2033
|
+
...ctx.combinedContext.length > 0 ? { context: ctx.combinedContext } : {},
|
|
2034
|
+
...ctx.role ? { role: ctx.role } : {},
|
|
2035
|
+
...ctx.pipelineRunId !== void 0 ? { pipelineRunId: ctx.pipelineRunId } : {}
|
|
2036
|
+
};
|
|
2037
|
+
let planner;
|
|
2038
|
+
if (step.model) {
|
|
2039
|
+
planner = new ModelPlanner({
|
|
2040
|
+
model: step.model,
|
|
2041
|
+
chat: this.getChat(),
|
|
2042
|
+
fallback: new HeuristicPlanner(),
|
|
2043
|
+
log: this.log
|
|
2044
|
+
});
|
|
2045
|
+
} else if (this.options.planner) {
|
|
2046
|
+
planner = this.options.planner;
|
|
2047
|
+
} else {
|
|
1484
2048
|
return {
|
|
1485
2049
|
status: "completed",
|
|
1486
|
-
summary:
|
|
1487
|
-
contextContribution: planText,
|
|
2050
|
+
summary: "(plan: no model or planner configured \u2014 skipped)",
|
|
1488
2051
|
taskIds: [],
|
|
1489
2052
|
reports: []
|
|
1490
2053
|
};
|
|
2054
|
+
}
|
|
2055
|
+
let plan;
|
|
2056
|
+
try {
|
|
2057
|
+
plan = await planner.plan(request);
|
|
1491
2058
|
} catch (error) {
|
|
1492
2059
|
return {
|
|
1493
2060
|
status: "failed",
|
|
@@ -1497,6 +2064,15 @@ Produce the final plan.`
|
|
|
1497
2064
|
reports: []
|
|
1498
2065
|
};
|
|
1499
2066
|
}
|
|
2067
|
+
await this.options.plans?.save(plan);
|
|
2068
|
+
return {
|
|
2069
|
+
status: "completed",
|
|
2070
|
+
summary: plan.summary,
|
|
2071
|
+
contextContribution: formatPlan(plan),
|
|
2072
|
+
taskIds: [],
|
|
2073
|
+
planId: plan.id,
|
|
2074
|
+
reports: []
|
|
2075
|
+
};
|
|
1500
2076
|
}
|
|
1501
2077
|
getChat() {
|
|
1502
2078
|
if (this.options.chat) return this.options.chat;
|
|
@@ -1506,6 +2082,21 @@ Produce the final plan.`
|
|
|
1506
2082
|
);
|
|
1507
2083
|
}
|
|
1508
2084
|
};
|
|
2085
|
+
function formatPlan(plan) {
|
|
2086
|
+
const lines = [
|
|
2087
|
+
`# Implementation Plan (by ${plan.generatedBy})`,
|
|
2088
|
+
plan.summary,
|
|
2089
|
+
"Steps:",
|
|
2090
|
+
...plan.steps.map((step) => {
|
|
2091
|
+
const targets = step.targets && step.targets.length > 0 ? ` [targets: ${step.targets.join(", ")}]` : "";
|
|
2092
|
+
return `${step.order}. ${step.title}${targets}`;
|
|
2093
|
+
})
|
|
2094
|
+
];
|
|
2095
|
+
if (plan.risks && plan.risks.length > 0) {
|
|
2096
|
+
lines.push("Risks:", ...plan.risks.map((risk) => `- ${risk}`));
|
|
2097
|
+
}
|
|
2098
|
+
return lines.join("\n");
|
|
2099
|
+
}
|
|
1509
2100
|
function createDefaultChatFn(providers) {
|
|
1510
2101
|
return async (modelRef, messages) => {
|
|
1511
2102
|
const separator = modelRef.indexOf(":");
|
|
@@ -1531,8 +2122,8 @@ function createDefaultChatFn(providers) {
|
|
|
1531
2122
|
}
|
|
1532
2123
|
|
|
1533
2124
|
// src/pipelines/store.ts
|
|
1534
|
-
import { mkdir as
|
|
1535
|
-
import { dirname as
|
|
2125
|
+
import { mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
|
|
2126
|
+
import { dirname as dirname3 } from "path";
|
|
1536
2127
|
var FilePipelineRunStore = class {
|
|
1537
2128
|
filePath;
|
|
1538
2129
|
queue = Promise.resolve();
|
|
@@ -1570,7 +2161,7 @@ var FilePipelineRunStore = class {
|
|
|
1570
2161
|
}
|
|
1571
2162
|
async load() {
|
|
1572
2163
|
try {
|
|
1573
|
-
const raw = await
|
|
2164
|
+
const raw = await readFile3(this.filePath, "utf8");
|
|
1574
2165
|
const parsed = JSON.parse(raw);
|
|
1575
2166
|
return Array.isArray(parsed) ? parsed : [];
|
|
1576
2167
|
} catch {
|
|
@@ -1579,16 +2170,93 @@ var FilePipelineRunStore = class {
|
|
|
1579
2170
|
}
|
|
1580
2171
|
async write(runs) {
|
|
1581
2172
|
const tmp = `${this.filePath}.tmp`;
|
|
1582
|
-
await
|
|
1583
|
-
await
|
|
2173
|
+
await mkdir3(dirname3(this.filePath), { recursive: true });
|
|
2174
|
+
await writeFile3(tmp, `${JSON.stringify(runs, null, 2)}
|
|
1584
2175
|
`, "utf8");
|
|
1585
|
-
await
|
|
2176
|
+
await rename3(tmp, this.filePath);
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
2179
|
+
|
|
2180
|
+
// src/planner/store.ts
|
|
2181
|
+
import { mkdir as mkdir4, readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
2182
|
+
import { dirname as dirname4 } from "path";
|
|
2183
|
+
var FilePlanStore = class {
|
|
2184
|
+
filePath;
|
|
2185
|
+
queue = Promise.resolve();
|
|
2186
|
+
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
2187
|
+
this.filePath = `${directory}/plans.json`;
|
|
2188
|
+
}
|
|
2189
|
+
async save(plan) {
|
|
2190
|
+
return this.synchronized(async () => {
|
|
2191
|
+
const plans = await this.load();
|
|
2192
|
+
const index = plans.findIndex((p) => p.id === plan.id);
|
|
2193
|
+
if (index >= 0) plans[index] = plan;
|
|
2194
|
+
else plans.push(plan);
|
|
2195
|
+
await this.write(plans);
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
async get(id) {
|
|
2199
|
+
return this.synchronized(async () => {
|
|
2200
|
+
const plans = await this.load();
|
|
2201
|
+
return plans.find((p) => p.id === id);
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
async list(filter = {}) {
|
|
2205
|
+
return this.synchronized(async () => {
|
|
2206
|
+
const plans = await this.load();
|
|
2207
|
+
return plans.filter((plan) => filter.pipelineRunId ? plan.pipelineRunId === filter.pipelineRunId : true).filter((plan) => filter.taskId ? plan.taskId === filter.taskId : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
synchronized(operation) {
|
|
2211
|
+
const result = this.queue.then(operation, operation);
|
|
2212
|
+
this.queue = result.then(
|
|
2213
|
+
() => void 0,
|
|
2214
|
+
() => void 0
|
|
2215
|
+
);
|
|
2216
|
+
return result;
|
|
2217
|
+
}
|
|
2218
|
+
async load() {
|
|
2219
|
+
try {
|
|
2220
|
+
const raw = await readFile4(this.filePath, "utf8");
|
|
2221
|
+
const parsed = JSON.parse(raw);
|
|
2222
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
2223
|
+
} catch {
|
|
2224
|
+
return [];
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
async write(plans) {
|
|
2228
|
+
const tmp = `${this.filePath}.tmp`;
|
|
2229
|
+
await mkdir4(dirname4(this.filePath), { recursive: true });
|
|
2230
|
+
await writeFile4(tmp, `${JSON.stringify(plans, null, 2)}
|
|
2231
|
+
`, "utf8");
|
|
2232
|
+
await rename4(tmp, this.filePath);
|
|
1586
2233
|
}
|
|
1587
2234
|
};
|
|
1588
2235
|
|
|
2236
|
+
// src/planner/factory.ts
|
|
2237
|
+
function buildPlanner(plannerConfig, chat, log = logger) {
|
|
2238
|
+
if (!plannerConfig) return void 0;
|
|
2239
|
+
if (plannerConfig.kind === "heuristic") {
|
|
2240
|
+
return new HeuristicPlanner();
|
|
2241
|
+
}
|
|
2242
|
+
if (!plannerConfig.model) {
|
|
2243
|
+
throw new ConfigError(
|
|
2244
|
+
"CONFIG_INVALID",
|
|
2245
|
+
'Planner kind "model" needs a model reference ("provider:model-id") in omninode.yaml.'
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
return new ModelPlanner({
|
|
2249
|
+
model: plannerConfig.model,
|
|
2250
|
+
chat,
|
|
2251
|
+
...plannerConfig.instruction !== void 0 ? { instruction: plannerConfig.instruction } : {},
|
|
2252
|
+
fallback: new HeuristicPlanner(),
|
|
2253
|
+
log
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
|
|
1589
2257
|
// src/reports/aggregate.ts
|
|
1590
|
-
import { randomBytes as
|
|
1591
|
-
var
|
|
2258
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
2259
|
+
var SEVERITY_RANK2 = {
|
|
1592
2260
|
info: 0,
|
|
1593
2261
|
low: 1,
|
|
1594
2262
|
medium: 2,
|
|
@@ -1611,8 +2279,8 @@ function aggregateReports(reports, options = {}) {
|
|
|
1611
2279
|
const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
1612
2280
|
const summary = `${reports.length} report(s) from ${sources.length > 0 ? sources.join(", ") : "no agents"}: ${findingGroups.length} unique finding group(s) (${totalRawFindings} raw), ${recommendationGroups.length} recommendation group(s).`;
|
|
1613
2281
|
return {
|
|
1614
|
-
id: `combined-${Date.now().toString(36)}-${
|
|
1615
|
-
title: options.objective !== void 0 ?
|
|
2282
|
+
id: `combined-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
|
|
2283
|
+
title: options.objective !== void 0 ? truncate2(options.objective, 120) : "Combined report",
|
|
1616
2284
|
...options.objective !== void 0 ? { objective: options.objective } : {},
|
|
1617
2285
|
...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
|
|
1618
2286
|
taskIds: options.taskIds ?? [],
|
|
@@ -1625,7 +2293,7 @@ function aggregateReports(reports, options = {}) {
|
|
|
1625
2293
|
};
|
|
1626
2294
|
}
|
|
1627
2295
|
function rank(finding) {
|
|
1628
|
-
return finding.severity ?
|
|
2296
|
+
return finding.severity ? SEVERITY_RANK2[finding.severity] : -1;
|
|
1629
2297
|
}
|
|
1630
2298
|
function mergeFinding(groups, finding, report, threshold) {
|
|
1631
2299
|
const source = { agent: report.agent, reportId: report.id };
|
|
@@ -1635,7 +2303,7 @@ function mergeFinding(groups, finding, report, threshold) {
|
|
|
1635
2303
|
group.details.push(finding.detail);
|
|
1636
2304
|
group.sources.push(source);
|
|
1637
2305
|
group.evidence.push(...finding.evidence ?? []);
|
|
1638
|
-
if (finding.severity && (!group.severity ||
|
|
2306
|
+
if (finding.severity && (!group.severity || SEVERITY_RANK2[finding.severity] > SEVERITY_RANK2[group.severity])) {
|
|
1639
2307
|
group.severity = finding.severity;
|
|
1640
2308
|
}
|
|
1641
2309
|
return;
|
|
@@ -1675,7 +2343,7 @@ function tokens(text) {
|
|
|
1675
2343
|
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3)
|
|
1676
2344
|
);
|
|
1677
2345
|
}
|
|
1678
|
-
function
|
|
2346
|
+
function truncate2(text, max) {
|
|
1679
2347
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
1680
2348
|
}
|
|
1681
2349
|
|
|
@@ -1719,14 +2387,14 @@ function extractReportFromText(raw, meta) {
|
|
|
1719
2387
|
const nonEmpty = trimmed.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
1720
2388
|
const proseLine = nonEmpty.find((line) => !line.startsWith("#") && !BULLET.test(line)) ?? nonEmpty[0] ?? "";
|
|
1721
2389
|
const cleaned = proseLine.replace(/^#{1,6}\s+/, "").replace(BULLET, "$1");
|
|
1722
|
-
const summary = cleaned.length > 0 ?
|
|
2390
|
+
const summary = cleaned.length > 0 ? truncate3(cleaned, 200) : "(no output)";
|
|
1723
2391
|
const sections = parseSections(trimmed);
|
|
1724
2392
|
const findingTexts = collectBullets(sections, [/finding/, /issue/, /problem/, /vulnerab/]);
|
|
1725
2393
|
const recommendationTexts = collectBullets(sections, [/recommend/, /suggestion/, /next step/, /action/]);
|
|
1726
2394
|
const evidenceTexts = collectBullets(sections, [/evidence/, /proof/, /log/]);
|
|
1727
2395
|
const findings = findingTexts.map((text, index) => ({
|
|
1728
2396
|
id: `${meta.taskId}-f-${index + 1}`,
|
|
1729
|
-
title:
|
|
2397
|
+
title: truncate3(text, 120),
|
|
1730
2398
|
detail: text,
|
|
1731
2399
|
severity: guessSeverity(text)
|
|
1732
2400
|
}));
|
|
@@ -1737,12 +2405,12 @@ function extractReportFromText(raw, meta) {
|
|
|
1737
2405
|
...meta.model !== void 0 ? { model: meta.model } : {},
|
|
1738
2406
|
summary,
|
|
1739
2407
|
findings,
|
|
1740
|
-
recommendations: recommendationTexts.map((t) =>
|
|
2408
|
+
recommendations: recommendationTexts.map((t) => truncate3(t, 200)),
|
|
1741
2409
|
metadata: evidenceTexts.length > 0 ? { evidence: evidenceTexts } : void 0,
|
|
1742
2410
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1743
2411
|
};
|
|
1744
2412
|
}
|
|
1745
|
-
function
|
|
2413
|
+
function truncate3(text, max) {
|
|
1746
2414
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
1747
2415
|
}
|
|
1748
2416
|
|
|
@@ -1797,8 +2465,8 @@ var ReportService = class {
|
|
|
1797
2465
|
};
|
|
1798
2466
|
|
|
1799
2467
|
// src/reports/store.ts
|
|
1800
|
-
import { mkdir as
|
|
1801
|
-
import { dirname as
|
|
2468
|
+
import { mkdir as mkdir5, readFile as readFile5, rename as rename5, writeFile as writeFile5 } from "fs/promises";
|
|
2469
|
+
import { dirname as dirname5 } from "path";
|
|
1802
2470
|
var FileReportStore = class {
|
|
1803
2471
|
filePath;
|
|
1804
2472
|
queue = Promise.resolve();
|
|
@@ -1862,7 +2530,7 @@ var FileReportStore = class {
|
|
|
1862
2530
|
}
|
|
1863
2531
|
async load() {
|
|
1864
2532
|
try {
|
|
1865
|
-
const raw = await
|
|
2533
|
+
const raw = await readFile5(this.filePath, "utf8");
|
|
1866
2534
|
const parsed = JSON.parse(raw);
|
|
1867
2535
|
return Array.isArray(parsed) ? parsed : [];
|
|
1868
2536
|
} catch {
|
|
@@ -1871,10 +2539,10 @@ var FileReportStore = class {
|
|
|
1871
2539
|
}
|
|
1872
2540
|
async write(entries) {
|
|
1873
2541
|
const tmp = `${this.filePath}.tmp`;
|
|
1874
|
-
await
|
|
1875
|
-
await
|
|
2542
|
+
await mkdir5(dirname5(this.filePath), { recursive: true });
|
|
2543
|
+
await writeFile5(tmp, `${JSON.stringify(entries, null, 2)}
|
|
1876
2544
|
`, "utf8");
|
|
1877
|
-
await
|
|
2545
|
+
await rename5(tmp, this.filePath);
|
|
1878
2546
|
}
|
|
1879
2547
|
};
|
|
1880
2548
|
|
|
@@ -1882,10 +2550,12 @@ var FileReportStore = class {
|
|
|
1882
2550
|
function buildPipelineEngine(config, log = logger) {
|
|
1883
2551
|
const agents = buildAgentRegistry(config, log);
|
|
1884
2552
|
const roles = buildRoleRegistry(config);
|
|
2553
|
+
const chat = createDefaultChatFn(config.project.providers);
|
|
1885
2554
|
const tasks = new TaskEngine({
|
|
1886
2555
|
agents,
|
|
1887
2556
|
roles,
|
|
1888
2557
|
store: new FileTaskStore(),
|
|
2558
|
+
...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
|
|
1889
2559
|
log
|
|
1890
2560
|
});
|
|
1891
2561
|
return new PipelineEngine({
|
|
@@ -1894,23 +2564,28 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
1894
2564
|
roles,
|
|
1895
2565
|
providers: config.project.providers,
|
|
1896
2566
|
store: new FilePipelineRunStore(),
|
|
1897
|
-
chat
|
|
2567
|
+
chat,
|
|
2568
|
+
...config.project.planner !== void 0 ? { planner: buildPlanner(config.project.planner, chat, log) } : {},
|
|
2569
|
+
plans: new FilePlanStore(),
|
|
1898
2570
|
reports: new ReportService(new FileReportStore(), log),
|
|
1899
2571
|
log
|
|
1900
2572
|
});
|
|
1901
2573
|
}
|
|
1902
2574
|
|
|
1903
2575
|
// src/version.ts
|
|
1904
|
-
var OMNINODE_VERSION = "0.
|
|
2576
|
+
var OMNINODE_VERSION = "0.9.0";
|
|
1905
2577
|
export {
|
|
1906
2578
|
AgentError,
|
|
1907
2579
|
AgentRegistry,
|
|
1908
2580
|
ConfigError,
|
|
1909
2581
|
ConsoleLogSink,
|
|
1910
2582
|
FilePipelineRunStore,
|
|
2583
|
+
FileReportStore,
|
|
1911
2584
|
FileTaskStore,
|
|
2585
|
+
LocalMemoryProvider,
|
|
1912
2586
|
Logger,
|
|
1913
2587
|
MemoryError,
|
|
2588
|
+
MemoryService,
|
|
1914
2589
|
ModelRegistry,
|
|
1915
2590
|
OMNINODE_VERSION,
|
|
1916
2591
|
OPENAI_COMPATIBLE_TYPES,
|
|
@@ -1919,13 +2594,17 @@ export {
|
|
|
1919
2594
|
OpenAICompatibleProvider,
|
|
1920
2595
|
PipelineEngine,
|
|
1921
2596
|
PipelineError,
|
|
2597
|
+
PlannerError,
|
|
1922
2598
|
ProcessAgent,
|
|
1923
2599
|
ProtocolError,
|
|
1924
2600
|
ProviderError,
|
|
2601
|
+
RememberaMemoryProvider,
|
|
2602
|
+
ReportService,
|
|
1925
2603
|
RoleRegistry,
|
|
1926
2604
|
TaskEngine,
|
|
1927
2605
|
TaskError,
|
|
1928
2606
|
agentConfigSchema,
|
|
2607
|
+
aggregateReports,
|
|
1929
2608
|
appConfigSchema,
|
|
1930
2609
|
authHeaders,
|
|
1931
2610
|
buildAgentRegistry,
|
|
@@ -1934,21 +2613,26 @@ export {
|
|
|
1934
2613
|
composeTaskText,
|
|
1935
2614
|
createAgent,
|
|
1936
2615
|
createDefaultChatFn,
|
|
2616
|
+
createMemoryProvider,
|
|
1937
2617
|
createProvider,
|
|
1938
2618
|
createTaskEngine,
|
|
1939
2619
|
defaultProjectConfigYaml,
|
|
1940
2620
|
expandEnvRefs,
|
|
2621
|
+
extractReportFromText,
|
|
1941
2622
|
findConfigFile,
|
|
1942
2623
|
isOmniNodeError,
|
|
2624
|
+
keywords,
|
|
1943
2625
|
loadConfig,
|
|
1944
2626
|
logger,
|
|
1945
2627
|
pipelineConfigSchema,
|
|
1946
2628
|
pipelineStepConfigSchema,
|
|
2629
|
+
plannerConfigSchema,
|
|
1947
2630
|
projectConfigSchema,
|
|
1948
2631
|
providerConfigSchema,
|
|
1949
2632
|
registryKey,
|
|
1950
2633
|
requestJson,
|
|
1951
2634
|
resolveApiKey,
|
|
1952
|
-
roleConfigSchema
|
|
2635
|
+
roleConfigSchema,
|
|
2636
|
+
similar
|
|
1953
2637
|
};
|
|
1954
2638
|
//# sourceMappingURL=index.js.map
|