@yanlinglabs/winter-conformance 0.0.2

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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -0
  3. package/dist/bun-required.d.ts +54 -0
  4. package/dist/goldens.d.ts +11 -0
  5. package/dist/index-v0zvhx0z.js +1576 -0
  6. package/dist/index-z5gwvsfh.js +49 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/index.js +49 -0
  9. package/dist/official/capture.d.ts +1 -0
  10. package/dist/official/fetch.d.ts +40 -0
  11. package/dist/official/index.d.ts +3 -0
  12. package/dist/official/index.js +27 -0
  13. package/dist/trace.d.ts +9 -0
  14. package/dist/trace.js +8 -0
  15. package/goldens/advertised-set-round.trace.json +81 -0
  16. package/goldens/background-task-round.trace.json +210 -0
  17. package/goldens/bash-background-round.trace.json +148 -0
  18. package/goldens/canusetool-approved-round.trace.json +119 -0
  19. package/goldens/compaction-auto-round.trace.json +219 -0
  20. package/goldens/compaction-manual-round.trace.json +232 -0
  21. package/goldens/denied-tool-round.trace.json +141 -0
  22. package/goldens/hook-denied-round.trace.json +169 -0
  23. package/goldens/hooked-tool-round.trace.json +147 -0
  24. package/goldens/interrupt.trace.json +129 -0
  25. package/goldens/mcp-tool-round.trace.json +131 -0
  26. package/goldens/messaging-facet-round.trace.json +278 -0
  27. package/goldens/mode-switch-mid-session.trace.json +200 -0
  28. package/goldens/multi-turn.trace.json +110 -0
  29. package/goldens/p6-anthropic-fake.trace.json +147 -0
  30. package/goldens/p6-gemini-fake.trace.json +132 -0
  31. package/goldens/p6-openai-chat-fake.trace.json +133 -0
  32. package/goldens/p6-openai-responses-fake.trace.json +145 -0
  33. package/goldens/p6-resolution-failure.trace.json +68 -0
  34. package/goldens/plain-query.trace.json +82 -0
  35. package/goldens/resume.trace.json +162 -0
  36. package/goldens/sendmessage-child-round.trace.json +158 -0
  37. package/goldens/skill-invocation-round.trace.json +123 -0
  38. package/goldens/structured-exhaustion-round.trace.json +182 -0
  39. package/goldens/structured-output-round.trace.json +106 -0
  40. package/goldens/subagent-permission-round.trace.json +174 -0
  41. package/goldens/subagent-spawn-round.trace.json +120 -0
  42. package/goldens/tool-round.trace.json +119 -0
  43. package/goldens/toolsearch-select-round.trace.json +185 -0
  44. package/package.json +50 -0
