@open330/oac 2026.4.3 → 2026.220.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +2 -2
  3. package/dist/{chunk-YWIB3TRI.js → chunk-6A37SKAJ.js} +15 -2
  4. package/dist/chunk-6A37SKAJ.js.map +1 -0
  5. package/dist/{chunk-ZRYAHZQJ.js → chunk-7C7SC4TZ.js} +1 -1
  6. package/dist/{chunk-XUW3XWTX.js → chunk-7Y4LZUDP.js} +89 -121
  7. package/dist/chunk-7Y4LZUDP.js.map +1 -0
  8. package/dist/{chunk-CJAJ4MBO.js → chunk-OS3XDHOJ.js} +57 -18
  9. package/dist/chunk-OS3XDHOJ.js.map +1 -0
  10. package/dist/{chunk-HDMLNOND.js → chunk-OTPXGXO7.js} +44 -18
  11. package/dist/chunk-OTPXGXO7.js.map +1 -0
  12. package/dist/{chunk-7FWC3Z4W.js → chunk-QPVNC7S4.js} +479 -196
  13. package/dist/chunk-QPVNC7S4.js.map +1 -0
  14. package/dist/cli/cli.js +6 -6
  15. package/dist/cli/index.js +6 -6
  16. package/dist/completion/index.js +4 -8
  17. package/dist/completion/index.js.map +1 -1
  18. package/dist/core/index.d.ts +16 -1
  19. package/dist/core/index.js +8 -4
  20. package/dist/dashboard/index.js +33 -24
  21. package/dist/dashboard/index.js.map +1 -1
  22. package/dist/discovery/index.d.ts +18 -2
  23. package/dist/discovery/index.js +4 -2
  24. package/dist/execution/index.d.ts +45 -1
  25. package/dist/execution/index.js +7 -3
  26. package/dist/repo/index.d.ts +2 -2
  27. package/dist/repo/index.js +1 -1
  28. package/dist/{types-CYCwgojB.d.ts → types-3_IAAxh5.d.ts} +1 -0
  29. package/docs/config-reference.md +271 -0
  30. package/docs/multi-agent-support-technical-spec.md +312 -0
  31. package/package.json +23 -18
  32. package/dist/chunk-7FWC3Z4W.js.map +0 -1
  33. package/dist/chunk-CJAJ4MBO.js.map +0 -1
  34. package/dist/chunk-HDMLNOND.js.map +0 -1
  35. package/dist/chunk-XUW3XWTX.js.map +0 -1
  36. package/dist/chunk-YWIB3TRI.js.map +0 -1
  37. /package/dist/{chunk-ZRYAHZQJ.js.map → chunk-7C7SC4TZ.js.map} +0 -0
@@ -1,11 +1,16 @@
1
1
  import {
2
2
  OacError,
3
3
  executionError
4
- } from "./chunk-ZRYAHZQJ.js";
4
+ } from "./chunk-7C7SC4TZ.js";
5
+ import {
6
+ isRecord
7
+ } from "./chunk-6A37SKAJ.js";
5
8
 
6
9
  // src/execution/agents/claude-code.adapter.ts
7
10
  import { createInterface } from "readline";
8
11
  import { execa } from "execa";
12
+
13
+ // src/execution/agents/shared.ts
9
14
  var AsyncEventQueue = class {
10
15
  values = [];
11
16
  resolvers = [];
@@ -62,9 +67,6 @@ var AsyncEventQueue = class {
62
67
  }
63
68
  }
64
69
  };
