ai-foreman 1.7.28 → 2.0.0
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 +228 -24
- package/dist/adapters/codex.d.ts +17 -7
- package/dist/adapters/codex.js +195 -17
- 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 +86 -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/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 +30 -0
- package/dist/buildApproval.js +68 -0
- 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/builderGuidanceFollowup.js +17 -6
- package/dist/cli/manager.js +22 -20
- package/dist/cli/start.js +32 -24
- package/dist/cli/tickets.js +40 -24
- package/dist/continuity.d.ts +10 -14
- package/dist/continuity.js +198 -179
- package/dist/continuityOrigin.d.ts +13 -0
- package/dist/continuityOrigin.js +91 -0
- package/dist/foreman.d.ts +20 -5
- package/dist/foreman.js +57 -187
- 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 +2 -3
- package/dist/graph/turn.js +28 -40
- package/dist/handoffProtocol.js +2 -2
- package/dist/handoffs.d.ts +3 -1
- package/dist/handoffs.js +90 -59
- package/dist/hostGraphApplicability.d.ts +46 -0
- package/dist/hostGraphApplicability.js +97 -0
- package/dist/hostQaAugmentation.d.ts +18 -0
- package/dist/hostQaAugmentation.js +81 -0
- package/dist/hostQaComparison.d.ts +21 -2
- package/dist/hostQaComparison.js +43 -28
- package/dist/hostSourcePreservation.d.ts +38 -0
- package/dist/hostSourcePreservation.js +72 -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/probeIpc.d.ts +14 -0
- package/dist/probeIpc.js +29 -0
- package/dist/qaBuildGate.d.ts +21 -0
- package/dist/qaBuildGate.js +48 -12
- 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 +11 -7
- 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/qaEffectiveConfig.d.ts +1 -0
- package/dist/qaEffectiveConfig.js +6 -1
- 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 +133 -229
- package/dist/qaPreparationInputs.js +19 -0
- package/dist/qaPreparationStore.js +34 -6
- package/dist/qaPrerequisites.d.ts +2 -1
- package/dist/qaPrerequisites.js +21 -1
- package/dist/qaProtocolV2.d.ts +15 -0
- package/dist/qaRecovery.js +21 -4
- package/dist/qaReview.js +232 -394
- package/dist/qaReviewEvidence.d.ts +7 -3
- package/dist/qaReviewEvidence.js +15 -4
- package/dist/qaRuntime.js +1 -1
- package/dist/qaSnapshot.js +71 -21
- package/dist/qaStructuredResponse.d.ts +10 -0
- package/dist/qaStructuredResponse.js +57 -0
- package/dist/qaTimeline.js +5 -3
- package/dist/qaToolRecovery.d.ts +12 -0
- package/dist/qaToolRecovery.js +44 -0
- package/dist/qaVerificationContract.d.ts +3 -2
- package/dist/qaVerificationContract.js +58 -34
- package/dist/readinessGate.js +5 -4
- package/dist/runtimeReadiness.js +23 -15
- 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 +257 -0
- package/dist/supervisedStart.d.ts +1 -1
- package/dist/supervisedStart.js +78 -31
- package/dist/supervisor.d.ts +18 -2
- package/dist/supervisor.js +74 -17
- package/dist/ticketPlanning.d.ts +1 -373
- package/dist/ticketPlanning.js +2 -26
- 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 +105 -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 +100 -2
- package/dist/workflowDb.js +781 -35
- package/dist/workflowReader.d.ts +1 -0
- package/dist/workflowReader.js +9 -0
- package/package.json +15 -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",
|
|
@@ -58,7 +64,7 @@ export function buildClaudeQueryOptions(opts) {
|
|
|
58
64
|
autoAllowBashIfSandboxed: true,
|
|
59
65
|
allowUnsandboxedCommands: false,
|
|
60
66
|
network: { allowedDomains: [], deniedDomains: ["*"] },
|
|
61
|
-
filesystem: { allowWrite: [
|
|
67
|
+
filesystem: { allowWrite: [scratchDirectory], denyWrite: [opts.cwd, ...(opts.configRoot ? [opts.configRoot] : [])] },
|
|
62
68
|
} } : {}),
|
|
63
69
|
};
|
|
64
70
|
const exactSkills = opts.preloadedSkillContent?.length
|
|
@@ -73,9 +79,9 @@ export function buildClaudeQueryOptions(opts) {
|
|
|
73
79
|
}
|
|
74
80
|
return base;
|
|
75
81
|
}
|
|
76
|
-
function qaEnvironment(source,
|
|
82
|
+
function qaEnvironment(source, scratchDirectory) {
|
|
77
83
|
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:
|
|
84
|
+
return { ...Object.fromEntries(allowed.flatMap((key) => source[key] === undefined ? [] : [[key, source[key]]])), TMPDIR: scratchDirectory };
|
|
79
85
|
}
|
|
80
86
|
export async function probeClaudeSession(ref, input) {
|
|
81
87
|
const now = input.now ?? new Date();
|
|
@@ -176,6 +182,7 @@ export function claudeApiRetryEvent(message) {
|
|
|
176
182
|
*/
|
|
177
183
|
export class ClaudeAdapter {
|
|
178
184
|
opts;
|
|
185
|
+
queryFactory;
|
|
179
186
|
agent = "claude";
|
|
180
187
|
graphRuntimeSettings() { return { model: this.opts.model, effort: this.opts.effort, fast: this.opts.fast, runtimeExecutable: this.opts.runtimeExecutable }; }
|
|
181
188
|
inbox = new AsyncQueue();
|
|
@@ -183,6 +190,11 @@ export class ClaudeAdapter {
|
|
|
183
190
|
query;
|
|
184
191
|
abort = new AbortController();
|
|
185
192
|
pumpDone;
|
|
193
|
+
rotatingQuery = false;
|
|
194
|
+
configuringTurn = false;
|
|
195
|
+
output;
|
|
196
|
+
outputWireContract;
|
|
197
|
+
responseOnlyViolation = false;
|
|
186
198
|
_sessionId;
|
|
187
199
|
_sessionRef;
|
|
188
200
|
sessionIdentityReady;
|
|
@@ -204,8 +216,10 @@ export class ClaudeAdapter {
|
|
|
204
216
|
cumulativeUsage = { observedAt: new Date(0).toISOString(), source: "provider" };
|
|
205
217
|
priorTurnUsage = {};
|
|
206
218
|
activeProviderTurnId;
|
|
219
|
+
activeTurnPolicy;
|
|
207
220
|
activeProviderTurnSpanId;
|
|
208
221
|
toolSpans = new Map();
|
|
222
|
+
structuredSubmissionIds = new Set();
|
|
209
223
|
toolCalls = new Map();
|
|
210
224
|
completedToolCalls = new Set();
|
|
211
225
|
unknownToolResults = new Map();
|
|
@@ -221,6 +235,7 @@ export class ClaudeAdapter {
|
|
|
221
235
|
streamEnded = false;
|
|
222
236
|
closed = false;
|
|
223
237
|
closePromise;
|
|
238
|
+
scratchDirectory;
|
|
224
239
|
launchScopeObserved = false;
|
|
225
240
|
providerIdentityObserved = false;
|
|
226
241
|
toolCwd;
|
|
@@ -251,6 +266,7 @@ export class ClaudeAdapter {
|
|
|
251
266
|
return;
|
|
252
267
|
}
|
|
253
268
|
try {
|
|
269
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
254
270
|
await new OperationDeadline("Claude fast mode", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ fastMode: true }));
|
|
255
271
|
this.fastModeEnabled = true;
|
|
256
272
|
}
|
|
@@ -269,10 +285,13 @@ export class ClaudeAdapter {
|
|
|
269
285
|
this.fastModeEnabled = false;
|
|
270
286
|
}
|
|
271
287
|
async disableFastMode() {
|
|
288
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
272
289
|
await new OperationDeadline("Claude standard speed", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ fastMode: false }));
|
|
273
290
|
this.disableFastModeNotice();
|
|
274
291
|
}
|
|
275
292
|
static async create(opts) {
|
|
293
|
+
const inheritedBudget = inheritedStructuredLiveBudget("claude");
|
|
294
|
+
opts = { ...opts, queryBudget: opts.queryBudget ?? inheritedBudget };
|
|
276
295
|
try {
|
|
277
296
|
const runtimeExecutable = opts.runtimeExecutable ?? resolveExecutablePath("claude");
|
|
278
297
|
if (!runtimeExecutable) {
|
|
@@ -310,8 +329,9 @@ export class ClaudeAdapter {
|
|
|
310
329
|
}
|
|
311
330
|
}
|
|
312
331
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
313
|
-
constructor(opts,
|
|
332
|
+
constructor(opts, queryFactory) {
|
|
314
333
|
this.opts = opts;
|
|
334
|
+
this.queryFactory = queryFactory;
|
|
315
335
|
this.phaseBarrier.begin("initialization");
|
|
316
336
|
this._sessionId = opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId;
|
|
317
337
|
this._sessionRef = opts.resumeSessionRef;
|
|
@@ -324,15 +344,61 @@ export class ClaudeAdapter {
|
|
|
324
344
|
start: (attemptId) => opts.observer.store.startSpan(this.observationContext(), { kind: "provider_wait", name: "Waiting for Claude after question answer", attributes: { attemptId } }),
|
|
325
345
|
finish: (spanId, outcome) => opts.observer.store.finishSpan(spanId, { outcome }),
|
|
326
346
|
} : undefined);
|
|
327
|
-
|
|
347
|
+
// Planning and interpretation also use read-only sessions, without a QA
|
|
348
|
+
// snapshot's sibling scratch directory. Own a real, private directory for
|
|
349
|
+
// the complete query lifecycle, including exact-session schema rotations.
|
|
350
|
+
// macOS's default TMPDIR is too long for the SDK's Unix socket paths.
|
|
351
|
+
if (opts.sessionRole === "qa" || opts.sandboxMode === "read-only")
|
|
352
|
+
this.scratchDirectory = mkdtempSync(join(process.platform === "darwin" ? "/tmp" : tmpdir(), "rf-c-"));
|
|
353
|
+
try {
|
|
354
|
+
this.startQuery();
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
this.finishQueryBudget();
|
|
358
|
+
if (this.scratchDirectory)
|
|
359
|
+
rmSync(this.scratchDirectory, { recursive: true, force: true });
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
queryBudget;
|
|
364
|
+
queryBudgetAbort;
|
|
365
|
+
finishQueryBudget() {
|
|
366
|
+
if (this.queryBudgetAbort)
|
|
367
|
+
this.queryBudget?.signal.removeEventListener("abort", this.queryBudgetAbort);
|
|
368
|
+
this.queryBudgetAbort = undefined;
|
|
369
|
+
this.queryBudget?.close();
|
|
370
|
+
this.queryBudget = undefined;
|
|
371
|
+
}
|
|
372
|
+
startQuery() {
|
|
373
|
+
const opts = this.opts;
|
|
374
|
+
this.queryBudget = opts.queryBudget?.reserveQuery("claude");
|
|
375
|
+
if (this.queryBudget) {
|
|
376
|
+
const lease = this.queryBudget;
|
|
377
|
+
this.queryBudgetAbort = () => { this.abort.abort(); void this.close(); };
|
|
378
|
+
lease.signal.addEventListener("abort", this.queryBudgetAbort, { once: true });
|
|
379
|
+
if (lease.signal.aborted) {
|
|
380
|
+
this.finishQueryBudget();
|
|
381
|
+
throw new Error("Live query deadline expired before launch");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
this.query = this.queryFactory({
|
|
328
385
|
prompt: this.providerInputs(),
|
|
329
386
|
options: {
|
|
330
|
-
...buildClaudeQueryOptions(opts),
|
|
387
|
+
...buildClaudeQueryOptions(opts, this.scratchDirectory),
|
|
388
|
+
...(this.queryBudget ? { maxBudgetUsd: this.queryBudget.maximumSpendUsd } : {}),
|
|
389
|
+
...(this._sessionId ? { resume: this._sessionId } : {}),
|
|
390
|
+
...(this.outputWireContract ? { outputFormat: { type: "json_schema", schema: this.outputWireContract.wireSchema } } : {}),
|
|
331
391
|
abortController: this.abort,
|
|
332
392
|
hooks: {
|
|
333
393
|
PreToolUse: [{ hooks: [async (input) => {
|
|
334
394
|
if (input.hook_event_name !== "PreToolUse")
|
|
335
395
|
return {};
|
|
396
|
+
if (this.isResultSubmission(input.tool_name, input.tool_input)) {
|
|
397
|
+
const reason = this.resultSubmissionError(input.tool_input);
|
|
398
|
+
return reason ? { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } } : {};
|
|
399
|
+
}
|
|
400
|
+
if (this.output && this.responseOnlyTurn)
|
|
401
|
+
this.responseOnlyViolation = true;
|
|
336
402
|
this.toolCwd = input.cwd;
|
|
337
403
|
this.toolCwds.set(input.tool_use_id, input.cwd);
|
|
338
404
|
if (this.toolCwds.size > 1000)
|
|
@@ -356,6 +422,12 @@ export class ClaudeAdapter {
|
|
|
356
422
|
},
|
|
357
423
|
stderr: (data) => this.captureStderr(data),
|
|
358
424
|
canUseTool: async (toolName, input, requestOptions = {}) => {
|
|
425
|
+
if (this.isResultSubmission(toolName, input)) {
|
|
426
|
+
const reason = this.resultSubmissionError(input);
|
|
427
|
+
return reason ? { behavior: "deny", message: reason } : { behavior: "allow", updatedInput: input };
|
|
428
|
+
}
|
|
429
|
+
if (this.output && this.responseOnlyTurn)
|
|
430
|
+
this.responseOnlyViolation = true;
|
|
359
431
|
if (this.responseOnlyTurn)
|
|
360
432
|
return { behavior: "deny", message: "QA response correction forbids all tools, including read-only tools", interrupt: true };
|
|
361
433
|
const isProviderQuestion = toolName === "AskUserQuestion";
|
|
@@ -394,6 +466,60 @@ export class ClaudeAdapter {
|
|
|
394
466
|
});
|
|
395
467
|
this.pumpDone = this.pump();
|
|
396
468
|
}
|
|
469
|
+
isResultSubmission(name, input) {
|
|
470
|
+
// Only the SDK's built-in submission in a host-configured structured query.
|
|
471
|
+
// An MCP name or arbitrary tool cannot acquire this exception.
|
|
472
|
+
return Boolean(this.pending && this.output && name === "StructuredOutput");
|
|
473
|
+
}
|
|
474
|
+
resultSubmissionError(input) {
|
|
475
|
+
try {
|
|
476
|
+
// This SDK hook sees provider wire data, not the canonical artifact.
|
|
477
|
+
// Canonical validation is deliberately deferred until the selected
|
|
478
|
+
// normalizer runs in structuredReceipt.
|
|
479
|
+
validateProviderWireValue(this.outputWireContract, input);
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
// The SDK submission is data, including when malformed. Give its bounded
|
|
484
|
+
// native retry the actual wire-contract violations, never a mutation-policy
|
|
485
|
+
// diagnosis. Only a valid wire payload may invoke the SDK submission tool.
|
|
486
|
+
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.`;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async configureOutput(output, wireContract) {
|
|
490
|
+
const selected = output ? wireContract ?? providerWireContract(output, "claude", "default", { requireRegistered: true }) : undefined;
|
|
491
|
+
if (selected && (selected.provider !== "claude" || selected.canonical.schemaDigest !== output.schemaDigest))
|
|
492
|
+
throw new Error("Claude received a mismatched provider wire contract");
|
|
493
|
+
if (output?.schemaDigest === this.output?.schemaDigest && output?.operation === this.output?.operation && selected?.descriptorDigest === this.outputWireContract?.descriptorDigest)
|
|
494
|
+
return;
|
|
495
|
+
if (this.pending || this.closed || this.streamEnded)
|
|
496
|
+
throw new Error("Cannot change output schema on an active or unavailable Claude query");
|
|
497
|
+
this.rotatingQuery = true;
|
|
498
|
+
try {
|
|
499
|
+
this.inbox.close();
|
|
500
|
+
this.abort.abort();
|
|
501
|
+
this.query.close?.();
|
|
502
|
+
await new OperationDeadline("Claude schema transition", this.opts.shutdownTimeoutMs ?? 10_000).run(() => this.pumpDone);
|
|
503
|
+
this.finishQueryBudget();
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
this.streamEnded = true;
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
this.rotatingQuery = false;
|
|
511
|
+
}
|
|
512
|
+
this.inbox = new AsyncQueue();
|
|
513
|
+
this.abort = new AbortController();
|
|
514
|
+
this.output = output;
|
|
515
|
+
this.outputWireContract = selected;
|
|
516
|
+
this.fastModePrepared = undefined;
|
|
517
|
+
this.fastModeEnabled = false;
|
|
518
|
+
this.providerIdentityObserved = false;
|
|
519
|
+
this.startQuery();
|
|
520
|
+
if (this._sessionId)
|
|
521
|
+
this.eventQueue.push({ kind: "session-transition", transition: "resumed", detail: "Exact session resumed for output schema change" });
|
|
522
|
+
}
|
|
397
523
|
/** Background loop: consume the SDK message stream until it ends. */
|
|
398
524
|
async *providerInputs() {
|
|
399
525
|
for await (const message of this.inbox) {
|
|
@@ -413,6 +539,8 @@ export class ClaudeAdapter {
|
|
|
413
539
|
}
|
|
414
540
|
}
|
|
415
541
|
catch (err) {
|
|
542
|
+
if (this.rotatingQuery)
|
|
543
|
+
return;
|
|
416
544
|
this.questionTrace?.finish(this.closed ? "closed" : "stream-error");
|
|
417
545
|
// Suppress the AbortError that fires when close() aborts the stream.
|
|
418
546
|
const isShutdownAbort = this.closed &&
|
|
@@ -429,6 +557,8 @@ export class ClaudeAdapter {
|
|
|
429
557
|
}
|
|
430
558
|
}
|
|
431
559
|
finally {
|
|
560
|
+
if (this.rotatingQuery)
|
|
561
|
+
return;
|
|
432
562
|
this.questionTrace?.finish(this.closed ? "closed" : "stream-ended");
|
|
433
563
|
this.streamEnded = true;
|
|
434
564
|
if (!this.closed && this.pending) {
|
|
@@ -464,7 +594,7 @@ export class ClaudeAdapter {
|
|
|
464
594
|
this.turnSignals.push(`assistant error: ${msg.error}`);
|
|
465
595
|
}
|
|
466
596
|
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() });
|
|
597
|
+
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
598
|
if (block.type === "text" && block.text) {
|
|
469
599
|
if (msg.error && this.fastModeEnabled && isFastModeUnavailable(block.text) && !this.turnSawWork)
|
|
470
600
|
continue;
|
|
@@ -473,6 +603,16 @@ export class ClaudeAdapter {
|
|
|
473
603
|
this.opts.observer?.signal(true);
|
|
474
604
|
}
|
|
475
605
|
else if (block.type === "tool_use") {
|
|
606
|
+
// The SDK result submission is transport, not a domain tool. Retain its
|
|
607
|
+
// provider-item evidence without falsely reporting an acceptance violation.
|
|
608
|
+
if (this.isResultSubmission(block.name, block.input)) {
|
|
609
|
+
this.structuredSubmissionIds.add(block.id);
|
|
610
|
+
if (this.structuredSubmissionIds.size > 1000)
|
|
611
|
+
this.structuredSubmissionIds.delete(this.structuredSubmissionIds.values().next().value);
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
if (this.output && this.responseOnlyTurn && !this.isResultSubmission(block.name, block.input))
|
|
615
|
+
this.responseOnlyViolation = true;
|
|
476
616
|
this.turnSawWork = true;
|
|
477
617
|
const callId = block.id;
|
|
478
618
|
this.toolCalls.set(callId, { name: block.name, input: block.input });
|
|
@@ -537,6 +677,8 @@ export class ClaudeAdapter {
|
|
|
537
677
|
this.eventQueue.push({ kind: "activity", provider: "claude", state: "running tool", detail: `${msg.tool_name} (${Math.floor(msg.elapsed_time_seconds)}s)`, transient: true });
|
|
538
678
|
const progress = msg;
|
|
539
679
|
const callId = typeof progress.tool_use_id === "string" ? progress.tool_use_id : typeof progress.toolUseID === "string" ? progress.toolUseID : undefined;
|
|
680
|
+
if (callId && this.structuredSubmissionIds.has(callId))
|
|
681
|
+
return;
|
|
540
682
|
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
683
|
this.opts.observer?.signal(false);
|
|
542
684
|
}
|
|
@@ -586,7 +728,8 @@ export class ClaudeAdapter {
|
|
|
586
728
|
cwd: this.opts.cwd,
|
|
587
729
|
diagnostics: rawDiagnostics,
|
|
588
730
|
} : undefined;
|
|
589
|
-
|
|
731
|
+
this.queryBudget?.record(msg.total_cost_usd);
|
|
732
|
+
let result = {
|
|
590
733
|
text: failure ? formatClaudeFailure(failure) : text,
|
|
591
734
|
isError: msg.is_error,
|
|
592
735
|
numTurns: msg.num_turns,
|
|
@@ -599,8 +742,21 @@ export class ClaudeAdapter {
|
|
|
599
742
|
rawResponse: text,
|
|
600
743
|
cleanedResponse: failure ? formatClaudeFailure(failure) : text,
|
|
601
744
|
providerMetadata: { provider: "claude", sessionId: this._sessionId, sessionRef: this._sessionRef },
|
|
745
|
+
...(this.activeTurnPolicy?.selectedDispatch ? { selectedDispatch: this.activeTurnPolicy.selectedDispatch } : {}),
|
|
602
746
|
};
|
|
603
747
|
const rawResult = msg;
|
|
748
|
+
if (this.output) {
|
|
749
|
+
const selectedDispatch = this.activeTurnPolicy?.selectedDispatch;
|
|
750
|
+
if (!selectedDispatch || !this._sessionId)
|
|
751
|
+
throw new Error("Structured terminal lost its sealed dispatch tuple or session");
|
|
752
|
+
result = { ...result, selectedDispatch, structured: msg.subtype !== "success" || msg.is_error || this.responseOnlyViolation
|
|
753
|
+
? { state: "rejected", reason: this.responseOnlyViolation ? "tool-policy-violation" : "execution-not-completed", detail: `Claude terminal ${msg.subtype}`, dispatchId: result.turnId }
|
|
754
|
+
: !this.outputWireContract || !this.activeTurnPolicy?.selectedDispatch
|
|
755
|
+
? { state: "rejected", reason: "invalid-contract", detail: "Claude structured turn has no sealed descriptor or tuple", dispatchId: result.turnId }
|
|
756
|
+
: 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) };
|
|
757
|
+
if (result.structured.state === "rejected")
|
|
758
|
+
result.isError = true;
|
|
759
|
+
}
|
|
604
760
|
const mergedUsage = mergeClaudeProviderSessionUsage(this.cumulativeUsage, rawResult);
|
|
605
761
|
result.inputTokens = mergedUsage.inputTokens;
|
|
606
762
|
result.outputTokens = mergedUsage.outputTokens;
|
|
@@ -616,11 +772,30 @@ export class ClaudeAdapter {
|
|
|
616
772
|
}
|
|
617
773
|
}
|
|
618
774
|
async sendTurn(text, policy) {
|
|
619
|
-
if (this.pending)
|
|
775
|
+
if (this.pending || this.configuringTurn)
|
|
620
776
|
throw new Error("a turn is already in progress");
|
|
777
|
+
if (policy?.output)
|
|
778
|
+
assertActiveProviderDispatch(policy, text, "claude", this.sessionRef());
|
|
779
|
+
this.configuringTurn = true;
|
|
780
|
+
try {
|
|
781
|
+
const selected = policy?.output ? policy.wireContract ?? providerWireContract(policy.output, "claude", "default", { requireRegistered: true }) : undefined;
|
|
782
|
+
if (selected && policy?.renderedWireContractDigest && policy.renderedWireContractDigest !== selected.descriptorDigest)
|
|
783
|
+
throw new Error("Claude received mismatched rendered wire guidance");
|
|
784
|
+
if (selected) {
|
|
785
|
+
text = renderProviderWireInstruction(text, selected);
|
|
786
|
+
policy = { ...policy, wireContract: selected, renderedWireContractDigest: selected.descriptorDigest };
|
|
787
|
+
}
|
|
788
|
+
if (policy?.output?.schemaDigest !== this.output?.schemaDigest || policy?.output?.operation !== this.output?.operation || selected?.descriptorDigest !== this.outputWireContract?.descriptorDigest)
|
|
789
|
+
await this.configureOutput(policy?.output, selected);
|
|
790
|
+
}
|
|
791
|
+
finally {
|
|
792
|
+
this.configuringTurn = false;
|
|
793
|
+
}
|
|
794
|
+
this.responseOnlyViolation = false;
|
|
795
|
+
this.activeTurnPolicy = policy;
|
|
621
796
|
this.responseOnlyTurn = Boolean(policy?.responseOnly);
|
|
622
797
|
this.phaseBarrier.begin(policy?.purpose ?? (policy?.responseOnly ? "response-repair" : "implementation"));
|
|
623
|
-
const turnId = randomUUID();
|
|
798
|
+
const turnId = policy?.dispatchId ?? randomUUID();
|
|
624
799
|
this.activeProviderTurnId = turnId;
|
|
625
800
|
const observer = this.opts.observer;
|
|
626
801
|
if (!observer) {
|
|
@@ -663,7 +838,7 @@ export class ClaudeAdapter {
|
|
|
663
838
|
if (this.opts.fast)
|
|
664
839
|
await this.prepareFastMode();
|
|
665
840
|
const result = await this.sendTurnAttempt(text);
|
|
666
|
-
if (result.isError && this.fastModeEnabled && !this.turnSawWork && isFastModeUnavailable(result.rawResponse ?? result.text) && !this.streamEnded) {
|
|
841
|
+
if (!this.output && result.isError && this.fastModeEnabled && !this.turnSawWork && isFastModeUnavailable(result.rawResponse ?? result.text) && !this.streamEnded) {
|
|
667
842
|
await this.disableFastMode();
|
|
668
843
|
return this.sendTurnAttempt(text);
|
|
669
844
|
}
|
|
@@ -712,9 +887,9 @@ export class ClaudeAdapter {
|
|
|
712
887
|
for (const block of body.content) {
|
|
713
888
|
if (block.type !== "tool_result")
|
|
714
889
|
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() });
|
|
890
|
+
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
891
|
const callId = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
|
|
717
|
-
if (!callId)
|
|
892
|
+
if (!callId || this.structuredSubmissionIds.has(callId))
|
|
718
893
|
continue;
|
|
719
894
|
if (this.completedToolCalls.has(callId))
|
|
720
895
|
continue;
|
|
@@ -735,11 +910,15 @@ export class ClaudeAdapter {
|
|
|
735
910
|
: failed && /cancel(?:led|ed)/i.test(text) ? "cancelled"
|
|
736
911
|
: failed && /permission denied/i.test(text) ? "permission_denied"
|
|
737
912
|
: failed && /not found/i.test(text) ? "not_found"
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
913
|
+
// Claude's Read tool explicitly reports its size guard this
|
|
914
|
+
// way. It is a terminal, observed rejection, not a
|
|
915
|
+
// provisional transport error that might later complete.
|
|
916
|
+
: failed && /\b(?:content|file)\b[\s\S]{0,160}\bexceeds\s+(?:the\s+)?maximum\s+allowed\s+tokens\b/i.test(text) ? "output_limit"
|
|
917
|
+
: failed && /\b(?:exit(?:ed)?|status)\s+[1-9]\d*\b/i.test(text) ? "nonzero_exit"
|
|
918
|
+
: failed && /\b(?:failed|error|exception)\b/i.test(text) ? "explicit_error"
|
|
919
|
+
: failed ? "unexplained_error"
|
|
920
|
+
: "completed";
|
|
921
|
+
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
922
|
if (status !== "unknown") {
|
|
744
923
|
this.completedToolCalls.add(callId);
|
|
745
924
|
if (this.completedToolCalls.size > 1000)
|
|
@@ -749,7 +928,7 @@ export class ClaudeAdapter {
|
|
|
749
928
|
const outputDigest = createHash("sha256").update(text).digest("hex");
|
|
750
929
|
// Persist only correlation metadata. The result text can contain source
|
|
751
930
|
// paths, command output, or credentials and must not enter lifecycle logs.
|
|
752
|
-
this.opts.onLifecycleTrace?.({ phase: "provider-tool-result", at: new Date().toISOString(), sessionId: this._sessionId, providerTurnId: this.activeProviderTurnId, toolCallId: callId, toolName: call?.name ?? "unidentified tool", classification: status, resultReason, isError: failed, outputDigest, rawOutputBytes: Buffer.byteLength(text) });
|
|
931
|
+
this.opts.onLifecycleTrace?.({ phase: "provider-tool-result", at: new Date().toISOString(), sessionId: this._sessionId, providerTurnId: this.activeProviderTurnId, toolCallId: callId, toolName: call?.name ?? "unidentified tool", classification: status, resultReason, isError: failed, outputDigest, rawOutputBytes: Buffer.byteLength(text), completionKnown: status !== "unknown" });
|
|
753
932
|
if (status === "unknown") {
|
|
754
933
|
if (this.unknownToolResults.get(callId) === outputDigest)
|
|
755
934
|
continue;
|
|
@@ -795,6 +974,7 @@ export class ClaudeAdapter {
|
|
|
795
974
|
// Require an advertised built-in local command; never substitute a
|
|
796
975
|
// model-generated warm-up for QA's pre-dispatch identity validation.
|
|
797
976
|
if (commands.some(command => command.name === "context" && "builtin" in command && command.builtin === true)) {
|
|
977
|
+
providerControlOperationV2("claude", "/context");
|
|
798
978
|
this.opts.onLifecycleTrace?.({ phase: "local-context-initialization-start", at: new Date().toISOString() });
|
|
799
979
|
const result = await this.sendTurn("/context", { responseOnly: true });
|
|
800
980
|
this.opts.onLifecycleTrace?.({ phase: "local-context-initialization-complete", at: new Date().toISOString() });
|
|
@@ -807,7 +987,11 @@ export class ClaudeAdapter {
|
|
|
807
987
|
}
|
|
808
988
|
return this.sessionIdentityReady;
|
|
809
989
|
};
|
|
810
|
-
const
|
|
990
|
+
const ready = await Promise.race([prepare(), interrupted]);
|
|
991
|
+
// The identity promise resolves once. A host may later adopt a new scope
|
|
992
|
+
// for the same conversation (for example a handoff generation), so the
|
|
993
|
+
// prepared session is the adapter's current reference, as for Codex.
|
|
994
|
+
const ref = this._sessionRef?.sessionId === ready.sessionId ? this._sessionRef : ready;
|
|
811
995
|
if (!ref.validatedAt)
|
|
812
996
|
throw new Error("Claude session identity was not provider-validated during initialization");
|
|
813
997
|
return ref;
|
|
@@ -857,6 +1041,7 @@ export class ClaudeAdapter {
|
|
|
857
1041
|
}
|
|
858
1042
|
}
|
|
859
1043
|
async compactInternal() {
|
|
1044
|
+
providerControlOperationV2("claude", "/compact");
|
|
860
1045
|
this.compactResult = undefined;
|
|
861
1046
|
this.manualCompactionInFlight = true;
|
|
862
1047
|
const normalMs = Math.min(180_000, Math.max(1, this.opts.compactionTimeoutMs ?? 120_000));
|
|
@@ -898,7 +1083,9 @@ export class ClaudeAdapter {
|
|
|
898
1083
|
// queue, including a later local command. Establish identity first.
|
|
899
1084
|
if (!this.providerIdentityObserved && typeof this.query.supportedCommands === "function")
|
|
900
1085
|
await this.prepareSession();
|
|
1086
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
901
1087
|
await new OperationDeadline("Claude settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ autoCompactEnabled: true }));
|
|
1088
|
+
providerControlOperationV2("claude", "getContextUsage");
|
|
902
1089
|
const baseline = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
903
1090
|
if (!Number.isFinite(baseline.maxTokens) || baseline.maxTokens <= 0
|
|
904
1091
|
|| !Number.isFinite(baseline.autoCompactThreshold) || baseline.autoCompactThreshold === undefined
|
|
@@ -912,7 +1099,9 @@ export class ClaudeAdapter {
|
|
|
912
1099
|
if (reserve < 0)
|
|
913
1100
|
throw new Error("Claude reported an invalid automatic-compaction reserve");
|
|
914
1101
|
const requestedWindow = tokenLimit(baseline.maxTokens, threshold) + reserve;
|
|
1102
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
915
1103
|
await new OperationDeadline("Claude settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ autoCompactEnabled: true, autoCompactWindow: requestedWindow }));
|
|
1104
|
+
providerControlOperationV2("claude", "getContextUsage");
|
|
916
1105
|
const installed = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
917
1106
|
if (!installed.isAutoCompactEnabled || !Number.isFinite(installed.maxTokens) || installed.maxTokens <= 0
|
|
918
1107
|
|| !Number.isFinite(installed.autoCompactThreshold) || installed.autoCompactThreshold === undefined) {
|
|
@@ -942,6 +1131,7 @@ export class ClaudeAdapter {
|
|
|
942
1131
|
restoreNativeCompactions(compactions) { this.nativeCompactions.unshift(...compactions); }
|
|
943
1132
|
async contextUsage() {
|
|
944
1133
|
try {
|
|
1134
|
+
providerControlOperationV2("claude", "getContextUsage");
|
|
945
1135
|
const usage = await new OperationDeadline("Claude context usage", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.getContextUsage());
|
|
946
1136
|
const result = { used: usage.totalTokens, maximum: usage.maxTokens, percentage: usage.percentage, observedAt: new Date().toISOString(), source: "provider-query" };
|
|
947
1137
|
this.eventQueue.push({ kind: "context-usage", ...result });
|
|
@@ -959,14 +1149,21 @@ export class ClaudeAdapter {
|
|
|
959
1149
|
return { ok: false, error: "Claude SDK cannot change reasoning/fast controls on an existing transport" };
|
|
960
1150
|
if (settings.model === this.opts.model)
|
|
961
1151
|
return { ok: true };
|
|
1152
|
+
providerControlOperationV2("claude", "/model");
|
|
1153
|
+
if (settings.model && /[\r\n]/.test(settings.model))
|
|
1154
|
+
return { ok: false, error: "Model control requires one model identifier" };
|
|
962
1155
|
if (this.opts.fast)
|
|
963
1156
|
await this.prepareFastMode();
|
|
1157
|
+
if (this.fastModeEnabled)
|
|
1158
|
+
providerControlOperationV2("claude", "applyFlagSettings");
|
|
964
1159
|
if (this.fastModeEnabled)
|
|
965
1160
|
await new OperationDeadline("Claude standard speed", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.query.applyFlagSettings({ fastMode: false }));
|
|
966
1161
|
this.fastModeEnabled = false;
|
|
967
1162
|
const result = await new OperationDeadline("Claude model settings", this.opts.rpcTimeoutMs ?? 60_000).run(() => this.sendTurn(`/model ${settings.model ?? "default"}`), () => this.abort.abort());
|
|
968
1163
|
if (result.isError)
|
|
969
1164
|
return { ok: false, error: result.text, ...(result.failure ? { failure: result.failure } : {}) };
|
|
1165
|
+
if (result.numTurns !== 0 || result.structured)
|
|
1166
|
+
return { ok: false, error: "Model control did not complete as a non-authoritative provider acknowledgement" };
|
|
970
1167
|
this.opts.model = settings.model;
|
|
971
1168
|
this.fastModePrepared = undefined;
|
|
972
1169
|
this.fastModeUnavailable = false;
|
|
@@ -995,8 +1192,15 @@ export class ClaudeAdapter {
|
|
|
995
1192
|
catch {
|
|
996
1193
|
// interrupt is best-effort — ignore if no turn is active
|
|
997
1194
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1195
|
+
try {
|
|
1196
|
+
this.query.close?.();
|
|
1197
|
+
await new OperationDeadline("Claude shutdown", this.opts.shutdownTimeoutMs ?? 10_000).run(() => this.pumpDone).catch(() => { });
|
|
1198
|
+
}
|
|
1199
|
+
finally {
|
|
1200
|
+
this.finishQueryBudget();
|
|
1201
|
+
if (this.scratchDirectory)
|
|
1202
|
+
rmSync(this.scratchDirectory, { recursive: true, force: true });
|
|
1203
|
+
}
|
|
1000
1204
|
}
|
|
1001
1205
|
failSessionIdentity(error) {
|
|
1002
1206
|
this.rejectSessionIdentity(error);
|