ai-foreman 1.7.8 → 1.7.9
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 +13 -29
- package/dist/adapters/claude.js +76 -334
- package/dist/adapters/codex.d.ts +13 -18
- package/dist/adapters/codex.js +103 -195
- package/dist/adapters/recovering.d.ts +5 -6
- package/dist/adapters/recovering.js +5 -9
- package/dist/adapters/types.d.ts +24 -66
- package/dist/branch/currentGuard.d.ts +5 -5
- package/dist/branch/currentGuard.js +4 -6
- package/dist/branch/runner.d.ts +2 -0
- package/dist/branch/runner.js +2 -1
- package/dist/cli/start.js +90 -165
- package/dist/continuity.d.ts +6 -8
- package/dist/continuity.js +26 -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 +19 -108
- package/dist/sessionLifecycle.js +310 -1024
- package/dist/statusReporter.d.ts +5 -27
- package/dist/statusReporter.js +20 -76
- package/dist/workflowDb.d.ts +10 -47
- package/dist/workflowDb.js +41 -146
- package/package.json +4 -4
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BuilderAdapter, BuilderAdapterOptions, BuilderEvent, CompactResult,
|
|
1
|
+
import type { BuilderAdapter, BuilderAdapterOptions, BuilderEvent, CompactResult, ContextUsage, NativeCompaction, ProviderSessionUsage, ProviderSettingSwitch, TurnResult } from "./types.js";
|
|
2
2
|
import type { ProviderSessionRefV1, SessionAvailabilityV1 } from "rafi-spec";
|
|
3
3
|
export interface CodexLineResult {
|
|
4
4
|
events: BuilderEvent[];
|
|
@@ -24,21 +24,16 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
24
24
|
private activeText;
|
|
25
25
|
private usage?;
|
|
26
26
|
private usageRevision;
|
|
27
|
-
private contextSequence;
|
|
28
27
|
private providerSessionUsage?;
|
|
29
28
|
private lastTurnTokens?;
|
|
30
29
|
private stderr;
|
|
31
|
-
private
|
|
32
|
-
private
|
|
33
|
-
private
|
|
34
|
-
private
|
|
35
|
-
private
|
|
36
|
-
private
|
|
37
|
-
private
|
|
38
|
-
private boundaryInterrupt?;
|
|
39
|
-
private compactionSequence;
|
|
40
|
-
private activeCompaction?;
|
|
41
|
-
private manualCompactionPending;
|
|
30
|
+
private nativeAutoCompactTokenLimit?;
|
|
31
|
+
private autoCompactionPrepared;
|
|
32
|
+
private preparedAutoCompactThreshold?;
|
|
33
|
+
private nativeCompactions;
|
|
34
|
+
private nativeCompactionSequence;
|
|
35
|
+
private manualCompactionInFlight;
|
|
36
|
+
private observedToolCalls;
|
|
42
37
|
constructor(opts: BuilderAdapterOptions);
|
|
43
38
|
buildInstruction(instruction: string): string;
|
|
44
39
|
buildAppServerArgs(): string[];
|
|
@@ -46,11 +41,12 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
46
41
|
buildArgs(instruction: string): string[];
|
|
47
42
|
sendTurn(instruction: string): Promise<TurnResult>;
|
|
48
43
|
private sendTurnInternal;
|
|
49
|
-
prepareContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
|
|
50
|
-
updateContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
|
|
51
|
-
interruptTurnAtCompactionBoundary(providerEventId?: string): Promise<InterruptResult>;
|
|
52
44
|
compact(): Promise<CompactResult>;
|
|
45
|
+
prepareAutoCompaction(thresholdPercent?: number | undefined): Promise<void>;
|
|
46
|
+
drainNativeCompactions(): NativeCompaction[];
|
|
47
|
+
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
53
48
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
49
|
+
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
54
50
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
55
51
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
56
52
|
sessionId(): string | undefined;
|
|
@@ -67,7 +63,6 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
67
63
|
private handle;
|
|
68
64
|
private waitFor;
|
|
69
65
|
private disconnect;
|
|
70
|
-
|
|
71
|
-
private restartTransport;
|
|
66
|
+
private restartForAutoCompaction;
|
|
72
67
|
private buildSkillsAppendix;
|
|
73
68
|
}
|
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,16 @@ 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
|
-
boundaryInterrupt;
|
|
56
|
-
compactionSequence = 0;
|
|
57
|
-
activeCompaction;
|
|
58
|
-
manualCompactionPending = false;
|
|
46
|
+
nativeAutoCompactTokenLimit;
|
|
47
|
+
autoCompactionPrepared = false;
|
|
48
|
+
preparedAutoCompactThreshold;
|
|
49
|
+
nativeCompactions = [];
|
|
50
|
+
nativeCompactionSequence = 0;
|
|
51
|
+
manualCompactionInFlight = false;
|
|
52
|
+
observedToolCalls = 0;
|
|
59
53
|
constructor(opts) {
|
|
60
54
|
this.opts = opts;
|
|
61
55
|
this._sessionId = opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId;
|
|
@@ -67,8 +61,8 @@ export class CodexAdapter {
|
|
|
67
61
|
}
|
|
68
62
|
buildAppServerArgs() {
|
|
69
63
|
const args = ["app-server", "--listen", "stdio://"];
|
|
70
|
-
if (this.
|
|
71
|
-
args.push("-c", `model_auto_compact_token_limit=${this.
|
|
64
|
+
if (this.nativeAutoCompactTokenLimit !== undefined) {
|
|
65
|
+
args.push("-c", `model_auto_compact_token_limit=${this.nativeAutoCompactTokenLimit}`, "-c", 'model_auto_compact_token_limit_scope="total"');
|
|
72
66
|
}
|
|
73
67
|
return args;
|
|
74
68
|
}
|
|
@@ -87,11 +81,9 @@ export class CodexAdapter {
|
|
|
87
81
|
return args;
|
|
88
82
|
}
|
|
89
83
|
async sendTurn(instruction) {
|
|
90
|
-
if (this.opts.contextManagementPolicy && !this.contextPrepared)
|
|
91
|
-
await this.prepareContextManagement(this.opts.contextManagementPolicy);
|
|
92
84
|
return withActivityPhase(`Codex ${activityPhase(this.opts.runtimePhase)}`, () => this.sendTurnInternal(instruction));
|
|
93
85
|
}
|
|
94
|
-
async sendTurnInternal(instruction
|
|
86
|
+
async sendTurnInternal(instruction) {
|
|
95
87
|
if (this.closed)
|
|
96
88
|
throw new Error("builder is closed");
|
|
97
89
|
this.eventQueue.push({ kind: "activity", state: "starting Codex turn", provider: "codex", model: this.opts.model });
|
|
@@ -100,49 +92,29 @@ export class CodexAdapter {
|
|
|
100
92
|
await this.ensureThread();
|
|
101
93
|
this.activeText = [];
|
|
102
94
|
this.lastTurnTokens = undefined;
|
|
103
|
-
this.setupTurnActive = setup;
|
|
104
|
-
this.setupToolAttempted = false;
|
|
105
95
|
const completion = this.waitFor("turn/completed", (params) => params.threadId === this._sessionId);
|
|
106
96
|
turnStartDispatched = true;
|
|
107
|
-
|
|
97
|
+
await this.request("turn/start", {
|
|
108
98
|
threadId: this._sessionId,
|
|
109
|
-
input: [{ type: "text", text:
|
|
99
|
+
input: [{ type: "text", text: this.buildInstruction(instruction), text_elements: [] }],
|
|
110
100
|
cwd: this.opts.cwd,
|
|
111
101
|
model: this.opts.model ?? null,
|
|
112
102
|
effort: this.opts.effort ?? (this.opts.fast ? "low" : null),
|
|
113
103
|
});
|
|
114
|
-
const startedTurn = started.turn;
|
|
115
|
-
this.activeTurnId = typeof startedTurn?.id === "string" ? startedTurn.id : undefined;
|
|
116
104
|
const params = await completion;
|
|
117
105
|
const turn = params.turn;
|
|
118
106
|
const failed = turn?.status === "failed";
|
|
119
|
-
const interrupted = turn?.status === "interrupted" || Boolean(this.boundaryInterrupt);
|
|
120
107
|
const error = turn?.error;
|
|
121
108
|
const text = this.activeText.join("\n");
|
|
122
109
|
const result = {
|
|
123
110
|
text: failed ? normalizeRuntimeErrorText("codex", String(error?.message ?? text), null, "app-server turn") : text,
|
|
124
111
|
isError: failed, numTurns: 1, costUsd: 0, costAuthoritative: false,
|
|
125
112
|
...(this.lastTurnTokens ?? {}),
|
|
126
|
-
...(interrupted ? { interrupted: { reason: "compaction-boundary", ...(this.boundaryInterrupt?.providerEventId ? { providerEventId: this.boundaryInterrupt.providerEventId } : {}) } } : {}),
|
|
127
113
|
};
|
|
128
|
-
if (setup && this.setupToolAttempted)
|
|
129
|
-
throw new Error("Codex context setup attempted to invoke a tool; native context capability was not established");
|
|
130
114
|
this.eventQueue.push({ kind: "turn-complete", result });
|
|
131
115
|
return result;
|
|
132
116
|
}
|
|
133
117
|
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
118
|
if (error instanceof SessionUnavailableError) {
|
|
147
119
|
const result = sessionUnavailableResult(error);
|
|
148
120
|
this.eventQueue.push({ kind: "error", message: result.text });
|
|
@@ -164,87 +136,10 @@ export class CodexAdapter {
|
|
|
164
136
|
this.eventQueue.push({ kind: "turn-complete", result });
|
|
165
137
|
return result;
|
|
166
138
|
}
|
|
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
139
|
}
|
|
244
140
|
async compact() {
|
|
245
141
|
try {
|
|
246
142
|
await this.ensureThread();
|
|
247
|
-
this.manualCompactionPending = true;
|
|
248
143
|
this.eventQueue.push({ kind: "session-transition", transition: "compacting" });
|
|
249
144
|
const usageRevision = this.usageRevision;
|
|
250
145
|
// A pre-compaction observation must never be mistaken for proof that the
|
|
@@ -255,17 +150,23 @@ export class CodexAdapter {
|
|
|
255
150
|
const done = this.waitFor("item/completed", (params) => {
|
|
256
151
|
const item = params.item;
|
|
257
152
|
return params.threadId === this._sessionId && item?.type === "contextCompaction";
|
|
258
|
-
},
|
|
153
|
+
}, 30_000);
|
|
259
154
|
const postCompactUsage = this.waitFor("thread/tokenUsage/updated", (params) => {
|
|
260
155
|
return (params.threadId === undefined || params.threadId === this._sessionId)
|
|
261
156
|
&& this.usageRevision > usageRevision
|
|
262
157
|
&& this.usage !== undefined;
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
158
|
+
}, 30_000);
|
|
159
|
+
this.manualCompactionInFlight = true;
|
|
160
|
+
try {
|
|
161
|
+
await Promise.all([
|
|
162
|
+
this.request("thread/compact/start", { threadId: this._sessionId }),
|
|
163
|
+
done,
|
|
164
|
+
postCompactUsage,
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
this.manualCompactionInFlight = false;
|
|
169
|
+
}
|
|
269
170
|
this.eventQueue.push({ kind: "session-transition", transition: "compacted" });
|
|
270
171
|
return { ok: true };
|
|
271
172
|
}
|
|
@@ -274,11 +175,55 @@ export class CodexAdapter {
|
|
|
274
175
|
return { ok: false, error: error.message, failure: error.failure };
|
|
275
176
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
276
177
|
}
|
|
277
|
-
|
|
278
|
-
|
|
178
|
+
}
|
|
179
|
+
async prepareAutoCompaction(thresholdPercent = this.opts.autoCompactThresholdPercent) {
|
|
180
|
+
if (thresholdPercent === undefined)
|
|
181
|
+
return;
|
|
182
|
+
const threshold = validThreshold(thresholdPercent);
|
|
183
|
+
if (this.autoCompactionPrepared && this.preparedAutoCompactThreshold === threshold)
|
|
184
|
+
return;
|
|
185
|
+
this.opts.autoCompactThresholdPercent = threshold;
|
|
186
|
+
await this.ensureThread();
|
|
187
|
+
// Codex reports its model context window in token-usage notifications, not
|
|
188
|
+
// in thread/start. Establish the otherwise idle thread with a constrained
|
|
189
|
+
// setup turn, then restart the app server with the provider-native ceiling
|
|
190
|
+
// before any Builder or QA work is sent.
|
|
191
|
+
if (!this.usage?.maximum) {
|
|
192
|
+
const toolsBefore = this.observedToolCalls;
|
|
193
|
+
const setup = await this.sendTurnInternal("Rafi internal initialization only. Do not call tools or modify files. Reply briefly that the context is ready.");
|
|
194
|
+
if (setup.isError)
|
|
195
|
+
throw new Error(`Codex automatic-compaction setup failed: ${setup.text.slice(0, 240)}`);
|
|
196
|
+
if (this.observedToolCalls !== toolsBefore)
|
|
197
|
+
throw new Error("Codex automatic-compaction setup unexpectedly called a tool");
|
|
279
198
|
}
|
|
199
|
+
const maximum = this.usage?.maximum;
|
|
200
|
+
if (!maximum || !Number.isFinite(maximum) || maximum <= 0) {
|
|
201
|
+
throw new Error("Codex did not report a model context window during automatic-compaction setup");
|
|
202
|
+
}
|
|
203
|
+
this.nativeAutoCompactTokenLimit = tokenLimit(maximum, threshold);
|
|
204
|
+
await this.restartForAutoCompaction();
|
|
205
|
+
await this.ensureThread();
|
|
206
|
+
this.autoCompactionPrepared = true;
|
|
207
|
+
this.preparedAutoCompactThreshold = threshold;
|
|
280
208
|
}
|
|
209
|
+
drainNativeCompactions() {
|
|
210
|
+
const pending = this.nativeCompactions;
|
|
211
|
+
this.nativeCompactions = [];
|
|
212
|
+
return pending;
|
|
213
|
+
}
|
|
214
|
+
restoreNativeCompactions(compactions) { this.nativeCompactions.unshift(...compactions); }
|
|
281
215
|
async contextUsage() { return this.usage; }
|
|
216
|
+
async contextUsageAfterNativeCompaction(compaction) {
|
|
217
|
+
if (compaction.usageRevision === undefined || this.usageRevision > compaction.usageRevision)
|
|
218
|
+
return this.usage;
|
|
219
|
+
try {
|
|
220
|
+
await this.waitFor("thread/tokenUsage/updated", () => this.usageRevision > compaction.usageRevision, 5_000);
|
|
221
|
+
return this.usage;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
282
227
|
async sessionUsage() { return this.providerSessionUsage ? { ...this.providerSessionUsage } : undefined; }
|
|
283
228
|
async switchSettings(settings) {
|
|
284
229
|
this.opts.model = settings.model;
|
|
@@ -413,7 +358,6 @@ export class CodexAdapter {
|
|
|
413
358
|
this.stderr = "";
|
|
414
359
|
const executable = this.opts.runtimeExecutable ?? "codex";
|
|
415
360
|
const child = spawn(executable, this.buildAppServerArgs(), { cwd: this.opts.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
416
|
-
this.spawnedNativeTokenLimit = this.nativeTokenLimit;
|
|
417
361
|
this.process = child;
|
|
418
362
|
// A short-lived server may close stdin before its stderr/exit event. The
|
|
419
363
|
// close handler owns rejection so diagnostics include the complete stderr.
|
|
@@ -463,29 +407,10 @@ export class CodexAdapter {
|
|
|
463
407
|
if (!message.method)
|
|
464
408
|
return;
|
|
465
409
|
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") {
|
|
410
|
+
if (message.method === "item/started") {
|
|
472
411
|
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
|
-
}
|
|
412
|
+
if (isCodexToolItem(item))
|
|
413
|
+
this.observedToolCalls += 1;
|
|
489
414
|
const activity = codexItemActivity(item);
|
|
490
415
|
if (activity)
|
|
491
416
|
this.eventQueue.push({ kind: "activity", provider: "codex", ...activity });
|
|
@@ -496,24 +421,22 @@ export class CodexAdapter {
|
|
|
496
421
|
this.activeText.push(item.text);
|
|
497
422
|
this.eventQueue.push({ kind: "text", text: item.text });
|
|
498
423
|
}
|
|
499
|
-
else if (item?.type === "commandExecution")
|
|
424
|
+
else if (item?.type === "commandExecution") {
|
|
500
425
|
this.eventQueue.push({ kind: "tool", name: "command_execution", input: { command: item.command } });
|
|
426
|
+
}
|
|
427
|
+
else if (item?.type === "contextCompaction" && !this.manualCompactionInFlight) {
|
|
428
|
+
// Completion is the provider's authoritative confirmation. Token-usage
|
|
429
|
+
// notifications may arrive before or after it, so do not make durable
|
|
430
|
+
// compaction accounting depend on their delivery order.
|
|
431
|
+
this.nativeCompactions.push({
|
|
432
|
+
id: `codex:${this._sessionId ?? "unknown"}:${Date.now()}:${++this.nativeCompactionSequence}`,
|
|
433
|
+
occurredAt: new Date().toISOString(),
|
|
434
|
+
provider: "codex",
|
|
435
|
+
usageRevision: this.usageRevision,
|
|
436
|
+
});
|
|
437
|
+
this.eventQueue.push({ kind: "session-transition", transition: "compacted", detail: "provider-native automatic compaction" });
|
|
438
|
+
}
|
|
501
439
|
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
440
|
const activity = codexItemActivity(item, true);
|
|
518
441
|
if (activity)
|
|
519
442
|
this.eventQueue.push({ kind: "activity", provider: "codex", ...activity });
|
|
@@ -531,8 +454,7 @@ export class CodexAdapter {
|
|
|
531
454
|
const tokenUsage = params.tokenUsage;
|
|
532
455
|
const total = tokenUsage?.total;
|
|
533
456
|
const last = tokenUsage?.last;
|
|
534
|
-
|
|
535
|
-
const used = Number(last?.totalTokens);
|
|
457
|
+
const used = Number(total?.totalTokens);
|
|
536
458
|
const maximum = Number(tokenUsage?.modelContextWindow);
|
|
537
459
|
const observedAt = new Date().toISOString();
|
|
538
460
|
const totalInput = optionalNonNegative(total?.inputTokens);
|
|
@@ -546,11 +468,7 @@ export class CodexAdapter {
|
|
|
546
468
|
outputTokens: optionalNonNegative(last?.outputTokens),
|
|
547
469
|
};
|
|
548
470
|
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
|
-
};
|
|
471
|
+
this.usage = { used, maximum, percentage: used / maximum * 100, observedAt, source: "provider-event" };
|
|
554
472
|
this.usageRevision += 1;
|
|
555
473
|
this.eventQueue.push({ kind: "context-usage", ...this.usage });
|
|
556
474
|
}
|
|
@@ -606,23 +524,15 @@ export class CodexAdapter {
|
|
|
606
524
|
waiter.reject(error);
|
|
607
525
|
this.notificationWaiters.clear();
|
|
608
526
|
}
|
|
609
|
-
|
|
610
|
-
async restartTransport() {
|
|
611
|
-
if (this.activeTurnId)
|
|
612
|
-
throw new Error("cannot restart Codex app-server while a turn is active");
|
|
527
|
+
async restartForAutoCompaction() {
|
|
613
528
|
const child = this.process;
|
|
529
|
+
if (!child)
|
|
530
|
+
return;
|
|
614
531
|
this.process = undefined;
|
|
615
532
|
this.initialized = false;
|
|
616
533
|
this.threadAttached = false;
|
|
617
|
-
if (child
|
|
534
|
+
if (child.exitCode === null)
|
|
618
535
|
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
536
|
}
|
|
627
537
|
buildSkillsAppendix() {
|
|
628
538
|
if (!this.opts.skills?.length)
|
|
@@ -635,18 +545,13 @@ function optionalNonNegative(value) {
|
|
|
635
545
|
const number = Number(value);
|
|
636
546
|
return Number.isFinite(number) && number >= 0 ? number : undefined;
|
|
637
547
|
}
|
|
638
|
-
function
|
|
639
|
-
|
|
548
|
+
function validThreshold(value) {
|
|
549
|
+
if (!Number.isInteger(value) || value < 1 || value > 99)
|
|
550
|
+
throw new Error("automatic compaction threshold must be an integer from 1 to 99");
|
|
551
|
+
return value;
|
|
640
552
|
}
|
|
641
|
-
function
|
|
642
|
-
|
|
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");
|
|
647
|
-
}
|
|
648
|
-
function isToolItem(type) {
|
|
649
|
-
return type === "commandExecution" || type === "fileChange" || type === "mcpToolCall" || type === "dynamicToolCall" || type === "webSearch";
|
|
553
|
+
function tokenLimit(maximum, percentage) {
|
|
554
|
+
return Math.max(1, Math.floor(maximum * percentage / 100));
|
|
650
555
|
}
|
|
651
556
|
function activityPhase(phase) {
|
|
652
557
|
if (phase === "planning")
|
|
@@ -679,6 +584,9 @@ function codexItemActivity(item, completed = false) {
|
|
|
679
584
|
return { state: completed ? "received response" : "writing response" };
|
|
680
585
|
return undefined;
|
|
681
586
|
}
|
|
587
|
+
function isCodexToolItem(item) {
|
|
588
|
+
return item?.type === "commandExecution" || item?.type === "mcpToolCall" || item?.type === "dynamicToolCall" || item?.type === "webSearch" || item?.type === "fileChange";
|
|
589
|
+
}
|
|
682
590
|
function loadSkillMarkdown(cwd, skill) {
|
|
683
591
|
const projectPath = join(cwd, ".agents", "skills", skill, "SKILL.md");
|
|
684
592
|
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, 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,10 @@ 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<void>;
|
|
33
|
+
drainNativeCompactions(): import("./types.js").NativeCompaction[];
|
|
34
|
+
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
35
|
+
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
37
36
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
38
37
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
39
38
|
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,10 @@ 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
|
-
interruptTurnAtCompactionBoundary(providerEventId) { return this.adapter.interruptTurnAtCompactionBoundary?.(providerEventId) ?? Promise.resolve({ ok: false, error: "compaction-boundary interruption unavailable", providerEventId }); }
|
|
77
|
-
installManagedTurnDispatcher(dispatcher) { this.turnDispatcher = dispatcher; }
|
|
70
|
+
prepareAutoCompaction(thresholdPercent) { return this.adapter.prepareAutoCompaction?.(thresholdPercent) ?? Promise.resolve(); }
|
|
71
|
+
drainNativeCompactions() { return this.adapter.drainNativeCompactions?.() ?? []; }
|
|
72
|
+
restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
|
|
73
|
+
contextUsageAfterNativeCompaction(compaction) { return this.adapter.contextUsageAfterNativeCompaction?.(compaction) ?? Promise.resolve(undefined); }
|
|
78
74
|
contextUsage() { return this.adapter.contextUsage?.() ?? Promise.resolve(undefined); }
|
|
79
75
|
sessionUsage() { return this.adapter.sessionUsage?.() ?? Promise.resolve(undefined); }
|
|
80
76
|
events() {
|