@hilbras/omninode 0.10.0 → 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -7
- package/dist/cli/index.js +271 -254
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +192 -31
- package/dist/index.js +233 -233
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { realpathSync } from "fs";
|
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/version.ts
|
|
9
|
-
var OMNINODE_VERSION = "0.
|
|
9
|
+
var OMNINODE_VERSION = "2.0.0-alpha.1";
|
|
10
10
|
|
|
11
11
|
// src/errors/index.ts
|
|
12
12
|
var OmniNodeError = class extends Error {
|
|
@@ -87,6 +87,7 @@ var agentConfigSchema = z.object({
|
|
|
87
87
|
env: z.record(z.string()).optional(),
|
|
88
88
|
input_mode: z.enum(["stdin", "arg", "protocol"]).default("stdin"),
|
|
89
89
|
timeout_ms: z.number().int().positive().optional(),
|
|
90
|
+
inherit_env: z.boolean().optional(),
|
|
90
91
|
metadata: z.record(z.unknown()).optional()
|
|
91
92
|
}).strict();
|
|
92
93
|
var roleConfigSchema = z.object({
|
|
@@ -260,6 +261,7 @@ function toAgentConfig(raw) {
|
|
|
260
261
|
env: raw.env,
|
|
261
262
|
inputMode: raw.input_mode,
|
|
262
263
|
timeoutMs: raw.timeout_ms,
|
|
264
|
+
inheritEnv: raw.inherit_env,
|
|
263
265
|
metadata: raw.metadata
|
|
264
266
|
};
|
|
265
267
|
}
|
|
@@ -897,6 +899,17 @@ function registerModelsCommand(program) {
|
|
|
897
899
|
import { spawn } from "child_process";
|
|
898
900
|
var DEFAULT_TIMEOUT_MS2 = 6e5;
|
|
899
901
|
var KILL_GRACE_MS = 5e3;
|
|
902
|
+
function buildChildEnv(config) {
|
|
903
|
+
const configured = config.env ?? {};
|
|
904
|
+
if (config.inheritEnv !== false) {
|
|
905
|
+
return { ...process.env, ...configured };
|
|
906
|
+
}
|
|
907
|
+
return {
|
|
908
|
+
PATH: process.env.PATH ?? "",
|
|
909
|
+
HOME: process.env.HOME ?? "",
|
|
910
|
+
...configured
|
|
911
|
+
};
|
|
912
|
+
}
|
|
900
913
|
function composeTaskText(input) {
|
|
901
914
|
const sections = [`# Objective
|
|
902
915
|
${input.objective}`];
|
|
@@ -963,7 +976,7 @@ var ProcessAgent = class {
|
|
|
963
976
|
[...this.config.args ?? [], ...mode === "arg" ? [composeTaskText(input)] : []],
|
|
964
977
|
{
|
|
965
978
|
cwd: this.config.cwd,
|
|
966
|
-
env:
|
|
979
|
+
env: buildChildEnv(this.config),
|
|
967
980
|
stdio: ["pipe", "pipe", "pipe"]
|
|
968
981
|
}
|
|
969
982
|
);
|
|
@@ -1289,40 +1302,54 @@ function registerRoleCommands(program) {
|
|
|
1289
1302
|
});
|
|
1290
1303
|
}
|
|
1291
1304
|
|
|
1292
|
-
// src/
|
|
1305
|
+
// src/persistence/json-file-store.ts
|
|
1293
1306
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
1294
1307
|
import { dirname } from "path";
|
|
1295
|
-
var
|
|
1308
|
+
var JsonFileStore = class {
|
|
1296
1309
|
filePath;
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
*/
|
|
1310
|
+
schemaVersion;
|
|
1311
|
+
onCorrupt;
|
|
1312
|
+
log;
|
|
1301
1313
|
queue = Promise.resolve();
|
|
1302
|
-
constructor(
|
|
1303
|
-
this.filePath = `${directory}
|
|
1314
|
+
constructor(options) {
|
|
1315
|
+
this.filePath = `${options.directory ?? `${process.cwd()}/.omninode`}/${options.fileName}`;
|
|
1316
|
+
this.schemaVersion = options.schemaVersion ?? 1;
|
|
1317
|
+
this.onCorrupt = options.onCorrupt;
|
|
1318
|
+
this.log = logger.child({ component: "persistence", file: options.fileName });
|
|
1304
1319
|
}
|
|
1305
|
-
|
|
1306
|
-
return this.
|
|
1307
|
-
const tasks = await this.load();
|
|
1308
|
-
const index = tasks.findIndex((t) => t.id === task.id);
|
|
1309
|
-
if (index >= 0) tasks[index] = task;
|
|
1310
|
-
else tasks.push(task);
|
|
1311
|
-
await this.write(tasks);
|
|
1312
|
-
});
|
|
1320
|
+
get path() {
|
|
1321
|
+
return this.filePath;
|
|
1313
1322
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1323
|
+
/** All items, or [] when missing/corrupt. */
|
|
1324
|
+
async readAll() {
|
|
1325
|
+
let raw;
|
|
1326
|
+
try {
|
|
1327
|
+
raw = await readFile(this.filePath, "utf8");
|
|
1328
|
+
} catch {
|
|
1329
|
+
return [];
|
|
1330
|
+
}
|
|
1331
|
+
try {
|
|
1332
|
+
const parsed = JSON.parse(raw);
|
|
1333
|
+
if (Array.isArray(parsed)) return parsed;
|
|
1334
|
+
if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.items)) {
|
|
1335
|
+
return parsed.items;
|
|
1336
|
+
}
|
|
1337
|
+
throw new Error("persisted file has an unrecognized shape");
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
this.log.warn(`Treating ${this.filePath} as empty \u2014 it could not be parsed.`);
|
|
1340
|
+
this.onCorrupt?.(this.filePath, error);
|
|
1341
|
+
return [];
|
|
1342
|
+
}
|
|
1319
1343
|
}
|
|
1320
|
-
async
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
}
|
|
1344
|
+
async writeAll(items) {
|
|
1345
|
+
const document = { schemaVersion: this.schemaVersion, items };
|
|
1346
|
+
const tmp = `${this.filePath}.tmp`;
|
|
1347
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
1348
|
+
await writeFile(tmp, `${JSON.stringify(document, null, 2)}
|
|
1349
|
+
`, "utf8");
|
|
1350
|
+
await rename(tmp, this.filePath);
|
|
1325
1351
|
}
|
|
1352
|
+
/** Serializes an async operation against this file. */
|
|
1326
1353
|
synchronized(operation) {
|
|
1327
1354
|
const result = this.queue.then(operation, operation);
|
|
1328
1355
|
this.queue = result.then(
|
|
@@ -1331,21 +1358,37 @@ var FileTaskStore = class {
|
|
|
1331
1358
|
);
|
|
1332
1359
|
return result;
|
|
1333
1360
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
return
|
|
1341
|
-
}
|
|
1361
|
+
/** Read-modify-write helper: applies `mutate` to the current items. */
|
|
1362
|
+
async mutate(mutate) {
|
|
1363
|
+
return this.synchronized(async () => {
|
|
1364
|
+
const items = await this.readAll();
|
|
1365
|
+
const [next, result] = await mutate(items);
|
|
1366
|
+
await this.writeAll(next);
|
|
1367
|
+
return result;
|
|
1368
|
+
});
|
|
1342
1369
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
// src/tasks/store.ts
|
|
1373
|
+
var FileTaskStore = class extends JsonFileStore {
|
|
1374
|
+
constructor(directory) {
|
|
1375
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "tasks.json" });
|
|
1376
|
+
}
|
|
1377
|
+
save(task) {
|
|
1378
|
+
return this.mutate((tasks) => {
|
|
1379
|
+
const index = tasks.findIndex((t) => t.id === task.id);
|
|
1380
|
+
if (index >= 0) tasks[index] = task;
|
|
1381
|
+
else tasks.push(task);
|
|
1382
|
+
return [tasks, void 0];
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
async get(id) {
|
|
1386
|
+
const tasks = await this.readAll();
|
|
1387
|
+
return tasks.find((t) => t.id === id);
|
|
1388
|
+
}
|
|
1389
|
+
async list(filter = {}) {
|
|
1390
|
+
const tasks = await this.readAll();
|
|
1391
|
+
return tasks.filter((task) => filter.status ? task.status === filter.status : true).filter((task) => filter.project ? task.project === filter.project : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
1349
1392
|
}
|
|
1350
1393
|
};
|
|
1351
1394
|
|
|
@@ -1400,6 +1443,7 @@ var TaskEngine = class {
|
|
|
1400
1443
|
};
|
|
1401
1444
|
await this.options.store.save(task);
|
|
1402
1445
|
this.log.info(`Created task ${task.id}.`);
|
|
1446
|
+
await this.audit("task.created", task.id, { objective: task.objective, project: task.project });
|
|
1403
1447
|
return task;
|
|
1404
1448
|
}
|
|
1405
1449
|
async queue(id) {
|
|
@@ -1491,6 +1535,7 @@ var TaskEngine = class {
|
|
|
1491
1535
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1492
1536
|
};
|
|
1493
1537
|
await this.options.store.save(reset);
|
|
1538
|
+
await this.audit("task.retry", id, { from: task.status });
|
|
1494
1539
|
this.log.info(`Task ${id} reset for retry.`);
|
|
1495
1540
|
return reset;
|
|
1496
1541
|
}
|
|
@@ -1519,16 +1564,66 @@ var TaskEngine = class {
|
|
|
1519
1564
|
...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
|
|
1520
1565
|
};
|
|
1521
1566
|
await this.options.store.save(updated);
|
|
1567
|
+
await this.audit(`task.${to === "queued" ? "queued" : to}`, updated.id, {
|
|
1568
|
+
from: task.status
|
|
1569
|
+
});
|
|
1522
1570
|
return updated;
|
|
1523
1571
|
}
|
|
1572
|
+
async audit(action, id, detail) {
|
|
1573
|
+
await this.options.audit?.record({
|
|
1574
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1575
|
+
action,
|
|
1576
|
+
id,
|
|
1577
|
+
...detail !== void 0 ? { detail } : {}
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1524
1580
|
};
|
|
1525
1581
|
function generateTaskId() {
|
|
1526
1582
|
return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
1527
1583
|
}
|
|
1528
1584
|
|
|
1529
|
-
// src/
|
|
1530
|
-
import { mkdir as mkdir2
|
|
1585
|
+
// src/audit/index.ts
|
|
1586
|
+
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
1531
1587
|
import { dirname as dirname2 } from "path";
|
|
1588
|
+
var FileAuditLog = class {
|
|
1589
|
+
filePath;
|
|
1590
|
+
log;
|
|
1591
|
+
constructor(directory = `${process.cwd()}/.omninode`, log = logger) {
|
|
1592
|
+
this.filePath = `${directory}/audit.jsonl`;
|
|
1593
|
+
this.log = log.child({ component: "audit" });
|
|
1594
|
+
}
|
|
1595
|
+
async record(event) {
|
|
1596
|
+
try {
|
|
1597
|
+
await mkdir2(dirname2(this.filePath), { recursive: true });
|
|
1598
|
+
await appendFile(this.filePath, `${JSON.stringify(event)}
|
|
1599
|
+
`, "utf8");
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
this.log.warn(
|
|
1602
|
+
`Audit write failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
/** Recent events, oldest first; used by the CLI. */
|
|
1607
|
+
async recent(limit = 20) {
|
|
1608
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
1609
|
+
let raw;
|
|
1610
|
+
try {
|
|
1611
|
+
raw = await readFile2(this.filePath, "utf8");
|
|
1612
|
+
} catch {
|
|
1613
|
+
return [];
|
|
1614
|
+
}
|
|
1615
|
+
const events = raw.split("\n").filter((line) => line.length > 0).map((line) => {
|
|
1616
|
+
try {
|
|
1617
|
+
return JSON.parse(line);
|
|
1618
|
+
} catch {
|
|
1619
|
+
return void 0;
|
|
1620
|
+
}
|
|
1621
|
+
}).filter((event) => event !== void 0);
|
|
1622
|
+
return events.slice(-limit);
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1626
|
+
// src/memory/local.ts
|
|
1532
1627
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
1533
1628
|
"the",
|
|
1534
1629
|
"and",
|
|
@@ -1585,29 +1680,24 @@ function relevanceScore(queryKeywords, entry2) {
|
|
|
1585
1680
|
}
|
|
1586
1681
|
return overlap * 2 + tagMatches * 1.5;
|
|
1587
1682
|
}
|
|
1588
|
-
var LocalMemoryProvider = class {
|
|
1683
|
+
var LocalMemoryProvider = class extends JsonFileStore {
|
|
1589
1684
|
name = "local";
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
1593
|
-
this.filePath = `${directory}/memory.json`;
|
|
1685
|
+
constructor(directory) {
|
|
1686
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "memory.json" });
|
|
1594
1687
|
}
|
|
1595
1688
|
async query(query) {
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
return filtered.map((entry2) => ({ entry: entry2, score: relevanceScore(queryKeywords, entry2) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10).map(({ entry: entry2 }) => entry2);
|
|
1606
|
-
});
|
|
1689
|
+
const entries = await this.readAll();
|
|
1690
|
+
const filtered = entries.filter((entry2) => query.scope ? entry2.scope === query.scope : true).filter(
|
|
1691
|
+
(entry2) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry2.tags ?? []).includes(tag)) : true
|
|
1692
|
+
);
|
|
1693
|
+
if (!query.text || query.text.trim().length === 0) {
|
|
1694
|
+
return filtered.sort((a, b) => (b.updatedAt ?? b.createdAt ?? "").localeCompare(a.updatedAt ?? a.createdAt ?? "")).slice(0, query.limit ?? 10);
|
|
1695
|
+
}
|
|
1696
|
+
const queryKeywords = keywords(query.text);
|
|
1697
|
+
return filtered.map((entry2) => ({ entry: entry2, score: relevanceScore(queryKeywords, entry2) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10).map(({ entry: entry2 }) => entry2);
|
|
1607
1698
|
}
|
|
1608
|
-
|
|
1609
|
-
return this.
|
|
1610
|
-
const entries = await this.load();
|
|
1699
|
+
write(entry2) {
|
|
1700
|
+
return this.mutate((entries) => {
|
|
1611
1701
|
const index = entries.findIndex((e) => e.key === entry2.key);
|
|
1612
1702
|
const stamped = {
|
|
1613
1703
|
...entry2,
|
|
@@ -1616,35 +1706,11 @@ var LocalMemoryProvider = class {
|
|
|
1616
1706
|
};
|
|
1617
1707
|
if (index >= 0) entries[index] = stamped;
|
|
1618
1708
|
else entries.push(stamped);
|
|
1619
|
-
|
|
1709
|
+
return [entries, void 0];
|
|
1620
1710
|
});
|
|
1621
1711
|
}
|
|
1622
1712
|
async count() {
|
|
1623
|
-
return (await this.
|
|
1624
|
-
}
|
|
1625
|
-
synchronized(operation) {
|
|
1626
|
-
const result = this.queue.then(operation, operation);
|
|
1627
|
-
this.queue = result.then(
|
|
1628
|
-
() => void 0,
|
|
1629
|
-
() => void 0
|
|
1630
|
-
);
|
|
1631
|
-
return result;
|
|
1632
|
-
}
|
|
1633
|
-
async load() {
|
|
1634
|
-
try {
|
|
1635
|
-
const raw = await readFile2(this.filePath, "utf8");
|
|
1636
|
-
const parsed = JSON.parse(raw);
|
|
1637
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
1638
|
-
} catch {
|
|
1639
|
-
return [];
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
async saveAll(entries) {
|
|
1643
|
-
const tmp = `${this.filePath}.tmp`;
|
|
1644
|
-
await mkdir2(dirname2(this.filePath), { recursive: true });
|
|
1645
|
-
await writeFile2(tmp, `${JSON.stringify(entries, null, 2)}
|
|
1646
|
-
`, "utf8");
|
|
1647
|
-
await rename2(tmp, this.filePath);
|
|
1713
|
+
return (await this.readAll()).length;
|
|
1648
1714
|
}
|
|
1649
1715
|
};
|
|
1650
1716
|
|
|
@@ -1848,6 +1914,7 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
1848
1914
|
roles,
|
|
1849
1915
|
store: new FileTaskStore(directory),
|
|
1850
1916
|
...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
|
|
1917
|
+
audit: new FileAuditLog(),
|
|
1851
1918
|
log
|
|
1852
1919
|
});
|
|
1853
1920
|
}
|
|
@@ -2160,84 +2227,39 @@ var ReportService = class {
|
|
|
2160
2227
|
};
|
|
2161
2228
|
|
|
2162
2229
|
// src/reports/store.ts
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
filePath;
|
|
2167
|
-
queue = Promise.resolve();
|
|
2168
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
2169
|
-
this.filePath = `${directory}/reports.json`;
|
|
2230
|
+
var FileReportStore = class extends JsonFileStore {
|
|
2231
|
+
constructor(directory) {
|
|
2232
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "reports.json" });
|
|
2170
2233
|
}
|
|
2171
2234
|
async saveReports(reports) {
|
|
2172
|
-
|
|
2173
|
-
const entries = await this.load();
|
|
2235
|
+
await this.mutate((entries) => {
|
|
2174
2236
|
for (const report of reports) {
|
|
2175
|
-
const index = entries.findIndex(
|
|
2176
|
-
(e) => e.kind === "report" && e.report.id === report.id
|
|
2177
|
-
);
|
|
2237
|
+
const index = entries.findIndex((e) => e.kind === "report" && e.report.id === report.id);
|
|
2178
2238
|
const entry2 = { kind: "report", report };
|
|
2179
2239
|
if (index >= 0) entries[index] = entry2;
|
|
2180
2240
|
else entries.push(entry2);
|
|
2181
2241
|
}
|
|
2182
|
-
|
|
2242
|
+
return [entries, void 0];
|
|
2183
2243
|
});
|
|
2184
2244
|
}
|
|
2185
2245
|
async listReports(filter = {}) {
|
|
2186
|
-
return this.
|
|
2187
|
-
const entries = await this.load();
|
|
2188
|
-
return entries.filter((e) => e.kind === "report").map((e) => e.report).filter((report) => filter.taskId ? report.taskId === filter.taskId : true).filter((report) => filter.agent ? report.agent === filter.agent : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2189
|
-
});
|
|
2246
|
+
return (await this.readAll()).filter((e) => e.kind === "report").map((e) => e.report).filter((report) => filter.taskId ? report.taskId === filter.taskId : true).filter((report) => filter.agent ? report.agent === filter.agent : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2190
2247
|
}
|
|
2191
2248
|
async saveCombined(combined) {
|
|
2192
|
-
|
|
2193
|
-
const
|
|
2194
|
-
const index = entries.findIndex(
|
|
2195
|
-
(e) => e.kind === "combined" && e.combined.id === combined.id
|
|
2196
|
-
);
|
|
2249
|
+
await this.mutate((entries) => {
|
|
2250
|
+
const index = entries.findIndex((e) => e.kind === "combined" && e.combined.id === combined.id);
|
|
2197
2251
|
const entry2 = { kind: "combined", combined };
|
|
2198
2252
|
if (index >= 0) entries[index] = entry2;
|
|
2199
2253
|
else entries.push(entry2);
|
|
2200
|
-
|
|
2254
|
+
return [entries, void 0];
|
|
2201
2255
|
});
|
|
2202
2256
|
}
|
|
2203
2257
|
async getCombined(id) {
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
const found = entries.find((e) => e.kind === "combined" && e.combined.id === id);
|
|
2207
|
-
return found?.kind === "combined" ? found.combined : void 0;
|
|
2208
|
-
});
|
|
2258
|
+
const found = (await this.readAll()).find((e) => e.kind === "combined" && e.combined.id === id);
|
|
2259
|
+
return found?.kind === "combined" ? found.combined : void 0;
|
|
2209
2260
|
}
|
|
2210
2261
|
async listCombined(filter = {}) {
|
|
2211
|
-
return this.
|
|
2212
|
-
const entries = await this.load();
|
|
2213
|
-
return entries.filter((e) => e.kind === "combined").map((e) => e.combined).filter(
|
|
2214
|
-
(combined) => filter.pipelineRunId ? combined.pipelineRunId === filter.pipelineRunId : true
|
|
2215
|
-
).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2216
|
-
});
|
|
2217
|
-
}
|
|
2218
|
-
synchronized(operation) {
|
|
2219
|
-
const result = this.queue.then(operation, operation);
|
|
2220
|
-
this.queue = result.then(
|
|
2221
|
-
() => void 0,
|
|
2222
|
-
() => void 0
|
|
2223
|
-
);
|
|
2224
|
-
return result;
|
|
2225
|
-
}
|
|
2226
|
-
async load() {
|
|
2227
|
-
try {
|
|
2228
|
-
const raw = await readFile3(this.filePath, "utf8");
|
|
2229
|
-
const parsed = JSON.parse(raw);
|
|
2230
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
2231
|
-
} catch {
|
|
2232
|
-
return [];
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
async write(entries) {
|
|
2236
|
-
const tmp = `${this.filePath}.tmp`;
|
|
2237
|
-
await mkdir3(dirname3(this.filePath), { recursive: true });
|
|
2238
|
-
await writeFile3(tmp, `${JSON.stringify(entries, null, 2)}
|
|
2239
|
-
`, "utf8");
|
|
2240
|
-
await rename3(tmp, this.filePath);
|
|
2262
|
+
return (await this.readAll()).filter((e) => e.kind === "combined").map((e) => e.combined).filter((combined) => filter.pipelineRunId ? combined.pipelineRunId === filter.pipelineRunId : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2241
2263
|
}
|
|
2242
2264
|
};
|
|
2243
2265
|
|
|
@@ -2676,6 +2698,12 @@ var PipelineEngine = class {
|
|
|
2676
2698
|
};
|
|
2677
2699
|
await this.options.store.save(run2);
|
|
2678
2700
|
this.log.info(`Running pipeline ${def.id} as ${run2.id}.`);
|
|
2701
|
+
await this.options.audit?.record({
|
|
2702
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2703
|
+
action: "pipeline.run.started",
|
|
2704
|
+
id: run2.id,
|
|
2705
|
+
detail: { pipeline: def.id, objective }
|
|
2706
|
+
});
|
|
2679
2707
|
const deps = this.effectiveDependencies(def);
|
|
2680
2708
|
const outcomes = /* @__PURE__ */ new Map();
|
|
2681
2709
|
const pending = new Set(def.steps.map((s) => s.id));
|
|
@@ -2766,6 +2794,12 @@ ${contribution}`);
|
|
|
2766
2794
|
};
|
|
2767
2795
|
await this.options.store.save(finished);
|
|
2768
2796
|
this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
|
|
2797
|
+
await this.options.audit?.record({
|
|
2798
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2799
|
+
action: "pipeline.run.finished",
|
|
2800
|
+
id: run2.id,
|
|
2801
|
+
detail: { pipeline: def.id, status: finalStatus }
|
|
2802
|
+
});
|
|
2769
2803
|
return finished;
|
|
2770
2804
|
}
|
|
2771
2805
|
async get(runId) {
|
|
@@ -2910,6 +2944,12 @@ ${contribution}`);
|
|
|
2910
2944
|
};
|
|
2911
2945
|
}
|
|
2912
2946
|
await this.options.plans?.save(plan);
|
|
2947
|
+
await this.options.audit?.record({
|
|
2948
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2949
|
+
action: "plan.generated",
|
|
2950
|
+
id: plan.id,
|
|
2951
|
+
detail: { generatedBy: plan.generatedBy, steps: plan.steps.length }
|
|
2952
|
+
});
|
|
2913
2953
|
return {
|
|
2914
2954
|
status: "completed",
|
|
2915
2955
|
summary: plan.summary,
|
|
@@ -2967,114 +3007,44 @@ function createDefaultChatFn(providers) {
|
|
|
2967
3007
|
}
|
|
2968
3008
|
|
|
2969
3009
|
// src/pipelines/store.ts
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
filePath;
|
|
2974
|
-
queue = Promise.resolve();
|
|
2975
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
2976
|
-
this.filePath = `${directory}/pipelines.json`;
|
|
3010
|
+
var FilePipelineRunStore = class extends JsonFileStore {
|
|
3011
|
+
constructor(directory) {
|
|
3012
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "pipelines.json" });
|
|
2977
3013
|
}
|
|
2978
|
-
|
|
2979
|
-
return this.
|
|
2980
|
-
const runs = await this.load();
|
|
3014
|
+
save(run2) {
|
|
3015
|
+
return this.mutate((runs) => {
|
|
2981
3016
|
const index = runs.findIndex((r) => r.id === run2.id);
|
|
2982
3017
|
if (index >= 0) runs[index] = run2;
|
|
2983
3018
|
else runs.push(run2);
|
|
2984
|
-
|
|
3019
|
+
return [runs, void 0];
|
|
2985
3020
|
});
|
|
2986
3021
|
}
|
|
2987
3022
|
async get(id) {
|
|
2988
|
-
return this.
|
|
2989
|
-
const runs = await this.load();
|
|
2990
|
-
return runs.find((r) => r.id === id);
|
|
2991
|
-
});
|
|
3023
|
+
return (await this.readAll()).find((r) => r.id === id);
|
|
2992
3024
|
}
|
|
2993
3025
|
async list(filter = {}) {
|
|
2994
|
-
return this.
|
|
2995
|
-
const runs = await this.load();
|
|
2996
|
-
return runs.filter((run2) => filter.pipelineId ? run2.pipelineId === filter.pipelineId : true).filter((run2) => filter.status ? run2.status === filter.status : true).sort((a, b) => (a.startedAt ?? "").localeCompare(b.startedAt ?? ""));
|
|
2997
|
-
});
|
|
2998
|
-
}
|
|
2999
|
-
synchronized(operation) {
|
|
3000
|
-
const result = this.queue.then(operation, operation);
|
|
3001
|
-
this.queue = result.then(
|
|
3002
|
-
() => void 0,
|
|
3003
|
-
() => void 0
|
|
3004
|
-
);
|
|
3005
|
-
return result;
|
|
3006
|
-
}
|
|
3007
|
-
async load() {
|
|
3008
|
-
try {
|
|
3009
|
-
const raw = await readFile4(this.filePath, "utf8");
|
|
3010
|
-
const parsed = JSON.parse(raw);
|
|
3011
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
3012
|
-
} catch {
|
|
3013
|
-
return [];
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
async write(runs) {
|
|
3017
|
-
const tmp = `${this.filePath}.tmp`;
|
|
3018
|
-
await mkdir4(dirname4(this.filePath), { recursive: true });
|
|
3019
|
-
await writeFile4(tmp, `${JSON.stringify(runs, null, 2)}
|
|
3020
|
-
`, "utf8");
|
|
3021
|
-
await rename4(tmp, this.filePath);
|
|
3026
|
+
return (await this.readAll()).filter((run2) => filter.pipelineId ? run2.pipelineId === filter.pipelineId : true).filter((run2) => filter.status ? run2.status === filter.status : true).sort((a, b) => (a.startedAt ?? "").localeCompare(b.startedAt ?? ""));
|
|
3022
3027
|
}
|
|
3023
3028
|
};
|
|
3024
3029
|
|
|
3025
3030
|
// src/planner/store.ts
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
filePath;
|
|
3030
|
-
queue = Promise.resolve();
|
|
3031
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
3032
|
-
this.filePath = `${directory}/plans.json`;
|
|
3031
|
+
var FilePlanStore = class extends JsonFileStore {
|
|
3032
|
+
constructor(directory) {
|
|
3033
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "plans.json" });
|
|
3033
3034
|
}
|
|
3034
|
-
|
|
3035
|
-
return this.
|
|
3036
|
-
const plans = await this.load();
|
|
3035
|
+
save(plan) {
|
|
3036
|
+
return this.mutate((plans) => {
|
|
3037
3037
|
const index = plans.findIndex((p) => p.id === plan.id);
|
|
3038
3038
|
if (index >= 0) plans[index] = plan;
|
|
3039
3039
|
else plans.push(plan);
|
|
3040
|
-
|
|
3040
|
+
return [plans, void 0];
|
|
3041
3041
|
});
|
|
3042
3042
|
}
|
|
3043
3043
|
async get(id) {
|
|
3044
|
-
return this.
|
|
3045
|
-
const plans = await this.load();
|
|
3046
|
-
return plans.find((p) => p.id === id);
|
|
3047
|
-
});
|
|
3044
|
+
return (await this.readAll()).find((p) => p.id === id);
|
|
3048
3045
|
}
|
|
3049
3046
|
async list(filter = {}) {
|
|
3050
|
-
return this.
|
|
3051
|
-
const plans = await this.load();
|
|
3052
|
-
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));
|
|
3053
|
-
});
|
|
3054
|
-
}
|
|
3055
|
-
synchronized(operation) {
|
|
3056
|
-
const result = this.queue.then(operation, operation);
|
|
3057
|
-
this.queue = result.then(
|
|
3058
|
-
() => void 0,
|
|
3059
|
-
() => void 0
|
|
3060
|
-
);
|
|
3061
|
-
return result;
|
|
3062
|
-
}
|
|
3063
|
-
async load() {
|
|
3064
|
-
try {
|
|
3065
|
-
const raw = await readFile5(this.filePath, "utf8");
|
|
3066
|
-
const parsed = JSON.parse(raw);
|
|
3067
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
3068
|
-
} catch {
|
|
3069
|
-
return [];
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
async write(plans) {
|
|
3073
|
-
const tmp = `${this.filePath}.tmp`;
|
|
3074
|
-
await mkdir5(dirname5(this.filePath), { recursive: true });
|
|
3075
|
-
await writeFile5(tmp, `${JSON.stringify(plans, null, 2)}
|
|
3076
|
-
`, "utf8");
|
|
3077
|
-
await rename5(tmp, this.filePath);
|
|
3047
|
+
return (await this.readAll()).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));
|
|
3078
3048
|
}
|
|
3079
3049
|
};
|
|
3080
3050
|
|
|
@@ -3109,6 +3079,7 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
3109
3079
|
roles,
|
|
3110
3080
|
store: new FileTaskStore(),
|
|
3111
3081
|
...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
|
|
3082
|
+
audit: new FileAuditLog(),
|
|
3112
3083
|
log
|
|
3113
3084
|
});
|
|
3114
3085
|
return new PipelineEngine({
|
|
@@ -3121,6 +3092,7 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
3121
3092
|
...config.project.planner !== void 0 ? { planner: buildPlanner(config.project.planner, chat, log) } : {},
|
|
3122
3093
|
plans: new FilePlanStore(),
|
|
3123
3094
|
reports: new ReportService(new FileReportStore(), log),
|
|
3095
|
+
audit: new FileAuditLog(),
|
|
3124
3096
|
log
|
|
3125
3097
|
});
|
|
3126
3098
|
}
|
|
@@ -3344,6 +3316,14 @@ function registerPlanCommands(program) {
|
|
|
3344
3316
|
}
|
|
3345
3317
|
|
|
3346
3318
|
// src/cli/commands/status.ts
|
|
3319
|
+
function groupBy(items, key) {
|
|
3320
|
+
const counts = {};
|
|
3321
|
+
for (const item of items) {
|
|
3322
|
+
const k = key(item);
|
|
3323
|
+
counts[k] = (counts[k] ?? 0) + 1;
|
|
3324
|
+
}
|
|
3325
|
+
return counts;
|
|
3326
|
+
}
|
|
3347
3327
|
function countBy(items, key) {
|
|
3348
3328
|
const counts = /* @__PURE__ */ new Map();
|
|
3349
3329
|
for (const item of items) {
|
|
@@ -3354,41 +3334,77 @@ function countBy(items, key) {
|
|
|
3354
3334
|
return `${items.length} (${[...counts.entries()].map(([k, v]) => `${k} ${v}`).join(", ")})`;
|
|
3355
3335
|
}
|
|
3356
3336
|
function registerStatusCommand(program) {
|
|
3357
|
-
program.command("status").description("Show the project's OmniNode state overview (config, tasks, runs, reports, plans, memory).").action(async () => {
|
|
3337
|
+
program.command("status").description("Show the project's OmniNode state overview (config, tasks, runs, reports, plans, memory).").option("--json", "Emit machine-readable JSON (for scripts and CI).").action(async (options) => {
|
|
3358
3338
|
const config = loadConfig();
|
|
3359
|
-
console.log(`OmniNode v${OMNINODE_VERSION} \u2014 project "${config.project.name}"`);
|
|
3360
|
-
console.log(
|
|
3361
|
-
`providers: ${config.project.providers.length > 0 ? config.project.providers.map((p) => p.name).join(", ") : "none"}`
|
|
3362
|
-
);
|
|
3363
|
-
console.log(
|
|
3364
|
-
`agents: ${config.project.agents.length > 0 ? config.project.agents.map((a) => a.name).join(", ") : "none"}`
|
|
3365
|
-
);
|
|
3366
|
-
console.log(
|
|
3367
|
-
`roles: ${config.project.roles.length > 0 ? config.project.roles.map((r) => r.id).join(", ") : "none"}`
|
|
3368
|
-
);
|
|
3369
|
-
console.log(
|
|
3370
|
-
`pipelines: ${config.project.pipelines.length > 0 ? config.project.pipelines.map((p) => p.id).join(", ") : "none"}`
|
|
3371
|
-
);
|
|
3372
3339
|
const tasks = await new FileTaskStore().list();
|
|
3373
|
-
console.log(`tasks: ${countBy(tasks, (t) => t.status)}`);
|
|
3374
3340
|
const runs = await new FilePipelineRunStore().list();
|
|
3375
|
-
console.log(`runs: ${countBy(runs, (r) => r.status)}`);
|
|
3376
3341
|
const reportStore = new FileReportStore();
|
|
3377
3342
|
const reports = await reportStore.listReports();
|
|
3378
3343
|
const combined = await reportStore.listCombined();
|
|
3379
|
-
console.log(`reports: ${reports.length} raw, ${combined.length} combined`);
|
|
3380
3344
|
const plans = await new FilePlanStore().list();
|
|
3381
|
-
|
|
3345
|
+
let memory;
|
|
3382
3346
|
if (config.project.memory !== void 0) {
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
|
|
3347
|
+
const service2 = new MemoryService(createMemoryProvider(config.project.memory));
|
|
3348
|
+
memory = {
|
|
3349
|
+
provider: service2.providerName,
|
|
3350
|
+
entries: (await service2.providerRef.query({ limit: 1e4 })).length
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
if (options.json) {
|
|
3354
|
+
console.log(
|
|
3355
|
+
JSON.stringify(
|
|
3356
|
+
{
|
|
3357
|
+
version: OMNINODE_VERSION,
|
|
3358
|
+
project: config.project.name,
|
|
3359
|
+
providers: config.project.providers.map((p) => p.name),
|
|
3360
|
+
agents: config.project.agents.map((a) => a.name),
|
|
3361
|
+
roles: config.project.roles.map((r) => r.id),
|
|
3362
|
+
pipelines: config.project.pipelines.map((p) => p.id),
|
|
3363
|
+
tasks: { total: tasks.length, byStatus: groupBy(tasks, (t) => t.status) },
|
|
3364
|
+
runs: { total: runs.length, byStatus: groupBy(runs, (r) => r.status) },
|
|
3365
|
+
reports: { raw: reports.length, combined: combined.length },
|
|
3366
|
+
plans: plans.length,
|
|
3367
|
+
...memory !== void 0 ? { memory } : {}
|
|
3368
|
+
},
|
|
3369
|
+
null,
|
|
3370
|
+
2
|
|
3371
|
+
)
|
|
3372
|
+
);
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
console.log(`OmniNode v${OMNINODE_VERSION} \u2014 project "${config.project.name}"`);
|
|
3376
|
+
console.log(`providers: ${config.project.providers.map((p) => p.name).join(", ") || "none"}`);
|
|
3377
|
+
console.log(`agents: ${config.project.agents.map((a) => a.name).join(", ") || "none"}`);
|
|
3378
|
+
console.log(`roles: ${config.project.roles.map((r) => r.id).join(", ") || "none"}`);
|
|
3379
|
+
console.log(`pipelines: ${config.project.pipelines.map((p) => p.id).join(", ") || "none"}`);
|
|
3380
|
+
console.log(`tasks: ${countBy(tasks, (t) => t.status)}`);
|
|
3381
|
+
console.log(`runs: ${countBy(runs, (r) => r.status)}`);
|
|
3382
|
+
console.log(`reports: ${reports.length} raw, ${combined.length} combined`);
|
|
3383
|
+
console.log(`plans: ${plans.length}`);
|
|
3384
|
+
if (memory) {
|
|
3385
|
+
console.log(`memory: ${memory.provider}, ${memory.entries} entr(ies)`);
|
|
3386
3386
|
} else {
|
|
3387
3387
|
console.log("memory: not configured (local provider available \u2014 add a `memory:` section)");
|
|
3388
3388
|
}
|
|
3389
3389
|
});
|
|
3390
3390
|
}
|
|
3391
3391
|
|
|
3392
|
+
// src/cli/commands/audit.ts
|
|
3393
|
+
function registerAuditCommand(program) {
|
|
3394
|
+
program.command("audit").description("Show the recent audit log \u2014 task and pipeline lifecycle events (\xA724).").option("-n, --limit <count>", "Number of events to show (default 20).", (value) => Number(value), 20).action(async (options) => {
|
|
3395
|
+
const events = await new FileAuditLog().recent(options.limit);
|
|
3396
|
+
if (events.length === 0) {
|
|
3397
|
+
console.log("No audit events recorded yet.");
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
for (const event of events) {
|
|
3401
|
+
const id = event.id ? ` ${event.id}` : "";
|
|
3402
|
+
const detail = event.detail && Object.keys(event.detail).length > 0 ? ` ${JSON.stringify(event.detail)}` : "";
|
|
3403
|
+
console.log(`${event.at} ${event.action}${id}${detail}`);
|
|
3404
|
+
}
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3392
3408
|
// src/cli/index.ts
|
|
3393
3409
|
function createProgram() {
|
|
3394
3410
|
const program = new Command();
|
|
@@ -3407,6 +3423,7 @@ function createProgram() {
|
|
|
3407
3423
|
registerMemoryCommands(program);
|
|
3408
3424
|
registerPlanCommands(program);
|
|
3409
3425
|
registerStatusCommand(program);
|
|
3426
|
+
registerAuditCommand(program);
|
|
3410
3427
|
registerRunAlias(program);
|
|
3411
3428
|
return program;
|
|
3412
3429
|
}
|