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/claude.js
CHANGED
|
@@ -145,10 +145,6 @@ export class ClaudeAdapter {
|
|
|
145
145
|
query;
|
|
146
146
|
abort = new AbortController();
|
|
147
147
|
pumpDone;
|
|
148
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
149
|
-
queryFactory;
|
|
150
|
-
transportGeneration = 0;
|
|
151
|
-
transportObservedSessionId;
|
|
152
148
|
_sessionId;
|
|
153
149
|
_sessionRef;
|
|
154
150
|
stderrChunks = [];
|
|
@@ -156,19 +152,12 @@ export class ClaudeAdapter {
|
|
|
156
152
|
structuredError;
|
|
157
153
|
apiErrorStatus;
|
|
158
154
|
compactResult;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
compactionTerminalObserved = false;
|
|
166
|
-
lastContextUsage;
|
|
167
|
-
/** Full provider context capacity, before Rafi narrows Claude's auto-compact window. */
|
|
168
|
-
modelContextWindow;
|
|
169
|
-
/** SDK-owned space between autoCompactWindow and the actual used-token trigger. */
|
|
170
|
-
nativeAutoCompactReserve;
|
|
171
|
-
contextPrepared = false;
|
|
155
|
+
autoCompactionPrepared = false;
|
|
156
|
+
preparedAutoCompactThreshold;
|
|
157
|
+
preparedAutoCompactionPolicy;
|
|
158
|
+
manualCompactionInFlight = false;
|
|
159
|
+
nativeCompactions = [];
|
|
160
|
+
nativeCompactionSequence = 0;
|
|
172
161
|
cumulativeUsage = { observedAt: new Date(0).toISOString(), source: "provider" };
|
|
173
162
|
pending;
|
|
174
163
|
terminalResult;
|
|
@@ -212,30 +201,18 @@ export class ClaudeAdapter {
|
|
|
212
201
|
}
|
|
213
202
|
}
|
|
214
203
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
215
|
-
constructor(opts,
|
|
204
|
+
constructor(opts, query) {
|
|
216
205
|
this.opts = opts;
|
|
217
206
|
this._sessionId = opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId;
|
|
218
207
|
this._sessionRef = opts.resumeSessionRef;
|
|
219
|
-
this.
|
|
220
|
-
this.query = this.openQuery();
|
|
221
|
-
const generation = ++this.transportGeneration;
|
|
222
|
-
this.pumpDone = this.pump(this.query, generation);
|
|
223
|
-
}
|
|
224
|
-
openQuery(nativeAutoCompactWindow) {
|
|
225
|
-
const scopedOpts = this._sessionRef
|
|
226
|
-
? { ...this.opts, resumeSessionId: this._sessionRef.sessionId, resumeSessionRef: this._sessionRef }
|
|
227
|
-
: this.opts;
|
|
228
|
-
return this.queryFactory({
|
|
208
|
+
this.query = query({
|
|
229
209
|
prompt: this.inbox,
|
|
230
210
|
options: {
|
|
231
|
-
...buildClaudeQueryOptions(
|
|
232
|
-
...(nativeAutoCompactWindow === undefined ? {} : {
|
|
233
|
-
settings: { autoCompactEnabled: true, autoCompactWindow: nativeAutoCompactWindow },
|
|
234
|
-
}),
|
|
211
|
+
...buildClaudeQueryOptions(opts),
|
|
235
212
|
abortController: this.abort,
|
|
236
213
|
stderr: (data) => this.captureStderr(data),
|
|
237
214
|
canUseTool: async (toolName, input, requestOptions = {}) => {
|
|
238
|
-
const decision = await
|
|
215
|
+
const decision = await opts.permission({
|
|
239
216
|
toolName,
|
|
240
217
|
input,
|
|
241
218
|
signal: requestOptions.signal,
|
|
@@ -250,22 +227,16 @@ export class ClaudeAdapter {
|
|
|
250
227
|
},
|
|
251
228
|
},
|
|
252
229
|
});
|
|
230
|
+
this.pumpDone = this.pump();
|
|
253
231
|
}
|
|
254
232
|
/** Background loop: consume the SDK message stream until it ends. */
|
|
255
|
-
async pump(
|
|
233
|
+
async pump() {
|
|
256
234
|
try {
|
|
257
|
-
for await (const msg of query) {
|
|
258
|
-
|
|
259
|
-
return;
|
|
260
|
-
// Keep provider messages ordered through post-compaction measurement. In
|
|
261
|
-
// particular, a result message must not overtake the fresh context sample
|
|
262
|
-
// produced for the preceding compact_result/compact_boundary message.
|
|
263
|
-
await this.handle(msg);
|
|
235
|
+
for await (const msg of this.query) {
|
|
236
|
+
this.handle(msg);
|
|
264
237
|
}
|
|
265
238
|
}
|
|
266
239
|
catch (err) {
|
|
267
|
-
if (generation !== this.transportGeneration)
|
|
268
|
-
return;
|
|
269
240
|
// Suppress the AbortError that fires when close() aborts the stream.
|
|
270
241
|
const isShutdownAbort = this.closed &&
|
|
271
242
|
(err instanceof Error &&
|
|
@@ -280,8 +251,6 @@ export class ClaudeAdapter {
|
|
|
280
251
|
}
|
|
281
252
|
}
|
|
282
253
|
finally {
|
|
283
|
-
if (generation !== this.transportGeneration)
|
|
284
|
-
return;
|
|
285
254
|
this.streamEnded = true;
|
|
286
255
|
if (!this.closed && this.pending) {
|
|
287
256
|
const result = this.streamFailureResult("Claude stream ended without a result", Boolean(this.opts.resumeSessionRef ?? this.opts.resumeSessionId));
|
|
@@ -294,9 +263,8 @@ export class ClaudeAdapter {
|
|
|
294
263
|
this.eventQueue.close();
|
|
295
264
|
}
|
|
296
265
|
}
|
|
297
|
-
|
|
266
|
+
handle(msg) {
|
|
298
267
|
if ("session_id" in msg && typeof msg.session_id === "string") {
|
|
299
|
-
this.transportObservedSessionId = msg.session_id;
|
|
300
268
|
this._sessionId = msg.session_id;
|
|
301
269
|
this.observeSession(msg.session_id);
|
|
302
270
|
}
|
|
@@ -325,43 +293,16 @@ export class ClaudeAdapter {
|
|
|
325
293
|
this.turnSignals.push(...msg.output.map((line) => `auth: ${line}`));
|
|
326
294
|
}
|
|
327
295
|
else if (msg.type === "system" && msg.subtype === "status") {
|
|
328
|
-
if (msg.status === "compacting")
|
|
329
|
-
this.compactionTerminalObserved = false;
|
|
296
|
+
if (msg.status === "compacting")
|
|
330
297
|
this.eventQueue.push({ kind: "session-transition", transition: "compacting" });
|
|
331
|
-
const providerSequence = ++this.compactionSequence;
|
|
332
|
-
const origin = this.manualCompactionPending ? "rafi-manual" : "provider-auto";
|
|
333
|
-
this.activeCompaction = { providerEventId: msg.uuid, providerSequence, origin };
|
|
334
|
-
this.eventQueue.push({
|
|
335
|
-
kind: "context-compaction", phase: "started", origin, providerEventId: msg.uuid,
|
|
336
|
-
providerSequence, provider: "claude", sessionId: this._sessionId, sessionRef: this._sessionRef,
|
|
337
|
-
observedAt: new Date().toISOString(),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
298
|
if (msg.compact_result === "success") {
|
|
341
299
|
this.compactResult = { ok: true };
|
|
342
300
|
this.eventQueue.push({ kind: "session-transition", transition: "compacted" });
|
|
343
|
-
if (!this.
|
|
344
|
-
|
|
301
|
+
if (!this.manualCompactionInFlight)
|
|
302
|
+
this.nativeCompactions.push({ id: `claude:${this._sessionId ?? "unknown"}:${++this.nativeCompactionSequence}`, occurredAt: new Date().toISOString(), provider: "claude" });
|
|
345
303
|
}
|
|
346
304
|
else if (msg.compact_result === "failed") {
|
|
347
305
|
this.compactResult = { ok: false, error: sanitizeDiagnostics(msg.compact_error ?? "Claude native compaction failed") };
|
|
348
|
-
if (!this.compactionTerminalObserved)
|
|
349
|
-
await this.completeCompaction(false, this.compactResult.error);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
else if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
353
|
-
if (!this.compactionTerminalObserved) {
|
|
354
|
-
// The boundary is itself a successful native compact signal. Query the
|
|
355
|
-
// SDK immediately and use its post_tokens only as a fallback if that
|
|
356
|
-
// authoritative query is unavailable.
|
|
357
|
-
if (!this.activeCompaction) {
|
|
358
|
-
this.activeCompaction = {
|
|
359
|
-
providerEventId: msg.uuid,
|
|
360
|
-
providerSequence: ++this.compactionSequence,
|
|
361
|
-
origin: msg.compact_metadata.trigger === "manual" ? "rafi-manual" : "provider-auto",
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
await this.completeCompaction(true, undefined, msg.compact_metadata.post_tokens);
|
|
365
306
|
}
|
|
366
307
|
}
|
|
367
308
|
else if (msg.type === "system" && msg.subtype === "api_retry") {
|
|
@@ -423,9 +364,7 @@ export class ClaudeAdapter {
|
|
|
423
364
|
costUsd: msg.total_cost_usd,
|
|
424
365
|
costAuthoritative: Number.isFinite(msg.total_cost_usd),
|
|
425
366
|
failure,
|
|
426
|
-
...(this.boundaryInterrupt ? { interrupted: { reason: "compaction-boundary", ...(this.boundaryInterrupt.providerEventId ? { providerEventId: this.boundaryInterrupt.providerEventId } : {}) } } : {}),
|
|
427
367
|
};
|
|
428
|
-
this.boundaryInterrupt = undefined;
|
|
429
368
|
const rawResult = msg;
|
|
430
369
|
const mergedUsage = mergeClaudeProviderSessionUsage(this.cumulativeUsage, rawResult);
|
|
431
370
|
result.inputTokens = mergedUsage.inputTokens;
|
|
@@ -440,11 +379,7 @@ export class ClaudeAdapter {
|
|
|
440
379
|
}
|
|
441
380
|
}
|
|
442
381
|
sendTurn(text) {
|
|
443
|
-
return withActivityPhase(`Claude ${activityPhase(this.opts.runtimePhase)}`,
|
|
444
|
-
if (this.opts.contextManagementPolicy && !this.contextPrepared)
|
|
445
|
-
await this.prepareContextManagement(this.opts.contextManagementPolicy);
|
|
446
|
-
return this.sendTurnInternal(text);
|
|
447
|
-
});
|
|
382
|
+
return withActivityPhase(`Claude ${activityPhase(this.opts.runtimePhase)}`, () => this.sendTurnInternal(text));
|
|
448
383
|
}
|
|
449
384
|
sendTurnInternal(text) {
|
|
450
385
|
if (this.closed)
|
|
@@ -496,25 +431,75 @@ export class ClaudeAdapter {
|
|
|
496
431
|
}
|
|
497
432
|
async compact() {
|
|
498
433
|
this.compactResult = undefined;
|
|
499
|
-
this.
|
|
434
|
+
this.manualCompactionInFlight = true;
|
|
435
|
+
let result;
|
|
500
436
|
try {
|
|
501
|
-
|
|
502
|
-
if (result.failure?.category === "session-unavailable") {
|
|
503
|
-
return { ok: false, error: result.text || result.failure.diagnostics, failure: result.failure };
|
|
504
|
-
}
|
|
505
|
-
if (this.compactResult)
|
|
506
|
-
return this.compactResult;
|
|
507
|
-
return { ok: false, error: sanitizeDiagnostics(result.text || "Claude did not emit an explicit compact status") };
|
|
437
|
+
result = await this.sendTurn("/compact");
|
|
508
438
|
}
|
|
509
439
|
finally {
|
|
510
|
-
this.
|
|
440
|
+
this.manualCompactionInFlight = false;
|
|
441
|
+
}
|
|
442
|
+
if (result.failure?.category === "session-unavailable") {
|
|
443
|
+
return { ok: false, error: result.text || result.failure.diagnostics, failure: result.failure };
|
|
511
444
|
}
|
|
445
|
+
if (this.compactResult)
|
|
446
|
+
return this.compactResult;
|
|
447
|
+
return { ok: false, error: sanitizeDiagnostics(result.text || "Claude did not emit an explicit compact status") };
|
|
448
|
+
}
|
|
449
|
+
async prepareAutoCompaction(thresholdPercent = this.opts.autoCompactThresholdPercent) {
|
|
450
|
+
if (thresholdPercent === undefined)
|
|
451
|
+
return;
|
|
452
|
+
const threshold = validThreshold(thresholdPercent);
|
|
453
|
+
if (this.autoCompactionPrepared && this.preparedAutoCompactThreshold === threshold)
|
|
454
|
+
return this.preparedAutoCompactionPolicy;
|
|
455
|
+
this.opts.autoCompactThresholdPercent = threshold;
|
|
456
|
+
await this.query.initializationResult();
|
|
457
|
+
await this.query.applyFlagSettings({ autoCompactEnabled: true });
|
|
458
|
+
const baseline = await this.query.getContextUsage();
|
|
459
|
+
if (!Number.isFinite(baseline.maxTokens) || baseline.maxTokens <= 0
|
|
460
|
+
|| !Number.isFinite(baseline.autoCompactThreshold) || baseline.autoCompactThreshold === undefined
|
|
461
|
+
|| !baseline.isAutoCompactEnabled) {
|
|
462
|
+
throw new Error("Claude did not expose an enabled native automatic-compaction threshold");
|
|
463
|
+
}
|
|
464
|
+
// Claude's setting is the total window. Preserve the provider's own
|
|
465
|
+
// response/compaction reserve, so the resulting used-token trigger is at
|
|
466
|
+
// or before Rafi's configured percentage.
|
|
467
|
+
const reserve = baseline.maxTokens - baseline.autoCompactThreshold;
|
|
468
|
+
if (reserve < 0)
|
|
469
|
+
throw new Error("Claude reported an invalid automatic-compaction reserve");
|
|
470
|
+
const requestedWindow = tokenLimit(baseline.maxTokens, threshold) + reserve;
|
|
471
|
+
await this.query.applyFlagSettings({ autoCompactEnabled: true, autoCompactWindow: requestedWindow });
|
|
472
|
+
const installed = await this.query.getContextUsage();
|
|
473
|
+
if (!installed.isAutoCompactEnabled || !Number.isFinite(installed.maxTokens) || installed.maxTokens <= 0
|
|
474
|
+
|| !Number.isFinite(installed.autoCompactThreshold) || installed.autoCompactThreshold === undefined) {
|
|
475
|
+
throw new Error("Claude did not expose an enabled native automatic-compaction threshold after configuration");
|
|
476
|
+
}
|
|
477
|
+
// `applyFlagSettings` can legitimately clamp autoCompactWindow. The
|
|
478
|
+
// provider's reported trigger is authoritative; rejecting the entire run
|
|
479
|
+
// merely because a configured value is below that minimum made defaults
|
|
480
|
+
// unusable. Lifecycle code consumes this verified effective ceiling.
|
|
481
|
+
const effectiveThresholdPercent = Math.max(1, Math.min(99, Math.floor(installed.autoCompactThreshold / installed.maxTokens * 100)));
|
|
482
|
+
this.preparedAutoCompactionPolicy = {
|
|
483
|
+
requestedThresholdPercent: threshold,
|
|
484
|
+
effectiveThresholdPercent,
|
|
485
|
+
modelContextWindow: installed.maxTokens,
|
|
486
|
+
triggerTokens: installed.autoCompactThreshold,
|
|
487
|
+
};
|
|
488
|
+
this.autoCompactionPrepared = true;
|
|
489
|
+
this.preparedAutoCompactThreshold = threshold;
|
|
490
|
+
return this.preparedAutoCompactionPolicy;
|
|
512
491
|
}
|
|
492
|
+
autoCompactionPolicy() { return this.preparedAutoCompactionPolicy; }
|
|
493
|
+
drainNativeCompactions() {
|
|
494
|
+
const pending = this.nativeCompactions;
|
|
495
|
+
this.nativeCompactions = [];
|
|
496
|
+
return pending;
|
|
497
|
+
}
|
|
498
|
+
restoreNativeCompactions(compactions) { this.nativeCompactions.unshift(...compactions); }
|
|
513
499
|
async contextUsage() {
|
|
514
500
|
try {
|
|
515
501
|
const usage = await this.query.getContextUsage();
|
|
516
|
-
const result =
|
|
517
|
-
this.lastContextUsage = result;
|
|
502
|
+
const result = { used: usage.totalTokens, maximum: usage.maxTokens, percentage: usage.percentage, observedAt: new Date().toISOString(), source: "provider-query" };
|
|
518
503
|
this.eventQueue.push({ kind: "context-usage", ...result });
|
|
519
504
|
return result;
|
|
520
505
|
}
|
|
@@ -522,32 +507,6 @@ export class ClaudeAdapter {
|
|
|
522
507
|
return undefined;
|
|
523
508
|
}
|
|
524
509
|
}
|
|
525
|
-
async prepareContextManagement(policy) {
|
|
526
|
-
validateContextPolicy(policy);
|
|
527
|
-
if (this.contextPrepared)
|
|
528
|
-
return this.configureContextManagement(policy, false);
|
|
529
|
-
await this.query.initializationResult();
|
|
530
|
-
const prepared = await this.configureContextManagement(policy, true);
|
|
531
|
-
this.contextPrepared = true;
|
|
532
|
-
return prepared;
|
|
533
|
-
}
|
|
534
|
-
async updateContextManagement(policy) {
|
|
535
|
-
validateContextPolicy(policy);
|
|
536
|
-
return this.configureContextManagement(policy, false);
|
|
537
|
-
}
|
|
538
|
-
async interruptTurnAtCompactionBoundary(providerEventId) {
|
|
539
|
-
if (!this.pending)
|
|
540
|
-
return { ok: false, error: "no active Claude turn exists at the compaction boundary", providerEventId };
|
|
541
|
-
try {
|
|
542
|
-
this.boundaryInterrupt = { providerEventId };
|
|
543
|
-
await this.query.interrupt();
|
|
544
|
-
return { ok: true, providerEventId };
|
|
545
|
-
}
|
|
546
|
-
catch (error) {
|
|
547
|
-
this.boundaryInterrupt = undefined;
|
|
548
|
-
return { ok: false, error: error instanceof Error ? error.message : String(error), providerEventId };
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
510
|
async sessionUsage() {
|
|
552
511
|
return this.cumulativeUsage.observedAt === new Date(0).toISOString() ? undefined : { ...this.cumulativeUsage };
|
|
553
512
|
}
|
|
@@ -556,14 +515,11 @@ export class ClaudeAdapter {
|
|
|
556
515
|
return { ok: false, error: "Claude SDK cannot change reasoning/fast controls on an existing transport" };
|
|
557
516
|
if (settings.model === this.opts.model)
|
|
558
517
|
return { ok: true };
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
catch (error) {
|
|
565
|
-
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
566
|
-
}
|
|
518
|
+
const result = await this.sendTurn(`/model ${settings.model ?? "default"}`);
|
|
519
|
+
if (result.isError)
|
|
520
|
+
return { ok: false, error: result.text, ...(result.failure ? { failure: result.failure } : {}) };
|
|
521
|
+
this.opts.model = settings.model;
|
|
522
|
+
return { ok: true };
|
|
567
523
|
}
|
|
568
524
|
events() {
|
|
569
525
|
return this.eventQueue;
|
|
@@ -592,200 +548,6 @@ export class ClaudeAdapter {
|
|
|
592
548
|
source: "observed",
|
|
593
549
|
});
|
|
594
550
|
}
|
|
595
|
-
makeContextUsage(usage) {
|
|
596
|
-
const maximum = this.modelContextWindow
|
|
597
|
-
?? (Number.isFinite(usage.maxTokens) && usage.maxTokens > 0 ? usage.maxTokens : this.lastContextUsage?.maximum);
|
|
598
|
-
// Once Rafi installs a smaller autoCompactWindow, the SDK's percentage and
|
|
599
|
-
// maxTokens describe that artificial window. Enforcement and display stay
|
|
600
|
-
// relative to the full model capacity discovered before reconfiguration.
|
|
601
|
-
const percentage = maximum ? usage.totalTokens / maximum * 100
|
|
602
|
-
: Number.isFinite(usage.percentage) ? usage.percentage : undefined;
|
|
603
|
-
return {
|
|
604
|
-
used: usage.totalTokens,
|
|
605
|
-
...(maximum ? { maximum } : {}),
|
|
606
|
-
...(percentage !== undefined ? { percentage } : {}),
|
|
607
|
-
observedAt: new Date().toISOString(),
|
|
608
|
-
source: "provider-query",
|
|
609
|
-
sequence: ++this.contextSequence,
|
|
610
|
-
...(this._sessionId ? { sessionId: this._sessionId } : {}),
|
|
611
|
-
...(this.opts.model ?? usage.model ? { model: this.opts.model ?? usage.model } : {}),
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
async configureContextManagement(policy, initial) {
|
|
615
|
-
const modelChanged = this.contextPolicy !== undefined && this.contextPolicy.model !== policy.model;
|
|
616
|
-
if (modelChanged) {
|
|
617
|
-
this.modelContextWindow = undefined;
|
|
618
|
-
this.nativeAutoCompactReserve = undefined;
|
|
619
|
-
await this.restartTransport();
|
|
620
|
-
}
|
|
621
|
-
this.contextPolicy = { ...policy };
|
|
622
|
-
this.compactionSequence = Math.max(this.compactionSequence, policy.providerSequenceStart ?? 0);
|
|
623
|
-
this.contextSequence = Math.max(this.contextSequence, policy.providerSequenceStart ?? 0);
|
|
624
|
-
if (initial || modelChanged || !this.modelContextWindow || !this.nativeAutoCompactReserve) {
|
|
625
|
-
// Enable first so getContextUsage exposes the provider's real trigger.
|
|
626
|
-
await this.query.applyFlagSettings({ autoCompactEnabled: true });
|
|
627
|
-
const baseline = await this.query.getContextUsage();
|
|
628
|
-
const baselineWindow = positiveNumber(baseline.maxTokens);
|
|
629
|
-
const baselineTrigger = positiveNumber(baseline.autoCompactThreshold);
|
|
630
|
-
if (!baselineWindow || !baselineTrigger || baselineTrigger >= baselineWindow) {
|
|
631
|
-
throw new Error("Claude did not expose a usable maxTokens/autoCompactThreshold pair after enabling native auto-compaction");
|
|
632
|
-
}
|
|
633
|
-
this.modelContextWindow = policy.knownModelContextWindow ?? baselineWindow;
|
|
634
|
-
this.nativeAutoCompactReserve = baselineWindow - baselineTrigger;
|
|
635
|
-
}
|
|
636
|
-
const maximum = this.modelContextWindow;
|
|
637
|
-
const reserve = this.nativeAutoCompactReserve;
|
|
638
|
-
const configuredTokenLimit = tokenCeiling(maximum, policy.configuredThresholdPercent);
|
|
639
|
-
// Claude's setting is a total window, not the used-token trigger. Preserve
|
|
640
|
-
// the SDK-reported output/compaction reserve so its resulting trigger lands
|
|
641
|
-
// on (or earlier than) Rafi's configured absolute ceiling.
|
|
642
|
-
const requestedNativeWindow = configuredTokenLimit + reserve;
|
|
643
|
-
await this.query.applyFlagSettings({ autoCompactEnabled: true, autoCompactWindow: requestedNativeWindow });
|
|
644
|
-
let verified = await this.query.getContextUsage();
|
|
645
|
-
// The SDK applies flag-layer changes through an internal settings watcher;
|
|
646
|
-
// give that watcher a short bounded interval before declaring the native
|
|
647
|
-
// control unavailable.
|
|
648
|
-
for (let attempt = 0; attempt < 4
|
|
649
|
-
&& positiveNumber(verified.autoCompactThreshold) !== undefined
|
|
650
|
-
&& positiveNumber(verified.autoCompactThreshold) > configuredTokenLimit; attempt += 1) {
|
|
651
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
652
|
-
verified = await this.query.getContextUsage();
|
|
653
|
-
}
|
|
654
|
-
if (initial && positiveNumber(verified.autoCompactThreshold) > configuredTokenLimit) {
|
|
655
|
-
// Some SDK builds commit flag-layer changes to the active query state at
|
|
656
|
-
// the next streaming-input boundary. Use the same approved, tool-free
|
|
657
|
-
// setup exchange that the handoff monitor already recognizes, then
|
|
658
|
-
// verify again before any real role work is dispatched.
|
|
659
|
-
const setup = await this.sendTurnInternal("Rafi context setup only. Do not call tools or change files. Reply with exactly CONTEXT_READY.");
|
|
660
|
-
if (setup.isError || setup.text.trim() !== "CONTEXT_READY") {
|
|
661
|
-
throw new Error(`Claude context setup did not complete safely: ${setup.text.slice(0, 240)}`);
|
|
662
|
-
}
|
|
663
|
-
verified = await this.query.getContextUsage();
|
|
664
|
-
}
|
|
665
|
-
if (positiveNumber(verified.autoCompactThreshold) > configuredTokenLimit) {
|
|
666
|
-
// Current SDK releases accept applyFlagSettings but some noninteractive
|
|
667
|
-
// transports do not project autoCompactWindow into their already-created
|
|
668
|
-
// query state. Restart only that transport with equivalent inline flag
|
|
669
|
-
// settings and resume the exact scoped conversation.
|
|
670
|
-
await this.restartTransport(requestedNativeWindow);
|
|
671
|
-
verified = await this.query.getContextUsage();
|
|
672
|
-
}
|
|
673
|
-
if (!verified.isAutoCompactEnabled)
|
|
674
|
-
throw new Error("Claude disabled native auto-compaction during context configuration");
|
|
675
|
-
const installed = positiveNumber(verified.autoCompactThreshold);
|
|
676
|
-
if (!installed)
|
|
677
|
-
throw new Error("Claude did not report the installed native auto-compaction trigger");
|
|
678
|
-
if (installed > configuredTokenLimit) {
|
|
679
|
-
throw new Error(`Claude installed native compaction at ${installed} tokens, later than the configured ${configuredTokenLimit}-token ceiling `
|
|
680
|
-
+ `(requested SDK window ${requestedNativeWindow}; this ceiling may be below the provider's supported minimum)`);
|
|
681
|
-
}
|
|
682
|
-
const sample = this.makeContextUsage(verified);
|
|
683
|
-
this.lastContextUsage = sample;
|
|
684
|
-
this.eventQueue.push({ kind: "context-usage", ...sample });
|
|
685
|
-
return {
|
|
686
|
-
modelContextWindow: maximum,
|
|
687
|
-
configuredTokenLimit,
|
|
688
|
-
installedNativeTokenLimit: installed,
|
|
689
|
-
installedNativePercent: installed / maximum * 100,
|
|
690
|
-
sample,
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
async restartTransport(nativeAutoCompactWindow) {
|
|
694
|
-
if (this.pending)
|
|
695
|
-
throw new Error("Claude context transport cannot restart while a provider turn is active");
|
|
696
|
-
const expectedSessionId = this._sessionId;
|
|
697
|
-
const expectedSessionRef = this._sessionRef;
|
|
698
|
-
if (!expectedSessionId || !expectedSessionRef) {
|
|
699
|
-
throw new Error("Claude context transport restart requires an established location-scoped session");
|
|
700
|
-
}
|
|
701
|
-
const priorQuery = this.query;
|
|
702
|
-
const priorInbox = this.inbox;
|
|
703
|
-
const priorPump = this.pumpDone;
|
|
704
|
-
const generation = ++this.transportGeneration;
|
|
705
|
-
priorInbox.close();
|
|
706
|
-
priorQuery.close();
|
|
707
|
-
await priorPump.catch(() => { });
|
|
708
|
-
this.inbox = new AsyncQueue();
|
|
709
|
-
this.abort = new AbortController();
|
|
710
|
-
this.streamEnded = false;
|
|
711
|
-
this.terminalResult = undefined;
|
|
712
|
-
this.transportObservedSessionId = undefined;
|
|
713
|
-
this.query = this.openQuery(nativeAutoCompactWindow);
|
|
714
|
-
this.pumpDone = this.pump(this.query, generation);
|
|
715
|
-
await this.query.initializationResult();
|
|
716
|
-
for (let attempt = 0; attempt < 10 && !this.transportObservedSessionId; attempt += 1) {
|
|
717
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
718
|
-
}
|
|
719
|
-
if (this.transportObservedSessionId && this.transportObservedSessionId !== expectedSessionId) {
|
|
720
|
-
this.query.close();
|
|
721
|
-
throw new Error(`Claude context transport did not resume the exact scoped session ${expectedSessionId} `
|
|
722
|
-
+ `(observed ${this.transportObservedSessionId})`);
|
|
723
|
-
}
|
|
724
|
-
if (!this.transportObservedSessionId) {
|
|
725
|
-
// Claude does not always repeat the session-start frame until the next
|
|
726
|
-
// user message. Validate the exact persisted, location-scoped target;
|
|
727
|
-
// the immediately following context query then proves this transport is
|
|
728
|
-
// usable before any real role action is dispatched.
|
|
729
|
-
const availability = await probeClaudeSession(expectedSessionRef, {
|
|
730
|
-
cwd: this.opts.cwd,
|
|
731
|
-
configRoot: this.opts.configRoot,
|
|
732
|
-
workspaceIdentity: this.opts.workspaceIdentity,
|
|
733
|
-
role: this.opts.sessionRole,
|
|
734
|
-
stream: this.opts.sessionStream,
|
|
735
|
-
ticketId: this.opts.ticketId,
|
|
736
|
-
deliveryUnitId: this.opts.deliveryUnitId,
|
|
737
|
-
});
|
|
738
|
-
if (availability.status !== "available") {
|
|
739
|
-
this.query.close();
|
|
740
|
-
throw new Error(`Claude context transport could not validate scoped session ${expectedSessionId}: `
|
|
741
|
-
+ `${availability.reason ?? availability.status} (${availability.detail ?? "no provider detail"})`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
async completeCompaction(ok, reason, fallbackPostTokens) {
|
|
746
|
-
this.compactionTerminalObserved = true;
|
|
747
|
-
const active = this.activeCompaction;
|
|
748
|
-
const providerSequence = active?.providerSequence ?? ++this.compactionSequence;
|
|
749
|
-
const providerEventId = active?.providerEventId ?? `claude:${this._sessionId ?? "pending"}:${providerSequence}`;
|
|
750
|
-
const origin = active?.origin ?? (this.manualCompactionPending ? "rafi-manual" : "provider-auto");
|
|
751
|
-
this.activeCompaction = undefined;
|
|
752
|
-
if (!ok) {
|
|
753
|
-
this.eventQueue.push({
|
|
754
|
-
kind: "context-compaction", phase: "failed", origin, providerEventId, providerSequence,
|
|
755
|
-
provider: "claude", sessionId: this._sessionId, sessionRef: this._sessionRef,
|
|
756
|
-
observedAt: new Date().toISOString(), reason,
|
|
757
|
-
});
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
let postCompactSample;
|
|
761
|
-
try {
|
|
762
|
-
const usage = await this.query.getContextUsage();
|
|
763
|
-
postCompactSample = { ...this.makeContextUsage(usage), source: "post-compact" };
|
|
764
|
-
this.lastContextUsage = postCompactSample;
|
|
765
|
-
this.eventQueue.push({ kind: "context-usage", ...postCompactSample });
|
|
766
|
-
}
|
|
767
|
-
catch {
|
|
768
|
-
if (fallbackPostTokens !== undefined && this.lastContextUsage?.maximum) {
|
|
769
|
-
postCompactSample = {
|
|
770
|
-
...this.makeContextUsage({
|
|
771
|
-
totalTokens: fallbackPostTokens,
|
|
772
|
-
maxTokens: this.lastContextUsage.maximum,
|
|
773
|
-
model: this.opts.model,
|
|
774
|
-
}),
|
|
775
|
-
source: "post-compact",
|
|
776
|
-
};
|
|
777
|
-
this.lastContextUsage = postCompactSample;
|
|
778
|
-
this.eventQueue.push({ kind: "context-usage", ...postCompactSample });
|
|
779
|
-
}
|
|
780
|
-
// Otherwise the shared coordinator records this success as unverified
|
|
781
|
-
// and hands off after its own bounded query attempts.
|
|
782
|
-
}
|
|
783
|
-
this.eventQueue.push({
|
|
784
|
-
kind: "context-compaction", phase: "succeeded", origin, providerEventId, providerSequence,
|
|
785
|
-
provider: "claude", sessionId: this._sessionId, sessionRef: this._sessionRef,
|
|
786
|
-
observedAt: new Date().toISOString(), ...(postCompactSample ? { postCompactSample } : {}),
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
551
|
settlePending(result, emit = true) {
|
|
790
552
|
const pending = this.pending;
|
|
791
553
|
if (!pending)
|
|
@@ -813,20 +575,14 @@ export class ClaudeAdapter {
|
|
|
813
575
|
}
|
|
814
576
|
}
|
|
815
577
|
function finiteNumber(value) { const parsed = Number(value); return value !== null && value !== undefined && value !== "" && Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined; }
|
|
816
|
-
function
|
|
817
|
-
|
|
818
|
-
|
|
578
|
+
function validThreshold(value) {
|
|
579
|
+
if (!Number.isInteger(value) || value < 1 || value > 99)
|
|
580
|
+
throw new Error("automatic compaction threshold must be an integer from 1 to 99");
|
|
581
|
+
return value;
|
|
819
582
|
}
|
|
820
|
-
function
|
|
583
|
+
function tokenLimit(maximum, percentage) {
|
|
821
584
|
return Math.max(1, Math.floor(maximum * percentage / 100));
|
|
822
585
|
}
|
|
823
|
-
function validateContextPolicy(policy) {
|
|
824
|
-
if (!Number.isInteger(policy.configuredThresholdPercent) || policy.configuredThresholdPercent < 1 || policy.configuredThresholdPercent > 99) {
|
|
825
|
-
throw new Error("configured context threshold must be an integer from 1 to 99");
|
|
826
|
-
}
|
|
827
|
-
if (!Number.isSafeInteger(policy.compactMaximum) || policy.compactMaximum < 1)
|
|
828
|
-
throw new Error("compact maximum must be a positive safe integer");
|
|
829
|
-
}
|
|
830
586
|
function isMissingClaudeSession(message) {
|
|
831
587
|
return /no conversation found with session id|session .* not found|conversation .* not found/i.test(message);
|
|
832
588
|
}
|
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, NativeAutoCompactionPolicy, 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,17 @@ 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
|
|
39
|
-
private compactionSequence;
|
|
40
|
-
private activeCompaction?;
|
|
41
|
-
private manualCompactionPending;
|
|
30
|
+
private nativeAutoCompactTokenLimit?;
|
|
31
|
+
private autoCompactionPrepared;
|
|
32
|
+
private preparedAutoCompactThreshold?;
|
|
33
|
+
private preparedAutoCompactionPolicy?;
|
|
34
|
+
private nativeCompactions;
|
|
35
|
+
private nativeCompactionSequence;
|
|
36
|
+
private manualCompactionInFlight;
|
|
37
|
+
private observedToolCalls;
|
|
42
38
|
constructor(opts: BuilderAdapterOptions);
|
|
43
39
|
buildInstruction(instruction: string): string;
|
|
44
40
|
buildAppServerArgs(): string[];
|
|
@@ -46,11 +42,13 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
46
42
|
buildArgs(instruction: string): string[];
|
|
47
43
|
sendTurn(instruction: string): Promise<TurnResult>;
|
|
48
44
|
private sendTurnInternal;
|
|
49
|
-
prepareContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
|
|
50
|
-
updateContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
|
|
51
|
-
interruptTurnAtCompactionBoundary(providerEventId?: string): Promise<InterruptResult>;
|
|
52
45
|
compact(): Promise<CompactResult>;
|
|
46
|
+
prepareAutoCompaction(thresholdPercent?: number | undefined): Promise<NativeAutoCompactionPolicy | void>;
|
|
47
|
+
autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
|
|
48
|
+
drainNativeCompactions(): NativeCompaction[];
|
|
49
|
+
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
53
50
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
51
|
+
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
54
52
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
55
53
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
56
54
|
sessionId(): string | undefined;
|
|
@@ -67,7 +65,6 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
67
65
|
private handle;
|
|
68
66
|
private waitFor;
|
|
69
67
|
private disconnect;
|
|
70
|
-
|
|
71
|
-
private restartTransport;
|
|
68
|
+
private restartForAutoCompaction;
|
|
72
69
|
private buildSkillsAppendix;
|
|
73
70
|
}
|