Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ vendor/
scratch/
node_modules/
CHANGELOG.md
# Intentionally invalid source: the broken-toolchain eval fixture.
tests/fixtures/broken-toolchain/

17 changes: 17 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ pathIgnorePatterns = [
"tests/fixtures/**",
"eval/tasks/**",
"tmp/**",
# Bundles vendor/intx-storage-isogit's browser entry point with Bun.build,
# under the "intx-src" export condition that resolves @intx/log and
# @intx/mime to ./src/*.ts. Upstream's own monorepo vendors those two
# packages too, so the condition finds real source; here they stay on
# published npm (dist-only, no src/), so the condition matches an export
# key whose target file doesn't exist. Not a vendored-code defect — see
# docs/VENDORING.md.
"vendor/intx-storage-isogit/src/browser-bundle.test.ts",
]
47 changes: 45 additions & 2 deletions docs/VENDORING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,21 @@ points straight at `./src/*.ts` files rather than a `dist/` build.
| Package | Vendor path | License | Synced from upstream commit | Retrieved | Local patches |
| ---------------------- | ----------------------------- | ------------- | ------------------------------------------ | ---------- | -------------------------------------------- |
| `@intx/inference` | `vendor/intx-inference/` | LGPL-2.1-only | `ad0f99e7977b3ad4f28d8cc8d446ac52a4a2d685` | 2026-08-10 | Yes — see `vendor/intx-inference/PATCHES.md` |
| `@intx/types` | `vendor/intx-types/` | LGPL-2.1-only | `ad0f99e7977b3ad4f28d8cc8d446ac52a4a2d685` | 2026-08-10 | None — verbatim |
| `@intx/storage-isogit` | `vendor/intx-storage-isogit/` | LGPL-2.1-only | `ad0f99e7977b3ad4f28d8cc8d446ac52a4a2d685` | 2026-08-10 | None — verbatim |
| `@intx/types` | `vendor/intx-types/` | LGPL-2.1-only | `55c4431e60cc97dae2f63bfd52de56166e42b13b` | 2026-08-22 | None — verbatim |
| `@intx/storage-isogit` | `vendor/intx-storage-isogit/` | LGPL-2.1-only | `55c4431e60cc97dae2f63bfd52de56166e42b13b` | 2026-08-22 | None — verbatim |

`@intx/inference` is temporarily behind `@intx/types` and
`@intx/storage-isogit` as of the 2026-08-22 sync: this is stage 2 of a
staged re-vendor (the two lower-coupling packages first), with
`@intx/inference`'s re-sync tracked separately as stage 3. The
cross-package coupling described below did not regress in this gap — the
`PendingOperation` shape both packages already shared did not change
between `ad0f99e7` and `55c4431e` (`vendor/intx-types/src/runtime.ts` and
`src/index.ts` are byte-identical across that range), so `@intx/inference`
built against the older `@intx/types` still typechecks against the newer
one. A later re-sync of `@intx/inference` should still move all three
together per the coupling rule below, since that check only holds for
this specific gap, not in general.

The license column records what each package declares in its own
`package.json`; the corresponding `LICENSE` file travels with every vendored
Expand Down Expand Up @@ -115,6 +128,36 @@ those packages now resolves to the single root instance. As of this sync,
no modifications. A diff against any later upstream checkout at the same
paths will show 100% upstream-authored lines.

## Notable upstream shape changes carried by the 2026-08-22 sync

Upstream commit `5f798bea` ("Inject the runtime into iso-git storage")
split `@intx/storage-isogit`'s entry point: the package root (`.`) no
longer exports a bindable `createIsogitStore` free function, and callers
now pick a runtime-bound entry point instead — `./node` (backed by
`createNodeIsogitRuntime`) or `./browser`. `vendor/intx-storage-isogit/`'s
`package.json` `exports` map picks up both new subpaths verbatim from
upstream. The one first-party consumer,
`src/session/optimized-context-store.ts`, now imports `createIsogitStore`
from `@intx/storage-isogit/node` instead of the package root — the
`./node` re-export has the identical `(dir, signer?, gcPolicy?)` signature
the old root export had, so this is an import-path change, not a
behavioral one. The package also picked up two new dependencies
(`@isomorphic-git/lightning-fs`, `buffer`, both used only by the new
`./browser` runtime, which nothing here imports) and a new
`@intx/crypto` dev dependency for its own test suite, pinned to `0.2.2`
like the existing `@intx/mime` dev dependency on `@intx/inference` — the
same "stay on published npm for a package we don't vendor" pattern.

