ai-foreman 1.7.29 → 2.0.1
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 -1
- package/dist/adapters/claude.d.ts +23 -12
- package/dist/adapters/claude.js +231 -26
- package/dist/adapters/codex.d.ts +19 -7
- package/dist/adapters/codex.js +202 -19
- package/dist/adapters/recovering.d.ts +2 -0
- package/dist/adapters/recovering.js +5 -1
- package/dist/adapters/structured.d.ts +71 -0
- package/dist/adapters/structured.js +213 -0
- package/dist/adapters/types.d.ts +85 -9
- package/dist/agentRun.d.ts +2 -0
- package/dist/agentRun.js +2 -1
- package/dist/boundedTurnEvidence.d.ts +18 -0
- package/dist/boundedTurnEvidence.js +76 -0
- package/dist/branch/currentGuard.d.ts +2 -0
- package/dist/branch/currentGuard.js +2 -0
- package/dist/branch/git.js +3 -8
- package/dist/branch/planner.js +1 -4
- package/dist/branch/runner.d.ts +2 -1
- package/dist/branch/runner.js +10 -7
- package/dist/buildAdmission.js +4 -0
- package/dist/buildApproval.d.ts +15 -0
- package/dist/buildApproval.js +28 -9
- package/dist/buildAssignment.d.ts +1 -0
- package/dist/buildAssignment.js +82 -23
- package/dist/buildInterventions.d.ts +2 -2
- package/dist/buildInterventions.js +11 -4
- package/dist/buildLaunchGate.js +3 -2
- package/dist/buildRuns.d.ts +1 -0
- package/dist/buildRuns.js +6 -1
- package/dist/builderGuidanceFollowup.js +17 -6
- package/dist/cli/doctor.js +3 -1
- package/dist/cli/manager.js +22 -20
- package/dist/cli/start.js +85 -39
- package/dist/cli/tickets.d.ts +0 -8
- package/dist/cli/tickets.js +50 -60
- package/dist/continuity.d.ts +10 -16
- package/dist/continuity.js +223 -208
- package/dist/continuityOrigin.d.ts +13 -0
- package/dist/continuityOrigin.js +91 -0
- package/dist/dispatchNotSent.d.ts +4 -0
- package/dist/dispatchNotSent.js +10 -0
- package/dist/foreman.d.ts +15 -16
- package/dist/foreman.js +70 -278
- package/dist/gitExclude.d.ts +16 -0
- package/dist/gitExclude.js +46 -0
- package/dist/graph/boundary.d.ts +1 -1
- package/dist/graph/boundary.js +1 -1
- package/dist/graph/host.js +8 -19
- package/dist/graph/manager.d.ts +1 -1
- package/dist/graph/manager.js +1 -1
- package/dist/graph/preparation.d.ts +38 -0
- package/dist/graph/preparation.js +7 -6
- package/dist/graph/semantic.d.ts +1 -0
- package/dist/graph/semantic.js +4 -1
- package/dist/graph/session.js +7 -2
- package/dist/graph/turn.d.ts +3 -4
- package/dist/graph/turn.js +29 -41
- package/dist/handoffProtocol.js +2 -2
- package/dist/handoffs.d.ts +5 -3
- package/dist/handoffs.js +92 -88
- package/dist/hostGraphApplicability.d.ts +2 -1
- package/dist/hostGraphApplicability.js +17 -19
- package/dist/hostQaAugmentation.js +21 -4
- package/dist/hostQaComparison.d.ts +21 -2
- package/dist/hostQaComparison.js +43 -29
- package/dist/hostSourcePreservation.d.ts +38 -0
- package/dist/hostSourcePreservation.js +72 -0
- package/dist/humanDecision.d.ts +7 -0
- package/dist/humanDecision.js +11 -0
- package/dist/interviews.d.ts +7 -0
- package/dist/interviews.js +18 -1
- package/dist/legacyCommunicationDisplay.d.ts +2 -0
- package/dist/legacyCommunicationDisplay.js +2 -0
- package/dist/managerPacket.js +3 -3
- package/dist/markers.d.ts +4 -3
- package/dist/markers.js +4 -20
- package/dist/observability.js +3 -8
- package/dist/probeIpc.d.ts +14 -0
- package/dist/probeIpc.js +29 -0
- package/dist/qaBuildGate.d.ts +21 -0
- package/dist/qaBuildGate.js +60 -13
- package/dist/qaBuilderCoverage.js +22 -13
- package/dist/qaBuilderEvidence.d.ts +16 -1
- package/dist/qaBuilderEvidence.js +48 -8
- package/dist/qaContractCoverage.d.ts +2 -2
- package/dist/qaContractCoverage.js +4 -9
- package/dist/qaContractDelivery.js +34 -20
- package/dist/qaCoverageAssembly.d.ts +22 -0
- package/dist/qaCoverageAssembly.js +16 -0
- package/dist/qaDeliveryJournal.d.ts +2 -0
- package/dist/qaDeliveryJournal.js +19 -0
- package/dist/qaFailureDelivery.js +97 -85
- package/dist/qaHostTransformation.d.ts +26 -0
- package/dist/qaHostTransformation.js +100 -0
- package/dist/qaPreparation.d.ts +8 -20
- package/dist/qaPreparation.js +153 -254
- package/dist/qaPreparationInputs.js +19 -0
- package/dist/qaPreparationStore.d.ts +11 -0
- package/dist/qaPreparationStore.js +44 -4
- package/dist/qaPrerequisites.d.ts +2 -1
- package/dist/qaPrerequisites.js +21 -1
- package/dist/qaProtocolV2.d.ts +15 -0
- package/dist/qaRecovery.d.ts +0 -3
- package/dist/qaRecovery.js +21 -27
- package/dist/qaReview.js +241 -406
- package/dist/qaReviewEvidence.d.ts +7 -3
- package/dist/qaReviewEvidence.js +15 -4
- package/dist/qaRuntime.js +1 -1
- package/dist/qaSnapshot.js +76 -21
- package/dist/qaStructuredResponse.d.ts +18 -0
- package/dist/qaStructuredResponse.js +81 -0
- package/dist/qaTimeline.js +2 -2
- package/dist/qaVerificationContract.d.ts +1 -2
- package/dist/qaVerificationContract.js +24 -35
- package/dist/readinessGate.js +5 -4
- package/dist/recoveryOwners.d.ts +32 -0
- package/dist/recoveryOwners.js +87 -0
- package/dist/runtimePreflight.d.ts +11 -0
- package/dist/runtimePreflight.js +77 -0
- package/dist/runtimeReadiness.js +23 -15
- package/dist/sources/sourceRegistry.d.ts +0 -3
- package/dist/sources/sourceRegistry.js +0 -42
- package/dist/statusReporter.d.ts +2 -0
- package/dist/statusReporter.js +2 -0
- package/dist/structuredCutover.d.ts +31 -0
- package/dist/structuredCutover.js +510 -0
- package/dist/structuredCutoverFiles.d.ts +10 -0
- package/dist/structuredCutoverFiles.js +181 -0
- package/dist/structuredDispatchAuthority.d.ts +22 -0
- package/dist/structuredDispatchAuthority.js +110 -0
- package/dist/structuredDispatchRecord.d.ts +29 -0
- package/dist/structuredDispatchRecord.js +36 -0
- package/dist/structuredLiveBudget.d.ts +2 -0
- package/dist/structuredLiveBudget.js +23 -0
- package/dist/structuredOperation.d.ts +18 -0
- package/dist/structuredOperation.js +279 -0
- package/dist/supervisedStart.js +35 -21
- package/dist/ticketPlanning.d.ts +1 -376
- package/dist/ticketPlanning.js +2 -49
- package/dist/ticketPopulation.d.ts +0 -3
- package/dist/ticketPopulation.js +4 -21
- package/dist/ticketPopulationPageAuthority.d.ts +13 -0
- package/dist/ticketPopulationPageAuthority.js +30 -0
- package/dist/ticketPopulationPaging.d.ts +27 -0
- package/dist/ticketPopulationPaging.js +127 -0
- package/dist/tickets/renderMarkdown.d.ts +14 -0
- package/dist/tickets/renderMarkdown.js +39 -5
- package/dist/tickets/ticketLoader.js +10 -0
- package/dist/tickets/ticketSchema.d.ts +107 -0
- package/dist/tickets/ticketSchema.js +4 -1
- package/dist/workflowDb.d.ts +116 -3
- package/dist/workflowDb.js +852 -61
- package/dist/workflowReader.d.ts +1 -0
- package/dist/workflowReader.js +9 -0
- package/package.json +23 -7
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProviderPhaseBarrier
|
|
1
|
+
import { ProviderPhaseBarrier } from "../providerPhase.js";
|
|
2
2
|
import type { SDKSessionInfo, PermissionResult } from "@anthropic-ai/claude-agent-sdk";
|
|
3
3
|
/** Lazy-load the Claude Agent SDK. Throws an actionable error if not installed. */
|
|
4
4
|
export declare function requireClaudeSDK(): Promise<typeof import("@anthropic-ai/claude-agent-sdk")>;
|
|
@@ -9,7 +9,7 @@ import type { ProviderSessionRefV1, SessionAvailabilityV1 } from "rafi-spec";
|
|
|
9
9
|
* Extracted so tests can assert on the shape without making a live SDK call.
|
|
10
10
|
* `canUseTool` is omitted here — it's a closure that the constructor adds.
|
|
11
11
|
*/
|
|
12
|
-
export declare function buildClaudeQueryOptions(opts: Omit<BuilderAdapterOptions, "permission"
|
|
12
|
+
export declare function buildClaudeQueryOptions(opts: Omit<BuilderAdapterOptions, "permission">, scratchDirectory?: string): Record<string, unknown>;
|
|
13
13
|
export declare function probeClaudeSession(ref: ProviderSessionRefV1, input: {
|
|
14
14
|
cwd: string;
|
|
15
15
|
configRoot?: string;
|
|
@@ -43,6 +43,7 @@ export declare function claudeApiRetryEvent(message: {
|
|
|
43
43
|
*/
|
|
44
44
|
export declare class ClaudeAdapter implements BuilderAdapter {
|
|
45
45
|
private readonly opts;
|
|
46
|
+
private readonly queryFactory;
|
|
46
47
|
readonly agent: "claude";
|
|
47
48
|
graphRuntimeSettings(): {
|
|
48
49
|
model: string | undefined;
|
|
@@ -50,11 +51,16 @@ export declare class ClaudeAdapter implements BuilderAdapter {
|
|
|
50
51
|
fast: boolean | undefined;
|
|
51
52
|
runtimeExecutable: string | undefined;
|
|
52
53
|
};
|
|
53
|
-
private
|
|
54
|
+
private inbox;
|
|
54
55
|
private readonly eventQueue;
|
|
55
|
-
private
|
|
56
|
-
private
|
|
57
|
-
private
|
|
56
|
+
private query;
|
|
57
|
+
private abort;
|
|
58
|
+
private pumpDone;
|
|
59
|
+
private rotatingQuery;
|
|
60
|
+
private configuringTurn;
|
|
61
|
+
private output?;
|
|
62
|
+
private outputWireContract?;
|
|
63
|
+
private responseOnlyViolation;
|
|
58
64
|
private _sessionId?;
|
|
59
65
|
private _sessionRef?;
|
|
60
66
|
private readonly sessionIdentityReady;
|
|
@@ -76,8 +82,10 @@ export declare class ClaudeAdapter implements BuilderAdapter {
|
|
|
76
82
|
private cumulativeUsage;
|
|
77
83
|
private priorTurnUsage;
|
|
78
84
|
private activeProviderTurnId?;
|
|
85
|
+
private activeTurnPolicy?;
|
|
79
86
|
private activeProviderTurnSpanId?;
|
|
80
87
|
private readonly toolSpans;
|
|
88
|
+
private readonly structuredSubmissionIds;
|
|
81
89
|
private readonly toolCalls;
|
|
82
90
|
private readonly completedToolCalls;
|
|
83
91
|
private readonly unknownToolResults;
|
|
@@ -96,6 +104,7 @@ export declare class ClaudeAdapter implements BuilderAdapter {
|
|
|
96
104
|
private streamEnded;
|
|
97
105
|
private closed;
|
|
98
106
|
private closePromise?;
|
|
107
|
+
private readonly scratchDirectory?;
|
|
99
108
|
private launchScopeObserved;
|
|
100
109
|
private providerIdentityObserved;
|
|
101
110
|
private toolCwd?;
|
|
@@ -109,16 +118,18 @@ export declare class ClaudeAdapter implements BuilderAdapter {
|
|
|
109
118
|
private disableFastMode;
|
|
110
119
|
static create(opts: BuilderAdapterOptions): Promise<ClaudeAdapter>;
|
|
111
120
|
private constructor();
|
|
121
|
+
private queryBudget?;
|
|
122
|
+
private queryBudgetAbort?;
|
|
123
|
+
private finishQueryBudget;
|
|
124
|
+
private startQuery;
|
|
125
|
+
private isResultSubmission;
|
|
126
|
+
private resultSubmissionError;
|
|
127
|
+
private configureOutput;
|
|
112
128
|
/** Background loop: consume the SDK message stream until it ends. */
|
|
113
129
|
private providerInputs;
|
|
114
130
|
private pump;
|
|
115
131
|
private handle;
|
|
116
|
-
sendTurn(text: string, policy?:
|
|
117
|
-
handback?: boolean;
|
|
118
|
-
responseOnly?: boolean;
|
|
119
|
-
logicalActionId?: string;
|
|
120
|
-
purpose?: ProviderTurnPurpose;
|
|
121
|
-
}): Promise<TurnResult>;
|
|
132
|
+
sendTurn(text: string, policy?: import("./types.js").ProviderTurnPolicy): Promise<TurnResult>;
|
|
122
133
|
private sendTurnInternal;
|
|
123
134
|
private sendTurnAttempt;
|
|
124
135
|
private captureStderr;
|
package/dist/adapters/claude.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { inheritedStructuredLiveBudget } from "../structuredLiveBudget.js";
|
|
1
2
|
import { ProviderPhaseBarrier } from "../providerPhase.js";
|
|
2
3
|
import { OperationDeadline } from "../util/deadline.js";
|
|
4
|
+
import { assertActiveProviderDispatch } from "../structuredDispatchAuthority.js";
|
|
3
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
6
|
import { dirname, join, relative, isAbsolute } from "node:path";
|
|
7
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
5
9
|
/** Lazy-load the Claude Agent SDK. Throws an actionable error if not installed. */
|
|
6
10
|
export async function requireClaudeSDK() {
|
|
7
11
|
try {
|
|
@@ -22,19 +26,21 @@ import { createProviderSessionRef, validateProviderSessionScope, canonicalSessio
|
|
|
22
26
|
import { SessionUnavailableError, sessionUnavailableResult } from "./sessionFailure.js";
|
|
23
27
|
import { isFastModeUnavailable, reportFastModeUnavailable } from "./fastMode.js";
|
|
24
28
|
import { handoffSystemInstructions } from "../handoffProtocol.js";
|
|
29
|
+
import { providerControlOperationV2, providerWireContract, renderProviderWireInstruction, validateProviderWireValue } from "rafi-spec";
|
|
30
|
+
import { structuredReceipt } from "./structured.js";
|
|
25
31
|
const DEFAULT_PROVIDER_IDLE_TIMEOUT_MS = 30 * 60_000;
|
|
26
32
|
/**
|
|
27
33
|
* Pure function: build the `options` object passed to `query()`.
|
|
28
34
|
* Extracted so tests can assert on the shape without making a live SDK call.
|
|
29
35
|
* `canUseTool` is omitted here — it's a closure that the constructor adds.
|
|
30
36
|
*/
|
|
31
|
-
export function buildClaudeQueryOptions(opts) {
|
|
37
|
+
export function buildClaudeQueryOptions(opts, scratchDirectory = join(dirname(opts.cwd), "scratch")) {
|
|
32
38
|
const qaReadOnly = opts.sessionRole === "qa" || opts.sandboxMode === "read-only";
|
|
33
39
|
const confinedBuilder = opts.sessionRole === "builder";
|
|
34
40
|
const base = {
|
|
35
41
|
cwd: opts.cwd,
|
|
36
42
|
pathToClaudeCodeExecutable: opts.runtimeExecutable,
|
|
37
|
-
env: qaReadOnly ? qaEnvironment(process.env,
|
|
43
|
+
env: qaReadOnly ? qaEnvironment(process.env, scratchDirectory) : { ...process.env },
|
|
38
44
|
model: opts.model,
|
|
39
45
|
resume: opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId,
|
|
40
46
|
permissionMode: qaReadOnly ? "default" : "acceptEdits",
|
|
@@ -57,14 +63,15 @@ export function buildClaudeQueryOptions(opts) {
|
|
|
57
63
|
failIfUnavailable: true,
|
|
58
64
|
autoAllowBashIfSandboxed: true,
|
|
59
65
|
allowUnsandboxedCommands: false,
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
// QA stays offline unless the operator granted --qa-network for this build.
|
|
67
|
+
network: opts.networkAccess === true ? { allowedDomains: ["*"] } : { allowedDomains: [], deniedDomains: ["*"] },
|
|
68
|
+
filesystem: { allowWrite: [scratchDirectory], denyWrite: [opts.cwd, ...(opts.configRoot ? [opts.configRoot] : [])] },
|
|
62
69
|
} } : {}),
|
|
63
70
|
};
|
|
64
71
|
const exactSkills = opts.preloadedSkillContent?.length
|
|
65
72
|
? ["# Preloaded Skills", "Use the following skills for this run.", ...opts.preloadedSkillContent.map((skill) => skill.content)].join("\n\n")
|
|
66
73
|
: undefined;
|
|
67
|
-
const systemAppend = [handoffSystemInstructions(opts.sessionRole, opts.systemPromptAppend), opts.sessionRole === "builder" ? `Builder runtime contract: shell network ${opts.networkAccess === true ? "explicitly approved for this build" : "disabled"}; sandbox fallback disabled. If acquisition requires unavailable access, request input first or use a provenance-verified local bundle. A ticket answer does not grant runtime permissions.` : undefined, exactSkills].filter((part) => Boolean(part)).join("\n\n");
|
|
74
|
+
const systemAppend = [handoffSystemInstructions(opts.sessionRole, opts.systemPromptAppend), opts.sessionRole === "builder" ? `Builder runtime contract: shell network ${opts.networkAccess === true ? "explicitly approved for this build" : "disabled"}; sandbox fallback disabled. If acquisition requires unavailable access, request input first or use a provenance-verified local bundle. A ticket answer does not grant runtime permissions.` : undefined, opts.sessionRole === "qa" && opts.networkAccess === true ? "QA runtime contract: shell network explicitly approved for this review (--qa-network); you may run live checks against operator-provided local or remote services. Workspace stays read-only." : undefined, exactSkills].filter((part) => Boolean(part)).join("\n\n");
|
|
68
75
|
if (systemAppend) {
|
|
69
76
|
base.systemPrompt = { type: "preset", preset: "claude_code", append: systemAppend };
|
|
70
77
|
}
|
|
@@ -73,9 +80,9 @@ export function buildClaudeQueryOptions(opts) {
|
|
|
73
80
|
}
|
|
74
81
|
return base;
|
|
75
82
|
}
|
|
76
|
-
function qaEnvironment(source,
|
|
83
|
+
function qaEnvironment(source, scratchDirectory) {
|
|
77
84
|
const allowed = ["PATH", "HOME", "USER", "LOGNAME", "SHELL", "TMPDIR", "LANG", "LC_ALL", "TERM", "NO_COLOR", "FORCE_COLOR", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL", "CLAUDE_CODE_API_KEY_HELPER_TTL_MS", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"];
|
|
78
|
-
return { ...Object.fromEntries(allowed.flatMap((key) => source[key] === undefined ? [] : [[key, source[key]]])), TMPDIR:
|
|
85
|
+
return { ...Object.fromEntries(allowed.flatMap((key) => source[key] === undefined ? [] : [[key, source[key]]])), TMPDIR: scratchDirectory };
|
|
79
86
|
}
|
|
80
87
|
export async function probeClaudeSession(ref, input) {
|
|
81
88
|
const now = input.now ?? new Date();
|
|
@@ -176,6 +183,7 @@ export function claudeApiRetryEvent(message) {
|
|
|
176
183
|
*/
|
|
177
184
|
export class ClaudeAdapter {
|
|
178
185
|
opts;
|
|
186
|
+
queryFactory;
|
|
179
187
|
agent = "claude";
|
|
180
188
|
graphRuntimeSettings() { return { model: this.opts.model, effort: this.opts.effort, fast: this.opts.fast, runtimeExecutable: this.opts.runtimeExecutable }; }
|
|
181
189
|
inbox = new AsyncQueue();
|
|
@@ -183,6 +191,11 @@ export class ClaudeAdapter {
|
|
|
183
191
|
query;
|
|
184
192
|
abort = new AbortController();
|
|
185
193
|
pumpDone;
|
|
194
|
+
rotatingQuery = false;
|
|
195
|
+
configuringTurn = false;
|
|
196
|
+
output;
|
|
197
|
+
outputWireContract;
|
|
198
|
+
responseOnlyViolation = false;
|
|
186
199
|
_sessionId;
|
|
187
200
|
_sessionRef;
|
|
188
201
|
sessionIdentityReady;
|
|
@@ -204,8 +217,10 @@ export class ClaudeAdapter {
|
|
|
204
217
|
cumulativeUsage = { observedAt: new Date(0).toISOString(), source: "provider" };
|
|
205
218
|
priorTurnUsage = {};
|
|
206
219
|
activeProviderTurnId;
|
|
220
|
+
activeTurnPolicy;
|
|
207
221
|
activeProviderTurnSpanId;
|
|
208
222
|
toolSpans = new Map();
|
|
223
|
+
structuredSubmissionIds = new Set();
|
|
209
224
|
toolCalls = new Map();
|
|
210
225
|
completedToolCalls = new Set();
|
|
211
226
|
unknownToolResults = new Map();
|
|
@@ -221,6 +236,7 @@ export class ClaudeAdapter {
|
|
|
221
236
|
streamEnded = false;
|
|
222
237
|
closed = false;
|
|
223
238
|
closePromise;
|
|
239
|
+
scratchDirectory;
|
|
224
240
|
launchScopeObserved = false;
|
|
225
241
|
providerIdentityObserved = false;
|
|
226
242
|
toolCwd;
|
|
@@ -251,6 +267,7 @@ export class ClaudeAdapter {
|
|
|
251
267
|
return;
|
|
252
268
|
}
|
|
253
269
|
try {
|
|
270
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
254
271
|
await new OperationDeadline("Claude fast mode", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ fastMode: true }));
|
|
255
272
|
this.fastModeEnabled = true;
|
|
256
273
|
}
|
|
@@ -269,10 +286,13 @@ export class ClaudeAdapter {
|
|
|
269
286
|
this.fastModeEnabled = false;
|
|
270
287
|
}
|
|
271
288
|
async disableFastMode() {
|
|
289
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
272
290
|
await new OperationDeadline("Claude standard speed", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ fastMode: false }));
|
|
273
291
|
this.disableFastModeNotice();
|
|
274
292
|
}
|
|
275
293
|
static async create(opts) {
|
|
294
|
+
const inheritedBudget = inheritedStructuredLiveBudget("claude");
|
|
295
|
+
opts = { ...opts, queryBudget: opts.queryBudget ?? inheritedBudget };
|
|
276
296
|
try {
|
|
277
297
|
const runtimeExecutable = opts.runtimeExecutable ?? resolveExecutablePath("claude");
|
|
278
298
|
if (!runtimeExecutable) {
|
|
@@ -310,8 +330,9 @@ export class ClaudeAdapter {
|
|
|
310
330
|
}
|
|
311
331
|
}
|
|
312
332
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
313
|
-
constructor(opts,
|
|
333
|
+
constructor(opts, queryFactory) {
|
|
314
334
|
this.opts = opts;
|
|
335
|
+
this.queryFactory = queryFactory;
|
|
315
336
|
this.phaseBarrier.begin("initialization");
|
|
316
337
|
this._sessionId = opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId;
|
|
317
338
|
this._sessionRef = opts.resumeSessionRef;
|
|
@@ -324,15 +345,61 @@ export class ClaudeAdapter {
|
|
|
324
345
|
start: (attemptId) => opts.observer.store.startSpan(this.observationContext(), { kind: "provider_wait", name: "Waiting for Claude after question answer", attributes: { attemptId } }),
|
|
325
346
|
finish: (spanId, outcome) => opts.observer.store.finishSpan(spanId, { outcome }),
|
|
326
347
|
} : undefined);
|
|
327
|
-
|
|
348
|
+
// Planning and interpretation also use read-only sessions, without a QA
|
|
349
|
+
// snapshot's sibling scratch directory. Own a real, private directory for
|
|
350
|
+
// the complete query lifecycle, including exact-session schema rotations.
|
|
351
|
+
// macOS's default TMPDIR is too long for the SDK's Unix socket paths.
|
|
352
|
+
if (opts.sessionRole === "qa" || opts.sandboxMode === "read-only")
|
|
353
|
+
this.scratchDirectory = mkdtempSync(join(process.platform === "darwin" ? "/tmp" : tmpdir(), "rf-c-"));
|
|
354
|
+
try {
|
|
355
|
+
this.startQuery();
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
this.finishQueryBudget();
|
|
359
|
+
if (this.scratchDirectory)
|
|
360
|
+
rmSync(this.scratchDirectory, { recursive: true, force: true });
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
queryBudget;
|
|
365
|
+
queryBudgetAbort;
|
|
366
|
+
finishQueryBudget() {
|
|
367
|
+
if (this.queryBudgetAbort)
|
|
368
|
+
this.queryBudget?.signal.removeEventListener("abort", this.queryBudgetAbort);
|
|
369
|
+
this.queryBudgetAbort = undefined;
|
|
370
|
+
this.queryBudget?.close();
|
|
371
|
+
this.queryBudget = undefined;
|
|
372
|
+
}
|
|
373
|
+
startQuery() {
|
|
374
|
+
const opts = this.opts;
|
|
375
|
+
this.queryBudget = opts.queryBudget?.reserveQuery("claude");
|
|
376
|
+
if (this.queryBudget) {
|
|
377
|
+
const lease = this.queryBudget;
|
|
378
|
+
this.queryBudgetAbort = () => { this.abort.abort(); void this.close(); };
|
|
379
|
+
lease.signal.addEventListener("abort", this.queryBudgetAbort, { once: true });
|
|
380
|
+
if (lease.signal.aborted) {
|
|
381
|
+
this.finishQueryBudget();
|
|
382
|
+
throw new Error("Live query deadline expired before launch");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
this.query = this.queryFactory({
|
|
328
386
|
prompt: this.providerInputs(),
|
|
329
387
|
options: {
|
|
330
|
-
...buildClaudeQueryOptions(opts),
|
|
388
|
+
...buildClaudeQueryOptions(opts, this.scratchDirectory),
|
|
389
|
+
...(this.queryBudget ? { maxBudgetUsd: this.queryBudget.maximumSpendUsd } : {}),
|
|
390
|
+
...(this._sessionId ? { resume: this._sessionId } : {}),
|
|
391
|
+
...(this.outputWireContract ? { outputFormat: { type: "json_schema", schema: this.outputWireContract.wireSchema } } : {}),
|
|
331
392
|
abortController: this.abort,
|
|
332
393
|
hooks: {
|
|
333
394
|
PreToolUse: [{ hooks: [async (input) => {
|
|
334
395
|
if (input.hook_event_name !== "PreToolUse")
|
|
335
396
|
return {};
|
|
397
|
+
if (this.isResultSubmission(input.tool_name, input.tool_input)) {
|
|
398
|
+
const reason = this.resultSubmissionError(input.tool_input);
|
|
399
|
+
return reason ? { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } } : {};
|
|
400
|
+
}
|
|
401
|
+
if (this.output && this.responseOnlyTurn)
|
|
402
|
+
this.responseOnlyViolation = true;
|
|
336
403
|
this.toolCwd = input.cwd;
|
|
337
404
|
this.toolCwds.set(input.tool_use_id, input.cwd);
|
|
338
405
|
if (this.toolCwds.size > 1000)
|
|
@@ -356,6 +423,12 @@ export class ClaudeAdapter {
|
|
|
356
423
|
},
|
|
357
424
|
stderr: (data) => this.captureStderr(data),
|
|
358
425
|
canUseTool: async (toolName, input, requestOptions = {}) => {
|
|
426
|
+
if (this.isResultSubmission(toolName, input)) {
|
|
427
|
+
const reason = this.resultSubmissionError(input);
|
|
428
|
+
return reason ? { behavior: "deny", message: reason } : { behavior: "allow", updatedInput: input };
|
|
429
|
+
}
|
|
430
|
+
if (this.output && this.responseOnlyTurn)
|
|
431
|
+
this.responseOnlyViolation = true;
|
|
359
432
|
if (this.responseOnlyTurn)
|
|
360
433
|
return { behavior: "deny", message: "QA response correction forbids all tools, including read-only tools", interrupt: true };
|
|
361
434
|
const isProviderQuestion = toolName === "AskUserQuestion";
|
|
@@ -394,6 +467,60 @@ export class ClaudeAdapter {
|
|
|
394
467
|
});
|
|
395
468
|
this.pumpDone = this.pump();
|
|
396
469
|
}
|
|
470
|
+
isResultSubmission(name, input) {
|
|
471
|
+
// Only the SDK's built-in submission in a host-configured structured query.
|
|
472
|
+
// An MCP name or arbitrary tool cannot acquire this exception.
|
|
473
|
+
return Boolean(this.pending && this.output && name === "StructuredOutput");
|
|
474
|
+
}
|
|
475
|
+
resultSubmissionError(input) {
|
|
476
|
+
try {
|
|
477
|
+
// This SDK hook sees provider wire data, not the canonical artifact.
|
|
478
|
+
// Canonical validation is deliberately deferred until the selected
|
|
479
|
+
// normalizer runs in structuredReceipt.
|
|
480
|
+
validateProviderWireValue(this.outputWireContract, input);
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
// The SDK submission is data, including when malformed. Give its bounded
|
|
485
|
+
// native retry the actual wire-contract violations, never a mutation-policy
|
|
486
|
+
// diagnosis. Only a valid wire payload may invoke the SDK submission tool.
|
|
487
|
+
return `Native structured result violates selected ${this.outputWireContract.profile} wire contract for ${this.output.operation}: ${String(error).slice(0, 2400)}. Return every required field with the declared type.`;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async configureOutput(output, wireContract) {
|
|
491
|
+
const selected = output ? wireContract ?? providerWireContract(output, "claude", "default", { requireRegistered: true }) : undefined;
|
|
492
|
+
if (selected && (selected.provider !== "claude" || selected.canonical.schemaDigest !== output.schemaDigest))
|
|
493
|
+
throw new Error("Claude received a mismatched provider wire contract");
|
|
494
|
+
if (output?.schemaDigest === this.output?.schemaDigest && output?.operation === this.output?.operation && selected?.descriptorDigest === this.outputWireContract?.descriptorDigest)
|
|
495
|
+
return;
|
|
496
|
+
if (this.pending || this.closed || this.streamEnded)
|
|
497
|
+
throw new Error("Cannot change output schema on an active or unavailable Claude query");
|
|
498
|
+
this.rotatingQuery = true;
|
|
499
|
+
try {
|
|
500
|
+
this.inbox.close();
|
|
501
|
+
this.abort.abort();
|
|
502
|
+
this.query.close?.();
|
|
503
|
+
await new OperationDeadline("Claude schema transition", this.opts.shutdownTimeoutMs ?? 10_000).run(() => this.pumpDone);
|
|
504
|
+
this.finishQueryBudget();
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
this.streamEnded = true;
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
finally {
|
|
511
|
+
this.rotatingQuery = false;
|
|
512
|
+
}
|
|
513
|
+
this.inbox = new AsyncQueue();
|
|
514
|
+
this.abort = new AbortController();
|
|
515
|
+
this.output = output;
|
|
516
|
+
this.outputWireContract = selected;
|
|
517
|
+
this.fastModePrepared = undefined;
|
|
518
|
+
this.fastModeEnabled = false;
|
|
519
|
+
this.providerIdentityObserved = false;
|
|
520
|
+
this.startQuery();
|
|
521
|
+
if (this._sessionId)
|
|
522
|
+
this.eventQueue.push({ kind: "session-transition", transition: "resumed", detail: "Exact session resumed for output schema change" });
|
|
523
|
+
}
|
|
397
524
|
/** Background loop: consume the SDK message stream until it ends. */
|
|
398
525
|
async *providerInputs() {
|
|
399
526
|
for await (const message of this.inbox) {
|
|
@@ -413,6 +540,8 @@ export class ClaudeAdapter {
|
|
|
413
540
|
}
|
|
414
541
|
}
|
|
415
542
|
catch (err) {
|
|
543
|
+
if (this.rotatingQuery)
|
|
544
|
+
return;
|
|
416
545
|
this.questionTrace?.finish(this.closed ? "closed" : "stream-error");
|
|
417
546
|
// Suppress the AbortError that fires when close() aborts the stream.
|
|
418
547
|
const isShutdownAbort = this.closed &&
|
|
@@ -429,6 +558,8 @@ export class ClaudeAdapter {
|
|
|
429
558
|
}
|
|
430
559
|
}
|
|
431
560
|
finally {
|
|
561
|
+
if (this.rotatingQuery)
|
|
562
|
+
return;
|
|
432
563
|
this.questionTrace?.finish(this.closed ? "closed" : "stream-ended");
|
|
433
564
|
this.streamEnded = true;
|
|
434
565
|
if (!this.closed && this.pending) {
|
|
@@ -464,7 +595,7 @@ export class ClaudeAdapter {
|
|
|
464
595
|
this.turnSignals.push(`assistant error: ${msg.error}`);
|
|
465
596
|
}
|
|
466
597
|
for (const block of msg.message.content) {
|
|
467
|
-
this.eventQueue.push({ kind: "provider-item", provider: "claude", lifecycle: "completed", itemType: String(block.type ?? "unknown"), payload: structuredClone(block), payloadCompleteness: "complete", providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
598
|
+
this.eventQueue.push({ kind: "provider-item", provider: "claude", lifecycle: "completed", itemType: block.type === "tool_use" && this.isResultSubmission(block.name, block.input) ? "structuredOutput" : String(block.type ?? "unknown"), payload: structuredClone(block), payloadCompleteness: "complete", providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
468
599
|
if (block.type === "text" && block.text) {
|
|
469
600
|
if (msg.error && this.fastModeEnabled && isFastModeUnavailable(block.text) && !this.turnSawWork)
|
|
470
601
|
continue;
|
|
@@ -473,6 +604,16 @@ export class ClaudeAdapter {
|
|
|
473
604
|
this.opts.observer?.signal(true);
|
|
474
605
|
}
|
|
475
606
|
else if (block.type === "tool_use") {
|
|
607
|
+
// The SDK result submission is transport, not a domain tool. Retain its
|
|
608
|
+
// provider-item evidence without falsely reporting an acceptance violation.
|
|
609
|
+
if (this.isResultSubmission(block.name, block.input)) {
|
|
610
|
+
this.structuredSubmissionIds.add(block.id);
|
|
611
|
+
if (this.structuredSubmissionIds.size > 1000)
|
|
612
|
+
this.structuredSubmissionIds.delete(this.structuredSubmissionIds.values().next().value);
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (this.output && this.responseOnlyTurn && !this.isResultSubmission(block.name, block.input))
|
|
616
|
+
this.responseOnlyViolation = true;
|
|
476
617
|
this.turnSawWork = true;
|
|
477
618
|
const callId = block.id;
|
|
478
619
|
this.toolCalls.set(callId, { name: block.name, input: block.input });
|
|
@@ -537,6 +678,8 @@ export class ClaudeAdapter {
|
|
|
537
678
|
this.eventQueue.push({ kind: "activity", provider: "claude", state: "running tool", detail: `${msg.tool_name} (${Math.floor(msg.elapsed_time_seconds)}s)`, transient: true });
|
|
538
679
|
const progress = msg;
|
|
539
680
|
const callId = typeof progress.tool_use_id === "string" ? progress.tool_use_id : typeof progress.toolUseID === "string" ? progress.toolUseID : undefined;
|
|
681
|
+
if (callId && this.structuredSubmissionIds.has(callId))
|
|
682
|
+
return;
|
|
540
683
|
this.eventQueue.push({ kind: "tool", name: msg.tool_name, input: {}, lifecycle: "progress", callId, durationMs: Math.max(0, msg.elapsed_time_seconds * 1000), completionKnown: false, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
541
684
|
this.opts.observer?.signal(false);
|
|
542
685
|
}
|
|
@@ -586,7 +729,8 @@ export class ClaudeAdapter {
|
|
|
586
729
|
cwd: this.opts.cwd,
|
|
587
730
|
diagnostics: rawDiagnostics,
|
|
588
731
|
} : undefined;
|
|
589
|
-
|
|
732
|
+
this.queryBudget?.record(msg.total_cost_usd);
|
|
733
|
+
let result = {
|
|
590
734
|
text: failure ? formatClaudeFailure(failure) : text,
|
|
591
735
|
isError: msg.is_error,
|
|
592
736
|
numTurns: msg.num_turns,
|
|
@@ -599,8 +743,21 @@ export class ClaudeAdapter {
|
|
|
599
743
|
rawResponse: text,
|
|
600
744
|
cleanedResponse: failure ? formatClaudeFailure(failure) : text,
|
|
601
745
|
providerMetadata: { provider: "claude", sessionId: this._sessionId, sessionRef: this._sessionRef },
|
|
746
|
+
...(this.activeTurnPolicy?.selectedDispatch ? { selectedDispatch: this.activeTurnPolicy.selectedDispatch } : {}),
|
|
602
747
|
};
|
|
603
748
|
const rawResult = msg;
|
|
749
|
+
if (this.output) {
|
|
750
|
+
const selectedDispatch = this.activeTurnPolicy?.selectedDispatch;
|
|
751
|
+
if (!selectedDispatch || !this._sessionId)
|
|
752
|
+
throw new Error("Structured terminal lost its sealed dispatch tuple or session");
|
|
753
|
+
result = { ...result, selectedDispatch, structured: msg.subtype !== "success" || msg.is_error || this.responseOnlyViolation
|
|
754
|
+
? { state: "rejected", reason: this.responseOnlyViolation ? "tool-policy-violation" : "execution-not-completed", detail: `Claude terminal ${msg.subtype}`, dispatchId: result.turnId }
|
|
755
|
+
: !this.outputWireContract || !this.activeTurnPolicy?.selectedDispatch
|
|
756
|
+
? { state: "rejected", reason: "invalid-contract", detail: "Claude structured turn has no sealed descriptor or tuple", dispatchId: result.turnId }
|
|
757
|
+
: structuredReceipt(this.output, "claude", rawResult.structured_output, { dispatchId: result.turnId, sessionId: this._sessionId, nativeTurnId: typeof rawResult.uuid === "string" ? rawResult.uuid : undefined }, "decoded-json", this.outputWireContract, this.activeTurnPolicy.selectedDispatch) };
|
|
758
|
+
if (result.structured.state === "rejected")
|
|
759
|
+
result.isError = true;
|
|
760
|
+
}
|
|
604
761
|
const mergedUsage = mergeClaudeProviderSessionUsage(this.cumulativeUsage, rawResult);
|
|
605
762
|
result.inputTokens = mergedUsage.inputTokens;
|
|
606
763
|
result.outputTokens = mergedUsage.outputTokens;
|
|
@@ -616,11 +773,30 @@ export class ClaudeAdapter {
|
|
|
616
773
|
}
|
|
617
774
|
}
|
|
618
775
|
async sendTurn(text, policy) {
|
|
619
|
-
if (this.pending)
|
|
776
|
+
if (this.pending || this.configuringTurn)
|
|
620
777
|
throw new Error("a turn is already in progress");
|
|
778
|
+
if (policy?.output)
|
|
779
|
+
assertActiveProviderDispatch(policy, text, "claude", this.sessionRef());
|
|
780
|
+
this.configuringTurn = true;
|
|
781
|
+
try {
|
|
782
|
+
const selected = policy?.output ? policy.wireContract ?? providerWireContract(policy.output, "claude", "default", { requireRegistered: true }) : undefined;
|
|
783
|
+
if (selected && policy?.renderedWireContractDigest && policy.renderedWireContractDigest !== selected.descriptorDigest)
|
|
784
|
+
throw new Error("Claude received mismatched rendered wire guidance");
|
|
785
|
+
if (selected) {
|
|
786
|
+
text = renderProviderWireInstruction(text, selected);
|
|
787
|
+
policy = { ...policy, wireContract: selected, renderedWireContractDigest: selected.descriptorDigest };
|
|
788
|
+
}
|
|
789
|
+
if (policy?.output?.schemaDigest !== this.output?.schemaDigest || policy?.output?.operation !== this.output?.operation || selected?.descriptorDigest !== this.outputWireContract?.descriptorDigest)
|
|
790
|
+
await this.configureOutput(policy?.output, selected);
|
|
791
|
+
}
|
|
792
|
+
finally {
|
|
793
|
+
this.configuringTurn = false;
|
|
794
|
+
}
|
|
795
|
+
this.responseOnlyViolation = false;
|
|
796
|
+
this.activeTurnPolicy = policy;
|
|
621
797
|
this.responseOnlyTurn = Boolean(policy?.responseOnly);
|
|
622
798
|
this.phaseBarrier.begin(policy?.purpose ?? (policy?.responseOnly ? "response-repair" : "implementation"));
|
|
623
|
-
const turnId = randomUUID();
|
|
799
|
+
const turnId = policy?.dispatchId ?? randomUUID();
|
|
624
800
|
this.activeProviderTurnId = turnId;
|
|
625
801
|
const observer = this.opts.observer;
|
|
626
802
|
if (!observer) {
|
|
@@ -663,7 +839,7 @@ export class ClaudeAdapter {
|
|
|
663
839
|
if (this.opts.fast)
|
|
664
840
|
await this.prepareFastMode();
|
|
665
841
|
const result = await this.sendTurnAttempt(text);
|
|
666
|
-
if (result.isError && this.fastModeEnabled && !this.turnSawWork && isFastModeUnavailable(result.rawResponse ?? result.text) && !this.streamEnded) {
|
|
842
|
+
if (!this.output && result.isError && this.fastModeEnabled && !this.turnSawWork && isFastModeUnavailable(result.rawResponse ?? result.text) && !this.streamEnded) {
|
|
667
843
|
await this.disableFastMode();
|
|
668
844
|
return this.sendTurnAttempt(text);
|
|
669
845
|
}
|
|
@@ -712,9 +888,9 @@ export class ClaudeAdapter {
|
|
|
712
888
|
for (const block of body.content) {
|
|
713
889
|
if (block.type !== "tool_result")
|
|
714
890
|
continue;
|
|
715
|
-
this.eventQueue.push({ kind: "provider-item", provider: "claude", lifecycle: "completed", itemType: "tool_result", payload: structuredClone(block), payloadCompleteness: "complete", providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
891
|
+
this.eventQueue.push({ kind: "provider-item", provider: "claude", lifecycle: "completed", itemType: typeof block.tool_use_id === "string" && this.structuredSubmissionIds.has(block.tool_use_id) ? "structuredOutput" : "tool_result", payload: structuredClone(block), payloadCompleteness: "complete", providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
716
892
|
const callId = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
|
|
717
|
-
if (!callId)
|
|
893
|
+
if (!callId || this.structuredSubmissionIds.has(callId))
|
|
718
894
|
continue;
|
|
719
895
|
if (this.completedToolCalls.has(callId))
|
|
720
896
|
continue;
|
|
@@ -735,11 +911,15 @@ export class ClaudeAdapter {
|
|
|
735
911
|
: failed && /cancel(?:led|ed)/i.test(text) ? "cancelled"
|
|
736
912
|
: failed && /permission denied/i.test(text) ? "permission_denied"
|
|
737
913
|
: failed && /not found/i.test(text) ? "not_found"
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
914
|
+
// Claude's Read tool explicitly reports its size guard this
|
|
915
|
+
// way. It is a terminal, observed rejection, not a
|
|
916
|
+
// provisional transport error that might later complete.
|
|
917
|
+
: failed && /\b(?:content|file)\b[\s\S]{0,160}\bexceeds\s+(?:the\s+)?maximum\s+allowed\s+tokens\b/i.test(text) ? "output_limit"
|
|
918
|
+
: failed && /\b(?:exit(?:ed)?|status)\s+[1-9]\d*\b/i.test(text) ? "nonzero_exit"
|
|
919
|
+
: failed && /\b(?:failed|error|exception)\b/i.test(text) ? "explicit_error"
|
|
920
|
+
: failed ? "unexplained_error"
|
|
921
|
+
: "completed";
|
|
922
|
+
const status = resultReason === "host_policy_denied" ? "denied" : resultReason === "timeout" ? "timed_out" : resultReason === "cancelled" ? "cancelled" : ["permission_denied", "not_found", "output_limit", "nonzero_exit", "explicit_error"].includes(resultReason) ? "failed" : resultReason === "completed" ? "completed" : "unknown";
|
|
743
923
|
if (status !== "unknown") {
|
|
744
924
|
this.completedToolCalls.add(callId);
|
|
745
925
|
if (this.completedToolCalls.size > 1000)
|
|
@@ -795,6 +975,7 @@ export class ClaudeAdapter {
|
|
|
795
975
|
// Require an advertised built-in local command; never substitute a
|
|
796
976
|
// model-generated warm-up for QA's pre-dispatch identity validation.
|
|
797
977
|
if (commands.some(command => command.name === "context" && "builtin" in command && command.builtin === true)) {
|
|
978
|
+
providerControlOperationV2("claude", "/context");
|
|
798
979
|
this.opts.onLifecycleTrace?.({ phase: "local-context-initialization-start", at: new Date().toISOString() });
|
|
799
980
|
const result = await this.sendTurn("/context", { responseOnly: true });
|
|
800
981
|
this.opts.onLifecycleTrace?.({ phase: "local-context-initialization-complete", at: new Date().toISOString() });
|
|
@@ -807,7 +988,11 @@ export class ClaudeAdapter {
|
|
|
807
988
|
}
|
|
808
989
|
return this.sessionIdentityReady;
|
|
809
990
|
};
|
|
810
|
-
const
|
|
991
|
+
const ready = await Promise.race([prepare(), interrupted]);
|
|
992
|
+
// The identity promise resolves once. A host may later adopt a new scope
|
|
993
|
+
// for the same conversation (for example a handoff generation), so the
|
|
994
|
+
// prepared session is the adapter's current reference, as for Codex.
|
|
995
|
+
const ref = this._sessionRef?.sessionId === ready.sessionId ? this._sessionRef : ready;
|
|
811
996
|
if (!ref.validatedAt)
|
|
812
997
|
throw new Error("Claude session identity was not provider-validated during initialization");
|
|
813
998
|
return ref;
|
|
@@ -857,6 +1042,7 @@ export class ClaudeAdapter {
|
|
|
857
1042
|
}
|
|
858
1043
|
}
|
|
859
1044
|
async compactInternal() {
|
|
1045
|
+
providerControlOperationV2("claude", "/compact");
|
|
860
1046
|
this.compactResult = undefined;
|
|
861
1047
|
this.manualCompactionInFlight = true;
|
|
862
1048
|
const normalMs = Math.min(180_000, Math.max(1, this.opts.compactionTimeoutMs ?? 120_000));
|
|
@@ -898,7 +1084,9 @@ export class ClaudeAdapter {
|
|
|
898
1084
|
// queue, including a later local command. Establish identity first.
|
|
899
1085
|
if (!this.providerIdentityObserved && typeof this.query.supportedCommands === "function")
|
|
900
1086
|
await this.prepareSession();
|
|
1087
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
901
1088
|
await new OperationDeadline("Claude settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ autoCompactEnabled: true }));
|
|
1089
|
+
providerControlOperationV2("claude", "getContextUsage");
|
|
902
1090
|
const baseline = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
903
1091
|
if (!Number.isFinite(baseline.maxTokens) || baseline.maxTokens <= 0
|
|
904
1092
|
|| !Number.isFinite(baseline.autoCompactThreshold) || baseline.autoCompactThreshold === undefined
|
|
@@ -912,7 +1100,9 @@ export class ClaudeAdapter {
|
|
|
912
1100
|
if (reserve < 0)
|
|
913
1101
|
throw new Error("Claude reported an invalid automatic-compaction reserve");
|
|
914
1102
|
const requestedWindow = tokenLimit(baseline.maxTokens, threshold) + reserve;
|
|
1103
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
915
1104
|
await new OperationDeadline("Claude settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ autoCompactEnabled: true, autoCompactWindow: requestedWindow }));
|
|
1105
|
+
providerControlOperationV2("claude", "getContextUsage");
|
|
916
1106
|
const installed = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
917
1107
|
if (!installed.isAutoCompactEnabled || !Number.isFinite(installed.maxTokens) || installed.maxTokens <= 0
|
|
918
1108
|
|| !Number.isFinite(installed.autoCompactThreshold) || installed.autoCompactThreshold === undefined) {
|
|
@@ -942,6 +1132,7 @@ export class ClaudeAdapter {
|
|
|
942
1132
|
restoreNativeCompactions(compactions) { this.nativeCompactions.unshift(...compactions); }
|
|
943
1133
|
async contextUsage() {
|
|
944
1134
|
try {
|
|
1135
|
+
providerControlOperationV2("claude", "getContextUsage");
|
|
945
1136
|
const usage = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
946
1137
|
const result = { used: usage.totalTokens, maximum: usage.maxTokens, percentage: usage.percentage, observedAt: new Date().toISOString(), source: "provider-query" };
|
|
947
1138
|
this.eventQueue.push({ kind: "context-usage", ...result });
|
|
@@ -959,14 +1150,21 @@ export class ClaudeAdapter {
|
|
|
959
1150
|
return { ok: false, error: "Claude SDK cannot change reasoning/fast controls on an existing transport" };
|
|
960
1151
|
if (settings.model === this.opts.model)
|
|
961
1152
|
return { ok: true };
|
|
1153
|
+
providerControlOperationV2("claude", "/model");
|
|
1154
|
+
if (settings.model && /[\r\n]/.test(settings.model))
|
|
1155
|
+
return { ok: false, error: "Model control requires one model identifier" };
|
|
962
1156
|
if (this.opts.fast)
|
|
963
1157
|
await this.prepareFastMode();
|
|
1158
|
+
if (this.fastModeEnabled)
|
|
1159
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
964
1160
|
if (this.fastModeEnabled)
|
|
965
1161
|
await new OperationDeadline("Claude standard speed", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ fastMode: false }));
|
|
966
1162
|
this.fastModeEnabled = false;
|
|
967
1163
|
const result = await new OperationDeadline("Claude model settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.sendTurn(`/model ${settings.model ?? "default"}`), () => this.abort.abort());
|
|
968
1164
|
if (result.isError)
|
|
969
1165
|
return { ok: false, error: result.text, ...(result.failure ? { failure: result.failure } : {}) };
|
|
1166
|
+
if (result.numTurns !== 0 || result.structured)
|
|
1167
|
+
return { ok: false, error: "Model control did not complete as a non-authoritative provider acknowledgement" };
|
|
970
1168
|
this.opts.model = settings.model;
|
|
971
1169
|
this.fastModePrepared = undefined;
|
|
972
1170
|
this.fastModeUnavailable = false;
|
|
@@ -995,8 +1193,15 @@ export class ClaudeAdapter {
|
|
|
995
1193
|
catch {
|
|
996
1194
|
// interrupt is best-effort — ignore if no turn is active
|
|
997
1195
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1196
|
+
try {
|
|
1197
|
+
this.query.close?.();
|
|
1198
|
+
await new OperationDeadline("Claude shutdown", this.opts.shutdownTimeoutMs ?? 10_000).run(() => this.pumpDone).catch(() => { });
|
|
1199
|
+
}
|
|
1200
|
+
finally {
|
|
1201
|
+
this.finishQueryBudget();
|
|
1202
|
+
if (this.scratchDirectory)
|
|
1203
|
+
rmSync(this.scratchDirectory, { recursive: true, force: true });
|
|
1204
|
+
}
|
|
1000
1205
|
}
|
|
1001
1206
|
failSessionIdentity(error) {
|
|
1002
1207
|
this.rejectSessionIdentity(error);
|
|
@@ -1137,7 +1342,7 @@ export class ClaudeAdapter {
|
|
|
1137
1342
|
pending.idleTimer.unref();
|
|
1138
1343
|
}
|
|
1139
1344
|
providerIdleTimeoutMs() {
|
|
1140
|
-
const configured = this.opts.providerIdleTimeoutMs;
|
|
1345
|
+
const configured = this.opts.providerIdleTimeoutMs ?? Number(process.env.RAFI_PROVIDER_IDLE_TIMEOUT_MS);
|
|
1141
1346
|
return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_PROVIDER_IDLE_TIMEOUT_MS;
|
|
1142
1347
|
}
|
|
1143
1348
|
streamFailureResult(message, sessionUnavailable) {
|