engineering-behavior-observatory 0.2.3 → 0.2.4

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.
@@ -20,14 +20,14 @@ export declare function validateArtifact(artifact: string, document: unknown): A
20
20
  export declare function validateRunManifestEvidence(artifact: string, manifest: unknown, bundleRoot: string): ArtifactValidationError[];
21
21
  export declare function validateExportManifest(artifact: string, exportManifest: unknown, containingManifest: unknown | undefined, bundleRoot?: string): ArtifactValidationError[];
22
22
  export declare function readVerifiedArtifact(artifactRoot: string, relativePath: string, expectedDigest: Digest, maxBytes?: number): Promise<Buffer>;
23
- export declare function inspectRetainedArtifact(artifactRoot: string, relativePath: string): Promise<{
23
+ export declare function inspectRetainedArtifact(artifactRoot: string, relativePath: string, maxBytes?: number): Promise<{
24
24
  digest: Digest;
25
25
  sizeBytes: number;
26
26
  }>;
27
27
  export declare function writeMetadataAtomically(artifactRoot: string, relativePath: string, metadata: unknown, signal?: AbortSignal, options?: {
28
28
  overwrite?: boolean;
29
29
  }): Promise<Digest>;
30
- export declare function writeArtifactAtomically(artifactRoot: string, relativePath: string, content: Uint8Array, signal?: AbortSignal, options?: {
30
+ export declare function writeArtifactAtomically(artifactRoot: string, relativePath: string, content: Uint8Array | AsyncIterable<Uint8Array>, signal?: AbortSignal, options?: {
31
31
  overwrite?: boolean;
32
32
  }): Promise<Digest>;
33
33
  export declare function writeMetadataAtomicallyIfAbsentSync(artifactRoot: string, relativePath: string, metadata: unknown, rootHandle?: BundleRootHandle, beforeDestinationPublish?: () => void, afterDestinationPublish?: () => void): {
@@ -852,22 +852,35 @@ export async function readVerifiedArtifact(artifactRoot, relativePath, expectedD
852
852
  await handle.close();
853
853
  }
854
854
  }
855
- export async function inspectRetainedArtifact(artifactRoot, relativePath) {
855
+ export async function inspectRetainedArtifact(artifactRoot, relativePath, maxBytes) {
856
+ if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes < 0)) {
857
+ throw new Error("Artifact byte limit must be a nonnegative safe integer.");
858
+ }
856
859
  const { path } = await resolveExistingArtifactPath(artifactRoot, relativePath);
857
- return inspectExistingPath(path, relativePath);
860
+ return inspectExistingPath(path, relativePath, maxBytes);
858
861
  }
859
862
  export async function writeMetadataAtomically(artifactRoot, relativePath, metadata, signal, options = {}) {
860
863
  const bytes = Buffer.from(canonicalizeMetadata(metadata));
861
864
  return writeArtifactAtomically(artifactRoot, relativePath, bytes, signal, options);
862
865
  }
863
866
  export async function writeArtifactAtomically(artifactRoot, relativePath, content, signal, options = {}) {
864
- const bytes = Buffer.from(content);
867
+ const source = content instanceof Uint8Array ? [Buffer.from(content)] : content;
868
+ const hash = createHash("sha256");
869
+ async function* chunks() {
870
+ for await (const chunk of source) {
871
+ if (signal?.aborted)
872
+ throw new Error("Artifact metadata write interrupted.");
873
+ const bytes = Buffer.from(chunk);
874
+ hash.update(bytes);
875
+ yield bytes;
876
+ }
877
+ }
865
878
  const { parent, path } = await prepareArtifactPath(artifactRoot, relativePath);
866
879
  const temporaryPath = resolve(parent, `.${randomUUID()}.tmp`);
867
880
  let handle;
868
881
  try {
869
882
  handle = await open(temporaryPath, "wx", 0o600);
870
- await handle.writeFile(bytes);
883
+ await handle.writeFile(chunks());
871
884
  await handle.sync();
872
885
  await handle.close();
873
886
  handle = undefined;
@@ -894,8 +907,10 @@ export async function writeArtifactAtomically(artifactRoot, relativePath, conten
894
907
  await handle?.close();
895
908
  await rm(temporaryPath, { force: true });
896
909
  }
897
- const digest = digestBytes(bytes);
898
- await readVerifiedArtifact(artifactRoot, relativePath, digest);
910
+ const digest = { algorithm: "sha256", value: hash.digest("hex") };
911
+ const inspected = await inspectRetainedArtifact(artifactRoot, relativePath);
912
+ if (inspected.digest.value !== digest.value)
913
+ throw new Error(`Artifact "${relativePath}" digest does not match its source reference.`);
899
914
  return digest;
900
915
  }
901
916
  export function writeMetadataAtomicallyIfAbsentSync(artifactRoot, relativePath, metadata, rootHandle, beforeDestinationPublish, afterDestinationPublish) {
@@ -1270,7 +1285,7 @@ function prepareArtifactPathSync(artifactRoot, relativePath) {
1270
1285
  function digestExistingPath(path, relativePath) {
1271
1286
  return inspectExistingPath(path, relativePath).digest;
1272
1287
  }
1273
- function inspectExistingPath(path, relativePath) {
1288
+ function inspectExistingPath(path, relativePath, maxBytes) {
1274
1289
  const descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
1275
1290
  try {
1276
1291
  let opened = fstatSync(descriptor);
@@ -1283,6 +1298,9 @@ function inspectExistingPath(path, relativePath) {
1283
1298
  || !Number.isSafeInteger(opened.size) || opened.size < 0) {
1284
1299
  throw new Error(`Artifact path "${relativePath}" is not an isolated regular file.`);
1285
1300
  }
1301
+ if (maxBytes !== undefined && opened.size > maxBytes) {
1302
+ throw new Error(`Artifact "${relativePath}" exceeds the qualification byte limit of ${maxBytes}.`);
1303
+ }
1286
1304
  const hash = createHash("sha256");
1287
1305
  const chunk = Buffer.allocUnsafe(64 * 1024);
1288
1306
  for (let offset = 0; offset < opened.size;) {
@@ -1,9 +1,12 @@
1
1
  import { execFile, spawn } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
+ import { createReadStream, createWriteStream } from "node:fs";
3
4
  import { cp, lstat, mkdir, mkdtemp, readdir, rm, rmdir, utimes, writeFile } from "node:fs/promises";
4
5
  import { tmpdir } from "node:os";
5
6
  import { join, resolve, sep } from "node:path";
6
7
  import { promisify } from "node:util";
8
+ import { Transform } from "node:stream";
9
+ import { pipeline } from "node:stream/promises";
7
10
  import { HOOK_EVENTS } from "@anthropic-ai/claude-agent-sdk";
8
11
  import { assertNoDuplicateJsonKeys, assertUniqueArtifactIdentities, canonicalizeMetadata, inspectRetainedArtifact, readVerifiedArtifact, validateArtifact, validateExportManifest, validateRunManifestEvidence, writeArtifactAtomically, writeMetadataAtomically, } from "./artifacts.js";
9
12
  import { isSafeArtifactRelativePath } from "./contracts.js";
@@ -11,7 +14,7 @@ import { digestWorkspace, digestWorkspaceTree } from "./verifiers.js";
11
14
  import { readBoundedFile } from "./scheduler.js";
12
15
  const execFileAsync = promisify(execFile);
13
16
  const MAX_WORKSPACE_PATCH_BYTES = 64 * 1024 * 1024;
14
- const MAX_WORKSPACE_SNAPSHOT_BYTES = 128 * 1024 * 1024;
17
+ const MAX_WORKSPACE_SNAPSHOT_BYTES = 1024 * 1024 * 1024;
15
18
  const MAX_QUALIFICATION_ARTIFACT_BYTES = 64 * 1024 * 1024;
16
19
  const QUALIFICATION_DIMENSION_RANK = { qualified: 0, unsupported: 1, gap: 2, unqualified: 3 };
17
20
  const PINNED_HOOK_EVENTS = new Set(HOOK_EVENTS);
@@ -151,11 +154,17 @@ export class RunBundleAssembler {
151
154
  throw new Error("Final workspace metadata changed while its outcome was being captured.");
152
155
  }
153
156
  const format = patch === undefined ? "snapshot" : "patch";
154
- const content = patch ?? await workspaceSnapshot(capturedPath, treeDigest);
155
157
  const relativePath = format === "patch"
156
158
  ? options.relativePath ?? "workspace.patch"
157
159
  : options.snapshotRelativePath ?? "workspace.tar.gz";
158
- await writeArtifactAtomically(this.bundleRoot, relativePath, content, undefined, { overwrite: false });
160
+ if (patch !== undefined) {
161
+ await writeArtifactAtomically(this.bundleRoot, relativePath, patch, undefined, { overwrite: false });
162
+ }
163
+ else {
164
+ await workspaceSnapshot(capturedPath, treeDigest, async (snapshotPath) => {
165
+ await writeArtifactAtomically(this.bundleRoot, relativePath, createReadStream(snapshotPath), undefined, { overwrite: false });
166
+ });
167
+ }
159
168
  const descriptor = await this.registerArtifact({
160
169
  id: options.id ?? "workspace",
161
170
  source: options.source ?? `workspace-${format}`,
@@ -312,9 +321,15 @@ export async function qualifyRunBundle(bundleRoot, options = {}) {
312
321
  const state = { descriptor, valid: true };
313
322
  states.set(descriptor.id, state);
314
323
  try {
315
- const bytes = await readVerifiedArtifact(bundleRoot, descriptor.relativePath, digestValue(descriptor.digest), descriptor.kind === "workspace" && descriptor.mediaType === "application/gzip"
316
- ? MAX_WORKSPACE_SNAPSHOT_BYTES
317
- : MAX_QUALIFICATION_ARTIFACT_BYTES);
324
+ if (descriptor.kind === "workspace" && descriptor.mediaType === "application/gzip") {
325
+ // Opaque snapshots need streaming integrity checks, not a whole-archive buffer.
326
+ const inspected = await inspectRetainedArtifact(bundleRoot, descriptor.relativePath, MAX_WORKSPACE_SNAPSHOT_BYTES);
327
+ if (inspected.sizeBytes !== descriptor.sizeBytes || digestString(inspected.digest.value) !== descriptor.digest) {
328
+ throw new Error("Workspace snapshot size or digest does not match its descriptor.");
329
+ }
330
+ continue;
331
+ }
332
+ const bytes = await readVerifiedArtifact(bundleRoot, descriptor.relativePath, digestValue(descriptor.digest), MAX_QUALIFICATION_ARTIFACT_BYTES);
318
333
  if (descriptor.mediaType === "application/x-ndjson") {
319
334
  const summary = parseNativeJsonl(bytes);
320
335
  state.hookNames = summary.hookNames;
@@ -966,7 +981,7 @@ async function workspacePatch(startPath, finalPath, finalTreeDigest) {
966
981
  await rm(temporaryRoot, { recursive: true, force: true });
967
982
  }
968
983
  }
969
- async function workspaceSnapshot(finalPath, finalTreeDigest) {
984
+ async function workspaceSnapshot(finalPath, finalTreeDigest, use) {
970
985
  const temporaryRoot = await mkdtemp(join(tmpdir(), "ebo-workspace-snapshot-"));
971
986
  const snapshotPath = join(temporaryRoot, "workspace.tar.gz");
972
987
  const snapshotParent = join(temporaryRoot, "source");
@@ -975,18 +990,33 @@ async function workspaceSnapshot(finalPath, finalTreeDigest) {
975
990
  try {
976
991
  await mkdir(snapshotParent);
977
992
  await cp(finalPath, snapshotRoot, { recursive: true, preserveTimestamps: true, force: false });
978
- const { stdout } = await execFileAsync(TAR_COMMAND, ["-czf", "-", "-C", snapshotParent, "workspace"], {
979
- encoding: "buffer",
980
- maxBuffer: MAX_WORKSPACE_SNAPSHOT_BYTES,
993
+ const child = spawn(TAR_COMMAND, ["-czf", "-", "-C", snapshotParent, "workspace"], { stdio: ["ignore", "pipe", "pipe"] });
994
+ let stderr = "";
995
+ child.stderr.on("data", (chunk) => { stderr = (stderr + chunk.toString("utf8")).slice(-8192); });
996
+ const exited = new Promise((resolveExit, reject) => {
997
+ child.once("error", reject);
998
+ child.once("close", (code) => code === 0 ? resolveExit() : reject(new Error(`Workspace snapshot tar failed (${code}): ${stderr.trim()}`)));
981
999
  });
982
- const snapshot = Buffer.from(stdout);
983
- await writeFile(snapshotPath, snapshot, { flag: "wx", mode: 0o600 });
1000
+ let size = 0;
1001
+ const limit = new Transform({ transform(chunk, _encoding, callback) {
1002
+ size += chunk.length;
1003
+ callback(size > MAX_WORKSPACE_SNAPSHOT_BYTES ? new Error("Workspace snapshot exceeds the 1 GiB compressed byte limit.") : null, chunk);
1004
+ } });
1005
+ const streamed = pipeline(child.stdout, limit, createWriteStream(snapshotPath, { flags: "wx", mode: 0o600 }));
1006
+ try {
1007
+ await Promise.all([exited, streamed]);
1008
+ }
1009
+ finally {
1010
+ if (child.exitCode === null)
1011
+ child.kill("SIGKILL");
1012
+ await Promise.allSettled([exited, streamed]);
1013
+ }
984
1014
  await mkdir(extracted, { mode: 0o700 });
985
1015
  await execFileAsync(TAR_COMMAND, ["-xzpf", snapshotPath, "-C", extracted]);
986
1016
  if (await digestWorkspaceTree(join(extracted, "workspace")) !== finalTreeDigest) {
987
1017
  throw new Error("Bounded workspace snapshot cannot reproduce the final workspace tree.");
988
1018
  }
989
- return snapshot;
1019
+ await use(snapshotPath);
990
1020
  }
991
1021
  finally {
992
1022
  await rm(temporaryRoot, { recursive: true, force: true });
@@ -46,6 +46,15 @@ missing-evidence entries. Inspect the detail before retrying capture against a
46
46
  retained workspace. A later successful capture is recovery evidence; it does
47
47
  not change the original attempt's recorded failure.
48
48
 
49
+ Workspace capture tries a patch up to 64 MiB, then falls back to a snapshot
50
+ when the patch is too large or cannot reproduce the tree. Snapshots stream to
51
+ disk with a 1 GiB compressed limit and are extracted to verify the tree digest
52
+ before publication. Snapshot qualification also checks integrity without loading
53
+ the archive into memory. Exceeding the limit leaves explicit missing evidence
54
+ and retains the workspace; it never truncates the archive. These capture limits
55
+ are separate from portable-export limits. Configure known cache-directory
56
+ exclusions before a study rather than dropping files from an existing attempt.
57
+
49
58
  Use a new output destination for derived records and reruns. Do not edit a
50
59
  native bundle to make a validator accept it.
51
60
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engineering-behavior-observatory",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Capture engineering-agent trajectories, evaluate behavior, and inspect cited evidence across harnesses.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -47,6 +47,7 @@
47
47
  "release/0.2.1/",
48
48
  "release/0.2.2/",
49
49
  "release/0.2.3/",
50
+ "release/0.2.4/",
50
51
  "schemas/",
51
52
  "scripts/atlas-grafana.sh"
52
53
  ],
@@ -0,0 +1,16 @@
1
+ # v0.2.4 known limitations
2
+
3
+ The [v0.2.3 limitations](../0.2.3/KNOWN_LIMITATIONS.md) continue to apply.
4
+
5
+ - Snapshots remain bounded to 1 GiB compressed. Temporary source and extracted
6
+ copies also require free disk space; large cache trees increase capture time.
7
+ - Known cache exclusions must be declared before a study. Capture does not
8
+ silently omit additional files to fit a limit.
9
+ - Opaque snapshots remain restricted evidence; larger capture support does
10
+ not make them portable sanitized exports.
11
+ - Full-parallel testing exposed intermittent interruption/cleanup failures.
12
+ Release acceptance runs tests serially, as in earlier releases.
13
+ - npm audit reports three dependency findings (two moderate, one high) through
14
+ the pinned Cursor SDK's connect-node/undici dependency chain. npm reports no
15
+ available automatic fix for this dependency graph. This patch leaves those
16
+ dependencies unchanged; passing acceptance is not a clean security audit.
@@ -0,0 +1,23 @@
1
+ # v0.2.4: Stream large workspace snapshots
2
+
3
+ Workspace snapshot capture no longer buffers the compressed archive in child
4
+ process stdout. Archives stream to disk, pass extraction and tree-digest checks,
5
+ and publish atomically. Qualification verifies snapshot integrity in chunks.
6
+
7
+ The compressed snapshot limit increases from 128 MiB to 1 GiB. Output above
8
+ that limit fails explicitly and retains partial evidence. The 64 MiB patch
9
+ limit, capture exclusions, archive format, and portable-export policy are
10
+ unchanged. No runtime pins or dependencies change.
11
+
12
+ ## Verification
13
+
14
+ Regression tests cover a 129 MiB incompressible snapshot, streamed-write failure
15
+ cleanup, no-clobber publication, and bounded integrity checks. A retained real
16
+ workspace that failed on v0.2.3 was captured separately as a 605,143,040-byte
17
+ snapshot, with its extracted tree digest verified. Original evidence was not
18
+ rewritten.
19
+
20
+ Run `npm ci` and `npm run acceptance` on Node 24.19.0 for the full suite,
21
+ documentation and package checks, and two byte-identical package builds.
22
+ See [known limitations](KNOWN_LIMITATIONS.md) and the
23
+ [reproducibility manifest](reproducibility.json).
@@ -0,0 +1,74 @@
1
+ {
2
+ "schemaVersion": "ebo.release-reproducibility/v1",
3
+ "release": {
4
+ "name": "engineering-behavior-observatory",
5
+ "version": "0.2.4"
6
+ },
7
+ "runtime": {
8
+ "node": "24.19.0",
9
+ "claudeAgentSdk": "0.3.258",
10
+ "openhandsAgentServer": "1.46.0",
11
+ "deepseekClient": "0.1.1-rc.2",
12
+ "deepseekProtocol": "0.1.1-rc.2",
13
+ "deepseekRuntime": "0.1.1-rc.2",
14
+ "codexAppServer": "0.153.4",
15
+ "grafana": "13.2.0",
16
+ "grafanaInfinity": "4.0.0",
17
+ "piSdk": "0.85.1",
18
+ "cursorSdk": "1.0.31"
19
+ },
20
+ "commands": [
21
+ "npm ci",
22
+ "npm run acceptance"
23
+ ],
24
+ "fixtureCoverage": {
25
+ "agent-sdk": "frozen queue entry through qualified capture, approved export, normalization, configurable judging, review, aggregation, and Atlas",
26
+ "openhands-agent-server": "pinned REST/WebSocket stream-final reconciliation, workspace/outcome evidence, partial capture, and retained evaluation",
27
+ "deepseek-harness": "official-client JSON-RPC composition, receipt-to-idle completion, stderr, interruption, shutdown, swaps, and retained evaluation",
28
+ "codex-app-server": "owned stdio lifecycle, full history, OTLP, interruption/failure, export, and retained evaluation; configurable workspace-write networking with explicit offline queue propagation",
29
+ "acceptance-cases": "seeded ordering, secret scanning, partial attempts, native references, abstention, disputes, denominators, and unsupported comparisons",
30
+ "pi-sdk": "frozen queue, passive hooks, durable session, partial cleanup, export, retained normalization and evaluation",
31
+ "cursor-sdk": "frozen queue, official store, callbacks/history, bounded cancellation, export, retained normalization and evaluation",
32
+ "workspace-capture": "streamed snapshot capture, atomic publication and qualification beyond 128 MiB; bounded 1 GiB compressed output; retained error and partial evidence"
33
+ },
34
+ "determinism": {
35
+ "stable": [
36
+ "task, run, attempt, event, assertion, aggregate, and Atlas case identities",
37
+ "fixture, native-reference, normalized-dataset, source/cohort, archive, and package digests",
38
+ "seeded queue and review-sample ordering"
39
+ ],
40
+ "excluded": [
41
+ "native and EBO observation timestamps",
42
+ "lifecycle start/finish timestamps",
43
+ "provider timing and usage",
44
+ "temporary workspace and output paths"
45
+ ]
46
+ },
47
+ "fixtures": {
48
+ "tests/fixtures/task-packet.valid.v1.json": "ac2ef1043c0cc16bf4c21d05c1dc880ca1b4b8cc2f9ea7653328e91f0b0e2681",
49
+ "test/fixtures/agent-sdk-normalizer/complete.input.json": "7285dee088517e5de38ceda84fc7c58d9303949c7448ab3b5e10a5a2d2ebdfe3",
50
+ "test/fixtures/agent-sdk-normalizer/complete.expected.jsonl": "d32fa3a013bc179f1673f7e1a89b8a721bb8ddaf63eba5bc8a3f14f1cc148ed8",
51
+ "contracts/openhands-agent-server-v1.44.1.json": "e7a07977688a0703b15751a8a1ab17f29be45f9fdf438017e0dbcbc49cca37b0",
52
+ "contracts/openhands-agent-server-v1.46.0.json": "455cdddd2c206cb2f20245c2189b92e773657ec14d651236baa532b2dbadeb28",
53
+ "test/fixtures/openhands/v1.44.1/streamed-events.json": "d751083ba2ef94c9865117db481cc2f4933772bbbbb6b9fcf60571563715c1ea",
54
+ "test/fixtures/openhands/v1.44.1/final-events.json": "551db9904e860004d604c923692b5c5d3aaee966220492214c60003d2df415b6",
55
+ "test/fixtures/deepseek/golden-success.json": "f3867d38169bf0e28f64aba1dd60553f21cf07f41f81b4bad48261e49fa3ca88",
56
+ "test/fixtures/deepseek/golden-interrupted.json": "79e1f8404df1ee3b562d0bdbc0b2ee712ffc6d5774992b92970814ea815677b7",
57
+ "test/fixtures/deepseek/compositions/minimal/composition.json": "72fe4368e105f1bad82b7fdf8380e92c9fa689d8985f6ab91f534f0baf3eda02",
58
+ "contracts/codex-app-server-0.153.4/manifest.json": "e62281ff5e5d1cc71d07763b85e997ccc9ddbad9aa82a469da4181ee4b1890a8",
59
+ "test/fixtures/codex/legacy-0.150.1.dataset.json": "f71906b009e546afbf3b4e79396f223181382f3b3d8795e9cfcd032398a9d6bf",
60
+ "test/fixtures/behavior-assertions/abstained.json": "7f1962864f664c794925a67d93074e7b150af0f57c58041a81e062990d641197",
61
+ "test/fixtures/behavior-assertions/disputed.review.json": "450d3d9efe7feefaf7500b3ad20ab2803b412c7e53b91763125e15a83be87981",
62
+ "test/fixtures/comparison/exact.json": "cafa68e94b8d50dc308ddcfc2e73aa139c828b4ecd1e1c762bbc51bf1b44501e",
63
+ "test/fixtures/structural-observations/golden.json": "bad71a3a93524b76c0a2fdbb58e98e1e65be46cae14acf18ac89d9ba76e03e8a",
64
+ "test/atlas-fixture.ts": "9f03ce9cbcd0e5d3e3a5916866554ceb1c21f1c8712e58529499c1ea499f048d",
65
+ "test/pi.test.ts": "a00997c5617ca178e19b25b0d483cbdce8ce7c306d4c871557945b31a2e657cf",
66
+ "test/cursor-sdk.test.ts": "753094f127b11dc392ec2244c9edddabec27aa94347bb6159a292129ed2a2822",
67
+ "examples/cursor-sdk/README.md": "a2cf481123e7f3deb1c5a682ba208a775c3f91c35e1a6c1a428c517c79692618",
68
+ "examples/cursor-sdk/capture-profile.json": "d1a5787875daf5523b3783f259b891c256a904cbb10279d5be214af846305a2d",
69
+ "examples/cursor-sdk/harness.json": "2f1da6f2b17258d0ee59431f687696901ce3692589175d182f3a4bbe8dd4d689",
70
+ "examples/cursor-sdk/model.json": "ad8f31ff5d2387c02d79226679022358afafc61ba5dda73040e3f754530a230a",
71
+ "examples/cursor-sdk/native-limits.json": "40fe451f56b0863e79b42f5c85defbce678e1dc90dd248d15c430db176d71511",
72
+ "examples/cursor-sdk/native-tool-policy.json": "0fef98b36cd39de82914c5b068022c1335dadda24b7d6e7df398b2430e6a16e0"
73
+ }
74
+ }
package/release/README.md CHANGED
@@ -5,6 +5,7 @@ For downloads and published artifacts, see
5
5
 
6
6
  | Version | Changes and verification | Support boundary |
7
7
  | :--- | :--- | :--- |
8
+ | [0.2.4](0.2.4/README.md) | Stream workspace snapshots and integrity checks; support archives up to 1 GiB | [Known limitations](0.2.4/KNOWN_LIMITATIONS.md) |
8
9
  | [0.2.3](0.2.3/README.md) | Retain workspace capture errors in partial-bundle reports | [Known limitations](0.2.3/KNOWN_LIMITATIONS.md) |
9
10
  | [0.2.2](0.2.2/README.md) | Configurable Codex tool networking, enabled by default for workspace-write | [Known limitations](0.2.2/KNOWN_LIMITATIONS.md) |
10
11
  | [0.2.1](0.2.1/README.md) | Documentation redesign, Apache-2.0, and npm distribution setup | [Known limitations](0.2.1/KNOWN_LIMITATIONS.md) |