ai-foreman 1.7.18 → 1.7.20
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 +33 -8
- package/dist/activity.d.ts +11 -1
- package/dist/activity.js +70 -15
- package/dist/adapters/claude.d.ts +11 -1
- package/dist/adapters/claude.js +122 -18
- package/dist/adapters/codex.d.ts +13 -1
- package/dist/adapters/codex.js +148 -31
- package/dist/adapters/recovering.d.ts +3 -1
- package/dist/adapters/recovering.js +12 -4
- package/dist/adapters/types.d.ts +27 -1
- package/dist/agentRun.js +2 -0
- package/dist/branch/currentGuard.d.ts +3 -1
- package/dist/branch/currentGuard.js +11 -2
- package/dist/branch/resume.js +1 -1
- package/dist/branch/runner.d.ts +2 -0
- package/dist/branch/runner.js +92 -12
- package/dist/buildAdmission.d.ts +61 -0
- package/dist/buildAdmission.js +284 -0
- package/dist/buildApproval.d.ts +11 -0
- package/dist/buildApproval.js +143 -0
- package/dist/buildLaunchGate.d.ts +1 -0
- package/dist/buildLaunchGate.js +36 -0
- package/dist/buildRuns.d.ts +18 -1
- package/dist/buildRuns.js +261 -83
- package/dist/cli/recovery.js +9 -13
- package/dist/cli/runtimeAuthPrompt.d.ts +12 -0
- package/dist/cli/runtimeAuthPrompt.js +55 -6
- package/dist/cli/start.d.ts +24 -1
- package/dist/cli/start.js +2026 -1808
- package/dist/cli/tickets.d.ts +11 -0
- package/dist/cli/tickets.js +382 -145
- package/dist/continuity.d.ts +12 -2
- package/dist/continuity.js +131 -12
- package/dist/diagnostics.js +35 -1
- package/dist/foreman.d.ts +10 -3
- package/dist/foreman.js +207 -46
- package/dist/handoffs.js +121 -79
- package/dist/humanDecision.d.ts +18 -0
- package/dist/humanDecision.js +44 -0
- package/dist/index.js +4 -2
- package/dist/log.d.ts +1 -1
- package/dist/managerPacket.js +1 -0
- package/dist/observability.js +2 -1
- package/dist/processIdentity.d.ts +21 -1
- package/dist/processIdentity.js +80 -5
- package/dist/providerQuestions.d.ts +5 -0
- package/dist/providerQuestions.js +24 -3
- package/dist/qaDeliveryJournal.d.ts +70 -0
- package/dist/qaDeliveryJournal.js +1 -0
- package/dist/qaFailureDelivery.d.ts +26 -10
- package/dist/qaFailureDelivery.js +399 -412
- package/dist/qaHandbackHistory.d.ts +4 -0
- package/dist/qaHandbackHistory.js +30 -0
- package/dist/qaHandbackMigration.d.ts +9 -0
- package/dist/qaHandbackMigration.js +147 -0
- package/dist/qaPrerequisites.d.ts +26 -0
- package/dist/qaPrerequisites.js +61 -0
- package/dist/qaProtocolV2.d.ts +8 -0
- package/dist/qaProtocolV2.js +3 -3
- package/dist/qaReview.d.ts +8 -0
- package/dist/qaReview.js +113 -27
- package/dist/questionTrace.d.ts +31 -0
- package/dist/questionTrace.js +85 -0
- package/dist/readOnlyProposal.d.ts +8 -0
- package/dist/readOnlyProposal.js +41 -0
- package/dist/readinessCleanup.d.ts +19 -0
- package/dist/readinessCleanup.js +115 -0
- package/dist/readinessGate.d.ts +1 -0
- package/dist/readinessGate.js +82 -0
- package/dist/recoveryGuidance.d.ts +8 -0
- package/dist/recoveryGuidance.js +25 -0
- package/dist/recoveryPolicy.js +14 -3
- package/dist/runtimeAuth.d.ts +2 -1
- package/dist/runtimeAuth.js +2 -2
- package/dist/runtimeReadiness.d.ts +25 -1
- package/dist/runtimeReadiness.js +256 -14
- package/dist/sessionLifecycle.d.ts +1 -0
- package/dist/sessionLifecycle.js +53 -3
- package/dist/sources/sourceRegistry.d.ts +7 -0
- package/dist/sources/sourceRegistry.js +14 -0
- package/dist/stateTransfer.js +37 -7
- package/dist/statusReporter.d.ts +3 -1
- package/dist/statusReporter.js +8 -2
- package/dist/supervisedStart.d.ts +18 -0
- package/dist/supervisedStart.js +382 -0
- package/dist/supervisor.d.ts +2 -0
- package/dist/supervisor.js +70 -24
- package/dist/ticketPopulation.js +1 -0
- package/dist/tickets/setupConfig.d.ts +8 -0
- package/dist/tickets/setupConfig.js +38 -4
- package/dist/util/deadline.d.ts +13 -0
- package/dist/util/deadline.js +60 -0
- package/dist/windowsProbeJob.d.ts +12 -0
- package/dist/windowsProbeJob.js +144 -0
- package/dist/workflowDb.d.ts +125 -12
- package/dist/workflowDb.js +810 -72
- package/dist/workflowReader.d.ts +17 -2
- package/dist/workflowReader.js +118 -1
- package/package.json +8 -4
package/dist/adapters/claude.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OperationDeadline } from "../util/deadline.js";
|
|
1
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
/** Lazy-load the Claude Agent SDK. Throws an actionable error if not installed. */
|
|
@@ -11,8 +12,9 @@ export async function requireClaudeSDK() {
|
|
|
11
12
|
"Or use Codex for this run with --agent codex.");
|
|
12
13
|
}
|
|
13
14
|
}
|
|
15
|
+
import { QuestionRoundTripTrace } from "../questionTrace.js";
|
|
14
16
|
import { AsyncQueue } from "../util/asyncQueue.js";
|
|
15
|
-
import { BuilderEventQueue, withActivityPhase } from "../activity.js";
|
|
17
|
+
import { BuilderEventQueue, currentActivity, withActivityPhase } from "../activity.js";
|
|
16
18
|
import { normalizeRuntimeErrorText } from "../runtimeAuth.js";
|
|
17
19
|
import { classifyClaudeSdkFailure, resolveExecutablePath, sanitizeDiagnostics, } from "../runtimeReadiness.js";
|
|
18
20
|
import { createProviderSessionRef, validateProviderSessionScope, canonicalSessionPath } from "../sessionIdentity.js";
|
|
@@ -178,6 +180,8 @@ export class ClaudeAdapter {
|
|
|
178
180
|
structuredError;
|
|
179
181
|
apiErrorStatus;
|
|
180
182
|
compactResult;
|
|
183
|
+
manualCompactionDeadline;
|
|
184
|
+
compactionPromise;
|
|
181
185
|
autoCompactionPrepared = false;
|
|
182
186
|
preparedAutoCompactThreshold;
|
|
183
187
|
preparedAutoCompactionPolicy;
|
|
@@ -190,6 +194,8 @@ export class ClaudeAdapter {
|
|
|
190
194
|
activeProviderTurnSpanId;
|
|
191
195
|
toolSpans = new Map();
|
|
192
196
|
pending;
|
|
197
|
+
questionTrace;
|
|
198
|
+
responseOnlyTurn = false;
|
|
193
199
|
terminalResult;
|
|
194
200
|
streamEnded = false;
|
|
195
201
|
closed = false;
|
|
@@ -240,6 +246,10 @@ export class ClaudeAdapter {
|
|
|
240
246
|
this.rejectSessionIdentity = reject;
|
|
241
247
|
});
|
|
242
248
|
void this.sessionIdentityReady.catch(() => { });
|
|
249
|
+
this.questionTrace = new QuestionRoundTripTrace(opts.runtimePhase ?? "builder", opts.onQuestionTrace, opts.observer ? {
|
|
250
|
+
start: (attemptId) => opts.observer.store.startSpan(this.observationContext(), { kind: "provider_wait", name: "Waiting for Claude after question answer", attributes: { attemptId } }),
|
|
251
|
+
finish: (spanId, outcome) => opts.observer.store.finishSpan(spanId, { outcome }),
|
|
252
|
+
} : undefined);
|
|
243
253
|
this.query = query({
|
|
244
254
|
prompt: this.inbox,
|
|
245
255
|
options: {
|
|
@@ -259,7 +269,10 @@ export class ClaudeAdapter {
|
|
|
259
269
|
}] }] },
|
|
260
270
|
stderr: (data) => this.captureStderr(data),
|
|
261
271
|
canUseTool: async (toolName, input, requestOptions = {}) => {
|
|
272
|
+
if (this.responseOnlyTurn)
|
|
273
|
+
return { behavior: "deny", message: "QA response correction forbids all tools, including read-only tools", interrupt: true };
|
|
262
274
|
const isProviderQuestion = toolName === "AskUserQuestion";
|
|
275
|
+
const questionAttempt = isProviderQuestion ? this.questionTrace?.begin() : undefined;
|
|
263
276
|
if (isProviderQuestion)
|
|
264
277
|
this.beginProviderQuestionWait();
|
|
265
278
|
try {
|
|
@@ -274,11 +287,16 @@ export class ClaudeAdapter {
|
|
|
274
287
|
blockedPath: requestOptions.blockedPath,
|
|
275
288
|
toolUseID: requestOptions.toolUseID,
|
|
276
289
|
});
|
|
290
|
+
const result = permissionDecisionToClaudeResult(decision, requestOptions.toolUseID);
|
|
291
|
+
if (questionAttempt)
|
|
292
|
+
this.questionTrace?.returned(questionAttempt, decision.behavior === "allow" ? "allowed" : "denied");
|
|
277
293
|
if (isProviderQuestion)
|
|
278
294
|
this.endProviderQuestionWait(decision.behavior === "allow");
|
|
279
|
-
return
|
|
295
|
+
return result;
|
|
280
296
|
}
|
|
281
297
|
catch (error) {
|
|
298
|
+
if (questionAttempt)
|
|
299
|
+
this.questionTrace?.returned(questionAttempt, "callback-error");
|
|
282
300
|
if (isProviderQuestion)
|
|
283
301
|
this.endProviderQuestionWait(false);
|
|
284
302
|
throw error;
|
|
@@ -296,6 +314,7 @@ export class ClaudeAdapter {
|
|
|
296
314
|
}
|
|
297
315
|
}
|
|
298
316
|
catch (err) {
|
|
317
|
+
this.questionTrace?.finish(this.closed ? "closed" : "stream-error");
|
|
299
318
|
// Suppress the AbortError that fires when close() aborts the stream.
|
|
300
319
|
const isShutdownAbort = this.closed &&
|
|
301
320
|
(err instanceof Error &&
|
|
@@ -311,6 +330,7 @@ export class ClaudeAdapter {
|
|
|
311
330
|
}
|
|
312
331
|
}
|
|
313
332
|
finally {
|
|
333
|
+
this.questionTrace?.finish(this.closed ? "closed" : "stream-ended");
|
|
314
334
|
this.streamEnded = true;
|
|
315
335
|
if (!this.closed && this.pending) {
|
|
316
336
|
const result = this.streamFailureResult("Claude stream ended without a result", Boolean(this.opts.resumeSessionRef ?? this.opts.resumeSessionId));
|
|
@@ -326,6 +346,15 @@ export class ClaudeAdapter {
|
|
|
326
346
|
}
|
|
327
347
|
}
|
|
328
348
|
handle(msg) {
|
|
349
|
+
const questionStreamOutcome = msg.type === "system" && msg.subtype === "api_retry" ? "provider-retry"
|
|
350
|
+
: msg.type === "auth_status" && msg.error ? "provider-auth-error"
|
|
351
|
+
: msg.type === "assistant" && (msg.error === "authentication_failed" || msg.error === "oauth_org_not_allowed") ? "provider-auth-error"
|
|
352
|
+
: msg.type === "assistant" && msg.error ? "provider-error" : "stream-message";
|
|
353
|
+
this.questionTrace?.message(questionStreamOutcome);
|
|
354
|
+
if (this.pending?.providerQuestionAnswered && !this.pending.providerQuestionWaits) {
|
|
355
|
+
this.pending.providerQuestionAnswered = false;
|
|
356
|
+
currentActivity()?.update("Claude stream resumed");
|
|
357
|
+
}
|
|
329
358
|
this.touchPendingTurn();
|
|
330
359
|
if ("session_id" in msg && typeof msg.session_id === "string") {
|
|
331
360
|
this.observeSession(msg.session_id, "cwd" in msg && typeof msg.cwd === "string" ? msg.cwd : undefined);
|
|
@@ -370,8 +399,11 @@ export class ClaudeAdapter {
|
|
|
370
399
|
this.turnSignals.push(...msg.output.map((line) => `auth: ${line}`));
|
|
371
400
|
}
|
|
372
401
|
else if (msg.type === "system" && msg.subtype === "status") {
|
|
373
|
-
if (msg.status === "compacting")
|
|
402
|
+
if (msg.status === "compacting") {
|
|
403
|
+
if (this.manualCompactionInFlight && msg.session_id === this._sessionId)
|
|
404
|
+
this.manualCompactionDeadline?.extendForCorrelatedProgress();
|
|
374
405
|
this.eventQueue.push({ kind: "session-transition", transition: "compacting" });
|
|
406
|
+
}
|
|
375
407
|
if (msg.compact_result === "success") {
|
|
376
408
|
this.compactResult = { ok: true };
|
|
377
409
|
this.eventQueue.push({ kind: "session-transition", transition: "compacted" });
|
|
@@ -476,7 +508,10 @@ export class ClaudeAdapter {
|
|
|
476
508
|
this.stderrChunks.length = 0;
|
|
477
509
|
}
|
|
478
510
|
}
|
|
479
|
-
async sendTurn(text) {
|
|
511
|
+
async sendTurn(text, policy) {
|
|
512
|
+
if (this.pending)
|
|
513
|
+
throw new Error("a turn is already in progress");
|
|
514
|
+
this.responseOnlyTurn = Boolean(policy?.responseOnly);
|
|
480
515
|
const turnId = randomUUID();
|
|
481
516
|
this.activeProviderTurnId = turnId;
|
|
482
517
|
const observer = this.opts.observer;
|
|
@@ -486,6 +521,7 @@ export class ClaudeAdapter {
|
|
|
486
521
|
}
|
|
487
522
|
finally {
|
|
488
523
|
this.activeProviderTurnId = undefined;
|
|
524
|
+
this.responseOnlyTurn = false;
|
|
489
525
|
}
|
|
490
526
|
}
|
|
491
527
|
const context = this.observationContext();
|
|
@@ -512,6 +548,7 @@ export class ClaudeAdapter {
|
|
|
512
548
|
}
|
|
513
549
|
this.activeProviderTurnId = undefined;
|
|
514
550
|
this.activeProviderTurnSpanId = undefined;
|
|
551
|
+
this.responseOnlyTurn = false;
|
|
515
552
|
}
|
|
516
553
|
}
|
|
517
554
|
sendTurnInternal(text) {
|
|
@@ -577,7 +614,7 @@ export class ClaudeAdapter {
|
|
|
577
614
|
return this._sessionId;
|
|
578
615
|
}
|
|
579
616
|
sessionRef() { return this._sessionRef; }
|
|
580
|
-
async prepareSession(timeoutMs =
|
|
617
|
+
async prepareSession(timeoutMs = this.opts.preparationTimeoutMs ?? 120_000) {
|
|
581
618
|
if (this.closed || this.streamEnded || this.terminalResult)
|
|
582
619
|
throw new Error(this.terminalResult?.text ?? "Claude session is closed");
|
|
583
620
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
@@ -592,7 +629,24 @@ export class ClaudeAdapter {
|
|
|
592
629
|
aborted();
|
|
593
630
|
timer = setTimeout(() => reject(new Error(`Claude did not expose a scoped session identity within ${timeoutMs}ms`)), timeoutMs);
|
|
594
631
|
});
|
|
595
|
-
const
|
|
632
|
+
const prepare = async () => {
|
|
633
|
+
await this.query.initializationResult();
|
|
634
|
+
if (!this._sessionRef?.validatedAt && typeof this.query.supportedCommands === "function") {
|
|
635
|
+
const commands = await this.query.supportedCommands();
|
|
636
|
+
// Some SDK/CLI pairs expose init identity only after their first input.
|
|
637
|
+
// Require an advertised built-in local command; never substitute a
|
|
638
|
+
// model-generated warm-up for QA's pre-dispatch identity validation.
|
|
639
|
+
if (commands.some(command => command.name === "context" && "builtin" in command && command.builtin === true)) {
|
|
640
|
+
this.opts.onLifecycleTrace?.({ phase: "local-context-initialization-start", at: new Date().toISOString() });
|
|
641
|
+
const result = await this.sendTurn("/context", { responseOnly: true });
|
|
642
|
+
this.opts.onLifecycleTrace?.({ phase: "local-context-initialization-complete", at: new Date().toISOString() });
|
|
643
|
+
if (result.isError || result.failure || result.costUsd > 0)
|
|
644
|
+
throw new Error("Claude local context initialization did not complete without model work");
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return this.sessionIdentityReady;
|
|
648
|
+
};
|
|
649
|
+
const ref = await Promise.race([prepare(), interrupted]);
|
|
596
650
|
if (!ref.validatedAt)
|
|
597
651
|
throw new Error("Claude session identity was not provider-validated during initialization");
|
|
598
652
|
return ref;
|
|
@@ -627,19 +681,38 @@ export class ClaudeAdapter {
|
|
|
627
681
|
});
|
|
628
682
|
}
|
|
629
683
|
async compact() {
|
|
630
|
-
|
|
684
|
+
if (this.compactionPromise)
|
|
685
|
+
return this.compactionPromise;
|
|
686
|
+
const operation = this.opts.observer
|
|
631
687
|
? this.opts.observer.span("compaction", "Claude context compaction", () => this.compactInternal())
|
|
632
688
|
: this.compactInternal();
|
|
689
|
+
this.compactionPromise = operation;
|
|
690
|
+
try {
|
|
691
|
+
return await operation;
|
|
692
|
+
}
|
|
693
|
+
finally {
|
|
694
|
+
if (this.compactionPromise === operation)
|
|
695
|
+
this.compactionPromise = undefined;
|
|
696
|
+
}
|
|
633
697
|
}
|
|
634
698
|
async compactInternal() {
|
|
635
699
|
this.compactResult = undefined;
|
|
636
700
|
this.manualCompactionInFlight = true;
|
|
701
|
+
const normalMs = Math.min(180_000, Math.max(1, this.opts.compactionTimeoutMs ?? 120_000));
|
|
702
|
+
const deadline = new OperationDeadline("Claude compaction", normalMs, Math.min(180_000, normalMs * 1.5));
|
|
703
|
+
this.manualCompactionDeadline = deadline;
|
|
637
704
|
let result;
|
|
638
705
|
try {
|
|
639
|
-
result = await this.sendTurn("/compact");
|
|
706
|
+
result = await deadline.run(() => this.sendTurn("/compact"), () => this.abort.abort());
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
const failure = new SessionUnavailableError({ runtime: "claude", phase: "turn", dispatchState: "unknown", executable: this.opts.runtimeExecutable ?? "claude", cwd: this.opts.cwd, diagnostics: String(error) });
|
|
710
|
+
await this.close();
|
|
711
|
+
return { ok: false, error: failure.message, failure: failure.failure };
|
|
640
712
|
}
|
|
641
713
|
finally {
|
|
642
714
|
this.manualCompactionInFlight = false;
|
|
715
|
+
this.manualCompactionDeadline = undefined;
|
|
643
716
|
}
|
|
644
717
|
if (result.failure?.category === "session-unavailable") {
|
|
645
718
|
return { ok: false, error: result.text || result.failure.diagnostics, failure: result.failure };
|
|
@@ -649,15 +722,23 @@ export class ClaudeAdapter {
|
|
|
649
722
|
return { ok: false, error: sanitizeDiagnostics(result.text || "Claude did not emit an explicit compact status") };
|
|
650
723
|
}
|
|
651
724
|
async prepareAutoCompaction(thresholdPercent = this.opts.autoCompactThresholdPercent) {
|
|
725
|
+
return new OperationDeadline("Claude auto-compaction preparation", this.opts.preparationTimeoutMs ?? 120_000).run(() => this.prepareAutoCompactionInternal(thresholdPercent), () => this.abort.abort());
|
|
726
|
+
}
|
|
727
|
+
async prepareAutoCompactionInternal(thresholdPercent = this.opts.autoCompactThresholdPercent) {
|
|
652
728
|
if (thresholdPercent === undefined)
|
|
653
729
|
return;
|
|
654
730
|
const threshold = validThreshold(thresholdPercent);
|
|
655
731
|
if (this.autoCompactionPrepared && this.preparedAutoCompactThreshold === threshold)
|
|
656
732
|
return this.preparedAutoCompactionPolicy;
|
|
657
733
|
this.opts.autoCompactThresholdPercent = threshold;
|
|
658
|
-
await this.query.initializationResult();
|
|
659
|
-
|
|
660
|
-
|
|
734
|
+
await new OperationDeadline("Claude initialization", this.opts.preparationTimeoutMs ?? 120_000).run(() => this.query.initializationResult());
|
|
735
|
+
// The SDK can acknowledge initialization while its CLI is still waiting
|
|
736
|
+
// for first input. Sending getContextUsage then deadlocks the control
|
|
737
|
+
// queue, including a later local command. Establish identity first.
|
|
738
|
+
if (!this._sessionRef?.validatedAt && typeof this.query.supportedCommands === "function")
|
|
739
|
+
await this.prepareSession();
|
|
740
|
+
await new OperationDeadline("Claude settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ autoCompactEnabled: true }));
|
|
741
|
+
const baseline = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
661
742
|
if (!Number.isFinite(baseline.maxTokens) || baseline.maxTokens <= 0
|
|
662
743
|
|| !Number.isFinite(baseline.autoCompactThreshold) || baseline.autoCompactThreshold === undefined
|
|
663
744
|
|| !baseline.isAutoCompactEnabled) {
|
|
@@ -670,8 +751,8 @@ export class ClaudeAdapter {
|
|
|
670
751
|
if (reserve < 0)
|
|
671
752
|
throw new Error("Claude reported an invalid automatic-compaction reserve");
|
|
672
753
|
const requestedWindow = tokenLimit(baseline.maxTokens, threshold) + reserve;
|
|
673
|
-
await this.query.applyFlagSettings({ autoCompactEnabled: true, autoCompactWindow: requestedWindow });
|
|
674
|
-
const installed = await this.query.getContextUsage();
|
|
754
|
+
await new OperationDeadline("Claude settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ autoCompactEnabled: true, autoCompactWindow: requestedWindow }));
|
|
755
|
+
const installed = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
675
756
|
if (!installed.isAutoCompactEnabled || !Number.isFinite(installed.maxTokens) || installed.maxTokens <= 0
|
|
676
757
|
|| !Number.isFinite(installed.autoCompactThreshold) || installed.autoCompactThreshold === undefined) {
|
|
677
758
|
throw new Error("Claude did not expose an enabled native automatic-compaction threshold after configuration");
|
|
@@ -700,7 +781,7 @@ export class ClaudeAdapter {
|
|
|
700
781
|
restoreNativeCompactions(compactions) { this.nativeCompactions.unshift(...compactions); }
|
|
701
782
|
async contextUsage() {
|
|
702
783
|
try {
|
|
703
|
-
const usage = await this.query.getContextUsage();
|
|
784
|
+
const usage = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
704
785
|
const result = { used: usage.totalTokens, maximum: usage.maxTokens, percentage: usage.percentage, observedAt: new Date().toISOString(), source: "provider-query" };
|
|
705
786
|
this.eventQueue.push({ kind: "context-usage", ...result });
|
|
706
787
|
return result;
|
|
@@ -717,12 +798,13 @@ export class ClaudeAdapter {
|
|
|
717
798
|
return { ok: false, error: "Claude SDK cannot change reasoning/fast controls on an existing transport" };
|
|
718
799
|
if (settings.model === this.opts.model)
|
|
719
800
|
return { ok: true };
|
|
720
|
-
const result = await this.sendTurn(`/model ${settings.model ?? "default"}`);
|
|
801
|
+
const result = await new OperationDeadline("Claude model settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.sendTurn(`/model ${settings.model ?? "default"}`), () => this.abort.abort());
|
|
721
802
|
if (result.isError)
|
|
722
803
|
return { ok: false, error: result.text, ...(result.failure ? { failure: result.failure } : {}) };
|
|
723
804
|
this.opts.model = settings.model;
|
|
724
805
|
return { ok: true };
|
|
725
806
|
}
|
|
807
|
+
observeEvents(listener) { return this.eventQueue.observe(listener); }
|
|
726
808
|
events() {
|
|
727
809
|
return this.eventQueue;
|
|
728
810
|
}
|
|
@@ -730,16 +812,18 @@ export class ClaudeAdapter {
|
|
|
730
812
|
if (this.closed)
|
|
731
813
|
return;
|
|
732
814
|
this.closed = true;
|
|
815
|
+
this.questionTrace?.finish("closed");
|
|
733
816
|
this.rejectSessionIdentity(new Error("Claude session closed during initialization"));
|
|
734
817
|
this.abort.abort();
|
|
735
818
|
this.inbox.close();
|
|
736
819
|
try {
|
|
737
|
-
await this.query.interrupt();
|
|
820
|
+
await new OperationDeadline("Claude interrupt", this.opts.shutdownTimeoutMs ?? 10_000).run(() => this.query.interrupt());
|
|
738
821
|
}
|
|
739
822
|
catch {
|
|
740
823
|
// interrupt is best-effort — ignore if no turn is active
|
|
741
824
|
}
|
|
742
|
-
|
|
825
|
+
this.query.close?.();
|
|
826
|
+
await new OperationDeadline("Claude shutdown", this.opts.shutdownTimeoutMs ?? 10_000).run(() => this.pumpDone).catch(() => { });
|
|
743
827
|
}
|
|
744
828
|
failSessionIdentity(error) {
|
|
745
829
|
this.rejectSessionIdentity(error);
|
|
@@ -784,12 +868,15 @@ export class ClaudeAdapter {
|
|
|
784
868
|
this.resolveSessionIdentity(this._sessionRef);
|
|
785
869
|
}
|
|
786
870
|
settlePending(result, emit = true) {
|
|
871
|
+
this.questionTrace?.finish(result.isError ? "result-error" : "result");
|
|
787
872
|
const pending = this.pending;
|
|
788
873
|
if (!pending)
|
|
789
874
|
return;
|
|
790
875
|
this.pending = undefined;
|
|
791
876
|
if (pending.idleTimer)
|
|
792
877
|
clearTimeout(pending.idleTimer);
|
|
878
|
+
if (pending.hardTimer)
|
|
879
|
+
clearTimeout(pending.hardTimer);
|
|
793
880
|
if (!result.turnId) {
|
|
794
881
|
result.turnId = pending.turnId;
|
|
795
882
|
result.hostInstruction = pending.instruction;
|
|
@@ -814,6 +901,11 @@ export class ClaudeAdapter {
|
|
|
814
901
|
if (pending.idleTimer)
|
|
815
902
|
clearTimeout(pending.idleTimer);
|
|
816
903
|
pending.idleTimer = undefined;
|
|
904
|
+
if (pending.hardTimer) {
|
|
905
|
+
clearTimeout(pending.hardTimer);
|
|
906
|
+
pending.hardTimer = undefined;
|
|
907
|
+
pending.hardRemainingMs = Math.max(0, (pending.hardRemainingMs ?? this.opts.turnDeadlineMs ?? 3_600_000) - (performance.now() - (pending.hardArmedAt ?? performance.now())));
|
|
908
|
+
}
|
|
817
909
|
}
|
|
818
910
|
endProviderQuestionWait(answered) {
|
|
819
911
|
const pending = this.pending;
|
|
@@ -829,11 +921,23 @@ export class ClaudeAdapter {
|
|
|
829
921
|
clearTimeout(pending.idleTimer);
|
|
830
922
|
if ((pending.providerQuestionWaits ?? 0) > 0)
|
|
831
923
|
return;
|
|
924
|
+
if (!pending.hardTimer) {
|
|
925
|
+
pending.hardRemainingMs ??= this.opts.turnDeadlineMs ?? 3_600_000;
|
|
926
|
+
pending.hardArmedAt = performance.now();
|
|
927
|
+
pending.hardTimer = setTimeout(() => {
|
|
928
|
+
if (this.pending !== pending)
|
|
929
|
+
return;
|
|
930
|
+
this.settlePending(this.streamFailureResult("Claude active turn deadline exceeded; dispatched work requires reconciliation", false));
|
|
931
|
+
void this.close();
|
|
932
|
+
}, pending.hardRemainingMs);
|
|
933
|
+
pending.hardTimer.unref();
|
|
934
|
+
}
|
|
832
935
|
const timeoutMs = this.providerIdleTimeoutMs();
|
|
833
936
|
pending.idleTimer = setTimeout(() => {
|
|
834
937
|
if (this.pending !== pending || this.closed)
|
|
835
938
|
return;
|
|
836
|
-
|
|
939
|
+
this.questionTrace?.finish("idle-timeout");
|
|
940
|
+
const context = pending.providerQuestionAnswered ? " after your answer was sent; Claude has not resumed its stream yet" : "";
|
|
837
941
|
const message = `Claude provider was silent for ${Math.round(timeoutMs / 60_000)} minutes${context}; the turn may have been dispatched and will not be retried automatically`;
|
|
838
942
|
const result = this.streamFailureResult(message, false);
|
|
839
943
|
this.eventQueue.push({ kind: "error", message });
|
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
25
25
|
private usage?;
|
|
26
26
|
private usageRevision;
|
|
27
27
|
private providerSessionUsage?;
|
|
28
|
+
private turnUsageBaseline?;
|
|
28
29
|
private lastTurnTokens?;
|
|
29
30
|
private stderr;
|
|
30
31
|
private nativeAutoCompactTokenLimit?;
|
|
@@ -34,6 +35,9 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
34
35
|
private nativeCompactions;
|
|
35
36
|
private nativeCompactionSequence;
|
|
36
37
|
private manualCompactionInFlight;
|
|
38
|
+
private compactionPromise?;
|
|
39
|
+
private compactionActive;
|
|
40
|
+
private compactionUncertain;
|
|
37
41
|
private observedToolCalls;
|
|
38
42
|
private activeProviderTurnId?;
|
|
39
43
|
private activeProviderTurnSpanId?;
|
|
@@ -43,11 +47,16 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
43
47
|
buildAppServerArgs(): string[];
|
|
44
48
|
/** Deprecated fixture helper. Runtime execution uses only app-server. */
|
|
45
49
|
buildArgs(instruction: string): string[];
|
|
46
|
-
sendTurn(instruction: string
|
|
50
|
+
sendTurn(instruction: string, policy?: {
|
|
51
|
+
handback?: boolean;
|
|
52
|
+
responseOnly?: boolean;
|
|
53
|
+
logicalActionId?: string;
|
|
54
|
+
}): Promise<TurnResult>;
|
|
47
55
|
private sendTurnInternal;
|
|
48
56
|
compact(): Promise<CompactResult>;
|
|
49
57
|
private compactInternal;
|
|
50
58
|
prepareAutoCompaction(thresholdPercent?: number | undefined): Promise<NativeAutoCompactionPolicy | void>;
|
|
59
|
+
private prepareAutoCompactionInternal;
|
|
51
60
|
requiresAutoCompactionSetupTurn(): boolean;
|
|
52
61
|
autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
|
|
53
62
|
drainNativeCompactions(): NativeCompaction[];
|
|
@@ -61,9 +70,11 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
61
70
|
prepareSession(): Promise<ProviderSessionRefV1>;
|
|
62
71
|
adoptSessionRef(ref: ProviderSessionRefV1): void;
|
|
63
72
|
validateSession(): Promise<SessionAvailabilityV1>;
|
|
73
|
+
observeEvents(listener: (event: BuilderEvent) => void): () => void;
|
|
64
74
|
events(): AsyncIterable<BuilderEvent>;
|
|
65
75
|
close(): Promise<void>;
|
|
66
76
|
private ensureThread;
|
|
77
|
+
private ensureThreadInternal;
|
|
67
78
|
private threadAttached;
|
|
68
79
|
private ensureConnection;
|
|
69
80
|
private request;
|
|
@@ -71,6 +82,7 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
71
82
|
private handle;
|
|
72
83
|
private observationContext;
|
|
73
84
|
private waitFor;
|
|
85
|
+
private traceLifecycle;
|
|
74
86
|
private touchWaiters;
|
|
75
87
|
private providerIdleTimeoutMs;
|
|
76
88
|
private disconnect;
|