@hilbras/omninode 2.0.0-alpha.3 → 2.0.0-alpha.5
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 +32 -9
- package/dist/cli/index.js +369 -47
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +221 -11
- package/dist/index.js +341 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -140,6 +140,11 @@ var agentConfigSchema = z.object({
|
|
|
140
140
|
input_mode: z.enum(["stdin", "arg", "protocol"]).default("stdin"),
|
|
141
141
|
timeout_ms: z.number().int().positive().optional(),
|
|
142
142
|
inherit_env: z.boolean().optional(),
|
|
143
|
+
env_policy: z.enum(["inherit", "allowlist", "denylist", "explicit"]).optional(),
|
|
144
|
+
env_allowlist: z.array(z.string()).optional(),
|
|
145
|
+
env_denylist: z.array(z.string()).optional(),
|
|
146
|
+
max_output_bytes: z.number().int().positive().optional(),
|
|
147
|
+
allow_external_cwd: z.boolean().optional(),
|
|
143
148
|
metadata: z.record(z.unknown()).optional()
|
|
144
149
|
}).strict();
|
|
145
150
|
var roleConfigSchema = z.object({
|
|
@@ -314,6 +319,11 @@ function toAgentConfig(raw) {
|
|
|
314
319
|
inputMode: raw.input_mode,
|
|
315
320
|
timeoutMs: raw.timeout_ms,
|
|
316
321
|
inheritEnv: raw.inherit_env,
|
|
322
|
+
envPolicy: raw.env_policy,
|
|
323
|
+
envAllowlist: raw.env_allowlist,
|
|
324
|
+
envDenylist: raw.env_denylist,
|
|
325
|
+
maxOutputBytes: raw.max_output_bytes,
|
|
326
|
+
allowExternalCwd: raw.allow_external_cwd,
|
|
317
327
|
metadata: raw.metadata
|
|
318
328
|
};
|
|
319
329
|
}
|
|
@@ -1010,20 +1020,94 @@ function requireCompletion(session) {
|
|
|
1010
1020
|
}
|
|
1011
1021
|
}
|
|
1012
1022
|
|
|
1013
|
-
// src/agents/
|
|
1014
|
-
|
|
1015
|
-
|
|
1023
|
+
// src/agents/cwd.ts
|
|
1024
|
+
import { statSync } from "fs";
|
|
1025
|
+
import { isAbsolute, relative, resolve } from "path";
|
|
1026
|
+
function validateWorkingDirectory(cwd, options = {}) {
|
|
1027
|
+
if (!cwd) return void 0;
|
|
1028
|
+
const normalized = isAbsolute(cwd) ? resolve(cwd) : resolve(process.cwd(), cwd);
|
|
1029
|
+
let stats;
|
|
1030
|
+
try {
|
|
1031
|
+
stats = statSync(normalized);
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
throw new AgentError(
|
|
1034
|
+
"AGENT_NOT_FOUND",
|
|
1035
|
+
`Working directory "${normalized}" does not exist or is not accessible.`,
|
|
1036
|
+
{ cause: error, details: { cwd: normalized } }
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
if (!stats.isDirectory()) {
|
|
1040
|
+
throw new AgentError("AGENT_NOT_FOUND", `Working directory "${normalized}" is not a directory.`, {
|
|
1041
|
+
details: { cwd: normalized }
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
try {
|
|
1045
|
+
statSync(resolve(normalized, "."));
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
throw new AgentError(
|
|
1048
|
+
"AGENT_NOT_FOUND",
|
|
1049
|
+
`Working directory "${normalized}" is not accessible.`,
|
|
1050
|
+
{ cause: error }
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
1054
|
+
const rel = relative(projectRoot, normalized);
|
|
1055
|
+
const outsideProject = rel.startsWith("..") || isAbsolute(rel);
|
|
1056
|
+
if (outsideProject && options.allowExternal !== true) {
|
|
1057
|
+
throw new AgentError(
|
|
1058
|
+
"AGENT_NOT_FOUND",
|
|
1059
|
+
`Working directory "${normalized}" is outside the project root "${projectRoot}". Set allow_external_cwd: true on the agent to permit this.`,
|
|
1060
|
+
{ details: { cwd: normalized, projectRoot } }
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
return { cwd: normalized, outsideProject };
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// src/agents/env.ts
|
|
1067
|
+
var BASELINE_KEYS = ["PATH", "HOME"];
|
|
1068
|
+
function resolveEnvPolicy(config) {
|
|
1069
|
+
if (config.envPolicy !== void 0) return config.envPolicy;
|
|
1070
|
+
if (config.inheritEnv === false) return "explicit";
|
|
1071
|
+
return "inherit";
|
|
1072
|
+
}
|
|
1016
1073
|
function buildChildEnv(config) {
|
|
1017
1074
|
const configured = config.env ?? {};
|
|
1018
|
-
|
|
1075
|
+
const policy = resolveEnvPolicy(config);
|
|
1076
|
+
if (policy === "inherit") {
|
|
1019
1077
|
return { ...process.env, ...configured };
|
|
1020
1078
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1079
|
+
const base = {};
|
|
1080
|
+
for (const key of BASELINE_KEYS) {
|
|
1081
|
+
const value = process.env[key];
|
|
1082
|
+
if (value !== void 0) base[key] = value;
|
|
1083
|
+
}
|
|
1084
|
+
switch (policy) {
|
|
1085
|
+
case "explicit":
|
|
1086
|
+
return { ...base, ...configured };
|
|
1087
|
+
case "allowlist": {
|
|
1088
|
+
const allowed = { ...base };
|
|
1089
|
+
for (const key of config.envAllowlist ?? []) {
|
|
1090
|
+
const value = process.env[key];
|
|
1091
|
+
if (value !== void 0) allowed[key] = value;
|
|
1092
|
+
}
|
|
1093
|
+
return { ...allowed, ...configured };
|
|
1094
|
+
}
|
|
1095
|
+
case "denylist": {
|
|
1096
|
+
const denied = new Set((config.envDenylist ?? []).map((k) => k.toUpperCase()));
|
|
1097
|
+
const filtered = {};
|
|
1098
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1099
|
+
if (denied.has(key.toUpperCase())) continue;
|
|
1100
|
+
filtered[key] = value;
|
|
1101
|
+
}
|
|
1102
|
+
return { ...filtered, ...configured };
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1026
1105
|
}
|
|
1106
|
+
|
|
1107
|
+
// src/agents/process/index.ts
|
|
1108
|
+
var DEFAULT_TIMEOUT_MS2 = 6e5;
|
|
1109
|
+
var KILL_GRACE_MS = 5e3;
|
|
1110
|
+
var DEFAULT_MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
1027
1111
|
function composeTaskText(input) {
|
|
1028
1112
|
const sections = [`# Objective
|
|
1029
1113
|
${input.objective}`];
|
|
@@ -1085,40 +1169,73 @@ var ProcessAgent = class {
|
|
|
1085
1169
|
}
|
|
1086
1170
|
const mode = this.config.inputMode ?? "stdin";
|
|
1087
1171
|
const timeoutMs = this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
1088
|
-
return new Promise((
|
|
1172
|
+
return new Promise((resolve2, reject) => {
|
|
1173
|
+
let cwd;
|
|
1174
|
+
try {
|
|
1175
|
+
cwd = validateWorkingDirectory(this.config.cwd, {
|
|
1176
|
+
allowExternal: this.config.allowExternalCwd
|
|
1177
|
+
})?.cwd;
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
reject(error);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1089
1182
|
const child = spawn(
|
|
1090
1183
|
command,
|
|
1091
1184
|
[...this.config.args ?? [], ...mode === "arg" ? [composeTaskText(input)] : []],
|
|
1092
1185
|
{
|
|
1093
|
-
cwd:
|
|
1186
|
+
...cwd !== void 0 ? { cwd } : {},
|
|
1094
1187
|
env: buildChildEnv(this.config),
|
|
1095
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1188
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1189
|
+
// Own process group so the whole tree can be terminated together
|
|
1190
|
+
// (roadmap §8: no orphaned children).
|
|
1191
|
+
...process.platform === "win32" ? {} : { detached: true }
|
|
1096
1192
|
}
|
|
1097
1193
|
);
|
|
1098
1194
|
this.running.set(input.taskId, child);
|
|
1195
|
+
GLOBAL_RUNNING.add(child);
|
|
1196
|
+
installSignalForwarding();
|
|
1099
1197
|
let stdout = "";
|
|
1100
1198
|
let stderr = "";
|
|
1101
1199
|
let timedOut = false;
|
|
1200
|
+
let outputLimitExceeded = false;
|
|
1102
1201
|
let settled = false;
|
|
1202
|
+
const maxOutputBytes = this.config.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
1103
1203
|
const timer = setTimeout(() => {
|
|
1104
1204
|
timedOut = true;
|
|
1105
|
-
this.log.warn(`Task ${input.taskId} exceeded ${timeoutMs}ms \u2014 killing process.`);
|
|
1106
|
-
child
|
|
1107
|
-
setTimeout(() => child
|
|
1205
|
+
this.log.warn(`Task ${input.taskId} exceeded ${timeoutMs}ms \u2014 killing process tree.`);
|
|
1206
|
+
killTree(child, "SIGTERM");
|
|
1207
|
+
setTimeout(() => killTree(child, "SIGKILL"), KILL_GRACE_MS).unref();
|
|
1108
1208
|
}, timeoutMs);
|
|
1109
1209
|
const settle = (finish) => {
|
|
1110
1210
|
if (settled) return;
|
|
1111
1211
|
settled = true;
|
|
1112
1212
|
clearTimeout(timer);
|
|
1113
1213
|
this.running.delete(input.taskId);
|
|
1214
|
+
GLOBAL_RUNNING.delete(child);
|
|
1114
1215
|
finish();
|
|
1115
1216
|
};
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1217
|
+
const collect = (into, chunk) => {
|
|
1218
|
+
if (outputLimitExceeded) return;
|
|
1219
|
+
const text = sanitizeChunk(chunk);
|
|
1220
|
+
if (into === "stdout") {
|
|
1221
|
+
if (stdout.length + text.length > maxOutputBytes) {
|
|
1222
|
+
stdout = (stdout + text).slice(0, maxOutputBytes);
|
|
1223
|
+
outputLimitExceeded = true;
|
|
1224
|
+
this.log.warn(`Agent "${this.config.name}" exceeded the ${maxOutputBytes} byte output limit \u2014 killing it.`);
|
|
1225
|
+
killTree(child, "SIGKILL");
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
stdout += text;
|
|
1229
|
+
} else {
|
|
1230
|
+
if (stderr.length + text.length > maxOutputBytes) {
|
|
1231
|
+
stderr = (stderr + text).slice(0, maxOutputBytes);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
stderr += text;
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
child.stdout?.on("data", (chunk) => collect("stdout", chunk));
|
|
1238
|
+
child.stderr?.on("data", (chunk) => collect("stderr", chunk));
|
|
1122
1239
|
child.stdin?.on("error", () => {
|
|
1123
1240
|
});
|
|
1124
1241
|
if (mode !== "arg") {
|
|
@@ -1172,7 +1289,7 @@ var ProcessAgent = class {
|
|
|
1172
1289
|
const logs = stderr.trim().length > 0 ? stderr.split("\n") : void 0;
|
|
1173
1290
|
if (mode === "protocol") {
|
|
1174
1291
|
try {
|
|
1175
|
-
|
|
1292
|
+
resolve2(
|
|
1176
1293
|
interpretProtocolRun({
|
|
1177
1294
|
input,
|
|
1178
1295
|
agentName: this.config.name,
|
|
@@ -1187,14 +1304,14 @@ var ProcessAgent = class {
|
|
|
1187
1304
|
return;
|
|
1188
1305
|
}
|
|
1189
1306
|
const trimmed = stdout.trim();
|
|
1190
|
-
|
|
1307
|
+
resolve2({
|
|
1191
1308
|
taskId: input.taskId,
|
|
1192
|
-
status: code === 0 ? "completed" : "failed",
|
|
1309
|
+
status: code === 0 && !outputLimitExceeded ? "completed" : "failed",
|
|
1193
1310
|
summary: trimmed.length > 0 ? firstLine(trimmed) : void 0,
|
|
1194
1311
|
rawOutput: trimmed.length > 0 ? trimmed : void 0,
|
|
1195
1312
|
exitCode: code ?? void 0,
|
|
1196
1313
|
logs,
|
|
1197
|
-
error:
|
|
1314
|
+
...outputLimitExceeded ? { error: `Agent exceeded the ${maxOutputBytes} byte output limit and was terminated.` } : code === 0 ? {} : { error: describeExit(code, signal) }
|
|
1198
1315
|
});
|
|
1199
1316
|
});
|
|
1200
1317
|
});
|
|
@@ -1203,7 +1320,7 @@ var ProcessAgent = class {
|
|
|
1203
1320
|
/** Ask the agent to stop current work (SIGTERM). */
|
|
1204
1321
|
async cancel(taskId) {
|
|
1205
1322
|
const child = this.running.get(taskId);
|
|
1206
|
-
if (child) child
|
|
1323
|
+
if (child) killTree(child, "SIGTERM");
|
|
1207
1324
|
}
|
|
1208
1325
|
runningTaskIds() {
|
|
1209
1326
|
return [...this.running.keys()];
|
|
@@ -1278,6 +1395,43 @@ function interpretProtocolRun({
|
|
|
1278
1395
|
...exitCode !== 0 ? { error: `Process exited with code ${exitCode ?? "unknown"}` } : {}
|
|
1279
1396
|
};
|
|
1280
1397
|
}
|
|
1398
|
+
var GLOBAL_RUNNING = /* @__PURE__ */ new Set();
|
|
1399
|
+
var signalForwardingInstalled = false;
|
|
1400
|
+
function installSignalForwarding() {
|
|
1401
|
+
if (signalForwardingInstalled) return;
|
|
1402
|
+
signalForwardingInstalled = true;
|
|
1403
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
1404
|
+
process.on(signal, () => {
|
|
1405
|
+
for (const child of GLOBAL_RUNNING) {
|
|
1406
|
+
killTree(child, "SIGTERM");
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
function killTree(child, signal) {
|
|
1412
|
+
if (child.pid === void 0) return;
|
|
1413
|
+
if (process.platform !== "win32") {
|
|
1414
|
+
try {
|
|
1415
|
+
process.kill(-child.pid, signal);
|
|
1416
|
+
return;
|
|
1417
|
+
} catch {
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
try {
|
|
1421
|
+
child.kill(signal);
|
|
1422
|
+
} catch {
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function describeExit(code, signal) {
|
|
1426
|
+
if (signal !== null) {
|
|
1427
|
+
return signal === "SIGKILL" ? "Process was killed (SIGKILL) \u2014 usually an external kill or OOM." : `Process terminated by signal ${signal}.`;
|
|
1428
|
+
}
|
|
1429
|
+
if (code === null) return "Process terminated without an exit code.";
|
|
1430
|
+
return `Process exited with code ${code}.`;
|
|
1431
|
+
}
|
|
1432
|
+
function sanitizeChunk(chunk) {
|
|
1433
|
+
return [...chunk.toString("utf8")].filter((char) => char.charCodeAt(0) !== 0).join("");
|
|
1434
|
+
}
|
|
1281
1435
|
function normalizeReport(payload, taskId, agentName, index) {
|
|
1282
1436
|
if (payload === null || typeof payload !== "object") return void 0;
|
|
1283
1437
|
const raw = payload;
|
|
@@ -1297,6 +1451,40 @@ function firstLine(text, maxLength = 200) {
|
|
|
1297
1451
|
return line.length > maxLength ? `${line.slice(0, maxLength)}\u2026` : line;
|
|
1298
1452
|
}
|
|
1299
1453
|
|
|
1454
|
+
// src/agents/adapters.ts
|
|
1455
|
+
var AgentAdapterRegistry = class {
|
|
1456
|
+
adapters = /* @__PURE__ */ new Map();
|
|
1457
|
+
register(adapter) {
|
|
1458
|
+
this.adapters.set(adapter.integration, adapter);
|
|
1459
|
+
}
|
|
1460
|
+
get(integration) {
|
|
1461
|
+
return this.adapters.get(integration);
|
|
1462
|
+
}
|
|
1463
|
+
list() {
|
|
1464
|
+
return [...this.adapters.values()];
|
|
1465
|
+
}
|
|
1466
|
+
create(config) {
|
|
1467
|
+
const adapter = this.get(config.integration);
|
|
1468
|
+
if (!adapter) {
|
|
1469
|
+
throw new AgentError(
|
|
1470
|
+
"AGENT_NOT_FOUND",
|
|
1471
|
+
`No agent adapter registered for integration "${config.integration}". Registered: ${[...this.adapters.keys()].join(", ") || "(none)"}.`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return adapter.create(config);
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
var ProcessAgentAdapter = class {
|
|
1478
|
+
constructor(factory) {
|
|
1479
|
+
this.factory = factory;
|
|
1480
|
+
}
|
|
1481
|
+
factory;
|
|
1482
|
+
integration = "process";
|
|
1483
|
+
create(config) {
|
|
1484
|
+
return this.factory(config);
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1300
1488
|
// src/agents/registry.ts
|
|
1301
1489
|
var AgentRegistry = class {
|
|
1302
1490
|
agents = /* @__PURE__ */ new Map();
|
|
@@ -1312,14 +1500,15 @@ var AgentRegistry = class {
|
|
|
1312
1500
|
};
|
|
1313
1501
|
|
|
1314
1502
|
// src/agents/factory.ts
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1503
|
+
var defaultAgentAdapters = new AgentAdapterRegistry();
|
|
1504
|
+
defaultAgentAdapters.register(new ProcessAgentAdapter((config) => new ProcessAgent(config)));
|
|
1505
|
+
function createAgent(config, adapters = defaultAgentAdapters) {
|
|
1506
|
+
const registered = adapters.get(config.integration);
|
|
1507
|
+
if (registered) return registered.create(config);
|
|
1319
1508
|
if (config.integration === "native") {
|
|
1320
1509
|
throw new OmniNodeError(
|
|
1321
1510
|
"NOT_IMPLEMENTED",
|
|
1322
|
-
`
|
|
1511
|
+
`No native adapter is registered for "${config.name}". Register an IAgentAdapter (createAgent(config, adapters)) or use input over stdin/stdout (type "cli").`
|
|
1323
1512
|
);
|
|
1324
1513
|
}
|
|
1325
1514
|
throw new OmniNodeError("NOT_IMPLEMENTED", `Agent integration "${config.integration}" has no adapter yet.`);
|
|
@@ -1588,6 +1777,11 @@ var TaskEngine = class {
|
|
|
1588
1777
|
if (this.options.memory) await this.options.memory.recordTaskOutcome(current);
|
|
1589
1778
|
return current;
|
|
1590
1779
|
}
|
|
1780
|
+
const currentState = await this.options.store.get(current.id);
|
|
1781
|
+
if (currentState?.status === "cancelled") {
|
|
1782
|
+
current = currentState;
|
|
1783
|
+
break;
|
|
1784
|
+
}
|
|
1591
1785
|
const result = {
|
|
1592
1786
|
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
1593
1787
|
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
@@ -1699,7 +1893,7 @@ function classifyThrown(error) {
|
|
|
1699
1893
|
return "failed";
|
|
1700
1894
|
}
|
|
1701
1895
|
function sleep(ms) {
|
|
1702
|
-
return new Promise((
|
|
1896
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1703
1897
|
}
|
|
1704
1898
|
|
|
1705
1899
|
// src/audit/index.ts
|
|
@@ -2250,9 +2444,23 @@ var PipelineEngine = class {
|
|
|
2250
2444
|
}
|
|
2251
2445
|
options;
|
|
2252
2446
|
log;
|
|
2447
|
+
/** Runs executing in THIS process — cancellation can terminate them directly. */
|
|
2448
|
+
activeRuns = /* @__PURE__ */ new Map();
|
|
2253
2449
|
get store() {
|
|
2254
2450
|
return this.options.store;
|
|
2255
2451
|
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Saves the run while preserving fields that may be set by another process
|
|
2454
|
+
* (e.g. a cancellation request) — an in-memory snapshot must never clobber
|
|
2455
|
+
* externally requested cancellation (§9.2).
|
|
2456
|
+
*/
|
|
2457
|
+
async persist(run) {
|
|
2458
|
+
const current = await this.options.store.get(run.id);
|
|
2459
|
+
await this.options.store.save({
|
|
2460
|
+
...run,
|
|
2461
|
+
...current?.cancellationRequested === true ? { cancellationRequested: true, cancellationRequestedAt: current.cancellationRequestedAt } : {}
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2256
2464
|
/** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
|
|
2257
2465
|
validate(def) {
|
|
2258
2466
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -2352,18 +2560,44 @@ var PipelineEngine = class {
|
|
|
2352
2560
|
);
|
|
2353
2561
|
}
|
|
2354
2562
|
}
|
|
2563
|
+
if (step.model) {
|
|
2564
|
+
const separator = step.model.indexOf(":");
|
|
2565
|
+
if (separator <= 0 || separator === step.model.length - 1) {
|
|
2566
|
+
throw new PipelineError(
|
|
2567
|
+
"PIPELINE_INVALID",
|
|
2568
|
+
`Step "${step.id}" has an invalid model reference "${step.model}" \u2014 use "provider:model-id".`
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
const providerName = step.model.slice(0, separator);
|
|
2572
|
+
if (!this.options.providers.some((p) => p.name === providerName)) {
|
|
2573
|
+
throw new PipelineError(
|
|
2574
|
+
"PIPELINE_INVALID",
|
|
2575
|
+
`Step "${step.id}" uses model "${step.model}" but provider "${providerName}" is not configured.`
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2355
2579
|
}
|
|
2356
2580
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2357
2581
|
const run = {
|
|
2358
2582
|
id: `run-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
|
|
2359
2583
|
pipelineId: def.id,
|
|
2584
|
+
attempt: 1,
|
|
2360
2585
|
objective,
|
|
2361
2586
|
status: "running",
|
|
2362
2587
|
startedAt: now,
|
|
2363
2588
|
stepRuns: []
|
|
2364
2589
|
};
|
|
2365
2590
|
await this.options.store.save(run);
|
|
2366
|
-
|
|
2591
|
+
const liveTaskIds = /* @__PURE__ */ new Set();
|
|
2592
|
+
this.activeRuns.set(run.id, {
|
|
2593
|
+
taskIds: liveTaskIds,
|
|
2594
|
+
cancelTasks: async () => {
|
|
2595
|
+
for (const taskId of liveTaskIds) {
|
|
2596
|
+
await this.options.tasks.cancel(taskId).catch(() => void 0);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
});
|
|
2600
|
+
this.log.info(`Running pipeline ${def.id} as ${run.id} (attempt ${run.attempt}).`);
|
|
2367
2601
|
await this.options.audit?.record({
|
|
2368
2602
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2369
2603
|
action: "pipeline.run.started",
|
|
@@ -2375,7 +2609,22 @@ var PipelineEngine = class {
|
|
|
2375
2609
|
const pending = new Set(def.steps.map((s) => s.id));
|
|
2376
2610
|
const contextParts = [];
|
|
2377
2611
|
const allReports = [];
|
|
2612
|
+
let cancelled = false;
|
|
2378
2613
|
while (pending.size > 0) {
|
|
2614
|
+
const persisted = await this.options.store.get(run.id);
|
|
2615
|
+
if (persisted?.cancellationRequested === true) {
|
|
2616
|
+
cancelled = true;
|
|
2617
|
+
run.cancellationRequested = true;
|
|
2618
|
+
run.cancellationRequestedAt = persisted.cancellationRequestedAt;
|
|
2619
|
+
await this.activeRuns.get(run.id)?.cancelTasks();
|
|
2620
|
+
for (const step of def.steps.filter((s) => pending.has(s.id))) {
|
|
2621
|
+
const stepRun = { stepId: step.id, status: "cancelled" };
|
|
2622
|
+
run.stepRuns.push(stepRun);
|
|
2623
|
+
options.onStep?.(stepRun);
|
|
2624
|
+
}
|
|
2625
|
+
pending.clear();
|
|
2626
|
+
break;
|
|
2627
|
+
}
|
|
2379
2628
|
const ready = def.steps.filter(
|
|
2380
2629
|
(step) => pending.has(step.id) && (deps.get(step.id) ?? []).every((d) => outcomes.has(d))
|
|
2381
2630
|
);
|
|
@@ -2392,7 +2641,8 @@ var PipelineEngine = class {
|
|
|
2392
2641
|
...role ? { role } : {},
|
|
2393
2642
|
combinedContext: contextParts.join("\n\n"),
|
|
2394
2643
|
reports: [...allReports],
|
|
2395
|
-
pipelineRunId: run.id
|
|
2644
|
+
pipelineRunId: run.id,
|
|
2645
|
+
runId: run.id
|
|
2396
2646
|
};
|
|
2397
2647
|
return this.executeStep(step, ctx);
|
|
2398
2648
|
})
|
|
@@ -2409,6 +2659,8 @@ ${contribution}`);
|
|
|
2409
2659
|
}
|
|
2410
2660
|
const stepRun = {
|
|
2411
2661
|
stepId: step.id,
|
|
2662
|
+
attempt: outcome.attempt ?? 1,
|
|
2663
|
+
...outcome.executionIds && outcome.executionIds.length > 0 ? { executionIds: outcome.executionIds } : {},
|
|
2412
2664
|
status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : outcome.status === "partial" ? "partially_completed" : "completed",
|
|
2413
2665
|
...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
|
|
2414
2666
|
...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
|
|
@@ -2418,12 +2670,12 @@ ${contribution}`);
|
|
|
2418
2670
|
options.onStep?.(stepRun);
|
|
2419
2671
|
});
|
|
2420
2672
|
run.reports = [...allReports];
|
|
2421
|
-
await this.
|
|
2673
|
+
await this.persist(run);
|
|
2422
2674
|
}
|
|
2423
2675
|
const allOutcomes = [...outcomes.values()];
|
|
2424
|
-
const failed = allOutcomes.some((o) => o.status === "failed");
|
|
2425
|
-
const partial = !failed && allOutcomes.some((o) => o.status === "partial");
|
|
2426
|
-
const finalStatus = failed ? "failed" : partial ? "partial" : "completed";
|
|
2676
|
+
const failed = !cancelled && allOutcomes.some((o) => o.status === "failed");
|
|
2677
|
+
const partial = !cancelled && !failed && allOutcomes.some((o) => o.status === "partial");
|
|
2678
|
+
const finalStatus = cancelled ? "cancelled" : failed ? "failed" : partial ? "partial" : "completed";
|
|
2427
2679
|
const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
|
|
2428
2680
|
let resultSummary;
|
|
2429
2681
|
for (let i = def.steps.length - 1; i >= 0; i -= 1) {
|
|
@@ -2451,16 +2703,19 @@ ${contribution}`);
|
|
|
2451
2703
|
run.reports = collected;
|
|
2452
2704
|
}
|
|
2453
2705
|
}
|
|
2706
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2707
|
+
this.activeRuns.delete(run.id);
|
|
2454
2708
|
const finished = {
|
|
2455
2709
|
...run,
|
|
2456
2710
|
status: finalStatus,
|
|
2457
|
-
finishedAt
|
|
2711
|
+
finishedAt,
|
|
2712
|
+
attempts: [...run.attempts ?? [], { attempt: run.attempt, startedAt: run.startedAt ?? finishedAt, finishedAt, status: finalStatus }],
|
|
2458
2713
|
...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
|
|
2459
2714
|
...combinedReportId !== void 0 ? { combinedReportId } : {},
|
|
2460
2715
|
...planId !== void 0 ? { planId } : {},
|
|
2461
2716
|
...resultSummary !== void 0 ? { resultSummary } : {}
|
|
2462
2717
|
};
|
|
2463
|
-
await this.
|
|
2718
|
+
await this.persist(finished);
|
|
2464
2719
|
this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
|
|
2465
2720
|
await this.options.audit?.record({
|
|
2466
2721
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2470,6 +2725,23 @@ ${contribution}`);
|
|
|
2470
2725
|
});
|
|
2471
2726
|
return finished;
|
|
2472
2727
|
}
|
|
2728
|
+
/** First-class cancellation (§9.2): requests cancellation and terminates
|
|
2729
|
+
* tasks that are running in this process immediately. */
|
|
2730
|
+
async cancel(runId) {
|
|
2731
|
+
const run = await this.options.store.get(runId);
|
|
2732
|
+
if (!run) {
|
|
2733
|
+
throw new PipelineError("PIPELINE_NOT_FOUND", `Pipeline run "${runId}" does not exist.`);
|
|
2734
|
+
}
|
|
2735
|
+
await this.options.store.save({
|
|
2736
|
+
...run,
|
|
2737
|
+
cancellationRequested: true,
|
|
2738
|
+
cancellationRequestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2739
|
+
});
|
|
2740
|
+
const active = this.activeRuns.get(runId);
|
|
2741
|
+
if (active) {
|
|
2742
|
+
await active.cancelTasks();
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2473
2745
|
async get(runId) {
|
|
2474
2746
|
return this.options.store.get(runId);
|
|
2475
2747
|
}
|
|
@@ -2493,9 +2765,16 @@ ${contribution}`);
|
|
|
2493
2765
|
this.log.warn(`Step "${step.id}" failed \u2014 retry ${attempt}/${retries}.`);
|
|
2494
2766
|
}
|
|
2495
2767
|
last = await this.executeStepOnce(step, ctx);
|
|
2496
|
-
if (last.status !== "failed") return last;
|
|
2768
|
+
if (last.status !== "failed") return { ...last, attempt: attempt + 1 };
|
|
2497
2769
|
}
|
|
2498
|
-
return last;
|
|
2770
|
+
return { ...last, attempt: retries + 1 };
|
|
2771
|
+
}
|
|
2772
|
+
/** Registers tasks created for a step as live, so cancellation can terminate them (§9.2). */
|
|
2773
|
+
trackLiveTasks(runId, taskIds) {
|
|
2774
|
+
if (!runId) return;
|
|
2775
|
+
const active = this.activeRuns.get(runId);
|
|
2776
|
+
if (!active) return;
|
|
2777
|
+
for (const id of taskIds) active.taskIds.add(id);
|
|
2499
2778
|
}
|
|
2500
2779
|
async executeStepOnce(step, ctx) {
|
|
2501
2780
|
switch (step.kind) {
|
|
@@ -2528,7 +2807,9 @@ ${contribution}`);
|
|
|
2528
2807
|
})
|
|
2529
2808
|
)
|
|
2530
2809
|
);
|
|
2810
|
+
this.trackLiveTasks(ctx.runId, created.map((t) => t.id));
|
|
2531
2811
|
const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
|
|
2812
|
+
const executionIds = results.map((r) => r.executions?.at(-1)?.executionId).filter((id) => id !== void 0);
|
|
2532
2813
|
const completedTasks = results.filter((r) => r.status === "completed");
|
|
2533
2814
|
const failedTasks = results.filter((r) => r.status !== "completed");
|
|
2534
2815
|
const lines = results.map((result, index) => {
|
|
@@ -2551,7 +2832,13 @@ ${contribution}`);
|
|
|
2551
2832
|
reports
|
|
2552
2833
|
};
|
|
2553
2834
|
}
|
|
2554
|
-
return {
|
|
2835
|
+
return {
|
|
2836
|
+
status: "completed",
|
|
2837
|
+
summary,
|
|
2838
|
+
taskIds: created.map((t) => t.id),
|
|
2839
|
+
...executionIds.length > 0 ? { executionIds } : {},
|
|
2840
|
+
reports
|
|
2841
|
+
};
|
|
2555
2842
|
}
|
|
2556
2843
|
async runSingle(step, ctx, agentName) {
|
|
2557
2844
|
const task = await this.options.tasks.create({
|
|
@@ -2560,6 +2847,7 @@ ${contribution}`);
|
|
|
2560
2847
|
...ctx.role ? { role: ctx.role.id } : {},
|
|
2561
2848
|
...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
|
|
2562
2849
|
});
|
|
2850
|
+
this.trackLiveTasks(ctx.runId, [task.id]);
|
|
2563
2851
|
const result = await this.options.tasks.run(task.id);
|
|
2564
2852
|
if (result.status !== "completed") {
|
|
2565
2853
|
return {
|
|
@@ -3037,12 +3325,14 @@ var ProjectStores = class {
|
|
|
3037
3325
|
};
|
|
3038
3326
|
|
|
3039
3327
|
// src/version.ts
|
|
3040
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
3328
|
+
var OMNINODE_VERSION = "2.0.0-alpha.5";
|
|
3041
3329
|
export {
|
|
3330
|
+
AgentAdapterRegistry,
|
|
3042
3331
|
AgentError,
|
|
3043
3332
|
AgentRegistry,
|
|
3044
3333
|
ConfigError,
|
|
3045
3334
|
ConsoleLogSink,
|
|
3335
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
3046
3336
|
FileAuditLog,
|
|
3047
3337
|
FilePipelineRunStore,
|
|
3048
3338
|
FilePlanStore,
|
|
@@ -3067,6 +3357,7 @@ export {
|
|
|
3067
3357
|
PipelineError,
|
|
3068
3358
|
PlannerError,
|
|
3069
3359
|
ProcessAgent,
|
|
3360
|
+
ProcessAgentAdapter,
|
|
3070
3361
|
ProjectStores,
|
|
3071
3362
|
ProtocolDecoder,
|
|
3072
3363
|
ProtocolError,
|
|
@@ -3100,6 +3391,7 @@ export {
|
|
|
3100
3391
|
decodeMessage,
|
|
3101
3392
|
decodeStream,
|
|
3102
3393
|
defaultProjectConfigYaml,
|
|
3394
|
+
describeExit,
|
|
3103
3395
|
encodeMessage,
|
|
3104
3396
|
envelope,
|
|
3105
3397
|
expandEnvRefs,
|
|
@@ -3108,6 +3400,7 @@ export {
|
|
|
3108
3400
|
isOmniNodeError,
|
|
3109
3401
|
isSupportedProtocol,
|
|
3110
3402
|
keywords,
|
|
3403
|
+
killTree,
|
|
3111
3404
|
loadConfig,
|
|
3112
3405
|
logger,
|
|
3113
3406
|
newMessageId,
|
|
@@ -3122,8 +3415,11 @@ export {
|
|
|
3122
3415
|
requestJson,
|
|
3123
3416
|
requireCompletion,
|
|
3124
3417
|
resolveApiKey,
|
|
3418
|
+
resolveEnvPolicy,
|
|
3125
3419
|
roleConfigSchema,
|
|
3420
|
+
sanitizeChunk,
|
|
3126
3421
|
similar,
|
|
3127
|
-
validateEnvelope
|
|
3422
|
+
validateEnvelope,
|
|
3423
|
+
validateWorkingDirectory
|
|
3128
3424
|
};
|
|
3129
3425
|
//# sourceMappingURL=index.js.map
|