@hilbras/omninode 2.0.0-alpha.2 → 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/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
  }
@@ -673,19 +683,431 @@ var ModelRegistry = class {
673
683
 
674
684
  // src/agents/process/index.ts
675
685
  import { spawn } from "child_process";
676
- var DEFAULT_TIMEOUT_MS2 = 6e5;
677
- var KILL_GRACE_MS = 5e3;
686
+
687
+ // src/agent-protocol/version.ts
688
+ var PROTOCOL_V2 = "omninode-agent-protocol/2";
689
+ var SUPPORTED_PROTOCOL_VERSIONS = [
690
+ "omninode-agent-protocol/1",
691
+ PROTOCOL_V2
692
+ ];
693
+ var MAX_MESSAGE_BYTES = 1048576;
694
+ function isSupportedProtocol(protocol) {
695
+ return SUPPORTED_PROTOCOL_VERSIONS.includes(protocol);
696
+ }
697
+
698
+ // src/agent-protocol/types.ts
699
+ function newMessageId(prefix = "msg") {
700
+ const random = Math.random().toString(36).slice(2, 10);
701
+ return `${prefix}-${Date.now().toString(36)}-${random}`;
702
+ }
703
+ function envelope(type, payload, ids = {}) {
704
+ return {
705
+ protocol: PROTOCOL_V2,
706
+ messageId: newMessageId(),
707
+ ...ids.requestId !== void 0 ? { requestId: ids.requestId } : {},
708
+ ...ids.taskId !== void 0 ? { taskId: ids.taskId } : {},
709
+ ...ids.agentId !== void 0 ? { agentId: ids.agentId } : {},
710
+ ...ids.pipelineId !== void 0 ? { pipelineId: ids.pipelineId } : {},
711
+ ...ids.executionId !== void 0 ? { executionId: ids.executionId } : {},
712
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
713
+ type,
714
+ payload,
715
+ ...ids.metadata !== void 0 ? { metadata: ids.metadata } : {}
716
+ };
717
+ }
718
+
719
+ // src/agent-protocol/codec.ts
720
+ var KNOWN_TYPES = /* @__PURE__ */ new Set([
721
+ // node → agent
722
+ "TASK",
723
+ "HELLO_ACK",
724
+ "CONTEXT",
725
+ "INSTRUCTION",
726
+ "RESPONSE",
727
+ "CANCEL",
728
+ // agent → node
729
+ "HELLO",
730
+ "TASK_REQUEST",
731
+ "TASK_ACCEPTED",
732
+ "TASK_STARTED",
733
+ "STATUS",
734
+ "QUESTION",
735
+ "REPORT",
736
+ "ARTIFACT",
737
+ "COMPLETION",
738
+ "ERROR",
739
+ "CANCELLED"
740
+ ]);
741
+ var PAYLOAD_REQUIRED_TYPES = /* @__PURE__ */ new Set(["TASK", "REPORT", "COMPLETION", "ERROR", "QUESTION", "ARTIFACT", "HELLO"]);
742
+ function validateEnvelope(value, line) {
743
+ const sample = line?.slice(0, 200);
744
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
745
+ return { ok: false, error: { code: "NOT_AN_OBJECT", message: "message must be a JSON object", ...sample ? { sample } : {} } };
746
+ }
747
+ const raw = value;
748
+ const protocol = raw.protocol;
749
+ if (protocol === void 0) {
750
+ const type = raw.type;
751
+ if (typeof type !== "string" || !KNOWN_TYPES.has(type)) {
752
+ return { ok: false, error: { code: "UNKNOWN_TYPE", message: `unknown message type ${String(type)}`, ...sample ? { sample } : {} } };
753
+ }
754
+ return {
755
+ ok: true,
756
+ legacy: true,
757
+ envelope: {
758
+ protocol: "omninode-agent-protocol/1",
759
+ messageId: typeof raw.messageId === "string" ? raw.messageId : "legacy",
760
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
761
+ type,
762
+ payload: raw.payload
763
+ }
764
+ };
765
+ }
766
+ if (typeof protocol !== "string" || !isSupportedProtocol(protocol)) {
767
+ return {
768
+ ok: false,
769
+ error: { code: "UNSUPPORTED_VERSION", message: `unsupported protocol "${String(protocol)}"`, ...sample ? { sample } : {} }
770
+ };
771
+ }
772
+ if (raw.type === void 0) {
773
+ return { ok: false, error: { code: "MISSING_FIELD", message: "message is missing 'type'", ...sample ? { sample } : {} } };
774
+ }
775
+ if (typeof raw.type !== "string" || !KNOWN_TYPES.has(raw.type)) {
776
+ return { ok: false, error: { code: "UNKNOWN_TYPE", message: `unknown message type ${String(raw.type)}`, ...sample ? { sample } : {} } };
777
+ }
778
+ if (raw.timestamp !== void 0 && typeof raw.timestamp !== "string") {
779
+ return { ok: false, error: { code: "MISSING_FIELD", message: "'timestamp' must be a string", ...sample ? { sample } : {} } };
780
+ }
781
+ if (PAYLOAD_REQUIRED_TYPES.has(raw.type) && (raw.payload === void 0 || raw.payload === null)) {
782
+ return { ok: false, error: { code: "INVALID_PAYLOAD", message: `${raw.type} requires a payload`, ...sample ? { sample } : {} } };
783
+ }
784
+ return {
785
+ ok: true,
786
+ legacy: protocol !== PROTOCOL_V2,
787
+ envelope: {
788
+ protocol,
789
+ messageId: typeof raw.messageId === "string" ? raw.messageId : "unknown",
790
+ ...typeof raw.requestId === "string" ? { requestId: raw.requestId } : {},
791
+ ...typeof raw.taskId === "string" ? { taskId: raw.taskId } : {},
792
+ ...typeof raw.agentId === "string" ? { agentId: raw.agentId } : {},
793
+ ...typeof raw.pipelineId === "string" ? { pipelineId: raw.pipelineId } : {},
794
+ ...typeof raw.executionId === "string" ? { executionId: raw.executionId } : {},
795
+ timestamp: typeof raw.timestamp === "string" ? raw.timestamp : (/* @__PURE__ */ new Date()).toISOString(),
796
+ type: raw.type,
797
+ payload: raw.payload,
798
+ ...raw.metadata !== null && typeof raw.metadata === "object" ? { metadata: raw.metadata } : {}
799
+ }
800
+ };
801
+ }
802
+ function decodeMessage(line, maxBytes = MAX_MESSAGE_BYTES) {
803
+ const trimmed = line.trim();
804
+ if (trimmed.length === 0) {
805
+ return { ok: false, error: { code: "INVALID_JSON", message: "empty line" } };
806
+ }
807
+ if (trimmed.length > maxBytes) {
808
+ return {
809
+ ok: false,
810
+ error: { code: "OVERSIZED", message: `message of ${trimmed.length} bytes exceeds the ${maxBytes} byte limit`, sample: trimmed.slice(0, 200) }
811
+ };
812
+ }
813
+ let value;
814
+ try {
815
+ value = JSON.parse(trimmed);
816
+ } catch (error) {
817
+ return {
818
+ ok: false,
819
+ error: { code: "INVALID_JSON", message: error instanceof Error ? error.message : "invalid JSON", sample: trimmed.slice(0, 200) }
820
+ };
821
+ }
822
+ return validateEnvelope(value, trimmed);
823
+ }
824
+ function encodeMessage(envelope2, maxBytes = MAX_MESSAGE_BYTES) {
825
+ const line = JSON.stringify(envelope2);
826
+ if (line.length > maxBytes) {
827
+ throw new Error(`outbound protocol message of ${line.length} bytes exceeds the ${maxBytes} byte limit`);
828
+ }
829
+ return line;
830
+ }
831
+ var ProtocolDecoder = class {
832
+ buffer = "";
833
+ maxBytes;
834
+ constructor(maxBytes = MAX_MESSAGE_BYTES) {
835
+ this.maxBytes = maxBytes;
836
+ }
837
+ push(chunk) {
838
+ this.buffer += chunk;
839
+ const results = [];
840
+ let index = this.buffer.indexOf("\n");
841
+ while (index >= 0) {
842
+ const line = this.buffer.slice(0, index);
843
+ this.buffer = this.buffer.slice(index + 1);
844
+ if (line.trim().length > 0) results.push(decodeMessage(line, this.maxBytes));
845
+ index = this.buffer.indexOf("\n");
846
+ }
847
+ if (this.buffer.length > this.maxBytes) {
848
+ results.push({
849
+ ok: false,
850
+ error: { code: "OVERSIZED", message: `line exceeded ${this.maxBytes} bytes without a newline`, sample: this.buffer.slice(0, 200) }
851
+ });
852
+ this.buffer = "";
853
+ }
854
+ return results;
855
+ }
856
+ /** Flush any trailing line when the process exits. */
857
+ flush() {
858
+ if (this.buffer.trim().length === 0) {
859
+ this.buffer = "";
860
+ return [];
861
+ }
862
+ const result = decodeMessage(this.buffer, this.maxBytes);
863
+ this.buffer = "";
864
+ return [result];
865
+ }
866
+ };
867
+
868
+ // src/agent-protocol/handshake.ts
869
+ function parseHello(envelopeValue) {
870
+ if (envelopeValue.type !== "HELLO") {
871
+ return { ok: false, reason: `expected HELLO, received ${envelopeValue.type}` };
872
+ }
873
+ const payload = envelopeValue.payload;
874
+ if (payload === null || typeof payload !== "object") {
875
+ return { ok: false, reason: "HELLO payload must be an object" };
876
+ }
877
+ const p = payload;
878
+ if (typeof p.name !== "string" || p.name.length === 0) {
879
+ return { ok: false, reason: "HELLO payload is missing 'name'" };
880
+ }
881
+ return {
882
+ ok: true,
883
+ descriptor: {
884
+ protocol: envelopeValue.protocol,
885
+ name: p.name,
886
+ ...typeof p.version === "string" ? { version: p.version } : {},
887
+ capabilities: Array.isArray(p.capabilities) ? p.capabilities.filter((c) => typeof c === "string") : [],
888
+ supports: Array.isArray(p.supports) ? p.supports.filter((s) => typeof s === "string") : []
889
+ }
890
+ };
891
+ }
892
+ function buildHello(descriptor) {
893
+ return envelope("HELLO", descriptor, { agentId: descriptor.name });
894
+ }
895
+ function buildHelloAck(descriptor) {
896
+ return envelope("HELLO_ACK", {
897
+ ok: true,
898
+ protocol: PROTOCOL_V2,
899
+ agent: descriptor.name,
900
+ ...descriptor.version !== void 0 ? { agentVersion: descriptor.version } : {},
901
+ supportedProtocols: [PROTOCOL_V2]
902
+ }, { agentId: descriptor.name });
903
+ }
904
+
905
+ // src/agent-protocol/session.ts
906
+ var ProtocolSession = class {
907
+ decoder;
908
+ ids;
909
+ responder;
910
+ violations = [];
911
+ questions = [];
912
+ artifacts = [];
913
+ reports = [];
914
+ descriptor;
915
+ completionSummary;
916
+ errorMessage;
917
+ constructor(ids, options = {}) {
918
+ this.ids = ids;
919
+ this.responder = options.responder;
920
+ this.decoder = new ProtocolDecoder(options.maxBytes);
921
+ }
922
+ /** The v2 messages OmniNode sends to open a task (written to the agent's stdin). */
923
+ taskMessages(objective, role, context, instruction) {
924
+ const ids = this.ids;
925
+ const messages = [envelope("TASK", { taskId: ids.taskId, objective }, ids)];
926
+ if (role !== void 0) messages.push(envelope("CONTEXT", { role }, ids));
927
+ if (context !== void 0 && context.length > 0) messages.push(envelope("CONTEXT", { body: context }, ids));
928
+ if (instruction !== void 0 && instruction.length > 0) messages.push(envelope("INSTRUCTION", { text: instruction }, ids));
929
+ return messages.map((m) => encodeMessage(m));
930
+ }
931
+ helloAck(descriptor) {
932
+ return encodeMessage(buildHelloAck(descriptor));
933
+ }
934
+ /** Consumes decoded stdout, answering questions as they arrive. */
935
+ async handle(decoded) {
936
+ if (!decoded.ok) {
937
+ this.violations.push(decoded.error);
938
+ return;
939
+ }
940
+ const message = decoded.envelope;
941
+ if (message.protocol === PROTOCOL_V2) this.sawV2 = true;
942
+ if (message.type === "HELLO") {
943
+ const hello = parseHello(message);
944
+ if (hello.ok) this.descriptor = hello.descriptor;
945
+ else this.violations.push({ code: "INVALID_PAYLOAD", message: `bad HELLO: ${hello.reason}` });
946
+ return;
947
+ }
948
+ if (message.type === "QUESTION") {
949
+ await this.answerQuestion(message);
950
+ return;
951
+ }
952
+ if (message.type === "REPORT") {
953
+ this.reports.push(message.payload);
954
+ return;
955
+ }
956
+ if (message.type === "ARTIFACT") {
957
+ this.artifacts.push(message.payload);
958
+ return;
959
+ }
960
+ if (message.type === "COMPLETION") {
961
+ const payload = message.payload;
962
+ this.completionSummary = typeof payload?.summary === "string" ? payload.summary : "";
963
+ return;
964
+ }
965
+ if (message.type === "ERROR") {
966
+ const payload = message.payload;
967
+ const detail = typeof payload?.message === "string" ? payload.message : "agent reported an error";
968
+ const code = typeof payload?.code === "string" ? ` [${payload.code}]` : "";
969
+ this.errorMessage = `Agent error${code}: ${detail}`;
970
+ return;
971
+ }
972
+ if (message.type === "TASK_ACCEPTED" || message.type === "TASK_STARTED" || message.type === "STATUS") {
973
+ return;
974
+ }
975
+ this.violations.push({
976
+ code: "UNKNOWN_TYPE",
977
+ message: `unexpected message type ${message.type} from agent`
978
+ });
979
+ }
980
+ async answerQuestion(message) {
981
+ const payload = message.payload ?? {};
982
+ const questionId = typeof payload.questionId === "string" ? payload.questionId : message.messageId;
983
+ const question = typeof payload.question === "string" ? payload.question : "";
984
+ const expected = typeof payload.expected === "string" ? payload.expected : "text";
985
+ let answer;
986
+ if (this.responder) {
987
+ try {
988
+ answer = await this.responder({ questionId, question, expected });
989
+ } catch (error) {
990
+ this.violations.push({
991
+ code: "INVALID_PAYLOAD",
992
+ message: `question responder failed: ${error instanceof Error ? error.message : String(error)}`
993
+ });
994
+ }
995
+ }
996
+ this.questions.push({ questionId, question, ...answer !== void 0 ? { answer } : {} });
997
+ this.pendingAnswers.push(
998
+ encodeMessage(
999
+ envelope("RESPONSE", { questionId, ...answer !== void 0 ? { body: answer } : {} }, {
1000
+ ...this.ids,
1001
+ requestId: message.requestId ?? this.ids.requestId
1002
+ })
1003
+ )
1004
+ );
1005
+ }
1006
+ pendingAnswers = [];
1007
+ /** True once any inbound message carried the v2 protocol field. */
1008
+ sawV2 = false;
1009
+ };
1010
+ async function decodeStream(session, chunk) {
1011
+ const decoded = session.decoder.push(chunk);
1012
+ for (const result of decoded) {
1013
+ await session.handle(result);
1014
+ }
1015
+ return session.pendingAnswers.splice(0);
1016
+ }
1017
+ function requireCompletion(session) {
1018
+ if (session.errorMessage !== void 0) {
1019
+ throw new ProtocolError(session.errorMessage);
1020
+ }
1021
+ }
1022
+
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
+ }
678
1073
  function buildChildEnv(config) {
679
1074
  const configured = config.env ?? {};
680
- if (config.inheritEnv !== false) {
1075
+ const policy = resolveEnvPolicy(config);
1076
+ if (policy === "inherit") {
681
1077
  return { ...process.env, ...configured };
682
1078
  }
683
- return {
684
- PATH: process.env.PATH ?? "",
685
- HOME: process.env.HOME ?? "",
686
- ...configured
687
- };
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
+ }
688
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;
689
1111
  function composeTaskText(input) {
690
1112
  const sections = [`# Objective
691
1113
  ${input.objective}`];
@@ -706,18 +1128,19 @@ ${input.instruction}`);
706
1128
  return sections.join("\n\n");
707
1129
  }
708
1130
  function buildProtocolMessages(input, agentName) {
709
- const meta = {
1131
+ const session = new ProtocolSession({
1132
+ requestId: `req-${input.taskId}`,
710
1133
  taskId: input.taskId,
711
- agent: agentName,
712
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
713
- };
714
- const messages = [
715
- { type: "TASK", payload: { taskId: input.taskId, objective: input.objective } }
716
- ];
717
- if (input.role) messages.push({ type: "ROLE", payload: input.role });
718
- if (input.context) messages.push({ type: "CONTEXT", payload: { body: input.context } });
719
- if (input.instruction) messages.push({ type: "INSTRUCTION", payload: { text: input.instruction } });
720
- return messages.map((message) => JSON.stringify({ meta, message }));
1134
+ agentId: agentName,
1135
+ ...input.pipelineId !== void 0 ? { pipelineId: input.pipelineId } : {},
1136
+ ...input.executionId !== void 0 ? { executionId: input.executionId } : {}
1137
+ });
1138
+ return session.taskMessages(
1139
+ input.objective,
1140
+ input.role,
1141
+ input.context,
1142
+ input.instruction
1143
+ );
721
1144
  }
722
1145
  var ProcessAgent = class {
723
1146
  config;
@@ -746,40 +1169,73 @@ var ProcessAgent = class {
746
1169
  }
747
1170
  const mode = this.config.inputMode ?? "stdin";
748
1171
  const timeoutMs = this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
749
- return new Promise((resolve, reject) => {
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
+ }
750
1182
  const child = spawn(
751
1183
  command,
752
1184
  [...this.config.args ?? [], ...mode === "arg" ? [composeTaskText(input)] : []],
753
1185
  {
754
- cwd: this.config.cwd,
1186
+ ...cwd !== void 0 ? { cwd } : {},
755
1187
  env: buildChildEnv(this.config),
756
- 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 }
757
1192
  }
758
1193
  );
759
1194
  this.running.set(input.taskId, child);
1195
+ GLOBAL_RUNNING.add(child);
1196
+ installSignalForwarding();
760
1197
  let stdout = "";
761
1198
  let stderr = "";
762
1199
  let timedOut = false;
1200
+ let outputLimitExceeded = false;
763
1201
  let settled = false;
1202
+ const maxOutputBytes = this.config.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
764
1203
  const timer = setTimeout(() => {
765
1204
  timedOut = true;
766
- this.log.warn(`Task ${input.taskId} exceeded ${timeoutMs}ms \u2014 killing process.`);
767
- child.kill("SIGTERM");
768
- setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
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();
769
1208
  }, timeoutMs);
770
1209
  const settle = (finish) => {
771
1210
  if (settled) return;
772
1211
  settled = true;
773
1212
  clearTimeout(timer);
774
1213
  this.running.delete(input.taskId);
1214
+ GLOBAL_RUNNING.delete(child);
775
1215
  finish();
776
1216
  };
777
- child.stdout?.on("data", (chunk) => {
778
- stdout += chunk.toString();
779
- });
780
- child.stderr?.on("data", (chunk) => {
781
- stderr += chunk.toString();
782
- });
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));
783
1239
  child.stdin?.on("error", () => {
784
1240
  });
785
1241
  if (mode !== "arg") {
@@ -833,7 +1289,7 @@ var ProcessAgent = class {
833
1289
  const logs = stderr.trim().length > 0 ? stderr.split("\n") : void 0;
834
1290
  if (mode === "protocol") {
835
1291
  try {
836
- resolve(
1292
+ resolve2(
837
1293
  interpretProtocolRun({
838
1294
  input,
839
1295
  agentName: this.config.name,
@@ -848,14 +1304,14 @@ var ProcessAgent = class {
848
1304
  return;
849
1305
  }
850
1306
  const trimmed = stdout.trim();
851
- resolve({
1307
+ resolve2({
852
1308
  taskId: input.taskId,
853
- status: code === 0 ? "completed" : "failed",
1309
+ status: code === 0 && !outputLimitExceeded ? "completed" : "failed",
854
1310
  summary: trimmed.length > 0 ? firstLine(trimmed) : void 0,
855
1311
  rawOutput: trimmed.length > 0 ? trimmed : void 0,
856
1312
  exitCode: code ?? void 0,
857
1313
  logs,
858
- error: code === 0 ? void 0 : `Process exited with code ${code ?? "unknown"}${signal ? ` (signal: ${signal})` : ""}`
1314
+ ...outputLimitExceeded ? { error: `Agent exceeded the ${maxOutputBytes} byte output limit and was terminated.` } : code === 0 ? {} : { error: describeExit(code, signal) }
859
1315
  });
860
1316
  });
861
1317
  });
@@ -864,67 +1320,117 @@ var ProcessAgent = class {
864
1320
  /** Ask the agent to stop current work (SIGTERM). */
865
1321
  async cancel(taskId) {
866
1322
  const child = this.running.get(taskId);
867
- if (child) child.kill("SIGTERM");
1323
+ if (child) killTree(child, "SIGTERM");
868
1324
  }
869
1325
  runningTaskIds() {
870
1326
  return [...this.running.keys()];
871
1327
  }
872
1328
  };
873
- function interpretProtocolRun({ input, agentName, stdout, exitCode, logs }) {
874
- const reports = [];
875
- let summary;
876
- let error;
877
- let sawProtocolMessage = false;
878
- for (const line of stdout.split("\n")) {
879
- const trimmed2 = line.trim();
880
- if (trimmed2.length === 0) continue;
881
- const message = parseAgentMessage(trimmed2);
882
- if (!message) continue;
883
- sawProtocolMessage = true;
884
- if (message.type === "REPORT") {
885
- const report = normalizeReport(message.payload, input.taskId, agentName, reports.length);
886
- if (report) reports.push(report);
887
- } else if (message.type === "COMPLETION") {
888
- const payload = message.payload;
889
- if (typeof payload?.summary === "string") summary = payload.summary;
890
- } else if (message.type === "ERROR") {
891
- const payload = message.payload;
892
- const detail = typeof payload?.message === "string" ? payload.message : "unknown error";
893
- const code = typeof payload?.code === "string" ? ` [${payload.code}]` : "";
894
- error = `Agent error${code}: ${detail}`;
895
- }
1329
+ function interpretProtocolRun({
1330
+ input,
1331
+ agentName,
1332
+ stdout,
1333
+ exitCode,
1334
+ logs,
1335
+ responder
1336
+ }) {
1337
+ const session = new ProtocolSession(
1338
+ {
1339
+ requestId: `req-${input.taskId}`,
1340
+ taskId: input.taskId,
1341
+ agentId: agentName,
1342
+ ...input.pipelineId !== void 0 ? { pipelineId: input.pipelineId } : {},
1343
+ ...input.executionId !== void 0 ? { executionId: input.executionId } : {}
1344
+ },
1345
+ { ...responder !== void 0 ? { responder } : {} }
1346
+ );
1347
+ for (const result of session.decoder.push(stdout)) {
1348
+ session.handle(result);
896
1349
  }
897
- if (!sawProtocolMessage && exitCode === 0) {
898
- throw new ProtocolError(
899
- `Agent "${agentName}" produced no protocol messages on stdout (input mode "protocol").`,
900
- { details: { taskId: input.taskId, stdout: stdout.slice(0, 2e3) } }
901
- );
1350
+ for (const result of session.decoder.flush()) {
1351
+ session.handle(result);
902
1352
  }
1353
+ const reports = session.reports.flatMap((payload) => Array.isArray(payload) ? payload : [payload]).map((payload, index) => normalizeReport(payload, input.taskId, agentName, index)).filter((report) => report !== void 0);
903
1354
  const trimmed = stdout.trim();
1355
+ const protocolBlock = {
1356
+ protocol: session.descriptor?.protocol ?? PROTOCOL_V2,
1357
+ legacy: !session.sawV2 && session.descriptor === void 0,
1358
+ ...session.descriptor !== void 0 ? { descriptor: session.descriptor } : {},
1359
+ questions: session.questions,
1360
+ violations: session.violations.map((v) => `${v.code}: ${v.message}`)
1361
+ };
1362
+ if (session.errorMessage !== void 0) {
1363
+ return {
1364
+ taskId: input.taskId,
1365
+ status: "failed",
1366
+ error: session.errorMessage,
1367
+ rawOutput: trimmed.length > 0 ? trimmed : void 0,
1368
+ exitCode: exitCode ?? void 0,
1369
+ logs,
1370
+ protocol: protocolBlock,
1371
+ ...reports.length > 0 ? { reports } : {}
1372
+ };
1373
+ }
1374
+ if (session.descriptor === void 0 && session.violations.length > 0 && session.reports.length === 0 && session.completionSummary === void 0) {
1375
+ return {
1376
+ taskId: input.taskId,
1377
+ status: "failed",
1378
+ error: `agent produced no valid protocol messages: ${protocolBlock.violations.join("; ")}`,
1379
+ rawOutput: trimmed.length > 0 ? trimmed : void 0,
1380
+ exitCode: exitCode ?? void 0,
1381
+ logs,
1382
+ protocol: protocolBlock
1383
+ };
1384
+ }
1385
+ const completed = exitCode === 0 && session.completionSummary !== void 0;
904
1386
  return {
905
1387
  taskId: input.taskId,
906
- status: error !== void 0 || exitCode !== 0 ? "failed" : "completed",
907
- summary: summary ?? reports[0]?.summary,
1388
+ status: completed || exitCode === 0 ? completed ? "completed" : "completed" : "failed",
1389
+ summary: session.completionSummary ?? reports[0]?.summary,
908
1390
  rawOutput: trimmed.length > 0 ? trimmed : void 0,
909
1391
  exitCode: exitCode ?? void 0,
910
1392
  logs,
911
- reports: reports.length > 0 ? reports : void 0,
912
- error: error ?? (exitCode !== 0 ? `Process exited with code ${exitCode ?? "unknown"}` : void 0)
1393
+ protocol: protocolBlock,
1394
+ ...reports.length > 0 ? { reports } : {},
1395
+ ...exitCode !== 0 ? { error: `Process exited with code ${exitCode ?? "unknown"}` } : {}
913
1396
  };
914
1397
  }
915
- function parseAgentMessage(line) {
916
- try {
917
- const parsed = JSON.parse(line);
918
- if (typeof parsed.type === "string") {
919
- return { type: parsed.type, payload: parsed.payload };
920
- }
921
- const inner = parsed.message;
922
- if (inner && typeof inner.type === "string") {
923
- return { type: inner.type, payload: inner.payload };
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 {
924
1418
  }
1419
+ }
1420
+ try {
1421
+ child.kill(signal);
925
1422
  } catch {
926
1423
  }
927
- return void 0;
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, "");
928
1434
  }
929
1435
  function normalizeReport(payload, taskId, agentName, index) {
930
1436
  if (payload === null || typeof payload !== "object") return void 0;
@@ -945,6 +1451,40 @@ function firstLine(text, maxLength = 200) {
945
1451
  return line.length > maxLength ? `${line.slice(0, maxLength)}\u2026` : line;
946
1452
  }
947
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
+
948
1488
  // src/agents/registry.ts
949
1489
  var AgentRegistry = class {
950
1490
  agents = /* @__PURE__ */ new Map();
@@ -960,14 +1500,15 @@ var AgentRegistry = class {
960
1500
  };
961
1501
 
962
1502
  // src/agents/factory.ts
963
- function createAgent(config) {
964
- if (config.integration === "process") {
965
- return new ProcessAgent(config);
966
- }
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);
967
1508
  if (config.integration === "native") {
968
1509
  throw new OmniNodeError(
969
1510
  "NOT_IMPLEMENTED",
970
- `Native agent adapters are planned for a later phase. Use input over stdin/stdout (type "cli") for "${config.name}" for now.`
1511
+ `No native adapter is registered for "${config.name}". Register an IAgentAdapter (createAgent(config, adapters)) or use input over stdin/stdout (type "cli").`
971
1512
  );
972
1513
  }
973
1514
  throw new OmniNodeError("NOT_IMPLEMENTED", `Agent integration "${config.integration}" has no adapter yet.`);
@@ -1347,7 +1888,7 @@ function classifyThrown(error) {
1347
1888
  return "failed";
1348
1889
  }
1349
1890
  function sleep(ms) {
1350
- return new Promise((resolve) => setTimeout(resolve, ms));
1891
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1351
1892
  }
1352
1893
 
1353
1894
  // src/audit/index.ts
@@ -2685,12 +3226,14 @@ var ProjectStores = class {
2685
3226
  };
2686
3227
 
2687
3228
  // src/version.ts
2688
- var OMNINODE_VERSION = "2.0.0-alpha.2";
3229
+ var OMNINODE_VERSION = "2.0.0-alpha.4";
2689
3230
  export {
3231
+ AgentAdapterRegistry,
2690
3232
  AgentError,
2691
3233
  AgentRegistry,
2692
3234
  ConfigError,
2693
3235
  ConsoleLogSink,
3236
+ DEFAULT_MAX_OUTPUT_BYTES,
2694
3237
  FileAuditLog,
2695
3238
  FilePipelineRunStore,
2696
3239
  FilePlanStore,
@@ -2700,6 +3243,7 @@ export {
2700
3243
  JsonFileStore,
2701
3244
  LocalMemoryProvider,
2702
3245
  Logger,
3246
+ MAX_MESSAGE_BYTES,
2703
3247
  MemoryError,
2704
3248
  MemoryService,
2705
3249
  ModelPlanner,
@@ -2709,16 +3253,21 @@ export {
2709
3253
  OmniHilbrasProvider,
2710
3254
  OmniNodeError,
2711
3255
  OpenAICompatibleProvider,
3256
+ PROTOCOL_V2,
2712
3257
  PipelineEngine,
2713
3258
  PipelineError,
2714
3259
  PlannerError,
2715
3260
  ProcessAgent,
3261
+ ProcessAgentAdapter,
2716
3262
  ProjectStores,
3263
+ ProtocolDecoder,
2717
3264
  ProtocolError,
3265
+ ProtocolSession,
2718
3266
  ProviderError,
2719
3267
  RememberaMemoryProvider,
2720
3268
  ReportService,
2721
3269
  RoleRegistry,
3270
+ SUPPORTED_PROTOCOL_VERSIONS,
2722
3271
  TERMINAL_TASK_STATUSES,
2723
3272
  TaskEngine,
2724
3273
  TaskError,
@@ -2728,6 +3277,8 @@ export {
2728
3277
  authHeaders,
2729
3278
  buildAgentRegistry,
2730
3279
  buildChildEnv,
3280
+ buildHello,
3281
+ buildHelloAck,
2731
3282
  buildPipelineEngine,
2732
3283
  buildPlanner,
2733
3284
  buildPlannerContext,
@@ -2738,14 +3289,23 @@ export {
2738
3289
  createMemoryProvider,
2739
3290
  createProvider,
2740
3291
  createTaskEngine,
3292
+ decodeMessage,
3293
+ decodeStream,
2741
3294
  defaultProjectConfigYaml,
3295
+ describeExit,
3296
+ encodeMessage,
3297
+ envelope,
2742
3298
  expandEnvRefs,
2743
3299
  extractReportFromText,
2744
3300
  findConfigFile,
2745
3301
  isOmniNodeError,
3302
+ isSupportedProtocol,
2746
3303
  keywords,
3304
+ killTree,
2747
3305
  loadConfig,
2748
3306
  logger,
3307
+ newMessageId,
3308
+ parseHello,
2749
3309
  parsePlanJson,
2750
3310
  pipelineConfigSchema,
2751
3311
  pipelineStepConfigSchema,
@@ -2754,8 +3314,13 @@ export {
2754
3314
  providerConfigSchema,
2755
3315
  registryKey,
2756
3316
  requestJson,
3317
+ requireCompletion,
2757
3318
  resolveApiKey,
3319
+ resolveEnvPolicy,
2758
3320
  roleConfigSchema,
2759
- similar
3321
+ sanitizeChunk,
3322
+ similar,
3323
+ validateEnvelope,
3324
+ validateWorkingDirectory
2760
3325
  };
2761
3326
  //# sourceMappingURL=index.js.map