One new upstream test, `browser-bundle.test.ts`, is excluded via
`bunfig.toml`'s `pathIgnorePatterns`. It bundles `browser.ts` with
`Bun.build` under the `intx-src` export condition, which resolves
`@intx/log` and `@intx/mime` to `./src/*.ts` — real files in upstream's
own monorepo, where those two are also vendored source. Here they remain
published npm installs (`dist/` only, no `src/`), so the condition
matches an export key whose target does not exist and the bundle fails
to resolve. This is an environment gap, not a defect in the vendored
code; re-check it whenever `@intx/log` or `@intx/mime` get vendored too.

`@intx/inference` carries local patches — real fixes not yet present
upstream, not workarounds for something upstream has since fixed. Every
patched location carries a one-line comment naming its site-specific entry
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default tseslint.config(
"node_modules/**",
"**/node_modules/**",
// Intentionally invalid source: the broken-toolchain eval fixture.

"tests/fixtures/broken-toolchain/**",
],
},
Expand Down
2 changes: 1 addition & 1 deletion src/session/optimized-context-store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { type } from "arktype";
import { createIsogitStore } from "@intx/storage-isogit";
import { createIsogitStore } from "@intx/storage-isogit/node";
import {
ContentBlock,
type ConnectorThreadState,
Expand Down
68 changes: 66 additions & 2 deletions vendor/intx-storage-isogit/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# @intx/storage-isogit

Isomorphic-git backed implementation of `ContextStore` and
`AuditStore`. Each agent gets its own git repository on disk;
`AuditStore`. Each agent gets its own git repository in the host filesystem;
inference state lives on a working branch, the tool-authorization
audit log lives on its own branch, and mail history lives in a
dedicated audit store that commits each inbound and outbound
Expand All @@ -12,17 +12,81 @@ Consumed by `@intx/agent` for in-process persistence, and by
repositories that move between the hub and the sidecar as packs.

```ts
import { createIsogitStore } from "@intx/storage-isogit";
import { createIsogitStore } from "@intx/storage-isogit/node";

const store = await createIsogitStore("./tmp/agent-repo", signer);

// store implements both ContextStore and AuditStore -- hand it to
// the inference and tool layers as appropriate.
```

Node and Bun consumers use the `/node` entry point above. The package root is
runtime-neutral and exports `createIsogitStorage(runtime)` for hosts that
provide their own filesystem:

```ts
import type { FsClient } from "isomorphic-git";
import { createIsogitStorage } from "@intx/storage-isogit";

type IsogitRuntime = {
fs: FsClient;
rename(oldPath: string, newPath: string): Promise<void>;
path: {
join(...parts: string[]): string;
relative(from: string, to: string): string;
resolve(filepath: string): string;
};
flush?: () => Promise<void>;
};
```

The storage layer derives its other filesystem operations from `FsClient`,
including recursive directory creation and removal, existence checks, and
UTF-8 text reads. `rename` remains explicit because completed packs require an
atomic publish primitive. Persistent backends can implement `flush` to make
completed mutations durable before the API call resolves.

Audit and error record paths are append-only. Repeating a byte-identical
record is an idempotent retry, including after an uncertain persistence
failure; reusing the same path for different content is rejected. Mail commits
reconcile an uncertain ref publication before the same store accepts another
message, so a committed message cannot have its ordinal reused.

Browsers can use the included LightningFS IndexedDB adapter:

```ts
import { createBrowserIsogitStorage } from "@intx/storage-isogit/browser";

const storage = createBrowserIsogitStorage("agent-storage");
const store = await storage.createIsogitStore("/agents/example");
```

The browser adapter supplies isomorphic-git's Buffer compatibility, atomic
rename, POSIX path operations, and IndexedDB-backed file storage. Each call to
`createBrowserIsogitStorage(name)` claims a fresh volume and clears any prior
contents for that name. Call it exactly once per name during a page or worker
lifetime, then pass the returned storage object to every consumer. Reusing the
same name from another factory call, bundle, tab, or worker is unsupported and
may clear active data.

Browser storage is intentionally disposable: a page reload, tab close, or
worker restart ends the logical session, and the next owner starts from an
empty volume instead of attempting recovery. `flush()` writes the current
filesystem state to IndexedDB, but does not promise that state will survive a
new owner. IndexedDB is the storage medium, not a restart-persistence
guarantee. Use absolute repository paths; relative browser paths resolve from
`/`. Applications that require restart persistence must inject a durable
runtime through the package root and own that runtime's recovery guarantees.

The pack-send and pack-receive helpers (`createDeployPack`,
`createNegotiatedPack`, `applyPack`, `receivePackObjects`) produce
and consume the wire bytes that `@intx/pack-transport` chunks
across the WebSocket. A `CommitSigner` is optional but required
when the consumer needs every commit to carry a verifiable
signature.

Pack receivers flush an accepted pack before checkout or ref promotion. If a
later ref or persistence operation fails, the call rejects but retains the
pack because the ref update may already be observable. Callers must treat that
error as an uncertain promotion outcome rather than assuming the ref is
unchanged; whichever ref value is visible remains backed by durable objects.
13 changes: 13 additions & 0 deletions vendor/intx-storage-isogit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,26 @@
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
},
"./node": {
"types": "./src/node.ts",
"default": "./src/node.ts"
},
"./browser": {
"types": "./src/browser.ts",
"default": "./src/browser.ts"
}
},
"dependencies": {
"@intx/log": "0.2.2",
"@intx/mime": "0.2.2",
"@intx/types": "workspace:*",
"@isomorphic-git/lightning-fs": "4.7.0",
"arktype": "catalog:",
"buffer": "^6.0.3",
"isomorphic-git": "catalog:"
},
"devDependencies": {
"@intx/crypto": "0.2.2"
}
}
25 changes: 25 additions & 0 deletions vendor/intx-storage-isogit/src/browser-bundle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { expect, test } from "bun:test";
import path from "node:path";

