@hilbras/omninode 1.0.0 → 2.0.0-alpha.2
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 +14 -1
- package/dist/cli/index.js +257 -282
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +192 -36
- package/dist/index.js +284 -281
- 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 = "
|
|
9
|
+
var OMNINODE_VERSION = "2.0.0-alpha.2";
|
|
10
10
|
|
|
11
11
|
// src/errors/index.ts
|
|
12
12
|
var OmniNodeError = class extends Error {
|
|
@@ -1037,7 +1037,19 @@ var ProcessAgent = class {
|
|
|
1037
1037
|
new AgentError(
|
|
1038
1038
|
"AGENT_TIMEOUT",
|
|
1039
1039
|
`Agent "${this.config.name}" timed out after ${timeoutMs}ms and was killed.`,
|
|
1040
|
-
{
|
|
1040
|
+
{
|
|
1041
|
+
details: {
|
|
1042
|
+
operation: "agent.run",
|
|
1043
|
+
agent: this.config.name,
|
|
1044
|
+
durationMs: timeoutMs,
|
|
1045
|
+
// The process was started and may have performed side
|
|
1046
|
+
// effects before being killed — the outcome is unknown,
|
|
1047
|
+
// not failed (roadmap §6.3).
|
|
1048
|
+
dispatched: true,
|
|
1049
|
+
stdout: stdout.slice(0, 4e3),
|
|
1050
|
+
stderr: stderr.slice(0, 2e3)
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1041
1053
|
)
|
|
1042
1054
|
);
|
|
1043
1055
|
return;
|
|
@@ -1302,40 +1314,54 @@ function registerRoleCommands(program) {
|
|
|
1302
1314
|
});
|
|
1303
1315
|
}
|
|
1304
1316
|
|
|
1305
|
-
// src/
|
|
1317
|
+
// src/persistence/json-file-store.ts
|
|
1306
1318
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
1307
1319
|
import { dirname } from "path";
|
|
1308
|
-
var
|
|
1320
|
+
var JsonFileStore = class {
|
|
1309
1321
|
filePath;
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
*/
|
|
1322
|
+
schemaVersion;
|
|
1323
|
+
onCorrupt;
|
|
1324
|
+
log;
|
|
1314
1325
|
queue = Promise.resolve();
|
|
1315
|
-
constructor(
|
|
1316
|
-
this.filePath = `${directory}
|
|
1326
|
+
constructor(options) {
|
|
1327
|
+
this.filePath = `${options.directory ?? `${process.cwd()}/.omninode`}/${options.fileName}`;
|
|
1328
|
+
this.schemaVersion = options.schemaVersion ?? 1;
|
|
1329
|
+
this.onCorrupt = options.onCorrupt;
|
|
1330
|
+
this.log = logger.child({ component: "persistence", file: options.fileName });
|
|
1317
1331
|
}
|
|
1318
|
-
|
|
1319
|
-
return this.
|
|
1320
|
-
const tasks = await this.load();
|
|
1321
|
-
const index = tasks.findIndex((t) => t.id === task.id);
|
|
1322
|
-
if (index >= 0) tasks[index] = task;
|
|
1323
|
-
else tasks.push(task);
|
|
1324
|
-
await this.write(tasks);
|
|
1325
|
-
});
|
|
1332
|
+
get path() {
|
|
1333
|
+
return this.filePath;
|
|
1326
1334
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1335
|
+
/** All items, or [] when missing/corrupt. */
|
|
1336
|
+
async readAll() {
|
|
1337
|
+
let raw;
|
|
1338
|
+
try {
|
|
1339
|
+
raw = await readFile(this.filePath, "utf8");
|
|
1340
|
+
} catch {
|
|
1341
|
+
return [];
|
|
1342
|
+
}
|
|
1343
|
+
try {
|
|
1344
|
+
const parsed = JSON.parse(raw);
|
|
1345
|
+
if (Array.isArray(parsed)) return parsed;
|
|
1346
|
+
if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.items)) {
|
|
1347
|
+
return parsed.items;
|
|
1348
|
+
}
|
|
1349
|
+
throw new Error("persisted file has an unrecognized shape");
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
this.log.warn(`Treating ${this.filePath} as empty \u2014 it could not be parsed.`);
|
|
1352
|
+
this.onCorrupt?.(this.filePath, error);
|
|
1353
|
+
return [];
|
|
1354
|
+
}
|
|
1332
1355
|
}
|
|
1333
|
-
async
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
}
|
|
1356
|
+
async writeAll(items) {
|
|
1357
|
+
const document = { schemaVersion: this.schemaVersion, items };
|
|
1358
|
+
const tmp = `${this.filePath}.tmp`;
|
|
1359
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
1360
|
+
await writeFile(tmp, `${JSON.stringify(document, null, 2)}
|
|
1361
|
+
`, "utf8");
|
|
1362
|
+
await rename(tmp, this.filePath);
|
|
1338
1363
|
}
|
|
1364
|
+
/** Serializes an async operation against this file. */
|
|
1339
1365
|
synchronized(operation) {
|
|
1340
1366
|
const result = this.queue.then(operation, operation);
|
|
1341
1367
|
this.queue = result.then(
|
|
@@ -1344,21 +1370,37 @@ var FileTaskStore = class {
|
|
|
1344
1370
|
);
|
|
1345
1371
|
return result;
|
|
1346
1372
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
return
|
|
1354
|
-
}
|
|
1373
|
+
/** Read-modify-write helper: applies `mutate` to the current items. */
|
|
1374
|
+
async mutate(mutate) {
|
|
1375
|
+
return this.synchronized(async () => {
|
|
1376
|
+
const items = await this.readAll();
|
|
1377
|
+
const [next, result] = await mutate(items);
|
|
1378
|
+
await this.writeAll(next);
|
|
1379
|
+
return result;
|
|
1380
|
+
});
|
|
1355
1381
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
// src/tasks/store.ts
|
|
1385
|
+
var FileTaskStore = class extends JsonFileStore {
|
|
1386
|
+
constructor(directory) {
|
|
1387
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "tasks.json" });
|
|
1388
|
+
}
|
|
1389
|
+
save(task) {
|
|
1390
|
+
return this.mutate((tasks) => {
|
|
1391
|
+
const index = tasks.findIndex((t) => t.id === task.id);
|
|
1392
|
+
if (index >= 0) tasks[index] = task;
|
|
1393
|
+
else tasks.push(task);
|
|
1394
|
+
return [tasks, void 0];
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
async get(id) {
|
|
1398
|
+
const tasks = await this.readAll();
|
|
1399
|
+
return tasks.find((t) => t.id === id);
|
|
1400
|
+
}
|
|
1401
|
+
async list(filter = {}) {
|
|
1402
|
+
const tasks = await this.readAll();
|
|
1403
|
+
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));
|
|
1362
1404
|
}
|
|
1363
1405
|
};
|
|
1364
1406
|
|
|
@@ -1367,12 +1409,18 @@ import { randomBytes } from "crypto";
|
|
|
1367
1409
|
var ALLOWED_TRANSITIONS = {
|
|
1368
1410
|
created: ["queued", "running", "cancelled"],
|
|
1369
1411
|
queued: ["running", "cancelled"],
|
|
1370
|
-
running: ["completed", "failed", "cancelled"],
|
|
1412
|
+
running: ["completed", "failed", "timed_out", "unknown", "cancelled", "partially_completed"],
|
|
1371
1413
|
waiting: ["running", "cancelled"],
|
|
1372
1414
|
completed: [],
|
|
1373
|
-
failed:
|
|
1374
|
-
|
|
1415
|
+
// failed → running is the only re-entry: bounded automatic retry (§6.4).
|
|
1416
|
+
failed: ["running"],
|
|
1417
|
+
timed_out: [],
|
|
1418
|
+
cancelled: [],
|
|
1419
|
+
unknown: [],
|
|
1420
|
+
partially_completed: []
|
|
1375
1421
|
};
|
|
1422
|
+
var RETRYABLE_ERROR_CODES = ["AGENT_FAILED"];
|
|
1423
|
+
var EXECUTION_HISTORY_LIMIT = 10;
|
|
1376
1424
|
var TaskEngine = class {
|
|
1377
1425
|
constructor(options) {
|
|
1378
1426
|
this.options = options;
|
|
@@ -1404,6 +1452,7 @@ var TaskEngine = class {
|
|
|
1404
1452
|
id: generateTaskId(),
|
|
1405
1453
|
objective: input.objective.trim(),
|
|
1406
1454
|
status: "created",
|
|
1455
|
+
attempt: 0,
|
|
1407
1456
|
createdAt: now,
|
|
1408
1457
|
updatedAt: now,
|
|
1409
1458
|
...input.project !== void 0 ? { project: input.project } : {},
|
|
@@ -1420,8 +1469,10 @@ var TaskEngine = class {
|
|
|
1420
1469
|
const task = await this.mustGet(id);
|
|
1421
1470
|
return this.transition(task, "queued");
|
|
1422
1471
|
}
|
|
1423
|
-
/** Runs the task through its assigned agent and records the result.
|
|
1424
|
-
|
|
1472
|
+
/** Runs the task through its assigned agent and records the result.
|
|
1473
|
+
* `maxAttempts` enables bounded automatic retries of *retryable* errors
|
|
1474
|
+
* with linear backoff; unknown/timed_out outcomes are never auto-retried. */
|
|
1475
|
+
async run(id, options = {}) {
|
|
1425
1476
|
const task = await this.mustGet(id);
|
|
1426
1477
|
if (task.status !== "created" && task.status !== "queued") {
|
|
1427
1478
|
throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be run.`);
|
|
@@ -1434,49 +1485,84 @@ var TaskEngine = class {
|
|
|
1434
1485
|
throw new TaskError("TASK_INVALID", `Agent "${task.agent}" is not registered anymore.`);
|
|
1435
1486
|
}
|
|
1436
1487
|
const role = task.role ? this.options.roles.get(task.role) : void 0;
|
|
1437
|
-
const running = await this.transition(task, "running");
|
|
1438
1488
|
this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
|
|
1439
|
-
let background =
|
|
1489
|
+
let background = task.context?.background;
|
|
1440
1490
|
if (this.options.memory) {
|
|
1441
1491
|
const memoryContext = await this.options.memory.gatherContext({
|
|
1442
|
-
objective:
|
|
1443
|
-
...
|
|
1444
|
-
...
|
|
1492
|
+
objective: task.objective,
|
|
1493
|
+
...task.role ? { role: task.role } : {},
|
|
1494
|
+
...task.project ? { project: task.project } : {}
|
|
1445
1495
|
});
|
|
1446
1496
|
if (memoryContext.length > 0) {
|
|
1447
1497
|
background = [memoryContext, background].filter((part) => part && part.length > 0).join("\n\n");
|
|
1448
1498
|
}
|
|
1449
1499
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
});
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1500
|
+
const maxAttempts = Math.max(1, options.maxAttempts ?? 1);
|
|
1501
|
+
const backoffMs = options.retryBackoffMs ?? 0;
|
|
1502
|
+
let current = task;
|
|
1503
|
+
for (let i = 0; i < maxAttempts; i += 1) {
|
|
1504
|
+
const attempt = current.attempt + 1;
|
|
1505
|
+
const executionId = `exec-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
1506
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1507
|
+
current = await this.transition(current, "running", { attempt });
|
|
1508
|
+
let output;
|
|
1509
|
+
let thrown;
|
|
1510
|
+
try {
|
|
1511
|
+
output = await agent.run({
|
|
1512
|
+
taskId: current.id,
|
|
1513
|
+
objective: current.objective,
|
|
1514
|
+
...role ? { role } : {},
|
|
1515
|
+
...background !== void 0 ? { context: background } : {}
|
|
1516
|
+
});
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
thrown = error;
|
|
1519
|
+
}
|
|
1520
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1521
|
+
if (thrown !== void 0 || output === void 0) {
|
|
1522
|
+
const outcome = classifyThrown(thrown);
|
|
1523
|
+
const message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
1524
|
+
const retryable = outcome === "failed" && RETRYABLE_ERROR_CODES.includes(errorCode(thrown)) && i < maxAttempts - 1;
|
|
1525
|
+
current = await this.transition(current, outcome, {
|
|
1526
|
+
attempt,
|
|
1527
|
+
lastError: message,
|
|
1528
|
+
execution: {
|
|
1529
|
+
executionId,
|
|
1530
|
+
attempt,
|
|
1531
|
+
startedAt,
|
|
1532
|
+
finishedAt,
|
|
1533
|
+
outcome,
|
|
1534
|
+
error: message
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
this.log.warn(`Task ${current.id} attempt ${attempt} \u2192 ${outcome}: ${message}`);
|
|
1538
|
+
if (retryable) {
|
|
1539
|
+
if (backoffMs > 0) await sleep(backoffMs * attempt);
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (this.options.memory) await this.options.memory.recordTaskOutcome(current);
|
|
1543
|
+
return current;
|
|
1544
|
+
}
|
|
1545
|
+
const result = {
|
|
1546
|
+
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
1547
|
+
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
1548
|
+
// Keep the verbatim output (bounded) for report extraction and audit.
|
|
1549
|
+
...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
|
|
1550
|
+
...output.error !== void 0 ? { error: output.error } : {},
|
|
1551
|
+
finishedAt
|
|
1552
|
+
};
|
|
1553
|
+
const finalStatus = output.status === "completed" ? "completed" : "failed";
|
|
1554
|
+
current = await this.transition(current, finalStatus, {
|
|
1555
|
+
attempt,
|
|
1556
|
+
result,
|
|
1557
|
+
execution: { executionId, attempt, startedAt, finishedAt, outcome: finalStatus }
|
|
1463
1558
|
});
|
|
1559
|
+
this.log.info(`Task ${current.id} finished: ${finalStatus}.`);
|
|
1560
|
+
break;
|
|
1464
1561
|
}
|
|
1465
|
-
const result = {
|
|
1466
|
-
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
1467
|
-
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
1468
|
-
// Keep the verbatim output (bounded) for report extraction and audit.
|
|
1469
|
-
...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
|
|
1470
|
-
...output.error !== void 0 ? { error: output.error } : {},
|
|
1471
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1472
|
-
};
|
|
1473
|
-
const finalStatus = output.status === "completed" ? "completed" : "failed";
|
|
1474
|
-
const finished = await this.transition(running, finalStatus, { result });
|
|
1475
|
-
this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
|
|
1476
1562
|
if (this.options.memory) {
|
|
1477
|
-
await this.options.memory.recordTaskOutcome(
|
|
1563
|
+
await this.options.memory.recordTaskOutcome(current);
|
|
1478
1564
|
}
|
|
1479
|
-
return
|
|
1565
|
+
return current;
|
|
1480
1566
|
}
|
|
1481
1567
|
async cancel(id) {
|
|
1482
1568
|
const task = await this.mustGet(id);
|
|
@@ -1489,13 +1575,14 @@ var TaskEngine = class {
|
|
|
1489
1575
|
}
|
|
1490
1576
|
return this.transition(task, "cancelled");
|
|
1491
1577
|
}
|
|
1492
|
-
/** Error recovery
|
|
1578
|
+
/** Error recovery: reset a failed/cancelled/unknown/timed_out task so it can run again. */
|
|
1493
1579
|
async retry(id) {
|
|
1494
1580
|
const task = await this.mustGet(id);
|
|
1495
|
-
|
|
1581
|
+
const retryableStates = ["failed", "cancelled", "unknown", "timed_out"];
|
|
1582
|
+
if (!retryableStates.includes(task.status)) {
|
|
1496
1583
|
throw new TaskError(
|
|
1497
1584
|
"TASK_INVALID",
|
|
1498
|
-
`Only failed or cancelled tasks can be retried \u2014 task ${id} is "${task.status}".`
|
|
1585
|
+
`Only failed, timed out, unknown or cancelled tasks can be retried \u2014 task ${id} is "${task.status}".`
|
|
1499
1586
|
);
|
|
1500
1587
|
}
|
|
1501
1588
|
const { result: _discarded, ...rest } = task;
|
|
@@ -1527,10 +1614,14 @@ var TaskEngine = class {
|
|
|
1527
1614
|
if (!allowed.includes(to)) {
|
|
1528
1615
|
throw new TaskError("TASK_INVALID", `Task ${task.id} cannot move from "${task.status}" to "${to}".`);
|
|
1529
1616
|
}
|
|
1617
|
+
const executions = extra.execution ? [...task.executions ?? [], extra.execution].slice(-EXECUTION_HISTORY_LIMIT) : task.executions;
|
|
1530
1618
|
const updated = {
|
|
1531
1619
|
...task,
|
|
1532
1620
|
status: to,
|
|
1533
1621
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1622
|
+
...extra.attempt !== void 0 ? { attempt: extra.attempt } : {},
|
|
1623
|
+
...extra.lastError !== void 0 ? { lastError: extra.lastError } : {},
|
|
1624
|
+
...executions !== void 0 ? { executions } : {},
|
|
1534
1625
|
...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
|
|
1535
1626
|
};
|
|
1536
1627
|
await this.options.store.save(updated);
|
|
@@ -1551,6 +1642,19 @@ var TaskEngine = class {
|
|
|
1551
1642
|
function generateTaskId() {
|
|
1552
1643
|
return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
1553
1644
|
}
|
|
1645
|
+
function errorCode(error) {
|
|
1646
|
+
return error?.code ?? "";
|
|
1647
|
+
}
|
|
1648
|
+
function classifyThrown(error) {
|
|
1649
|
+
if (error instanceof AgentError && error.code === "AGENT_TIMEOUT") {
|
|
1650
|
+
const dispatched = error.details?.dispatched;
|
|
1651
|
+
return dispatched === false ? "timed_out" : "unknown";
|
|
1652
|
+
}
|
|
1653
|
+
return "failed";
|
|
1654
|
+
}
|
|
1655
|
+
function sleep(ms) {
|
|
1656
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1657
|
+
}
|
|
1554
1658
|
|
|
1555
1659
|
// src/audit/index.ts
|
|
1556
1660
|
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
@@ -1575,10 +1679,10 @@ var FileAuditLog = class {
|
|
|
1575
1679
|
}
|
|
1576
1680
|
/** Recent events, oldest first; used by the CLI. */
|
|
1577
1681
|
async recent(limit = 20) {
|
|
1578
|
-
const { readFile:
|
|
1682
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
1579
1683
|
let raw;
|
|
1580
1684
|
try {
|
|
1581
|
-
raw = await
|
|
1685
|
+
raw = await readFile2(this.filePath, "utf8");
|
|
1582
1686
|
} catch {
|
|
1583
1687
|
return [];
|
|
1584
1688
|
}
|
|
@@ -1594,8 +1698,6 @@ var FileAuditLog = class {
|
|
|
1594
1698
|
};
|
|
1595
1699
|
|
|
1596
1700
|
// src/memory/local.ts
|
|
1597
|
-
import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
1598
|
-
import { dirname as dirname3 } from "path";
|
|
1599
1701
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
1600
1702
|
"the",
|
|
1601
1703
|
"and",
|
|
@@ -1652,29 +1754,24 @@ function relevanceScore(queryKeywords, entry2) {
|
|
|
1652
1754
|
}
|
|
1653
1755
|
return overlap * 2 + tagMatches * 1.5;
|
|
1654
1756
|
}
|
|
1655
|
-
var LocalMemoryProvider = class {
|
|
1757
|
+
var LocalMemoryProvider = class extends JsonFileStore {
|
|
1656
1758
|
name = "local";
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
1660
|
-
this.filePath = `${directory}/memory.json`;
|
|
1759
|
+
constructor(directory) {
|
|
1760
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "memory.json" });
|
|
1661
1761
|
}
|
|
1662
1762
|
async query(query) {
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
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);
|
|
1673
|
-
});
|
|
1763
|
+
const entries = await this.readAll();
|
|
1764
|
+
const filtered = entries.filter((entry2) => query.scope ? entry2.scope === query.scope : true).filter(
|
|
1765
|
+
(entry2) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry2.tags ?? []).includes(tag)) : true
|
|
1766
|
+
);
|
|
1767
|
+
if (!query.text || query.text.trim().length === 0) {
|
|
1768
|
+
return filtered.sort((a, b) => (b.updatedAt ?? b.createdAt ?? "").localeCompare(a.updatedAt ?? a.createdAt ?? "")).slice(0, query.limit ?? 10);
|
|
1769
|
+
}
|
|
1770
|
+
const queryKeywords = keywords(query.text);
|
|
1771
|
+
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);
|
|
1674
1772
|
}
|
|
1675
|
-
|
|
1676
|
-
return this.
|
|
1677
|
-
const entries = await this.load();
|
|
1773
|
+
write(entry2) {
|
|
1774
|
+
return this.mutate((entries) => {
|
|
1678
1775
|
const index = entries.findIndex((e) => e.key === entry2.key);
|
|
1679
1776
|
const stamped = {
|
|
1680
1777
|
...entry2,
|
|
@@ -1683,35 +1780,11 @@ var LocalMemoryProvider = class {
|
|
|
1683
1780
|
};
|
|
1684
1781
|
if (index >= 0) entries[index] = stamped;
|
|
1685
1782
|
else entries.push(stamped);
|
|
1686
|
-
|
|
1783
|
+
return [entries, void 0];
|
|
1687
1784
|
});
|
|
1688
1785
|
}
|
|
1689
1786
|
async count() {
|
|
1690
|
-
return (await this.
|
|
1691
|
-
}
|
|
1692
|
-
synchronized(operation) {
|
|
1693
|
-
const result = this.queue.then(operation, operation);
|
|
1694
|
-
this.queue = result.then(
|
|
1695
|
-
() => void 0,
|
|
1696
|
-
() => void 0
|
|
1697
|
-
);
|
|
1698
|
-
return result;
|
|
1699
|
-
}
|
|
1700
|
-
async load() {
|
|
1701
|
-
try {
|
|
1702
|
-
const raw = await readFile2(this.filePath, "utf8");
|
|
1703
|
-
const parsed = JSON.parse(raw);
|
|
1704
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
1705
|
-
} catch {
|
|
1706
|
-
return [];
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
async saveAll(entries) {
|
|
1710
|
-
const tmp = `${this.filePath}.tmp`;
|
|
1711
|
-
await mkdir3(dirname3(this.filePath), { recursive: true });
|
|
1712
|
-
await writeFile2(tmp, `${JSON.stringify(entries, null, 2)}
|
|
1713
|
-
`, "utf8");
|
|
1714
|
-
await rename2(tmp, this.filePath);
|
|
1787
|
+
return (await this.readAll()).length;
|
|
1715
1788
|
}
|
|
1716
1789
|
};
|
|
1717
1790
|
|
|
@@ -1981,7 +2054,7 @@ function registerTaskCommands(program) {
|
|
|
1981
2054
|
const cancelled = await engine.cancel(taskId);
|
|
1982
2055
|
console.log(`Task ${cancelled.id} is now "${cancelled.status}".`);
|
|
1983
2056
|
});
|
|
1984
|
-
task.command("retry <task-id>").description("Reset a failed
|
|
2057
|
+
task.command("retry <task-id>").description("Reset a failed, timed out, unknown or cancelled task to created; combine with --run to execute it again.").option("--run", "Run the task again immediately after resetting it.").action(async (taskId, options) => {
|
|
1985
2058
|
const config = loadConfig();
|
|
1986
2059
|
const engine = createTaskEngine(config);
|
|
1987
2060
|
const reset = await engine.retry(taskId);
|
|
@@ -2004,6 +2077,15 @@ function printOutcome(task, options = {}) {
|
|
|
2004
2077
|
if (task.context?.files) console.log(` files: ${task.context.files.join(", ")}`);
|
|
2005
2078
|
console.log(` objective: ${task.objective}`);
|
|
2006
2079
|
}
|
|
2080
|
+
if (options.verbose && task.attempt > 0) {
|
|
2081
|
+
console.log(` attempt: ${task.attempt}`);
|
|
2082
|
+
}
|
|
2083
|
+
if (task.lastError) console.log(` error: ${task.lastError}`);
|
|
2084
|
+
if (options.verbose && task.executions && task.executions.length > 0) {
|
|
2085
|
+
console.log(
|
|
2086
|
+
` executions: ${task.executions.map((e) => `#${e.attempt}:${e.outcome}`).join(", ")}`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2007
2089
|
const result = task.result;
|
|
2008
2090
|
if (!result) return;
|
|
2009
2091
|
if (result.summary) console.log(` summary: ${result.summary}`);
|
|
@@ -2228,84 +2310,39 @@ var ReportService = class {
|
|
|
2228
2310
|
};
|
|
2229
2311
|
|
|
2230
2312
|
// src/reports/store.ts
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
filePath;
|
|
2235
|
-
queue = Promise.resolve();
|
|
2236
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
2237
|
-
this.filePath = `${directory}/reports.json`;
|
|
2313
|
+
var FileReportStore = class extends JsonFileStore {
|
|
2314
|
+
constructor(directory) {
|
|
2315
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "reports.json" });
|
|
2238
2316
|
}
|
|
2239
2317
|
async saveReports(reports) {
|
|
2240
|
-
|
|
2241
|
-
const entries = await this.load();
|
|
2318
|
+
await this.mutate((entries) => {
|
|
2242
2319
|
for (const report of reports) {
|
|
2243
|
-
const index = entries.findIndex(
|
|
2244
|
-
(e) => e.kind === "report" && e.report.id === report.id
|
|
2245
|
-
);
|
|
2320
|
+
const index = entries.findIndex((e) => e.kind === "report" && e.report.id === report.id);
|
|
2246
2321
|
const entry2 = { kind: "report", report };
|
|
2247
2322
|
if (index >= 0) entries[index] = entry2;
|
|
2248
2323
|
else entries.push(entry2);
|
|
2249
2324
|
}
|
|
2250
|
-
|
|
2325
|
+
return [entries, void 0];
|
|
2251
2326
|
});
|
|
2252
2327
|
}
|
|
2253
2328
|
async listReports(filter = {}) {
|
|
2254
|
-
return this.
|
|
2255
|
-
const entries = await this.load();
|
|
2256
|
-
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));
|
|
2257
|
-
});
|
|
2329
|
+
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));
|
|
2258
2330
|
}
|
|
2259
2331
|
async saveCombined(combined) {
|
|
2260
|
-
|
|
2261
|
-
const
|
|
2262
|
-
const index = entries.findIndex(
|
|
2263
|
-
(e) => e.kind === "combined" && e.combined.id === combined.id
|
|
2264
|
-
);
|
|
2332
|
+
await this.mutate((entries) => {
|
|
2333
|
+
const index = entries.findIndex((e) => e.kind === "combined" && e.combined.id === combined.id);
|
|
2265
2334
|
const entry2 = { kind: "combined", combined };
|
|
2266
2335
|
if (index >= 0) entries[index] = entry2;
|
|
2267
2336
|
else entries.push(entry2);
|
|
2268
|
-
|
|
2337
|
+
return [entries, void 0];
|
|
2269
2338
|
});
|
|
2270
2339
|
}
|
|
2271
2340
|
async getCombined(id) {
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
const found = entries.find((e) => e.kind === "combined" && e.combined.id === id);
|
|
2275
|
-
return found?.kind === "combined" ? found.combined : void 0;
|
|
2276
|
-
});
|
|
2341
|
+
const found = (await this.readAll()).find((e) => e.kind === "combined" && e.combined.id === id);
|
|
2342
|
+
return found?.kind === "combined" ? found.combined : void 0;
|
|
2277
2343
|
}
|
|
2278
2344
|
async listCombined(filter = {}) {
|
|
2279
|
-
return this.
|
|
2280
|
-
const entries = await this.load();
|
|
2281
|
-
return entries.filter((e) => e.kind === "combined").map((e) => e.combined).filter(
|
|
2282
|
-
(combined) => filter.pipelineRunId ? combined.pipelineRunId === filter.pipelineRunId : true
|
|
2283
|
-
).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2284
|
-
});
|
|
2285
|
-
}
|
|
2286
|
-
synchronized(operation) {
|
|
2287
|
-
const result = this.queue.then(operation, operation);
|
|
2288
|
-
this.queue = result.then(
|
|
2289
|
-
() => void 0,
|
|
2290
|
-
() => void 0
|
|
2291
|
-
);
|
|
2292
|
-
return result;
|
|
2293
|
-
}
|
|
2294
|
-
async load() {
|
|
2295
|
-
try {
|
|
2296
|
-
const raw = await readFile3(this.filePath, "utf8");
|
|
2297
|
-
const parsed = JSON.parse(raw);
|
|
2298
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
2299
|
-
} catch {
|
|
2300
|
-
return [];
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
async write(entries) {
|
|
2304
|
-
const tmp = `${this.filePath}.tmp`;
|
|
2305
|
-
await mkdir4(dirname4(this.filePath), { recursive: true });
|
|
2306
|
-
await writeFile3(tmp, `${JSON.stringify(entries, null, 2)}
|
|
2307
|
-
`, "utf8");
|
|
2308
|
-
await rename3(tmp, this.filePath);
|
|
2345
|
+
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));
|
|
2309
2346
|
}
|
|
2310
2347
|
};
|
|
2311
2348
|
|
|
@@ -2789,7 +2826,7 @@ ${contribution}`);
|
|
|
2789
2826
|
}
|
|
2790
2827
|
const stepRun = {
|
|
2791
2828
|
stepId: step.id,
|
|
2792
|
-
status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : "completed",
|
|
2829
|
+
status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : outcome.status === "partial" ? "partially_completed" : "completed",
|
|
2793
2830
|
...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
|
|
2794
2831
|
...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
|
|
2795
2832
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
@@ -2800,8 +2837,10 @@ ${contribution}`);
|
|
|
2800
2837
|
run2.reports = [...allReports];
|
|
2801
2838
|
await this.options.store.save(run2);
|
|
2802
2839
|
}
|
|
2803
|
-
const
|
|
2804
|
-
const
|
|
2840
|
+
const allOutcomes = [...outcomes.values()];
|
|
2841
|
+
const failed = allOutcomes.some((o) => o.status === "failed");
|
|
2842
|
+
const partial = !failed && allOutcomes.some((o) => o.status === "partial");
|
|
2843
|
+
const finalStatus = failed ? "failed" : partial ? "partial" : "completed";
|
|
2805
2844
|
const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
|
|
2806
2845
|
let resultSummary;
|
|
2807
2846
|
for (let i = def.steps.length - 1; i >= 0; i -= 1) {
|
|
@@ -2907,6 +2946,7 @@ ${contribution}`);
|
|
|
2907
2946
|
)
|
|
2908
2947
|
);
|
|
2909
2948
|
const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
|
|
2949
|
+
const completedTasks = results.filter((r) => r.status === "completed");
|
|
2910
2950
|
const failedTasks = results.filter((r) => r.status !== "completed");
|
|
2911
2951
|
const lines = results.map((result, index) => {
|
|
2912
2952
|
const agentName = agentNames[index] ?? "unknown";
|
|
@@ -2916,12 +2956,15 @@ ${contribution}`);
|
|
|
2916
2956
|
const reports = results.flatMap((r) => r.result?.reports ?? []);
|
|
2917
2957
|
const summary = lines.join("\n");
|
|
2918
2958
|
if (failedTasks.length > 0) {
|
|
2919
|
-
const firstError = failedTasks[0]?.result?.error ?? "unknown
|
|
2959
|
+
const firstError = failedTasks[0]?.result?.error ?? failedTasks[0]?.lastError ?? "unknown outcome";
|
|
2960
|
+
const states = failedTasks.map((r) => r.status).join(", ");
|
|
2920
2961
|
return {
|
|
2921
|
-
|
|
2962
|
+
// Some agents succeeded → the step produced usable but incomplete
|
|
2963
|
+
// intelligence: partial, not failed (roadmap §9.3).
|
|
2964
|
+
status: completedTasks.length > 0 ? "partial" : "failed",
|
|
2922
2965
|
summary,
|
|
2923
2966
|
taskIds: created.map((t) => t.id),
|
|
2924
|
-
error: `${failedTasks.length}/${results.length} task(s)
|
|
2967
|
+
error: `${failedTasks.length}/${results.length} task(s) incomplete (${states}) \u2014 ${firstError}`,
|
|
2925
2968
|
reports
|
|
2926
2969
|
};
|
|
2927
2970
|
}
|
|
@@ -2937,7 +2980,9 @@ ${contribution}`);
|
|
|
2937
2980
|
const result = await this.options.tasks.run(task.id);
|
|
2938
2981
|
if (result.status !== "completed") {
|
|
2939
2982
|
return {
|
|
2940
|
-
|
|
2983
|
+
// Unknown/timed_out outcomes are not failures — the work may have
|
|
2984
|
+
// happened remotely; the step is incomplete, not broken.
|
|
2985
|
+
status: result.status === "unknown" || result.status === "timed_out" ? "partial" : "failed",
|
|
2941
2986
|
summary: result.result?.summary ?? "",
|
|
2942
2987
|
taskIds: [task.id],
|
|
2943
2988
|
error: result.result?.error ?? `task ${task.id} did not complete`,
|
|
@@ -3053,114 +3098,44 @@ function createDefaultChatFn(providers) {
|
|
|
3053
3098
|
}
|
|
3054
3099
|
|
|
3055
3100
|
// src/pipelines/store.ts
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
filePath;
|
|
3060
|
-
queue = Promise.resolve();
|
|
3061
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
3062
|
-
this.filePath = `${directory}/pipelines.json`;
|
|
3101
|
+
var FilePipelineRunStore = class extends JsonFileStore {
|
|
3102
|
+
constructor(directory) {
|
|
3103
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "pipelines.json" });
|
|
3063
3104
|
}
|
|
3064
|
-
|
|
3065
|
-
return this.
|
|
3066
|
-
const runs = await this.load();
|
|
3105
|
+
save(run2) {
|
|
3106
|
+
return this.mutate((runs) => {
|
|
3067
3107
|
const index = runs.findIndex((r) => r.id === run2.id);
|
|
3068
3108
|
if (index >= 0) runs[index] = run2;
|
|
3069
3109
|
else runs.push(run2);
|
|
3070
|
-
|
|
3110
|
+
return [runs, void 0];
|
|
3071
3111
|
});
|
|
3072
3112
|
}
|
|
3073
3113
|
async get(id) {
|
|
3074
|
-
return this.
|
|
3075
|
-
const runs = await this.load();
|
|
3076
|
-
return runs.find((r) => r.id === id);
|
|
3077
|
-
});
|
|
3114
|
+
return (await this.readAll()).find((r) => r.id === id);
|
|
3078
3115
|
}
|
|
3079
3116
|
async list(filter = {}) {
|
|
3080
|
-
return this.
|
|
3081
|
-
const runs = await this.load();
|
|
3082
|
-
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 ?? ""));
|
|
3083
|
-
});
|
|
3084
|
-
}
|
|
3085
|
-
synchronized(operation) {
|
|
3086
|
-
const result = this.queue.then(operation, operation);
|
|
3087
|
-
this.queue = result.then(
|
|
3088
|
-
() => void 0,
|
|
3089
|
-
() => void 0
|
|
3090
|
-
);
|
|
3091
|
-
return result;
|
|
3092
|
-
}
|
|
3093
|
-
async load() {
|
|
3094
|
-
try {
|
|
3095
|
-
const raw = await readFile4(this.filePath, "utf8");
|
|
3096
|
-
const parsed = JSON.parse(raw);
|
|
3097
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
3098
|
-
} catch {
|
|
3099
|
-
return [];
|
|
3100
|
-
}
|
|
3101
|
-
}
|
|
3102
|
-
async write(runs) {
|
|
3103
|
-
const tmp = `${this.filePath}.tmp`;
|
|
3104
|
-
await mkdir5(dirname5(this.filePath), { recursive: true });
|
|
3105
|
-
await writeFile4(tmp, `${JSON.stringify(runs, null, 2)}
|
|
3106
|
-
`, "utf8");
|
|
3107
|
-
await rename4(tmp, this.filePath);
|
|
3117
|
+
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 ?? ""));
|
|
3108
3118
|
}
|
|
3109
3119
|
};
|
|
3110
3120
|
|
|
3111
3121
|
// src/planner/store.ts
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
filePath;
|
|
3116
|
-
queue = Promise.resolve();
|
|
3117
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
3118
|
-
this.filePath = `${directory}/plans.json`;
|
|
3122
|
+
var FilePlanStore = class extends JsonFileStore {
|
|
3123
|
+
constructor(directory) {
|
|
3124
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "plans.json" });
|
|
3119
3125
|
}
|
|
3120
|
-
|
|
3121
|
-
return this.
|
|
3122
|
-
const plans = await this.load();
|
|
3126
|
+
save(plan) {
|
|
3127
|
+
return this.mutate((plans) => {
|
|
3123
3128
|
const index = plans.findIndex((p) => p.id === plan.id);
|
|
3124
3129
|
if (index >= 0) plans[index] = plan;
|
|
3125
3130
|
else plans.push(plan);
|
|
3126
|
-
|
|
3131
|
+
return [plans, void 0];
|
|
3127
3132
|
});
|
|
3128
3133
|
}
|
|
3129
3134
|
async get(id) {
|
|
3130
|
-
return this.
|
|
3131
|
-
const plans = await this.load();
|
|
3132
|
-
return plans.find((p) => p.id === id);
|
|
3133
|
-
});
|
|
3135
|
+
return (await this.readAll()).find((p) => p.id === id);
|
|
3134
3136
|
}
|
|
3135
3137
|
async list(filter = {}) {
|
|
3136
|
-
return this.
|
|
3137
|
-
const plans = await this.load();
|
|
3138
|
-
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));
|
|
3139
|
-
});
|
|
3140
|
-
}
|
|
3141
|
-
synchronized(operation) {
|
|
3142
|
-
const result = this.queue.then(operation, operation);
|
|
3143
|
-
this.queue = result.then(
|
|
3144
|
-
() => void 0,
|
|
3145
|
-
() => void 0
|
|
3146
|
-
);
|
|
3147
|
-
return result;
|
|
3148
|
-
}
|
|
3149
|
-
async load() {
|
|
3150
|
-
try {
|
|
3151
|
-
const raw = await readFile5(this.filePath, "utf8");
|
|
3152
|
-
const parsed = JSON.parse(raw);
|
|
3153
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
3154
|
-
} catch {
|
|
3155
|
-
return [];
|
|
3156
|
-
}
|
|
3157
|
-
}
|
|
3158
|
-
async write(plans) {
|
|
3159
|
-
const tmp = `${this.filePath}.tmp`;
|
|
3160
|
-
await mkdir6(dirname6(this.filePath), { recursive: true });
|
|
3161
|
-
await writeFile5(tmp, `${JSON.stringify(plans, null, 2)}
|
|
3162
|
-
`, "utf8");
|
|
3163
|
-
await rename5(tmp, this.filePath);
|
|
3138
|
+
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));
|
|
3164
3139
|
}
|
|
3165
3140
|
};
|
|
3166
3141
|
|