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.
@@ -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
- contextPolicy;
49
- nativeTokenLimit;
50
- spawnedNativeTokenLimit;
51
- contextPrepared = false;
52
- setupTurnActive = false;
53
- setupToolAttempted = false;
54
- activeTurnId;
55
- boundaryInterrupt;
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.nativeTokenLimit !== undefined) {
71
- args.push("-c", `model_auto_compact_token_limit=${this.nativeTokenLimit}`, "-c", 'model_auto_compact_token_limit_scope="total"');
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, setup = false) {
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
- const started = await this.request("turn/start", {
98
+ await this.request("turn/start", {
108
99
  threadId: this._sessionId,
109
- input: [{ type: "text", text: setup ? instruction : this.buildInstruction(instruction), text_elements: [] }],
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
- }, CODEX_COMPACTION_TIMEOUT_MS);
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
- }, CODEX_COMPACTION_TIMEOUT_MS);
264
- await Promise.all([
265
- this.request("thread/compact/start", { threadId: this._sessionId }),
266
- done,
267
- postCompactUsage,
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
- finally {
278
- this.manualCompactionPending = false;
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 === "turn/started") {
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 (this.setupTurnActive && item && isToolItem(item.type)) {
474
- this.setupToolAttempted = true;
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
- // `last` is active context occupancy. `total` is lifetime session usage.
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
- const sequence = ++this.contextSequence;
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
- /** Restart only app-server; the scoped thread remains the continuity boundary. */
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 && child.exitCode === null)
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 tokenCeiling(maximum, percentage) {
639
- return Math.max(1, Math.floor(maximum * percentage / 100));
640
- }
641
- function validateContextPolicy(policy) {
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 isToolItem(type) {
649
- return type === "commandExecution" || type === "fileChange" || type === "mcpToolCall" || type === "dynamicToolCall" || type === "webSearch";
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, ContextManagementPolicy, ContextUsage, InterruptResult, ManagedTurnDispatcher, PreparedContextManagement, ProviderSessionUsage, TurnResult } from "./types.js";
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
- prepareContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
34
- updateContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
35
- interruptTurnAtCompactionBoundary(providerEventId?: string): Promise<InterruptResult>;
36
- installManagedTurnDispatcher(dispatcher: ManagedTurnDispatcher): void;
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 invoke = () => this.adapter.sendTurn(text);
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
- prepareContextManagement(policy) { if (!this.adapter.prepareContextManagement)
73
- return Promise.reject(new Error("native context management unavailable")); return this.adapter.prepareContextManagement(policy); }
74
- updateContextManagement(policy) { if (!this.adapter.updateContextManagement)
75
- return Promise.reject(new Error("native context reconfiguration unavailable")); return this.adapter.updateContextManagement(policy); }
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
+ 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() {