@backburner/cli 0.1.3 → 0.1.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/src/agents/codex.js
CHANGED
|
@@ -192,7 +192,9 @@ export class CodexCliAgent {
|
|
|
192
192
|
: {})
|
|
193
193
|
};
|
|
194
194
|
const parsedThreadId = extractThreadIdFromCodexOutput(stdout);
|
|
195
|
-
const providerLimitSignal =
|
|
195
|
+
const providerLimitSignal = exitCode === 0
|
|
196
|
+
? undefined
|
|
197
|
+
: detectProviderLimit("codex", exitCode, extractCodexProviderErrorText(stdout), stderr);
|
|
196
198
|
return {
|
|
197
199
|
taskStatus: execution.taskStatus,
|
|
198
200
|
execution,
|
|
@@ -259,3 +261,61 @@ function extractCodexStreamJsonAssistantText(streamText) {
|
|
|
259
261
|
}
|
|
260
262
|
return undefined;
|
|
261
263
|
}
|
|
264
|
+
function extractCodexProviderErrorText(streamText) {
|
|
265
|
+
if (!streamText.trim()) {
|
|
266
|
+
return "";
|
|
267
|
+
}
|
|
268
|
+
const extractedErrors = [];
|
|
269
|
+
let sawJsonLine = false;
|
|
270
|
+
for (const line of streamText.split(/\r?\n/)) {
|
|
271
|
+
const trimmed = line.trim();
|
|
272
|
+
if (!trimmed) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const event = JSON.parse(trimmed);
|
|
277
|
+
sawJsonLine = true;
|
|
278
|
+
collectCodexErrorStrings(event, extractedErrors);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Non-JSON Codex output is a direct process surface and can be scanned.
|
|
282
|
+
extractedErrors.push(trimmed);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return sawJsonLine ? extractedErrors.join("\n") : streamText;
|
|
286
|
+
}
|
|
287
|
+
function collectCodexErrorStrings(value, out) {
|
|
288
|
+
if (!value || typeof value !== "object") {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const record = value;
|
|
292
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
293
|
+
const isErrorEvent = type.toLowerCase().includes("error");
|
|
294
|
+
if (isErrorEvent) {
|
|
295
|
+
for (const key of ["message", "error", "details"]) {
|
|
296
|
+
const text = stringifyCodexErrorField(record[key]);
|
|
297
|
+
if (text) {
|
|
298
|
+
out.push(text);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const errorText = stringifyCodexErrorField(record.error);
|
|
303
|
+
if (errorText && (isErrorEvent || record.status === "failed")) {
|
|
304
|
+
out.push(errorText);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function stringifyCodexErrorField(value) {
|
|
308
|
+
if (typeof value === "string") {
|
|
309
|
+
return value;
|
|
310
|
+
}
|
|
311
|
+
if (value && typeof value === "object") {
|
|
312
|
+
const record = value;
|
|
313
|
+
if (typeof record.message === "string") {
|
|
314
|
+
return record.message;
|
|
315
|
+
}
|
|
316
|
+
if (typeof record.code === "string") {
|
|
317
|
+
return record.code;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
@@ -310,11 +310,12 @@ export async function dispatchPendingTasks(input) {
|
|
|
310
310
|
let providerLimitQueueProviders;
|
|
311
311
|
let providerLimitResetTime;
|
|
312
312
|
let swappedAfterProviderLimit = false;
|
|
313
|
-
|
|
313
|
+
const actionableProviderLimitSignal = result.taskStatus === "completed" ? undefined : result.providerLimitSignal;
|
|
314
|
+
if (actionableProviderLimitSignal) {
|
|
314
315
|
try {
|
|
315
316
|
const limitState = await input.stateStore.load();
|
|
316
317
|
const providerLimits = [...(limitState.providerLimits ?? [])];
|
|
317
|
-
const signal =
|
|
318
|
+
const signal = actionableProviderLimitSignal;
|
|
318
319
|
const existingIdx = providerLimits.findIndex(l => l.provider === signal.provider);
|
|
319
320
|
const newRecord = mapSignalToRecord(signal, now());
|
|
320
321
|
if (existingIdx !== -1) {
|
|
@@ -362,15 +363,15 @@ export async function dispatchPendingTasks(input) {
|
|
|
362
363
|
}
|
|
363
364
|
catch (err) {
|
|
364
365
|
input.logger?.warn(`[dispatch] Failed to persist provider limit updates: ${err}`);
|
|
365
|
-
providerLimitQueueProviders =
|
|
366
|
-
providerLimitResetTime =
|
|
366
|
+
providerLimitQueueProviders = actionableProviderLimitSignal.provider;
|
|
367
|
+
providerLimitResetTime = actionableProviderLimitSignal.resetTime;
|
|
367
368
|
}
|
|
368
369
|
}
|
|
369
|
-
const isProviderLimit = !!
|
|
370
|
+
const isProviderLimit = !!actionableProviderLimitSignal;
|
|
370
371
|
const shouldQueueForProviderLimit = isProviderLimit && !swappedAfterProviderLimit;
|
|
371
372
|
const finalStatus = shouldQueueForProviderLimit ? "queued" : swappedAfterProviderLimit ? "pending" : result.taskStatus;
|
|
372
373
|
if (shouldQueueForProviderLimit && !preparingTask.context.notifiedQueued) {
|
|
373
|
-
await safelyNotifyQueued(input.lifecycleNotifier, preparingTask, providerLimitQueueProviders ??
|
|
374
|
+
await safelyNotifyQueued(input.lifecycleNotifier, preparingTask, providerLimitQueueProviders ?? actionableProviderLimitSignal.provider, providerLimitResetTime, input.logger);
|
|
374
375
|
}
|
|
375
376
|
tasks[index] = {
|
|
376
377
|
...preparingTask,
|
|
@@ -395,12 +396,12 @@ export async function dispatchPendingTasks(input) {
|
|
|
395
396
|
}
|
|
396
397
|
: {})
|
|
397
398
|
},
|
|
398
|
-
...(shouldQueueForProviderLimit ? { waitingOnProvider:
|
|
399
|
+
...(shouldQueueForProviderLimit ? { waitingOnProvider: actionableProviderLimitSignal.provider } : { waitingOnProvider: undefined })
|
|
399
400
|
};
|
|
400
401
|
await input.stateStore.writeExecutionState(executions);
|
|
401
402
|
await input.stateStore.writeTaskState(tasks);
|
|
402
403
|
if (shouldQueueForProviderLimit) {
|
|
403
|
-
console.log(`🤖 Task ${preparingTask.id} has been queued (waiting on provider ${
|
|
404
|
+
console.log(`🤖 Task ${preparingTask.id} has been queued (waiting on provider ${actionableProviderLimitSignal.provider} due to rate limit).`);
|
|
404
405
|
}
|
|
405
406
|
if (assignmentResolution.mode === "pinned") {
|
|
406
407
|
try {
|
|
@@ -11,7 +11,7 @@ export const planBreakdownHandler = {
|
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
function derivePlanBreakdownTask(context, env) {
|
|
14
|
-
const { workstream, attention, issue, activePullRequest, repoConfig, repoSyncRecord } = context;
|
|
14
|
+
const { workstream, attention, issue, activePullRequest, activeWorktree, repoConfig, repoSyncRecord } = context;
|
|
15
15
|
if (hasGeneratedPlanBreakdown(workstream)) {
|
|
16
16
|
return undefined;
|
|
17
17
|
}
|
|
@@ -23,6 +23,13 @@ function derivePlanBreakdownTask(context, env) {
|
|
|
23
23
|
if (!source) {
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
|
+
const prWorktree = source.kind === "pull_request" &&
|
|
27
|
+
activeWorktree &&
|
|
28
|
+
activePullRequest &&
|
|
29
|
+
activePullRequest.id === source.id &&
|
|
30
|
+
activeWorktree.branchName === activePullRequest.headBranch
|
|
31
|
+
? activeWorktree
|
|
32
|
+
: undefined;
|
|
26
33
|
return buildTaskRecord(buildTaskId("plan_breakdown", source.id), "plan_breakdown", source, repoSyncRecord, {
|
|
27
34
|
reason: `The "${repoConfig.labels.planBreakdownNeeded}" label is present, requesting a technical breakdown before implementation.`,
|
|
28
35
|
summary: `Break down implementation for ${source.kind.replace("_", " ")} #${source.number}: ${source.title}`,
|
|
@@ -40,6 +47,16 @@ function derivePlanBreakdownTask(context, env) {
|
|
|
40
47
|
issueBody: source.body
|
|
41
48
|
}
|
|
42
49
|
: {}),
|
|
43
|
-
...(
|
|
50
|
+
...(prWorktree
|
|
51
|
+
? {
|
|
52
|
+
localRepoPath: prWorktree.path,
|
|
53
|
+
branchName: prWorktree.branchName,
|
|
54
|
+
planDocumentPath: prWorktree.planDocumentPath,
|
|
55
|
+
...(prWorktree.productSpecDocumentPath ? { productSpecDocumentPath: prWorktree.productSpecDocumentPath } : {}),
|
|
56
|
+
logDocumentPath: prWorktree.logDocumentPath
|
|
57
|
+
}
|
|
58
|
+
: repoSyncRecord?.localPath
|
|
59
|
+
? { localRepoPath: repoSyncRecord.localPath }
|
|
60
|
+
: {})
|
|
44
61
|
}), workstream.id);
|
|
45
62
|
}
|