From a16734248a7b26c4f27fd74a3dd5057646fa7152 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:17:29 +0000 Subject: [PATCH 1/4] fix(docs): validate generated markdown links --- .agents/setup | 15 ++ .github/workflows/ci.yml | 13 ++ CLAUDE.md | 6 +- justfile | 9 ++ lychee.toml | 17 +++ scripts/check-markdown-links.ts | 144 ++++++++++++++++++ website/package.json | 2 +- website/public/docs/docs/authentication.md | 2 +- website/public/docs/docs/debugging.md | 2 +- website/scripts/generate-page-markdown.mjs | 60 ++++++++ .../src/content/docs/docs/architecture.mdx | 2 +- .../src/content/docs/docs/authentication.mdx | 4 +- website/src/content/docs/docs/debugging.mdx | 2 +- .../docs/docs/frameworks/vercel-eve.mdx | 4 +- website/src/content/docs/docs/quickstart.mdx | 2 +- website/src/content/docs/docs/software.mdx | 2 +- 16 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 lychee.toml create mode 100644 scripts/check-markdown-links.ts create mode 100644 website/scripts/generate-page-markdown.mjs diff --git a/.agents/setup b/.agents/setup index 06e65c7537..dbd128f902 100755 --- a/.agents/setup +++ b/.agents/setup @@ -5,6 +5,8 @@ readonly pnpm_version="10.13.1" readonly docs_theme_revision="450c498555135098c6a927adfdf13458be9be22a" readonly docs_theme_icons_revision="71c3e482fe43e4cbc4f4327a26c8899b6bd4aaa7" readonly docs_theme_dir="website/vendor/theme" +readonly lychee_version="0.24.2" +readonly lychee_linux_x64_sha256="73657a111819a30c47c08352896796f23d64e4eb2b3ed39b6d32149241566fc5" if [[ "$(node --version | cut -d. -f1)" != "v24" ]]; then echo "Installing Node.js 24..." @@ -18,6 +20,19 @@ if ! command -v pnpm >/dev/null 2>&1 || [[ "$(pnpm --version)" != "$pnpm_version npm install --global "pnpm@${pnpm_version}" fi +if ! command -v lychee >/dev/null 2>&1 || [[ "$(lychee --version)" != "lychee ${lychee_version}" ]]; then + echo "Installing lychee ${lychee_version}..." + lychee_archive="$(mktemp)" + lychee_dir="$(mktemp -d)" + curl -fsSL -o "$lychee_archive" \ + "https://github.com/lycheeverse/lychee/releases/download/lychee-v${lychee_version}/lychee-x86_64-unknown-linux-musl.tar.gz" + echo "${lychee_linux_x64_sha256} ${lychee_archive}" | sha256sum -c - + tar -xzf "$lychee_archive" -C "$lychee_dir" + mkdir -p "$HOME/.local/bin" + install -m 0755 "$(find "$lychee_dir" -type f -name lychee -print -quit)" "$HOME/.local/bin/lychee" + rm -rf "$lychee_archive" "$lychee_dir" +fi + if [[ ! -f "${docs_theme_dir}/package.json" || ! -f "${docs_theme_dir}/vendor/icons/dist/index.js" ]]; then tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b412b54bb0..10bcba67d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,15 @@ jobs: rm -rf website/vendor/theme && mkdir -p website/vendor cp -r /tmp/docs-theme/packages/theme website/vendor/theme - run: pnpm install --frozen-lockfile + - name: Install lychee + run: | + version=0.24.2 + archive=/tmp/lychee.tar.gz + curl -fsSL -o "$archive" \ + "https://github.com/lycheeverse/lychee/releases/download/lychee-v${version}/lychee-x86_64-unknown-linux-musl.tar.gz" + echo "73657a111819a30c47c08352896796f23d64e4eb2b3ed39b6d32149241566fc5 $archive" | sha256sum -c - + tar -xzf "$archive" -C /tmp + sudo install -m 0755 /tmp/lychee-x86_64-unknown-linux-musl/lychee /usr/local/bin/lychee - run: | # Browser runtime sources remain in-tree, but are intentionally disabled # until their sidecar/reactor architecture has a separate approved design. @@ -60,6 +69,10 @@ jobs: --filter='!@rivet-dev/agentos-runtime-browser' \ --filter='!@rivet-dev/agentos-playground' fi + - name: Check documentation links and generated Markdown assets + run: | + pnpm --dir website build + pnpm exec tsx scripts/check-markdown-links.ts --built-site website/dist # The Codex adapter is ordinary TypeScript, while its executable is a # reproducibly built 56 MB WASI artifact. Keep the cheap PR lane source-only; # nightly and publish workflows build the pinned executable explicitly. diff --git a/CLAUDE.md b/CLAUDE.md index f4600d3f00..686e985f9b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -237,7 +237,11 @@ custom host-syscall imports. Treat that target as **native POSIX**; - Runnable docs code must come from real checked example files via the docs theme `` mechanism. Inline code is fine only for shell commands, config fragments, or non-runnable examples. -- Validate docs changes with `pnpm --dir website build` when the site changes. +- Whenever website, docs, README, or other Markdown content changes, run + `just check-site-links` before submitting the work. It builds the website and + verifies local and external source links plus client-generated Markdown + assets such as `/docs/apps.md`. Use `just check-site-links --no-external` + only for fast iteration, not as the final check. ## Tests diff --git a/justfile b/justfile index d4c518788b..e71103577d 100644 --- a/justfile +++ b/justfile @@ -244,6 +244,15 @@ docs: docs-build: pnpm --filter @rivet-dev/agentos-website build +# Check links in authored Markdown/MDX. Pass --no-external for a fast local-only check. +check-markdown-links *args: + pnpm exec tsx scripts/check-markdown-links.ts "$@" + +# Build the website, then verify client-generated Markdown URLs and source links. +check-site-links *args: + pnpm --dir website build + pnpm exec tsx scripts/check-markdown-links.ts --built-site website/dist "$@" + test-bounded cmd='pnpm test': #!/usr/bin/env bash set -euo pipefail diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 0000000000..c29f073641 --- /dev/null +++ b/lychee.toml @@ -0,0 +1,17 @@ +# Parsing and HTTP behavior are owned by lychee. The TypeScript launcher adds +# checkout-specific remaps for the website's /docs and /images routes. +no_progress = true +format = "detailed" +include_fragments = "anchor-only" +fallback_extensions = ["mdx", "md"] + +timeout = 10 +max_retries = 1 +max_concurrency = 16 +host_concurrency = 4 +method = "get" +accept = ["100..=103", "200..=299", "401", "403", "429"] +user_agent = "agentOS-markdown-link-check/1.0" + +include_mail = false +exclude_all_private = true diff --git a/scripts/check-markdown-links.ts b/scripts/check-markdown-links.ts new file mode 100644 index 0000000000..58c1f8eaee --- /dev/null +++ b/scripts/check-markdown-links.ts @@ -0,0 +1,144 @@ +#!/usr/bin/env tsx + +import { execFileSync, spawnSync } from "node:child_process"; +import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { dirname, relative, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); + +function usage(): never { + console.error( + "Usage: pnpm exec tsx scripts/check-markdown-links.ts [--no-external] [--built-site path] [path ...]", + ); + process.exit(2); +} + +function sourceMarkdownFiles(requestedPaths: string[]): string[] { + const output = execFileSync( + "git", + ["ls-files", "--cached", "--others", "--exclude-standard", "--", "*.md", "*.mdx"], + { cwd: repoRoot, encoding: "utf8" }, + ); + const requested = requestedPaths.map((path) => resolve(repoRoot, path)); + const generatedDocs = resolve(repoRoot, "website/public/docs"); + return output + .split("\n") + .filter(Boolean) + .map((path) => resolve(repoRoot, path)) + .filter((path) => path !== generatedDocs && !path.startsWith(`${generatedDocs}/`)) + .filter( + (path) => + requested.length === 0 || + requested.some((root) => path === root || path.startsWith(`${root}/`)), + ) + .map((path) => relative(repoRoot, path)) + .sort(); +} + +function fileUrl(path: string): string { + return pathToFileURL(path).href.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function checkBuiltMarkdownAssets(siteDir: string): boolean { + const failures: string[] = []; + const visit = (directory: string) => { + for (const entry of readdirSync(directory, { withFileTypes: true })) { + const path = resolve(directory, entry.name); + if (entry.isDirectory()) { + visit(path); + continue; + } + if (entry.name !== "index.html") continue; + const html = readFileSync(path, "utf8"); + const island = html.match( + /component-export="DocsPageDropdown"[^>]*\sprops="([^"]+)"/, + ); + if (!island) continue; + const props = JSON.parse( + island[1].replace(/"/g, '"').replace(/&/g, "&"), + ) as { markdownPath?: [number, string] }; + const advertisedPath = props.markdownPath?.[1]; + if (!advertisedPath) { + failures.push(`${relative(repoRoot, path)}: missing markdownPath prop`); + continue; + } + const markdownAsset = resolve(siteDir, `${advertisedPath}.md`); + if ( + !markdownAsset.startsWith(`${siteDir}/`) || + !existsSync(markdownAsset) + ) { + failures.push(`/${advertisedPath}.md`); + } + } + }; + visit(siteDir); + for (const path of failures.sort()) { + console.error(`Built docs page points to missing Markdown asset: ${path}`); + } + if (failures.length > 0) { + console.error(`\nFound ${failures.length} missing built Markdown assets.\n`); + } + return failures.length === 0; +} + +function main() { + let offline = false; + let builtSite: string | undefined; + const requestedPaths: string[] = []; + const argv = process.argv.slice(2); + for (let index = 0; index < argv.length; index++) { + const arg = argv[index]; + if (arg === "--no-external") offline = true; + else if (arg === "--built-site") { + const path = argv[++index]; + if (!path) usage(); + builtSite = resolve(repoRoot, path); + } + else if (arg === "--help" || arg === "-h" || arg.startsWith("-")) usage(); + else requestedPaths.push(arg); + } + + if (builtSite && !existsSync(builtSite)) { + throw new Error(`built site does not exist: ${relative(repoRoot, builtSite)}`); + } + const builtSitePassed = builtSite ? checkBuiltMarkdownAssets(builtSite) : true; + + const files = sourceMarkdownFiles(requestedPaths); + if (files.length === 0) throw new Error("no Markdown source files found"); + + const rootUrl = fileUrl(repoRoot); + const args = [ + "--config", + resolve(repoRoot, "lychee.toml"), + "--root-dir", + repoRoot, + "--remap", + `^${rootUrl}/docs/(.*)$ ${pathToFileURL(resolve(repoRoot, "website/src/content/docs/docs")).href}/$1`, + "--remap", + `^${rootUrl}/images/(.*)$ ${pathToFileURL(resolve(repoRoot, "website/public/images")).href}/$1`, + "--remap", + `^${rootUrl}/registry/?$ ${pathToFileURL(resolve(repoRoot, "website/src/pages/registry/index.astro")).href}`, + "--remap", + `^${rootUrl}/use-cases/?$ ${pathToFileURL(resolve(repoRoot, "website/src/pages/use-cases.astro")).href}`, + ]; + if (offline) args.push("--offline"); + args.push("--files-from", "-"); + + const result = spawnSync("lychee", args, { + cwd: repoRoot, + input: `${files.join("\n")}\n`, + stdio: ["pipe", "inherit", "inherit"], + }); + if (result.error) { + if ((result.error as NodeJS.ErrnoException).code === "ENOENT") { + throw new Error( + "lychee is not installed; see https://github.com/lycheeverse/lychee#installation", + ); + } + throw result.error; + } + process.exitCode = builtSitePassed ? (result.status ?? 1) : 1; +} + +main(); diff --git a/website/package.json b/website/package.json index 07e6853f0b..76c67a9292 100644 --- a/website/package.json +++ b/website/package.json @@ -10,7 +10,7 @@ "docs:gen:ts": "typedoc", "gen:registry": "node scripts/gen-registry.mjs", "check-types": "astro check", - "build": "node scripts/gen-registry.mjs && pnpm run docs:gen:ts && astro build", + "build": "node scripts/gen-registry.mjs && pnpm run docs:gen:ts && astro build && node scripts/generate-page-markdown.mjs", "preview": "astro preview", "astro": "astro", "docs:index": "node --input-type=module -e \"import('@rivet-dev/docs-theme/scripts/index-docs').then(m => m.indexDocs())\"" diff --git a/website/public/docs/docs/authentication.md b/website/public/docs/docs/authentication.md index 479317e0fa..a66402f440 100644 --- a/website/public/docs/docs/authentication.md +++ b/website/public/docs/docs/authentication.md @@ -13,4 +13,4 @@ agentOS uses the same authentication system as [Rivet Actors](/docs/actors/authe The server declares the credential shape and validates it in `onBeforeConnect` (throw to reject); the client passes credentials as `params`. -See [Actor Authentication](/docs/actors/authentication) for JWT validation, role-based access control, external auth providers, and token caching. \ No newline at end of file +See [Actor Authentication](/docs/actors/authentication) for JWT validation, role-based access control, external auth providers, and token caching. diff --git a/website/public/docs/docs/debugging.md b/website/public/docs/docs/debugging.md index 24b2aa208f..9174e90917 100644 --- a/website/public/docs/docs/debugging.md +++ b/website/public/docs/docs/debugging.md @@ -52,4 +52,4 @@ ts=2026-… level=debug message="querying: api.anthropic.com. A" Most sidecar log activity is on the session/ACP path. A bare `AgentOs.create()` or a single `exec()` emits almost nothing — create a session (and send a prompt) to see request-handling logs. -Use **agent logs** to see what the agent did (tool calls, model errors), and **runtime logs** to see what the sidecar did around it (request timing, DNS, lifecycle). \ No newline at end of file +Use **agent logs** to see what the agent did (tool calls, model errors), and **runtime logs** to see what the sidecar did around it (request timing, DNS, lifecycle). diff --git a/website/scripts/generate-page-markdown.mjs b/website/scripts/generate-page-markdown.mjs new file mode 100644 index 0000000000..1ad554b520 --- /dev/null +++ b/website/scripts/generate-page-markdown.mjs @@ -0,0 +1,60 @@ +import { + copyFileSync, + existsSync, + mkdirSync, + readFileSync, + readdirSync, + writeFileSync, +} from "node:fs"; +import { dirname, join, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const websiteRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const repoRoot = resolve(websiteRoot, ".."); +const distDir = join(websiteRoot, "dist"); + +function parseFrontmatter(raw) { + const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/); + if (!match) return { data: {}, body: raw }; + const data = {}; + for (const line of match[1].split(/\r?\n/)) { + const value = line.match(/^(\w+):\s*(.*)$/); + if (value) data[value[1]] = value[2].trim().replace(/^["']|["']$/g, ""); + } + return { data, body: match[2] }; +} + +function cookbookMarkdown(slug) { + const raw = readFileSync(join(repoRoot, "examples", slug, "README.md"), "utf8"); + const { data, body } = parseFrontmatter(raw); + const title = data.title || slug; + const description = data.description ? `\n${data.description}\n` : ""; + const withoutSource = body.replace(/\n#{1,6}\s+Source\b[\s\S]*$/i, "\n").trimEnd(); + return `# ${title}\n${description}\n${withoutSource}\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/${slug})\n`; +} + +function visit(directory) { + for (const entry of readdirSync(directory, { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isDirectory()) { + visit(path); + continue; + } + if (entry.name !== "index.html") continue; + const html = readFileSync(path, "utf8"); + if (!html.includes('component-export="DocsPageDropdown"')) continue; + + const route = relative(distDir, dirname(path)).replace(/\\/g, "/"); + const output = join(distDir, `${route}.md`); + mkdirSync(dirname(output), { recursive: true }); + if (route.startsWith("docs/")) { + const source = join(websiteRoot, "public", "docs", `${route}.md`); + if (!existsSync(source)) throw new Error(`missing generated Markdown source: ${source}`); + copyFileSync(source, output); + } else if (route.startsWith("cookbooks/")) { + writeFileSync(output, cookbookMarkdown(route.slice("cookbooks/".length))); + } + } +} + +visit(distDir); diff --git a/website/src/content/docs/docs/architecture.mdx b/website/src/content/docs/docs/architecture.mdx index 5df952c5f4..1ad023e0e7 100644 --- a/website/src/content/docs/docs/architecture.mdx +++ b/website/src/content/docs/docs/architecture.mdx @@ -261,7 +261,7 @@ The executor is the untrusted half of the VM. It runs the guest code and reaches ## Agents & sessions -An agent (such as [Pi](https://github.com/mariozechner/pi-coding-agent)) is just another guest process running inside a VM, behind the same boundary as any other code. A **session** keeps that agent alive across many prompts and streams its output back to your app as events. +An agent (such as [Pi](https://github.com/earendil-works/pi)) is just another guest process running inside a VM, behind the same boundary as any other code. A **session** keeps that agent alive across many prompts and streams its output back to your app as events. diff --git a/website/src/content/docs/docs/authentication.mdx b/website/src/content/docs/docs/authentication.mdx index 519ebe36c2..6d18211088 100644 --- a/website/src/content/docs/docs/authentication.mdx +++ b/website/src/content/docs/docs/authentication.mdx @@ -4,7 +4,7 @@ description: "Authenticate connections to agentOS actors using Rivet Actor conne skill: true --- -agentOS uses the same authentication system as [Rivet Actors](/docs/actors/authentication): clients send credentials as connection params, and you validate them server-side. +agentOS uses the same authentication system as [Rivet Actors](https://rivet.dev/docs/actors/authentication): clients send credentials as connection params, and you validate them server-side. - Clients pass credentials in `params` when they connect. - Validate them on the server in `onBeforeConnect` (throw to reject the connection), or extract user data into connection state with `createConnState` (read it in actions via `c.conn.state`). @@ -21,4 +21,4 @@ The server declares the credential shape and validates it in `onBeforeConnect` ( -See [Actor Authentication](/docs/actors/authentication) for JWT validation, role-based access control, external auth providers, and token caching. +See [Actor Authentication](https://rivet.dev/docs/actors/authentication) for JWT validation, role-based access control, external auth providers, and token caching. diff --git a/website/src/content/docs/docs/debugging.mdx b/website/src/content/docs/docs/debugging.mdx index 7e5513315b..dfadf95263 100644 --- a/website/src/content/docs/docs/debugging.mdx +++ b/website/src/content/docs/docs/debugging.mdx @@ -11,7 +11,7 @@ The coding agent (ACP adapter) runs as a process inside the VM and uses **stdout -It's a VM-level option covering every session's agent process; if omitted, chunks are written to the host `process.stderr` by default. See [Sessions → Agent logs](/docs/sessions#agent-logs). +It's a VM-level option covering every session's agent process; if omitted, chunks are written to the host `process.stderr` by default. See [Agent logs](#agent-logs-onagentstderr). ## Agent crashes (`onAgentExit`) diff --git a/website/src/content/docs/docs/frameworks/vercel-eve.mdx b/website/src/content/docs/docs/frameworks/vercel-eve.mdx index c810df4564..793e656328 100644 --- a/website/src/content/docs/docs/frameworks/vercel-eve.mdx +++ b/website/src/content/docs/docs/frameworks/vercel-eve.mdx @@ -39,7 +39,7 @@ npm add @rivet-dev/agentos @rivet-dev/agentos-eve @rivet-dev/vercel-world ``` - `@rivet-dev/agentos` and `@rivet-dev/agentos-eve`: Provide the agentOS VM and connect Eve's sandbox API to it. -- `@rivet-dev/vercel-world`: Runs Eve workflows on [Rivet World](https://workflow-sdk.dev/worlds). +- `@rivet-dev/vercel-world`: Runs Eve workflows on [Rivet World](https://workflow-sdk.dev/). @@ -139,7 +139,7 @@ See the `agentOS()` [configuration reference](/docs/core#configuration-reference Rivet World stores Eve workflow runs in Rivet Actors so they resume instead of restarting. -[Read the Rivet World documentation →](https://rivet.dev/docs/integrations/vercel-workflows) +[Read the Rivet World documentation →](https://rivet.dev/integrations/vercel-workflows) ## Advanced diff --git a/website/src/content/docs/docs/quickstart.mdx b/website/src/content/docs/docs/quickstart.mdx index c5dd0e4dee..4128381241 100644 --- a/website/src/content/docs/docs/quickstart.mdx +++ b/website/src/content/docs/docs/quickstart.mdx @@ -65,7 +65,7 @@ import { AGENT_PROMPT } from '../../../components/marketing/agentPrompt'; 1. **Install** - **@rivet-dev/agentos** — Actor framework with built-in persistence and orchestration - - **@agentos-software/pi** — [Pi](https://github.com/mariozechner/pi-coding-agent) coding agent. [Claude Code](/docs/agents/claude), [Codex](/docs/agents/codex), and [OpenCode](/docs/agents/opencode) install the same way. + - **@agentos-software/pi** — [Pi](https://github.com/earendil-works/pi) coding agent. [Claude Code](/docs/agents/claude), [Codex](/docs/agents/codex), and [OpenCode](/docs/agents/opencode) install the same way. ```bash npm install @rivet-dev/agentos @agentos-software/pi diff --git a/website/src/content/docs/docs/software.mdx b/website/src/content/docs/docs/software.mdx index 2d8f1c1aec..1b7c1b3bcc 100644 --- a/website/src/content/docs/docs/software.mdx +++ b/website/src/content/docs/docs/software.mdx @@ -30,4 +30,4 @@ Browse all available software packages on the [Registry](/registry). ## Custom Software -Package your own agents, command packages, and WASM commands. See [Software Definition](/docs/custom-software/definition) to define a package, and [Building Binaries](/docs/custom-software/building-wasm) to compile WASM commands from source in the [secure-exec registry](https://github.com/rivet-dev/secure-exec/tree/main/registry). +Package your own agents, command packages, and WASM commands. See [Software Definition](/docs/custom-software/definition) to define a package, and [Building Binaries](/docs/custom-software/building-wasm) to compile WASM commands from source in the [agentOS software registry](https://github.com/rivet-dev/agentos/tree/main/software). From 5783abedaa6c96bda24979764188702b09c1dae8 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:21:39 +0000 Subject: [PATCH 2/4] fix(ci): vendor generated docs icons --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10bcba67d9..7512f68cba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,9 @@ jobs: git -C /tmp/docs-theme checkout 450c498555135098c6a927adfdf13458be9be22a rm -rf website/vendor/theme && mkdir -p website/vendor cp -r /tmp/docs-theme/packages/theme website/vendor/theme + git -C /tmp/docs-theme checkout 71c3e482fe43e4cbc4f4327a26c8899b6bd4aaa7 + rm -rf website/vendor/theme/vendor/icons/dist + cp -r /tmp/docs-theme/packages/theme/vendor/icons/dist website/vendor/theme/vendor/icons/dist - run: pnpm install --frozen-lockfile - name: Install lychee run: | From 4cfe2380558be5ef8ef4fd7304ff399dcdb2a5f6 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:59:35 +0000 Subject: [PATCH 3/4] fix(ci): allow benchmark test homes --- scripts/check-layout.mjs | 1 + scripts/check-layout.test.mjs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/check-layout.mjs b/scripts/check-layout.mjs index ed56dcb00b..34388300e9 100644 --- a/scripts/check-layout.mjs +++ b/scripts/check-layout.mjs @@ -49,6 +49,7 @@ const allowedTestHomes = [ /^software\/[^/]+\/test\/.+\.test\.ts$/, /^toolchain\/conformance\/.+\.test\.ts$/, /^packages\/[^/]+\/tests\/.+\.test\.ts$/, + /^benchmarks\/[^/]+\/.+\.test\.ts$/, /^experiments\/[^/]+\/.+\.test\.ts$/, /^scripts\/.+\.test\.ts$/, ]; diff --git a/scripts/check-layout.test.mjs b/scripts/check-layout.test.mjs index 1fc8bb8a33..e802cca36c 100644 --- a/scripts/check-layout.test.mjs +++ b/scripts/check-layout.test.mjs @@ -8,7 +8,7 @@ import test from "node:test"; const script = join(dirname(fileURLToPath(import.meta.url)), "check-layout.mjs"); -test("allows experiment tests and ignores nested Claude worktrees", () => { +test("allows benchmark and experiment tests and ignores nested Claude worktrees", () => { const root = mkdtempSync(join(tmpdir(), "agentos-layout-")); try { const nestedTest = join( @@ -20,6 +20,9 @@ test("allows experiment tests and ignores nested Claude worktrees", () => { const experimentTest = join(root, "experiments/gigacode/gate.test.ts"); mkdirSync(dirname(experimentTest), { recursive: true }); writeFileSync(experimentTest, "export {};\n"); + const benchmarkTest = join(root, "benchmarks/agentos-apps/src/load.test.ts"); + mkdirSync(dirname(benchmarkTest), { recursive: true }); + writeFileSync(benchmarkTest, "export {};\n"); const bin = join(root, "bin"); mkdirSync(bin); From a917301f83ada5264750a8279854663c95059399 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:07:55 +0000 Subject: [PATCH 4/4] fix(core): expose positional file reads --- packages/core/src/agent-os.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core/src/agent-os.ts b/packages/core/src/agent-os.ts index 77be0da761..38998f8fff 100644 --- a/packages/core/src/agent-os.ts +++ b/packages/core/src/agent-os.ts @@ -3190,6 +3190,15 @@ export class AgentOs { return this.#kernel.readFile(path); } + async pread( + path: string, + offset: number, + length: number, + ): Promise { + this._assertSafeAbsolutePath(path); + return this._vfs().pread(path, offset, length); + } + async writeFile(path: string, content: string | Uint8Array): Promise { this._assertWritableAbsolutePath(path); return this.#kernel.writeFile(path, content);