@@ -0,0 +1,1576 @@
1
+ import {
2
+ normalizeTrace2
3
+ } from "./index-z5gwvsfh.js";
4
+
5
+ // src/official/fetch.ts
6
+ import { createHash } from "node:crypto";
7
+
8
+ // src/bun-required.ts
9
+ function brandedInstanceOf(brand) {
10
+ return (candidate) => typeof candidate === "object" && candidate !== null && (brand in candidate);
11
+ }
12
+ var BUN_REQUIRED_BRAND = Symbol.for("@yanlinglabs/winter-conformance:BunRequiredError");
13
+
14
+ class BunRequiredError2 extends Error {
15
+ name = "BunRequiredError";
16
+ [BUN_REQUIRED_BRAND] = true;
17
+ static [Symbol.hasInstance] = brandedInstanceOf(BUN_REQUIRED_BRAND);
18
+ functionName;
19
+ bunApi;
20
+ constructor(functionName, bunApi, detail) {
21
+ super(`${functionName}() requires the Bun runtime: it uses ${bunApi}, which has no Node equivalent this package implements. ${detail}`);
22
+ this.functionName = functionName;
23
+ this.bunApi = bunApi;
24
+ }
25
+ }
26
+ function hasBunRuntime2() {
27
+ return typeof globalThis.Bun !== "undefined";
28
+ }
29
+ function requireBunRuntime2(functionName, bunApi, detail) {
30
+ if (!hasBunRuntime2())
31
+ throw new BunRequiredError2(functionName, bunApi, detail);
32
+ }
33
+
34
+ // src/official/fetch.ts
35
+ import { mkdirSync, mkdtempSync, writeFileSync, readFileSync } from "node:fs";
36
+ import { tmpdir } from "node:os";
37
+ import { join } from "node:path";
38
+ var CHECKSUM_MISMATCH_BRAND = Symbol.for("@yanlinglabs/winter-conformance:ChecksumMismatchError");
39
+ var OFFICIAL_COMPAT_UNAVAILABLE_BRAND = Symbol.for("@yanlinglabs/winter-conformance:OfficialCompatUnavailableError");
40
+
41
+ class ChecksumMismatchError2 extends Error {
42
+ [CHECKSUM_MISMATCH_BRAND] = true;
43
+ static [Symbol.hasInstance] = brandedInstanceOf(CHECKSUM_MISMATCH_BRAND);
44
+ constructor(message) {
45
+ super(message);
46
+ this.name = "ChecksumMismatchError";
47
+ }
48
+ }
49
+
50
+ class OfficialCompatUnavailableError2 extends Error {
51
+ [OFFICIAL_COMPAT_UNAVAILABLE_BRAND] = true;
52
+ static [Symbol.hasInstance] = brandedInstanceOf(OFFICIAL_COMPAT_UNAVAILABLE_BRAND);
53
+ constructor(message) {
54
+ super(message);
55
+ this.name = "OfficialCompatUnavailableError";
56
+ }
57
+ }
58
+ var CHECKSUMS_URL = new URL("../../compat/anthropic/0.3.250/checksums.json", import.meta.url);
59
+ var cachedChecksums;
60
+ function getChecksums2() {
61
+ if (cachedChecksums !== undefined)
62
+ return cachedChecksums;
63
+ let raw;
64
+ try {
65
+ raw = readFileSync(CHECKSUMS_URL, "utf8");
66
+ } catch (e) {
67
+ if (e?.code === "ENOENT") {
68
+ throw new OfficialCompatUnavailableError2("official upstream fetch needs the repository checkout; the published package does not ship compat/");
69
+ }
70
+ throw e;
71
+ }
72
+ cachedChecksums = JSON.parse(raw);
73
+ return cachedChecksums;
74
+ }
75
+ function verifyDigest2(bytes, expected) {
76
+ const actual = createHash("sha256").update(bytes).digest("hex");
77
+ if (actual !== expected)
78
+ throw new ChecksumMismatchError2(`expected ${expected}, got ${actual}`);
79
+ }
80
+ function verifySha512Integrity2(bytes, expectedIntegrity) {
81
+ const prefix = "sha512-";
82
+ if (!expectedIntegrity.startsWith(prefix)) {
83
+ throw new Error(`unsupported integrity format (expected a "${prefix}" SRI string): ${expectedIntegrity}`);
84
+ }
85
+ const expectedBase64 = expectedIntegrity.slice(prefix.length);
86
+ const actualBase64 = createHash("sha512").update(bytes).digest("base64");
87
+ if (actualBase64 !== expectedBase64) {
88
+ throw new ChecksumMismatchError2(`expected ${expectedIntegrity}, got ${prefix}${actualBase64}`);
89
+ }
90
+ }
91
+ function resolveCacheDir2(cacheDir) {
92
+ if (cacheDir !== undefined) {
93
+ mkdirSync(cacheDir, { recursive: true });
94
+ return { dir: cacheDir, ownedDir: false };
95
+ }
96
+ return { dir: mkdtempSync(join(tmpdir(), "winter-upstream-")), ownedDir: true };
97
+ }
98
+ async function fetchAndVerifyUpstream2(opts = {}) {
99
+ const checksums = getChecksums2();
100
+ const res = await fetch(checksums.tarballUrl);
101
+ if (!res.ok)
102
+ throw new Error(`upstream fetch failed: ${res.status} ${res.statusText}`);
103
+ const bytes = new Uint8Array(await res.arrayBuffer());
104
+ verifyDigest2(bytes, checksums.wrapperTarballSha256);
105
+ verifySha512Integrity2(bytes, checksums.wrapperTarballIntegrity);
106
+ const { dir, ownedDir } = resolveCacheDir2(opts.cacheDir);
107
+ const tarballPath = join(dir, "claude-agent-sdk-0.3.250.tgz");
108
+ writeFileSync(tarballPath, bytes);
109
+ return { tarballPath, sha256: checksums.wrapperTarballSha256, ownedDir };
110
+ }
111
+ // src/official/capture.ts
112
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync, readdirSync, statSync } from "node:fs";
113
+ import { createHash as createHash2 } from "node:crypto";
114
+ import { tmpdir as tmpdir2 } from "node:os";
115
+ import { dirname, join as join2 } from "node:path";
116
+ var CANNED_TEXT_RESPONSE = {
117
+ id: "msg_capture_canned_01",
118
+ type: "message",
119
+ role: "assistant",
120
+ model: "claude-sonnet-4-5-20250929",
121
+ content: [{ type: "text", text: "echo: hi" }],
122
+ stop_reason: "end_turn",
123
+ stop_sequence: null,
124
+ usage: { input_tokens: 10, output_tokens: 5 }
125
+ };
126
+ function cannedToolUseResponse(readTargetPath) {
127
+ return {
128
+ id: "msg_capture_canned_tooluse_01",
129
+ type: "message",
130
+ role: "assistant",
131
+ model: "claude-sonnet-4-5-20250929",
132
+ content: [{ type: "tool_use", id: "toolu_capture_01", name: "Read", input: { file_path: readTargetPath } }],
133
+ stop_reason: "tool_use",
134
+ stop_sequence: null,
135
+ usage: { input_tokens: 10, output_tokens: 5 }
136
+ };
137
+ }
138
+ var CANNED_TEXT_AFTER_TOOL_RESPONSE = {
139
+ id: "msg_capture_canned_02",
140
+ type: "message",
141
+ role: "assistant",
142
+ model: "claude-sonnet-4-5-20250929",
143
+ content: [{ type: "text", text: "capture: tool round complete" }],
144
+ stop_reason: "end_turn",
145
+ stop_sequence: null,
146
+ usage: { input_tokens: 12, output_tokens: 6 }
147
+ };
148
+ function requestAlreadySawToolResult(body) {
149
+ try {
150
+ const messages = body.messages;
151
+ if (!Array.isArray(messages))
152
+ return false;
153
+ for (const m of messages) {
154
+ const content = m.content;
155
+ if (Array.isArray(content) && content.some((block) => block.type === "tool_result"))
156
+ return true;
157
+ }
158
+ return false;
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+ async function installOfficialSdk(cleanups) {
164
+ const { tarballPath, ownedDir } = await fetchAndVerifyUpstream2();
165
+ if (ownedDir)
166
+ cleanups.push(() => rmSync(dirname(tarballPath), { recursive: true, force: true }));
167
+ const npmPrefix = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-"));
168
+ cleanups.push(() => rmSync(npmPrefix, { recursive: true, force: true }));
169
+ const install = Bun.spawn(["npm", "install", "--no-save", "--ignore-scripts", "--prefix", npmPrefix, tarballPath], { stdout: "pipe", stderr: "pipe" });
170
+ const installOut = await new Response(install.stdout).text() + await new Response(install.stderr).text();
171
+ if (await install.exited !== 0) {
172
+ throw new Error(`npm install --prefix ${npmPrefix} failed:
173
+ ${installOut}`);
174
+ }
175
+ const officialPkgDir = join2(npmPrefix, "node_modules", "@anthropic-ai", "claude-agent-sdk");
176
+ const officialPkgJson = JSON.parse(readFileSync2(join2(officialPkgDir, "package.json"), "utf8"));
177
+ if (!officialPkgJson.main)
178
+ throw new Error('installed @anthropic-ai/claude-agent-sdk package.json has no "main" field');
179
+ const entryPath = join2(officialPkgDir, officialPkgJson.main);
180
+ return await import(entryPath);
181
+ }
182
+ async function runPlainQueryCapture(officialSdk) {
183
+ const cleanups = [];
184
+ let server;
185
+ try {
186
+ const claudeConfigDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-config-"));
187
+ cleanups.push(() => rmSync(claudeConfigDir, { recursive: true, force: true }));
188
+ const homeDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-home-"));
189
+ cleanups.push(() => rmSync(homeDir, { recursive: true, force: true }));
190
+ const fixtureCwd = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-cwd-"));
191
+ cleanups.push(() => rmSync(fixtureCwd, { recursive: true, force: true }));
192
+ let requestCount = 0;
193
+ server = Bun.serve({
194
+ port: 0,
195
+ hostname: "127.0.0.1",
196
+ fetch(req) {
197
+ requestCount++;
198
+ const url = new URL(req.url);
199
+ console.error(`[loopback A] #${requestCount} ${req.method} ${url.pathname}${url.search} host=${req.headers.get("host")}`);
200
+ return new Response(JSON.stringify(CANNED_TEXT_RESPONSE), { status: 200, headers: { "content-type": "application/json" } });
201
+ }
202
+ });
203
+ console.error(`
204
+ === Scenario A: plain query ===`);
205
+ console.error(`[capture A] loopback listening on ${server.url.href} (the ONLY endpoint the official runtime is given)`);
206
+ console.error(`[capture A] CLAUDE_CONFIG_DIR=${claudeConfigDir} HOME=${homeDir} (both fresh mkdtemp — never the real ~/.claude or the real user home)`);
207
+ const entries = [];
208
+ let thrown;
209
+ try {
210
+ const q = officialSdk.query({
211
+ prompt: "hi",
212
+ options: {
213
+ model: "sonnet",
214
+ cwd: fixtureCwd,
215
+ settingSources: [],
216
+ env: {
217
+ ANTHROPIC_BASE_URL: server.url.href.replace(/\/$/, ""),
218
+ ANTHROPIC_API_KEY: "test",
219
+ CLAUDE_CONFIG_DIR: claudeConfigDir,
220
+ HOME: homeDir
221
+ }
222
+ }
223
+ });
224
+ for await (const msg of q) {
225
+ entries.push({ sequence: entries.length, direction: "runtime-to-host", kind: msg.type === "system" ? `system/${msg.subtype}` : msg.type, payload: msg });
226
+ }
227
+ } catch (e) {
228
+ thrown = e;
229
+ }
230
+ console.error(`[capture A] official runtime made ${requestCount} request(s) to the loopback; ${entries.length} message(s) yielded`);
231
+ if (thrown)
232
+ console.error(`[capture A] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
233
+ console.log(`
234
+ --- Scenario A normalized trace ---`);
235
+ console.log(JSON.stringify(normalizeTrace2(entries), null, 2));
236
+ } finally {
237
+ server?.stop(true);
238
+ for (const cleanup of cleanups)
239
+ cleanup();
240
+ }
241
+ }
242
+ async function runInitToolsCapture(officialSdk) {
243
+ const cleanups = [];
244
+ let server;
245
+ try {
246
+ const claudeConfigDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-config-c-"));
247
+ cleanups.push(() => rmSync(claudeConfigDir, { recursive: true, force: true }));
248
+ const homeDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-home-c-"));
249
+ cleanups.push(() => rmSync(homeDir, { recursive: true, force: true }));
250
+ const fixtureCwd = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-cwd-c-"));
251
+ cleanups.push(() => rmSync(fixtureCwd, { recursive: true, force: true }));
252
+ let requestCount = 0;
253
+ server = Bun.serve({
254
+ port: 0,
255
+ hostname: "127.0.0.1",
256
+ fetch(req) {
257
+ requestCount++;
258
+ const url = new URL(req.url);
259
+ console.error(`[loopback C] #${requestCount} ${req.method} ${url.pathname}${url.search} host=${req.headers.get("host")}`);
260
+ return new Response(JSON.stringify(CANNED_TEXT_RESPONSE), { status: 200, headers: { "content-type": "application/json" } });
261
+ }
262
+ });
263
+ console.error(`
264
+ === Scenario C: official default system/init.tools ===`);
265
+ console.error(`[capture C] loopback listening on ${server.url.href} (the ONLY endpoint the official runtime is given)`);
266
+ console.error(`[capture C] CLAUDE_CONFIG_DIR=${claudeConfigDir} HOME=${homeDir} (both fresh mkdtemp — never the real ~/.claude or the real user home)`);
267
+ const entries = [];
268
+ let thrown;
269
+ try {
270
+ const q = officialSdk.query({
271
+ prompt: "hi",
272
+ options: {
273
+ model: "sonnet",
274
+ cwd: fixtureCwd,
275
+ settingSources: [],
276
+ env: {
277
+ ANTHROPIC_BASE_URL: server.url.href.replace(/\/$/, ""),
278
+ ANTHROPIC_API_KEY: "test",
279
+ CLAUDE_CONFIG_DIR: claudeConfigDir,
280
+ HOME: homeDir
281
+ }
282
+ }
283
+ });
284
+ for await (const msg of q) {
285
+ entries.push({ sequence: entries.length, direction: "runtime-to-host", kind: msg.type === "system" ? `system/${msg.subtype}` : msg.type, payload: msg });
286
+ }
287
+ } catch (e) {
288
+ thrown = e;
289
+ }
290
+ console.error(`[capture C] official runtime made ${requestCount} request(s) to the loopback; ${entries.length} message(s) yielded`);
291
+ if (thrown)
292
+ console.error(`[capture C] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
293
+ const normalized = normalizeTrace2(entries);
294
+ const initEntry = normalized.find((e) => e.kind === "system/init");
295
+ const officialTools = initEntry ? initEntry.payload.tools : undefined;
296
+ console.log(`
297
+ --- Scenario C normalized trace ---`);
298
+ console.log(JSON.stringify(normalized, null, 2));
299
+ console.log(`
300
+ --- Scenario C: the official runtime's own default system/init.tools list (compare by eye against Winter's own default buildAdvertisedSet output) ---`);
301
+ console.log(JSON.stringify(officialTools ?? "(no system/init message observed)", null, 2));
302
+ if (Array.isArray(officialTools)) {
303
+ console.error(`[capture C] official default advertised set has ${officialTools.length} name(s) -- see the printed list above for the exact names (WS-06 §6 obligation 1/4: report-only, never asserted as a count anywhere in the committed test suite)`);
304
+ }
305
+ } finally {
306
+ server?.stop(true);
307
+ for (const cleanup of cleanups)
308
+ cleanup();
309
+ }
310
+ }
311
+ async function runPermissionsAndHooksCapture(officialSdk) {
312
+ const cleanups = [];
313
+ let server;
314
+ try {
315
+ const claudeConfigDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-config-b-"));
316
+ cleanups.push(() => rmSync(claudeConfigDir, { recursive: true, force: true }));
317
+ const homeDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-home-b-"));
318
+ cleanups.push(() => rmSync(homeDir, { recursive: true, force: true }));
319
+ const fixtureCwd = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-cwd-b-"));
320
+ cleanups.push(() => rmSync(fixtureCwd, { recursive: true, force: true }));
321
+ const readTargetDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-readtarget-"));
322
+ cleanups.push(() => rmSync(readTargetDir, { recursive: true, force: true }));
323
+ const readTargetPath = join2(readTargetDir, "capture-dummy.txt");
324
+ writeFileSync2(readTargetPath, `winter capture fixture -- not real data
325
+ `);
326
+ let requestCount = 0;
327
+ server = Bun.serve({
328
+ port: 0,
329
+ hostname: "127.0.0.1",
330
+ async fetch(req) {
331
+ requestCount++;
332
+ const url = new URL(req.url);
333
+ let sawToolResult = false;
334
+ try {
335
+ const bodyText = await req.clone().text();
336
+ sawToolResult = bodyText.length > 0 && requestAlreadySawToolResult(JSON.parse(bodyText));
337
+ } catch {}
338
+ console.error(`[loopback B] #${requestCount} ${req.method} ${url.pathname}${url.search} host=${req.headers.get("host")} sawToolResult=${sawToolResult}`);
339
+ const body = sawToolResult ? CANNED_TEXT_AFTER_TOOL_RESPONSE : cannedToolUseResponse(readTargetPath);
340
+ return new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json" } });
341
+ }
342
+ });
343
+ console.error(`
344
+ === Scenario B: canUseTool + includeHookEvents + PreToolUse (observer) + SessionEnd ===`);
345
+ console.error(`[capture B] loopback listening on ${server.url.href} (the ONLY endpoint the official runtime is given)`);
346
+ console.error(`[capture B] CLAUDE_CONFIG_DIR=${claudeConfigDir} HOME=${homeDir} (both fresh mkdtemp); read target=${readTargetPath} (fresh mkdtemp, dummy content, never read for real)`);
347
+ let canUseToolCallCount = 0;
348
+ let canUseToolFieldSet = [];
349
+ let canUseToolPrintableSnapshot = {};
350
+ let sessionEndHookRan = false;
351
+ const preToolUseHookInvocations = [];
352
+ const entries = [];
353
+ let thrown;
354
+ try {
355
+ const q = officialSdk.query({
356
+ prompt: "please read the capture fixture file",
357
+ options: {
358
+ model: "sonnet",
359
+ cwd: fixtureCwd,
360
+ settingSources: [],
361
+ includeHookEvents: true,
362
+ env: {
363
+ ANTHROPIC_BASE_URL: server.url.href.replace(/\/$/, ""),
364
+ ANTHROPIC_API_KEY: "test",
365
+ CLAUDE_CONFIG_DIR: claudeConfigDir,
366
+ HOME: homeDir
367
+ },
368
+ canUseTool: async (toolName, input, opts) => {
369
+ canUseToolCallCount++;
370
+ canUseToolFieldSet = Object.keys(opts);
371
+ canUseToolPrintableSnapshot = {
372
+ toolName,
373
+ input,
374
+ ...Object.fromEntries(Object.entries(opts).map(([k, v]) => [k, k === "signal" ? `<${typeof v}>` : v]))
375
+ };
376
+ return { behavior: "deny", message: "capture: denied by canUseTool (Task 13 official capture)" };
377
+ },
378
+ hooks: {
379
+ PreToolUse: [
380
+ {
381
+ hooks: [
382
+ async (input) => {
383
+ preToolUseHookInvocations.push(input);
384
+ return {};
385
+ }
386
+ ]
387
+ }
388
+ ],
389
+ SessionEnd: [
390
+ {
391
+ hooks: [
392
+ async () => {
393
+ sessionEndHookRan = true;
394
+ return {};
395
+ }
396
+ ]
397
+ }
398
+ ]
399
+ }
400
+ }
401
+ });
402
+ for await (const msg of q) {
403
+ entries.push({ sequence: entries.length, direction: "runtime-to-host", kind: msg.type === "system" ? `system/${msg.subtype}` : msg.type, payload: msg });
404
+ }
405
+ } catch (e) {
406
+ thrown = e;
407
+ }
408
+ console.error(`[capture B] official runtime made ${requestCount} request(s) to the loopback; ${entries.length} message(s) yielded`);
409
+ if (thrown)
410
+ console.error(`[capture B] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
411
+ console.error(`[capture B] canUseTool invoked ${canUseToolCallCount} time(s); PreToolUse hook invoked ${preToolUseHookInvocations.length} time(s); SessionEnd hook body ran: ${sessionEndHookRan}`);
412
+ const normalized = normalizeTrace2(entries);
413
+ const lifecycleKinds = normalized.map((e) => e.kind).filter((k) => k.startsWith("system/hook_"));
414
+ console.error(`[capture B] lifecycle-family messages observed on the stream: ${lifecycleKinds.length ? lifecycleKinds.join(", ") : "(none)"}`);
415
+ console.log(`
416
+ --- Scenario B normalized trace ---`);
417
+ console.log(JSON.stringify(normalized, null, 2));
418
+ console.log(`
419
+ --- Scenario B: canUseTool's received field set (WS-07 §7.1 comparison) ---`);
420
+ console.log(JSON.stringify(canUseToolFieldSet, null, 2));
421
+ console.log(`
422
+ --- Scenario B: canUseTool's received values (signal recorded by type only) ---`);
423
+ console.log(JSON.stringify(canUseToolPrintableSnapshot, null, 2));
424
+ console.log(`
425
+ --- Scenario B: Carry 3 (WS-17 capture-note) signals ---`);
426
+ console.log(JSON.stringify({ sessionEndHookBodyRan: sessionEndHookRan, lifecycleMessagesObserved: lifecycleKinds }, null, 2));
427
+ } finally {
428
+ server?.stop(true);
429
+ for (const cleanup of cleanups)
430
+ cleanup();
431
+ }
432
+ }
433
+ function schemaShapeOnly(schema, insidePropertiesMap = false) {
434
+ if (Array.isArray(schema))
435
+ return schema.map((v) => schemaShapeOnly(v));
436
+ if (schema === null || typeof schema !== "object")
437
+ return schema;
438
+ const out = {};
439
+ for (const [k, v] of Object.entries(schema)) {
440
+ if (!insidePropertiesMap && (k === "description" || k === "title" || k === "$comment"))
441
+ continue;
442
+ out[k] = schemaShapeOnly(v, k === "properties");
443
+ }
444
+ return out;
445
+ }
446
+ function capturedToolsFrom(body) {
447
+ const tools = body.tools;
448
+ if (!Array.isArray(tools))
449
+ return;
450
+ const names = tools.map((t) => String(t.name));
451
+ const agent = tools.find((t) => {
452
+ const n = t.name;
453
+ return n === "Agent" || n === "Task";
454
+ });
455
+ return { names, agentSchema: agent === undefined ? undefined : schemaShapeOnly(agent.input_schema) };
456
+ }
457
+ async function runAdvertisedSchemaCapture(officialSdk) {
458
+ const cleanups = [];
459
+ let server;
460
+ try {
461
+ const claudeConfigDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-config-d-"));
462
+ cleanups.push(() => rmSync(claudeConfigDir, { recursive: true, force: true }));
463
+ const homeDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-home-d-"));
464
+ cleanups.push(() => rmSync(homeDir, { recursive: true, force: true }));
465
+ const fixtureCwd = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-cwd-d-"));
466
+ cleanups.push(() => rmSync(fixtureCwd, { recursive: true, force: true }));
467
+ let captured;
468
+ let requestCount = 0;
469
+ server = Bun.serve({
470
+ port: 0,
471
+ hostname: "127.0.0.1",
472
+ async fetch(req) {
473
+ requestCount++;
474
+ const url = new URL(req.url);
475
+ console.error(`[loopback D] #${requestCount} ${req.method} ${url.pathname}${url.search}`);
476
+ try {
477
+ const body = await req.json();
478
+ if (captured === undefined)
479
+ captured = capturedToolsFrom(body);
480
+ } catch {}
481
+ return new Response(JSON.stringify(CANNED_TEXT_RESPONSE), { status: 200, headers: { "content-type": "application/json" } });
482
+ }
483
+ });
484
+ console.error(`
485
+ === Scenario D: the live request's advertised tools[] (WS-09 §8.5 ground truth; WS-10 §17 OQ1) ===`);
486
+ console.error(`[capture D] loopback ${server.url.href}; CLAUDE_CONFIG_DIR=${claudeConfigDir} HOME=${homeDir} (both fresh mkdtemp)`);
487
+ let thrown;
488
+ try {
489
+ const q = officialSdk.query({
490
+ prompt: "hi",
491
+ options: {
492
+ model: "sonnet",
493
+ cwd: fixtureCwd,
494
+ settingSources: [],
495
+ env: {
496
+ ANTHROPIC_BASE_URL: server.url.href.replace(/\/$/, ""),
497
+ ANTHROPIC_API_KEY: "test",
498
+ CLAUDE_CONFIG_DIR: claudeConfigDir,
499
+ HOME: homeDir
500
+ }
501
+ }
502
+ });
503
+ for await (const _msg of q) {}
504
+ } catch (e) {
505
+ thrown = e;
506
+ }
507
+ if (thrown)
508
+ console.error(`[capture D] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
509
+ console.log(`
510
+ --- Scenario D: every tool NAME in the live request's tools[] ---`);
511
+ console.log(JSON.stringify(captured?.names ?? "(no completions request body with a tools[] array was observed)", null, 2));
512
+ console.log(`
513
+ --- Scenario D: the Agent/Task tool's own input_schema (SHAPE ONLY -- descriptions stripped) ---`);
514
+ console.log(JSON.stringify(captured?.agentSchema ?? "(no Agent/Task tool present in the advertised set)", null, 2));
515
+ const agentProps = Object.keys(captured?.agentSchema?.properties ?? {});
516
+ console.log(`
517
+ --- Scenario D verdicts ---`);
518
+ console.log(JSON.stringify({
519
+ "WS-10 §17 OQ1 -- is `name` advertised on Agent in a DEFAULT session?": agentProps.length === 0 ? "(no Agent schema captured)" : agentProps.includes("name"),
520
+ "Agent input_schema property names": agentProps,
521
+ "SendMessage advertised?": captured?.names.includes("SendMessage") ?? "(unknown)",
522
+ "ListAgents advertised?": captured?.names.includes("ListAgents") ?? "(unknown)",
523
+ "ToolSearch advertised?": captured?.names.includes("ToolSearch") ?? "(unknown)",
524
+ "WaitForMcpServers advertised?": captured?.names.includes("WaitForMcpServers") ?? "(unknown)",
525
+ "MCP bridge tools advertised?": (captured?.names ?? []).filter((n) => n.startsWith("ListMcpResources") || n.startsWith("ReadMcpResource") || n === "RefreshMcpTools")
526
+ }, null, 2));
527
+ } finally {
528
+ server?.stop(true);
529
+ for (const cleanup of cleanups)
530
+ cleanup();
531
+ }
532
+ }
533
+ async function runMcpOutputCapCapture(officialSdk, maxOutputTokens) {
534
+ const label = maxOutputTokens === undefined ? "default" : `MAX_MCP_OUTPUT_TOKENS=${maxOutputTokens}`;
535
+ const cleanups = [];
536
+ let server;
537
+ try {
538
+ const claudeConfigDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-config-e-"));
539
+ cleanups.push(() => rmSync(claudeConfigDir, { recursive: true, force: true }));
540
+ const homeDir = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-home-e-"));
541
+ cleanups.push(() => rmSync(homeDir, { recursive: true, force: true }));
542
+ const fixtureCwd = mkdtempSync2(join2(tmpdir2(), "winter-official-capture-cwd-e-"));
543
+ cleanups.push(() => rmSync(fixtureCwd, { recursive: true, force: true }));
544
+ const HUGE = "winterfiller ".repeat(30000);
545
+ const sdkAny = officialSdk;
546
+ if (typeof sdkAny.createSdkMcpServer !== "function" || typeof sdkAny.tool !== "function") {
547
+ console.log(`
548
+ --- Scenario E (${label}): SKIPPED -- the installed package does not export createSdkMcpServer/tool ---`);
549
+ return;
550
+ }
551
+ const bigTool = sdkAny.tool("bigoutput", "returns a very large payload", {}, async () => ({
552
+ content: [{ type: "text", text: HUGE }]
553
+ }));
554
+ const mcpServer = sdkAny.createSdkMcpServer({ name: "capfixture", version: "1.0.0", tools: [bigTool] });
555
+ let toolResultTexts = [];
556
+ let requestCount = 0;
557
+ server = Bun.serve({
558
+ port: 0,
559
+ hostname: "127.0.0.1",
560
+ async fetch(req) {
561
+ requestCount++;
562
+ let body;
563
+ try {
564
+ body = await req.json();
565
+ } catch {
566
+ return new Response(JSON.stringify(CANNED_TEXT_RESPONSE), { status: 200, headers: { "content-type": "application/json" } });
567
+ }
568
+ const messages = body.messages ?? [];
569
+ for (const m of messages) {
570
+ const content = m.content;
571
+ if (!Array.isArray(content))
572
+ continue;
573
+ for (const block of content) {
574
+ if (block.type !== "tool_result")
575
+ continue;
576
+ const c = block.content;
577
+ const text = typeof c === "string" ? c : Array.isArray(c) ? c.map((b) => String(b.text ?? "")).join("") : "";
578
+ if (text.length > 0)
579
+ toolResultTexts.push(text);
580
+ }
581
+ }
582
+ if (requestAlreadySawToolResult(body)) {
583
+ return new Response(JSON.stringify(CANNED_TEXT_AFTER_TOOL_RESPONSE), { status: 200, headers: { "content-type": "application/json" } });
584
+ }
585
+ return new Response(JSON.stringify({
586
+ id: "msg_capture_mcp_01",
587
+ type: "message",
588
+ role: "assistant",
589
+ model: "claude-sonnet-4-5-20250929",
590
+ content: [{ type: "tool_use", id: "toolu_capture_mcp_01", name: "mcp__capfixture__bigoutput", input: {} }],
591
+ stop_reason: "tool_use",
592
+ stop_sequence: null,
593
+ usage: { input_tokens: 10, output_tokens: 5 }
594
+ }), { status: 200, headers: { "content-type": "application/json" } });
595
+ }
596
+ });
597
+ console.error(`
598
+ === Scenario E (${label}): MAX_MCP_OUTPUT_TOKENS default + truncation (WS-09 §12 OQ1) ===`);
599
+ console.error(`[capture E/${label}] loopback ${server.url.href}; payload sent by the fixture server: ${HUGE.length} chars`);
600
+ let thrown;
601
+ try {
602
+ const q = officialSdk.query({
603
+ prompt: "call the big tool",
604
+ options: {
605
+ model: "sonnet",
606
+ cwd: fixtureCwd,
607
+ settingSources: [],
608
+ allowedTools: ["mcp__capfixture__bigoutput"],
609
+ permissionMode: "bypassPermissions",
610
+ mcpServers: { capfixture: mcpServer },
611
+ env: {
612
+ ANTHROPIC_BASE_URL: server.url.href.replace(/\/$/, ""),
613
+ ANTHROPIC_API_KEY: "test",
614
+ CLAUDE_CONFIG_DIR: claudeConfigDir,
615
+ HOME: homeDir,
616
+ ...maxOutputTokens !== undefined ? { MAX_MCP_OUTPUT_TOKENS: maxOutputTokens } : {}
617
+ }
618
+ }
619
+ });
620
+ for await (const _msg of q) {}
621
+ } catch (e) {
622
+ thrown = e;
623
+ }
624
+ if (thrown)
625
+ console.error(`[capture E/${label}] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
626
+ const longest = toolResultTexts.reduce((a, b) => b.length > a.length ? b : a, "");
627
+ console.log(`
628
+ --- Scenario E (${label}) ---`);
629
+ console.log(JSON.stringify({
630
+ "payload chars the fixture server returned": HUGE.length,
631
+ "tool_result blocks observed": toolResultTexts.length,
632
+ "longest tool_result chars the runtime forwarded": longest.length,
633
+ truncated: longest.length > 0 && longest.length < HUGE.length,
634
+ "head (first 300 chars)": longest.slice(0, 300),
635
+ "tail (last 300 chars)": longest.slice(-300)
636
+ }, null, 2));
637
+ } finally {
638
+ server?.stop(true);
639
+ for (const cleanup of cleanups)
640
+ cleanup();
641
+ }
642
+ }
643
+ function makeScenarioDirs(tag, cleanups) {
644
+ const claudeConfigDir = mkdtempSync2(join2(tmpdir2(), `winter-official-capture-config-${tag}-`));
645
+ cleanups.push(() => rmSync(claudeConfigDir, { recursive: true, force: true }));
646
+ const homeDir = mkdtempSync2(join2(tmpdir2(), `winter-official-capture-home-${tag}-`));
647
+ cleanups.push(() => rmSync(homeDir, { recursive: true, force: true }));
648
+ const fixtureCwd = mkdtempSync2(join2(tmpdir2(), `winter-official-capture-cwd-${tag}-`));
649
+ cleanups.push(() => rmSync(fixtureCwd, { recursive: true, force: true }));
650
+ return { claudeConfigDir, homeDir, fixtureCwd };
651
+ }
652
+ function hermeticEnv(dirs, baseUrl, extra = {}) {
653
+ return {
654
+ ANTHROPIC_BASE_URL: baseUrl.replace(/\/$/, ""),
655
+ ANTHROPIC_API_KEY: "test",
656
+ CLAUDE_CONFIG_DIR: dirs.claudeConfigDir,
657
+ HOME: dirs.homeDir,
658
+ ...extra
659
+ };
660
+ }
661
+ async function drainWithDeadline(q, entries, label, deadlineMs, ac) {
662
+ let deadlineHit = false;
663
+ const timer = setTimeout(() => {
664
+ deadlineHit = true;
665
+ console.error(`[${label}] WALL-CLOCK DEADLINE ${deadlineMs}ms reached — aborting the run`);
666
+ ac?.abort();
667
+ }, deadlineMs);
668
+ let thrown;
669
+ try {
670
+ for await (const msg of q) {
671
+ entries.push({
672
+ sequence: entries.length,
673
+ direction: "runtime-to-host",
674
+ kind: msg.type === "system" ? `system/${msg.subtype}` : msg.type,
675
+ payload: msg
676
+ });
677
+ }
678
+ } catch (e) {
679
+ thrown = e;
680
+ } finally {
681
+ clearTimeout(timer);
682
+ }
683
+ return { thrown, deadlineHit };
684
+ }
685
+ function describeThrown(thrown) {
686
+ if (thrown === undefined)
687
+ return;
688
+ if (thrown instanceof Error) {
689
+ return { class: thrown.constructor.name, name: thrown.name, message: thrown.message };
690
+ }
691
+ return { class: typeof thrown, value: String(thrown) };
692
+ }
693
+ function sseResponse(events) {
694
+ const body = events.map((e) => `event: ${e.event}
695
+ data: ${JSON.stringify(e.data)}
696
+
697
+ `).join("");
698
+ return new Response(body, {
699
+ status: 200,
700
+ headers: { "content-type": "text/event-stream", "cache-control": "no-cache" }
701
+ });
702
+ }
703
+ function jsonResponse(body, status = 200, headers = {}) {
704
+ return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json", ...headers } });
705
+ }
706
+ var CAPTURE_STREAM_SIGNATURE = "WinterCaptureFakeThinkingSignature0123456789ABCDEF";
707
+ function messagesShape(body) {
708
+ const messages = body.messages;
709
+ if (!Array.isArray(messages))
710
+ return "(no messages array)";
711
+ return messages.map((m) => {
712
+ const rec = m;
713
+ const content = rec.content;
714
+ if (typeof content === "string")
715
+ return { role: rec.role, content: `<string:${content.length} chars>` };
716
+ if (!Array.isArray(content))
717
+ return { role: rec.role, content: typeof content };
718
+ return {
719
+ role: rec.role,
720
+ blocks: content.map((b) => {
721
+ const block = b;
722
+ const out = { type: block.type };
723
+ if (block.type === "thinking") {
724
+ out.hasSignature = Object.prototype.hasOwnProperty.call(block, "signature");
725
+ out.signatureIsTheCannedOne = block.signature === CAPTURE_STREAM_SIGNATURE;
726
+ out.thinkingChars = typeof block.thinking === "string" ? block.thinking.length : null;
727
+ }
728
+ if (block.type === "redacted_thinking")
729
+ out.hasData = Object.prototype.hasOwnProperty.call(block, "data");
730
+ if (block.type === "tool_use")
731
+ out.name = block.name;
732
+ if (block.type === "tool_result") {
733
+ out.contentKind = Array.isArray(block.content) ? "blocks" : typeof block.content;
734
+ if (Array.isArray(block.content))
735
+ out.blockTypes = block.content.map((c) => c.type);
736
+ }
737
+ if (block.type === "image")
738
+ out.sourceType = block.source?.type;
739
+ return out;
740
+ })
741
+ };
742
+ });
743
+ }
744
+ function requestEnvelopeShape(body) {
745
+ const rec = body;
746
+ return {
747
+ model: rec.model,
748
+ stream: rec.stream,
749
+ max_tokens: rec.max_tokens,
750
+ thinking: rec.thinking,
751
+ topLevelKeys: Object.keys(rec).sort(),
752
+ toolCount: Array.isArray(rec.tools) ? rec.tools.length : null
753
+ };
754
+ }
755
+ function streamTurn1Events(readTargetPath, withSignature) {
756
+ const model = "claude-sonnet-4-5-20250929";
757
+ const evs = [];
758
+ evs.push({
759
+ event: "message_start",
760
+ data: {
761
+ type: "message_start",
762
+ message: {
763
+ id: "msg_capture_stream_01",
764
+ type: "message",
765
+ role: "assistant",
766
+ model,
767
+ content: [],
768
+ stop_reason: null,
769
+ stop_sequence: null,
770
+ usage: { input_tokens: 11, output_tokens: 1 }
771
+ }
772
+ }
773
+ });
774
+ evs.push({
775
+ event: "content_block_start",
776
+ data: {
777
+ type: "content_block_start",
778
+ index: 0,
779
+ content_block: withSignature ? { type: "thinking", thinking: "", signature: "" } : { type: "thinking", thinking: "" }
780
+ }
781
+ });
782
+ evs.push({ event: "ping", data: { type: "ping" } });
783
+ evs.push({
784
+ event: "content_block_delta",
785
+ data: { type: "content_block_delta", index: 0, delta: { type: "thinking_delta", thinking: "winter capture: weighing two options" } }
786
+ });
787
+ if (withSignature) {
788
+ evs.push({
789
+ event: "content_block_delta",
790
+ data: { type: "content_block_delta", index: 0, delta: { type: "signature_delta", signature: CAPTURE_STREAM_SIGNATURE } }
791
+ });
792
+ }
793
+ evs.push({ event: "content_block_stop", data: { type: "content_block_stop", index: 0 } });
794
+ evs.push({ event: "content_block_start", data: { type: "content_block_start", index: 1, content_block: { type: "text", text: "" } } });
795
+ evs.push({ event: "content_block_delta", data: { type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "reading the " } } });
796
+ evs.push({ event: "content_block_delta", data: { type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "capture fixture" } } });
797
+ evs.push({ event: "content_block_stop", data: { type: "content_block_stop", index: 1 } });
798
+ evs.push({
799
+ event: "content_block_start",
800
+ data: { type: "content_block_start", index: 2, content_block: { type: "tool_use", id: "toolu_capture_stream_01", name: "Read", input: {} } }
801
+ });
802
+ evs.push({
803
+ event: "content_block_delta",
804
+ data: { type: "content_block_delta", index: 2, delta: { type: "input_json_delta", partial_json: '{"file_path": ' } }
805
+ });
806
+ evs.push({
807
+ event: "content_block_delta",
808
+ data: { type: "content_block_delta", index: 2, delta: { type: "input_json_delta", partial_json: `${JSON.stringify(readTargetPath)}}` } }
809
+ });
810
+ evs.push({ event: "content_block_stop", data: { type: "content_block_stop", index: 2 } });
811
+ evs.push({
812
+ event: "message_delta",
813
+ data: { type: "message_delta", delta: { stop_reason: "tool_use", stop_sequence: null }, usage: { output_tokens: 42 } }
814
+ });
815
+ evs.push({ event: "message_stop", data: { type: "message_stop" } });
816
+ return evs;
817
+ }
818
+ function streamTurn2Events() {
819
+ const model = "claude-sonnet-4-5-20250929";
820
+ return [
821
+ {
822
+ event: "message_start",
823
+ data: {
824
+ type: "message_start",
825
+ message: { id: "msg_capture_stream_02", type: "message", role: "assistant", model, content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 20, output_tokens: 1 } }
826
+ }
827
+ },
828
+ { event: "content_block_start", data: { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } } },
829
+ { event: "content_block_delta", data: { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "capture: stream round complete" } } },
830
+ { event: "content_block_stop", data: { type: "content_block_stop", index: 0 } },
831
+ { event: "message_delta", data: { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: 9 } } },
832
+ { event: "message_stop", data: { type: "message_stop" } }
833
+ ];
834
+ }
835
+ async function runStreamEventCapture(officialSdk, withSignature) {
836
+ const label = withSignature ? "signed" : "unsigned";
837
+ const cleanups = [];
838
+ let server;
839
+ try {
840
+ const dirs = makeScenarioDirs(`f-${label}`, cleanups);
841
+ const readTargetPath = join2(dirs.fixtureCwd, "capture-stream-fixture.txt");
842
+ writeFileSync2(readTargetPath, `winter capture fixture -- not real data
843
+ `);
844
+ let requestCount = 0;
845
+ const envelopes = [];
846
+ const messageShapes = [];
847
+ server = Bun.serve({
848
+ port: 0,
849
+ hostname: "127.0.0.1",
850
+ async fetch(req) {
851
+ requestCount++;
852
+ const url = new URL(req.url);
853
+ let body;
854
+ try {
855
+ body = await req.json();
856
+ } catch {
857
+ console.error(`[loopback F/${label}] #${requestCount} ${req.method} ${url.pathname} (non-JSON body)`);
858
+ return jsonResponse(CANNED_TEXT_RESPONSE);
859
+ }
860
+ envelopes.push(requestEnvelopeShape(body));
861
+ messageShapes.push(messagesShape(body));
862
+ const sawToolResult = requestAlreadySawToolResult(body);
863
+ const wantsStream = body.stream === true;
864
+ console.error(`[loopback F/${label}] #${requestCount} ${req.method} ${url.pathname} stream=${wantsStream} sawToolResult=${sawToolResult}`);
865
+ if (!wantsStream)
866
+ return jsonResponse(sawToolResult ? CANNED_TEXT_AFTER_TOOL_RESPONSE : CANNED_TEXT_RESPONSE);
867
+ return sseResponse(sawToolResult ? streamTurn2Events() : streamTurn1Events(readTargetPath, withSignature));
868
+ }
869
+ });
870
+ console.error(`
871
+ === Scenario F (${label}): includePartialMessages over text + thinking + tool_use ===`);
872
+ console.error(`[capture F/${label}] loopback ${server.url.href}; CLAUDE_CONFIG_DIR=${dirs.claudeConfigDir} HOME=${dirs.homeDir} (fresh mkdtemp)`);
873
+ const entries = [];
874
+ const ac = new AbortController;
875
+ const q = officialSdk.query({
876
+ prompt: "please read the capture fixture file",
877
+ options: {
878
+ model: "sonnet",
879
+ cwd: dirs.fixtureCwd,
880
+ settingSources: [],
881
+ includePartialMessages: true,
882
+ thinking: { type: "enabled", budgetTokens: 1024 },
883
+ permissionMode: "bypassPermissions",
884
+ abortController: ac,
885
+ env: hermeticEnv(dirs, server.url.href)
886
+ }
887
+ });
888
+ const { thrown, deadlineHit } = await drainWithDeadline(q, entries, `capture F/${label}`, 120000, ac);
889
+ console.error(`[capture F/${label}] ${requestCount} loopback request(s); ${entries.length} message(s) yielded; deadlineHit=${deadlineHit}`);
890
+ if (thrown)
891
+ console.error(`[capture F/${label}] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
892
+ const normalized = normalizeTrace2(entries);
893
+ const streamEvents = entries.map((e) => e.payload).filter((p) => p.type === "stream_event");
894
+ console.log(`
895
+ --- Scenario F (${label}) normalized trace ---`);
896
+ console.log(JSON.stringify(normalized, null, 2));
897
+ console.log(`
898
+ --- Scenario F (${label}): the raw stream_event list, in order (event.type / delta.type / index) ---`);
899
+ console.log(JSON.stringify(streamEvents.map((p, i) => {
900
+ const ev = p.event;
901
+ const delta = ev?.delta;
902
+ const cb = ev?.content_block;
903
+ return {
904
+ i,
905
+ eventType: ev?.type,
906
+ index: ev?.index,
907
+ deltaType: delta?.type,
908
+ contentBlockType: cb?.type,
909
+ parent_tool_use_id: p.parent_tool_use_id,
910
+ hasUuid: typeof p.uuid === "string",
911
+ hasSessionId: typeof p.session_id === "string",
912
+ ttft_ms: p.ttft_ms,
913
+ user_message_uuid: p.user_message_uuid === undefined ? "(absent)" : "(present)"
914
+ };
915
+ }), null, 2));
916
+ console.log(`
917
+ --- Scenario F (${label}): every stream_event's own top-level key set (union) ---`);
918
+ console.log(JSON.stringify([...new Set(streamEvents.flatMap((p) => Object.keys(p)))].sort(), null, 2));
919
+ console.log(`
920
+ --- Scenario F (${label}): request envelope shapes (item (c) wire spelling of ThinkingConfig) ---`);
921
+ console.log(JSON.stringify(envelopes, null, 2));
922
+ console.log(`
923
+ --- Scenario F (${label}): request messages SHAPE ONLY (item (f) — does the thinking block survive replay?) ---`);
924
+ console.log(JSON.stringify(messageShapes, null, 2));
925
+ console.log(`
926
+ --- Scenario F (${label}) verdicts ---`);
927
+ console.log(JSON.stringify({
928
+ "stream_event frames observed": streamEvents.length,
929
+ "distinct event.type values, in first-seen order": [...new Set(streamEvents.map((p) => p.event?.type))],
930
+ "did a ping reach the consumer?": streamEvents.some((p) => p.event?.type === "ping"),
931
+ "frames carrying user_message_uuid": streamEvents.filter((p) => p.user_message_uuid !== undefined).length,
932
+ "frames carrying ttft_ms": streamEvents.filter((p) => p.ttft_ms !== undefined).length,
933
+ "completed assistant frames": entries.filter((e) => e.payload.type === "assistant").length,
934
+ "query() threw": describeThrown(thrown) ?? false,
935
+ deadlineHit
936
+ }, null, 2));
937
+ } finally {
938
+ server?.stop(true);
939
+ for (const cleanup of cleanups)
940
+ cleanup();
941
+ }
942
+ }
943
+ async function runRetryCapture(officialSdk, policy) {
944
+ const cleanups = [];
945
+ let server;
946
+ const failingPostsBeforeSuccess = 2;
947
+ try {
948
+ const dirs = makeScenarioDirs(`g-${policy}`, cleanups);
949
+ let requestCount = 0;
950
+ let postCount = 0;
951
+ const requestLog = [];
952
+ const t0 = Date.now();
953
+ server = Bun.serve({
954
+ port: 0,
955
+ hostname: "127.0.0.1",
956
+ async fetch(req) {
957
+ requestCount++;
958
+ const n = requestCount;
959
+ let body;
960
+ try {
961
+ body = await req.json();
962
+ } catch {
963
+ body = {};
964
+ }
965
+ const model = body.model;
966
+ requestLog.push({ n, model, atMs: Date.now() - t0 });
967
+ const url = new URL(req.url);
968
+ const isPost = req.method === "POST";
969
+ if (isPost)
970
+ postCount++;
971
+ console.error(`[loopback G/${policy}] #${n} (POST #${isPost ? postCount : "-"}) ${req.method} ${url.pathname} model=${JSON.stringify(model)} t+${Date.now() - t0}ms`);
972
+ if (policy === "overloaded-then-ok") {
973
+ if (isPost && postCount <= failingPostsBeforeSuccess)
974
+ return jsonResponse({ type: "error", error: { type: "overloaded_error", message: "capture: synthetic overload" } }, 529);
975
+ return jsonResponse(CANNED_TEXT_RESPONSE);
976
+ }
977
+ if (policy === "ratelimit-then-ok") {
978
+ if (isPost && postCount <= failingPostsBeforeSuccess) {
979
+ return jsonResponse({ type: "error", error: { type: "rate_limit_error", message: "capture: synthetic rate limit" } }, 429, {
980
+ "retry-after": "2",
981
+ "anthropic-ratelimit-requests-limit": "1000",
982
+ "anthropic-ratelimit-requests-remaining": "0",
983
+ "anthropic-ratelimit-requests-reset": new Date(Date.now() + 2000).toISOString(),
984
+ "anthropic-ratelimit-unified-status": "rejected",
985
+ "anthropic-ratelimit-unified-reset": new Date(Date.now() + 2000).toISOString()
986
+ });
987
+ }
988
+ return jsonResponse(CANNED_TEXT_RESPONSE);
989
+ }
990
+ return jsonResponse({ type: "error", error: { type: "overloaded_error", message: "capture: synthetic persistent overload" } }, 529);
991
+ }
992
+ });
993
+ console.error(`
994
+ === Scenario G (${policy}): api_retry / rate_limit_event / fallback ===`);
995
+ console.error(`[capture G/${policy}] loopback ${server.url.href}; CLAUDE_CONFIG_DIR=${dirs.claudeConfigDir} HOME=${dirs.homeDir} (fresh mkdtemp)`);
996
+ const entries = [];
997
+ const ac = new AbortController;
998
+ const q = officialSdk.query({
999
+ prompt: "hi",
1000
+ options: {
1001
+ model: "sonnet",
1002
+ cwd: dirs.fixtureCwd,
1003
+ settingSources: [],
1004
+ abortController: ac,
1005
+ ...policy === "persistent-overloaded" ? { fallbackModel: "haiku" } : {},
1006
+ env: hermeticEnv(dirs, server.url.href)
1007
+ }
1008
+ });
1009
+ const { thrown, deadlineHit } = await drainWithDeadline(q, entries, `capture G/${policy}`, 180000, ac);
1010
+ console.error(`[capture G/${policy}] ${requestCount} loopback request(s); ${entries.length} message(s) yielded; deadlineHit=${deadlineHit}`);
1011
+ if (thrown)
1012
+ console.error(`[capture G/${policy}] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
1013
+ const payloads = entries.map((e) => e.payload);
1014
+ const retries = payloads.filter((p) => p.type === "system" && p.subtype === "api_retry");
1015
+ const rateLimits = payloads.filter((p) => p.type === "rate_limit_event");
1016
+ const refusals = payloads.filter((p) => p.type === "system" && (p.subtype === "model_refusal_fallback" || p.subtype === "model_refusal_no_fallback"));
1017
+ const results = payloads.filter((p) => p.type === "result");
1018
+ console.log(`
1019
+ --- Scenario G (${policy}) normalized trace ---`);
1020
+ console.log(JSON.stringify(normalizeTrace2(entries), null, 2));
1021
+ console.log(`
1022
+ --- Scenario G (${policy}): every api_retry frame, verbatim ---`);
1023
+ console.log(JSON.stringify(retries, null, 2));
1024
+ console.log(`
1025
+ --- Scenario G (${policy}): every rate_limit_event frame ---`);
1026
+ console.log(JSON.stringify(rateLimits.length > 0 ? rateLimits : "(none observed)", null, 2));
1027
+ const prosefreeRefusals = refusals.map((r) => {
1028
+ const out = { ...r };
1029
+ for (const k of ["content", "api_refusal_explanation"]) {
1030
+ const v = out[k];
1031
+ if (typeof v === "string")
1032
+ out[k] = `<${v.length} chars, withheld: vendor prose>`;
1033
+ }
1034
+ return out;
1035
+ });
1036
+ console.log(`
1037
+ --- Scenario G (${policy}): every refusal/fallback frame (prose fields as lengths) ---`);
1038
+ console.log(JSON.stringify(prosefreeRefusals.length > 0 ? prosefreeRefusals : "(none observed)", null, 2));
1039
+ console.log(`
1040
+ --- Scenario G (${policy}): the model on each outgoing request, with arrival times ---`);
1041
+ console.log(JSON.stringify(requestLog, null, 2));
1042
+ console.log(`
1043
+ --- Scenario G (${policy}) verdicts ---`);
1044
+ console.log(JSON.stringify({
1045
+ "api_retry frames": retries.length,
1046
+ "attempt / max_retries / retry_delay_ms / error_status / error": retries.map((r) => ({
1047
+ attempt: r.attempt,
1048
+ max_retries: r.max_retries,
1049
+ retry_delay_ms: r.retry_delay_ms,
1050
+ error_status: r.error_status,
1051
+ error: r.error
1052
+ })),
1053
+ "rate_limit_event frames": rateLimits.length,
1054
+ "refusal/fallback frames": refusals.map((r) => r.subtype),
1055
+ "distinct models the loopback was asked for": [...new Set(requestLog.map((r) => String(r.model)))],
1056
+ "result subtype / is_error": results.map((r) => ({ subtype: r.subtype, is_error: r.is_error, api_error_status: r.api_error_status, terminal_reason: r.terminal_reason })),
1057
+ "query() threw": describeThrown(thrown) ?? false,
1058
+ deadlineHit
1059
+ }, null, 2));
1060
+ } finally {
1061
+ server?.stop(true);
1062
+ for (const cleanup of cleanups)
1063
+ cleanup();
1064
+ }
1065
+ }
1066
+ function findFileRecursive(root, predicate, depth = 0) {
1067
+ if (depth > 6)
1068
+ return [];
1069
+ const out = [];
1070
+ let names;
1071
+ try {
1072
+ names = readdirSync(root);
1073
+ } catch {
1074
+ return out;
1075
+ }
1076
+ for (const name of names) {
1077
+ const full = join2(root, name);
1078
+ let isDir = false;
1079
+ try {
1080
+ isDir = statSync(full).isDirectory();
1081
+ } catch {
1082
+ continue;
1083
+ }
1084
+ if (isDir)
1085
+ out.push(...findFileRecursive(full, predicate, depth + 1));
1086
+ else if (predicate(name, full))
1087
+ out.push(full);
1088
+ }
1089
+ return out;
1090
+ }
1091
+ function sha256OfFile(path) {
1092
+ return createHash2("sha256").update(readFileSync2(path)).digest("hex");
1093
+ }
1094
+ function sha256AtPath(path) {
1095
+ try {
1096
+ return sha256OfFile(path);
1097
+ } catch {
1098
+ return "(ABSENT AT ITS OWN PATH — ASSERTION FAILED)";
1099
+ }
1100
+ }
1101
+ function countLinesAtPath(path) {
1102
+ try {
1103
+ return readFileSync2(path, "utf8").split(`
1104
+ `).filter((l) => l.trim().length > 0).length;
1105
+ } catch {
1106
+ return -1;
1107
+ }
1108
+ }
1109
+ async function runNeighborFileProbe(officialSdk) {
1110
+ const cleanups = [];
1111
+ let server;
1112
+ try {
1113
+ const dirs = makeScenarioDirs("h", cleanups);
1114
+ const sessionId = "0192f4c8-6f21-7c3a-9d55-1b8e2a7c40d1";
1115
+ const marker = `WINTER-PROVIDER-STATE-MARKER-${createHash2("sha256").update(sessionId).digest("hex").slice(0, 32)}`;
1116
+ let requestCount = 0;
1117
+ let markerSeenInAnyRequestBody = false;
1118
+ const requestPaths = [];
1119
+ let bulkyReplies = false;
1120
+ const BULK_TEXT = "winter capture filler sentence for the compaction threshold. ".repeat(700);
1121
+ server = Bun.serve({
1122
+ port: 0,
1123
+ hostname: "127.0.0.1",
1124
+ async fetch(req) {
1125
+ requestCount++;
1126
+ const url = new URL(req.url);
1127
+ requestPaths.push(`${req.method} ${url.pathname}`);
1128
+ let raw = "";
1129
+ try {
1130
+ raw = await req.clone().text();
1131
+ } catch {}
1132
+ if (raw.includes(marker))
1133
+ markerSeenInAnyRequestBody = true;
1134
+ console.error(`[loopback H] #${requestCount} ${req.method} ${url.pathname} bodyBytes=${raw.length} markerPresent=${raw.includes(marker)}`);
1135
+ if (!bulkyReplies)
1136
+ return jsonResponse(CANNED_TEXT_RESPONSE);
1137
+ return jsonResponse({
1138
+ ...CANNED_TEXT_RESPONSE,
1139
+ content: [{ type: "text", text: BULK_TEXT }],
1140
+ usage: { input_tokens: 12000, output_tokens: 12000 }
1141
+ });
1142
+ }
1143
+ });
1144
+ console.error(`
1145
+ === Scenario H: WS-17 probe (a) — neighbour-file survival across resume ===`);
1146
+ console.error(`[capture H] loopback ${server.url.href}; CLAUDE_CONFIG_DIR=${dirs.claudeConfigDir} HOME=${dirs.homeDir} cwd=${dirs.fixtureCwd} (all fresh mkdtemp, REUSED across runs)`);
1147
+ const entries1 = [];
1148
+ const ac1 = new AbortController;
1149
+ const r1 = await drainWithDeadline(officialSdk.query({
1150
+ prompt: "hi from run one",
1151
+ options: { model: "sonnet", cwd: dirs.fixtureCwd, settingSources: [], sessionId, abortController: ac1, env: hermeticEnv(dirs, server.url.href) }
1152
+ }), entries1, "capture H/run1", 120000, ac1);
1153
+ const init1 = entries1.map((e) => e.payload).find((p) => p.type === "system" && p.subtype === "init");
1154
+ console.error(`[capture H] run 1: ${entries1.length} message(s); session_id on init = ${String(init1?.session_id)}`);
1155
+ if (r1.thrown)
1156
+ console.error(`[capture H] run 1 threw: ${r1.thrown instanceof Error ? r1.thrown.stack ?? r1.thrown.message : String(r1.thrown)}`);
1157
+ const observedSessionId = typeof init1?.session_id === "string" ? init1.session_id : sessionId;
1158
+ const transcripts = findFileRecursive(dirs.claudeConfigDir, (name) => name === `${observedSessionId}.jsonl`);
1159
+ console.error(`[capture H] transcript search under <CLAUDE_CONFIG_DIR> found ${transcripts.length} match(es)`);
1160
+ if (transcripts.length === 0) {
1161
+ console.log(`
1162
+ --- Scenario H: NOT CAPTURABLE — no <sessionId>.jsonl transcript was written under <CLAUDE_CONFIG_DIR> after run 1 ---`);
1163
+ console.log(JSON.stringify({ observedSessionId, filesUnderConfigDir: findFileRecursive(dirs.claudeConfigDir, () => true).map((f) => f.replace(dirs.claudeConfigDir, "<CLAUDE_CONFIG_DIR>")) }, null, 2));
1164
+ return;
1165
+ }
1166
+ const transcriptPath = transcripts[0];
1167
+ const projectDir = dirname(transcriptPath);
1168
+ const namesBefore = readdirSync(projectDir).sort();
1169
+ const sidecarPath = join2(projectDir, `${observedSessionId}.provider-state.jsonl`);
1170
+ const sidecarLines = [
1171
+ { type: "provider-state", kind: "origin", sessionId: observedSessionId, anchorUuid: "00000000-0000-4000-8000-000000000001", provider: "winter-capture", model: "winter-capture-model", family: "capture", itemIndex: 0, marker, payload: {} },
1172
+ { type: "provider-state", kind: "native-state", sessionId: observedSessionId, anchorUuid: "00000000-0000-4000-8000-000000000001", itemIndex: 1, marker, payload: { opaque: marker } },
1173
+ { type: "provider-state", kind: "summary", sessionId: observedSessionId, anchorUuid: "00000000-0000-4000-8000-000000000001", itemIndex: 2, marker, payload: { text: "capture-only summary" } }
1174
+ ];
1175
+ writeFileSync2(sidecarPath, sidecarLines.map((l) => JSON.stringify(l)).join(`
1176
+ `) + `
1177
+ `);
1178
+ const sidecarSha256Before = sha256AtPath(sidecarPath);
1179
+ const sidecarBytesBefore = readFileSync2(sidecarPath).length;
1180
+ const transcriptLinesBefore = countLinesAtPath(transcriptPath);
1181
+ console.error(`[capture H] sidecar written beside the transcript: ${sidecarBytesBefore} bytes, sha256=${sidecarSha256Before}`);
1182
+ const entries2 = [];
1183
+ let r2 = { thrown: undefined, deadlineHit: false };
1184
+ const appendTurns = 6;
1185
+ bulkyReplies = true;
1186
+ for (let turn = 0;turn < appendTurns; turn++) {
1187
+ const ac2 = new AbortController;
1188
+ const r = await drainWithDeadline(officialSdk.query({
1189
+ prompt: `hi from append turn ${turn + 1}`,
1190
+ options: { model: "sonnet", cwd: dirs.fixtureCwd, settingSources: [], resume: observedSessionId, abortController: ac2, env: hermeticEnv(dirs, server.url.href) }
1191
+ }), entries2, `capture H/run2.${turn + 1}`, 120000, ac2);
1192
+ if (r.thrown !== undefined)
1193
+ r2 = { thrown: r.thrown, deadlineHit: r2.deadlineHit || r.deadlineHit };
1194
+ else
1195
+ r2 = { thrown: r2.thrown, deadlineHit: r2.deadlineHit || r.deadlineHit };
1196
+ if (r.thrown)
1197
+ console.error(`[capture H] append turn ${turn + 1} threw: ${r.thrown instanceof Error ? r.thrown.stack ?? r.thrown.message : String(r.thrown)}`);
1198
+ }
1199
+ const init2 = entries2.map((e) => e.payload).find((p) => p.type === "system" && p.subtype === "init");
1200
+ const sidecarSha256AfterResume = sha256AtPath(sidecarPath);
1201
+ const transcriptLinesAfterResume = countLinesAtPath(transcriptPath);
1202
+ console.error(`[capture H] after ${appendTurns} append turns: transcript ${transcriptLinesBefore} -> ${transcriptLinesAfterResume} lines; sidecar sha256=${sidecarSha256AfterResume}`);
1203
+ const entries3 = [];
1204
+ const ac3 = new AbortController;
1205
+ const r3 = await drainWithDeadline(officialSdk.query({
1206
+ prompt: "/compact",
1207
+ options: { model: "sonnet", cwd: dirs.fixtureCwd, settingSources: [], resume: observedSessionId, abortController: ac3, env: hermeticEnv(dirs, server.url.href) }
1208
+ }), entries3, "capture H/run3", 120000, ac3);
1209
+ const payloads3 = entries3.map((e) => e.payload);
1210
+ if (r3.thrown)
1211
+ console.error(`[capture H] run 3 (/compact) threw: ${r3.thrown instanceof Error ? r3.thrown.stack ?? r3.thrown.message : String(r3.thrown)}`);
1212
+ const sidecarSha256After = sha256AtPath(sidecarPath);
1213
+ const sidecarFoundElsewhere = findFileRecursive(dirs.claudeConfigDir, (name) => name === `${observedSessionId}.provider-state.jsonl`).map((f) => f.replace(dirs.claudeConfigDir, "<CLAUDE_CONFIG_DIR>"));
1214
+ const namesAfter = readdirSync(projectDir).sort();
1215
+ let transcriptParses = true;
1216
+ let transcriptLinesAfter = 0;
1217
+ try {
1218
+ for (const line of readFileSync2(transcriptPath, "utf8").split(`
1219
+ `)) {
1220
+ if (line.trim().length === 0)
1221
+ continue;
1222
+ JSON.parse(line);
1223
+ transcriptLinesAfter++;
1224
+ }
1225
+ } catch {
1226
+ transcriptParses = false;
1227
+ transcriptLinesAfter = -1;
1228
+ }
1229
+ console.log(`
1230
+ --- Scenario H: WS-17 probe (a) verdicts (R6-7 / R6-7a) ---`);
1231
+ console.log(JSON.stringify({
1232
+ sessionIdRequested: sessionId,
1233
+ sessionIdObservedOnInit: observedSessionId,
1234
+ "run 2 resumed the same session": init2?.session_id === observedSessionId,
1235
+ transcriptPath: transcriptPath.replace(dirs.claudeConfigDir, "<CLAUDE_CONFIG_DIR>"),
1236
+ sidecarPath: sidecarPath.replace(dirs.claudeConfigDir, "<CLAUDE_CONFIG_DIR>"),
1237
+ appendTurnsDriven: appendTurns,
1238
+ "ASSERTION 1 — sidecar sha256 unchanged": sidecarSha256Before === sidecarSha256After,
1239
+ "ASSERTION 1a — unchanged after the resume+append half alone": sidecarSha256Before === sidecarSha256AfterResume,
1240
+ sidecarSha256Before,
1241
+ sidecarSha256AfterResume,
1242
+ sidecarSha256After,
1243
+ transcriptLinesAfterResume,
1244
+ sidecarBytesBefore,
1245
+ sidecarBytesAfter: (() => {
1246
+ try {
1247
+ return readFileSync2(sidecarPath).length;
1248
+ } catch {
1249
+ return null;
1250
+ }
1251
+ })(),
1252
+ "sidecar found elsewhere under <CLAUDE_CONFIG_DIR> (diagnostic only — never rescues the assertion)": sidecarFoundElsewhere.length === 1 && sidecarFoundElsewhere[0]?.endsWith(`${observedSessionId}.provider-state.jsonl`) && sidecarSha256After.startsWith("(") === false ? "(at its own path, as expected)" : sidecarFoundElsewhere,
1253
+ "ASSERTION 2 — marker absent from EVERY request body": !markerSeenInAnyRequestBody,
1254
+ "ASSERTION 3 — transcript still parses as JSONL": transcriptParses,
1255
+ transcriptLinesBefore,
1256
+ transcriptLinesAfter,
1257
+ "transcript grew across resume": transcriptLinesAfter > transcriptLinesBefore,
1258
+ "project dir file NAMES before the resume": namesBefore,
1259
+ "project dir file NAMES after the resume + compact": namesAfter,
1260
+ "names added": namesAfter.filter((n) => !namesBefore.includes(n)),
1261
+ "names removed": namesBefore.filter((n) => !namesAfter.includes(n)),
1262
+ "run 3 (/compact): message kinds": payloads3.map((p) => p.type === "system" ? `system/${p.subtype}` : p.type),
1263
+ "run 3 (/compact): a compact_boundary frame appeared": payloads3.some((p) => p.type === "system" && p.subtype === "compact_boundary"),
1264
+ "run 3 threw": describeThrown(r3.thrown) ?? false,
1265
+ "loopback saw": requestPaths,
1266
+ "deadlines hit (run1/run2/run3)": [r1.deadlineHit, r2.deadlineHit, r3.deadlineHit]
1267
+ }, null, 2));
1268
+ console.log(`
1269
+ --- Scenario H: run 2 normalized trace ---`);
1270
+ console.log(JSON.stringify(normalizeTrace2(entries2), null, 2));
1271
+ console.log(`
1272
+ --- Scenario H: run 3 (/compact) normalized trace ---`);
1273
+ console.log(JSON.stringify(normalizeTrace2(entries3), null, 2));
1274
+ } finally {
1275
+ server?.stop(true);
1276
+ for (const cleanup of cleanups)
1277
+ cleanup();
1278
+ }
1279
+ }
1280
+ async function runFailureShapeCapture(officialSdk, variant) {
1281
+ const cleanups = [];
1282
+ let server;
1283
+ try {
1284
+ const dirs = makeScenarioDirs(`i-${variant}`, cleanups);
1285
+ let requestCount = 0;
1286
+ server = Bun.serve({
1287
+ port: 0,
1288
+ hostname: "127.0.0.1",
1289
+ fetch(req) {
1290
+ requestCount++;
1291
+ const url = new URL(req.url);
1292
+ console.error(`[loopback I/${variant}] #${requestCount} ${req.method} ${url.pathname}`);
1293
+ if (variant === "unknown-model") {
1294
+ return jsonResponse({ type: "error", error: { type: "not_found_error", message: "capture: synthetic unknown model" } }, 404);
1295
+ }
1296
+ return jsonResponse(CANNED_TEXT_RESPONSE);
1297
+ }
1298
+ });
1299
+ console.error(`
1300
+ === Scenario I (${variant}): the failure shape ===`);
1301
+ console.error(`[capture I/${variant}] loopback ${server.url.href}; CLAUDE_CONFIG_DIR=${dirs.claudeConfigDir} HOME=${dirs.homeDir} (fresh mkdtemp)`);
1302
+ const fullEnv = hermeticEnv(dirs, server.url.href);
1303
+ const env = { ...fullEnv };
1304
+ if (variant === "no-key") {
1305
+ delete env.ANTHROPIC_API_KEY;
1306
+ console.error(`[capture I/no-key] the child env is EXACTLY ${JSON.stringify(Object.keys(env).sort())} — no API key of any kind`);
1307
+ }
1308
+ const entries = [];
1309
+ const ac = new AbortController;
1310
+ const q = officialSdk.query({
1311
+ prompt: "hi",
1312
+ options: {
1313
+ model: variant === "unknown-model" ? "definitely-not-a-model" : "sonnet",
1314
+ cwd: dirs.fixtureCwd,
1315
+ settingSources: [],
1316
+ abortController: ac,
1317
+ env
1318
+ }
1319
+ });
1320
+ const { thrown, deadlineHit } = await drainWithDeadline(q, entries, `capture I/${variant}`, 90000, ac);
1321
+ const payloads = entries.map((e) => e.payload);
1322
+ const init = payloads.find((p) => p.type === "system" && p.subtype === "init");
1323
+ const auth = payloads.filter((p) => p.type === "auth_status");
1324
+ const results = payloads.filter((p) => p.type === "result");
1325
+ console.error(`[capture I/${variant}] ${requestCount} loopback request(s); ${entries.length} message(s) yielded; deadlineHit=${deadlineHit}`);
1326
+ console.log(`
1327
+ --- Scenario I (${variant}) normalized trace ---`);
1328
+ console.log(JSON.stringify(normalizeTrace2(entries), null, 2));
1329
+ console.log(`
1330
+ --- Scenario I (${variant}) verdicts ---`);
1331
+ console.log(JSON.stringify({
1332
+ "messages yielded": entries.length,
1333
+ "loopback requests": requestCount,
1334
+ "system/init observed": init !== undefined,
1335
+ "init.apiKeySource": init?.apiKeySource ?? "(no init frame)",
1336
+ "init.model": init?.model ?? "(no init frame)",
1337
+ "auth_status frames": auth.length,
1338
+ "auth_status payloads": auth.length > 0 ? auth : "(none)",
1339
+ "result frames": results.map((r) => ({
1340
+ subtype: r.subtype,
1341
+ is_error: r.is_error,
1342
+ api_error_status: r.api_error_status,
1343
+ terminal_reason: r.terminal_reason,
1344
+ num_turns: r.num_turns,
1345
+ total_cost_usd: r.total_cost_usd,
1346
+ "result string length": typeof r.result === "string" ? r.result.length : null
1347
+ })),
1348
+ "query() threw": describeThrown(thrown) ?? false,
1349
+ deadlineHit
1350
+ }, null, 2));
1351
+ } finally {
1352
+ server?.stop(true);
1353
+ for (const cleanup of cleanups)
1354
+ cleanup();
1355
+ }
1356
+ }
1357
+ async function runModelControlCapture(officialSdk) {
1358
+ const cleanups = [];
1359
+ let server;
1360
+ try {
1361
+ const dirs = makeScenarioDirs("j", cleanups);
1362
+ let requestCount = 0;
1363
+ const requestPaths = [];
1364
+ const modelsAsked = [];
1365
+ let releaseGate = () => {};
1366
+ const gate = new Promise((resolve) => {
1367
+ releaseGate = resolve;
1368
+ });
1369
+ const gateTimer = setTimeout(() => releaseGate(), 60000);
1370
+ cleanups.push(() => clearTimeout(gateTimer));
1371
+ server = Bun.serve({
1372
+ port: 0,
1373
+ hostname: "127.0.0.1",
1374
+ async fetch(req) {
1375
+ requestCount++;
1376
+ const url = new URL(req.url);
1377
+ requestPaths.push(`${req.method} ${url.pathname}`);
1378
+ try {
1379
+ modelsAsked.push((await req.json()).model);
1380
+ } catch {
1381
+ modelsAsked.push(null);
1382
+ }
1383
+ console.error(`[loopback J] #${requestCount} ${req.method} ${url.pathname} model=${JSON.stringify(modelsAsked[modelsAsked.length - 1])} (holding behind the control-call gate)`);
1384
+ await gate;
1385
+ return jsonResponse(CANNED_TEXT_RESPONSE);
1386
+ }
1387
+ });
1388
+ console.error(`
1389
+ === Scenario J: supportedModels() / setModel() ===`);
1390
+ console.error(`[capture J] loopback ${server.url.href}; CLAUDE_CONFIG_DIR=${dirs.claudeConfigDir} HOME=${dirs.homeDir} (fresh mkdtemp)`);
1391
+ const entries = [];
1392
+ const ac = new AbortController;
1393
+ const q = officialSdk.query({
1394
+ prompt: "hi",
1395
+ options: { model: "sonnet", cwd: dirs.fixtureCwd, settingSources: [], abortController: ac, env: hermeticEnv(dirs, server.url.href) }
1396
+ });
1397
+ const drained = drainWithDeadline(q, entries, "capture J", 120000, ac);
1398
+ const withTimeout = async (p, ms) => {
1399
+ let timer;
1400
+ try {
1401
+ const value = await Promise.race([
1402
+ p,
1403
+ new Promise((_, reject) => {
1404
+ timer = setTimeout(() => reject(new Error(`capture J: control call timed out after ${ms}ms`)), ms);
1405
+ })
1406
+ ]);
1407
+ return { ok: true, value };
1408
+ } catch (e) {
1409
+ return { ok: false, error: e };
1410
+ } finally {
1411
+ if (timer !== undefined)
1412
+ clearTimeout(timer);
1413
+ }
1414
+ };
1415
+ const modelsResult = typeof q.supportedModels === "function" ? await withTimeout(q.supportedModels(), 45000) : { ok: false, error: new Error("supportedModels is not a function on the returned query") };
1416
+ const setModelResult = typeof q.setModel === "function" ? await withTimeout(q.setModel("haiku"), 45000) : { ok: false, error: new Error("setModel is not a function on the returned query") };
1417
+ const accountResult = typeof q.accountInfo === "function" ? await withTimeout(q.accountInfo(), 45000) : { ok: false, error: new Error("accountInfo is not a function on the returned query") };
1418
+ releaseGate();
1419
+ const { thrown, deadlineHit } = await drained;
1420
+ const models = modelsResult.ok ? modelsResult.value : [];
1421
+ console.error(`[capture J] ${requestCount} loopback request(s); ${entries.length} message(s) yielded; deadlineHit=${deadlineHit}`);
1422
+ if (thrown)
1423
+ console.error(`[capture J] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
1424
+ console.log(`
1425
+ --- Scenario J: supportedModels() return shape ---`);
1426
+ console.log(JSON.stringify(modelsResult.ok ? {
1427
+ isArray: Array.isArray(models),
1428
+ count: Array.isArray(models) ? models.length : null,
1429
+ "union of row key names": Array.isArray(models) ? [...new Set(models.flatMap((m) => Object.keys(m)))].sort() : null,
1430
+ "every row, prose stripped": Array.isArray(models) ? models.map((m) => {
1431
+ const row = m;
1432
+ return {
1433
+ ...row,
1434
+ displayName: typeof row.displayName === "string" ? `<${row.displayName.length} chars>` : row.displayName,
1435
+ description: typeof row.description === "string" ? `<${row.description.length} chars>` : row.description
1436
+ };
1437
+ }) : null
1438
+ } : { threw: describeThrown(modelsResult.error) }, null, 2));
1439
+ console.log(`
1440
+ --- Scenario J: setModel("haiku") outcome ---`);
1441
+ console.log(JSON.stringify(setModelResult.ok ? { resolved: true, value: setModelResult.value } : { threw: describeThrown(setModelResult.error) }, null, 2));
1442
+ console.log(`
1443
+ --- Scenario J: accountInfo() outcome (item (d) — the account surface a query() stream never carries) ---`);
1444
+ console.log(JSON.stringify(accountResult.ok ? { resolved: true, "key names present": Object.keys(accountResult.value ?? {}).sort() } : { threw: describeThrown(accountResult.error) }, null, 2));
1445
+ console.log(`
1446
+ --- Scenario J verdicts ---`);
1447
+ console.log(JSON.stringify({
1448
+ "every request path the loopback saw": requestPaths,
1449
+ "the model on each request (did setModel take effect on the wire?)": modelsAsked,
1450
+ "did any /v1/models request reach the loopback?": requestPaths.some((p) => p.includes("/models")),
1451
+ "system/init.model": entries.map((e) => e.payload).find((p) => p.type === "system" && p.subtype === "init")?.model ?? "(no init frame)",
1452
+ "query() threw": describeThrown(thrown) ?? false,
1453
+ deadlineHit
1454
+ }, null, 2));
1455
+ } finally {
1456
+ server?.stop(true);
1457
+ for (const cleanup of cleanups)
1458
+ cleanup();
1459
+ }
1460
+ }
1461
+ async function runCostCapture(officialSdk) {
1462
+ const cleanups = [];
1463
+ let server;
1464
+ const fakeModel = "winter-capture-fake-model-1";
1465
+ try {
1466
+ const dirs = makeScenarioDirs("k", cleanups);
1467
+ let requestCount = 0;
1468
+ const modelsAsked = [];
1469
+ server = Bun.serve({
1470
+ port: 0,
1471
+ hostname: "127.0.0.1",
1472
+ async fetch(req) {
1473
+ requestCount++;
1474
+ let body = {};
1475
+ try {
1476
+ body = await req.json();
1477
+ } catch {}
1478
+ modelsAsked.push(body.model);
1479
+ const url = new URL(req.url);
1480
+ console.error(`[loopback K] #${requestCount} ${req.method} ${url.pathname} model=${JSON.stringify(body.model)}`);
1481
+ return jsonResponse({
1482
+ id: "msg_capture_cost_01",
1483
+ type: "message",
1484
+ role: "assistant",
1485
+ model: fakeModel,
1486
+ content: [{ type: "text", text: "echo: cost" }],
1487
+ stop_reason: "end_turn",
1488
+ stop_sequence: null,
1489
+ usage: { input_tokens: 17, output_tokens: 5, cache_read_input_tokens: 3, cache_creation_input_tokens: 2 }
1490
+ });
1491
+ }
1492
+ });
1493
+ console.error(`
1494
+ === Scenario K: total_cost_usd / modelUsage / costBasis for a fake model id ===`);
1495
+ console.error(`[capture K] loopback ${server.url.href}; CLAUDE_CONFIG_DIR=${dirs.claudeConfigDir} HOME=${dirs.homeDir} (fresh mkdtemp)`);
1496
+ const entries = [];
1497
+ const ac = new AbortController;
1498
+ const q = officialSdk.query({
1499
+ prompt: "hi",
1500
+ options: { model: fakeModel, cwd: dirs.fixtureCwd, settingSources: [], abortController: ac, env: hermeticEnv(dirs, server.url.href) }
1501
+ });
1502
+ const { thrown, deadlineHit } = await drainWithDeadline(q, entries, "capture K", 90000, ac);
1503
+ const payloads = entries.map((e) => e.payload);
1504
+ const result = payloads.find((p) => p.type === "result");
1505
+ const modelUsage = result?.modelUsage ?? {};
1506
+ console.error(`[capture K] ${requestCount} loopback request(s); ${entries.length} message(s) yielded; deadlineHit=${deadlineHit}`);
1507
+ if (thrown)
1508
+ console.error(`[capture K] query() threw: ${thrown instanceof Error ? thrown.stack ?? thrown.message : String(thrown)}`);
1509
+ console.log(`
1510
+ --- Scenario K normalized trace ---`);
1511
+ console.log(JSON.stringify(normalizeTrace2(entries), null, 2));
1512
+ console.log(`
1513
+ --- Scenario K verdicts ---`);
1514
+ console.log(JSON.stringify({
1515
+ "option model / response model / models the loopback was asked for": { option: fakeModel, response: fakeModel, requested: modelsAsked },
1516
+ "system/init.model": payloads.find((p) => p.type === "system" && p.subtype === "init")?.model ?? "(no init frame)",
1517
+ "result subtype / is_error": { subtype: result?.subtype, is_error: result?.is_error },
1518
+ total_cost_usd: result?.total_cost_usd,
1519
+ "modelUsage KEYS": Object.keys(modelUsage),
1520
+ "modelUsage rows": modelUsage,
1521
+ "costBasis per row": Object.fromEntries(Object.entries(modelUsage).map(([k, v]) => [k, v.costBasis ?? "(absent)"])),
1522
+ "usage (NonNullableUsage) key names — the only pinned way to learn them": Object.keys(result?.usage ?? {}).sort(),
1523
+ usage: result?.usage,
1524
+ "query() threw": describeThrown(thrown) ?? false,
1525
+ deadlineHit
1526
+ }, null, 2));
1527
+ } finally {
1528
+ server?.stop(true);
1529
+ for (const cleanup of cleanups)
1530
+ cleanup();
1531
+ }
1532
+ }
1533
+ async function runCapture2() {
1534
+ requireBunRuntime2("runCapture", "Bun.spawn and Bun.serve", "It installs the pinned official SDK into a throwaway npm prefix and drives it against loopback fakes. Run the capture under Bun (`bun run scripts/capture-official-golden.ts`); the GOLDENS it produces are plain JSON and are readable from Node.");
1535
+ const only = (process.env.RUN_OFFICIAL_CAPTURE_ONLY ?? "").split(",").map((s) => s.trim().toUpperCase()).filter((s) => s.length > 0);
1536
+ const want = (letter) => only.length === 0 || only.includes(letter);
1537
+ const cleanups = [];
1538
+ try {
1539
+ const officialSdk = await installOfficialSdk(cleanups);
1540
+ if (want("A"))
1541
+ await runPlainQueryCapture(officialSdk);
1542
+ if (want("B"))
1543
+ await runPermissionsAndHooksCapture(officialSdk);
1544
+ if (want("C"))
1545
+ await runInitToolsCapture(officialSdk);
1546
+ if (want("D"))
1547
+ await runAdvertisedSchemaCapture(officialSdk);
1548
+ if (want("E")) {
1549
+ await runMcpOutputCapCapture(officialSdk, undefined);
1550
+ await runMcpOutputCapCapture(officialSdk, "100");
1551
+ }
1552
+ if (want("F")) {
1553
+ await runStreamEventCapture(officialSdk, true);
1554
+ await runStreamEventCapture(officialSdk, false);
1555
+ }
1556
+ if (want("G")) {
1557
+ await runRetryCapture(officialSdk, "overloaded-then-ok");
1558
+ await runRetryCapture(officialSdk, "ratelimit-then-ok");
1559
+ await runRetryCapture(officialSdk, "persistent-overloaded");
1560
+ }
1561
+ if (want("H"))
1562
+ await runNeighborFileProbe(officialSdk);
1563
+ if (want("I")) {
1564
+ await runFailureShapeCapture(officialSdk, "no-key");
1565
+ await runFailureShapeCapture(officialSdk, "unknown-model");
1566
+ }
1567
+ if (want("J"))
1568
+ await runModelControlCapture(officialSdk);
1569
+ if (want("K"))
1570
+ await runCostCapture(officialSdk);
1571
+ } finally {
1572
+ for (const cleanup of cleanups)
1573
+ cleanup();
1574
+ }
1575
+ }
1576
+ export { BunRequiredError2, hasBunRuntime2, requireBunRuntime2, ChecksumMismatchError2, OfficialCompatUnavailableError2, getChecksums2, verifyDigest2, verifySha512Integrity2, resolveCacheDir2, fetchAndVerifyUpstream2, runCapture2 };