@hilbras/omninode 2.0.0-alpha.3 → 2.0.0-alpha.4
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 +25 -9
- package/dist/cli/index.js +229 -35
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +165 -9
- package/dist/index.js +231 -34
- 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").replace(/\u0000/g, "");
|
|
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.`);
|
|
@@ -1699,7 +1888,7 @@ function classifyThrown(error) {
|
|
|
1699
1888
|
return "failed";
|
|
1700
1889
|
}
|
|
1701
1890
|
function sleep(ms) {
|
|
1702
|
-
return new Promise((
|
|
1891
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1703
1892
|
}
|
|
1704
1893
|
|
|
1705
1894
|
// src/audit/index.ts
|
|
@@ -3037,12 +3226,14 @@ var ProjectStores = class {
|
|
|
3037
3226
|
};
|
|
3038
3227
|
|
|
3039
3228
|
// src/version.ts
|
|
3040
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
3229
|
+
var OMNINODE_VERSION = "2.0.0-alpha.4";
|
|
3041
3230
|
export {
|
|
3231
|
+
AgentAdapterRegistry,
|
|
3042
3232
|
AgentError,
|
|
3043
3233
|
AgentRegistry,
|
|
3044
3234
|
ConfigError,
|
|
3045
3235
|
ConsoleLogSink,
|
|
3236
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
3046
3237
|
FileAuditLog,
|
|
3047
3238
|
FilePipelineRunStore,
|
|
3048
3239
|
FilePlanStore,
|
|
@@ -3067,6 +3258,7 @@ export {
|
|
|
3067
3258
|
PipelineError,
|
|
3068
3259
|
PlannerError,
|
|
3069
3260
|
ProcessAgent,
|
|
3261
|
+
ProcessAgentAdapter,
|
|
3070
3262
|
ProjectStores,
|
|
3071
3263
|
ProtocolDecoder,
|
|
3072
3264
|
ProtocolError,
|
|
@@ -3100,6 +3292,7 @@ export {
|
|
|
3100
3292
|
decodeMessage,
|
|
3101
3293
|
decodeStream,
|
|
3102
3294
|
defaultProjectConfigYaml,
|
|
3295
|
+
describeExit,
|
|
3103
3296
|
encodeMessage,
|
|
3104
3297
|
envelope,
|
|
3105
3298
|
expandEnvRefs,
|
|
@@ -3108,6 +3301,7 @@ export {
|
|
|
3108
3301
|
isOmniNodeError,
|
|
3109
3302
|
isSupportedProtocol,
|
|
3110
3303
|
keywords,
|
|
3304
|
+
killTree,
|
|
3111
3305
|
loadConfig,
|
|
3112
3306
|
logger,
|
|
3113
3307
|
newMessageId,
|
|
@@ -3122,8 +3316,11 @@ export {
|
|
|
3122
3316
|
requestJson,
|
|
3123
3317
|
requireCompletion,
|
|
3124
3318
|
resolveApiKey,
|
|
3319
|
+
resolveEnvPolicy,
|
|
3125
3320
|
roleConfigSchema,
|
|
3321
|
+
sanitizeChunk,
|
|
3126
3322
|
similar,
|
|
3127
|
-
validateEnvelope
|
|
3323
|
+
validateEnvelope,
|
|
3324
|
+
validateWorkingDirectory
|
|
3128
3325
|
};
|
|
3129
3326
|
//# sourceMappingURL=index.js.map
|