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.
@@ -59,55 +59,7 @@ export interface TurnResult {
59
59
  inputTokens?: number;
60
60
  outputTokens?: number;
61
61
  failure?: RuntimeFailure;
62
- /** A provider turn stopped at a context-compaction boundary, not a failure. */
63
- interrupted?: {
64
- reason: "compaction-boundary";
65
- providerEventId?: string;
66
- };
67
62
  }
68
- export type ContextCompactionOrigin = "provider-auto" | "rafi-manual" | "recovery" | "boundary";
69
- export type ContextCompactionPhase = "started" | "succeeded" | "failed";
70
- /** Native context policy installed before a Builder/QA role may do real work. */
71
- export interface ContextManagementPolicy {
72
- role: "builder" | "qa";
73
- configuredThresholdPercent: number;
74
- compactMaximum: number;
75
- settingsRevision: number;
76
- model: string;
77
- /** Continue a provider-monotonic sequence after process recovery. */
78
- providerSequenceStart?: number;
79
- /** A persisted, model-matched maximum may avoid a discovery turn. */
80
- knownModelContextWindow?: number;
81
- /** Native compaction stays enabled until the host vetoes a boundary. */
82
- nativeCompactionEnabled?: boolean;
83
- }
84
- export interface PreparedContextManagement {
85
- modelContextWindow: number;
86
- configuredTokenLimit: number;
87
- installedNativeTokenLimit: number;
88
- installedNativePercent: number;
89
- sample?: ContextUsage;
90
- }
91
- export interface ContextCompactionEvent {
92
- kind: "context-compaction";
93
- phase: ContextCompactionPhase;
94
- origin: ContextCompactionOrigin;
95
- providerEventId: string;
96
- providerSequence: number;
97
- provider: "claude" | "codex";
98
- sessionId?: string;
99
- sessionRef?: ProviderSessionRefV1;
100
- observedAt: string;
101
- postCompactSample?: ContextUsage;
102
- reason?: string;
103
- }
104
- export interface InterruptResult {
105
- ok: boolean;
106
- error?: string;
107
- providerEventId?: string;
108
- }
109
- /** Host-owned gate used by wrappers that need additional provider turns. */
110
- export type ManagedTurnDispatcher = (text: string, invoke: () => Promise<TurnResult>) => Promise<TurnResult>;
111
63
  /** Observability events emitted while a builder works. */
