@link-assistant/hive-mind 2.11.13 ā 2.12.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.
- package/CHANGELOG.md +18 -0
- package/package.json +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
package/src/codex.lib.mjs
CHANGED
|
@@ -7,12 +7,10 @@ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
|
7
7
|
if (typeof globalThis.use === 'undefined') {
|
|
8
8
|
await ensureUseM();
|
|
9
9
|
}
|
|
10
|
-
|
|
11
10
|
const { $ } = await use('command-stream');
|
|
12
11
|
const fs = (await use('fs')).promises;
|
|
13
12
|
const path = (await use('path')).default;
|
|
14
13
|
const os = (await use('os')).default;
|
|
15
|
-
|
|
16
14
|
// Import log from general lib
|
|
17
15
|
import { log } from './lib.mjs';
|
|
18
16
|
// Issues #1955 / #1990: run-health analysis lives in its own module to keep this
|
|
@@ -45,32 +43,27 @@ import { createPullRequestBaseBranchCommandIntervention } from './solve.pr-base-
|
|
|
45
43
|
import Decimal from 'decimal.js-light';
|
|
46
44
|
import { ensureAiToolScratchIgnored, filterAiToolScratchFromStatus } from './ai-tool-scratch.lib.mjs';
|
|
47
45
|
import { CODEX_CACHE_READ_USAGE_PATHS, CODEX_CACHE_WRITE_USAGE_PATHS, CODEX_MODEL_DIAGNOSTIC_PATHS, CODEX_REASONING_USAGE_PATHS, CODEX_USAGE_FIELD_NAMES, createCodexTokenFieldAvailability, getFirstObservedNumber, hasAnyObservedPath, hasOwnPath } from './codex.usage-fields.lib.mjs';
|
|
48
|
-
|
|
49
46
|
const CODEX_LONG_CONTEXT_PRICE_THRESHOLD = 272000;
|
|
50
47
|
const CODEX_COMPACT_API_ENDPOINT = '/responses/compact';
|
|
51
48
|
const getCodexExecEnv = (verbose = false) => (verbose ? { ...process.env, RUST_LOG: 'debug' } : { ...process.env });
|
|
52
49
|
|
|
53
50
|
const escapeRegExp = value => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
54
|
-
|
|
55
51
|
const getCodexDiagnosticValue = (line, key) => {
|
|
56
52
|
const match = line.match(new RegExp(`${escapeRegExp(key)}=(?:"([^"]*)"|([^\\s")]+))`));
|
|
57
53
|
return match?.[1] ?? match?.[2] ?? null;
|
|
58
54
|
};
|
|
59
|
-
|
|
60
55
|
const getCodexDiagnosticInteger = (line, key) => {
|
|
61
56
|
const value = getCodexDiagnosticValue(line, key);
|
|
62
57
|
if (value === null) return null;
|
|
63
58
|
const parsed = Number.parseInt(value, 10);
|
|
64
59
|
return Number.isFinite(parsed) ? parsed : null;
|
|
65
60
|
};
|
|
66
|
-
|
|
67
61
|
const getCodexDiagnosticTimestamp = line => {
|
|
68
62
|
const eventTimestamp = getCodexDiagnosticValue(line, 'event.timestamp');
|
|
69
63
|
if (eventTimestamp) return eventTimestamp;
|
|
70
64
|
const logPrefixMatch = line.match(/^\[(\d{4}-\d{2}-\d{2}T[^\]]+Z)\]/u);
|
|
71
65
|
return logPrefixMatch?.[1] ?? null;
|
|
72
66
|
};
|
|
73
|
-
|
|
74
67
|
const isSuccessfulCodexCompactRequestLine = line => {
|
|
75
68
|
if (!line.includes('codex_otel.log_only:')) return false;
|
|
76
69
|
if (!line.includes('event.name="codex.api_request"')) return false;
|
|
@@ -90,7 +83,6 @@ const splitTokenCountEvenly = (total, partCount) => {
|
|
|
90
83
|
return value;
|
|
91
84
|
});
|
|
92
85
|
};
|
|
93
|
-
|
|
94
86
|
const splitCodexSubSessionInputTokens = (total, partCount, autoCompactTokenLimit = null) => {
|
|
95
87
|
const safeTotal = Math.max(0, Math.round(total || 0));
|
|
96
88
|
const safePartCount = Math.max(1, Math.round(partCount || 1));
|
|
@@ -109,13 +101,11 @@ const splitCodexSubSessionInputTokens = (total, partCount, autoCompactTokenLimit
|
|
|
109
101
|
}
|
|
110
102
|
return splitTokenCountEvenly(safeTotal, safePartCount);
|
|
111
103
|
};
|
|
112
|
-
|
|
113
104
|
const splitTokenCountByWeights = (total, weights) => {
|
|
114
105
|
const safeTotal = Math.max(0, Math.round(total || 0));
|
|
115
106
|
const safeWeights = Array.isArray(weights) && weights.length > 0 ? weights.map(weight => Math.max(0, weight || 0)) : [1];
|
|
116
107
|
const weightTotal = safeWeights.reduce((sum, weight) => sum + weight, 0);
|
|
117
108
|
if (weightTotal <= 0) return splitTokenCountEvenly(safeTotal, safeWeights.length);
|
|
118
|
-
|
|
119
109
|
let allocated = 0;
|
|
120
110
|
return safeWeights.map((weight, index) => {
|
|
121
111
|
if (index === safeWeights.length - 1) return Math.max(0, safeTotal - allocated);
|
|
@@ -124,7 +114,6 @@ const splitTokenCountByWeights = (total, weights) => {
|
|
|
124
114
|
return value;
|
|
125
115
|
});
|
|
126
116
|
};
|
|
127
|
-
|
|
128
117
|
const rebuildCodexSubSessionsFromCompactifications = tokenUsage => {
|
|
129
118
|
const compactifications = Array.isArray(tokenUsage.compactifications) ? tokenUsage.compactifications : [];
|
|
130
119
|
if (compactifications.length === 0 || (tokenUsage.stepCount || 0) === 0) {
|
|
@@ -137,7 +126,6 @@ const rebuildCodexSubSessionsFromCompactifications = tokenUsage => {
|
|
|
137
126
|
const cacheWriteChunks = splitTokenCountByWeights(tokenUsage.cacheWriteTokens || 0, inputChunks);
|
|
138
127
|
const cacheReadChunks = splitTokenCountByWeights(tokenUsage.cacheReadTokens || 0, inputChunks);
|
|
139
128
|
const outputChunks = splitTokenCountByWeights(tokenUsage.outputTokens || 0, inputChunks);
|
|
140
|
-
|
|
141
129
|
tokenUsage.subSessions = inputChunks.map((inputTokens, index) => {
|
|
142
130
|
const cacheCreationTokens = cacheWriteChunks[index] || 0;
|
|
143
131
|
const outputTokens = outputChunks[index] || 0;
|
|
@@ -155,14 +143,12 @@ const rebuildCodexSubSessionsFromCompactifications = tokenUsage => {
|
|
|
155
143
|
};
|
|
156
144
|
});
|
|
157
145
|
};
|
|
158
|
-
|
|
159
146
|
const recordCodexCompactification = (line, tokenUsage) => {
|
|
160
147
|
if (!isSuccessfulCodexCompactRequestLine(line)) return;
|
|
161
148
|
const timestamp = getCodexDiagnosticTimestamp(line);
|
|
162
149
|
const conversationId = getCodexDiagnosticValue(line, 'conversation.id');
|
|
163
150
|
const existing = tokenUsage.compactifications.find(compact => compact.timestamp === timestamp && compact.conversationId === conversationId);
|
|
164
151
|
if (existing) return;
|
|
165
|
-
|
|
166
152
|
tokenUsage.compactifications.push({
|
|
167
153
|
timestamp,
|
|
168
154
|
preTokens: null,
|
|
@@ -171,17 +157,14 @@ const recordCodexCompactification = (line, tokenUsage) => {
|
|
|
171
157
|
conversationId: conversationId || null,
|
|
172
158
|
});
|
|
173
159
|
};
|
|
174
|
-
|
|
175
160
|
const parseCodexDiagnosticLine = (line, tokenUsage) => {
|
|
176
161
|
const contextLimit = getCodexDiagnosticInteger(line, 'context_window') ?? getCodexDiagnosticInteger(line, 'model_context_window');
|
|
177
162
|
if (contextLimit !== null) tokenUsage.contextLimit = contextLimit;
|
|
178
163
|
|
|
179
164
|
const autoCompactTokenLimit = getCodexDiagnosticInteger(line, 'auto_compact_token_limit') ?? getCodexDiagnosticInteger(line, 'model_auto_compact_token_limit');
|
|
180
165
|
if (autoCompactTokenLimit !== null) tokenUsage.autoCompactTokenLimit = autoCompactTokenLimit;
|
|
181
|
-
|
|
182
166
|
recordCodexCompactification(line, tokenUsage);
|
|
183
167
|
};
|
|
184
|
-
|
|
185
168
|
export const createCodexTokenUsage = requestedModelId => ({
|
|
186
169
|
inputTokens: 0,
|
|
187
170
|
outputTokens: 0,
|
|
@@ -201,7 +184,6 @@ export const createCodexTokenUsage = requestedModelId => ({
|
|
|
201
184
|
compactifications: [],
|
|
202
185
|
tokenFieldAvailability: createCodexTokenFieldAvailability(),
|
|
203
186
|
});
|
|
204
|
-
|
|
205
187
|
const createEmptyCodexItemUsage = () => ({
|
|
206
188
|
inputTokens: 0,
|
|
207
189
|
cacheCreationTokens: 0,
|
|
@@ -209,7 +191,6 @@ const createEmptyCodexItemUsage = () => ({
|
|
|
209
191
|
outputTokens: 0,
|
|
210
192
|
totalTokens: null,
|
|
211
193
|
});
|
|
212
|
-
|
|
213
194
|
const upsertById = (items, nextItem) => {
|
|
214
195
|
const existingIndex = items.findIndex(item => item.id === nextItem.id);
|
|
215
196
|
if (existingIndex >= 0) {
|
|
@@ -231,10 +212,8 @@ const upsertCodexSubAgentCall = (subAgentCalls, item, requestedModelId = null) =
|
|
|
231
212
|
status: item.status || null,
|
|
232
213
|
usage: subAgentCalls.find(call => call.id === item.id)?.usage || createEmptyCodexItemUsage(),
|
|
233
214
|
};
|
|
234
|
-
|
|
235
215
|
upsertById(subAgentCalls, nextCall);
|
|
236
216
|
};
|
|
237
|
-
|
|
238
217
|
const upsertCodexCommandExecution = (commandExecutions, item) => {
|
|
239
218
|
upsertById(commandExecutions, {
|
|
240
219
|
id: item.id || null,
|
|
@@ -244,7 +223,6 @@ const upsertCodexCommandExecution = (commandExecutions, item) => {
|
|
|
244
223
|
status: item.status || null,
|
|
245
224
|
});
|
|
246
225
|
};
|
|
247
|
-
|
|
248
226
|
const upsertCodexFileChange = (fileChanges, item) => {
|
|
249
227
|
upsertById(fileChanges, {
|
|
250
228
|
id: item.id || null,
|
|
@@ -257,7 +235,6 @@ const upsertCodexFileChange = (fileChanges, item) => {
|
|
|
257
235
|
: [],
|
|
258
236
|
});
|
|
259
237
|
};
|
|
260
|
-
|
|
261
238
|
const upsertCodexMcpToolCall = (mcpToolCalls, item) => {
|
|
262
239
|
upsertById(mcpToolCalls, {
|
|
263
240
|
id: item.id || null,
|
|
@@ -278,7 +255,6 @@ const upsertCodexWebSearch = (webSearches, item) => {
|
|
|
278
255
|
action: item.action || null,
|
|
279
256
|
});
|
|
280
257
|
};
|
|
281
|
-
|
|
282
258
|
const upsertCodexTodoList = (todoLists, item) => {
|
|
283
259
|
upsertById(todoLists, {
|
|
284
260
|
id: item.id || null,
|
|
@@ -290,14 +266,12 @@ const upsertCodexTodoList = (todoLists, item) => {
|
|
|
290
266
|
: [],
|
|
291
267
|
});
|
|
292
268
|
};
|
|
293
|
-
|
|
294
269
|
const upsertCodexItemError = (itemErrors, item) => {
|
|
295
270
|
upsertById(itemErrors, {
|
|
296
271
|
id: item.id || null,
|
|
297
272
|
message: item.message || '',
|
|
298
273
|
});
|
|
299
274
|
};
|
|
300
|
-
|
|
301
275
|
// Issue #2136: `codex exec --json` writes its NDJSON protocol to **stdout** only.
|
|
302
276
|
// Its stderr carries OTEL tracing text (RUST_LOG=debug under --verbose), and each
|
|
303
277
|
// `codex.tool_result` record dumps the raw stdout of the command codex just ran ā
|
|
@@ -347,7 +321,6 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
347
321
|
// identity we can check. Diagnostics only; the gate stays order-based.
|
|
348
322
|
foreignThreadIds: state.foreignThreadIds || [],
|
|
349
323
|
};
|
|
350
|
-
|
|
351
324
|
nextState.tokenUsage.tokenFieldAvailability ||= createCodexTokenFieldAvailability();
|
|
352
325
|
if (!Array.isArray(nextState.tokenUsage.subSessions)) nextState.tokenUsage.subSessions = [];
|
|
353
326
|
if (!Array.isArray(nextState.tokenUsage.compactifications)) nextState.tokenUsage.compactifications = [];
|
|
@@ -357,9 +330,7 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
357
330
|
for (const rawLine of output.split('\n')) {
|
|
358
331
|
const line = rawLine.trim();
|
|
359
332
|
if (!line) continue;
|
|
360
|
-
|
|
361
333
|
parseCodexDiagnosticLine(line, nextState.tokenUsage);
|
|
362
|
-
|
|
363
334
|
let data;
|
|
364
335
|
try {
|
|
365
336
|
data = sanitizeObjectStrings(JSON.parse(line));
|
|
@@ -371,7 +342,6 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
371
342
|
if (rejection) nextState.pluginInstallRejections.push(rejection);
|
|
372
343
|
continue;
|
|
373
344
|
}
|
|
374
|
-
|
|
375
345
|
// Issue #1968: a stream line that parses to a bare `null` (or any non-object
|
|
376
346
|
// JSON primitive such as a number/string/boolean) must not crash the parser.
|
|
377
347
|
// Codex echoes the stdout of every command it runs back into its own NDJSON
|
|
@@ -381,7 +351,6 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
381
351
|
// aborted the entire solve. Real Codex events are always JSON objects, so any
|
|
382
352
|
// non-object line is safely ignored.
|
|
383
353
|
if (data === null || typeof data !== 'object') continue;
|
|
384
|
-
|
|
385
354
|
const eventType = typeof data.type === 'string' ? data.type : 'unknown';
|
|
386
355
|
|
|
387
356
|
// Issue #2136: a protocol-shaped object on a non-protocol stream is echoed
|
|
@@ -390,12 +359,10 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
390
359
|
nextState.telemetryEventCounts[eventType] = (nextState.telemetryEventCounts[eventType] || 0) + 1;
|
|
391
360
|
continue;
|
|
392
361
|
}
|
|
393
|
-
|
|
394
362
|
nextState.eventCounts[eventType] = (nextState.eventCounts[eventType] || 0) + 1;
|
|
395
363
|
if (eventType === 'turn.started' || eventType === 'turn.completed' || eventType === 'turn.failed') {
|
|
396
364
|
nextState.turnLifecycle.push(eventType);
|
|
397
365
|
}
|
|
398
|
-
|
|
399
366
|
if (eventType === 'thread.started' && typeof data.thread_id === 'string' && !nextState.sessionId) {
|
|
400
367
|
nextState.sessionId = data.thread_id;
|
|
401
368
|
} else if (eventType === 'thread.started' && typeof data.thread_id === 'string' && data.thread_id !== nextState.sessionId) {
|
|
@@ -406,7 +373,6 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
406
373
|
} else if (!nextState.sessionId && typeof data.session_id === 'string') {
|
|
407
374
|
nextState.sessionId = data.session_id;
|
|
408
375
|
}
|
|
409
|
-
|
|
410
376
|
for (const [pathName, getter] of CODEX_MODEL_DIAGNOSTIC_PATHS) {
|
|
411
377
|
if (typeof getter(data) === 'string') observedModelPaths.add(pathName);
|
|
412
378
|
}
|
|
@@ -420,26 +386,22 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
420
386
|
if (streamErrorText.includes('401') || streamErrorText.includes('Unauthorized')) nextState.authError = true;
|
|
421
387
|
nextState.streamErrors.push({ message: streamErrorText });
|
|
422
388
|
}
|
|
423
|
-
|
|
424
389
|
const turnFailureText = eventType === 'turn.failed' ? firstErrorText([data.error, data.message]) : '';
|
|
425
390
|
if (turnFailureText) {
|
|
426
391
|
if (turnFailureText.includes('401') || turnFailureText.includes('Unauthorized')) nextState.authError = true;
|
|
427
392
|
nextState.turnFailures.push({ message: turnFailureText });
|
|
428
393
|
}
|
|
429
|
-
|
|
430
394
|
if (eventType === 'turn.completed' && data.usage && typeof data.usage === 'object') {
|
|
431
395
|
const inputTokens = getFirstObservedNumber(data.usage, ['input_tokens']);
|
|
432
396
|
const cachedInputTokens = getFirstObservedNumber(data.usage, CODEX_CACHE_READ_USAGE_PATHS);
|
|
433
397
|
const cacheWriteTokens = getFirstObservedNumber(data.usage, CODEX_CACHE_WRITE_USAGE_PATHS);
|
|
434
398
|
const outputTokens = getFirstObservedNumber(data.usage, ['output_tokens']);
|
|
435
399
|
const reasoningTokens = getFirstObservedNumber(data.usage, CODEX_REASONING_USAGE_PATHS);
|
|
436
|
-
|
|
437
400
|
if (hasOwnPath(data.usage, 'input_tokens')) nextState.tokenUsage.tokenFieldAvailability.inputTokens = true;
|
|
438
401
|
if (hasAnyObservedPath(data.usage, CODEX_CACHE_READ_USAGE_PATHS)) nextState.tokenUsage.tokenFieldAvailability.cacheReadTokens = true;
|
|
439
402
|
if (hasAnyObservedPath(data.usage, CODEX_CACHE_WRITE_USAGE_PATHS)) nextState.tokenUsage.tokenFieldAvailability.cacheWriteTokens = true;
|
|
440
403
|
if (hasOwnPath(data.usage, 'output_tokens')) nextState.tokenUsage.tokenFieldAvailability.outputTokens = true;
|
|
441
404
|
if (hasAnyObservedPath(data.usage, CODEX_REASONING_USAGE_PATHS)) nextState.tokenUsage.tokenFieldAvailability.reasoningTokens = true;
|
|
442
|
-
|
|
443
405
|
const nonCachedInputTokens = Math.max(0, inputTokens - cachedInputTokens);
|
|
444
406
|
nextState.tokenUsage.inputTokens += nonCachedInputTokens;
|
|
445
407
|
nextState.tokenUsage.cacheReadTokens += cachedInputTokens;
|
|
@@ -463,19 +425,15 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
463
425
|
const usageFieldSet = CODEX_USAGE_FIELD_NAMES.filter(fieldName => hasOwnPath(data.usage, fieldName));
|
|
464
426
|
if (usageFieldSet.length > 0) nextState.observedUsageFieldSets.push(usageFieldSet);
|
|
465
427
|
}
|
|
466
|
-
|
|
467
428
|
const item = data.item;
|
|
468
429
|
const itemType = typeof item?.type === 'string' ? item.type : null;
|
|
469
430
|
if (itemType) nextState.itemTypeCounts[itemType] = (nextState.itemTypeCounts[itemType] || 0) + 1;
|
|
470
|
-
|
|
471
431
|
if ((eventType === 'item.completed' || eventType === 'item.updated') && itemType === 'agent_message' && typeof item.text === 'string' && item.text.trim()) {
|
|
472
432
|
nextState.resultSummary = item.text;
|
|
473
433
|
}
|
|
474
|
-
|
|
475
434
|
if ((eventType === 'item.completed' || eventType === 'item.updated') && itemType === 'reasoning' && typeof item.text === 'string' && item.text.trim()) {
|
|
476
435
|
nextState.reasoningSummaries.push(item.text);
|
|
477
436
|
}
|
|
478
|
-
|
|
479
437
|
if ((eventType === 'item.completed' || eventType === 'item.updated') && itemType === 'collab_tool_call' && item && typeof item === 'object') {
|
|
480
438
|
upsertCodexSubAgentCall(nextState.subAgentCalls, item, requestedModelId);
|
|
481
439
|
}
|
|
@@ -483,19 +441,15 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
483
441
|
if ((eventType === 'item.started' || eventType === 'item.updated' || eventType === 'item.completed') && itemType === 'command_execution' && item && typeof item === 'object') {
|
|
484
442
|
upsertCodexCommandExecution(nextState.commandExecutions, item);
|
|
485
443
|
}
|
|
486
|
-
|
|
487
444
|
if ((eventType === 'item.started' || eventType === 'item.updated' || eventType === 'item.completed') && itemType === 'file_change' && item && typeof item === 'object') {
|
|
488
445
|
upsertCodexFileChange(nextState.fileChanges, item);
|
|
489
446
|
}
|
|
490
|
-
|
|
491
447
|
if ((eventType === 'item.started' || eventType === 'item.updated' || eventType === 'item.completed') && itemType === 'mcp_tool_call' && item && typeof item === 'object') {
|
|
492
448
|
upsertCodexMcpToolCall(nextState.mcpToolCalls, item);
|
|
493
449
|
}
|
|
494
|
-
|
|
495
450
|
if ((eventType === 'item.started' || eventType === 'item.updated' || eventType === 'item.completed') && itemType === 'web_search' && item && typeof item === 'object') {
|
|
496
451
|
upsertCodexWebSearch(nextState.webSearches, item);
|
|
497
452
|
}
|
|
498
|
-
|
|
499
453
|
if ((eventType === 'item.started' || eventType === 'item.updated' || eventType === 'item.completed') && itemType === 'todo_list' && item && typeof item === 'object') {
|
|
500
454
|
upsertCodexTodoList(nextState.todoLists, item);
|
|
501
455
|
}
|
|
@@ -504,15 +458,12 @@ export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId =
|
|
|
504
458
|
upsertCodexItemError(nextState.itemErrors, item);
|
|
505
459
|
}
|
|
506
460
|
}
|
|
507
|
-
|
|
508
461
|
rebuildCodexSubSessionsFromCompactifications(nextState.tokenUsage);
|
|
509
462
|
nextState.observedModelDiagnosticPaths = [...observedModelPaths];
|
|
510
463
|
return nextState;
|
|
511
464
|
};
|
|
512
|
-
|
|
513
465
|
export const buildCodexResultModelUsage = (modelId, tokenUsage, pricingInfo = null) => {
|
|
514
466
|
if (!modelId || !tokenUsage) return null;
|
|
515
|
-
|
|
516
467
|
return {
|
|
517
468
|
[modelId]: {
|
|
518
469
|
inputTokens: tokenUsage.inputTokens || 0,
|
|
@@ -527,7 +478,6 @@ export const buildCodexResultModelUsage = (modelId, tokenUsage, pricingInfo = nu
|
|
|
527
478
|
},
|
|
528
479
|
};
|
|
529
480
|
};
|
|
530
|
-
|
|
531
481
|
const toCost = (tokens, pricePerMillion) => {
|
|
532
482
|
if (!Number.isFinite(tokens) || !Number.isFinite(pricePerMillion)) return 0;
|
|
533
483
|
return new Decimal(tokens).mul(pricePerMillion).div(1_000_000).toNumber();
|
|
@@ -542,16 +492,13 @@ const buildCodexPricingFallback = (modelId, tokenUsage, error = null) => ({
|
|
|
542
492
|
totalCostUSD: null,
|
|
543
493
|
error,
|
|
544
494
|
});
|
|
545
|
-
|
|
546
495
|
export const calculateCodexPricingFromModelInfo = (modelId, tokenUsage, modelInfo) => {
|
|
547
496
|
if (!modelId) return null;
|
|
548
497
|
if (!tokenUsage) return buildCodexPricingFallback(modelId, null);
|
|
549
498
|
if (!modelInfo?.cost) return buildCodexPricingFallback(modelId, tokenUsage, 'Model pricing not found in models.dev API');
|
|
550
|
-
|
|
551
499
|
const standardCost = modelInfo.cost;
|
|
552
500
|
const usesLongContextPricing = !!standardCost.context_over_200k && (tokenUsage.peakContextUsage || 0) > CODEX_LONG_CONTEXT_PRICE_THRESHOLD;
|
|
553
501
|
const cost = usesLongContextPricing ? { ...standardCost, ...standardCost.context_over_200k } : standardCost;
|
|
554
|
-
|
|
555
502
|
const pricing = {
|
|
556
503
|
inputPerMillion: cost.input || 0,
|
|
557
504
|
outputPerMillion: cost.output || 0,
|
|
@@ -559,7 +506,6 @@ export const calculateCodexPricingFromModelInfo = (modelId, tokenUsage, modelInf
|
|
|
559
506
|
cacheWritePerMillion: cost.cache_write ?? cost.input ?? 0,
|
|
560
507
|
reasoningPerMillion: cost.reasoning || 0,
|
|
561
508
|
};
|
|
562
|
-
|
|
563
509
|
const breakdown = {
|
|
564
510
|
input: toCost(tokenUsage.inputTokens || 0, pricing.inputPerMillion),
|
|
565
511
|
output: toCost(tokenUsage.outputTokens || 0, pricing.outputPerMillion),
|
|
@@ -571,7 +517,6 @@ export const calculateCodexPricingFromModelInfo = (modelId, tokenUsage, modelInf
|
|
|
571
517
|
|
|
572
518
|
tokenUsage.contextLimit = tokenUsage.contextLimit || modelInfo.limit?.context || null;
|
|
573
519
|
tokenUsage.outputLimit = tokenUsage.outputLimit || modelInfo.limit?.output || null;
|
|
574
|
-
|
|
575
520
|
return {
|
|
576
521
|
modelId,
|
|
577
522
|
modelName: modelInfo.name || modelId,
|
|
@@ -585,7 +530,6 @@ export const calculateCodexPricingFromModelInfo = (modelId, tokenUsage, modelInf
|
|
|
585
530
|
longContextThreshold: usesLongContextPricing ? CODEX_LONG_CONTEXT_PRICE_THRESHOLD : null,
|
|
586
531
|
};
|
|
587
532
|
};
|
|
588
|
-
|
|
589
533
|
export const calculateCodexPricing = async (modelId, tokenUsage) => {
|
|
590
534
|
if (!modelId) return null;
|
|
591
535
|
// Issue #2119: a Formal AI session is served by the local Link.Assistant
|
|
@@ -598,12 +542,10 @@ export const calculateCodexPricing = async (modelId, tokenUsage) => {
|
|
|
598
542
|
return buildCodexPricingFallback(modelId, tokenUsage, error.message);
|
|
599
543
|
}
|
|
600
544
|
};
|
|
601
|
-
|
|
602
545
|
// Function to validate Codex CLI connection
|
|
603
546
|
export const validateCodexConnection = async (model = defaultModels.codex, verbose = false) => {
|
|
604
547
|
// Map model alias to full ID
|
|
605
548
|
const mappedModel = mapModelToId(model);
|
|
606
|
-
|
|
607
549
|
// Retry configuration
|
|
608
550
|
const maxRetries = 3;
|
|
609
551
|
let retryCount = 0;
|
|
@@ -615,7 +557,6 @@ export const validateCodexConnection = async (model = defaultModels.codex, verbo
|
|
|
615
557
|
} else {
|
|
616
558
|
await log(`š Retry attempt ${retryCount}/${maxRetries} for Codex validation...`);
|
|
617
559
|
}
|
|
618
|
-
|
|
619
560
|
// Check if Codex CLI is installed and get version
|
|
620
561
|
try {
|
|
621
562
|
const versionResult = await $`timeout ${Math.floor(timeouts.codexCli / 1000)} codex --version`;
|
|
@@ -630,15 +571,12 @@ export const validateCodexConnection = async (model = defaultModels.codex, verbo
|
|
|
630
571
|
await log(`ā ļø Codex CLI version check failed (${versionError.code}), proceeding with connection test...`);
|
|
631
572
|
}
|
|
632
573
|
}
|
|
633
|
-
|
|
634
574
|
// Test basic Codex functionality with a simple "echo hi" command
|
|
635
575
|
// Using exec mode with JSON output for validation
|
|
636
576
|
const testResult = await $({ env: getCodexExecEnv(verbose) })`printf "echo hi" | timeout ${Math.floor(timeouts.codexCli / 1000)} codex exec --model ${mappedModel} --json --skip-git-repo-check -c model_reasoning_effort="none" --dangerously-bypass-approvals-and-sandbox`;
|
|
637
|
-
|
|
638
577
|
if (testResult.code !== 0) {
|
|
639
578
|
const stderr = testResult.stderr?.toString() || '';
|
|
640
579
|
const stdout = testResult.stdout?.toString() || '';
|
|
641
|
-
|
|
642
580
|
// Check for authentication errors in both stderr and stdout
|
|
643
581
|
// Codex CLI may return auth errors in JSON format on stdout
|
|
644
582
|
if (stderr.includes('auth') || stderr.includes('login') || stdout.includes('Not logged in') || stdout.includes('401 Unauthorized')) {
|
|
@@ -654,7 +592,6 @@ export const validateCodexConnection = async (model = defaultModels.codex, verbo
|
|
|
654
592
|
if (stdout && !stderr) await log(` Output: ${stdout.trim()}`, { level: 'error' });
|
|
655
593
|
return false;
|
|
656
594
|
}
|
|
657
|
-
|
|
658
595
|
// Success
|
|
659
596
|
await log('ā
Codex CLI connection validated successfully');
|
|
660
597
|
return true;
|
|
@@ -664,11 +601,9 @@ export const validateCodexConnection = async (model = defaultModels.codex, verbo
|
|
|
664
601
|
return false;
|
|
665
602
|
}
|
|
666
603
|
};
|
|
667
|
-
|
|
668
604
|
// Start the validation
|
|
669
605
|
return await attemptValidation();
|
|
670
606
|
};
|
|
671
|
-
|
|
672
607
|
// Function to handle Codex runtime switching (if applicable)
|
|
673
608
|
export const handleCodexRuntimeSwitch = async () => {
|
|
674
609
|
// Codex is typically run as a CLI tool, runtime switching may not be applicable
|
|
@@ -678,11 +613,9 @@ export const handleCodexRuntimeSwitch = async () => {
|
|
|
678
613
|
|
|
679
614
|
/** Check if Playwright MCP is available and connected to Codex @returns {Promise<boolean>} */
|
|
680
615
|
export const checkPlaywrightMcpAvailability = ensureCodexPlaywrightMcpServer;
|
|
681
|
-
|
|
682
616
|
// Main function to execute Codex with prompts and settings
|
|
683
617
|
export const executeCodex = async params => {
|
|
684
618
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, codexPath = 'codex', $ } = params;
|
|
685
|
-
|
|
686
619
|
if (argv.promptSubagentsViaAgentCommander) {
|
|
687
620
|
try {
|
|
688
621
|
await $`which start-agent`;
|
|
@@ -692,13 +625,11 @@ export const executeCodex = async params => {
|
|
|
692
625
|
await log('ā ļø agent-commander not installed; prompt guidance will be skipped (npm i -g @link-assistant/agent-commander)');
|
|
693
626
|
}
|
|
694
627
|
}
|
|
695
|
-
|
|
696
628
|
// Import prompt building functions from codex.prompts.lib.mjs
|
|
697
629
|
const { buildUserPrompt, buildSystemPrompt } = await import('./codex.prompts.lib.mjs');
|
|
698
630
|
const { checkModelVisionCapability } = await import('./claude.lib.mjs');
|
|
699
631
|
const mappedModel = mapModelToId(argv.model);
|
|
700
632
|
const modelSupportsVision = await checkModelVisionCapability(mappedModel);
|
|
701
|
-
|
|
702
633
|
if (argv.verbose) {
|
|
703
634
|
await log(`šļø Model vision capability: ${modelSupportsVision ? 'supported' : 'not supported'}`, { verbose: true });
|
|
704
635
|
}
|
|
@@ -721,7 +652,6 @@ export const executeCodex = async params => {
|
|
|
721
652
|
repo,
|
|
722
653
|
argv,
|
|
723
654
|
});
|
|
724
|
-
|
|
725
655
|
// Build the system prompt
|
|
726
656
|
const systemPrompt = buildSystemPrompt({
|
|
727
657
|
owner,
|
|
@@ -736,7 +666,6 @@ export const executeCodex = async params => {
|
|
|
736
666
|
argv,
|
|
737
667
|
modelSupportsVision,
|
|
738
668
|
});
|
|
739
|
-
|
|
740
669
|
// Log prompt details in verbose mode
|
|
741
670
|
if (argv.verbose) {
|
|
742
671
|
await log('\nš Final prompt structure:', { verbose: true });
|
|
@@ -745,7 +674,6 @@ export const executeCodex = async params => {
|
|
|
745
674
|
if (feedbackLines && feedbackLines.length > 0) {
|
|
746
675
|
await log(' Feedback info: Included', { verbose: true });
|
|
747
676
|
}
|
|
748
|
-
|
|
749
677
|
if (argv.dryRun) {
|
|
750
678
|
await log('\nš User prompt content:', { verbose: true });
|
|
751
679
|
await log('---BEGIN USER PROMPT---', { verbose: true });
|
|
@@ -757,7 +685,6 @@ export const executeCodex = async params => {
|
|
|
757
685
|
await log('---END SYSTEM PROMPT---', { verbose: true });
|
|
758
686
|
}
|
|
759
687
|
}
|
|
760
|
-
|
|
761
688
|
// Issue #1877: deploy the experimental HANDOFF.md Agent Skill so Codex loads
|
|
762
689
|
// it natively from .agents/skills/handoff/SKILL.md (no-op unless --use-handoff).
|
|
763
690
|
await deployHandoffSkill({ tempDir, argv, log, $ });
|
|
@@ -789,15 +716,12 @@ export const executeCodex = async params => {
|
|
|
789
716
|
|
|
790
717
|
export const executeCodexCommand = async params => {
|
|
791
718
|
const { tempDir, branchName, prompt, systemPrompt, argv, log, formatAligned, getResourceSnapshot, forkedRepo, feedbackLines, codexPath, $, owner, repo, prNumber, capabilityPreflight, calculatePricing = calculateCodexPricing, waitForRetryDelay = waitWithCountdown } = params;
|
|
792
|
-
|
|
793
719
|
const shellQuote = value => `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
794
720
|
const expectedBaseBranch = String(argv?.baseBranch || '').trim();
|
|
795
|
-
|
|
796
721
|
// Retry configuration
|
|
797
722
|
let retryCount = 0;
|
|
798
723
|
let baseBranchInterventionPrompt = null;
|
|
799
724
|
let baseBranchInterventionResumeCount = 0;
|
|
800
|
-
|
|
801
725
|
const executeWithRetry = async () => {
|
|
802
726
|
// Execute codex command from the cloned repository directory
|
|
803
727
|
if (retryCount === 0) {
|
|
@@ -805,7 +729,6 @@ export const executeCodexCommand = async params => {
|
|
|
805
729
|
} else {
|
|
806
730
|
await log(`\n${formatAligned('š', 'Retry attempt:', `${retryCount}/${retryLimits.maxTransientErrorRetries}`)}`);
|
|
807
731
|
}
|
|
808
|
-
|
|
809
732
|
if (argv.verbose) {
|
|
810
733
|
await log(` Model: ${argv.model}`, { verbose: true });
|
|
811
734
|
await log(` Working directory: ${tempDir}`, { verbose: true });
|
|
@@ -824,7 +747,6 @@ export const executeCodexCommand = async params => {
|
|
|
824
747
|
await log('š System resources before execution:', { verbose: true });
|
|
825
748
|
await log(` Memory: ${resourcesBefore.memory.split('\n')[1]}`, { verbose: true });
|
|
826
749
|
await log(` Load: ${resourcesBefore.load}`, { verbose: true });
|
|
827
|
-
|
|
828
750
|
let execCommand;
|
|
829
751
|
const mappedModel = mapModelToId(argv.model);
|
|
830
752
|
const { reasoningEffort, source: reasoningEffortSource, rolloutTokenBudget } = resolveCodexReasoningEffort(argv);
|
|
@@ -838,18 +760,15 @@ export const executeCodexCommand = async params => {
|
|
|
838
760
|
// Issue #2130: "run codex login" is wrong advice for a Formal-AI-served model.
|
|
839
761
|
const codexAuthRemedyLines = buildAuthRemedyLines({ model: argv.model, vendorRemedy: 'Please run: codex login' });
|
|
840
762
|
Object.assign(codexEnv, toolInvocation.env);
|
|
841
|
-
|
|
842
763
|
// For Codex, we combine system and user prompts into a single message
|
|
843
764
|
// Codex doesn't have separate system prompt support in CLI mode
|
|
844
765
|
const promptForAttempt = baseBranchInterventionPrompt ? `${prompt}\n\n${baseBranchInterventionPrompt}\n` : prompt;
|
|
845
766
|
const combinedPrompt = systemPrompt ? `${systemPrompt}\n\n${promptForAttempt}` : promptForAttempt;
|
|
846
|
-
|
|
847
767
|
// Write the combined prompt to a file for piping
|
|
848
768
|
// Use OS temporary directory instead of repository workspace to avoid polluting the repo
|
|
849
769
|
const promptFile = path.join(os.tmpdir(), `codex_prompt_${Date.now()}_${process.pid}.txt`);
|
|
850
770
|
const lastMessageFile = path.join(os.tmpdir(), `codex_last_message_${Date.now()}_${process.pid}.txt`);
|
|
851
771
|
await fs.writeFile(promptFile, combinedPrompt);
|
|
852
|
-
|
|
853
772
|
await log(` Resolved model ID: ${mappedModel}`, { verbose: true });
|
|
854
773
|
await log(` Execution mode: ${isResumeMode ? 'resume' : 'new exec'}`, { verbose: true });
|
|
855
774
|
await log(` Prompt file: ${promptFile}`, { verbose: true });
|
|
@@ -874,7 +793,6 @@ export const executeCodexCommand = async params => {
|
|
|
874
793
|
// Issue #2027: pair GPT-5.6 Sol's multi-agent `ultra` effort with a rollout token budget cap so it stays predictable and does not run away on cost.
|
|
875
794
|
if (rolloutTokenBudget) codexArgs += ` -c ${shellQuote(`rollout_token_budget=${rolloutTokenBudget}`)}`;
|
|
876
795
|
codexArgs += ' --dangerously-bypass-approvals-and-sandbox';
|
|
877
|
-
|
|
878
796
|
// Issue #1706: Append --disable-1m-context and --sub-session-size as Codex -c overrides.
|
|
879
797
|
let parsedSubSessionSize;
|
|
880
798
|
try {
|
|
@@ -904,14 +822,11 @@ export const executeCodexCommand = async params => {
|
|
|
904
822
|
if (disable1mArgs.length) await log(`š Codex --disable-1m-context: ${disable1mArgs.join(' ')}`, { verbose: true });
|
|
905
823
|
if (subSessionSizeArgs.length) await log(`š Codex --sub-session-size: ${subSessionSizeArgs.join(' ')}`, { verbose: true });
|
|
906
824
|
}
|
|
907
|
-
|
|
908
825
|
// Issue #2130: re-export the Formal AI environment inside the `sh -lc` script so a
|
|
909
826
|
// stale `formal-ai with --global` block in the operator profile cannot override it.
|
|
910
827
|
const fullCommand = `(${buildFormalAiEnvExports(toolInvocation.env)}cd ${shellQuote(tempDir)} && cat ${shellQuote(promptFile)} | ${toolInvocation.displayCommand} ${codexArgs})`;
|
|
911
|
-
|
|
912
828
|
const preparedResult = await logPreparedToolCommand({ argv, fullCommand, log, formatAligned });
|
|
913
829
|
if (preparedResult) return preparedResult;
|
|
914
|
-
|
|
915
830
|
try {
|
|
916
831
|
let interactiveHandler = null;
|
|
917
832
|
if (argv.interactiveMode && owner && repo && prNumber) {
|
|
@@ -936,7 +851,6 @@ export const executeCodexCommand = async params => {
|
|
|
936
851
|
mirror: false,
|
|
937
852
|
env: codexEnv,
|
|
938
853
|
})`sh -lc ${fullCommand}`;
|
|
939
|
-
|
|
940
854
|
await log(`${formatAligned('š', 'Command details:', '')}`);
|
|
941
855
|
await log(formatAligned('š', 'Working directory:', tempDir, 2));
|
|
942
856
|
await log(formatAligned('šæ', 'Branch:', branchName, 2));
|
|
@@ -945,9 +859,7 @@ export const executeCodexCommand = async params => {
|
|
|
945
859
|
if (argv.fork && forkedRepo) {
|
|
946
860
|
await log(formatAligned('š“', 'Fork:', forkedRepo, 2));
|
|
947
861
|
}
|
|
948
|
-
|
|
949
862
|
await log(`\n${formatAligned('ā¶ļø', 'Streaming output:', '')}\n`);
|
|
950
|
-
|
|
951
863
|
let exitCode = 0;
|
|
952
864
|
let sessionId = null;
|
|
953
865
|
let limitReached = false;
|
|
@@ -989,7 +901,6 @@ export const executeCodexCommand = async params => {
|
|
|
989
901
|
telemetryEventCounts: {},
|
|
990
902
|
turnLifecycle: [],
|
|
991
903
|
};
|
|
992
|
-
|
|
993
904
|
// Issue #2119: a process chunk boundary can fall in the middle of an
|
|
994
905
|
// NDJSON record. Parsing each raw chunk dropped both halves of a split
|
|
995
906
|
// record (token usage, session id, auth errors). Buffer whole lines so
|
|
@@ -1005,10 +916,8 @@ export const executeCodexCommand = async params => {
|
|
|
1005
916
|
}
|
|
1006
917
|
lastMessage = raw;
|
|
1007
918
|
const output = codexStdoutLines.write(raw);
|
|
1008
|
-
|
|
1009
919
|
codexJsonState = parseCodexExecJsonOutput(output, codexJsonState, mappedModel, { source: 'stdout' });
|
|
1010
920
|
await baseBranchCommandIntervention.handleCommandExecutions(codexJsonState.commandExecutions);
|
|
1011
|
-
|
|
1012
921
|
if (interactiveHandler || progressMonitor) {
|
|
1013
922
|
for (const rawLine of output.split('\n')) {
|
|
1014
923
|
const line = rawLine.trim();
|
|
@@ -1025,12 +934,10 @@ export const executeCodexCommand = async params => {
|
|
|
1025
934
|
}
|
|
1026
935
|
}
|
|
1027
936
|
}
|
|
1028
|
-
|
|
1029
937
|
if (codexJsonState.sessionId && codexJsonState.sessionId !== sessionId) {
|
|
1030
938
|
sessionId = codexJsonState.sessionId;
|
|
1031
939
|
await log(`š Session ID: ${sessionId}`);
|
|
1032
940
|
}
|
|
1033
|
-
|
|
1034
941
|
if (codexJsonState.resultSummary) {
|
|
1035
942
|
lastTextContent = codexJsonState.resultSummary;
|
|
1036
943
|
}
|
|
@@ -1042,7 +949,6 @@ export const executeCodexCommand = async params => {
|
|
|
1042
949
|
for (const line of codexAuthRemedyLines) await log(line, { level: 'error' });
|
|
1043
950
|
}
|
|
1044
951
|
}
|
|
1045
|
-
|
|
1046
952
|
if (chunk.type === 'stderr') {
|
|
1047
953
|
const rawError = chunk.data.toString();
|
|
1048
954
|
if (rawError && argv.verbose) {
|
|
@@ -1056,7 +962,6 @@ export const executeCodexCommand = async params => {
|
|
|
1056
962
|
exitCode = chunk.code;
|
|
1057
963
|
}
|
|
1058
964
|
}
|
|
1059
|
-
|
|
1060
965
|
// Release any line that was still being assembled when the stream ended.
|
|
1061
966
|
for (const [source, remaining] of [
|
|
1062
967
|
['stdout', codexStdoutLines.flush()],
|
|
@@ -1066,7 +971,6 @@ export const executeCodexCommand = async params => {
|
|
|
1066
971
|
codexJsonState = parseCodexExecJsonOutput(remaining, codexJsonState, mappedModel, { source });
|
|
1067
972
|
await baseBranchCommandIntervention.handleCommandExecutions(codexJsonState.commandExecutions);
|
|
1068
973
|
}
|
|
1069
|
-
|
|
1070
974
|
if (codexJsonState.sessionId && codexJsonState.sessionId !== sessionId) {
|
|
1071
975
|
sessionId = codexJsonState.sessionId;
|
|
1072
976
|
await log(`š Session ID: ${sessionId}`);
|
|
@@ -1084,7 +988,6 @@ export const executeCodexCommand = async params => {
|
|
|
1084
988
|
if (interactiveHandler) {
|
|
1085
989
|
await interactiveHandler.flush();
|
|
1086
990
|
}
|
|
1087
|
-
|
|
1088
991
|
// Issue #2130: a failed run legitimately has no final message and no
|
|
1089
992
|
// turn.completed usage, so those outcomes must not be logged as warnings.
|
|
1090
993
|
const runFailed = codexRunAlreadyFailed({ state: codexJsonState, exitCode });
|
|
@@ -1101,11 +1004,9 @@ export const executeCodexCommand = async params => {
|
|
|
1101
1004
|
await log(lastMessageFromFile, { verbose: true });
|
|
1102
1005
|
lastTextContent = lastTextContent || lastMessageFromFile;
|
|
1103
1006
|
}
|
|
1104
|
-
|
|
1105
1007
|
for (const line of buildCodexRunDiagnostics({ state: codexJsonState, exitCode, mappedModel })) {
|
|
1106
1008
|
await log(line.message, line.options);
|
|
1107
1009
|
}
|
|
1108
|
-
|
|
1109
1010
|
const baseBranchIntervention = baseBranchCommandIntervention.getIntervention();
|
|
1110
1011
|
if (baseBranchIntervention) {
|
|
1111
1012
|
if ((sessionId || argv.resume) && baseBranchInterventionResumeCount < 1) {
|
|
@@ -1115,7 +1016,6 @@ export const executeCodexCommand = async params => {
|
|
|
1115
1016
|
await log('\nš Resuming Codex with requested base-branch correction prompt...');
|
|
1116
1017
|
return await executeWithRetry();
|
|
1117
1018
|
}
|
|
1118
|
-
|
|
1119
1019
|
return {
|
|
1120
1020
|
success: false,
|
|
1121
1021
|
sessionId,
|
|
@@ -1157,17 +1057,14 @@ export const executeCodexCommand = async params => {
|
|
|
1157
1057
|
resultSummary: lastTextContent || null,
|
|
1158
1058
|
...outcome,
|
|
1159
1059
|
});
|
|
1160
|
-
|
|
1161
1060
|
// Check for authentication errors first - these should never be retried
|
|
1162
1061
|
if (authError) {
|
|
1163
1062
|
await logCodexResourceSnapshot({ getResourceSnapshot, log });
|
|
1164
|
-
|
|
1165
1063
|
// Throw an error to stop retries and propagate the auth failure
|
|
1166
1064
|
const error = new Error(`Codex authentication failed - 401 Unauthorized.${codexAuthRemedyLines.map(line => ` ${line.replace(/^\s*š”\s*/, '')}`).join('')}`);
|
|
1167
1065
|
error.isAuthError = true;
|
|
1168
1066
|
throw error;
|
|
1169
1067
|
}
|
|
1170
|
-
|
|
1171
1068
|
const codexErrorSummary = getCodexErrorEventSummary(codexJsonState);
|
|
1172
1069
|
if (codexErrorSummary.ignoredEvents.length > 0) {
|
|
1173
1070
|
const ignoredMessages = [...new Set(codexErrorSummary.ignoredEvents.map(event => event.message))].join('; ');
|
|
@@ -1191,7 +1088,6 @@ export const executeCodexCommand = async params => {
|
|
|
1191
1088
|
await log(`š Parsed reset time: ${JSON.stringify(limitInfo.resetTime)}, timezone: ${JSON.stringify(limitInfo.timezone)}`, { verbose: true });
|
|
1192
1089
|
limitReached = true;
|
|
1193
1090
|
limitResetTime = limitInfo.resetTime;
|
|
1194
|
-
|
|
1195
1091
|
// Issue #942: build proper solve resume command (preserves tool/model/dir).
|
|
1196
1092
|
const solveResumeCmd = __codexBuildSolveResumeCmd(argv, sessionId, tempDir);
|
|
1197
1093
|
const messageLines = formatUsageLimitMessage({
|
|
@@ -1225,12 +1121,9 @@ export const executeCodexCommand = async params => {
|
|
|
1225
1121
|
await log(`\n\nā Codex emitted error event: ${codexErrorSummary.message}`, { level: 'error' });
|
|
1226
1122
|
await log(` Error events: item=${codexErrorSummary.counts.item}, turn=${codexErrorSummary.counts.turn}, stream=${codexErrorSummary.counts.stream}`, { level: 'error' });
|
|
1227
1123
|
}
|
|
1228
|
-
|
|
1229
1124
|
await logCodexResourceSnapshot({ getResourceSnapshot, log });
|
|
1230
|
-
|
|
1231
1125
|
return buildRunResult({ success: false, errorInfo: codexErrorSummary, result: codexErrorSummary.message });
|
|
1232
1126
|
}
|
|
1233
|
-
|
|
1234
1127
|
if (exitCode !== 0) {
|
|
1235
1128
|
const retryableError = classifyRetryableError(lastMessage);
|
|
1236
1129
|
if (retryableError.isRetryable) {
|
|
@@ -1251,7 +1144,6 @@ export const executeCodexCommand = async params => {
|
|
|
1251
1144
|
}
|
|
1252
1145
|
await log(`\n\nā ${retryableError.label} persisted after ${maxRetries} retries`, { level: 'error' });
|
|
1253
1146
|
}
|
|
1254
|
-
|
|
1255
1147
|
// Check for usage limit errors first (more specific)
|
|
1256
1148
|
const limitInfo = detectUsageLimit(lastMessage);
|
|
1257
1149
|
if (limitInfo.isUsageLimit) {
|
|
@@ -1270,7 +1162,6 @@ export const executeCodexCommand = async params => {
|
|
|
1270
1162
|
sessionId,
|
|
1271
1163
|
solveResumeCommand: solveResumeCmd,
|
|
1272
1164
|
});
|
|
1273
|
-
|
|
1274
1165
|
for (const line of messageLines) {
|
|
1275
1166
|
await log(line, { level: 'warning' });
|
|
1276
1167
|
}
|
|
@@ -1279,12 +1170,9 @@ export const executeCodexCommand = async params => {
|
|
|
1279
1170
|
} else {
|
|
1280
1171
|
await log(`\n\nā Codex command failed with exit code ${exitCode}`, { level: 'error' });
|
|
1281
1172
|
}
|
|
1282
|
-
|
|
1283
1173
|
await logCodexResourceSnapshot({ getResourceSnapshot, log });
|
|
1284
|
-
|
|
1285
1174
|
return buildRunResult({ success: false, errorInfo: getCodexErrorEventSummary(codexJsonState) });
|
|
1286
1175
|
}
|
|
1287
|
-
|
|
1288
1176
|
// Issue #2102: a rejected `request_plugin_install` means codex asked for a
|
|
1289
1177
|
// capability the preflight did not provision, and under `codex exec` that
|
|
1290
1178
|
// request can never succeed ā so a run that produced nothing is blocked,
|
|
@@ -1297,7 +1185,6 @@ export const executeCodexCommand = async params => {
|
|
|
1297
1185
|
|
|
1298
1186
|
return buildRunResult({ success: false, errorInfo: getCodexErrorEventSummary(codexJsonState), pluginProvisioning, result: [pluginProvisioning.message, ...pluginProvisioning.guidance].join(' ') });
|
|
1299
1187
|
}
|
|
1300
|
-
|
|
1301
1188
|
// Issue #1990: exit code 0 and the absence of a fatal codex error event are
|
|
1302
1189
|
// necessary but NOT sufficient for success. Verify the run actually
|
|
1303
1190
|
// completed its turn before declaring success. A broken-but-exit-0 run (the
|
|
@@ -1307,7 +1194,6 @@ export const executeCodexCommand = async params => {
|
|
|
1307
1194
|
const completionHealth = getCodexCompletionHealth(codexJsonState, { lastMessage });
|
|
1308
1195
|
if (!completionHealth.healthy) {
|
|
1309
1196
|
await reportCodexCompletionFailure({ completionHealth, log, getResourceSnapshot });
|
|
1310
|
-
|
|
1311
1197
|
// Issue #1990: preserve the codex session so an outer full restart can
|
|
1312
1198
|
// resume with context (mirrors the transient-error retry above and the
|
|
1313
1199
|
// `--tool claude` behavior). We do NOT inline-retry within the same broken
|
|
@@ -1315,10 +1201,8 @@ export const executeCodexCommand = async params => {
|
|
|
1315
1201
|
// docker isolation) the container filesystem are preserved for a clean
|
|
1316
1202
|
// restart at the orchestration level.
|
|
1317
1203
|
if (sessionId && !argv.resume) argv.resume = sessionId;
|
|
1318
|
-
|
|
1319
1204
|
return buildRunResult({ success: false, errorInfo: getCodexErrorEventSummary(codexJsonState), completionHealth, pluginProvisioning, incompleteSession: completionHealth.incompleteSession, diskPressureDetected: completionHealth.diskPressureDetected, result: completionHealth.reasons.join(' ') });
|
|
1320
1205
|
}
|
|
1321
|
-
|
|
1322
1206
|
await log('\n\nā
Codex command completed');
|
|
1323
1207
|
|
|
1324
1208
|
// Issue #1263: Log if result summary was captured
|
|
@@ -1327,7 +1211,6 @@ export const executeCodexCommand = async params => {
|
|
|
1327
1211
|
} else {
|
|
1328
1212
|
await log('ā ļø No result summary captured from Codex output or last-message file', { level: 'warning', verbose: true });
|
|
1329
1213
|
}
|
|
1330
|
-
|
|
1331
1214
|
return buildRunResult({ success: true, pluginProvisioning });
|
|
1332
1215
|
} catch (error) {
|
|
1333
1216
|
// Don't report auth errors to Sentry as they are user configuration issues
|
|
@@ -1339,14 +1222,11 @@ export const executeCodexCommand = async params => {
|
|
|
1339
1222
|
operation: 'run_codex_command',
|
|
1340
1223
|
});
|
|
1341
1224
|
}
|
|
1342
|
-
|
|
1343
1225
|
await log(`\n\nā Error executing Codex command: ${error.message}`, { level: 'error' });
|
|
1344
|
-
|
|
1345
1226
|
// Re-throw auth errors to stop any outer retry loops
|
|
1346
1227
|
if (error.isAuthError) {
|
|
1347
1228
|
throw error;
|
|
1348
1229
|
}
|
|
1349
|
-
|
|
1350
1230
|
return {
|
|
1351
1231
|
success: false,
|
|
1352
1232
|
sessionId: null,
|
|
@@ -1369,7 +1249,6 @@ export const executeCodexCommand = async params => {
|
|
|
1369
1249
|
// Start the execution with retry logic
|
|
1370
1250
|
return await executeWithRetry();
|
|
1371
1251
|
};
|
|
1372
|
-
|
|
1373
1252
|
export const checkForUncommittedChanges = async (tempDir, owner, repo, branchName, $, log, autoCommit = false, autoRestartEnabled = true) => {
|
|
1374
1253
|
// Similar to Claude and OpenCode version, check for uncommitted changes
|
|
1375
1254
|
await log('\nš Checking for uncommitted changes...');
|
|
@@ -1377,17 +1256,14 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
1377
1256
|
await ensureAiToolScratchIgnored(tempDir, log);
|
|
1378
1257
|
try {
|
|
1379
1258
|
const gitStatusResult = await $({ cwd: tempDir })`git status --porcelain 2>&1`;
|
|
1380
|
-
|
|
1381
1259
|
if (gitStatusResult.code === 0) {
|
|
1382
1260
|
const statusOutput = filterAiToolScratchFromStatus(gitStatusResult.stdout.toString().trim());
|
|
1383
|
-
|
|
1384
1261
|
if (statusOutput) {
|
|
1385
1262
|
await log('š Found uncommitted changes');
|
|
1386
1263
|
await log('Changes:');
|
|
1387
1264
|
for (const line of statusOutput.split('\n')) {
|
|
1388
1265
|
await log(` ${line}`);
|
|
1389
1266
|
}
|
|
1390
|
-
|
|
1391
1267
|
if (autoCommit) {
|
|
1392
1268
|
await log('š¾ Auto-committing changes (--auto-commit-uncommitted-changes is enabled)...');
|
|
1393
1269
|
|
|
@@ -1395,12 +1271,9 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
1395
1271
|
if (addResult.code === 0) {
|
|
1396
1272
|
const commitMessage = 'Auto-commit: Changes made by Codex during problem-solving session';
|
|
1397
1273
|
const commitResult = await $({ cwd: tempDir })`git commit -m ${commitMessage}`;
|
|
1398
|
-
|
|
1399
1274
|
if (commitResult.code === 0) {
|
|
1400
1275
|
await log('ā
Changes committed successfully');
|
|
1401
|
-
|
|
1402
1276
|
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
1403
|
-
|
|
1404
1277
|
if (pushResult.code === 0) {
|
|
1405
1278
|
await log('ā
Changes pushed successfully');
|
|
1406
1279
|
} else {
|
|
@@ -1455,7 +1328,6 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
1455
1328
|
return false;
|
|
1456
1329
|
}
|
|
1457
1330
|
};
|
|
1458
|
-
|
|
1459
1331
|
// Export all functions as default object too
|
|
1460
1332
|
export default {
|
|
1461
1333
|
validateCodexConnection,
|