65
- function isRecord(value) {
66
- return typeof value === "object" && value !== null;
67
- }
68
70
  function readNumber(value) {
69
71
  if (typeof value !== "number" || !Number.isFinite(value)) {
70
72
  return void 0;
@@ -78,6 +80,8 @@ function readString(value) {
78
80
  const trimmed = value.trim();
79
81
  return trimmed.length > 0 ? trimmed : void 0;
80
82
  }
83
+
84
+ // src/execution/agents/claude-code.adapter.ts
81
85
  function parseJsonPayload(line) {
82
86
  const trimmed = line.trim();
83
87
  if (trimmed.length === 0) {
@@ -497,78 +501,6 @@ var ClaudeCodeAdapter = class {
497
501
  import { stat } from "fs/promises";
498
502
  import { createInterface as createInterface2 } from "readline";
499
503
  import { execa as execa2 } from "execa";
500
- var AsyncEventQueue2 = class {
501
- values = [];
502
- resolvers = [];
503
- done = false;
504
- pendingError;
505
- push(value) {
506
- if (this.done) {
507
- return;
508
- }
509
- const nextResolver = this.resolvers.shift();
510
- if (nextResolver) {
511
- nextResolver({ done: false, value });
512
- return;
513
- }
514
- this.values.push(value);
515
- }
516
- close() {
517
- if (this.done) {
518
- return;
519
- }
520
- this.done = true;
521
- this.flush();
522
- }
523
- fail(error) {
524
- this.pendingError = error;
525
- this.done = true;
526
- this.flush();
527
- }
528
- [Symbol.asyncIterator]() {
529
- return {
530
- next: async () => {
531
- if (this.values.length > 0) {
532
- const value = this.values.shift();
533
- if (value === void 0) {
534
- return { done: true, value: void 0 };
535
- }
536
- return { done: false, value };
537
- }
538
- if (this.pendingError !== void 0) {
539
- throw this.pendingError;
540
- }
541
- if (this.done) {
542
- return { done: true, value: void 0 };
543
- }
544
- return new Promise((resolve2) => {
545
- this.resolvers.push(resolve2);
546
- });
547
- }
548
- };
549
- }
550
- flush() {
551
- for (const resolve2 of this.resolvers.splice(0)) {
552
- resolve2({ done: true, value: void 0 });
553
- }
554
- }
555
- };
556
- function isRecord2(value) {
557
- return typeof value === "object" && value !== null;
558
- }
559
- function readString2(value) {
560
- if (typeof value !== "string") {
561
- return void 0;
562
- }
563
- const trimmed = value.trim();
564
- return trimmed.length > 0 ? trimmed : void 0;
565
- }
566
- function readNumber2(value) {
567
- if (typeof value !== "number" || !Number.isFinite(value)) {
568
- return void 0;
569
- }
570
- return Math.max(0, Math.floor(value));
571
- }
572
504
  function parseJsonPayload2(line) {
573
505
  const trimmed = line.trim();
574
506
  if (trimmed.length === 0) {
@@ -576,7 +508,7 @@ function parseJsonPayload2(line) {
576
508
  }
577
509
  try {
578
510
  const parsed = JSON.parse(trimmed);
579
- if (isRecord2(parsed)) {
511
+ if (isRecord(parsed)) {
580
512
  return parsed;
581
513
  }
582
514
  } catch {
@@ -584,15 +516,15 @@ function parseJsonPayload2(line) {
584
516
  return void 0;
585
517
  }
586
518
  function parseTokenPatchFromPayload2(payload) {
587
- const usage = isRecord2(payload.usage) ? payload.usage : void 0;
519
+ const usage = isRecord(payload.usage) ? payload.usage : void 0;
588
520
  return {
589
- inputTokens: readNumber2(
521
+ inputTokens: readNumber(
590
522
  payload.inputTokens ?? payload.input_tokens ?? payload.promptTokens ?? payload.prompt_tokens ?? usage?.inputTokens ?? usage?.input_tokens ?? usage?.promptTokens ?? usage?.prompt_tokens
591
523
  ),
592
- outputTokens: readNumber2(
524
+ outputTokens: readNumber(
593
525
  payload.outputTokens ?? payload.output_tokens ?? payload.completionTokens ?? payload.completion_tokens ?? usage?.outputTokens ?? usage?.output_tokens ?? usage?.completionTokens ?? usage?.completion_tokens
594
526
  ),
595
- cumulativeTokens: readNumber2(
527
+ cumulativeTokens: readNumber(
596
528
  payload.cumulativeTokens ?? payload.cumulative_tokens ?? payload.totalTokens ?? payload.total_tokens ?? usage?.cumulativeTokens ?? usage?.cumulative_tokens ?? usage?.totalTokens ?? usage?.total_tokens
597
529
  )
598
530
  };
@@ -637,7 +569,7 @@ function normalizeFileAction2(value) {
637
569
  function parseFileEditFromPayload2(payload) {
638
570
  if (payload.type === "file_edit") {
639
571
  const action = normalizeFileAction2(payload.action);
640
- const path = readString2(payload.path);
572
+ const path = readString(payload.path);
641
573
  if (action && path) {
642
574
  return {
643
575
  type: "file_edit",
@@ -646,9 +578,9 @@ function parseFileEditFromPayload2(payload) {
646
578
  };
647
579
  }
648
580
  }
649
- const tool = readString2(payload.tool ?? payload.tool_name ?? payload.name);
650
- const input = isRecord2(payload.input) ? payload.input : void 0;
651
- const inputPath = readString2(input?.path ?? input?.file_path ?? input?.filePath);
581
+ const tool = readString(payload.tool ?? payload.tool_name ?? payload.name);
582
+ const input = isRecord(payload.input) ? payload.input : void 0;
583
+ const inputPath = readString(input?.path ?? input?.file_path ?? input?.filePath);
652
584
  if (!tool || !inputPath) {
653
585
  return void 0;
654
586
  }
@@ -664,7 +596,7 @@ function parseFileEditFromPayload2(payload) {
664
596
  return void 0;
665
597
  }
666
598
  function parseToolUseFromPayload(payload) {
667
- const tool = readString2(payload.tool ?? payload.tool_name ?? payload.name);
599
+ const tool = readString(payload.tool ?? payload.tool_name ?? payload.name);
668
600
  if (!tool) {
669
601
  return void 0;
670
602
  }
@@ -680,7 +612,7 @@ function parseErrorFromPayload(payload) {
680
612
  }
681
613
  return {
682
614
  type: "error",
683
- message: readString2(payload.message) ?? "Unknown Codex CLI error",
615
+ message: readString(payload.message) ?? "Unknown Codex CLI error",
684
616
  recoverable: payload.recoverable !== false
685
617
  };
686
618
  }
@@ -740,7 +672,7 @@ function normalizeExitCode2(value) {
740
672
  return 1;
741
673
  }
742
674
  function hasBooleanFlag2(value, key) {
743
- if (!isRecord2(value)) {
675
+ if (!isRecord(value)) {
744
676
  return false;
745
677
  }
746
678
  return value[key] === true;
@@ -810,7 +742,7 @@ var CodexAdapter = class {
810
742
  outputTokens: 0,
811
743
  cumulativeTokens: 0
812
744
  };
813
- const eventQueue = new AsyncEventQueue2();
745
+ const eventQueue = new AsyncEventQueue();
814
746
  const processEnv = {
815
747
  ...Object.fromEntries(
816
748
  Object.entries(process.env).filter(
@@ -974,6 +906,387 @@ var CodexAdapter = class {
974
906
  }
975
907
  };
976
908
 
909
+ // src/execution/agents/opencode.adapter.ts
910
+ import { stat as stat2 } from "fs/promises";
911
+ import { createInterface as createInterface3 } from "readline";
912
+ import { execa as execa3 } from "execa";
913
+ function parseJsonPayload3(line) {
914
+ const trimmed = line.trim();
915
+ if (trimmed.length === 0) return void 0;
916
+ try {
917
+ const parsed = JSON.parse(trimmed);
918
+ if (isRecord(parsed)) return parsed;
919
+ } catch {
920
+ }
921
+ return void 0;
922
+ }
923
+ function parseTokenPatchFromPayload3(payload) {
924
+ const usage = isRecord(payload.usage) ? payload.usage : void 0;
925
+ return {
926
+ inputTokens: readNumber(
927
+ payload.inputTokens ?? payload.input_tokens ?? payload.promptTokens ?? payload.prompt_tokens ?? usage?.inputTokens ?? usage?.input_tokens ?? usage?.promptTokens ?? usage?.prompt_tokens
928
+ ),
929
+ outputTokens: readNumber(
930
+ payload.outputTokens ?? payload.output_tokens ?? payload.completionTokens ?? payload.completion_tokens ?? usage?.outputTokens ?? usage?.output_tokens ?? usage?.completionTokens ?? usage?.completion_tokens
931
+ ),
932
+ cumulativeTokens: readNumber(
933
+ payload.cumulativeTokens ?? payload.cumulative_tokens ?? payload.totalTokens ?? payload.total_tokens ?? usage?.cumulativeTokens ?? usage?.cumulative_tokens ?? usage?.totalTokens ?? usage?.total_tokens
934
+ )
935
+ };
936
+ }
937
+ function patchTokenState3(state, patch) {
938
+ if (patch.inputTokens === void 0 && patch.outputTokens === void 0 && patch.cumulativeTokens === void 0) {
939
+ return void 0;
940
+ }
941
+ state.inputTokens = patch.inputTokens ?? state.inputTokens;
942
+ state.outputTokens = patch.outputTokens ?? state.outputTokens;
943
+ const computedTotal = state.inputTokens + state.outputTokens;
944
+ state.cumulativeTokens = Math.max(
945
+ state.cumulativeTokens,
946
+ patch.cumulativeTokens ?? computedTotal
947
+ );
948
+ return {
949
+ type: "tokens",
950
+ inputTokens: state.inputTokens,
951
+ outputTokens: state.outputTokens,
952
+ cumulativeTokens: state.cumulativeTokens
953
+ };
954
+ }
955
+ function parseTokenEvent3(line, payload, state) {
956
+ if (payload) {
957
+ return patchTokenState3(state, parseTokenPatchFromPayload3(payload));
958
+ }
959
+ const inputMatch = line.match(/(?:input|prompt)\s*tokens?\s*[:=]\s*(\d+)/i);
960
+ const outputMatch = line.match(/(?:output|completion)\s*tokens?\s*[:=]\s*(\d+)/i);
961
+ const totalMatch = line.match(/(?:total|cumulative|used)\s*tokens?\s*[:=]\s*(\d+)/i);
962
+ return patchTokenState3(state, {
963
+ inputTokens: inputMatch ? Number.parseInt(inputMatch[1], 10) : void 0,
964
+ outputTokens: outputMatch ? Number.parseInt(outputMatch[1], 10) : void 0,
965
+ cumulativeTokens: totalMatch ? Number.parseInt(totalMatch[1], 10) : void 0
966
+ });
967
+ }
968
+ function normalizeFileAction3(value) {
969
+ if (value !== "create" && value !== "modify" && value !== "delete") return void 0;
970
+ return value;
971
+ }
972
+ function parseFileEditFromPayload3(payload) {
973
+ if (payload.type === "file_edit") {
974
+ const action = normalizeFileAction3(payload.action);
975
+ const path = readString(payload.path);
976
+ if (action && path) return { type: "file_edit", action, path };
977
+ }
978
+ const tool = readString(payload.tool ?? payload.tool_name ?? payload.name);
979
+ const input = isRecord(payload.input) ? payload.input : void 0;
980
+ const inputPath = readString(input?.path ?? input?.file_path ?? input?.filePath);
981
+ if (!tool || !inputPath) return void 0;
982
+ if (tool === "create_file") return { type: "file_edit", action: "create", path: inputPath };
983
+ if (tool === "delete_file") return { type: "file_edit", action: "delete", path: inputPath };
984
+ if (tool === "write_file" || tool === "edit_file" || tool === "replace_file") {
985
+ return { type: "file_edit", action: "modify", path: inputPath };
986
+ }
987
+ return void 0;
988
+ }
989
+ function parseToolUseFromPayload2(payload) {
990
+ const tool = readString(payload.tool ?? payload.tool_name ?? payload.name);
991
+ if (!tool) return void 0;
992
+ return { type: "tool_use", tool, input: payload.input };
993
+ }
994
+ function parseErrorFromPayload2(payload) {
995
+ if (payload.type !== "error") return void 0;
996
+ return {
997
+ type: "error",
998
+ message: readString(payload.message) ?? "Unknown OpenCode CLI error",
999
+ recoverable: payload.recoverable !== false
1000
+ };
1001
+ }
1002
+ function estimateTokenCount3(text) {
1003
+ if (text.length === 0) return 0;
1004
+ return Math.max(1, Math.ceil(text.length / 4));
1005
+ }
1006
+ function normalizeUnknownError3(error, executionId) {
1007
+ if (error instanceof OacError) return error;
1008
+ const message = error instanceof Error ? error.message : String(error);
1009
+ if (/timed out|timeout/i.test(message)) {
1010
+ return executionError("AGENT_TIMEOUT", `OpenCode execution timed out for ${executionId}`, {
1011
+ context: { executionId, message },
1012
+ cause: error
1013
+ });
1014
+ }
1015
+ if (/out of memory|ENOMEM|heap/i.test(message)) {
1016
+ return executionError("AGENT_OOM", `OpenCode execution ran out of memory for ${executionId}`, {
1017
+ context: { executionId, message },
1018
+ cause: error
1019
+ });
1020
+ }
1021
+ if (/rate.limit|429|too many requests|throttl/i.test(message)) {
1022
+ return executionError("AGENT_RATE_LIMITED", `OpenCode execution rate-limited for ${executionId}`, {
1023
+ context: { executionId, message },
1024
+ cause: error
1025
+ });
1026
+ }
1027
+ if (/network|ECONN|ENOTFOUND|EAI_AGAIN/i.test(message)) {
1028
+ return new OacError(
1029
+ "OpenCode execution failed due to network issues",
1030
+ "NETWORK_ERROR",
1031
+ "recoverable",
1032
+ { executionId, message },
1033
+ error
1034
+ );
1035
+ }
1036
+ return executionError("AGENT_EXECUTION_FAILED", `OpenCode execution failed for ${executionId}`, {
1037
+ context: { executionId, message },
1038
+ cause: error
1039
+ });
1040
+ }
1041
+ function computeTotalTokens3(state) {
1042
+ return Math.max(state.cumulativeTokens, state.inputTokens + state.outputTokens);
1043
+ }
1044
+ function normalizeExitCode3(value) {
1045
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1046
+ return 1;
1047
+ }
1048
+ function hasBooleanFlag3(value, key) {
1049
+ if (!isRecord(value)) return false;
1050
+ return value[key] === true;
1051
+ }
1052
+ function buildFailureMessage3(stdout, stderr) {
1053
+ const trimmedStderr = stderr.trim();
1054
+ if (trimmedStderr.length > 0) return trimmedStderr;
1055
+ const trimmedStdout = stdout.trim();
1056
+ if (trimmedStdout.length > 0) return trimmedStdout;
1057
+ return "OpenCode CLI process exited with a non-zero status.";
1058
+ }
1059
+ function parseVersion2(output) {
1060
+ const match = output.match(/(\d+\.\d+\.\d+)/);
1061
+ return match ? match[1] : void 0;
1062
+ }
1063
+ async function estimateContextTokens2(targetFiles) {
1064
+ let totalBytes = 0;
1065
+ for (const filePath of targetFiles) {
1066
+ try {
1067
+ const fileStat = await stat2(filePath);
1068
+ if (fileStat.isFile()) totalBytes += fileStat.size;
1069
+ } catch {
1070
+ }
1071
+ }
1072
+ return Math.ceil(totalBytes / 4);
1073
+ }
1074
+ var OpenCodeAdapter = class {
1075
+ id = "opencode";
1076
+ name = "OpenCode";
1077
+ runningExecutions = /* @__PURE__ */ new Map();
1078
+ async checkAvailability() {
1079
+ try {
1080
+ const result = await execa3("opencode", ["--version"], { reject: false });
1081
+ if (result.exitCode === 0) {
1082
+ const versionLine = result.stdout.trim().split("\n")[0] ?? "";
1083
+ return {
1084
+ available: true,
1085
+ version: parseVersion2(versionLine)
1086
+ };
1087
+ }
1088
+ return {
1089
+ available: false,
1090
+ error: result.stderr.trim() || result.stdout.trim() || `opencode --version exited with code ${result.exitCode}`
1091
+ };
1092
+ } catch (error) {
1093
+ const message = error instanceof Error ? error.message : String(error);
1094
+ return { available: false, error: message };
1095
+ }
1096
+ }
1097
+ execute(params) {
1098
+ const startedAt = Date.now();
1099
+ const filesChanged = /* @__PURE__ */ new Set();
1100
+ const tokenState = {
1101
+ inputTokens: 0,
1102
+ outputTokens: 0,
1103
+ cumulativeTokens: 0
1104
+ };
1105
+ const eventQueue = new AsyncEventQueue();
1106
+ const processEnv = {
1107
+ ...Object.fromEntries(
1108
+ Object.entries(process.env).filter(
1109
+ (entry) => typeof entry[1] === "string"
1110
+ )
1111
+ ),
1112
+ ...params.env,
1113
+ OAC_TOKEN_BUDGET: `${params.tokenBudget}`,
1114
+ OAC_ALLOW_COMMITS: `${params.allowCommits}`
1115
+ };
1116
+ const subprocess = execa3(
1117
+ "opencode",
1118
+ ["run", "--format", "json", params.prompt],
1119
+ {
1120
+ cwd: params.workingDirectory,
1121
+ env: processEnv,
1122
+ reject: false,
1123
+ timeout: params.timeoutMs
1124
+ }
1125
+ );
1126
+ this.runningExecutions.set(params.executionId, subprocess);
1127
+ const processStdoutLine = (line) => {
1128
+ const payload = parseJsonPayload3(line);
1129
+ const tokenEvent = parseTokenEvent3(line, payload, tokenState);
1130
+ if (tokenEvent?.type === "tokens") {
1131
+ eventQueue.push(tokenEvent);
1132
+ }
1133
+ if (!payload) return;
1134
+ const fileEvent = parseFileEditFromPayload3(payload);
1135
+ if (fileEvent) {
1136
+ filesChanged.add(fileEvent.path);
1137
+ eventQueue.push(fileEvent);
1138
+ }
1139
+ const toolEvent = parseToolUseFromPayload2(payload);
1140
+ if (toolEvent) {
1141
+ eventQueue.push(toolEvent);
1142
+ }
1143
+ const errorEvent = parseErrorFromPayload2(payload);
1144
+ if (errorEvent) {
1145
+ eventQueue.push(errorEvent);
1146
+ }
1147
+ };
1148
+ const consumeStream = async (stream, streamName) => {
1149
+ if (!stream) return;
1150
+ const lineReader = createInterface3({
1151
+ input: stream,
1152
+ crlfDelay: Number.POSITIVE_INFINITY
1153
+ });
1154
+ for await (const line of lineReader) {
1155
+ eventQueue.push({ type: "output", content: line, stream: streamName });
1156
+ if (streamName === "stdout") {
1157
+ processStdoutLine(line);
1158
+ } else if (/error|failed|exception/i.test(line)) {
1159
+ eventQueue.push({ type: "error", message: line.trim(), recoverable: true });
1160
+ }
1161
+ }
1162
+ };
1163
+ const stdoutDone = consumeStream(subprocess.stdout ?? void 0, "stdout");
1164
+ const stderrDone = consumeStream(subprocess.stderr ?? void 0, "stderr");
1165
+ const resultPromise = (async () => {
1166
+ try {
1167
+ const settled = await subprocess;
1168
+ await Promise.all([stdoutDone, stderrDone]);
1169
+ const timedOut = hasBooleanFlag3(settled, "timedOut");
1170
+ if (timedOut) {
1171
+ const timeoutError = executionError(
1172
+ "AGENT_TIMEOUT",
1173
+ `OpenCode execution timed out for ${params.executionId}`,
1174
+ { context: { executionId: params.executionId, timeoutMs: params.timeoutMs } }
1175
+ );
1176
+ eventQueue.push({ type: "error", message: timeoutError.message, recoverable: true });
1177
+ throw timeoutError;
1178
+ }
1179
+ const canceled = hasBooleanFlag3(settled, "isCanceled");
1180
+ if (canceled) {
1181
+ return {
1182
+ success: false,
1183
+ exitCode: normalizeExitCode3(settled.exitCode),
1184
+ totalTokensUsed: computeTotalTokens3(tokenState),
1185
+ filesChanged: [...filesChanged],
1186
+ duration: Date.now() - startedAt,
1187
+ error: "OpenCode execution was cancelled."
1188
+ };
1189
+ }
1190
+ const exitCode = normalizeExitCode3(settled.exitCode);
1191
+ const success = exitCode === 0;
1192
+ return {
1193
+ success,
1194
+ exitCode,
1195
+ totalTokensUsed: computeTotalTokens3(tokenState),
1196
+ filesChanged: [...filesChanged],
1197
+ duration: Date.now() - startedAt,
1198
+ error: success ? void 0 : buildFailureMessage3(settled.stdout, settled.stderr)
1199
+ };
1200
+ } catch (error) {
1201
+ const normalized = normalizeUnknownError3(error, params.executionId);
1202
+ eventQueue.push({
1203
+ type: "error",
1204
+ message: normalized.message,
1205
+ recoverable: normalized.severity !== "fatal"
1206
+ });
1207
+ eventQueue.fail(normalized);
1208
+ throw normalized;
1209
+ } finally {
1210
+ this.runningExecutions.delete(params.executionId);
1211
+ eventQueue.close();
1212
+ }
1213
+ })();
1214
+ return {
1215
+ executionId: params.executionId,
1216
+ providerId: this.id,
1217
+ events: eventQueue,
1218
+ result: resultPromise,
1219
+ pid: subprocess.pid
1220
+ };
1221
+ }
1222
+ async estimateTokens(params) {
1223
+ const baseTokens = params.targetFiles.length * 2e3;
1224
+ const promptTokens = estimateTokenCount3(params.prompt);
1225
+ const contextTokens = await estimateContextTokens2(params.targetFiles);
1226
+ const expectedOutputTokens = baseTokens;
1227
+ const totalEstimatedTokens = contextTokens + promptTokens + expectedOutputTokens;
1228
+ return {
1229
+ taskId: params.taskId,
1230
+ providerId: this.id,
1231
+ contextTokens,
1232
+ promptTokens,
1233
+ expectedOutputTokens,
1234
+ totalEstimatedTokens,
1235
+ confidence: 0.5,
1236
+ feasible: totalEstimatedTokens < 2e5
1237
+ };
1238
+ }
1239
+ async abort(executionId) {
1240
+ const running = this.runningExecutions.get(executionId);
1241
+ if (!running) return;
1242
+ running.kill("SIGTERM");
1243
+ const forceKillTimer = setTimeout(() => {
1244
+ running.kill("SIGKILL");
1245
+ }, 3e3);
1246
+ forceKillTimer.unref();
1247
+ try {
1248
+ await running;
1249
+ } catch {
1250
+ } finally {
1251
+ clearTimeout(forceKillTimer);
1252
+ }
1253
+ }
1254
+ };
1255
+
1256
+ // src/execution/agents/registry.ts
1257
+ var AdapterRegistry = class {
1258
+ factories = /* @__PURE__ */ new Map();
1259
+ /** Well-known aliases (e.g. legacy IDs) that map to canonical provider IDs. */
1260
+ aliases = /* @__PURE__ */ new Map([
1261
+ ["codex-cli", "codex"]
1262
+ ]);
1263
+ /** Register a new adapter factory. Replaces any previous factory for the same ID. */
1264
+ register(id, factory) {
1265
+ this.factories.set(id, factory);
1266
+ }
1267
+ /** Add an alias that maps to an existing canonical ID. */
1268
+ alias(alias, canonicalId) {
1269
+ this.aliases.set(alias, canonicalId);
1270
+ }
1271
+ /** Resolve an ID (including aliases) and return the factory, or `undefined`. */
1272
+ get(rawId) {
1273
+ const canonicalId = this.aliases.get(rawId) ?? rawId;
1274
+ return this.factories.get(canonicalId);
1275
+ }
1276
+ /** Canonical ID after alias resolution. */
1277
+ resolveId(rawId) {
1278
+ return this.aliases.get(rawId) ?? rawId;
1279
+ }
1280
+ /** All registered canonical provider IDs. */
1281
+ registeredIds() {
1282
+ return [...this.factories.keys()];
1283
+ }
1284
+ };
1285
+ var adapterRegistry = new AdapterRegistry();
1286
+ adapterRegistry.register("claude-code", () => new ClaudeCodeAdapter());
1287
+ adapterRegistry.register("codex", () => new CodexAdapter());
1288
+ adapterRegistry.register("opencode", () => new OpenCodeAdapter());
1289
+
977
1290
  // src/execution/sandbox.ts
978
1291
  import { mkdir } from "fs/promises";
979
1292
  import { join, resolve } from "path";
@@ -993,7 +1306,14 @@ function withWorktreeLock(fn) {
993
1306
  function getWorktreePath(repoPath, branchName) {
994
1307
  return resolve(join(repoPath, "..", ".oac-worktrees", branchName));
995
1308
  }
1309
+ var SAFE_BRANCH_RE = /^[a-zA-Z0-9/_.-]+$/;
996
1310
  async function createSandbox(repoPath, branchName, baseBranch) {
1311
+ if (!SAFE_BRANCH_RE.test(branchName)) {
1312
+ throw executionError("AGENT_EXECUTION_FAILED", `Invalid branch name: ${branchName}`);
1313
+ }
1314
+ if (!SAFE_BRANCH_RE.test(baseBranch)) {
1315
+ throw executionError("AGENT_EXECUTION_FAILED", `Invalid base branch name: ${baseBranch}`);
1316
+ }
997
1317
  const worktreePath = getWorktreePath(repoPath, branchName);
998
1318
  const worktreeRoot = resolve(join(repoPath, "..", ".oac-worktrees"));
999
1319
  const git = simpleGit(repoPath);
@@ -1026,6 +1346,68 @@ async function createSandbox(repoPath, branchName, baseBranch) {
1026
1346
 
1027
1347
  // src/execution/worker.ts
1028
1348
  import { randomUUID } from "crypto";
1349
+
1350
+ // src/execution/normalize-error.ts
1351
+ function toErrorMessage(error) {
1352
+ if (error instanceof Error) {
1353
+ return error.message;
1354
+ }
1355
+ return String(error);
1356
+ }
1357
+ function normalizeExecutionError(error, context) {
1358
+ if (error instanceof OacError) {
1359
+ return error;
1360
+ }
1361
+ const message = toErrorMessage(error);
1362
+ const { jobId, taskId, attempt, executionId } = context;
1363
+ const ctx = { taskId };
1364
+ if (jobId) ctx.jobId = jobId;
1365
+ if (executionId) ctx.executionId = executionId;
1366
+ if (attempt !== void 0) ctx.attempt = attempt;
1367
+ ctx.message = message;
1368
+ if (/timed out|timeout/i.test(message)) {
1369
+ return executionError("AGENT_TIMEOUT", `Task ${taskId} timed out during execution.`, {
1370
+ context: ctx,
1371
+ cause: error
1372
+ });
1373
+ }
1374
+ if (/out of memory|ENOMEM|heap/i.test(message)) {
1375
+ return executionError("AGENT_OOM", `Task ${taskId} ran out of memory.`, {
1376
+ context: ctx,
1377
+ cause: error
1378
+ });
1379
+ }
1380
+ if (/network|ECONN|ENOTFOUND|EAI_AGAIN/i.test(message)) {
1381
+ return new OacError(
1382
+ `Task ${taskId} failed due to a network error.`,
1383
+ "NETWORK_ERROR",
1384
+ "recoverable",
1385
+ ctx,
1386
+ error
1387
+ );
1388
+ }
1389
+ if (/index\.lock|cannot lock ref|Unable to create '.+?\.git\/index\.lock'/i.test(message)) {
1390
+ return new OacError(
1391
+ `Task ${taskId} failed due to a git lock conflict.`,
1392
+ "GIT_LOCK_FAILED",
1393
+ "recoverable",
1394
+ ctx,
1395
+ error
1396
+ );
1397
+ }
1398
+ if (isRecord(error) && error.name === "AbortError") {
1399
+ return executionError("AGENT_EXECUTION_FAILED", `Task ${taskId} was aborted.`, {
1400
+ context: ctx,
1401
+ cause: error
1402
+ });
1403
+ }
1404
+ return executionError("AGENT_EXECUTION_FAILED", `Task ${taskId} failed during execution.`, {
1405
+ context: ctx,
1406
+ cause: error
1407
+ });
1408
+ }
1409
+
1410
+ // src/execution/worker.ts
1029
1411
  var DEFAULT_TOKEN_BUDGET = 5e4;
1030
1412
  var DEFAULT_TIMEOUT_MS = 3e5;
1031
1413
  function readPositiveNumber(value) {
@@ -1097,30 +1479,6 @@ function mergeExecutionResult(result, observedTokens, observedFiles, startedAt)
1097
1479
  error: result.error
1098
1480
  };
1099
1481
  }
1100
- function normalizeExecutionError(error, task, executionId) {
1101
- if (error instanceof OacError) {
1102
- return error;
1103
- }
1104
- const message = error instanceof Error ? error.message : String(error);
1105
- if (/timed out|timeout/i.test(message)) {
1106
- return executionError("AGENT_TIMEOUT", `Task ${task.id} timed out during execution.`, {
1107
- context: {
1108
- taskId: task.id,
1109
- executionId,
1110
- message
1111
- },
1112
- cause: error
1113
- });
1114
- }
1115
- return executionError("AGENT_EXECUTION_FAILED", `Task ${task.id} failed during execution.`, {
1116
- context: {
1117
- taskId: task.id,
1118
- executionId,
1119
- message
1120
- },
1121
- cause: error
1122
- });
1123
- }
1124
1482
  async function executeTask(agent, task, sandbox, eventBus, options = {}) {
1125
1483
  const executionId = options.executionId ?? randomUUID();
1126
1484
  const tokenBudget = options.tokenBudget ?? readMetadataNumber(task, "tokenBudget") ?? DEFAULT_TOKEN_BUDGET;
@@ -1162,7 +1520,7 @@ async function executeTask(agent, task, sandbox, eventBus, options = {}) {
1162
1520
  await streamPromise;
1163
1521
  } catch {
1164
1522
  }
1165
- throw normalizeExecutionError(error, task, executionId);
1523
+ throw normalizeExecutionError(error, { taskId: task.id, executionId });
1166
1524
  }
1167
1525
  }
1168
1526
  function buildEpicPrompt(epic) {
@@ -1225,15 +1583,6 @@ var DEFAULT_CONCURRENCY = 2;
1225
1583
  var DEFAULT_MAX_ATTEMPTS = 2;
1226
1584
  var DEFAULT_TIMEOUT_MS2 = 3e5;
1227
1585
  var DEFAULT_TOKEN_BUDGET2 = 5e4;
1228
- function isRecord3(value) {
1229
- return typeof value === "object" && value !== null;
1230
- }
1231
- function toErrorMessage(error) {
1232
- if (error instanceof Error) {
1233
- return error.message;
1234
- }
1235
- return String(error);
1236
- }
1237
1586
  function sanitizeBranchSegment(value) {
1238
1587
  const sanitized = value.toLowerCase().replace(/[^a-z0-9/_-]+/g, "-").replace(/-+/g, "-").replace(/^[-/]+|[-/]+$/g, "");
1239
1588
  return sanitized || "task";
@@ -1452,78 +1801,10 @@ var ExecutionEngine = class {
1452
1801
  return `${this.branchPrefix}/${dateSegment}/${taskSegment}-${job.id.slice(0, 8)}-a${job.attempts}`;
1453
1802
  }
1454
1803
  normalizeError(error, job) {
1455
- if (error instanceof OacError) {
1456
- return error;
1457
- }
1458
- const message = toErrorMessage(error);
1459
- if (/timed out|timeout/i.test(message)) {
1460
- return executionError("AGENT_TIMEOUT", `Job ${job.id} timed out.`, {
1461
- context: {
1462
- jobId: job.id,
1463
- taskId: job.task.id,
1464
- message,
1465
- attempt: job.attempts
1466
- },
1467
- cause: error
1468
- });
1469
- }
1470
- if (/out of memory|ENOMEM|heap/i.test(message)) {
1471
- return executionError("AGENT_OOM", `Job ${job.id} ran out of memory.`, {
1472
- context: {
1473
- jobId: job.id,
1474
- taskId: job.task.id,
1475
- message,
1476
- attempt: job.attempts
1477
- },
1478
- cause: error
1479
- });
1480
- }
1481
- if (/network|ECONN|ENOTFOUND|EAI_AGAIN/i.test(message)) {
1482
- return new OacError(
1483
- `Job ${job.id} failed due to a network error.`,
1484
- "NETWORK_ERROR",
1485
- "recoverable",
1486
- {
1487
- jobId: job.id,
1488
- taskId: job.task.id,
1489
- message,
1490
- attempt: job.attempts
1491
- },
1492
- error
1493
- );
1494
- }
1495
- if (/index\.lock|cannot lock ref|Unable to create '.+?\.git\/index\.lock'/i.test(message)) {
1496
- return new OacError(
1497
- `Job ${job.id} failed due to a git lock conflict.`,
1498
- "GIT_LOCK_FAILED",
1499
- "recoverable",
1500
- {
1501
- jobId: job.id,
1502
- taskId: job.task.id,
1503
- message,
1504
- attempt: job.attempts
1505
- },
1506
- error
1507
- );
1508
- }
1509
- if (isRecord3(error) && error.name === "AbortError") {
1510
- return executionError("AGENT_EXECUTION_FAILED", `Job ${job.id} was aborted.`, {
1511
- context: {
1512
- jobId: job.id,
1513
- taskId: job.task.id,
1514
- attempt: job.attempts
1515
- },
1516
- cause: error
1517
- });
1518
- }
1519
- return executionError("AGENT_EXECUTION_FAILED", `Job ${job.id} failed unexpectedly.`, {
1520
- context: {
1521
- jobId: job.id,
1522
- taskId: job.task.id,
1523
- message,
1524
- attempt: job.attempts
1525
- },
1526
- cause: error
1804
+ return normalizeExecutionError(error, {
1805
+ jobId: job.id,
1806
+ taskId: job.task.id,
1807
+ attempt: job.attempts
1527
1808
  });
1528
1809
  }
1529
1810
  buildRunResult() {
@@ -1540,6 +1821,8 @@ var ExecutionEngine = class {
1540
1821
  export {
1541
1822
  ClaudeCodeAdapter,
1542
1823
  CodexAdapter,
1824
+ OpenCodeAdapter,
1825
+ adapterRegistry,
1543
1826
  createSandbox,
1544
1827
  executeTask,
1545
1828
  buildEpicPrompt,
@@ -1547,4 +1830,4 @@ export {
1547
1830
  isTransientError,
1548
1831
  ExecutionEngine
1549
1832
  };
1550
- //# sourceMappingURL=chunk-7FWC3Z4W.js.map
1833
+ //# sourceMappingURL=chunk-QPVNC7S4.js.map