112
64
  export type BuilderEvent = {
113
65
  kind: "text";
@@ -145,10 +97,7 @@ export type BuilderEvent = {
145
97
  percentage?: number;
146
98
  observedAt?: string;
147
99
  source?: "provider-event" | "provider-query" | "post-compact";
148
- sequence?: number;
149
- sessionId?: string;
150
- model?: string;
151
- } | ContextCompactionEvent | {
100
+ } | {
152
101
  kind: "error";
153
102
  message: string;
154
103
  };
@@ -158,10 +107,17 @@ export interface ContextUsage {
158
107
  percentage?: number;
159
108
  observedAt?: string;
160
109
  source?: "provider-event" | "provider-query" | "post-compact";
161
- /** Monotonic within one scoped provider session. */
162
- sequence?: number;
163
- sessionId?: string;
164
- model?: string;
110
+ }
111
+ /**
112
+ * The provider-native automatic-compaction policy that was actually installed.
113
+ * Providers may clamp a requested ceiling to a model- or runtime-specific
114
+ * minimum, so lifecycle enforcement must consume the effective value.
115
+ */
116
+ export interface NativeAutoCompactionPolicy {
117
+ requestedThresholdPercent: number;
118
+ effectiveThresholdPercent: number;
119
+ modelContextWindow?: number;
120
+ triggerTokens?: number;
165
121
  }
166
122
  export interface ProviderSessionUsage {
167
123
  inputTokens?: number;
@@ -177,6 +133,15 @@ export interface CompactResult {
177
133
  /** Preserve exact-session loss so lifecycle policy never treats it as an ordinary retryable compaction failure. */
178
134
  failure?: RuntimeFailure;
179
135
  }
136
+ /** A provider-confirmed compaction that happened without Rafi issuing /compact. */
137
+ export interface NativeCompaction {
138
+ /** Stable for the lifetime of an adapter; used for durable idempotency. */
139
+ id: string;
140
+ occurredAt: string;
141
+ provider: "claude" | "codex";
142
+ /** Adapter-local usage generation captured with the provider confirmation. */
143
+ usageRevision?: number;
144
+ }
180
145
  export interface ProviderSettingSwitch {
181
146
  model?: string;
182
147
  effort?: EffortLevel;
@@ -218,8 +183,8 @@ export interface BuilderAdapterOptions {
218
183
  systemPromptAppend?: string;
219
184
  /** Skill names to preload for this session (Claude: lazy-loaded; Codex: flattened). */
220
185
  skills?: string[];
221
- /** Builder/QA native policy used even for setup and handoff-acceptance turns. */
222
- contextManagementPolicy?: ContextManagementPolicy;
186
+ /** Provider-native context ceiling, as a percentage of that provider's model window. */
187
+ autoCompactThresholdPercent?: number;
223
188
  }
224
189
  export interface BuilderAdapter {
225
190
  readonly agent: "claude" | "codex";
@@ -235,14 +200,20 @@ export interface BuilderAdapter {
235
200
  validateSession?(): Promise<SessionAvailabilityV1>;
236
201
  /** Provider-native compaction on the exact live conversation. */
237
202
  compact?(): Promise<CompactResult>;
238
- /** Initialize the session and install native in-turn context controls. */
239
- prepareContextManagement?(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
240
- /** Safely reconfigure native in-turn controls on the same session. */
241
- updateContextManagement?(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
242
- /** Stop only at a provider-observed compaction boundary. */
243
- interruptTurnAtCompactionBoundary?(providerEventId?: string): Promise<InterruptResult>;
244
- /** Route wrapper-owned repair/retry/follow-up turns through the role gate. */
245
- installManagedTurnDispatcher?(dispatcher: ManagedTurnDispatcher): void;
203
+ /**
204
+ * Install and verify provider-native automatic compaction before role work is
205
+ * dispatched. This intentionally happens outside a work turn so a long,
206
+ * tool-heavy first turn is protected too.
207
+ */
208
+ prepareAutoCompaction?(thresholdPercent?: number): Promise<NativeAutoCompactionPolicy | void>;
209
+ /** The last provider-native automatic-compaction policy verified on this transport. */
210
+ autoCompactionPolicy?(): NativeAutoCompactionPolicy | undefined;
211
+ /** Consume provider-native compactions observed since the prior drain. */
212
+ drainNativeCompactions?(): NativeCompaction[];
213
+ /** Return a drained batch to the adapter when durable receipt failed. */
214
+ restoreNativeCompactions?(compactions: NativeCompaction[]): void;
215
+ /** Return occupancy known to postdate a native-compaction event, if available. */
216
+ contextUsageAfterNativeCompaction?(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
246
217
  /** Truthful provider context occupancy, when exposed by the provider. */
247
218
  contextUsage?(): Promise<ContextUsage | undefined>;
248
219
  /** Provider/session cumulative totals; distinct from live context occupancy. */
@@ -1,4 +1,4 @@
1
- import type { BuilderAdapter, BuilderEvent, CompactResult, ContextManagementPolicy, ContextUsage, InterruptResult, ManagedTurnDispatcher, PreparedContextManagement, ProviderSessionUsage, ProviderSettingSwitch, TurnResult } from "../adapters/types.js";
1
+ import type { BuilderAdapter, BuilderEvent, CompactResult, ContextUsage, NativeAutoCompactionPolicy, NativeCompaction, ProviderSessionUsage, ProviderSettingSwitch, TurnResult } from "../adapters/types.js";
2
2
  import type { ProviderSessionRefV1, SessionAvailabilityV1 } from "rafi-spec";
3
3
  export interface CurrentWorkflowIdentity {
4
4
  worktree: string;
@@ -26,10 +26,11 @@ export declare class CurrentWorkflowGuardAdapter implements BuilderAdapter {
26
26
  adoptSessionRef(ref: ProviderSessionRefV1): void;
27
27
  validateSession(): Promise<SessionAvailabilityV1>;
28
28
  compact(): Promise<CompactResult>;
29
- prepareContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
30
- updateContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
31
- interruptTurnAtCompactionBoundary(providerEventId?: string): Promise<InterruptResult>;
32
- installManagedTurnDispatcher(dispatcher: ManagedTurnDispatcher): void;
29
+ prepareAutoCompaction(thresholdPercent?: number): Promise<NativeAutoCompactionPolicy | void>;
30
+ autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
31
+ drainNativeCompactions(): import("../adapters/types.js").NativeCompaction[];
32
+ restoreNativeCompactions(compactions: NativeCompaction[]): void;
33
+ contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
33
34
  contextUsage(): Promise<ContextUsage | undefined>;
34
35
  sessionUsage(): Promise<ProviderSessionUsage | undefined>;
35
36
  switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
@@ -48,12 +48,11 @@ export class CurrentWorkflowGuardAdapter {
48
48
  adoptSessionRef(ref) { this.adapter.adoptSessionRef?.(ref); }
49
49
  validateSession() { assertCurrentWorkflowIdentity(this.cwd, this.expected); return this.adapter.validateSession?.() ?? Promise.resolve({ version: 1, status: "unknown", checkedAt: new Date().toISOString(), reason: "legacy-unscoped" }); }
50
50
  async compact() { assertCurrentWorkflowIdentity(this.cwd, this.expected); const result = await (this.adapter.compact?.() ?? Promise.resolve({ ok: false, error: "native compaction unavailable" })); assertCurrentWorkflowIdentity(this.cwd, this.expected); return result; }
51
- async prepareContextManagement(policy) { assertCurrentWorkflowIdentity(this.cwd, this.expected); if (!this.adapter.prepareContextManagement)
52
- throw new Error("native context management unavailable"); const result = await this.adapter.prepareContextManagement(policy); assertCurrentWorkflowIdentity(this.cwd, this.expected); return result; }
53
- async updateContextManagement(policy) { assertCurrentWorkflowIdentity(this.cwd, this.expected); if (!this.adapter.updateContextManagement)
54
- throw new Error("native context reconfiguration unavailable"); const result = await this.adapter.updateContextManagement(policy); assertCurrentWorkflowIdentity(this.cwd, this.expected); return result; }
55
- interruptTurnAtCompactionBoundary(providerEventId) { return this.adapter.interruptTurnAtCompactionBoundary?.(providerEventId) ?? Promise.resolve({ ok: false, error: "compaction-boundary interruption unavailable", providerEventId }); }
56
- installManagedTurnDispatcher(dispatcher) { this.adapter.installManagedTurnDispatcher?.(dispatcher); }
51
+ prepareAutoCompaction(thresholdPercent) { return this.adapter.prepareAutoCompaction?.(thresholdPercent) ?? Promise.resolve(); }
52
+ autoCompactionPolicy() { return this.adapter.autoCompactionPolicy?.(); }
53
+ drainNativeCompactions() { return this.adapter.drainNativeCompactions?.() ?? []; }
54
+ restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
55
+ contextUsageAfterNativeCompaction(compaction) { return this.adapter.contextUsageAfterNativeCompaction?.(compaction) ?? Promise.resolve(undefined); }
57
56
  contextUsage() { return this.adapter.contextUsage?.() ?? Promise.resolve(undefined); }
58
57
  sessionUsage() { return this.adapter.sessionUsage?.() ?? Promise.resolve(undefined); }
59
58
  switchSettings(settings) { return this.adapter.switchSettings?.(settings) ?? Promise.resolve({ ok: false, error: "settings switch unavailable" }); }
@@ -56,6 +56,8 @@ export interface BranchRunnerOptions {
56
56
  builderSessionStrategy?: SessionStrategy;
57
57
  qaSessionStrategy?: SessionStrategy;
58
58
  observeBuilder?: (builder: BuilderAdapter) => Promise<void>;
59
+ observeBuilderNativeCompactions?: (builder: BuilderAdapter, cwd: string) => Promise<void>;
60
+ observeQaNativeCompactions?: (adapter: BuilderAdapter) => Promise<void>;
59
61
  qaNonconvergence?: (context: QaNonconvergenceContext) => Promise<QaNonconvergenceDecision>;
60
62
  beforeBuilderTurn?: (adapter: BuilderAdapter, frozenAction: string, cwd: string) => Promise<BuilderAdapter>;
61
63
  builderSessionBoundary?: (adapter: BuilderAdapter, frozenAction: string, strategy: SessionStrategy, cwd: string) => Promise<BuilderAdapter>;
@@ -173,7 +173,7 @@ export async function runBranchPlan(opts) {
173
173
  }
174
174
  builderWorkSessions += 1;
175
175
  viewer = opts.observeBuilder?.(builder);
176
- const foreman = new Foreman(builder, opts.log, opts.notificationsEnabled, opts.qaEnabled, 3, undefined, undefined, undefined, opts.qaSessionStrategy ?? "compact", undefined, opts.builderSessionStrategy ?? "compact", undefined, opts.beforeBuilderTurn ? (adapter, action) => opts.beforeBuilderTurn(adapter, action, worktreePath) : undefined);
176
+ const foreman = new Foreman(builder, opts.log, opts.notificationsEnabled, opts.qaEnabled, 3, undefined, undefined, undefined, opts.qaSessionStrategy ?? "compact", undefined, opts.builderSessionStrategy ?? "compact", undefined, opts.beforeBuilderTurn ? (adapter, action) => opts.beforeBuilderTurn(adapter, action, worktreePath) : undefined, undefined, undefined, async (adapter) => opts.observeBuilderNativeCompactions?.(adapter, worktreePath));
177
177
  const { result, status } = await foreman.runInstruction(ticketInstruction);
178
178
  builder = foreman.builderAdapter();
179
179
  workflowCheckpoint(opts.projectDir, opts.runId, "builder-after", node.ticket.id, { status: status.kind, sessionId: builder.sessionId(), worktree: worktreePath });
@@ -242,6 +242,7 @@ export async function runBranchPlan(opts) {
242
242
  ticket: node.ticket, builderWorktree: worktreePath, builderSummary: status.summary ?? result.text,
243
243
  qaStrategy: opts.qaSessionStrategy ?? "compact", state: qaStream, createQa: opts.createQa, maxCycles: 3,
244
244
  sessionBoundary: opts.qaSessionBoundary,
245
+ observeNativeCompactions: opts.observeQaNativeCompactions,
245
246
  evidence: (entry) => opts.log.write("qa-evidence", { ticket: node.ticket.id, ...entry }),
246
247
  fix: async (issues) => {
247
248
  if (!builder)