@open330/oac-execution 2026.2.17

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 ADDED
@@ -0,0 +1,1479 @@
1
+ // src/agents/claude-code.adapter.ts
2
+ import { createInterface } from "readline";
3
+ import { OacError, executionError } from "@open330/oac-core";
4
+ import { execa } from "execa";
5
+ var AsyncEventQueue = class {
6
+ values = [];
7
+ resolvers = [];
8
+ done = false;
9
+ pendingError;
10
+ push(value) {
11
+ if (this.done) {
12
+ return;
13
+ }
14
+ const nextResolver = this.resolvers.shift();
15
+ if (nextResolver) {
16
+ nextResolver({ done: false, value });
17
+ return;
18
+ }
19
+ this.values.push(value);
20
+ }
21
+ close() {
22
+ if (this.done) {
23
+ return;
24
+ }
25
+ this.done = true;
26
+ this.flush();
27
+ }
28
+ fail(error) {
29
+ this.pendingError = error;
30
+ this.done = true;
31
+ this.flush();
32
+ }
33
+ [Symbol.asyncIterator]() {
34
+ return {
35
+ next: async () => {
36
+ if (this.values.length > 0) {
37
+ const value = this.values.shift();
38
+ if (value === void 0) {
39
+ return { done: true, value: void 0 };
40
+ }
41
+ return { done: false, value };
42
+ }
43
+ if (this.pendingError !== void 0) {
44
+ throw this.pendingError;
45
+ }
46
+ if (this.done) {
47
+ return { done: true, value: void 0 };
48
+ }
49
+ return new Promise((resolve2) => {
50
+ this.resolvers.push(resolve2);
51
+ });
52
+ }
53
+ };
54
+ }
55
+ flush() {
56
+ for (const resolve2 of this.resolvers.splice(0)) {
57
+ resolve2({ done: true, value: void 0 });
58
+ }
59
+ }
60
+ };
61
+ function isRecord(value) {
62
+ return typeof value === "object" && value !== null;
63
+ }
64
+ function readNumber(value) {
65
+ if (typeof value !== "number" || !Number.isFinite(value)) {
66
+ return void 0;
67
+ }
68
+ return Math.max(0, Math.floor(value));
69
+ }
70
+ function readString(value) {
71
+ if (typeof value !== "string") {
72
+ return void 0;
73
+ }
74
+ const trimmed = value.trim();
75
+ return trimmed.length > 0 ? trimmed : void 0;
76
+ }
77
+ function parseJsonPayload(line) {
78
+ const trimmed = line.trim();
79
+ if (trimmed.length === 0) {
80
+ return void 0;
81
+ }
82
+ const candidates = [trimmed];
83
+ const start = trimmed.indexOf("{");
84
+ const end = trimmed.lastIndexOf("}");
85
+ if (start >= 0 && end > start) {
86
+ const fragment = trimmed.slice(start, end + 1);
87
+ if (fragment !== trimmed) {
88
+ candidates.push(fragment);
89
+ }
90
+ }
91
+ for (const candidate of candidates) {
92
+ try {
93
+ const parsed = JSON.parse(candidate);
94
+ if (isRecord(parsed)) {
95
+ return parsed;
96
+ }
97
+ } catch {
98
+ }
99
+ }
100
+ return void 0;
101
+ }
102
+ function patchTokenState(state, patch) {
103
+ if (patch.inputTokens === void 0 && patch.outputTokens === void 0 && patch.cumulativeTokens === void 0) {
104
+ return void 0;
105
+ }
106
+ state.inputTokens = patch.inputTokens ?? state.inputTokens;
107
+ state.outputTokens = patch.outputTokens ?? state.outputTokens;
108
+ const computedTotal = state.inputTokens + state.outputTokens;
109
+ state.cumulativeTokens = Math.max(
110
+ state.cumulativeTokens,
111
+ patch.cumulativeTokens ?? computedTotal
112
+ );
113
+ return {
114
+ type: "tokens",
115
+ inputTokens: state.inputTokens,
116
+ outputTokens: state.outputTokens,
117
+ cumulativeTokens: state.cumulativeTokens
118
+ };
119
+ }
120
+ function parseTokenPatchFromPayload(payload) {
121
+ const usage = isRecord(payload.usage) ? payload.usage : void 0;
122
+ return {
123
+ inputTokens: readNumber(
124
+ payload.inputTokens ?? payload.input_tokens ?? payload.promptTokens ?? payload.prompt_tokens ?? usage?.inputTokens ?? usage?.input_tokens ?? usage?.promptTokens ?? usage?.prompt_tokens
125
+ ),
126
+ outputTokens: readNumber(
127
+ payload.outputTokens ?? payload.output_tokens ?? payload.completionTokens ?? payload.completion_tokens ?? usage?.outputTokens ?? usage?.output_tokens ?? usage?.completionTokens ?? usage?.completion_tokens
128
+ ),
129
+ cumulativeTokens: readNumber(
130
+ payload.cumulativeTokens ?? payload.cumulative_tokens ?? payload.totalTokens ?? payload.total_tokens ?? usage?.cumulativeTokens ?? usage?.cumulative_tokens ?? usage?.totalTokens ?? usage?.total_tokens
131
+ )
132
+ };
133
+ }
134
+ function parseTokenPatchFromLine(line) {
135
+ const inputMatch = line.match(/(?:input|prompt)\s*tokens?\s*[:=]\s*(\d+)/i);
136
+ const outputMatch = line.match(/(?:output|completion)\s*tokens?\s*[:=]\s*(\d+)/i);
137
+ const totalMatch = line.match(/(?:total|cumulative|used)\s*tokens?\s*[:=]\s*(\d+)/i);
138
+ return {
139
+ inputTokens: inputMatch ? Number.parseInt(inputMatch[1], 10) : void 0,
140
+ outputTokens: outputMatch ? Number.parseInt(outputMatch[1], 10) : void 0,
141
+ cumulativeTokens: totalMatch ? Number.parseInt(totalMatch[1], 10) : void 0
142
+ };
143
+ }
144
+ function parseTokenEvent(line, state) {
145
+ const payload = parseJsonPayload(line);
146
+ const patch = payload ? parseTokenPatchFromPayload(payload) : parseTokenPatchFromLine(line);
147
+ return patchTokenState(state, patch);
148
+ }
149
+ function normalizeFileAction(value) {
150
+ if (value !== "create" && value !== "modify" && value !== "delete") {
151
+ return void 0;
152
+ }
153
+ return value;
154
+ }
155
+ function parseFileEditFromPayload(payload) {
156
+ if (payload.type === "file_edit") {
157
+ const action = normalizeFileAction(payload.action);
158
+ const path = readString(payload.path);
159
+ if (action && path) {
160
+ return {
161
+ type: "file_edit",
162
+ action,
163
+ path
164
+ };
165
+ }
166
+ }
167
+ const tool = readString(payload.tool ?? payload.tool_name ?? payload.name);
168
+ const input = isRecord(payload.input) ? payload.input : void 0;
169
+ const inputPath = readString(input?.path ?? input?.file_path ?? input?.filePath);
170
+ if (!tool || !inputPath) {
171
+ return void 0;
172
+ }
173
+ if (tool === "create_file") {
174
+ return { type: "file_edit", action: "create", path: inputPath };
175
+ }
176
+ if (tool === "delete_file") {
177
+ return { type: "file_edit", action: "delete", path: inputPath };
178
+ }
179
+ if (tool === "write_file" || tool === "edit_file" || tool === "replace_file") {
180
+ return { type: "file_edit", action: "modify", path: inputPath };
181
+ }
182
+ return void 0;
183
+ }
184
+ function parseFileEditFromLine(line) {
185
+ const fileActionMatch = line.match(/\b(created|modified|deleted)\s+(?:file\s+)?([^\s"'`]+)/i);
186
+ if (!fileActionMatch) {
187
+ return void 0;
188
+ }
189
+ const actionMap = {
190
+ created: "create",
191
+ modified: "modify",
192
+ deleted: "delete"
193
+ };
194
+ const action = actionMap[fileActionMatch[1].toLowerCase()];
195
+ const path = fileActionMatch[2]?.trim();
196
+ if (!action || !path) {
197
+ return void 0;
198
+ }
199
+ return {
200
+ type: "file_edit",
201
+ action,
202
+ path
203
+ };
204
+ }
205
+ function parseFileEditEvent(line) {
206
+ const payload = parseJsonPayload(line);
207
+ return payload ? parseFileEditFromPayload(payload) : parseFileEditFromLine(line);
208
+ }
209
+ function parseToolUseEvent(line) {
210
+ const payload = parseJsonPayload(line);
211
+ if (!payload) {
212
+ return void 0;
213
+ }
214
+ const tool = readString(payload.tool ?? payload.tool_name ?? payload.name);
215
+ if (!tool) {
216
+ return void 0;
217
+ }
218
+ return {
219
+ type: "tool_use",
220
+ tool,
221
+ input: payload.input
222
+ };
223
+ }
224
+ function parseErrorEvent(line, stream) {
225
+ const payload = parseJsonPayload(line);
226
+ if (payload?.type === "error") {
227
+ const message = readString(payload.message) ?? "Unknown Claude CLI error";
228
+ const recoverable = payload.recoverable !== false;
229
+ return { type: "error", message, recoverable };
230
+ }
231
+ if (stream === "stderr" && /error|failed|exception/i.test(line)) {
232
+ return { type: "error", message: line.trim(), recoverable: true };
233
+ }
234
+ return void 0;
235
+ }
236
+ function estimateTokenCount(text) {
237
+ if (text.length === 0) {
238
+ return 0;
239
+ }
240
+ return Math.max(1, Math.ceil(text.length / 4));
241
+ }
242
+ function normalizeUnknownError(error, executionId) {
243
+ if (error instanceof OacError) {
244
+ return error;
245
+ }
246
+ const message = error instanceof Error ? error.message : String(error);
247
+ if (/timed out|timeout/i.test(message)) {
248
+ return executionError("AGENT_TIMEOUT", `Claude execution timed out for ${executionId}`, {
249
+ context: { executionId, message },
250
+ cause: error
251
+ });
252
+ }
253
+ if (/out of memory|ENOMEM|heap/i.test(message)) {
254
+ return executionError("AGENT_OOM", `Claude execution ran out of memory for ${executionId}`, {
255
+ context: { executionId, message },
256
+ cause: error
257
+ });
258
+ }
259
+ if (/network|ECONN|ENOTFOUND|EAI_AGAIN/i.test(message)) {
260
+ return new OacError(
261
+ "Claude execution failed due to network issues",
262
+ "NETWORK_ERROR",
263
+ "recoverable",
264
+ {
265
+ executionId,
266
+ message
267
+ },
268
+ error
269
+ );
270
+ }
271
+ return executionError("AGENT_EXECUTION_FAILED", `Claude execution failed for ${executionId}`, {
272
+ context: { executionId, message },
273
+ cause: error
274
+ });
275
+ }
276
+ function computeTotalTokens(state) {
277
+ return Math.max(state.cumulativeTokens, state.inputTokens + state.outputTokens);
278
+ }
279
+ function normalizeExitCode(value) {
280
+ if (typeof value === "number" && Number.isFinite(value)) {
281
+ return value;
282
+ }
283
+ return 1;
284
+ }
285
+ function hasBooleanFlag(value, key) {
286
+ if (!isRecord(value)) {
287
+ return false;
288
+ }
289
+ return value[key] === true;
290
+ }
291
+ function buildFailureMessage(stdout, stderr) {
292
+ const trimmedStderr = stderr.trim();
293
+ if (trimmedStderr.length > 0) {
294
+ return trimmedStderr;
295
+ }
296
+ const trimmedStdout = stdout.trim();
297
+ if (trimmedStdout.length > 0) {
298
+ return trimmedStdout;
299
+ }
300
+ return "Claude CLI process exited with a non-zero status.";
301
+ }
302
+ var ClaudeCodeAdapter = class {
303
+ id = "claude-code";
304
+ name = "Claude Code";
305
+ runningExecutions = /* @__PURE__ */ new Map();
306
+ async checkAvailability() {
307
+ try {
308
+ const result = await execa("claude", ["--version"], { reject: false });
309
+ const version = result.stdout.trim().split("\n")[0];
310
+ if (result.exitCode === 0) {
311
+ return {
312
+ available: true,
313
+ version: version.length > 0 ? version : void 0
314
+ };
315
+ }
316
+ return {
317
+ available: false,
318
+ error: result.stderr.trim() || `claude --version exited with code ${result.exitCode}`
319
+ };
320
+ } catch (error) {
321
+ const message = error instanceof Error ? error.message : String(error);
322
+ return {
323
+ available: false,
324
+ error: message
325
+ };
326
+ }
327
+ }
328
+ execute(params) {
329
+ const startedAt = Date.now();
330
+ const filesChanged = /* @__PURE__ */ new Set();
331
+ const tokenState = {
332
+ inputTokens: 0,
333
+ outputTokens: 0,
334
+ cumulativeTokens: 0
335
+ };
336
+ const eventQueue = new AsyncEventQueue();
337
+ const processEnv = {
338
+ ...Object.fromEntries(
339
+ Object.entries(process.env).filter(
340
+ (entry) => typeof entry[1] === "string"
341
+ )
342
+ ),
343
+ ...params.env,
344
+ OAC_TOKEN_BUDGET: `${params.tokenBudget}`,
345
+ OAC_ALLOW_COMMITS: `${params.allowCommits}`
346
+ };
347
+ const subprocess = execa("claude", ["-p", params.prompt], {
348
+ cwd: params.workingDirectory,
349
+ env: processEnv,
350
+ reject: false,
351
+ timeout: params.timeoutMs
352
+ });
353
+ this.runningExecutions.set(params.executionId, subprocess);
354
+ const consumeStream = async (stream, streamName) => {
355
+ if (!stream) {
356
+ return;
357
+ }
358
+ const lineReader = createInterface({
359
+ input: stream,
360
+ crlfDelay: Number.POSITIVE_INFINITY
361
+ });
362
+ for await (const line of lineReader) {
363
+ eventQueue.push({
364
+ type: "output",
365
+ content: line,
366
+ stream: streamName
367
+ });
368
+ const tokenEvent = parseTokenEvent(line, tokenState);
369
+ if (tokenEvent?.type === "tokens") {
370
+ eventQueue.push(tokenEvent);
371
+ }
372
+ const fileEvent = parseFileEditEvent(line);
373
+ if (fileEvent) {
374
+ filesChanged.add(fileEvent.path);
375
+ eventQueue.push(fileEvent);
376
+ }
377
+ const toolEvent = parseToolUseEvent(line);
378
+ if (toolEvent) {
379
+ eventQueue.push(toolEvent);
380
+ }
381
+ const errorEvent = parseErrorEvent(line, streamName);
382
+ if (errorEvent) {
383
+ eventQueue.push(errorEvent);
384
+ }
385
+ }
386
+ };
387
+ const stdoutDone = consumeStream(subprocess.stdout ?? void 0, "stdout");
388
+ const stderrDone = consumeStream(subprocess.stderr ?? void 0, "stderr");
389
+ const resultPromise = (async () => {
390
+ try {
391
+ const settled = await subprocess;
392
+ await Promise.all([stdoutDone, stderrDone]);
393
+ const timedOut = hasBooleanFlag(settled, "timedOut");
394
+ if (timedOut) {
395
+ const timeoutError = executionError(
396
+ "AGENT_TIMEOUT",
397
+ `Claude execution timed out for ${params.executionId}`,
398
+ {
399
+ context: {
400
+ executionId: params.executionId,
401
+ timeoutMs: params.timeoutMs
402
+ }
403
+ }
404
+ );
405
+ eventQueue.push({
406
+ type: "error",
407
+ message: timeoutError.message,
408
+ recoverable: true
409
+ });
410
+ throw timeoutError;
411
+ }
412
+ const canceled = hasBooleanFlag(settled, "isCanceled");
413
+ if (canceled) {
414
+ return {
415
+ success: false,
416
+ exitCode: normalizeExitCode(settled.exitCode),
417
+ totalTokensUsed: computeTotalTokens(tokenState),
418
+ filesChanged: [...filesChanged],
419
+ duration: Date.now() - startedAt,
420
+ error: "Claude execution was cancelled."
421
+ };
422
+ }
423
+ const exitCode = normalizeExitCode(settled.exitCode);
424
+ const success = exitCode === 0;
425
+ return {
426
+ success,
427
+ exitCode,
428
+ totalTokensUsed: computeTotalTokens(tokenState),
429
+ filesChanged: [...filesChanged],
430
+ duration: Date.now() - startedAt,
431
+ error: success ? void 0 : buildFailureMessage(settled.stdout, settled.stderr)
432
+ };
433
+ } catch (error) {
434
+ const normalized = normalizeUnknownError(error, params.executionId);
435
+ eventQueue.push({
436
+ type: "error",
437
+ message: normalized.message,
438
+ recoverable: normalized.severity !== "fatal"
439
+ });
440
+ eventQueue.fail(normalized);
441
+ throw normalized;
442
+ } finally {
443
+ this.runningExecutions.delete(params.executionId);
444
+ eventQueue.close();
445
+ }
446
+ })();
447
+ return {
448
+ executionId: params.executionId,
449
+ providerId: this.id,
450
+ events: eventQueue,
451
+ result: resultPromise,
452
+ pid: subprocess.pid
453
+ };
454
+ }
455
+ async estimateTokens(params) {
456
+ const contextTokens = params.contextTokens ?? params.targetFiles.length * 80 + params.targetFiles.join("\n").length;
457
+ const promptTokens = estimateTokenCount(params.prompt);
458
+ const expectedOutputTokens = params.expectedOutputTokens ?? Math.max(128, Math.ceil(promptTokens * 0.6));
459
+ const totalEstimatedTokens = contextTokens + promptTokens + expectedOutputTokens;
460
+ return {
461
+ taskId: params.taskId,
462
+ providerId: this.id,
463
+ contextTokens,
464
+ promptTokens,
465
+ expectedOutputTokens,
466
+ totalEstimatedTokens,
467
+ confidence: 0.6,
468
+ feasible: true
469
+ };
470
+ }
471
+ async abort(executionId) {
472
+ const running = this.runningExecutions.get(executionId);
473
+ if (!running) {
474
+ return;
475
+ }
476
+ running.kill("SIGTERM");
477
+ const forceKillTimer = setTimeout(() => {
478
+ running.kill("SIGKILL");
479
+ }, 5e3);
480
+ forceKillTimer.unref();
481
+ try {
482
+ await running;
483
+ } catch {
484
+ } finally {
485
+ clearTimeout(forceKillTimer);
486
+ }
487
+ }
488
+ };
489
+
490
+ // src/agents/codex.adapter.ts
491
+ import { stat } from "fs/promises";
492
+ import { createInterface as createInterface2 } from "readline";
493
+ import { OacError as OacError2, executionError as executionError2 } from "@open330/oac-core";
494
+ import { execa as execa2 } from "execa";
495
+ var AsyncEventQueue2 = class {
496
+ values = [];
497
+ resolvers = [];
498
+ done = false;
499
+ pendingError;
500
+ push(value) {
501
+ if (this.done) {
502
+ return;
503
+ }
504
+ const nextResolver = this.resolvers.shift();
505
+ if (nextResolver) {
506
+ nextResolver({ done: false, value });
507
+ return;
508
+ }
509
+ this.values.push(value);
510
+ }
511
+ close() {
512
+ if (this.done) {
513
+ return;
514
+ }
515
+ this.done = true;
516
+ this.flush();
517
+ }
518
+ fail(error) {
519
+ this.pendingError = error;
520
+ this.done = true;
521
+ this.flush();
522
+ }
523
+ [Symbol.asyncIterator]() {
524
+ return {
525
+ next: async () => {
526
+ if (this.values.length > 0) {
527
+ const value = this.values.shift();
528
+ if (value === void 0) {
529
+ return { done: true, value: void 0 };
530
+ }
531
+ return { done: false, value };
532
+ }
533
+ if (this.pendingError !== void 0) {
534
+ throw this.pendingError;
535
+ }
536
+ if (this.done) {
537
+ return { done: true, value: void 0 };
538
+ }
539
+ return new Promise((resolve2) => {
540
+ this.resolvers.push(resolve2);
541
+ });
542
+ }
543
+ };
544
+ }
545
+ flush() {
546
+ for (const resolve2 of this.resolvers.splice(0)) {
547
+ resolve2({ done: true, value: void 0 });
548
+ }
549
+ }
550
+ };
551
+ function isRecord2(value) {
552
+ return typeof value === "object" && value !== null;
553
+ }
554
+ function readString2(value) {
555
+ if (typeof value !== "string") {
556
+ return void 0;
557
+ }
558
+ const trimmed = value.trim();
559
+ return trimmed.length > 0 ? trimmed : void 0;
560
+ }
561
+ function readNumber2(value) {
562
+ if (typeof value !== "number" || !Number.isFinite(value)) {
563
+ return void 0;
564
+ }
565
+ return Math.max(0, Math.floor(value));
566
+ }
567
+ function parseJsonPayload2(line) {
568
+ const trimmed = line.trim();
569
+ if (trimmed.length === 0) {
570
+ return void 0;
571
+ }
572
+ try {
573
+ const parsed = JSON.parse(trimmed);
574
+ if (isRecord2(parsed)) {
575
+ return parsed;
576
+ }
577
+ } catch {
578
+ }
579
+ return void 0;
580
+ }
581
+ function parseTokenPatchFromPayload2(payload) {
582
+ const usage = isRecord2(payload.usage) ? payload.usage : void 0;
583
+ return {
584
+ inputTokens: readNumber2(
585
+ payload.inputTokens ?? payload.input_tokens ?? payload.promptTokens ?? payload.prompt_tokens ?? usage?.inputTokens ?? usage?.input_tokens ?? usage?.promptTokens ?? usage?.prompt_tokens
586
+ ),
587
+ outputTokens: readNumber2(
588
+ payload.outputTokens ?? payload.output_tokens ?? payload.completionTokens ?? payload.completion_tokens ?? usage?.outputTokens ?? usage?.output_tokens ?? usage?.completionTokens ?? usage?.completion_tokens
589
+ ),
590
+ cumulativeTokens: readNumber2(
591
+ payload.cumulativeTokens ?? payload.cumulative_tokens ?? payload.totalTokens ?? payload.total_tokens ?? usage?.cumulativeTokens ?? usage?.cumulative_tokens ?? usage?.totalTokens ?? usage?.total_tokens
592
+ )
593
+ };
594
+ }
595
+ function patchTokenState2(state, patch) {
596
+ if (patch.inputTokens === void 0 && patch.outputTokens === void 0 && patch.cumulativeTokens === void 0) {
597
+ return void 0;
598
+ }
599
+ state.inputTokens = patch.inputTokens ?? state.inputTokens;
600
+ state.outputTokens = patch.outputTokens ?? state.outputTokens;
601
+ const computedTotal = state.inputTokens + state.outputTokens;
602
+ state.cumulativeTokens = Math.max(
603
+ state.cumulativeTokens,
604
+ patch.cumulativeTokens ?? computedTotal
605
+ );
606
+ return {
607
+ type: "tokens",
608
+ inputTokens: state.inputTokens,
609
+ outputTokens: state.outputTokens,
610
+ cumulativeTokens: state.cumulativeTokens
611
+ };
612
+ }
613
+ function parseTokenEvent2(line, payload, state) {
614
+ if (payload) {
615
+ return patchTokenState2(state, parseTokenPatchFromPayload2(payload));
616
+ }
617
+ const inputMatch = line.match(/(?:input|prompt)\s*tokens?\s*[:=]\s*(\d+)/i);
618
+ const outputMatch = line.match(/(?:output|completion)\s*tokens?\s*[:=]\s*(\d+)/i);
619
+ const totalMatch = line.match(/(?:total|cumulative|used)\s*tokens?\s*[:=]\s*(\d+)/i);
620
+ return patchTokenState2(state, {
621
+ inputTokens: inputMatch ? Number.parseInt(inputMatch[1], 10) : void 0,
622
+ outputTokens: outputMatch ? Number.parseInt(outputMatch[1], 10) : void 0,
623
+ cumulativeTokens: totalMatch ? Number.parseInt(totalMatch[1], 10) : void 0
624
+ });
625
+ }
626
+ function normalizeFileAction2(value) {
627
+ if (value !== "create" && value !== "modify" && value !== "delete") {
628
+ return void 0;
629
+ }
630
+ return value;
631
+ }
632
+ function parseFileEditFromPayload2(payload) {
633
+ if (payload.type === "file_edit") {
634
+ const action = normalizeFileAction2(payload.action);
635
+ const path = readString2(payload.path);
636
+ if (action && path) {
637
+ return {
638
+ type: "file_edit",
639
+ action,
640
+ path
641
+ };
642
+ }
643
+ }
644
+ const tool = readString2(payload.tool ?? payload.tool_name ?? payload.name);
645
+ const input = isRecord2(payload.input) ? payload.input : void 0;
646
+ const inputPath = readString2(input?.path ?? input?.file_path ?? input?.filePath);
647
+ if (!tool || !inputPath) {
648
+ return void 0;
649
+ }
650
+ if (tool === "create_file") {
651
+ return { type: "file_edit", action: "create", path: inputPath };
652
+ }
653
+ if (tool === "delete_file") {
654
+ return { type: "file_edit", action: "delete", path: inputPath };
655
+ }
656
+ if (tool === "write_file" || tool === "edit_file" || tool === "replace_file") {
657
+ return { type: "file_edit", action: "modify", path: inputPath };
658
+ }
659
+ return void 0;
660
+ }
661
+ function parseToolUseFromPayload(payload) {
662
+ const tool = readString2(payload.tool ?? payload.tool_name ?? payload.name);
663
+ if (!tool) {
664
+ return void 0;
665
+ }
666
+ return {
667
+ type: "tool_use",
668
+ tool,
669
+ input: payload.input
670
+ };
671
+ }
672
+ function parseErrorFromPayload(payload) {
673
+ if (payload.type !== "error") {
674
+ return void 0;
675
+ }
676
+ return {
677
+ type: "error",
678
+ message: readString2(payload.message) ?? "Unknown Codex CLI error",
679
+ recoverable: payload.recoverable !== false
680
+ };
681
+ }
682
+ function estimateTokenCount2(text) {
683
+ if (text.length === 0) {
684
+ return 0;
685
+ }
686
+ return Math.max(1, Math.ceil(text.length / 4));
687
+ }
688
+ function normalizeUnknownError2(error, executionId) {
689
+ if (error instanceof OacError2) {
690
+ return error;
691
+ }
692
+ const message = error instanceof Error ? error.message : String(error);
693
+ if (/timed out|timeout/i.test(message)) {
694
+ return executionError2("AGENT_TIMEOUT", `Codex execution timed out for ${executionId}`, {
695
+ context: { executionId, message },
696
+ cause: error
697
+ });
698
+ }
699
+ if (/out of memory|ENOMEM|heap/i.test(message)) {
700
+ return executionError2("AGENT_OOM", `Codex execution ran out of memory for ${executionId}`, {
701
+ context: { executionId, message },
702
+ cause: error
703
+ });
704
+ }
705
+ if (/rate.limit|429|too many requests|throttl/i.test(message)) {
706
+ return executionError2(
707
+ "AGENT_RATE_LIMITED",
708
+ `Codex execution rate-limited for ${executionId}`,
709
+ {
710
+ context: { executionId, message },
711
+ cause: error
712
+ }
713
+ );
714
+ }
715
+ if (/network|ECONN|ENOTFOUND|EAI_AGAIN/i.test(message)) {
716
+ return new OacError2(
717
+ "Codex execution failed due to network issues",
718
+ "NETWORK_ERROR",
719
+ "recoverable",
720
+ {
721
+ executionId,
722
+ message
723
+ },
724
+ error
725
+ );
726
+ }
727
+ return executionError2("AGENT_EXECUTION_FAILED", `Codex execution failed for ${executionId}`, {
728
+ context: { executionId, message },
729
+ cause: error
730
+ });
731
+ }
732
+ function computeTotalTokens2(state) {
733
+ return Math.max(state.cumulativeTokens, state.inputTokens + state.outputTokens);
734
+ }
735
+ function normalizeExitCode2(value) {
736
+ if (typeof value === "number" && Number.isFinite(value)) {
737
+ return value;
738
+ }
739
+ return 1;
740
+ }
741
+ function hasBooleanFlag2(value, key) {
742
+ if (!isRecord2(value)) {
743
+ return false;
744
+ }
745
+ return value[key] === true;
746
+ }
747
+ function buildFailureMessage2(stdout, stderr) {
748
+ const trimmedStderr = stderr.trim();
749
+ if (trimmedStderr.length > 0) {
750
+ return trimmedStderr;
751
+ }
752
+ const trimmedStdout = stdout.trim();
753
+ if (trimmedStdout.length > 0) {
754
+ return trimmedStdout;
755
+ }
756
+ return "Codex CLI process exited with a non-zero status.";
757
+ }
758
+ function parseVersion(output) {
759
+ const match = output.match(/(\d+\.\d+\.\d+)/);
760
+ if (!match) {
761
+ return void 0;
762
+ }
763
+ return match[1];
764
+ }
765
+ async function estimateContextTokens(targetFiles) {
766
+ let totalBytes = 0;
767
+ for (const filePath of targetFiles) {
768
+ try {
769
+ const fileStat = await stat(filePath);
770
+ if (fileStat.isFile()) {
771
+ totalBytes += fileStat.size;
772
+ }
773
+ } catch {
774
+ }
775
+ }
776
+ return Math.ceil(totalBytes / 4);
777
+ }
778
+ var CodexAdapter = class {
779
+ id = "codex";
780
+ name = "Codex CLI";
781
+ runningExecutions = /* @__PURE__ */ new Map();
782
+ async checkAvailability() {
783
+ try {
784
+ const result = await execa2("codex", ["--version"], { reject: false });
785
+ if (result.exitCode === 0) {
786
+ const versionLine = result.stdout.trim().split("\n")[0] ?? "";
787
+ return {
788
+ available: true,
789
+ version: parseVersion(versionLine)
790
+ };
791
+ }
792
+ return {
793
+ available: false,
794
+ error: result.stderr.trim() || result.stdout.trim() || `codex --version exited with code ${result.exitCode}`
795
+ };
796
+ } catch (error) {
797
+ const message = error instanceof Error ? error.message : String(error);
798
+ return {
799
+ available: false,
800
+ error: message
801
+ };
802
+ }
803
+ }
804
+ execute(params) {
805
+ const startedAt = Date.now();
806
+ const filesChanged = /* @__PURE__ */ new Set();
807
+ const tokenState = {
808
+ inputTokens: 0,
809
+ outputTokens: 0,
810
+ cumulativeTokens: 0
811
+ };
812
+ const eventQueue = new AsyncEventQueue2();
813
+ const processEnv = {
814
+ ...Object.fromEntries(
815
+ Object.entries(process.env).filter(
816
+ (entry) => typeof entry[1] === "string"
817
+ )
818
+ ),
819
+ ...params.env,
820
+ OAC_TOKEN_BUDGET: `${params.tokenBudget}`,
821
+ OAC_ALLOW_COMMITS: `${params.allowCommits}`
822
+ };
823
+ const subprocess = execa2(
824
+ "codex",
825
+ ["exec", "--full-auto", "-C", params.workingDirectory, params.prompt],
826
+ {
827
+ cwd: params.workingDirectory,
828
+ env: processEnv,
829
+ reject: false,
830
+ timeout: params.timeoutMs
831
+ }
832
+ );
833
+ this.runningExecutions.set(params.executionId, subprocess);
834
+ const consumeStream = async (stream, streamName) => {
835
+ if (!stream) {
836
+ return;
837
+ }
838
+ const lineReader = createInterface2({
839
+ input: stream,
840
+ crlfDelay: Number.POSITIVE_INFINITY
841
+ });
842
+ for await (const line of lineReader) {
843
+ eventQueue.push({
844
+ type: "output",
845
+ content: line,
846
+ stream: streamName
847
+ });
848
+ if (streamName === "stdout") {
849
+ const payload = parseJsonPayload2(line);
850
+ const tokenEvent = parseTokenEvent2(line, payload, tokenState);
851
+ if (tokenEvent?.type === "tokens") {
852
+ eventQueue.push(tokenEvent);
853
+ }
854
+ if (payload) {
855
+ const fileEvent = parseFileEditFromPayload2(payload);
856
+ if (fileEvent) {
857
+ filesChanged.add(fileEvent.path);
858
+ eventQueue.push(fileEvent);
859
+ }
860
+ const toolEvent = parseToolUseFromPayload(payload);
861
+ if (toolEvent) {
862
+ eventQueue.push(toolEvent);
863
+ }
864
+ const errorEvent = parseErrorFromPayload(payload);
865
+ if (errorEvent) {
866
+ eventQueue.push(errorEvent);
867
+ }
868
+ }
869
+ } else if (/error|failed|exception/i.test(line)) {
870
+ eventQueue.push({
871
+ type: "error",
872
+ message: line.trim(),
873
+ recoverable: true
874
+ });
875
+ }
876
+ }
877
+ };
878
+ const stdoutDone = consumeStream(subprocess.stdout ?? void 0, "stdout");
879
+ const stderrDone = consumeStream(subprocess.stderr ?? void 0, "stderr");
880
+ const resultPromise = (async () => {
881
+ try {
882
+ const settled = await subprocess;
883
+ await Promise.all([stdoutDone, stderrDone]);
884
+ const timedOut = hasBooleanFlag2(settled, "timedOut");
885
+ if (timedOut) {
886
+ const timeoutError = executionError2(
887
+ "AGENT_TIMEOUT",
888
+ `Codex execution timed out for ${params.executionId}`,
889
+ {
890
+ context: {
891
+ executionId: params.executionId,
892
+ timeoutMs: params.timeoutMs
893
+ }
894
+ }
895
+ );
896
+ eventQueue.push({
897
+ type: "error",
898
+ message: timeoutError.message,
899
+ recoverable: true
900
+ });
901
+ throw timeoutError;
902
+ }
903
+ const canceled = hasBooleanFlag2(settled, "isCanceled");
904
+ if (canceled) {
905
+ return {
906
+ success: false,
907
+ exitCode: normalizeExitCode2(settled.exitCode),
908
+ totalTokensUsed: computeTotalTokens2(tokenState),
909
+ filesChanged: [...filesChanged],
910
+ duration: Date.now() - startedAt,
911
+ error: "Codex execution was cancelled."
912
+ };
913
+ }
914
+ const exitCode = normalizeExitCode2(settled.exitCode);
915
+ const success = exitCode === 0;
916
+ return {
917
+ success,
918
+ exitCode,
919
+ totalTokensUsed: computeTotalTokens2(tokenState),
920
+ filesChanged: [...filesChanged],
921
+ duration: Date.now() - startedAt,
922
+ error: success ? void 0 : buildFailureMessage2(settled.stdout, settled.stderr)
923
+ };
924
+ } catch (error) {
925
+ const normalized = normalizeUnknownError2(error, params.executionId);
926
+ eventQueue.push({
927
+ type: "error",
928
+ message: normalized.message,
929
+ recoverable: normalized.severity !== "fatal"
930
+ });
931
+ eventQueue.fail(normalized);
932
+ throw normalized;
933
+ } finally {
934
+ this.runningExecutions.delete(params.executionId);
935
+ eventQueue.close();
936
+ }
937
+ })();
938
+ return {
939
+ executionId: params.executionId,
940
+ providerId: this.id,
941
+ events: eventQueue,
942
+ result: resultPromise,
943
+ pid: subprocess.pid
944
+ };
945
+ }
946
+ async estimateTokens(params) {
947
+ const baseTokens = params.targetFiles.length * 2e3;
948
+ const promptTokens = estimateTokenCount2(params.prompt);
949
+ const contextTokens = await estimateContextTokens(params.targetFiles);
950
+ const expectedOutputTokens = baseTokens;
951
+ const totalEstimatedTokens = contextTokens + promptTokens + expectedOutputTokens;
952
+ return {
953
+ taskId: params.taskId,
954
+ providerId: this.id,
955
+ contextTokens,
956
+ promptTokens,
957
+ expectedOutputTokens,
958
+ totalEstimatedTokens,
959
+ confidence: 0.6,
960
+ feasible: totalEstimatedTokens < 2e5
961
+ };
962
+ }
963
+ async abort(executionId) {
964
+ const running = this.runningExecutions.get(executionId);
965
+ if (!running) {
966
+ return;
967
+ }
968
+ running.kill("SIGTERM");
969
+ const forceKillTimer = setTimeout(() => {
970
+ running.kill("SIGKILL");
971
+ }, 2e3);
972
+ forceKillTimer.unref();
973
+ try {
974
+ await running;
975
+ } catch {
976
+ } finally {
977
+ clearTimeout(forceKillTimer);
978
+ }
979
+ }
980
+ };
981
+
982
+ // src/sandbox.ts
983
+ import { mkdir } from "fs/promises";
984
+ import { join, resolve } from "path";
985
+ import { simpleGit } from "simple-git";
986
+ function getWorktreePath(repoPath, branchName) {
987
+ return resolve(join(repoPath, "..", ".oac-worktrees", branchName));
988
+ }
989
+ async function createSandbox(repoPath, branchName, baseBranch) {
990
+ const worktreePath = getWorktreePath(repoPath, branchName);
991
+ const worktreeRoot = resolve(join(repoPath, "..", ".oac-worktrees"));
992
+ const git = simpleGit(repoPath);
993
+ await mkdir(worktreeRoot, { recursive: true });
994
+ await git.raw(["worktree", "add", worktreePath, "-b", branchName, `origin/${baseBranch}`]);
995
+ let cleanedUp = false;
996
+ return {
997
+ path: worktreePath,
998
+ branchName,
999
+ cleanup: async () => {
1000
+ if (cleanedUp) {
1001
+ return;
1002
+ }
1003
+ cleanedUp = true;
1004
+ try {
1005
+ await git.raw(["worktree", "remove", worktreePath, "--force"]);
1006
+ } finally {
1007
+ try {
1008
+ await git.raw(["worktree", "prune"]);
1009
+ } catch {
1010
+ }
1011
+ }
1012
+ }
1013
+ };
1014
+ }
1015
+
1016
+ // src/worker.ts
1017
+ import { randomUUID } from "crypto";
1018
+ import {
1019
+ OacError as OacError3,
1020
+ executionError as executionError3
1021
+ } from "@open330/oac-core";
1022
+ var DEFAULT_TOKEN_BUDGET = 5e4;
1023
+ var DEFAULT_TIMEOUT_MS = 3e5;
1024
+ function readPositiveNumber(value) {
1025
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
1026
+ return void 0;
1027
+ }
1028
+ return Math.floor(value);
1029
+ }
1030
+ function readMetadataNumber(task, key) {
1031
+ return readPositiveNumber(task.metadata[key]);
1032
+ }
1033
+ function buildTaskPrompt(task) {
1034
+ const fileList = task.targetFiles.length > 0 ? task.targetFiles.join("\n") : "(none provided)";
1035
+ return [
1036
+ "You are implementing a scoped repository contribution task.",
1037
+ `Task ID: ${task.id}`,
1038
+ `Title: ${task.title}`,
1039
+ `Source: ${task.source}`,
1040
+ `Priority: ${task.priority}`,
1041
+ `Complexity: ${task.complexity}`,
1042
+ `Execution mode: ${task.executionMode}`,
1043
+ "Description:",
1044
+ task.description,
1045
+ "Target files:",
1046
+ fileList,
1047
+ "Apply minimal, safe changes and ensure the repository remains buildable."
1048
+ ].join("\n");
1049
+ }
1050
+ function stageFromEvent(event) {
1051
+ switch (event.type) {
1052
+ case "output":
1053
+ return event.stream;
1054
+ case "tokens":
1055
+ return "tokens";
1056
+ case "file_edit":
1057
+ return `file:${event.action}`;
1058
+ case "tool_use":
1059
+ return `tool:${event.tool}`;
1060
+ case "error":
1061
+ return event.recoverable ? "agent-warning" : "agent-error";
1062
+ default:
1063
+ return "running";
1064
+ }
1065
+ }
1066
+ function mergeExecutionResult(result, observedTokens, observedFiles, startedAt) {
1067
+ for (const changedFile of result.filesChanged) {
1068
+ observedFiles.add(changedFile);
1069
+ }
1070
+ return {
1071
+ success: result.success,
1072
+ exitCode: result.exitCode,
1073
+ totalTokensUsed: Math.max(result.totalTokensUsed, observedTokens),
1074
+ filesChanged: [...observedFiles],
1075
+ duration: result.duration > 0 ? result.duration : Date.now() - startedAt,
1076
+ error: result.error
1077
+ };
1078
+ }
1079
+ function normalizeExecutionError(error, task, executionId) {
1080
+ if (error instanceof OacError3) {
1081
+ return error;
1082
+ }
1083
+ const message = error instanceof Error ? error.message : String(error);
1084
+ if (/timed out|timeout/i.test(message)) {
1085
+ return executionError3("AGENT_TIMEOUT", `Task ${task.id} timed out during execution.`, {
1086
+ context: {
1087
+ taskId: task.id,
1088
+ executionId,
1089
+ message
1090
+ },
1091
+ cause: error
1092
+ });
1093
+ }
1094
+ return executionError3("AGENT_EXECUTION_FAILED", `Task ${task.id} failed during execution.`, {
1095
+ context: {
1096
+ taskId: task.id,
1097
+ executionId,
1098
+ message
1099
+ },
1100
+ cause: error
1101
+ });
1102
+ }
1103
+ async function executeTask(agent, task, sandbox, eventBus, options = {}) {
1104
+ const executionId = options.executionId ?? randomUUID();
1105
+ const tokenBudget = options.tokenBudget ?? readMetadataNumber(task, "tokenBudget") ?? DEFAULT_TOKEN_BUDGET;
1106
+ const timeoutMs = options.timeoutMs ?? readMetadataNumber(task, "timeoutMs") ?? DEFAULT_TIMEOUT_MS;
1107
+ const allowCommits = options.allowCommits ?? true;
1108
+ const startedAt = Date.now();
1109
+ let observedTokens = 0;
1110
+ const observedFiles = /* @__PURE__ */ new Set();
1111
+ const execution = agent.execute({
1112
+ executionId,
1113
+ workingDirectory: sandbox.path,
1114
+ prompt: buildTaskPrompt(task),
1115
+ targetFiles: task.targetFiles,
1116
+ tokenBudget,
1117
+ allowCommits,
1118
+ timeoutMs
1119
+ });
1120
+ const streamPromise = (async () => {
1121
+ for await (const event of execution.events) {
1122
+ if (event.type === "tokens") {
1123
+ observedTokens = Math.max(observedTokens, event.cumulativeTokens);
1124
+ }
1125
+ if (event.type === "file_edit") {
1126
+ observedFiles.add(event.path);
1127
+ }
1128
+ eventBus.emit("execution:progress", {
1129
+ jobId: executionId,
1130
+ tokensUsed: observedTokens,
1131
+ stage: stageFromEvent(event)
1132
+ });
1133
+ }
1134
+ })();
1135
+ try {
1136
+ const result = await execution.result;
1137
+ await streamPromise;
1138
+ return mergeExecutionResult(result, observedTokens, observedFiles, startedAt);
1139
+ } catch (error) {
1140
+ try {
1141
+ await streamPromise;
1142
+ } catch {
1143
+ }
1144
+ throw normalizeExecutionError(error, task, executionId);
1145
+ }
1146
+ }
1147
+
1148
+ // src/engine.ts
1149
+ import { randomUUID as randomUUID2 } from "crypto";
1150
+ import { setTimeout as delay } from "timers/promises";
1151
+ import {
1152
+ OacError as OacError4,
1153
+ executionError as executionError4
1154
+ } from "@open330/oac-core";
1155
+ import PQueue from "p-queue";
1156
+ var DEFAULT_CONCURRENCY = 2;
1157
+ var DEFAULT_MAX_ATTEMPTS = 2;
1158
+ var DEFAULT_TIMEOUT_MS2 = 3e5;
1159
+ var DEFAULT_TOKEN_BUDGET2 = 5e4;
1160
+ function isRecord3(value) {
1161
+ return typeof value === "object" && value !== null;
1162
+ }
1163
+ function toErrorMessage(error) {
1164
+ if (error instanceof Error) {
1165
+ return error.message;
1166
+ }
1167
+ return String(error);
1168
+ }
1169
+ function sanitizeBranchSegment(value) {
1170
+ const sanitized = value.toLowerCase().replace(/[^a-z0-9/_-]+/g, "-").replace(/-+/g, "-").replace(/^[-/]+|[-/]+$/g, "");
1171
+ return sanitized || "task";
1172
+ }
1173
+ function isTransientError(error) {
1174
+ return error.code === "AGENT_TIMEOUT" || error.code === "AGENT_OOM" || error.code === "AGENT_RATE_LIMITED" || error.code === "NETWORK_ERROR" || error.code === "GIT_LOCK_FAILED";
1175
+ }
1176
+ var ExecutionEngine = class {
1177
+ constructor(agents, eventBus, config = {}) {
1178
+ this.agents = agents;
1179
+ this.eventBus = eventBus;
1180
+ if (agents.length === 0) {
1181
+ throw executionError4(
1182
+ "AGENT_NOT_AVAILABLE",
1183
+ "ExecutionEngine requires at least one agent provider"
1184
+ );
1185
+ }
1186
+ this.concurrency = Math.max(1, config.concurrency ?? DEFAULT_CONCURRENCY);
1187
+ this.maxAttempts = Math.max(1, config.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
1188
+ this.repoPath = config.repoPath ?? process.cwd();
1189
+ this.baseBranch = config.baseBranch ?? "main";
1190
+ this.branchPrefix = config.branchPrefix ?? "oac";
1191
+ this.taskTimeoutMs = Math.max(1, config.taskTimeoutMs ?? DEFAULT_TIMEOUT_MS2);
1192
+ this.defaultTokenBudget = Math.max(1, config.defaultTokenBudget ?? DEFAULT_TOKEN_BUDGET2);
1193
+ this.queue = new PQueue({
1194
+ concurrency: this.concurrency,
1195
+ autoStart: false
1196
+ });
1197
+ }
1198
+ queue;
1199
+ jobs = /* @__PURE__ */ new Map();
1200
+ activeJobs = /* @__PURE__ */ new Map();
1201
+ concurrency;
1202
+ maxAttempts;
1203
+ repoPath;
1204
+ baseBranch;
1205
+ branchPrefix;
1206
+ taskTimeoutMs;
1207
+ defaultTokenBudget;
1208
+ aborted = false;
1209
+ nextAgentIndex = 0;
1210
+ enqueue(plan) {
1211
+ const enqueuedJobs = [];
1212
+ for (const { task, estimate } of plan.selectedTasks) {
1213
+ const job = {
1214
+ id: randomUUID2(),
1215
+ task,
1216
+ estimate,
1217
+ status: "queued",
1218
+ attempts: 0,
1219
+ maxAttempts: this.maxAttempts,
1220
+ createdAt: Date.now()
1221
+ };
1222
+ this.jobs.set(job.id, job);
1223
+ enqueuedJobs.push(job);
1224
+ this.schedule(job);
1225
+ }
1226
+ return enqueuedJobs;
1227
+ }
1228
+ async run() {
1229
+ this.aborted = false;
1230
+ this.queue.start();
1231
+ await this.queue.onIdle();
1232
+ return this.buildRunResult();
1233
+ }
1234
+ async abort() {
1235
+ this.aborted = true;
1236
+ this.queue.pause();
1237
+ this.queue.clear();
1238
+ const abortError = executionError4("AGENT_EXECUTION_FAILED", "Execution aborted by user.");
1239
+ for (const job of this.jobs.values()) {
1240
+ if (job.status === "queued" || job.status === "retrying") {
1241
+ job.status = "aborted";
1242
+ job.completedAt = Date.now();
1243
+ job.error = abortError;
1244
+ }
1245
+ }
1246
+ await Promise.all(
1247
+ [...this.activeJobs.values()].map(async ({ job, agent }) => {
1248
+ job.status = "aborted";
1249
+ job.completedAt = Date.now();
1250
+ job.error = abortError;
1251
+ this.eventBus.emit("execution:failed", {
1252
+ jobId: job.id,
1253
+ error: abortError
1254
+ });
1255
+ try {
1256
+ await agent.abort(job.id);
1257
+ } catch {
1258
+ }
1259
+ })
1260
+ );
1261
+ }
1262
+ schedule(job, delayMs = 0) {
1263
+ void this.queue.add(
1264
+ async () => {
1265
+ if (delayMs > 0) {
1266
+ await delay(delayMs);
1267
+ }
1268
+ await this.runJob(job);
1269
+ },
1270
+ { priority: job.task.priority }
1271
+ ).catch((error) => {
1272
+ const normalized = this.normalizeError(error, job);
1273
+ job.status = "failed";
1274
+ job.completedAt = Date.now();
1275
+ job.error = normalized;
1276
+ this.eventBus.emit("execution:failed", {
1277
+ jobId: job.id,
1278
+ error: normalized
1279
+ });
1280
+ });
1281
+ }
1282
+ async runJob(job) {
1283
+ if (this.aborted || job.status === "aborted") {
1284
+ return;
1285
+ }
1286
+ job.attempts += 1;
1287
+ job.status = "running";
1288
+ job.startedAt ??= Date.now();
1289
+ const agent = this.selectAgent();
1290
+ job.workerId = agent.id;
1291
+ this.activeJobs.set(job.id, { job, agent });
1292
+ this.eventBus.emit("execution:started", {
1293
+ jobId: job.id,
1294
+ task: job.task,
1295
+ agent: agent.id
1296
+ });
1297
+ let sandboxCleanup;
1298
+ try {
1299
+ const branchName = this.createBranchName(job);
1300
+ const sandbox = await createSandbox(this.repoPath, branchName, this.baseBranch);
1301
+ sandboxCleanup = sandbox.cleanup;
1302
+ const result = await executeTask(agent, job.task, sandbox, this.eventBus, {
1303
+ executionId: job.id,
1304
+ tokenBudget: job.estimate.totalEstimatedTokens > 0 ? job.estimate.totalEstimatedTokens : this.defaultTokenBudget,
1305
+ timeoutMs: this.taskTimeoutMs,
1306
+ allowCommits: true
1307
+ });
1308
+ job.result = result;
1309
+ job.completedAt = Date.now();
1310
+ if (result.success) {
1311
+ job.status = "completed";
1312
+ this.eventBus.emit("execution:completed", {
1313
+ jobId: job.id,
1314
+ result
1315
+ });
1316
+ return;
1317
+ }
1318
+ const failure = executionError4(
1319
+ "AGENT_EXECUTION_FAILED",
1320
+ result.error ?? `Task ${job.task.id} exited with code ${result.exitCode}.`,
1321
+ {
1322
+ context: {
1323
+ taskId: job.task.id,
1324
+ jobId: job.id,
1325
+ exitCode: result.exitCode,
1326
+ attempt: job.attempts
1327
+ }
1328
+ }
1329
+ );
1330
+ await this.handleFailure(job, failure);
1331
+ } catch (error) {
1332
+ const normalized = this.normalizeError(error, job);
1333
+ await this.handleFailure(job, normalized);
1334
+ } finally {
1335
+ this.activeJobs.delete(job.id);
1336
+ if (sandboxCleanup) {
1337
+ try {
1338
+ await sandboxCleanup();
1339
+ } catch (cleanupError) {
1340
+ const cleanupMessage = toErrorMessage(cleanupError);
1341
+ job.error ??= executionError4(
1342
+ "AGENT_EXECUTION_FAILED",
1343
+ `Sandbox cleanup failed for job ${job.id}`,
1344
+ {
1345
+ context: {
1346
+ jobId: job.id,
1347
+ cleanupError: cleanupMessage
1348
+ },
1349
+ cause: cleanupError
1350
+ }
1351
+ );
1352
+ }
1353
+ }
1354
+ }
1355
+ }
1356
+ async handleFailure(job, error) {
1357
+ job.error = error;
1358
+ if (this.aborted || job.status === "aborted") {
1359
+ job.status = "aborted";
1360
+ job.completedAt = Date.now();
1361
+ return;
1362
+ }
1363
+ if (job.attempts < job.maxAttempts && isTransientError(error)) {
1364
+ job.status = "retrying";
1365
+ const retryDelay = error.code === "AGENT_RATE_LIMITED" ? Math.min(6e4, 1e4 * 2 ** (job.attempts - 1)) : Math.min(5e3, job.attempts * 1e3);
1366
+ this.schedule(job, retryDelay);
1367
+ return;
1368
+ }
1369
+ job.status = "failed";
1370
+ job.completedAt = Date.now();
1371
+ this.eventBus.emit("execution:failed", {
1372
+ jobId: job.id,
1373
+ error
1374
+ });
1375
+ }
1376
+ selectAgent() {
1377
+ const agent = this.agents[this.nextAgentIndex % this.agents.length];
1378
+ this.nextAgentIndex = (this.nextAgentIndex + 1) % this.agents.length;
1379
+ return agent;
1380
+ }
1381
+ createBranchName(job) {
1382
+ const dateSegment = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replaceAll("-", "");
1383
+ const taskSegment = sanitizeBranchSegment(job.task.id);
1384
+ return `${this.branchPrefix}/${dateSegment}/${taskSegment}-${job.id.slice(0, 8)}-a${job.attempts}`;
1385
+ }
1386
+ normalizeError(error, job) {
1387
+ if (error instanceof OacError4) {
1388
+ return error;
1389
+ }
1390
+ const message = toErrorMessage(error);
1391
+ if (/timed out|timeout/i.test(message)) {
1392
+ return executionError4("AGENT_TIMEOUT", `Job ${job.id} timed out.`, {
1393
+ context: {
1394
+ jobId: job.id,
1395
+ taskId: job.task.id,
1396
+ message,
1397
+ attempt: job.attempts
1398
+ },
1399
+ cause: error
1400
+ });
1401
+ }
1402
+ if (/out of memory|ENOMEM|heap/i.test(message)) {
1403
+ return executionError4("AGENT_OOM", `Job ${job.id} ran out of memory.`, {
1404
+ context: {
1405
+ jobId: job.id,
1406
+ taskId: job.task.id,
1407
+ message,
1408
+ attempt: job.attempts
1409
+ },
1410
+ cause: error
1411
+ });
1412
+ }
1413
+ if (/network|ECONN|ENOTFOUND|EAI_AGAIN/i.test(message)) {
1414
+ return new OacError4(
1415
+ `Job ${job.id} failed due to a network error.`,
1416
+ "NETWORK_ERROR",
1417
+ "recoverable",
1418
+ {
1419
+ jobId: job.id,
1420
+ taskId: job.task.id,
1421
+ message,
1422
+ attempt: job.attempts
1423
+ },
1424
+ error
1425
+ );
1426
+ }
1427
+ if (/index\.lock|cannot lock ref|Unable to create '.+?\.git\/index\.lock'/i.test(message)) {
1428
+ return new OacError4(
1429
+ `Job ${job.id} failed due to a git lock conflict.`,
1430
+ "GIT_LOCK_FAILED",
1431
+ "recoverable",
1432
+ {
1433
+ jobId: job.id,
1434
+ taskId: job.task.id,
1435
+ message,
1436
+ attempt: job.attempts
1437
+ },
1438
+ error
1439
+ );
1440
+ }
1441
+ if (isRecord3(error) && error.name === "AbortError") {
1442
+ return executionError4("AGENT_EXECUTION_FAILED", `Job ${job.id} was aborted.`, {
1443
+ context: {
1444
+ jobId: job.id,
1445
+ taskId: job.task.id,
1446
+ attempt: job.attempts
1447
+ },
1448
+ cause: error
1449
+ });
1450
+ }
1451
+ return executionError4("AGENT_EXECUTION_FAILED", `Job ${job.id} failed unexpectedly.`, {
1452
+ context: {
1453
+ jobId: job.id,
1454
+ taskId: job.task.id,
1455
+ message,
1456
+ attempt: job.attempts
1457
+ },
1458
+ cause: error
1459
+ });
1460
+ }
1461
+ buildRunResult() {
1462
+ const jobs = [...this.jobs.values()];
1463
+ return {
1464
+ jobs,
1465
+ completed: jobs.filter((job) => job.status === "completed"),
1466
+ failed: jobs.filter((job) => job.status === "failed"),
1467
+ aborted: jobs.filter((job) => job.status === "aborted")
1468
+ };
1469
+ }
1470
+ };
1471
+ export {
1472
+ ClaudeCodeAdapter,
1473
+ CodexAdapter,
1474
+ ExecutionEngine,
1475
+ createSandbox,
1476
+ executeTask,
1477
+ isTransientError
1478
+ };
1479
+ //# sourceMappingURL=index.js.map