ai-foreman 1.7.8 → 1.7.10
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/README.md +1 -11
- package/dist/activity.js +2 -2
- package/dist/adapters/claude.d.ts +15 -29
- package/dist/adapters/claude.js +90 -334
- package/dist/adapters/codex.d.ts +15 -18
- package/dist/adapters/codex.js +112 -195
- package/dist/adapters/recovering.d.ts +6 -6
- package/dist/adapters/recovering.js +6 -9
- package/dist/adapters/types.d.ts +37 -66
- package/dist/branch/currentGuard.d.ts +6 -5
- package/dist/branch/currentGuard.js +5 -6
- package/dist/branch/runner.d.ts +2 -0
- package/dist/branch/runner.js +2 -1
- package/dist/cli/start.js +95 -165
- package/dist/continuity.d.ts +7 -8
- package/dist/continuity.js +27 -72
- package/dist/foreman.d.ts +7 -1
- package/dist/foreman.js +25 -1
- package/dist/handoffs.js +3 -223
- package/dist/qaReview.d.ts +2 -0
- package/dist/qaReview.js +1 -4
- package/dist/sessionLifecycle.d.ts +22 -108
- package/dist/sessionLifecycle.js +322 -1023
- package/dist/statusReporter.d.ts +6 -27
- package/dist/statusReporter.js +21 -76
- package/dist/workflowDb.d.ts +10 -47
- package/dist/workflowDb.js +41 -146
- package/package.json +4 -4
package/dist/adapters/codex.js
CHANGED
|
@@ -23,7 +23,6 @@ export function parseCodexLine(raw) {
|
|
|
23
23
|
return { events: [{ kind: "error", message: String(raw.error?.message ?? JSON.stringify(raw)) }] };
|
|
24
24
|
return { events: [] };
|
|
25
25
|
}
|
|
26
|
-
const CODEX_COMPACTION_TIMEOUT_MS = 120_000;
|
|
27
26
|
/** Persistent JSON-RPC controller for one live Codex thread. */
|
|
28
27
|
export class CodexAdapter {
|
|
29
28
|
opts;
|
|
@@ -41,21 +40,17 @@ export class CodexAdapter {
|
|
|
41
40
|
activeText = [];
|
|
42
41
|
usage;
|
|
43
42
|
usageRevision = 0;
|
|
44
|
-
contextSequence = 0;
|
|
45
43
|
providerSessionUsage;
|
|
46
44
|
lastTurnTokens;
|
|
47
45
|
stderr = "";
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
compactionSequence = 0;
|
|
57
|
-
activeCompaction;
|
|
58
|
-
manualCompactionPending = false;
|
|
46
|
+
nativeAutoCompactTokenLimit;
|
|
47
|
+
autoCompactionPrepared = false;
|
|
48
|
+
preparedAutoCompactThreshold;
|
|
49
|
+
preparedAutoCompactionPolicy;
|
|
50
|
+
nativeCompactions = [];
|
|
51
|
+
nativeCompactionSequence = 0;
|
|
52
|
+
manualCompactionInFlight = false;
|
|
53
|
+
observedToolCalls = 0;
|
|
59
54
|
constructor(opts) {
|
|
60
55
|
this.opts = opts;
|
|
61
56
|
this._sessionId = opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId;
|
|
@@ -67,8 +62,8 @@ export class CodexAdapter {
|
|
|
67
62
|
}
|
|
68
63
|
buildAppServerArgs() {
|
|
69
64
|
const args = ["app-server", "--listen", "stdio://"];
|
|
70
|
-
if (this.
|
|
71
|
-
args.push("-c", `model_auto_compact_token_limit=${this.
|
|
65
|
+
if (this.nativeAutoCompactTokenLimit !== undefined) {
|
|
66
|
+
args.push("-c", `model_auto_compact_token_limit=${this.nativeAutoCompactTokenLimit}`, "-c", 'model_auto_compact_token_limit_scope="total"');
|
|
72
67
|
}
|
|
73
68
|
return args;
|
|
74
69
|
}
|
|
@@ -87,11 +82,9 @@ export class CodexAdapter {
|
|
|
87
82
|
return args;
|
|
88
83
|
}
|
|
89
84
|
async sendTurn(instruction) {
|
|
90
|
-
if (this.opts.contextManagementPolicy && !this.contextPrepared)
|
|
91
|
-
await this.prepareContextManagement(this.opts.contextManagementPolicy);
|
|
92
85
|
return withActivityPhase(`Codex ${activityPhase(this.opts.runtimePhase)}`, () => this.sendTurnInternal(instruction));
|
|
93
86
|
}
|
|
94
|
-
async sendTurnInternal(instruction
|
|
87
|
+
async sendTurnInternal(instruction) {
|
|
95
88
|
if (this.closed)
|
|
96
89
|
throw new Error("builder is closed");
|
|
97
90
|
this.eventQueue.push({ kind: "activity", state: "starting Codex turn", provider: "codex", model: this.opts.model });
|
|
@@ -100,49 +93,29 @@ export class CodexAdapter {
|
|
|
100
93
|
await this.ensureThread();
|
|
101
94
|
this.activeText = [];
|
|
102
95
|
this.lastTurnTokens = undefined;
|
|
103
|
-
this.setupTurnActive = setup;
|
|
104
|
-
this.setupToolAttempted = false;
|
|
105
96
|
const completion = this.waitFor("turn/completed", (params) => params.threadId === this._sessionId);
|
|
106
97
|
turnStartDispatched = true;
|
|
107
|
-
|
|
98
|
+
await this.request("turn/start", {
|
|
108
99
|
threadId: this._sessionId,
|
|
109
|
-
input: [{ type: "text", text:
|
|
100
|
+
input: [{ type: "text", text: this.buildInstruction(instruction), text_elements: [] }],
|
|
110
101
|
cwd: this.opts.cwd,
|
|
111
102
|
model: this.opts.model ?? null,
|
|
112
103
|
effort: this.opts.effort ?? (this.opts.fast ? "low" : null),
|
|
113
104
|
});
|
|
114
|
-
const startedTurn = started.turn;
|
|
115
|
-
this.activeTurnId = typeof startedTurn?.id === "string" ? startedTurn.id : undefined;
|
|
116
105
|
const params = await completion;
|
|
117
106
|
const turn = params.turn;
|
|
118
107
|
const failed = turn?.status === "failed";
|
|
119
|
-
const interrupted = turn?.status === "interrupted" || Boolean(this.boundaryInterrupt);
|
|
120
108
|
const error = turn?.error;
|
|
121
109
|
const text = this.activeText.join("\n");
|
|
122
110
|
const result = {
|
|
123
111
|
text: failed ? normalizeRuntimeErrorText("codex", String(error?.message ?? text), null, "app-server turn") : text,
|
|
124
112
|
isError: failed, numTurns: 1, costUsd: 0, costAuthoritative: false,
|
|
125
113
|
...(this.lastTurnTokens ?? {}),
|
|
126
|
-
...(interrupted ? { interrupted: { reason: "compaction-boundary", ...(this.boundaryInterrupt?.providerEventId ? { providerEventId: this.boundaryInterrupt.providerEventId } : {}) } } : {}),
|
|
127
114
|
};
|
|
128
|
-
if (setup && this.setupToolAttempted)
|
|
129
|
-
throw new Error("Codex context setup attempted to invoke a tool; native context capability was not established");
|
|
130
115
|
this.eventQueue.push({ kind: "turn-complete", result });
|
|
131
116
|
return result;
|
|
132
117
|
}
|
|
133
118
|
catch (error) {
|
|
134
|
-
if (this.boundaryInterrupt) {
|
|
135
|
-
const result = {
|
|
136
|
-
text: this.activeText.join("\n"), isError: false, numTurns: 1, costUsd: 0,
|
|
137
|
-
costAuthoritative: false, ...(this.lastTurnTokens ?? {}),
|
|
138
|
-
interrupted: {
|
|
139
|
-
reason: "compaction-boundary",
|
|
140
|
-
...(this.boundaryInterrupt.providerEventId ? { providerEventId: this.boundaryInterrupt.providerEventId } : {}),
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
this.eventQueue.push({ kind: "turn-complete", result });
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
119
|
if (error instanceof SessionUnavailableError) {
|
|
147
120
|
const result = sessionUnavailableResult(error);
|
|
148
121
|
this.eventQueue.push({ kind: "error", message: result.text });
|
|
@@ -164,87 +137,10 @@ export class CodexAdapter {
|
|
|
164
137
|
this.eventQueue.push({ kind: "turn-complete", result });
|
|
165
138
|
return result;
|
|
166
139
|
}
|
|
167
|
-
finally {
|
|
168
|
-
this.activeTurnId = undefined;
|
|
169
|
-
this.setupTurnActive = false;
|
|
170
|
-
this.boundaryInterrupt = undefined;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
async prepareContextManagement(policy) {
|
|
174
|
-
validateContextPolicy(policy);
|
|
175
|
-
this.contextPolicy = { ...policy };
|
|
176
|
-
this.compactionSequence = Math.max(this.compactionSequence, policy.providerSequenceStart ?? 0);
|
|
177
|
-
this.contextSequence = Math.max(this.contextSequence, policy.providerSequenceStart ?? 0);
|
|
178
|
-
if (policy.knownModelContextWindow && policy.knownModelContextWindow > 0) {
|
|
179
|
-
this.nativeTokenLimit = tokenCeiling(policy.knownModelContextWindow, policy.configuredThresholdPercent);
|
|
180
|
-
}
|
|
181
|
-
await this.ensureThread();
|
|
182
|
-
if (!this.usage) {
|
|
183
|
-
const setup = await this.sendTurnInternal("Rafi context setup only. Do not call tools or change files. Reply with exactly CONTEXT_READY.", true);
|
|
184
|
-
if (setup.isError || setup.text.trim() !== "CONTEXT_READY") {
|
|
185
|
-
throw new Error(`Codex context setup did not complete safely: ${setup.text.slice(0, 240)}`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
const maximum = this.usage?.maximum ?? policy.knownModelContextWindow;
|
|
189
|
-
if (!maximum || !Number.isFinite(maximum) || maximum <= 0) {
|
|
190
|
-
throw new Error("Codex did not expose modelContextWindow during context setup");
|
|
191
|
-
}
|
|
192
|
-
const configuredTokenLimit = tokenCeiling(maximum, policy.configuredThresholdPercent);
|
|
193
|
-
this.nativeTokenLimit = configuredTokenLimit;
|
|
194
|
-
if (this.spawnedNativeTokenLimit !== configuredTokenLimit) {
|
|
195
|
-
await this.restartTransport();
|
|
196
|
-
await this.ensureThread();
|
|
197
|
-
}
|
|
198
|
-
this.contextPrepared = true;
|
|
199
|
-
return {
|
|
200
|
-
modelContextWindow: maximum,
|
|
201
|
-
configuredTokenLimit,
|
|
202
|
-
installedNativeTokenLimit: configuredTokenLimit,
|
|
203
|
-
installedNativePercent: configuredTokenLimit / maximum * 100,
|
|
204
|
-
...(this.usage ? { sample: { ...this.usage } } : {}),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
async updateContextManagement(policy) {
|
|
208
|
-
validateContextPolicy(policy);
|
|
209
|
-
if (this.activeTurnId)
|
|
210
|
-
throw new Error("Codex native context control can be reconfigured only after the active turn settles");
|
|
211
|
-
const maximum = this.usage?.maximum ?? policy.knownModelContextWindow;
|
|
212
|
-
if (!maximum || maximum <= 0)
|
|
213
|
-
return this.prepareContextManagement(policy);
|
|
214
|
-
this.contextPolicy = { ...policy };
|
|
215
|
-
this.compactionSequence = Math.max(this.compactionSequence, policy.providerSequenceStart ?? 0);
|
|
216
|
-
this.contextSequence = Math.max(this.contextSequence, policy.providerSequenceStart ?? 0);
|
|
217
|
-
const configuredTokenLimit = tokenCeiling(maximum, policy.configuredThresholdPercent);
|
|
218
|
-
this.nativeTokenLimit = configuredTokenLimit;
|
|
219
|
-
if (this.spawnedNativeTokenLimit !== configuredTokenLimit) {
|
|
220
|
-
await this.restartTransport();
|
|
221
|
-
await this.ensureThread();
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
modelContextWindow: maximum,
|
|
225
|
-
configuredTokenLimit,
|
|
226
|
-
installedNativeTokenLimit: configuredTokenLimit,
|
|
227
|
-
installedNativePercent: configuredTokenLimit / maximum * 100,
|
|
228
|
-
...(this.usage ? { sample: { ...this.usage } } : {}),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
async interruptTurnAtCompactionBoundary(providerEventId) {
|
|
232
|
-
if (!this.activeTurnId || !this._sessionId)
|
|
233
|
-
return { ok: false, error: "no active Codex turn exists at the compaction boundary", providerEventId };
|
|
234
|
-
try {
|
|
235
|
-
this.boundaryInterrupt = { providerEventId };
|
|
236
|
-
await this.request("turn/interrupt", { threadId: this._sessionId, turnId: this.activeTurnId });
|
|
237
|
-
return { ok: true, providerEventId };
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
this.boundaryInterrupt = undefined;
|
|
241
|
-
return { ok: false, error: error instanceof Error ? error.message : String(error), providerEventId };
|
|
242
|
-
}
|
|
243
140
|
}
|
|
244
141
|
async compact() {
|
|
245
142
|
try {
|
|
246
143
|
await this.ensureThread();
|
|
247
|
-
this.manualCompactionPending = true;
|
|
248
144
|
this.eventQueue.push({ kind: "session-transition", transition: "compacting" });
|
|
249
145
|
const usageRevision = this.usageRevision;
|
|
250
146
|
// A pre-compaction observation must never be mistaken for proof that the
|
|
@@ -255,17 +151,23 @@ export class CodexAdapter {
|
|
|
255
151
|
const done = this.waitFor("item/completed", (params) => {
|
|
256
152
|
const item = params.item;
|
|
257
153
|
return params.threadId === this._sessionId && item?.type === "contextCompaction";
|
|
258
|
-
},
|
|
154
|
+
}, 30_000);
|
|
259
155
|
const postCompactUsage = this.waitFor("thread/tokenUsage/updated", (params) => {
|
|
260
156
|
return (params.threadId === undefined || params.threadId === this._sessionId)
|
|
261
157
|
&& this.usageRevision > usageRevision
|
|
262
158
|
&& this.usage !== undefined;
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
159
|
+
}, 30_000);
|
|
160
|
+
this.manualCompactionInFlight = true;
|
|
161
|
+
try {
|
|
162
|
+
await Promise.all([
|
|
163
|
+
this.request("thread/compact/start", { threadId: this._sessionId }),
|
|
164
|
+
done,
|
|
165
|
+
postCompactUsage,
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
this.manualCompactionInFlight = false;
|
|
170
|
+
}
|
|
269
171
|
this.eventQueue.push({ kind: "session-transition", transition: "compacted" });
|
|
270
172
|
return { ok: true };
|
|
271
173
|
}
|
|
@@ -274,11 +176,63 @@ export class CodexAdapter {
|
|
|
274
176
|
return { ok: false, error: error.message, failure: error.failure };
|
|
275
177
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
276
178
|
}
|
|
277
|
-
|
|
278
|
-
|
|
179
|
+
}
|
|
180
|
+
async prepareAutoCompaction(thresholdPercent = this.opts.autoCompactThresholdPercent) {
|
|
181
|
+
if (thresholdPercent === undefined)
|
|
182
|
+
return;
|
|
183
|
+
const threshold = validThreshold(thresholdPercent);
|
|
184
|
+
if (this.autoCompactionPrepared && this.preparedAutoCompactThreshold === threshold)
|
|
185
|
+
return this.preparedAutoCompactionPolicy;
|
|
186
|
+
this.opts.autoCompactThresholdPercent = threshold;
|
|
187
|
+
await this.ensureThread();
|
|
188
|
+
// Codex reports its model context window in token-usage notifications, not
|
|
189
|
+
// in thread/start. Establish the otherwise idle thread with a constrained
|
|
190
|
+
// setup turn, then restart the app server with the provider-native ceiling
|
|
191
|
+
// before any Builder or QA work is sent.
|
|
192
|
+
if (!this.usage?.maximum) {
|
|
193
|
+
const toolsBefore = this.observedToolCalls;
|
|
194
|
+
const setup = await this.sendTurnInternal("Rafi internal initialization only. Do not call tools or modify files. Reply briefly that the context is ready.");
|
|
195
|
+
if (setup.isError)
|
|
196
|
+
throw new Error(`Codex automatic-compaction setup failed: ${setup.text.slice(0, 240)}`);
|
|
197
|
+
if (this.observedToolCalls !== toolsBefore)
|
|
198
|
+
throw new Error("Codex automatic-compaction setup unexpectedly called a tool");
|
|
199
|
+
}
|
|
200
|
+
const maximum = this.usage?.maximum;
|
|
201
|
+
if (!maximum || !Number.isFinite(maximum) || maximum <= 0) {
|
|
202
|
+
throw new Error("Codex did not report a model context window during automatic-compaction setup");
|
|
279
203
|
}
|
|
204
|
+
this.nativeAutoCompactTokenLimit = tokenLimit(maximum, threshold);
|
|
205
|
+
await this.restartForAutoCompaction();
|
|
206
|
+
await this.ensureThread();
|
|
207
|
+
this.autoCompactionPrepared = true;
|
|
208
|
+
this.preparedAutoCompactThreshold = threshold;
|
|
209
|
+
this.preparedAutoCompactionPolicy = {
|
|
210
|
+
requestedThresholdPercent: threshold,
|
|
211
|
+
effectiveThresholdPercent: threshold,
|
|
212
|
+
modelContextWindow: maximum,
|
|
213
|
+
triggerTokens: this.nativeAutoCompactTokenLimit,
|
|
214
|
+
};
|
|
215
|
+
return this.preparedAutoCompactionPolicy;
|
|
216
|
+
}
|
|
217
|
+
autoCompactionPolicy() { return this.preparedAutoCompactionPolicy; }
|
|
218
|
+
drainNativeCompactions() {
|
|
219
|
+
const pending = this.nativeCompactions;
|
|
220
|
+
this.nativeCompactions = [];
|
|
221
|
+
return pending;
|
|
280
222
|
}
|
|
223
|
+
restoreNativeCompactions(compactions) { this.nativeCompactions.unshift(...compactions); }
|
|
281
224
|
async contextUsage() { return this.usage; }
|
|
225
|
+
async contextUsageAfterNativeCompaction(compaction) {
|
|
226
|
+
if (compaction.usageRevision === undefined || this.usageRevision > compaction.usageRevision)
|
|
227
|
+
return this.usage;
|
|
228
|
+
try {
|
|
229
|
+
await this.waitFor("thread/tokenUsage/updated", () => this.usageRevision > compaction.usageRevision, 5_000);
|
|
230
|
+
return this.usage;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
282
236
|
async sessionUsage() { return this.providerSessionUsage ? { ...this.providerSessionUsage } : undefined; }
|
|
283
237
|
async switchSettings(settings) {
|
|
284
238
|
this.opts.model = settings.model;
|
|
@@ -413,7 +367,6 @@ export class CodexAdapter {
|
|
|
413
367
|
this.stderr = "";
|
|
414
368
|
const executable = this.opts.runtimeExecutable ?? "codex";
|
|
415
369
|
const child = spawn(executable, this.buildAppServerArgs(), { cwd: this.opts.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
416
|
-
this.spawnedNativeTokenLimit = this.nativeTokenLimit;
|
|
417
370
|
this.process = child;
|
|
418
371
|
// A short-lived server may close stdin before its stderr/exit event. The
|
|
419
372
|
// close handler owns rejection so diagnostics include the complete stderr.
|
|
@@ -463,29 +416,10 @@ export class CodexAdapter {
|
|
|
463
416
|
if (!message.method)
|
|
464
417
|
return;
|
|
465
418
|
const params = message.params ?? {};
|
|
466
|
-
if (message.method === "
|
|
467
|
-
const turn = params.turn;
|
|
468
|
-
if (typeof turn?.id === "string")
|
|
469
|
-
this.activeTurnId = turn.id;
|
|
470
|
-
}
|
|
471
|
-
else if (message.method === "item/started") {
|
|
419
|
+
if (message.method === "item/started") {
|
|
472
420
|
const item = params.item;
|
|
473
|
-
if (
|
|
474
|
-
this.
|
|
475
|
-
if (this.activeTurnId && this._sessionId)
|
|
476
|
-
void this.request("turn/interrupt", { threadId: this._sessionId, turnId: this.activeTurnId }).catch(() => { });
|
|
477
|
-
}
|
|
478
|
-
if (item?.type === "contextCompaction") {
|
|
479
|
-
const providerSequence = ++this.compactionSequence;
|
|
480
|
-
const providerEventId = String(item.id ?? `codex-compaction-${providerSequence}`);
|
|
481
|
-
const origin = this.manualCompactionPending ? "rafi-manual" : "provider-auto";
|
|
482
|
-
this.activeCompaction = { providerEventId, providerSequence, origin, usageRevision: this.usageRevision };
|
|
483
|
-
this.eventQueue.push({
|
|
484
|
-
kind: "context-compaction", phase: "started", origin, providerEventId, providerSequence,
|
|
485
|
-
provider: "codex", sessionId: this._sessionId, sessionRef: this._sessionRef,
|
|
486
|
-
observedAt: new Date().toISOString(),
|
|
487
|
-
});
|
|
488
|
-
}
|
|
421
|
+
if (isCodexToolItem(item))
|
|
422
|
+
this.observedToolCalls += 1;
|
|
489
423
|
const activity = codexItemActivity(item);
|
|
490
424
|
if (activity)
|
|
491
425
|
this.eventQueue.push({ kind: "activity", provider: "codex", ...activity });
|
|
@@ -496,24 +430,22 @@ export class CodexAdapter {
|
|
|
496
430
|
this.activeText.push(item.text);
|
|
497
431
|
this.eventQueue.push({ kind: "text", text: item.text });
|
|
498
432
|
}
|
|
499
|
-
else if (item?.type === "commandExecution")
|
|
433
|
+
else if (item?.type === "commandExecution") {
|
|
500
434
|
this.eventQueue.push({ kind: "tool", name: "command_execution", input: { command: item.command } });
|
|
435
|
+
}
|
|
436
|
+
else if (item?.type === "contextCompaction" && !this.manualCompactionInFlight) {
|
|
437
|
+
// Completion is the provider's authoritative confirmation. Token-usage
|
|
438
|
+
// notifications may arrive before or after it, so do not make durable
|
|
439
|
+
// compaction accounting depend on their delivery order.
|
|
440
|
+
this.nativeCompactions.push({
|
|
441
|
+
id: `codex:${this._sessionId ?? "unknown"}:${Date.now()}:${++this.nativeCompactionSequence}`,
|
|
442
|
+
occurredAt: new Date().toISOString(),
|
|
443
|
+
provider: "codex",
|
|
444
|
+
usageRevision: this.usageRevision,
|
|
445
|
+
});
|
|
446
|
+
this.eventQueue.push({ kind: "session-transition", transition: "compacted", detail: "provider-native automatic compaction" });
|
|
447
|
+
}
|
|
501
448
|
else {
|
|
502
|
-
if (item?.type === "contextCompaction") {
|
|
503
|
-
const active = this.activeCompaction;
|
|
504
|
-
const providerSequence = active?.providerSequence ?? ++this.compactionSequence;
|
|
505
|
-
const providerEventId = String(item.id ?? active?.providerEventId ?? `codex-compaction-${providerSequence}`);
|
|
506
|
-
const origin = active?.origin ?? (this.manualCompactionPending ? "rafi-manual" : "provider-auto");
|
|
507
|
-
const failed = item.status === "failed" || item.status === "error" || item.error !== undefined;
|
|
508
|
-
this.eventQueue.push({
|
|
509
|
-
kind: "context-compaction", phase: failed ? "failed" : "succeeded", origin, providerEventId, providerSequence,
|
|
510
|
-
provider: "codex", sessionId: this._sessionId, sessionRef: this._sessionRef,
|
|
511
|
-
observedAt: new Date().toISOString(),
|
|
512
|
-
...(failed ? { reason: String(item.error?.message ?? item.error ?? "Codex context compaction failed") } : {}),
|
|
513
|
-
...(!failed && this.usage && (!active || this.usageRevision > active.usageRevision) ? { postCompactSample: { ...this.usage, source: "post-compact" } } : {}),
|
|
514
|
-
});
|
|
515
|
-
this.activeCompaction = undefined;
|
|
516
|
-
}
|
|
517
449
|
const activity = codexItemActivity(item, true);
|
|
518
450
|
if (activity)
|
|
519
451
|
this.eventQueue.push({ kind: "activity", provider: "codex", ...activity });
|
|
@@ -531,8 +463,7 @@ export class CodexAdapter {
|
|
|
531
463
|
const tokenUsage = params.tokenUsage;
|
|
532
464
|
const total = tokenUsage?.total;
|
|
533
465
|
const last = tokenUsage?.last;
|
|
534
|
-
|
|
535
|
-
const used = Number(last?.totalTokens);
|
|
466
|
+
const used = Number(total?.totalTokens);
|
|
536
467
|
const maximum = Number(tokenUsage?.modelContextWindow);
|
|
537
468
|
const observedAt = new Date().toISOString();
|
|
538
469
|
const totalInput = optionalNonNegative(total?.inputTokens);
|
|
@@ -546,11 +477,7 @@ export class CodexAdapter {
|
|
|
546
477
|
outputTokens: optionalNonNegative(last?.outputTokens),
|
|
547
478
|
};
|
|
548
479
|
if (Number.isFinite(used) && used >= 0 && Number.isFinite(maximum) && maximum > 0) {
|
|
549
|
-
|
|
550
|
-
this.usage = {
|
|
551
|
-
used, maximum, percentage: used / maximum * 100, observedAt, source: "provider-event", sequence,
|
|
552
|
-
...(this._sessionId ? { sessionId: this._sessionId } : {}), ...(this.opts.model ? { model: this.opts.model } : {}),
|
|
553
|
-
};
|
|
480
|
+
this.usage = { used, maximum, percentage: used / maximum * 100, observedAt, source: "provider-event" };
|
|
554
481
|
this.usageRevision += 1;
|
|
555
482
|
this.eventQueue.push({ kind: "context-usage", ...this.usage });
|
|
556
483
|
}
|
|
@@ -606,23 +533,15 @@ export class CodexAdapter {
|
|
|
606
533
|
waiter.reject(error);
|
|
607
534
|
this.notificationWaiters.clear();
|
|
608
535
|
}
|
|
609
|
-
|
|
610
|
-
async restartTransport() {
|
|
611
|
-
if (this.activeTurnId)
|
|
612
|
-
throw new Error("cannot restart Codex app-server while a turn is active");
|
|
536
|
+
async restartForAutoCompaction() {
|
|
613
537
|
const child = this.process;
|
|
538
|
+
if (!child)
|
|
539
|
+
return;
|
|
614
540
|
this.process = undefined;
|
|
615
541
|
this.initialized = false;
|
|
616
542
|
this.threadAttached = false;
|
|
617
|
-
if (child
|
|
543
|
+
if (child.exitCode === null)
|
|
618
544
|
child.kill("SIGTERM");
|
|
619
|
-
for (const pending of this.pending.values())
|
|
620
|
-
pending.reject(new Error("Codex app-server restarted for context policy"));
|
|
621
|
-
this.pending.clear();
|
|
622
|
-
for (const waiters of this.notificationWaiters.values())
|
|
623
|
-
for (const waiter of waiters)
|
|
624
|
-
waiter.reject(new Error("Codex app-server restarted for context policy"));
|
|
625
|
-
this.notificationWaiters.clear();
|
|
626
545
|
}
|
|
627
546
|
buildSkillsAppendix() {
|
|
628
547
|
if (!this.opts.skills?.length)
|
|
@@ -635,18 +554,13 @@ function optionalNonNegative(value) {
|
|
|
635
554
|
const number = Number(value);
|
|
636
555
|
return Number.isFinite(number) && number >= 0 ? number : undefined;
|
|
637
556
|
}
|
|
638
|
-
function
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (!Number.isInteger(policy.configuredThresholdPercent) || policy.configuredThresholdPercent < 1 || policy.configuredThresholdPercent > 99) {
|
|
643
|
-
throw new Error("configured context threshold must be an integer from 1 to 99");
|
|
644
|
-
}
|
|
645
|
-
if (!Number.isSafeInteger(policy.compactMaximum) || policy.compactMaximum < 1)
|
|
646
|
-
throw new Error("compact maximum must be a positive safe integer");
|
|
557
|
+
function validThreshold(value) {
|
|
558
|
+
if (!Number.isInteger(value) || value < 1 || value > 99)
|
|
559
|
+
throw new Error("automatic compaction threshold must be an integer from 1 to 99");
|
|
560
|
+
return value;
|
|
647
561
|
}
|
|
648
|
-
function
|
|
649
|
-
return
|
|
562
|
+
function tokenLimit(maximum, percentage) {
|
|
563
|
+
return Math.max(1, Math.floor(maximum * percentage / 100));
|
|
650
564
|
}
|
|
651
565
|
function activityPhase(phase) {
|
|
652
566
|
if (phase === "planning")
|
|
@@ -679,6 +593,9 @@ function codexItemActivity(item, completed = false) {
|
|
|
679
593
|
return { state: completed ? "received response" : "writing response" };
|
|
680
594
|
return undefined;
|
|
681
595
|
}
|
|
596
|
+
function isCodexToolItem(item) {
|
|
597
|
+
return item?.type === "commandExecution" || item?.type === "mcpToolCall" || item?.type === "dynamicToolCall" || item?.type === "webSearch" || item?.type === "fileChange";
|
|
598
|
+
}
|
|
682
599
|
function loadSkillMarkdown(cwd, skill) {
|
|
683
600
|
const projectPath = join(cwd, ".agents", "skills", skill, "SKILL.md");
|
|
684
601
|
if (existsSync(projectPath))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentRuntime } from "../runtimeAuth.js";
|
|
2
|
-
import type { BuilderAdapter, BuilderEvent, CompactResult,
|
|
2
|
+
import type { BuilderAdapter, BuilderEvent, CompactResult, ContextUsage, NativeAutoCompactionPolicy, NativeCompaction, ProviderSessionUsage, TurnResult } from "./types.js";
|
|
3
3
|
import type { ProviderSessionRefV1, SessionAvailabilityV1 } from "rafi-spec";
|
|
4
4
|
export type TurnRecoveryChoice = "retry" | "switch" | "cancel";
|
|
5
5
|
export interface RecoveringAdapterOptions {
|
|
@@ -21,7 +21,6 @@ export declare class RecoveringAdapter implements BuilderAdapter {
|
|
|
21
21
|
private readonly eventQueue;
|
|
22
22
|
private eventPump;
|
|
23
23
|
private closed;
|
|
24
|
-
private turnDispatcher?;
|
|
25
24
|
constructor(opts: RecoveringAdapterOptions);
|
|
26
25
|
get agent(): "claude" | "codex";
|
|
27
26
|
sendTurn(text: string): Promise<TurnResult>;
|
|
@@ -30,10 +29,11 @@ export declare class RecoveringAdapter implements BuilderAdapter {
|
|
|
30
29
|
adoptSessionRef(ref: ProviderSessionRefV1): void;
|
|
31
30
|
validateSession(): Promise<SessionAvailabilityV1>;
|
|
32
31
|
compact(): Promise<CompactResult>;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
prepareAutoCompaction(thresholdPercent?: number): Promise<NativeAutoCompactionPolicy | void>;
|
|
33
|
+
autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
|
|
34
|
+
drainNativeCompactions(): import("./types.js").NativeCompaction[];
|
|
35
|
+
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
36
|
+
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
37
37
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
38
38
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
39
39
|
events(): AsyncIterable<BuilderEvent>;
|
|
@@ -9,7 +9,6 @@ export class RecoveringAdapter {
|
|
|
9
9
|
eventQueue = new AsyncQueue();
|
|
10
10
|
eventPump;
|
|
11
11
|
closed = false;
|
|
12
|
-
turnDispatcher;
|
|
13
12
|
constructor(opts) {
|
|
14
13
|
this.opts = opts;
|
|
15
14
|
this.adapter = opts.initial;
|
|
@@ -21,8 +20,7 @@ export class RecoveringAdapter {
|
|
|
21
20
|
}
|
|
22
21
|
async sendTurn(text) {
|
|
23
22
|
while (true) {
|
|
24
|
-
const
|
|
25
|
-
const result = this.turnDispatcher ? await this.turnDispatcher(text, invoke) : await invoke();
|
|
23
|
+
const result = await this.adapter.sendTurn(text);
|
|
26
24
|
const sessionRef = this.adapter.sessionRef?.();
|
|
27
25
|
if (sessionRef && this.opts.onSessionRef) {
|
|
28
26
|
try {
|
|
@@ -69,12 +67,11 @@ export class RecoveringAdapter {
|
|
|
69
67
|
return this.adapter.validateSession?.() ?? Promise.resolve({ version: 1, status: "unknown", checkedAt: new Date().toISOString(), reason: "legacy-unscoped", detail: "wrapped adapter does not expose scoped session validation" });
|
|
70
68
|
}
|
|
71
69
|
compact() { return this.adapter.compact?.() ?? Promise.resolve({ ok: false, error: "native compaction unavailable" }); }
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
installManagedTurnDispatcher(dispatcher) { this.turnDispatcher = dispatcher; }
|
|
70
|
+
prepareAutoCompaction(thresholdPercent) { return this.adapter.prepareAutoCompaction?.(thresholdPercent) ?? Promise.resolve(); }
|
|
71
|
+
autoCompactionPolicy() { return this.adapter.autoCompactionPolicy?.(); }
|
|
72
|
+
drainNativeCompactions() { return this.adapter.drainNativeCompactions?.() ?? []; }
|
|
73
|
+
restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
|
|
74
|
+
contextUsageAfterNativeCompaction(compaction) { return this.adapter.contextUsageAfterNativeCompaction?.(compaction) ?? Promise.resolve(undefined); }
|
|
78
75
|
contextUsage() { return this.adapter.contextUsage?.() ?? Promise.resolve(undefined); }
|
|
79
76
|
sessionUsage() { return this.adapter.sessionUsage?.() ?? Promise.resolve(undefined); }
|
|
80
77
|
events() {
|