test("browser entry bundles without Node filesystem or path modules", async () => {
const result = await Bun.build({
entrypoints: [path.join(import.meta.dir, "browser.ts")],
target: "browser",
format: "esm",
conditions: ["intx-src"],
});
if (!result.success) {
throw new Error(
result.logs
.map((log) => (log instanceof Error ? log.message : String(log)))
.join("\n"),
);
}
const output = result.outputs[0];
if (output === undefined) {
throw new Error("Browser storage bundle produced no output");
}
const bundle = await output.text();
expect(bundle).not.toContain('from "node:fs"');
expect(bundle).not.toContain('from "node:path"');
});
34 changes: 34 additions & 0 deletions vendor/intx-storage-isogit/src/browser-path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, test } from "bun:test";
import path from "node:path";

import { browserPath } from "./browser-path";

describe("browserPath", () => {
test.each([
[[], "."],
[["/repo", ".git", "objects"], "/repo/.git/objects"],
[["repo", "state", "..", "audit"], "repo/audit"],
[["/", "repo", "//state"], "/repo/state"],
] as const)("join(%j)", (parts, expected) => {
expect(browserPath.join(...parts)).toBe(expected);
expect(browserPath.join(...parts)).toBe(path.posix.join(...parts));
});

test.each([
["/repo", "/repo/.git/objects", ".git/objects"],
["/repo/state", "/repo/audit", "../audit"],
["/repo", "/repo", ""],
["repo", "other/file", "../other/file"],
])("relative(%s, %s)", (from, to, expected) => {
expect(browserPath.relative(from, to)).toBe(expected);
});

test.each([
["/repo/../state", "/state"],
["repo/./state", "/repo/state"],
["/../../repo", "/repo"],
["", "/"],
])("resolve(%s)", (filepath, expected) => {
expect(browserPath.resolve(filepath)).toBe(expected);
});
});
55 changes: 55 additions & 0 deletions vendor/intx-storage-isogit/src/browser-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { IsogitPath } from "./runtime";

function normalize(filepath: string, absolute: boolean): string {
const segments: string[] = [];
for (const segment of filepath.split("/")) {
if (segment === "" || segment === ".") {
continue;
}
if (segment === "..") {
if (segments.length > 0 && segments.at(-1) !== "..") {
segments.pop();
} else if (!absolute) {
segments.push(segment);
}
continue;
}
segments.push(segment);
}

if (absolute) {
return segments.length === 0 ? "/" : `/${segments.join("/")}`;
}
return segments.length === 0 ? "." : segments.join("/");
}

function resolve(filepath: string): string {
return normalize(filepath.startsWith("/") ? filepath : `/${filepath}`, true);
}

function join(...parts: string[]): string {
if (parts.length === 0) {
return ".";
}
const combined = parts.join("/");
return normalize(combined, combined.startsWith("/"));
}

function relative(from: string, to: string): string {
const fromSegments = resolve(from).split("/").filter(Boolean);
const toSegments = resolve(to).split("/").filter(Boolean);
let shared = 0;
while (
shared < fromSegments.length &&
shared < toSegments.length &&
fromSegments[shared] === toSegments[shared]
) {
shared += 1;
}
return [
...fromSegments.slice(shared).map(() => ".."),
...toSegments.slice(shared),
].join("/");
}

export const browserPath: IsogitPath = { join, relative, resolve };
Loading
Loading