@yanlinglabs/winter-conformance 0.0.9 → 0.0.11
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.
|
@@ -110,9 +110,10 @@ async function fetchAndVerifyUpstream2(opts = {}) {
|
|
|
110
110
|
}
|
|
111
111
|
// src/official/capture.ts
|
|
112
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";
|
|
113
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
114
114
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
115
115
|
import { dirname, join as join2 } from "node:path";
|
|
116
|
+
import { fileURLToPath } from "node:url";
|
|
116
117
|
var CANNED_TEXT_RESPONSE = {
|
|
117
118
|
id: "msg_capture_canned_01",
|
|
118
119
|
type: "message",
|
|
@@ -1530,6 +1531,117 @@ async function runCostCapture(officialSdk) {
|
|
|
1530
1531
|
cleanup();
|
|
1531
1532
|
}
|
|
1532
1533
|
}
|
|
1534
|
+
var COMPACTION_GOLDEN_VOLATILE_TOP_LEVEL = new Set([
|
|
1535
|
+
"uuid",
|
|
1536
|
+
"logicalParentUuid",
|
|
1537
|
+
"timestamp",
|
|
1538
|
+
"cwd",
|
|
1539
|
+
"sessionId",
|
|
1540
|
+
"version",
|
|
1541
|
+
"gitBranch",
|
|
1542
|
+
"slug",
|
|
1543
|
+
"userType",
|
|
1544
|
+
"entrypoint",
|
|
1545
|
+
"isMeta",
|
|
1546
|
+
"promptId"
|
|
1547
|
+
]);
|
|
1548
|
+
var COMPACTION_GOLDEN_VOLATILE_METADATA = new Set(["preTokens", "postTokens", "durationMs", "preservedSegment", "preservedMessages", "cumulativeDroppedTokens"]);
|
|
1549
|
+
function normalizeCompactionEntry(raw) {
|
|
1550
|
+
const out = {};
|
|
1551
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
1552
|
+
if (COMPACTION_GOLDEN_VOLATILE_TOP_LEVEL.has(k))
|
|
1553
|
+
continue;
|
|
1554
|
+
if (k === "parentUuid" && v !== null)
|
|
1555
|
+
continue;
|
|
1556
|
+
out[k] = v;
|
|
1557
|
+
}
|
|
1558
|
+
if (out.compactMetadata !== undefined && typeof out.compactMetadata === "object" && out.compactMetadata !== null) {
|
|
1559
|
+
out.compactMetadata = Object.fromEntries(Object.entries(out.compactMetadata).filter(([k]) => !COMPACTION_GOLDEN_VOLATILE_METADATA.has(k)));
|
|
1560
|
+
}
|
|
1561
|
+
return out;
|
|
1562
|
+
}
|
|
1563
|
+
function redactTranscriptPath(content, transcriptPath) {
|
|
1564
|
+
return content.split(transcriptPath).join("<TRANSCRIPT_PATH>");
|
|
1565
|
+
}
|
|
1566
|
+
async function runCompactionCapture(officialSdk, opts = {}) {
|
|
1567
|
+
const cleanups = [];
|
|
1568
|
+
let server;
|
|
1569
|
+
try {
|
|
1570
|
+
const dirs = makeScenarioDirs("l", cleanups);
|
|
1571
|
+
let requestCount = 0;
|
|
1572
|
+
server = Bun.serve({
|
|
1573
|
+
port: 0,
|
|
1574
|
+
hostname: "127.0.0.1",
|
|
1575
|
+
async fetch(req) {
|
|
1576
|
+
requestCount++;
|
|
1577
|
+
let body = {};
|
|
1578
|
+
try {
|
|
1579
|
+
body = await req.json();
|
|
1580
|
+
} catch {}
|
|
1581
|
+
const url = new URL(req.url);
|
|
1582
|
+
console.error(`[loopback L] #${requestCount} ${req.method} ${url.pathname} model=${JSON.stringify(body.model)}`);
|
|
1583
|
+
return jsonResponse({
|
|
1584
|
+
id: `msg_capture_l_${requestCount}`,
|
|
1585
|
+
type: "message",
|
|
1586
|
+
role: "assistant",
|
|
1587
|
+
model: body.model ?? "claude-haiku-4-5",
|
|
1588
|
+
content: [{ type: "text", text: `capture: reply ${requestCount}` }],
|
|
1589
|
+
stop_reason: "end_turn",
|
|
1590
|
+
stop_sequence: null,
|
|
1591
|
+
usage: { input_tokens: 20, output_tokens: 5 }
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
console.error(`
|
|
1596
|
+
=== Scenario L: the compaction golden (W18-12) ===`);
|
|
1597
|
+
console.error(`[capture L] loopback ${server.url.href}; CLAUDE_CONFIG_DIR=${dirs.claudeConfigDir} HOME=${dirs.homeDir} (fresh mkdtemp)`);
|
|
1598
|
+
const sessionId = randomUUID();
|
|
1599
|
+
const commonOptions = { model: "claude-haiku-4-5", cwd: dirs.fixtureCwd, settingSources: [] };
|
|
1600
|
+
const ac1 = new AbortController;
|
|
1601
|
+
await drainWithDeadline(officialSdk.query({ prompt: "hi from turn one", options: { ...commonOptions, sessionId, abortController: ac1, env: hermeticEnv(dirs, server.url.href) } }), [], "capture L/turn1", 60000, ac1);
|
|
1602
|
+
const ac2 = new AbortController;
|
|
1603
|
+
await drainWithDeadline(officialSdk.query({ prompt: "hi from turn two", options: { ...commonOptions, resume: sessionId, abortController: ac2, env: hermeticEnv(dirs, server.url.href) } }), [], "capture L/turn2", 60000, ac2);
|
|
1604
|
+
const ac3 = new AbortController;
|
|
1605
|
+
const entries3 = [];
|
|
1606
|
+
const r3 = await drainWithDeadline(officialSdk.query({ prompt: "/compact", options: { ...commonOptions, resume: sessionId, abortController: ac3, env: hermeticEnv(dirs, server.url.href) } }), entries3, "capture L/compact", 60000, ac3);
|
|
1607
|
+
if (r3.thrown)
|
|
1608
|
+
console.error(`[capture L] /compact threw: ${r3.thrown instanceof Error ? r3.thrown.stack ?? r3.thrown.message : String(r3.thrown)}`);
|
|
1609
|
+
const transcripts = findFileRecursive(dirs.claudeConfigDir, (name) => name === `${sessionId}.jsonl`);
|
|
1610
|
+
if (transcripts.length === 0) {
|
|
1611
|
+
console.log(`
|
|
1612
|
+
--- Scenario L: NOT CAPTURABLE — no ${sessionId}.jsonl transcript was written ---`);
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
const transcriptPath = transcripts[0];
|
|
1616
|
+
const lines = readFileSync2(transcriptPath, "utf8").trim().split(`
|
|
1617
|
+
`).filter((l) => l.length > 0).map((l) => JSON.parse(l));
|
|
1618
|
+
const rawBoundary = lines.find((e) => e.type === "system" && e.subtype === "compact_boundary");
|
|
1619
|
+
const rawSummary = lines.find((e) => e.isCompactSummary === true);
|
|
1620
|
+
if (rawBoundary === undefined || rawSummary === undefined) {
|
|
1621
|
+
console.log(`
|
|
1622
|
+
--- Scenario L: NOT CAPTURABLE — /compact ran but wrote no boundary+summary pair (types: ${lines.map((e) => e.type).join(",")}) ---`);
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const boundary = normalizeCompactionEntry(rawBoundary);
|
|
1626
|
+
const summaryMessage = rawSummary.message.content;
|
|
1627
|
+
const summary = normalizeCompactionEntry({ ...rawSummary, message: { ...rawSummary.message, content: redactTranscriptPath(summaryMessage, transcriptPath) } });
|
|
1628
|
+
console.log(`
|
|
1629
|
+
--- Scenario L: normalized boundary+summary ---`);
|
|
1630
|
+
console.log(JSON.stringify({ boundary, summary }, null, 2));
|
|
1631
|
+
if (opts.writeGolden === true) {
|
|
1632
|
+
const goldenPath = fileURLToPath(new URL("../../goldens/claude-2.1.250/compaction.jsonl", import.meta.url));
|
|
1633
|
+
writeFileSync2(goldenPath, `${JSON.stringify(boundary)}
|
|
1634
|
+
${JSON.stringify(summary)}
|
|
1635
|
+
`);
|
|
1636
|
+
console.error(`[capture L] wrote ${goldenPath}`);
|
|
1637
|
+
}
|
|
1638
|
+
return { boundary, summary };
|
|
1639
|
+
} finally {
|
|
1640
|
+
server?.stop(true);
|
|
1641
|
+
for (const cleanup of cleanups)
|
|
1642
|
+
cleanup();
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1533
1645
|
async function runCapture2() {
|
|
1534
1646
|
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
1647
|
const only = (process.env.RUN_OFFICIAL_CAPTURE_ONLY ?? "").split(",").map((s) => s.trim().toUpperCase()).filter((s) => s.length > 0);
|
|
@@ -1568,6 +1680,8 @@ async function runCapture2() {
|
|
|
1568
1680
|
await runModelControlCapture(officialSdk);
|
|
1569
1681
|
if (want("K"))
|
|
1570
1682
|
await runCostCapture(officialSdk);
|
|
1683
|
+
if (want("L"))
|
|
1684
|
+
await runCompactionCapture(officialSdk, { writeGolden: process.env.RUN_OFFICIAL_CAPTURE_WRITE_GOLDEN === "1" });
|
|
1571
1685
|
} finally {
|
|
1572
1686
|
for (const cleanup of cleanups)
|
|
1573
1687
|
cleanup();
|
package/dist/index.js
CHANGED
|
@@ -1 +1,28 @@
|
|
|
1
|
+
type OfficialQueryFn = (args: {
|
|
2
|
+
prompt: unknown;
|
|
3
|
+
options: Record<string, unknown>;
|
|
4
|
+
}) => AsyncIterable<{
|
|
5
|
+
type: string;
|
|
6
|
+
subtype?: string;
|
|
7
|
+
}>;
|
|
8
|
+
type OfficialSdk = {
|
|
9
|
+
query: OfficialQueryFn;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Strips the fields that differ on every real capture (uuids, timestamps, host/session identity,
|
|
13
|
+
* the numeric token/timing counters, and any preserved-message relink data — `preservedMessages`/
|
|
14
|
+
* `preservedSegment` name capture-specific uuids, so they vary run to run even though, since fix
|
|
15
|
+
* round 1, Winter's own writer DOES reproduce `compactMetadata.preservedMessages` byte-shape-exact
|
|
16
|
+
* to this golden — see `claudeCompactBoundaryEntry`'s own header in `dialect.ts`) while keeping
|
|
17
|
+
* `parentUuid` when it is the fixed `null` the boundary always carries (that value IS the shape,
|
|
18
|
+
* not noise).
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeCompactionEntry(raw: Record<string, unknown>): Record<string, unknown>;
|
|
21
|
+
export declare function runCompactionCapture(officialSdk: OfficialSdk, opts?: {
|
|
22
|
+
writeGolden?: boolean;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
boundary: Record<string, unknown>;
|
|
25
|
+
summary: Record<string, unknown>;
|
|
26
|
+
} | undefined>;
|
|
1
27
|
export declare function runCapture(): Promise<void>;
|
|
28
|
+
export {};
|
package/dist/official/index.js
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{"parentUuid": null, "isSidechain": false, "type": "system", "subtype": "compact_boundary", "content": "Conversation compacted", "level": "info", "compactMetadata": {"trigger": "manual"}}
|
|
2
|
+
{"isSidechain": false, "type": "user", "message": {"role": "user", "content": "This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.\n\nturn-3-reply\n\nIf you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: <TRANSCRIPT_PATH>\nContinue the conversation from where it left off without asking the user any further questions. Resume directly \u2014 do not acknowledge the summary, do not recap what was happening, do not preface with \"I'll continue\" or similar. Pick up the last task as if the break never happened."}, "isVisibleInTranscriptOnly": true, "isCompactSummary": true}
|