From afea8354fbf8737930a568cf8c5fc6bc889b2405 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Thu, 25 Jun 2026 18:01:03 -0700 Subject: [PATCH] fix: stream ACP session updates live for Pi turns --- CLAUDE.md | 11 +- Cargo.lock | 37 +- Cargo.toml | 16 +- TODO.md | 2 +- crates/agentos-sidecar/src/acp_extension.rs | 62 +- examples/quickstart/package.json | 10 +- package.json | 12 +- packages/agentos-sandbox/package.json | 4 +- packages/agentos/package.json | 2 +- packages/core/CLAUDE.md | 12 +- packages/core/package.json | 52 +- packages/core/pnpm-lock.yaml | 20 +- packages/core/src/packages.ts | 13 +- .../core/tests/helpers/registry-commands.ts | 46 +- .../core/tests/session-update-live.test.ts | 216 + packages/core/vitest.config.ts | 2 +- packages/dev-shell/package.json | 2 +- packages/posix/package.json | 2 +- packages/python/package.json | 2 +- packages/shell/package.json | 20 +- packages/shell/src/main.ts | 23 +- pnpm-lock.yaml | 765 +--- pnpm-workspace.yaml | 1 - registry/.gitignore | 1 - registry/agent/claude/package.json | 33 - .../claude/scripts/build-patched-cli.mjs | 342 -- registry/agent/claude/src/adapter.ts | 1300 ------ registry/agent/claude/src/index.ts | 39 - registry/agent/claude/src/patched-cli.ts | 45 - .../agent/claude/tests/patched-cli.test.mjs | 119 - registry/agent/claude/tsconfig.json | 10 - registry/agent/codex/package.json | 27 - registry/agent/codex/src/index.ts | 3 - registry/agent/codex/tests/package.test.mjs | 19 - registry/agent/codex/tsconfig.json | 10 - registry/agent/opencode/package.json | 30 - .../opencode/scripts/build-opencode-acp.mjs | 3681 ----------------- registry/agent/opencode/src/adapter.ts | 27 - registry/agent/opencode/src/index.ts | 26 - .../agent/opencode/src/opencode-acp.mjs.d.ts | 14 - registry/agent/opencode/tsconfig.json | 10 - .../opencode/upstream/opencode-v1.3.13.patch | 251 -- registry/agent/pi-cli/package.json | 27 - registry/agent/pi-cli/src/index.ts | 26 - registry/agent/pi-cli/tsconfig.json | 10 - registry/agent/pi/package.json | 33 - .../pi/scripts/build-snapshot-bundle.mjs | 226 - registry/agent/pi/src/adapter.ts | 1506 ------- registry/agent/pi/src/index.ts | 25 - registry/agent/pi/src/snapshot-entry.ts | 63 - registry/agent/pi/tsconfig.json | 10 - registry/tests/helpers.ts | 117 - .../tests/kernel/bridge-child-process.test.ts | 719 ---- .../ci-wasm-artifact-availability.test.ts | 44 - .../kernel/cross-runtime-network.test.ts | 207 - .../tests/kernel/cross-runtime-pipes.test.ts | 57 - .../kernel/cross-runtime-terminal.test.ts | 267 -- .../kernel/ctrl-c-shell-behavior.test.ts | 94 - .../tests/kernel/dispose-behavior.test.ts | 78 - .../tests/kernel/e2e-concurrently.test.ts | 174 - .../tests/kernel/e2e-nextjs-build.test.ts | 122 - registry/tests/kernel/e2e-npm-install.test.ts | 101 - .../tests/kernel/e2e-npm-lifecycle.test.ts | 151 - registry/tests/kernel/e2e-npm-scripts.test.ts | 102 - registry/tests/kernel/e2e-npm-suite.test.ts | 344 -- .../tests/kernel/e2e-npm-version-init.test.ts | 69 - .../tests/kernel/e2e-npx-and-pipes.test.ts | 105 - .../tests/kernel/e2e-project-matrix.test.ts | 498 --- .../tests/kernel/error-propagation.test.ts | 75 - .../tests/kernel/exec-integration.test.ts | 149 - registry/tests/kernel/fd-inheritance.test.ts | 77 - registry/tests/kernel/helpers.ts | 101 - .../tests/kernel/module-resolution.test.ts | 107 - .../tests/kernel/node-binary-behavior.test.ts | 436 -- registry/tests/kernel/repro-npm-install.ts | 24 - registry/tests/kernel/shim-streaming.test.ts | 31 - .../tests/kernel/signal-forwarding.test.ts | 107 - registry/tests/kernel/tree-test.test.ts | 152 - registry/tests/kernel/vfs-consistency.test.ts | 112 - .../projects/astro-pass/astro.config.mjs | 6 - .../tests/projects/astro-pass/fixture.json | 4 - .../tests/projects/astro-pass/package.json | 17 - .../astro-pass/src/components/Counter.jsx | 8 - .../tests/projects/astro-pass/src/index.js | 71 - .../projects/astro-pass/src/pages/index.astro | 13 - .../tests/projects/axios-pass/fixture.json | 4 - .../tests/projects/axios-pass/package.json | 8 - .../tests/projects/axios-pass/src/index.js | 54 - .../tests/projects/bcryptjs-pass/fixture.json | 4 - .../tests/projects/bcryptjs-pass/package.json | 8 - .../projects/bcryptjs-pass/pnpm-lock.yaml | 22 - .../tests/projects/bcryptjs-pass/src/index.js | 26 - .../tests/projects/bun-layout-pass/bun.lock | 15 - .../projects/bun-layout-pass/fixture.json | 5 - .../projects/bun-layout-pass/package.json | 8 - .../projects/bun-layout-pass/src/index.js | 11 - .../tests/projects/chalk-pass/fixture.json | 4 - .../tests/projects/chalk-pass/package.json | 8 - .../tests/projects/chalk-pass/src/index.js | 27 - .../conditional-exports-pass/fixture.json | 5 - .../package-lock.json | 21 - .../conditional-exports-pass/package.json | 8 - .../cond-exports-lib/lib/feature-cjs.js | 2 - .../cond-exports-lib/lib/feature-default.js | 2 - .../packages/cond-exports-lib/lib/main-cjs.js | 2 - .../cond-exports-lib/lib/main-default.js | 2 - .../packages/cond-exports-lib/package.json | 14 - .../conditional-exports-pass/src/index.js | 13 - .../projects/crypto-random-pass/fixture.json | 4 - .../projects/crypto-random-pass/package.json | 5 - .../projects/crypto-random-pass/src/index.js | 15 - registry/tests/projects/dotenv-pass/.env | 1 - .../tests/projects/dotenv-pass/fixture.json | 4 - .../tests/projects/dotenv-pass/package.json | 8 - .../tests/projects/dotenv-pass/src/index.js | 12 - .../tests/projects/drizzle-pass/fixture.json | 4 - .../tests/projects/drizzle-pass/package.json | 8 - .../tests/projects/drizzle-pass/src/index.js | 45 - .../projects/esm-import-pass/fixture.json | 4 - .../projects/esm-import-pass/package.json | 8 - .../projects/esm-import-pass/src/index.js | 9 - .../tests/projects/express-pass/fixture.json | 4 - .../tests/projects/express-pass/package.json | 8 - .../projects/express-pass/pnpm-lock.yaml | 585 --- .../tests/projects/express-pass/src/index.js | 64 - .../tests/projects/fastify-pass/fixture.json | 4 - .../tests/projects/fastify-pass/package.json | 8 - .../projects/fastify-pass/pnpm-lock.yaml | 352 -- .../tests/projects/fastify-pass/src/index.js | 76 - .../fs-metadata-rename-pass/fixture.json | 4 - .../fs-metadata-rename-pass/package.json | 5 - .../fs-metadata-rename-pass/src/index.js | 33 - .../tests/projects/ioredis-pass/fixture.json | 4 - .../tests/projects/ioredis-pass/package.json | 8 - .../projects/ioredis-pass/pnpm-lock.yaml | 99 - .../tests/projects/ioredis-pass/src/index.js | 74 - .../projects/jsonwebtoken-pass/fixture.json | 4 - .../projects/jsonwebtoken-pass/package.json | 8 - .../projects/jsonwebtoken-pass/src/index.js | 32 - .../projects/lodash-es-pass/fixture.json | 4 - .../projects/lodash-es-pass/package.json | 8 - .../projects/lodash-es-pass/pnpm-lock.yaml | 22 - .../projects/lodash-es-pass/src/index.js | 31 - .../projects/module-access-pass/fixture.json | 4 - .../projects/module-access-pass/package.json | 8 - .../projects/module-access-pass/src/index.js | 6 - .../vendor/entry-lib/index.js | 6 - .../vendor/entry-lib/package.json | 8 - .../vendor/transitive-lib/index.js | 4 - .../vendor/transitive-lib/package.json | 5 - .../tests/projects/mysql2-pass/fixture.json | 4 - .../tests/projects/mysql2-pass/package.json | 8 - .../tests/projects/mysql2-pass/src/index.js | 173 - .../net-create-server-pass/fixture.json | 4 - .../net-create-server-pass/package.json | 5 - .../net-create-server-pass/src/index.js | 3 - registry/tests/projects/nextjs-pass/.babelrc | 3 - .../tests/projects/nextjs-pass/fixture.json | 4 - .../projects/nextjs-pass/next-wasm-shim.cjs | 4 - .../tests/projects/nextjs-pass/next.config.js | 12 - .../tests/projects/nextjs-pass/package.json | 12 - .../projects/nextjs-pass/pages/api/hello.js | 5 - .../tests/projects/nextjs-pass/pages/index.js | 7 - .../projects/nextjs-pass/run-next-build.cjs | 19 - .../tests/projects/nextjs-pass/src/index.js | 93 - .../projects/node-fetch-pass/fixture.json | 4 - .../projects/node-fetch-pass/package.json | 8 - .../projects/node-fetch-pass/src/index.js | 59 - .../projects/npm-layout-pass/fixture.json | 5 - .../npm-layout-pass/package-lock.json | 20 - .../projects/npm-layout-pass/package.json | 8 - .../projects/npm-layout-pass/src/index.js | 11 - .../projects/optional-deps-pass/fixture.json | 5 - .../optional-deps-pass/package-lock.json | 31 - .../projects/optional-deps-pass/package.json | 11 - .../projects/optional-deps-pass/src/index.js | 18 - .../projects/peer-deps-pass/fixture.json | 5 - .../projects/peer-deps-pass/package-lock.json | 34 - .../projects/peer-deps-pass/package.json | 9 - .../peer-deps-pass/packages/host/index.js | 3 - .../peer-deps-pass/packages/host/package.json | 5 - .../peer-deps-pass/packages/plugin/index.js | 8 - .../packages/plugin/package.json | 8 - .../projects/peer-deps-pass/src/index.js | 11 - registry/tests/projects/pg-pass/fixture.json | 4 - registry/tests/projects/pg-pass/package.json | 8 - .../tests/projects/pg-pass/pnpm-lock.yaml | 124 - registry/tests/projects/pg-pass/src/index.js | 37 - .../tests/projects/pino-pass/fixture.json | 4 - .../tests/projects/pino-pass/package.json | 8 - .../tests/projects/pino-pass/pnpm-lock.yaml | 106 - .../tests/projects/pino-pass/src/index.js | 68 - .../projects/pnpm-layout-pass/fixture.json | 5 - .../projects/pnpm-layout-pass/package.json | 8 - .../projects/pnpm-layout-pass/pnpm-lock.yaml | 23 - .../projects/pnpm-layout-pass/src/index.js | 11 - registry/tests/projects/rivetkit/fixture.json | 4 - registry/tests/projects/rivetkit/package.json | 8 - registry/tests/projects/rivetkit/src/index.js | 12 - .../rivetkit/vendor/rivetkit/package.json | 11 - .../tests/projects/semver-pass/fixture.json | 4 - .../tests/projects/semver-pass/package.json | 8 - .../tests/projects/semver-pass/src/index.js | 9 - .../projects/sse-streaming-pass/fixture.json | 4 - .../projects/sse-streaming-pass/package.json | 5 - .../projects/sse-streaming-pass/src/index.js | 128 - .../tests/projects/ssh2-pass/fixture.json | 4 - .../tests/projects/ssh2-pass/package.json | 8 - .../tests/projects/ssh2-pass/src/index.js | 28 - .../ssh2-sftp-client-pass/fixture.json | 4 - .../ssh2-sftp-client-pass/package.json | 8 - .../ssh2-sftp-client-pass/src/index.js | 26 - .../transitive-deps-pass/fixture.json | 5 - .../transitive-deps-pass/package-lock.json | 43 - .../transitive-deps-pass/package.json | 8 - .../packages/level-a/index.js | 12 - .../packages/level-a/package.json | 8 - .../packages/level-b/index.js | 12 - .../packages/level-b/package.json | 8 - .../packages/level-c/index.js | 9 - .../packages/level-c/package.json | 5 - .../transitive-deps-pass/src/index.js | 18 - .../tests/projects/uuid-pass/fixture.json | 4 - .../tests/projects/uuid-pass/package.json | 8 - .../tests/projects/uuid-pass/src/index.js | 23 - .../tests/projects/vite-pass/app/main.jsx | 8 - .../tests/projects/vite-pass/fixture.json | 4 - registry/tests/projects/vite-pass/index.html | 11 - .../tests/projects/vite-pass/package.json | 17 - .../tests/projects/vite-pass/src/index.js | 71 - .../tests/projects/vite-pass/vite.config.mjs | 6 - .../workspace-layout-pass/fixture.json | 5 - .../workspace-layout-pass/package.json | 7 - .../packages/app/package.json | 7 - .../packages/app/src/index.js | 11 - .../packages/lib/package.json | 5 - .../packages/lib/src/index.js | 11 - registry/tests/projects/ws-pass/fixture.json | 4 - .../tests/projects/ws-pass/package-lock.json | 34 - registry/tests/projects/ws-pass/package.json | 8 - registry/tests/projects/ws-pass/src/index.js | 97 - .../tests/projects/yaml-pass/fixture.json | 4 - .../tests/projects/yaml-pass/package.json | 8 - .../tests/projects/yaml-pass/src/index.js | 51 - .../yarn-berry-layout-pass/.yarnrc.yml | 1 - .../yarn-berry-layout-pass/fixture.json | 5 - .../yarn-berry-layout-pass/package.json | 9 - .../yarn-berry-layout-pass/src/index.js | 11 - .../projects/yarn-berry-layout-pass/yarn.lock | 21 - .../yarn-classic-layout-pass/fixture.json | 5 - .../yarn-classic-layout-pass/package.json | 8 - .../yarn-classic-layout-pass/src/index.js | 11 - .../yarn-classic-layout-pass/yarn.lock | 8 - registry/tests/projects/zod-pass/fixture.json | 4 - registry/tests/projects/zod-pass/package.json | 8 - .../tests/projects/zod-pass/pnpm-lock.yaml | 22 - registry/tests/projects/zod-pass/src/index.js | 55 - registry/tests/terminal-harness.ts | 159 - scripts/benchmarks/README.md | 2 +- scripts/benchmarks/session.bench.ts | 16 +- scripts/check-registry-software-split.mjs | 150 - .../check-registry-software-split.test.mjs | 87 - .../check-registry-test-runtime-boundary.mjs | 155 - ...ck-registry-test-runtime-boundary.test.mjs | 94 - scripts/secure-exec-dep.mjs | 26 +- 265 files changed, 550 insertions(+), 17997 deletions(-) create mode 100644 packages/core/tests/session-update-live.test.ts delete mode 100644 registry/.gitignore delete mode 100644 registry/agent/claude/package.json delete mode 100644 registry/agent/claude/scripts/build-patched-cli.mjs delete mode 100644 registry/agent/claude/src/adapter.ts delete mode 100644 registry/agent/claude/src/index.ts delete mode 100644 registry/agent/claude/src/patched-cli.ts delete mode 100644 registry/agent/claude/tests/patched-cli.test.mjs delete mode 100644 registry/agent/claude/tsconfig.json delete mode 100644 registry/agent/codex/package.json delete mode 100644 registry/agent/codex/src/index.ts delete mode 100644 registry/agent/codex/tests/package.test.mjs delete mode 100644 registry/agent/codex/tsconfig.json delete mode 100644 registry/agent/opencode/package.json delete mode 100644 registry/agent/opencode/scripts/build-opencode-acp.mjs delete mode 100644 registry/agent/opencode/src/adapter.ts delete mode 100644 registry/agent/opencode/src/index.ts delete mode 100644 registry/agent/opencode/src/opencode-acp.mjs.d.ts delete mode 100644 registry/agent/opencode/tsconfig.json delete mode 100644 registry/agent/opencode/upstream/opencode-v1.3.13.patch delete mode 100644 registry/agent/pi-cli/package.json delete mode 100644 registry/agent/pi-cli/src/index.ts delete mode 100644 registry/agent/pi-cli/tsconfig.json delete mode 100644 registry/agent/pi/package.json delete mode 100644 registry/agent/pi/scripts/build-snapshot-bundle.mjs delete mode 100644 registry/agent/pi/src/adapter.ts delete mode 100644 registry/agent/pi/src/index.ts delete mode 100644 registry/agent/pi/src/snapshot-entry.ts delete mode 100644 registry/agent/pi/tsconfig.json delete mode 100644 registry/tests/helpers.ts delete mode 100644 registry/tests/kernel/bridge-child-process.test.ts delete mode 100644 registry/tests/kernel/ci-wasm-artifact-availability.test.ts delete mode 100644 registry/tests/kernel/cross-runtime-network.test.ts delete mode 100644 registry/tests/kernel/cross-runtime-pipes.test.ts delete mode 100644 registry/tests/kernel/cross-runtime-terminal.test.ts delete mode 100644 registry/tests/kernel/ctrl-c-shell-behavior.test.ts delete mode 100644 registry/tests/kernel/dispose-behavior.test.ts delete mode 100644 registry/tests/kernel/e2e-concurrently.test.ts delete mode 100644 registry/tests/kernel/e2e-nextjs-build.test.ts delete mode 100644 registry/tests/kernel/e2e-npm-install.test.ts delete mode 100644 registry/tests/kernel/e2e-npm-lifecycle.test.ts delete mode 100644 registry/tests/kernel/e2e-npm-scripts.test.ts delete mode 100644 registry/tests/kernel/e2e-npm-suite.test.ts delete mode 100644 registry/tests/kernel/e2e-npm-version-init.test.ts delete mode 100644 registry/tests/kernel/e2e-npx-and-pipes.test.ts delete mode 100644 registry/tests/kernel/e2e-project-matrix.test.ts delete mode 100644 registry/tests/kernel/error-propagation.test.ts delete mode 100644 registry/tests/kernel/exec-integration.test.ts delete mode 100644 registry/tests/kernel/fd-inheritance.test.ts delete mode 100644 registry/tests/kernel/helpers.ts delete mode 100644 registry/tests/kernel/module-resolution.test.ts delete mode 100644 registry/tests/kernel/node-binary-behavior.test.ts delete mode 100644 registry/tests/kernel/repro-npm-install.ts delete mode 100644 registry/tests/kernel/shim-streaming.test.ts delete mode 100644 registry/tests/kernel/signal-forwarding.test.ts delete mode 100644 registry/tests/kernel/tree-test.test.ts delete mode 100644 registry/tests/kernel/vfs-consistency.test.ts delete mode 100644 registry/tests/projects/astro-pass/astro.config.mjs delete mode 100644 registry/tests/projects/astro-pass/fixture.json delete mode 100644 registry/tests/projects/astro-pass/package.json delete mode 100644 registry/tests/projects/astro-pass/src/components/Counter.jsx delete mode 100644 registry/tests/projects/astro-pass/src/index.js delete mode 100644 registry/tests/projects/astro-pass/src/pages/index.astro delete mode 100644 registry/tests/projects/axios-pass/fixture.json delete mode 100644 registry/tests/projects/axios-pass/package.json delete mode 100644 registry/tests/projects/axios-pass/src/index.js delete mode 100644 registry/tests/projects/bcryptjs-pass/fixture.json delete mode 100644 registry/tests/projects/bcryptjs-pass/package.json delete mode 100644 registry/tests/projects/bcryptjs-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/bcryptjs-pass/src/index.js delete mode 100644 registry/tests/projects/bun-layout-pass/bun.lock delete mode 100644 registry/tests/projects/bun-layout-pass/fixture.json delete mode 100644 registry/tests/projects/bun-layout-pass/package.json delete mode 100644 registry/tests/projects/bun-layout-pass/src/index.js delete mode 100644 registry/tests/projects/chalk-pass/fixture.json delete mode 100644 registry/tests/projects/chalk-pass/package.json delete mode 100644 registry/tests/projects/chalk-pass/src/index.js delete mode 100644 registry/tests/projects/conditional-exports-pass/fixture.json delete mode 100644 registry/tests/projects/conditional-exports-pass/package-lock.json delete mode 100644 registry/tests/projects/conditional-exports-pass/package.json delete mode 100644 registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/feature-cjs.js delete mode 100644 registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/feature-default.js delete mode 100644 registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/main-cjs.js delete mode 100644 registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/main-default.js delete mode 100644 registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/package.json delete mode 100644 registry/tests/projects/conditional-exports-pass/src/index.js delete mode 100644 registry/tests/projects/crypto-random-pass/fixture.json delete mode 100644 registry/tests/projects/crypto-random-pass/package.json delete mode 100644 registry/tests/projects/crypto-random-pass/src/index.js delete mode 100644 registry/tests/projects/dotenv-pass/.env delete mode 100644 registry/tests/projects/dotenv-pass/fixture.json delete mode 100644 registry/tests/projects/dotenv-pass/package.json delete mode 100644 registry/tests/projects/dotenv-pass/src/index.js delete mode 100644 registry/tests/projects/drizzle-pass/fixture.json delete mode 100644 registry/tests/projects/drizzle-pass/package.json delete mode 100644 registry/tests/projects/drizzle-pass/src/index.js delete mode 100644 registry/tests/projects/esm-import-pass/fixture.json delete mode 100644 registry/tests/projects/esm-import-pass/package.json delete mode 100644 registry/tests/projects/esm-import-pass/src/index.js delete mode 100644 registry/tests/projects/express-pass/fixture.json delete mode 100644 registry/tests/projects/express-pass/package.json delete mode 100644 registry/tests/projects/express-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/express-pass/src/index.js delete mode 100644 registry/tests/projects/fastify-pass/fixture.json delete mode 100644 registry/tests/projects/fastify-pass/package.json delete mode 100644 registry/tests/projects/fastify-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/fastify-pass/src/index.js delete mode 100644 registry/tests/projects/fs-metadata-rename-pass/fixture.json delete mode 100644 registry/tests/projects/fs-metadata-rename-pass/package.json delete mode 100644 registry/tests/projects/fs-metadata-rename-pass/src/index.js delete mode 100644 registry/tests/projects/ioredis-pass/fixture.json delete mode 100644 registry/tests/projects/ioredis-pass/package.json delete mode 100644 registry/tests/projects/ioredis-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/ioredis-pass/src/index.js delete mode 100644 registry/tests/projects/jsonwebtoken-pass/fixture.json delete mode 100644 registry/tests/projects/jsonwebtoken-pass/package.json delete mode 100644 registry/tests/projects/jsonwebtoken-pass/src/index.js delete mode 100644 registry/tests/projects/lodash-es-pass/fixture.json delete mode 100644 registry/tests/projects/lodash-es-pass/package.json delete mode 100644 registry/tests/projects/lodash-es-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/lodash-es-pass/src/index.js delete mode 100644 registry/tests/projects/module-access-pass/fixture.json delete mode 100644 registry/tests/projects/module-access-pass/package.json delete mode 100644 registry/tests/projects/module-access-pass/src/index.js delete mode 100644 registry/tests/projects/module-access-pass/vendor/entry-lib/index.js delete mode 100644 registry/tests/projects/module-access-pass/vendor/entry-lib/package.json delete mode 100644 registry/tests/projects/module-access-pass/vendor/transitive-lib/index.js delete mode 100644 registry/tests/projects/module-access-pass/vendor/transitive-lib/package.json delete mode 100644 registry/tests/projects/mysql2-pass/fixture.json delete mode 100644 registry/tests/projects/mysql2-pass/package.json delete mode 100644 registry/tests/projects/mysql2-pass/src/index.js delete mode 100644 registry/tests/projects/net-create-server-pass/fixture.json delete mode 100644 registry/tests/projects/net-create-server-pass/package.json delete mode 100644 registry/tests/projects/net-create-server-pass/src/index.js delete mode 100644 registry/tests/projects/nextjs-pass/.babelrc delete mode 100644 registry/tests/projects/nextjs-pass/fixture.json delete mode 100644 registry/tests/projects/nextjs-pass/next-wasm-shim.cjs delete mode 100644 registry/tests/projects/nextjs-pass/next.config.js delete mode 100644 registry/tests/projects/nextjs-pass/package.json delete mode 100644 registry/tests/projects/nextjs-pass/pages/api/hello.js delete mode 100644 registry/tests/projects/nextjs-pass/pages/index.js delete mode 100644 registry/tests/projects/nextjs-pass/run-next-build.cjs delete mode 100644 registry/tests/projects/nextjs-pass/src/index.js delete mode 100644 registry/tests/projects/node-fetch-pass/fixture.json delete mode 100644 registry/tests/projects/node-fetch-pass/package.json delete mode 100644 registry/tests/projects/node-fetch-pass/src/index.js delete mode 100644 registry/tests/projects/npm-layout-pass/fixture.json delete mode 100644 registry/tests/projects/npm-layout-pass/package-lock.json delete mode 100644 registry/tests/projects/npm-layout-pass/package.json delete mode 100644 registry/tests/projects/npm-layout-pass/src/index.js delete mode 100644 registry/tests/projects/optional-deps-pass/fixture.json delete mode 100644 registry/tests/projects/optional-deps-pass/package-lock.json delete mode 100644 registry/tests/projects/optional-deps-pass/package.json delete mode 100644 registry/tests/projects/optional-deps-pass/src/index.js delete mode 100644 registry/tests/projects/peer-deps-pass/fixture.json delete mode 100644 registry/tests/projects/peer-deps-pass/package-lock.json delete mode 100644 registry/tests/projects/peer-deps-pass/package.json delete mode 100644 registry/tests/projects/peer-deps-pass/packages/host/index.js delete mode 100644 registry/tests/projects/peer-deps-pass/packages/host/package.json delete mode 100644 registry/tests/projects/peer-deps-pass/packages/plugin/index.js delete mode 100644 registry/tests/projects/peer-deps-pass/packages/plugin/package.json delete mode 100644 registry/tests/projects/peer-deps-pass/src/index.js delete mode 100644 registry/tests/projects/pg-pass/fixture.json delete mode 100644 registry/tests/projects/pg-pass/package.json delete mode 100644 registry/tests/projects/pg-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/pg-pass/src/index.js delete mode 100644 registry/tests/projects/pino-pass/fixture.json delete mode 100644 registry/tests/projects/pino-pass/package.json delete mode 100644 registry/tests/projects/pino-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/pino-pass/src/index.js delete mode 100644 registry/tests/projects/pnpm-layout-pass/fixture.json delete mode 100644 registry/tests/projects/pnpm-layout-pass/package.json delete mode 100644 registry/tests/projects/pnpm-layout-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/pnpm-layout-pass/src/index.js delete mode 100644 registry/tests/projects/rivetkit/fixture.json delete mode 100644 registry/tests/projects/rivetkit/package.json delete mode 100644 registry/tests/projects/rivetkit/src/index.js delete mode 100644 registry/tests/projects/rivetkit/vendor/rivetkit/package.json delete mode 100644 registry/tests/projects/semver-pass/fixture.json delete mode 100644 registry/tests/projects/semver-pass/package.json delete mode 100644 registry/tests/projects/semver-pass/src/index.js delete mode 100644 registry/tests/projects/sse-streaming-pass/fixture.json delete mode 100644 registry/tests/projects/sse-streaming-pass/package.json delete mode 100644 registry/tests/projects/sse-streaming-pass/src/index.js delete mode 100644 registry/tests/projects/ssh2-pass/fixture.json delete mode 100644 registry/tests/projects/ssh2-pass/package.json delete mode 100644 registry/tests/projects/ssh2-pass/src/index.js delete mode 100644 registry/tests/projects/ssh2-sftp-client-pass/fixture.json delete mode 100644 registry/tests/projects/ssh2-sftp-client-pass/package.json delete mode 100644 registry/tests/projects/ssh2-sftp-client-pass/src/index.js delete mode 100644 registry/tests/projects/transitive-deps-pass/fixture.json delete mode 100644 registry/tests/projects/transitive-deps-pass/package-lock.json delete mode 100644 registry/tests/projects/transitive-deps-pass/package.json delete mode 100644 registry/tests/projects/transitive-deps-pass/packages/level-a/index.js delete mode 100644 registry/tests/projects/transitive-deps-pass/packages/level-a/package.json delete mode 100644 registry/tests/projects/transitive-deps-pass/packages/level-b/index.js delete mode 100644 registry/tests/projects/transitive-deps-pass/packages/level-b/package.json delete mode 100644 registry/tests/projects/transitive-deps-pass/packages/level-c/index.js delete mode 100644 registry/tests/projects/transitive-deps-pass/packages/level-c/package.json delete mode 100644 registry/tests/projects/transitive-deps-pass/src/index.js delete mode 100644 registry/tests/projects/uuid-pass/fixture.json delete mode 100644 registry/tests/projects/uuid-pass/package.json delete mode 100644 registry/tests/projects/uuid-pass/src/index.js delete mode 100644 registry/tests/projects/vite-pass/app/main.jsx delete mode 100644 registry/tests/projects/vite-pass/fixture.json delete mode 100644 registry/tests/projects/vite-pass/index.html delete mode 100644 registry/tests/projects/vite-pass/package.json delete mode 100644 registry/tests/projects/vite-pass/src/index.js delete mode 100644 registry/tests/projects/vite-pass/vite.config.mjs delete mode 100644 registry/tests/projects/workspace-layout-pass/fixture.json delete mode 100644 registry/tests/projects/workspace-layout-pass/package.json delete mode 100644 registry/tests/projects/workspace-layout-pass/packages/app/package.json delete mode 100644 registry/tests/projects/workspace-layout-pass/packages/app/src/index.js delete mode 100644 registry/tests/projects/workspace-layout-pass/packages/lib/package.json delete mode 100644 registry/tests/projects/workspace-layout-pass/packages/lib/src/index.js delete mode 100644 registry/tests/projects/ws-pass/fixture.json delete mode 100644 registry/tests/projects/ws-pass/package-lock.json delete mode 100644 registry/tests/projects/ws-pass/package.json delete mode 100644 registry/tests/projects/ws-pass/src/index.js delete mode 100644 registry/tests/projects/yaml-pass/fixture.json delete mode 100644 registry/tests/projects/yaml-pass/package.json delete mode 100644 registry/tests/projects/yaml-pass/src/index.js delete mode 100644 registry/tests/projects/yarn-berry-layout-pass/.yarnrc.yml delete mode 100644 registry/tests/projects/yarn-berry-layout-pass/fixture.json delete mode 100644 registry/tests/projects/yarn-berry-layout-pass/package.json delete mode 100644 registry/tests/projects/yarn-berry-layout-pass/src/index.js delete mode 100644 registry/tests/projects/yarn-berry-layout-pass/yarn.lock delete mode 100644 registry/tests/projects/yarn-classic-layout-pass/fixture.json delete mode 100644 registry/tests/projects/yarn-classic-layout-pass/package.json delete mode 100644 registry/tests/projects/yarn-classic-layout-pass/src/index.js delete mode 100644 registry/tests/projects/yarn-classic-layout-pass/yarn.lock delete mode 100644 registry/tests/projects/zod-pass/fixture.json delete mode 100644 registry/tests/projects/zod-pass/package.json delete mode 100644 registry/tests/projects/zod-pass/pnpm-lock.yaml delete mode 100644 registry/tests/projects/zod-pass/src/index.js delete mode 100644 registry/tests/terminal-harness.ts delete mode 100644 scripts/check-registry-software-split.mjs delete mode 100644 scripts/check-registry-software-split.test.mjs delete mode 100644 scripts/check-registry-test-runtime-boundary.mjs delete mode 100644 scripts/check-registry-test-runtime-boundary.test.mjs diff --git a/CLAUDE.md b/CLAUDE.md index ed7163de6..f637aa308 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,8 +7,8 @@ Agent OS is the agent-facing wrapper around secure-exec. It provides ACP session - secure-exec dependency workflow. Manage the secure-exec dependency ONLY through `scripts/secure-exec-dep.mjs` (the `just secure-exec-*` recipes); never hand-edit the `path` / `version` / `catalog:` pins. - Testing against local secure-exec changes: run `just secure-exec-local` to repoint npm (`link:`) and crates (`path = "../secure-exec/..."`) at the sibling checkout, then `node scripts/secure-exec-dep.mjs set-crate-version ` so the Cargo version requirement matches the sibling crate version (otherwise cargo cannot resolve the path deps). Also run `pnpm install` in `../secure-exec` first, or cargo panics in `v8-runtime/build.rs` with "missing Node dependencies at .../packages/build-tools/node_modules" (the V8 bridge assets are built from there). Use `just secure-exec-status` to inspect. This mode is for local builds/tests ONLY. - Pushing changes that depend on secure-exec changes: NEVER push with local (`path:` / `link:`) dependencies — this rule still holds. First preview-publish the secure-exec changes to their own secure-exec branch (the `preview-publish-secure-exec` flow), then point agent-os back at that exact published version with `just secure-exec-pinned` + `just secure-exec-set-version `. Only commit/push the pinned state. Note the committed `Cargo.toml`/`Cargo.lock` stay pinned to a **crates.io** version (release-clean), NOT to a path/clone: a preview pin keeps the crate version at the last crates.io release and the secure-exec *crate* changes are picked up at CI build time by `prepare-build`, which clones secure-exec at the pinned `` and builds cargo in local mode (crates.io has no preview track). So you never commit a local path dep, yet a preview's crate changes still build. See "Depending on unreleased secure-exec changes". -- Keep generic runtime, kernel, VFS, language execution, and registry software behavior in secure-exec. -- Agent OS owns ACP, sessions, agent adapters, toolkit semantics, quickstarts, and the AgentOs facade. +- Keep generic runtime, kernel, VFS, language execution, generic registry software, and packaged agent definitions/adapters in secure-exec. +- Agent OS owns ACP, sessions, toolkit semantics, quickstarts, docs, and the AgentOs facade. - Call OS instances VMs, never sandboxes. - The protocol has no backwards compatibility. Clients and the sidecar ship in same-version lockstep, so never add protocol or config versioning, runtime negotiation, fallbacks, or converters. Configs such as `CreateVmConfig` carry no `version` field; the single same-version wire handshake is the only version check. Change the protocol freely and update both sides together. @@ -16,9 +16,10 @@ Agent OS is the agent-facing wrapper around secure-exec. It provides ACP session ### secure-exec dependency versions (`just`) -Two independent version tracks: -- **secure-exec** — the `@secure-exec/*` npm packages and the `secure-exec-*` Cargo crates share **one** version on a real **release** (npm + crates publish together). On a **preview** they diverge: npm pins to the `0.0.0-.` preview tag while the crate version requirement stays at the last crates.io release (crates.io has no preview track), and CI's `prepare-build` clones secure-exec at `` to build the crates. See "Depending on unreleased secure-exec changes". -- **`@agentos-software/*`** software packages (registry agents / WASM commands) are on a **separate** track and version independently of secure-exec. +Three release tracks: +- **secure-exec runtime** — `@secure-exec/*` npm packages and `secure-exec-*` crates. See "Depending on unreleased secure-exec changes" for preview-vs-release behavior. +- **`@agentos-software/*` registry packages** — generic VM software from secure-exec `registry/software/*` plus agent adapters from secure-exec `registry/agent/*`; versioned independently of secure-exec runtime packages. +- **agent-os product/API** — `@rivet-dev/agentos*`, AgentOs APIs, sidecar wrapper, docs, quickstarts, and examples; pins compatible secure-exec and registry package versions. Manage them ONLY via these recipes (never hand-edit `path`/`version`/`catalog:` pins): - `just secure-exec-local` — point deps at the sibling `../secure-exec` checkout for local hacking. diff --git a/Cargo.lock b/Cargo.lock index 3d728de6f..5a204718c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3036,19 +3036,16 @@ dependencies = [ [[package]] name = "secure-exec-bridge" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a6cc97f67aa7b0ec000de4e86a7ff34e816180ffd000f629e5c1ab54e30a43" +version = "0.3.0-rc.1" dependencies = [ "serde", "serde_json", + "tracing", ] [[package]] name = "secure-exec-client" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68514ecc89c077659befc6f10694b05b7053a75aa787a43ef9979ce95024baf3" +version = "0.3.0-rc.1" dependencies = [ "futures", "parking_lot", @@ -3061,9 +3058,7 @@ dependencies = [ [[package]] name = "secure-exec-execution" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1248cbd13738efd2d038ca3518d06fc7bb82bc59d92add52ccdda9147976eaf" +version = "0.3.0-rc.1" dependencies = [ "base64 0.22.1", "ciborium", @@ -3079,9 +3074,7 @@ dependencies = [ [[package]] name = "secure-exec-kernel" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aabbe6f91f4bf522f24e4c242d455d300c60b0b17a5556864c3c8cd72ce7dca" +version = "0.3.0-rc.1" dependencies = [ "base64 0.22.1", "getrandom 0.2.17", @@ -3095,9 +3088,7 @@ dependencies = [ [[package]] name = "secure-exec-sidecar" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415ecfb1827644dc7c669d31c9b8714e027773cb2f26a88d8793ecb5df513969" +version = "0.3.0-rc.1" dependencies = [ "async-trait", "aws-config", @@ -3146,9 +3137,7 @@ dependencies = [ [[package]] name = "secure-exec-v8-runtime" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c257fd2a912f7ea9abca3323d8932251c856af5134b8f1b887ca3ca9d25fba" +version = "0.3.0-rc.1" dependencies = [ "ciborium", "crossbeam-channel", @@ -3162,9 +3151,7 @@ dependencies = [ [[package]] name = "secure-exec-vfs" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34196ea77e160544fd01941c42691cfd86c2f41c61da9dfba85426875b2a4f6b" +version = "0.3.0-rc.1" dependencies = [ "async-trait", "aws-sdk-s3", @@ -3176,9 +3163,7 @@ dependencies = [ [[package]] name = "secure-exec-vfs-core" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4be477a10f63c5618b418d0568893be494477cc403e6f9235914ca355e5bbbd" +version = "0.3.0-rc.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -3190,9 +3175,7 @@ dependencies = [ [[package]] name = "secure-exec-vm-config" -version = "0.3.1-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da38c96de2a0d7c13f65d88e9dd9ef767ae0fedb5b6ee357fd497df78b742110" +version = "0.3.0-rc.1" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index b51cfe377..208d6bc51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,16 +22,16 @@ repository = "https://github.com/rivet-dev/agent-os" # normal crates.io dependencies so CI/publish builds do not need a sibling # checkout. [workspace.dependencies] -agentos-bridge = { package = "secure-exec-bridge", version = "0.3.1-rc.4" } +agentos-bridge = { package = "secure-exec-bridge", path = "../secure-exec/crates/bridge", version = "0.3.0-rc.1" } agentos-protocol = { path = "crates/agentos-protocol", version = "0.2.0-rc.3" } agentos-sidecar = { path = "crates/agentos-sidecar", version = "0.2.0-rc.3" } agentos-sidecar-browser = { path = "crates/agentos-sidecar-browser", version = "0.2.0-rc.3" } -agentos-kernel = { package = "secure-exec-kernel", version = "0.3.1-rc.4" } -agentos-execution = { package = "secure-exec-execution", version = "0.3.1-rc.4" } -agentos-v8-runtime = { package = "secure-exec-v8-runtime", version = "0.3.1-rc.4" } -secure-exec-client = { version = "0.3.1-rc.4" } -secure-exec-bridge = { version = "0.3.1-rc.4" } -secure-exec-sidecar = { version = "0.3.1-rc.4" } -secure-exec-vm-config = { version = "0.3.1-rc.4" } +agentos-kernel = { package = "secure-exec-kernel", path = "../secure-exec/crates/kernel", version = "0.3.0-rc.1" } +agentos-execution = { package = "secure-exec-execution", path = "../secure-exec/crates/execution", version = "0.3.0-rc.1" } +agentos-v8-runtime = { package = "secure-exec-v8-runtime", path = "../secure-exec/crates/v8-runtime", version = "0.3.0-rc.1" } +secure-exec-client = { path = "../secure-exec/crates/secure-exec-client", version = "0.3.0-rc.1" } +secure-exec-bridge = { path = "../secure-exec/crates/bridge", version = "0.3.0-rc.1" } +secure-exec-sidecar = { path = "../secure-exec/crates/sidecar", version = "0.3.0-rc.1" } +secure-exec-vm-config = { path = "../secure-exec/crates/vm-config", version = "0.3.0-rc.1" } vbare = "0.0.4" vbare-compiler = { package = "rivet-vbare-compiler", version = "0.0.5" } diff --git a/TODO.md b/TODO.md index 8f50d73c1..1d6f8d465 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,5 @@ # TODO - Add OCI import/export support for overlay filesystem layers and snapshots after phase 1. The phase-1 API should only guarantee the bundled base filesystem artifact and the internal snapshot export/import format. -- Run the full registry native-kernel suite after rebuilding the WASM command artifacts. `registry/tests/smoke.test.ts` skipped in this pass because the local `registry/native/target/wasm32-wasip1/release/commands` binaries were not present, so the new `createKernel` sidecar-backed path is only verified by package-level builds plus targeted core tests right now. +- Run the full secure-exec registry native-kernel suite after rebuilding the WASM command artifacts. `../secure-exec/registry/tests/smoke.test.ts` skipped in this pass because the local `../secure-exec/registry/native/target/wasm32-wasip1/release/commands` binaries were not present, so the new `createKernel` sidecar-backed path is only verified by package-level builds plus targeted core tests right now. - Expand verification for the new sidecar-backed kernel compatibility surface around `socketTable`/`processTable` observability and browser runtime end-to-end specs. The source builds are green and targeted tests passed, but the deeper integration suites were not exercised in this pass. diff --git a/crates/agentos-sidecar/src/acp_extension.rs b/crates/agentos-sidecar/src/acp_extension.rs index 584b2f42e..ffc6afd03 100644 --- a/crates/agentos-sidecar/src/acp_extension.rs +++ b/crates/agentos-sidecar/src/acp_extension.rs @@ -29,6 +29,11 @@ use tokio::sync::Mutex; const INITIALIZE_TIMEOUT: Duration = Duration::from_secs(10); const SESSION_NEW_TIMEOUT: Duration = Duration::from_secs(30); const SESSION_CLOSE_TIMEOUT: Duration = Duration::from_secs(5); +// While an ACP request is in flight the stdio loop is inside the extension +// dispatch, so this wait loop becomes the cooperative VM I/O pump. Keep it at +// the same cadence as secure-exec's outer event pump so adapter fetches and +// process output keep moving mid-turn. +const ACP_JSON_RPC_POLL_INTERVAL: Duration = Duration::from_micros(250); const ACP_CANCEL_METHOD: &str = "session/cancel"; /// Transcript-continuation preamble prepended (once) to the first prompt after a /// fallback resume. Lossy-but-universal floor: the agent is handed a *pointer* to @@ -673,7 +678,11 @@ impl AcpExtension { Err(error) => return AcpHandlerOutput::response(Err(error)), }; match ctx.ext_event_wire(event) { - Ok(event) => exchange.events.push(event), + Ok(frame) => { + if let Err(error) = deliver_event(&ctx, &mut exchange.events, frame) { + return AcpHandlerOutput::response(Err(error)); + } + } Err(error) => return AcpHandlerOutput::response(Err(error)), } } @@ -1336,6 +1345,23 @@ impl AcpSessionRecord { } #[allow(clippy::too_many_arguments)] +/// Deliver an ACP event frame to the host. Streams it live through the sidecar's +/// event sink (the stdio path) the instant it is produced; only when no live sink +/// is configured (an in-process `NativeSidecar` with no stdout loop) does it fall +/// back to collecting the frame into `events` for the dispatch-result batch. This +/// is what makes `session/update`s arrive mid-turn instead of all arriving at +/// once when the `session/prompt` dispatch finally resolves. +fn deliver_event( + ctx: &ExtensionContext<'_>, + events: &mut Vec, + frame: secure_exec_sidecar::wire::EventFrame, +) -> Result<(), SidecarError> { + if let Some(frame) = ctx.emit_event_wire(frame)? { + events.push(frame); + } + Ok(()) +} + async fn send_json_rpc_request( ctx: &mut ExtensionContext<'_>, process_id: &str, @@ -1400,7 +1426,7 @@ async fn send_json_rpc_request( } let remaining = deadline.saturating_duration_since(now); let event = ctx - .poll_event_wire(remaining.min(Duration::from_millis(250))) + .poll_event_wire(remaining.min(ACP_JSON_RPC_POLL_INTERVAL)) .await?; let Some(event) = event else { continue; @@ -1455,7 +1481,7 @@ async fn send_json_rpc_request( ); } if let Some(session_id) = event_session_id { - events.push(ctx.ext_event_wire(encode_event( + let frame = ctx.ext_event_wire(encode_event( AcpEvent::AcpSessionEvent(AcpSessionEvent { session_id: session_id.to_string(), notification: serde_json::to_string(&message).map_err( @@ -1466,7 +1492,8 @@ async fn send_json_rpc_request( }, )?, }), - )?)?); + )?)?; + deliver_event(ctx, &mut events, frame)?; } else { notifications.push(serde_json::to_string(&message).map_err( |error| { @@ -1482,16 +1509,23 @@ async fn send_json_rpc_request( EventPayload::ProcessOutputEvent(output) if output.process_id == process_id && output.channel == StreamChannel::Stderr => { - events.push( - ctx.ext_event_wire(encode_event(AcpEvent::AcpAgentStderrEvent( - AcpAgentStderrEvent { - session_id: event_session_id.unwrap_or_default().to_string(), - agent_type: agent_type.to_string(), - process_id: process_id.to_string(), - chunk: output.chunk, - }, - ))?)?, - ); + let frame = ctx.ext_event_wire(encode_event(AcpEvent::AcpAgentStderrEvent( + AcpAgentStderrEvent { + session_id: event_session_id.unwrap_or_default().to_string(), + agent_type: agent_type.to_string(), + process_id: process_id.to_string(), + chunk: output.chunk, + }, + ))?)?; + // Stream live during an owned session turn (prompt/cancel), but + // keep bootstrap stderr (initialize/session-new/load, which pass + // no session id) in the batch so it still arrives for callers that + // only subscribe after create/resume resolves. + if event_session_id.is_some() { + deliver_event(ctx, &mut events, frame)?; + } else { + events.push(frame); + } } EventPayload::ProcessExitedEvent(exited) if exited.process_id == process_id => { // Embed ADAPTER_EXITED_ERROR_MARKER directly so is_adapter_exited_error() diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json index 1db2f3f79..c37aa6c3b 100644 --- a/examples/quickstart/package.json +++ b/examples/quickstart/package.json @@ -27,11 +27,11 @@ "sandbox-agent": "^0.4.2", "dockerode": "^4.0.9", "get-port": "^7.1.0", - "@agentos-software/git": "catalog:", - "@agentos-software/claude-code": "catalog:", - "@agentos-software/opencode": "catalog:", - "@agentos-software/pi": "catalog:", - "@secure-exec/s3": "catalog:", + "@agentos-software/git": "link:../../../secure-exec/registry/software/git", + "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", + "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", + "@agentos-software/pi": "link:../../../secure-exec/registry/agent/pi", + "@secure-exec/s3": "link:../../../secure-exec/registry/file-system/s3", "zod": "^4.1.11" }, "devDependencies": { diff --git a/package.json b/package.json index f940d53de..8118093c0 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build": "npx turbo build", "test": "pnpm --dir packages/core build && pnpm --dir packages/dev-shell build && pnpm --dir packages/dev-shell check-types && pnpm --dir packages/dev-shell test && npx turbo test --concurrency=1 --filter='!@rivet-dev/agentos-dev-shell'", "test:migration-parity": "pnpm --dir packages/core exec vitest run tests/migration-parity.test.ts --reporter=verbose", - "test:post-python-parity": "pnpm --dir packages/core build && pnpm --dir packages/core exec vitest run tests/agentos-base-filesystem.test.ts && pnpm --dir packages/dev-shell exec vitest run test/dev-shell.integration.test.ts && ./node_modules/.bin/vitest run --testTimeout=55000 --hookTimeout=30000 registry/tests/kernel/cross-runtime-terminal.test.ts registry/tests/kernel/ctrl-c-shell-behavior.test.ts registry/tests/kernel/node-binary-behavior.test.ts registry/tests/kernel/e2e-project-matrix.test.ts", + "test:post-python-parity": "pnpm --dir packages/core build && pnpm --dir packages/core exec vitest run tests/agentos-base-filesystem.test.ts && pnpm --dir packages/dev-shell exec vitest run test/dev-shell.integration.test.ts", "test:watch": "npx turbo watch test", "check-types": "npx turbo check-types --concurrency=1", "lint": "pnpm biome check .", @@ -23,11 +23,11 @@ "@copilotkit/llmock": "^1.6.0", "@mariozechner/pi-coding-agent": "^0.60.0", "@rivet-dev/agentos-core": "workspace:*", - "@agentos-software/claude-code": "catalog:", - "@agentos-software/codex": "catalog:", - "@agentos-software/common": "catalog:", - "@secure-exec/core": "catalog:", - "@agentos-software/pi": "catalog:", + "@agentos-software/claude-code": "link:../secure-exec/registry/agent/claude", + "@agentos-software/codex": "link:../secure-exec/registry/agent/codex", + "@agentos-software/common": "link:../secure-exec/registry/software/common", + "@secure-exec/core": "link:../secure-exec/packages/core", + "@agentos-software/pi": "link:../secure-exec/registry/agent/pi", "@types/node": "^22.19.15", "jszip": "^3.10.1", "pdf-lib": "^1.17.1", diff --git a/packages/agentos-sandbox/package.json b/packages/agentos-sandbox/package.json index 64fb47d3e..d22d0320d 100644 --- a/packages/agentos-sandbox/package.json +++ b/packages/agentos-sandbox/package.json @@ -22,12 +22,12 @@ }, "dependencies": { "@rivet-dev/agentos-core": "workspace:*", - "@secure-exec/sandbox": "catalog:", + "@secure-exec/sandbox": "link:../../../secure-exec/registry/tool/sandbox", "sandbox-agent": "^0.4.2", "zod": "^4.1.11" }, "devDependencies": { - "@agentos-software/common": "catalog:", + "@agentos-software/common": "link:../../../secure-exec/registry/software/common", "@types/node": "^22.10.2", "typescript": "^5.7.2", "vitest": "^2.1.8" diff --git a/packages/agentos/package.json b/packages/agentos/package.json index b0e418a6c..434f7a7ab 100644 --- a/packages/agentos/package.json +++ b/packages/agentos/package.json @@ -51,7 +51,7 @@ "test": "vitest run" }, "dependencies": { - "@agentos-software/common": "catalog:", + "@agentos-software/common": "link:../../../secure-exec/registry/software/common", "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sidecar": "workspace:*", "@rivetkit/react": "0.0.0-feat-dylib-actor-plugin.c44621f", diff --git a/packages/core/CLAUDE.md b/packages/core/CLAUDE.md index 6f1a4beb3..e005a41a6 100644 --- a/packages/core/CLAUDE.md +++ b/packages/core/CLAUDE.md @@ -81,7 +81,7 @@ Each agent type needs: - When Node/Vitest code needs to shell out to Cargo, resolve it through `src/sidecar/cargo.ts` instead of assuming a login shell already put `~/.cargo/bin` on `PATH`. - For `tests/wasm-commands.test.ts`, broad `-t "grep"` or `-t "sed"` filters can pull in unrelated `rg`, `gzip`, or cross-package pipeline coverage via substring matches. When a story only gates the `grep`/`sed` blocks, use the explicit case names or a narrower `--testNamePattern` that only matches those block entries. - For `tests/wasm-commands.test.ts` and similar long-running VM truth suites, prefer one shared VM per `describe(...)` block over one VM per individual test unless the case truly needs pristine bootstrap state. Per-test VM boots push the file into multi-minute runtimes and make the RC sweep look hung even when it is still progressing. -- Cross-workspace suites like `registry/tests/*` import `@rivet-dev/agentos-core` from `packages/core/dist`, not directly from `src/`. After changing exported test-runtime code such as `src/runtime-compat.ts`, rebuild `packages/core` before trusting registry/package Vitest results. +- Cross-workspace suites in `../secure-exec/registry/tests/*` import `@rivet-dev/agentos-core` from `packages/core/dist`, not directly from `src/`. After changing exported test-runtime code such as `src/runtime-compat.ts`, rebuild `packages/core` before trusting registry/package Vitest results. - The `examples/quickstart` package also resolves `@rivet-dev/agentos-core` from `packages/core/dist`; after TypeScript changes in `packages/core/src`, rebuild `packages/core` before rerunning quickstart acceptance commands. - The synthetic `openShell()` fallback in `src/sidecar/rpc-client.ts` needs PTY-style output semantics for xterm-based harnesses: normalize terminal-visible line endings to `\r\n`, and route command stderr through the main `onData` stream instead of treating it like a separate non-PTY stderr channel. - **Always verify related tests pass before considering work done.** @@ -113,8 +113,8 @@ Each agent type needs: - Declarative sidecar permission rules must use explicit `["*"]` wildcards for rule `operations` and `paths`/`patterns`; empty arrays are rejected by the native sidecar instead of being treated as implicit wildcards. - **Pi SDK llmock setup**: Pi reads Anthropic endpoints from `~/.pi/agent/models.json`, not `ANTHROPIC_BASE_URL`. For `createSession("pi")` tests, write a provider override such as `{ "providers": { "anthropic": { "baseUrl": "", "apiKey": "mock-key" } } }` inside the VM before creating the session. - Pi headless llmock tests should still pass `ANTHROPIC_BASE_URL` through the session env even with the `~/.pi/agent/models.json` override, because some Pi SDK request paths still consult the env-configured base URL during ACP-driven tool turns. -- `packages/core` agent-session tests execute registry agent workspaces through their built `dist`/bin artifacts. After changing an adapter under `registry/agent/*/src`, rebuild that workspace before trusting the core Vitest result. -- Keep Claude's default `CLAUDE_CODE_NODE_SHELL_WRAPPER` enabled (`"1"`) in both `src/agents.ts` and `registry/agent/claude/src/index.ts`. Forcing it to `"0"` breaks real Bash-tool execution under llmock-backed sessions: shell redirections can still create empty files, but the command output/tool result never lands, which regresses `tests/claude-session.test.ts` and filesystem visibility checks. +- `packages/core` agent-session tests execute secure-exec registry agent workspaces through their built `dist`/bin artifacts. After changing an adapter under `../secure-exec/registry/agent/*/src`, rebuild that workspace before trusting the core Vitest result. +- Keep Claude's default `CLAUDE_CODE_NODE_SHELL_WRAPPER` enabled (`"1"`) in both `src/agents.ts` and `../secure-exec/registry/agent/claude/src/index.ts`. Forcing it to `"0"` breaks real Bash-tool execution under llmock-backed sessions: shell redirections can still create empty files, but the command output/tool result never lands, which regresses `tests/claude-session.test.ts` and filesystem visibility checks. - Registry/kernel suites that import `@rivet-dev/agentos-core/test/runtime` read `packages/core/dist/test/runtime.js`, not the TypeScript sources directly. After changing `src/runtime-compat.ts`, `src/sidecar/rpc-client.ts`, or other runtime-test surfaces, run `pnpm --dir packages/core build` before rerunning those registry Vitest files or they will keep exercising stale code. - **Module access**: Pass `mounts: [nodeModulesMount("/node_modules")]` to `AgentOs.create()` to expose a host `node_modules` tree at `/root/node_modules`. The VM module resolver reads the mounted tree through the kernel VFS (no host-direct reads, no `moduleAccessCwd`). pnpm puts devDeps in `packages/core/node_modules/`, so tests use `nodeModulesMount(join(resolve(import.meta.dirname, ".."), "node_modules"))`. Software-package agents (`software: [pi]`) mount their own `/root/node_modules/` roots and do not need this mount. - Quickstarts and integration tests that run full-tier registry commands (for example `@agentos-software/git`) should set both an explicit `/root/node_modules` mount (via `nodeModulesMount(...)`) and explicit `permissions` on `AgentOs.create()`. There is no `process.cwd()` default anymore: supply the exact `node_modules` tree (a flat install, not a pnpm workspace root whose symlinks escape the mount), and remember that omitting permissions defaults the native sidecar to deny-all. @@ -122,7 +122,7 @@ Each agent type needs: - Sandbox toolkit quickstarts/tests that depend on external Docker should use an explicit `SKIP_DOCKER=1` gate instead of `skipIf`, and the truthful host-tool path is to read `AGENTOS_TOOLS_PORT` inside the VM and `POST` `{ toolkit, tool, input }` to `http://127.0.0.1:$AGENTOS_TOOLS_PORT/call` from a guest Node script. - Shared Vitest helpers under `src/test/` should register optional capability coverage conditionally in code instead of with `describe.skipIf` / `test.skipIf`; `US-088` treats those markers as product-debt skips even when they only guard backend capability differences. - Pi bash-tool E2E coverage depends on registry WASM commands being built locally. Gate those tests with `tests/helpers/registry-commands.ts` `hasRegistryCommands` and include the `@agentos-software/common` software package only when the command artifacts exist. -- Registry package tests for C-built commands such as `duckdb` and `http_get` should also go through `tests/helpers/registry-commands.ts`: prefer copied `registry/software/*/wasm` artifacts, fall back to `registry/native/c/build` when available, and let the helper build missing C-source artifacts on demand before declaring the command unavailable. When bootstrapping from `registry/native/c`, build `make sysroot` first and then run a second `make` for the concrete `build/...` targets so `SYSROOT` resolves to the patched tree instead of the vanilla SDK sysroot chosen at parse time; in that second pass, treat `sysroot/lib/wasm32-wasi/libc.a` as already built so `make` does not loop back through the patch pipeline because of preserved sysroot timestamps. +- Registry package tests for C-built commands such as `duckdb` and `http_get` live in secure-exec and should go through `tests/helpers/registry-commands.ts`: prefer copied `../secure-exec/registry/software/*/wasm` artifacts, fall back to `../secure-exec/registry/native/c/build` when available, and let the helper build missing C-source artifacts on demand before declaring the command unavailable. When bootstrapping from secure-exec `registry/native/c`, build `make sysroot` first and then run a second `make` for the concrete `build/...` targets so `SYSROOT` resolves to the patched tree instead of the vanilla SDK sysroot chosen at parse time; in that second pass, treat `sysroot/lib/wasm32-wasi/libc.a` as already built so `make` does not loop back through the patch pipeline because of preserved sysroot timestamps. - `tests/claude-session.test.ts` is the Claude SDK truth suite. It runs the real `@anthropic-ai/claude-agent-sdk` session path through llmock and covers PATH-backed `xu`, text-only replies, nested `node` `execSync` and `spawn`, metadata, lifecycle, and mode updates. Run it with `pnpm --dir packages/core exec vitest run tests/claude-session.test.ts --reporter=verbose` when verifying Claude regressions. - **Kernel permissions are declarative pass-through config.** `AgentOsOptions.permissions` should stay JSON-serializable and be forwarded to the native sidecar without host-side probing or callback evaluation; Rust owns glob matching and policy decisions. - ACP session events are live-only over `onSessionEvent()`. Do not reintroduce sequence numbers, local replay buffers, or event cursor recovery. @@ -152,9 +152,9 @@ See `.agent/specs/test-structure.md` for the full restructuring plan. Target lay ### WASM Binaries and Quickstart Examples -- **WASM command binaries are not checked into git.** The `registry/software/*/wasm/` directories are build artifacts. +- **WASM command binaries are not checked into git.** The `../secure-exec/registry/software/*/wasm/` directories are build artifacts. - **Quickstart examples that use `exec()` or shell commands require WASM binaries.** Without them, these fail with "No shell available." -- **To build WASM binaries locally:** Run `make` in `registry/native/`, then `make copy-wasm` and `make build` in `registry/`. Requires Rust nightly + wasi-sdk. +- **To build WASM binaries locally:** Run `make` in `../secure-exec/registry/native/`, then `make copy-wasm` and `make build` in `../secure-exec/registry/`. Requires Rust nightly + wasi-sdk. - **Examples that work without WASM binaries:** `hello-world.ts`, `filesystem.ts`, `cron.ts` (schedule/cancel only). - **When testing quickstart examples**, don't treat WASM-dependent failures as regressions unless the WASM binaries are present. diff --git a/packages/core/package.json b/packages/core/package.json index ea73648b0..b2e7048c4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -54,11 +54,11 @@ "test": "vitest run --reporter=verbose" }, "dependencies": { - "@agentos-software/common": "catalog:", + "@agentos-software/common": "link:../../../secure-exec/registry/software/common", "@aws-sdk/client-s3": "^3.1019.0", "@rivet-dev/agentos-sidecar": "workspace:*", "@rivetkit/bare-ts": "^0.6.2", - "@secure-exec/core": "catalog:", + "@secure-exec/core": "link:../../../secure-exec/packages/core", "@xterm/headless": "^6.0.0", "better-sqlite3": "^12.8.0", "croner": "^10.0.1", @@ -78,31 +78,31 @@ "@bare-ts/tools": "0.15.0", "@copilotkit/llmock": "^1.6.0", "@rivet-dev/agentos-sandbox": "link:../agentos-sandbox", - "@agentos-software/git": "catalog:", - "@secure-exec/google-drive": "catalog:", - "@secure-exec/s3": "catalog:", + "@agentos-software/git": "link:../../../secure-exec/registry/software/git", + "@secure-exec/google-drive": "link:../../../secure-exec/registry/file-system/google-drive", + "@secure-exec/s3": "link:../../../secure-exec/registry/file-system/s3", "@mariozechner/pi-coding-agent": "^0.60.0", - "@agentos-software/claude-code": "catalog:", - "@agentos-software/codex-cli": "catalog:", - "@agentos-software/codex": "catalog:", - "@agentos-software/coreutils": "catalog:", - "@agentos-software/curl": "catalog:", - "@agentos-software/diffutils": "catalog:", - "@agentos-software/fd": "catalog:", - "@agentos-software/file": "catalog:", - "@agentos-software/findutils": "catalog:", - "@agentos-software/gawk": "catalog:", - "@agentos-software/grep": "catalog:", - "@agentos-software/gzip": "catalog:", - "@agentos-software/jq": "catalog:", - "@agentos-software/opencode": "catalog:", - "@agentos-software/pi": "catalog:", - "@agentos-software/pi-cli": "catalog:", - "@agentos-software/ripgrep": "catalog:", - "@agentos-software/sed": "catalog:", - "@agentos-software/tar": "catalog:", - "@agentos-software/tree": "catalog:", - "@agentos-software/yq": "catalog:", + "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", + "@agentos-software/codex-cli": "link:../../../secure-exec/registry/software/codex", + "@agentos-software/codex": "link:../../../secure-exec/registry/agent/codex", + "@agentos-software/coreutils": "link:../../../secure-exec/registry/software/coreutils", + "@agentos-software/curl": "link:../../../secure-exec/registry/software/curl", + "@agentos-software/diffutils": "link:../../../secure-exec/registry/software/diffutils", + "@agentos-software/fd": "link:../../../secure-exec/registry/software/fd", + "@agentos-software/file": "link:../../../secure-exec/registry/software/file", + "@agentos-software/findutils": "link:../../../secure-exec/registry/software/findutils", + "@agentos-software/gawk": "link:../../../secure-exec/registry/software/gawk", + "@agentos-software/grep": "link:../../../secure-exec/registry/software/grep", + "@agentos-software/gzip": "link:../../../secure-exec/registry/software/gzip", + "@agentos-software/jq": "link:../../../secure-exec/registry/software/jq", + "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", + "@agentos-software/pi": "link:../../../secure-exec/registry/agent/pi", + "@agentos-software/pi-cli": "link:../../../secure-exec/registry/agent/pi-cli", + "@agentos-software/ripgrep": "link:../../../secure-exec/registry/software/ripgrep", + "@agentos-software/sed": "link:../../../secure-exec/registry/software/sed", + "@agentos-software/tar": "link:../../../secure-exec/registry/software/tar", + "@agentos-software/tree": "link:../../../secure-exec/registry/software/tree", + "@agentos-software/yq": "link:../../../secure-exec/registry/software/yq", "@types/node": "^22.10.2", "pi-acp": "^0.0.23", "sandbox-agent": "^0.4.2", diff --git a/packages/core/pnpm-lock.yaml b/packages/core/pnpm-lock.yaml index ddf9a1d29..7055b9eb4 100644 --- a/packages/core/pnpm-lock.yaml +++ b/packages/core/pnpm-lock.yaml @@ -55,14 +55,14 @@ importers: specifier: ^0.60.0 version: 0.60.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) '@agentos-software/claude-code': - specifier: link:../../registry/agent/claude - version: link:../../registry/agent/claude + specifier: link:../../../secure-exec/registry/agent/claude + version: link:../../../secure-exec/registry/agent/claude '@secure-exec/codex': specifier: link:../../registry/software/codex version: link:../../registry/software/codex '@agentos-software/codex': - specifier: link:../../registry/agent/codex - version: link:../../registry/agent/codex + specifier: link:../../../secure-exec/registry/agent/codex + version: link:../../../secure-exec/registry/agent/codex '@secure-exec/coreutils': specifier: link:../../registry/software/coreutils version: link:../../registry/software/coreutils @@ -94,14 +94,14 @@ importers: specifier: link:../../registry/software/jq version: link:../../registry/software/jq '@agentos-software/opencode': - specifier: link:../../registry/agent/opencode - version: link:../../registry/agent/opencode + specifier: link:../../../secure-exec/registry/agent/opencode + version: link:../../../secure-exec/registry/agent/opencode '@agentos-software/pi': - specifier: link:../../registry/agent/pi - version: link:../../registry/agent/pi + specifier: link:../../../secure-exec/registry/agent/pi + version: link:../../../secure-exec/registry/agent/pi '@agentos-software/pi-cli': - specifier: link:../../registry/agent/pi-cli - version: link:../../registry/agent/pi-cli + specifier: link:../../../secure-exec/registry/agent/pi-cli + version: link:../../../secure-exec/registry/agent/pi-cli '@secure-exec/ripgrep': specifier: link:../../registry/software/ripgrep version: link:../../registry/software/ripgrep diff --git a/packages/core/src/packages.ts b/packages/core/src/packages.ts index a1be19135..da778bcf5 100644 --- a/packages/core/src/packages.ts +++ b/packages/core/src/packages.ts @@ -42,11 +42,10 @@ export function resolvePackageDir( } import type { AgentConfig } from "./agents.js"; -import type { Kernel } from "./runtime-compat.js"; const LOCAL_REGISTRY_COMMAND_DIR = resolve( dirname(fileURLToPath(import.meta.url)), - "../../..", + "../../../../secure-exec", "registry/native/target/wasm32-wasip1/release/commands", ); @@ -455,7 +454,10 @@ function collectCommandMetadata( } } - const commandDir = resolveLocalCommandDirFallback(pkg.commandDir, declaredCommands); + const commandDir = resolveLocalCommandDirFallback( + pkg.commandDir, + declaredCommands, + ); return { commandDir, @@ -464,7 +466,10 @@ function collectCommandMetadata( }; } -function hasUsableCommandDir(commandDir: string, declaredCommands: string[]): boolean { +function hasUsableCommandDir( + commandDir: string, + declaredCommands: string[], +): boolean { if (!existsSync(commandDir)) { return false; } diff --git a/packages/core/tests/helpers/registry-commands.ts b/packages/core/tests/helpers/registry-commands.ts index 65b5b6721..cc6de5177 100644 --- a/packages/core/tests/helpers/registry-commands.ts +++ b/packages/core/tests/helpers/registry-commands.ts @@ -6,7 +6,7 @@ * to AgentOs.create({ software: [...] }). * * When a C-backed registry package is missing its built command artifact, this - * helper builds the command on demand into `registry/native/c/build` and uses + * helper builds the command on demand into secure-exec `registry/native/c/build` and uses * that directory as a fallback command source. */ @@ -34,10 +34,16 @@ import yq from "@agentos-software/yq"; const __dirname = dirname(fileURLToPath(import.meta.url)); const FALLBACK_COMMAND_DIR = resolve( __dirname, - "../../../../registry/native/target/wasm32-wasip1/release/commands", + "../../../../../secure-exec/registry/native/target/wasm32-wasip1/release/commands", +); +const C_BUILD_COMMAND_DIR = resolve( + __dirname, + "../../../../../secure-exec/registry/native/c/build", +); +const C_BUILD_ROOT = resolve( + __dirname, + "../../../../../secure-exec/registry/native/c", ); -const C_BUILD_COMMAND_DIR = resolve(__dirname, "../../../../registry/native/c/build"); -const C_BUILD_ROOT = resolve(__dirname, "../../../../registry/native/c"); const C_PATCHED_SYSROOT_TARGET = "sysroot/lib/wasm32-wasi/libc.a"; const C_BUILD_TARGETS = new Map([ ["duckdb", "build/duckdb"], @@ -57,7 +63,9 @@ type CommandPackageLike = { function declaredCommandNames(pkg: CommandPackageLike): string[] { return (pkg.commands ?? []) .map((command) => command.name) - .filter((name): name is string => typeof name === "string" && name.length > 0); + .filter( + (name): name is string => typeof name === "string" && name.length > 0, + ); } function hasUsableCommandDir(dir: string, commands: string[]): boolean { @@ -112,10 +120,14 @@ function ensureFallbackCommandArtifacts(commands: string[]): string | false { return `Failed to build registry command artifacts via make sysroot:\n${output}`; } - const buildResult = spawnSync("make", ["-o", C_PATCHED_SYSROOT_TARGET, ...buildTargets], { - cwd: C_BUILD_ROOT, - encoding: "utf8", - }); + const buildResult = spawnSync( + "make", + ["-o", C_PATCHED_SYSROOT_TARGET, ...buildTargets], + { + cwd: C_BUILD_ROOT, + encoding: "utf8", + }, + ); if (buildResult.status === 0) { return false; } @@ -130,9 +142,9 @@ function ensureFallbackCommandArtifacts(commands: string[]): string | false { return `Failed to build registry command artifacts via make ${buildTargets.join(" ")}:\n${output}`; } -export function withFallbackCommandDir< - T extends CommandPackageLike, ->(pkg: T): T { +export function withFallbackCommandDir( + pkg: T, +): T { const commands = declaredCommandNames(pkg); if (hasUsableCommandDir(pkg.commandDir, commands)) { return pkg; @@ -155,15 +167,17 @@ export function withFallbackCommandDir< return pkg; } -export function commandPackageSkipReason(...packages: CommandPackageLike[]): string | false { +export function commandPackageSkipReason( + ...packages: CommandPackageLike[] +): string | false { const buildErrors = packages .map((pkg) => ensureFallbackCommandArtifacts(declaredCommandNames(pkg))) .filter((error): error is string => typeof error === "string"); const unavailable = packages.flatMap((pkg) => { const commands = declaredCommandNames(pkg); - return [pkg.commandDir, FALLBACK_COMMAND_DIR, C_BUILD_COMMAND_DIR].some((dir) => - hasUsableCommandDir(dir, commands), + return [pkg.commandDir, FALLBACK_COMMAND_DIR, C_BUILD_COMMAND_DIR].some( + (dir) => hasUsableCommandDir(dir, commands), ) ? [] : commands; @@ -207,4 +221,4 @@ export const hasRegistryCommands = /** Skip reason for tests that need registry commands. */ export const registrySkipReason = hasRegistryCommands ? false - : "Registry WASM binaries not available (run: make -C registry/native && make -C registry copy-wasm build)"; + : "Registry WASM binaries not available (run: make -C ../secure-exec/registry/native && make -C ../secure-exec/registry copy-wasm build)"; diff --git a/packages/core/tests/session-update-live.test.ts b/packages/core/tests/session-update-live.test.ts new file mode 100644 index 000000000..7f228b3ef --- /dev/null +++ b/packages/core/tests/session-update-live.test.ts @@ -0,0 +1,216 @@ +import { resolve } from "node:path"; +import common from "@agentos-software/common"; +import pi from "@agentos-software/pi"; +import type { Fixture, ToolCall } from "@copilotkit/llmock"; +import { describe, expect, test } from "vitest"; +import { AgentOs } from "../src/agent-os.js"; +import { + createAnthropicFixture, + startLlmock, + stopLlmock, +} from "./helpers/llmock-helper.js"; +import { moduleAccessMounts } from "./helpers/node-modules-mount.js"; +import { hasRegistryCommands } from "./helpers/registry-commands.js"; + +/** + * REPRO / REGRESSION: "onSessionUpdate not delivered live mid-turn with Pi" + * (zid / image 12). + * + * Root cause: the secure-exec stdio loop `await`s the entire `session/prompt` + * dispatch before flushing any frames, and `acp_extension.rs::session_request` + * collects every `session/update` into `exchange.events`, returning them only + * after the turn resolves. A streaming consumer (rivetkit / zid via agentos-core) + * therefore gets ZERO updates mid-turn and the whole batch at the end. + * + * Making the window observable: a zero-latency llmock collapses the whole agent + * turn into one synchronous burst, so "live" and "batched" look identical. To + * create a real mid-turn window we give the SECOND llmock response (the one after + * the tool result) a `latency`. The sequence becomes: + * + * t0 prompt starts + * ~ Pi calls the `write` tool -> emits the `tool_call` session/update, + * runs the tool, then requests the final message from the LLM + * t1 llmock holds that 2nd response open for `RESPONSE_LATENCY_MS` + * t2 final message returns, turn resolves + * + * Between t1 and t2 the `tool_call` update already exists. With live delivery it + * reaches `onSessionEvent` during the hold (so `resolve - firstEvent` is large, + * and at least one update is seen before resolution). With the batching bug every + * update arrives in a tight cluster at t2 (`resolve - firstEvent ~= 0`). + * + * This asserts the live contract, so it is RED on the batching bug and GREEN once + * the live-emit path lands. + */ + +const MODULE_ACCESS_CWD = resolve(import.meta.dirname, ".."); +const RESPONSE_LATENCY_MS = 1500; + +interface TimedEvent { + method: string; + params?: unknown; + t: number; +} + +function getRequestBody(req: unknown): Record { + const direct = req as Record; + const body = direct.body; + return body && typeof body === "object" + ? (body as Record) + : direct; +} + +function isPostToolResultRequest( + req: unknown, + expectedToolResult: string, +): boolean { + const s = JSON.stringify(getRequestBody(req)); + return s.includes('"role":"tool"') && s.includes(expectedToolResult); +} + +function isSessionUpdate(event: TimedEvent): boolean { + return event.method === "session/update"; +} + +describe("REPRO: Pi session/update live delivery (zid / image 12)", () => { + test("session/update events stream live mid-turn, not batched at prompt resolution", async () => { + const workspacePath = "/home/agentos/workspace/tool-verify.txt"; + const expectedToolResult = "Successfully wrote"; + const finalText = "tool-verify.txt was created successfully."; + + const events: TimedEvent[] = []; + let promptStart = 0; + let checkpointEvents = -1; // events seen the instant the 2nd LLM req arrives + + const toolCall: ToolCall = { + name: "write", + arguments: JSON.stringify({ + path: workspacePath, + content: "tool-test-ok", + }), + }; + const fixtures: Fixture[] = [ + createAnthropicFixture( + { + predicate: (req) => + !JSON.stringify(getRequestBody(req)).includes('"role":"tool"'), + }, + { toolCalls: [toolCall] }, + ), + { + match: { + predicate: (req) => { + const match = isPostToolResultRequest(req, expectedToolResult); + if (match && checkpointEvents < 0) { + checkpointEvents = events.length; + } + return match; + }, + }, + response: { content: finalText }, + // Hold the final response open so there is a real mid-turn window + // in which the already-produced tool_call update can stream. + latency: RESPONSE_LATENCY_MS, + }, + ]; + + const { mock, url } = await startLlmock(fixtures); + const vm = await AgentOs.create({ + loopbackExemptPorts: [Number(new URL(url).port)], + mounts: moduleAccessMounts(MODULE_ACCESS_CWD), + software: hasRegistryCommands ? [common, pi] : [pi], + }); + + let sessionId: string | undefined; + try { + const homeDir = "/home/agentos"; + await vm.mkdir(`${homeDir}/.pi/agent`, { recursive: true }); + await vm.writeFile( + `${homeDir}/.pi/agent/models.json`, + JSON.stringify({ + providers: { anthropic: { baseUrl: url, apiKey: "mock-key" } }, + }), + ); + const workspaceDir = "/home/agentos/workspace"; + await vm.mkdir(workspaceDir, { recursive: true }); + + sessionId = ( + await vm.createSession("pi", { + cwd: workspaceDir, + env: { + HOME: homeDir, + ANTHROPIC_API_KEY: "mock-key", + ANTHROPIC_BASE_URL: url, + }, + }) + ).sessionId; + + const unsubscribe = vm.onSessionEvent(sessionId, (event) => { + events.push({ + method: event.method, + params: event.params, + t: performance.now() - promptStart, + }); + }); + + promptStart = performance.now(); + const { response, text } = await vm.prompt( + sessionId, + "Write the text 'tool-test-ok' to tool-verify.txt. Do not explain, just do it.", + ); + const promptResolved = performance.now() - promptStart; + unsubscribe(); + + // Sanity: the turn completed correctly and actually exercised the + // latency hold (so the mid-turn window really existed). + expect(response.error).toBeUndefined(); + expect(text).toContain(finalText); + expect(mock.getRequests().length).toBeGreaterThanOrEqual(2); + expect( + promptResolved, + "the turn should span the injected latency window", + ).toBeGreaterThan(RESPONSE_LATENCY_MS * 0.6); + + const updates = events.filter(isSessionUpdate); + const firstUpdateT = updates.length ? updates[0].t : NaN; + const updatesBeforeResolve = updates.filter( + (e) => e.t < promptResolved - RESPONSE_LATENCY_MS * 0.4, + ).length; + const gap = promptResolved - firstUpdateT; + + /* eslint-disable no-console */ + console.log("\n===== ZID onSessionUpdate REPRO DIAGNOSTIC ====="); + console.log(`injected latency : ${RESPONSE_LATENCY_MS}ms`); + console.log(`prompt resolved at : ${promptResolved.toFixed(1)}ms`); + console.log( + `session/update events : ${updates.length} (of ${events.length} total)`, + ); + console.log(`first update delivered at : ${firstUpdateT.toFixed(1)}ms`); + console.log(`updates BEFORE resolution : ${updatesBeforeResolve}`); + console.log(`gap (resolve - firstUpdate): ${gap.toFixed(1)}ms`); + console.log(`events at 2nd-LLM-req time : ${checkpointEvents}`); + const verdict = + gap > RESPONSE_LATENCY_MS * 0.5 + ? "LIVE (FIXED): updates streamed during the mid-turn window" + : "BATCHED (BUG): every update arrived only at resolution"; + console.log(`VERDICT: ${verdict}`); + console.log("==================================================\n"); + /* eslint-enable no-console */ + + // The contract: onSessionEvent is live. The tool_call update is + // produced before the latency hold, so it must reach the subscriber + // well before the prompt resolves. + expect( + updatesBeforeResolve, + "BUG: no session/update arrived before resolution — events are batched until session/prompt resolves", + ).toBeGreaterThan(0); + expect( + gap, + "BUG: first update arrived at ~the same time as resolution — events are batched, not streamed", + ).toBeGreaterThan(RESPONSE_LATENCY_MS * 0.5); + } finally { + if (sessionId) vm.closeSession(sessionId); + await vm.dispose(); + await stopLlmock(mock); + } + }, 120_000); +}); diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index cf44ff48c..c092bdc4f 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -50,7 +50,7 @@ const KNOWN_FAILING_E2E_FILES = [ "tests/pi-acp-adapter.test.ts", "tests/process-lifecycle.test.ts", // Registry-artifact / shell-behavior failures (red in both CI and local): - // - duckdb-package: imports registry/software/duckdb/dist (unbuilt WASM in CI). + // - duckdb-package: imports secure-exec registry/software/duckdb/dist (unbuilt WASM in CI). // - shell-flat-api: openShell/writeShell/onShellData yields empty output. "tests/duckdb-package.test.ts", "tests/shell-flat-api.test.ts", diff --git a/packages/dev-shell/package.json b/packages/dev-shell/package.json index 76002c85a..45d4d7801 100644 --- a/packages/dev-shell/package.json +++ b/packages/dev-shell/package.json @@ -12,7 +12,7 @@ "test": "NODE_OPTIONS=--max-old-space-size=256 pnpm exec vitest run --fileParallelism=false --reporter=verbose" }, "dependencies": { - "@agentos-software/common": "catalog:", + "@agentos-software/common": "link:../../../secure-exec/registry/software/common", "@rivet-dev/agentos-core": "workspace:*", "pino": "^10.3.1" }, diff --git a/packages/posix/package.json b/packages/posix/package.json index 58fedee3b..c5a2f1eb0 100644 --- a/packages/posix/package.json +++ b/packages/posix/package.json @@ -23,7 +23,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@secure-exec/core": "catalog:" + "@secure-exec/core": "link:../../../secure-exec/packages/core" }, "devDependencies": { "@types/node": "^22.10.2", diff --git a/packages/python/package.json b/packages/python/package.json index 49af7b622..5e9306a9e 100644 --- a/packages/python/package.json +++ b/packages/python/package.json @@ -32,7 +32,7 @@ "test": "pnpm build && vitest run --fileParallelism=false" }, "dependencies": { - "@secure-exec/core": "catalog:", + "@secure-exec/core": "link:../../../secure-exec/packages/core", "pyodide": "^0.28.3" }, "peerDependencies": { diff --git a/packages/shell/package.json b/packages/shell/package.json index e63dc52b9..0f22a7841 100644 --- a/packages/shell/package.json +++ b/packages/shell/package.json @@ -13,16 +13,16 @@ }, "dependencies": { "@rivet-dev/agentos-core": "workspace:*", - "@agentos-software/common": "catalog:", - "@agentos-software/jq": "catalog:", - "@agentos-software/ripgrep": "catalog:", - "@agentos-software/fd": "catalog:", - "@agentos-software/tree": "catalog:", - "@agentos-software/file": "catalog:", - "@agentos-software/zip": "catalog:", - "@agentos-software/unzip": "catalog:", - "@agentos-software/yq": "catalog:", - "@agentos-software/codex-cli": "catalog:" + "@agentos-software/common": "link:../../../secure-exec/registry/software/common", + "@agentos-software/jq": "link:../../../secure-exec/registry/software/jq", + "@agentos-software/ripgrep": "link:../../../secure-exec/registry/software/ripgrep", + "@agentos-software/fd": "link:../../../secure-exec/registry/software/fd", + "@agentos-software/tree": "link:../../../secure-exec/registry/software/tree", + "@agentos-software/file": "link:../../../secure-exec/registry/software/file", + "@agentos-software/zip": "link:../../../secure-exec/registry/software/zip", + "@agentos-software/unzip": "link:../../../secure-exec/registry/software/unzip", + "@agentos-software/yq": "link:../../../secure-exec/registry/software/yq", + "@agentos-software/codex-cli": "link:../../../secure-exec/registry/software/codex" }, "devDependencies": { "@types/node": "^22.19.3", diff --git a/packages/shell/src/main.ts b/packages/shell/src/main.ts index b9dd76c1c..8b6a25e8a 100644 --- a/packages/shell/src/main.ts +++ b/packages/shell/src/main.ts @@ -5,8 +5,6 @@ import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import codex from "@agentos-software/codex-cli"; import common from "@agentos-software/common"; -import { AgentOs } from "@rivet-dev/agentos-core"; -import type { SoftwareInput } from "@rivet-dev/agentos-core"; import fd from "@agentos-software/fd"; import file from "@agentos-software/file"; import jq from "@agentos-software/jq"; @@ -15,15 +13,15 @@ import tree from "@agentos-software/tree"; import unzip from "@agentos-software/unzip"; import yq from "@agentos-software/yq"; import zip from "@agentos-software/zip"; +import type { SoftwareInput } from "@rivet-dev/agentos-core"; +import { AgentOs } from "@rivet-dev/agentos-core"; const __dirname = dirname(fileURLToPath(import.meta.url)); const COMMAND_SUBPATH = "registry/native/target/wasm32-wasip1/release/commands"; // Published packages ship package-local wasm/ dirs. Workspace packages use the -// native build output: agentos's own registry when present, or the sibling -// secure-exec checkout under `just secure-exec-local` (where the WASM is built). +// sibling secure-exec native build output. const fallbackCommandDir = [ - resolve(__dirname, "../../..", COMMAND_SUBPATH), resolve(__dirname, "../../../../secure-exec", COMMAND_SUBPATH), ].find((dir) => existsSync(dir)); function withLocalCommandFallback(software: SoftwareInput): SoftwareInput { @@ -49,9 +47,18 @@ function withLocalCommandFallback(software: SoftwareInput): SoftwareInput { return software; } -const software = [common, jq, ripgrep, fd, tree, file, zip, unzip, yq, codex].map( - withLocalCommandFallback, -); +const software = [ + common, + jq, + ripgrep, + fd, + tree, + file, + zip, + unzip, + yq, + codex, +].map(withLocalCommandFallback); function printUsage(): void { console.error( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e1aef548..441fcd972 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,96 +6,9 @@ settings: catalogs: default: - '@agentos-software/claude-code': - specifier: 0.0.0-nathan-binding-workspace.9be0a88 - version: 0.0.0-nathan-binding-workspace.9be0a88 - '@agentos-software/codex': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/codex-cli': - specifier: 0.0.0-codex-claude-runtime-fixes.9cbef3a - version: 0.0.0-codex-claude-runtime-fixes.9cbef3a - '@agentos-software/common': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/coreutils': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/curl': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/diffutils': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/fd': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/file': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/findutils': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/gawk': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/git': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/grep': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/gzip': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/jq': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/opencode': - specifier: 0.0.0-nathan-binding-workspace.9be0a88 - version: 0.0.0-nathan-binding-workspace.9be0a88 - '@agentos-software/pi': - specifier: 0.0.0-nathan-binding-workspace.9be0a88 - version: 0.0.0-nathan-binding-workspace.9be0a88 - '@agentos-software/pi-cli': - specifier: 0.0.0-nathan-binding-workspace.9be0a88 - version: 0.0.0-nathan-binding-workspace.9be0a88 - '@agentos-software/ripgrep': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/sed': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/tar': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/tree': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/unzip': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/yq': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@agentos-software/zip': - specifier: 0.3.0-rc.2 - version: 0.3.0-rc.2 - '@secure-exec/core': - specifier: 0.0.0-main.f183ed2 - version: 0.0.0-main.f183ed2 - '@secure-exec/google-drive': - specifier: 0.0.0-main.f183ed2 - version: 0.0.0-main.f183ed2 '@secure-exec/nodejs': specifier: 0.2.1 version: 0.2.1 - '@secure-exec/s3': - specifier: 0.0.0-main.f183ed2 - version: 0.0.0-main.f183ed2 - '@secure-exec/sandbox': - specifier: 0.0.0-main.f183ed2 - version: 0.0.0-main.f183ed2 overrides: '@rivet-dev/agentos-core': workspace:* @@ -108,17 +21,17 @@ importers: .: devDependencies: '@agentos-software/claude-code': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88(@cfworker/json-schema@4.1.1) + specifier: link:../secure-exec/registry/agent/claude + version: link:../secure-exec/registry/agent/claude '@agentos-software/codex': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../secure-exec/registry/agent/codex + version: link:../secure-exec/registry/agent/codex '@agentos-software/common': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../secure-exec/registry/software/common + version: link:../secure-exec/registry/software/common '@agentos-software/pi': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) + specifier: link:../secure-exec/registry/agent/pi + version: link:../secure-exec/registry/agent/pi '@biomejs/biome': specifier: ^2.3 version: 2.4.10 @@ -132,8 +45,8 @@ importers: specifier: workspace:* version: link:packages/core '@secure-exec/core': - specifier: 'catalog:' - version: 0.0.0-main.f183ed2 + specifier: link:../secure-exec/packages/core + version: link:../secure-exec/packages/core '@types/node': specifier: ^22.19.15 version: 22.19.15 @@ -194,17 +107,17 @@ importers: examples/quickstart: dependencies: '@agentos-software/claude-code': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88(@cfworker/json-schema@4.1.1) + specifier: link:../../../secure-exec/registry/agent/claude + version: link:../../../secure-exec/registry/agent/claude '@agentos-software/git': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/git + version: link:../../../secure-exec/registry/software/git '@agentos-software/opencode': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88 + specifier: link:../../../secure-exec/registry/agent/opencode + version: link:../../../secure-exec/registry/agent/opencode '@agentos-software/pi': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) + specifier: link:../../../secure-exec/registry/agent/pi + version: link:../../../secure-exec/registry/agent/pi '@rivet-dev/agentos-core': specifier: workspace:* version: link:../../packages/core @@ -212,8 +125,8 @@ importers: specifier: workspace:* version: link:../../packages/agentos-sandbox '@secure-exec/s3': - specifier: 'catalog:' - version: 0.0.0-main.f183ed2 + specifier: link:../../../secure-exec/registry/file-system/s3 + version: link:../../../secure-exec/registry/file-system/s3 dockerode: specifier: ^4.0.9 version: 4.0.10 @@ -240,8 +153,8 @@ importers: packages/agentos: dependencies: '@agentos-software/common': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/common + version: link:../../../secure-exec/registry/software/common '@rivet-dev/agentos-core': specifier: workspace:* version: link:../core @@ -283,8 +196,8 @@ importers: specifier: workspace:* version: link:../core '@secure-exec/sandbox': - specifier: 'catalog:' - version: 0.0.0-main.f183ed2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) + specifier: link:../../../secure-exec/registry/tool/sandbox + version: link:../../../secure-exec/registry/tool/sandbox sandbox-agent: specifier: ^0.4.2 version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) @@ -293,8 +206,8 @@ importers: version: 4.3.6 devDependencies: '@agentos-software/common': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/common + version: link:../../../secure-exec/registry/software/common '@types/node': specifier: ^22.10.2 version: 22.19.15 @@ -327,8 +240,8 @@ importers: packages/core: dependencies: '@agentos-software/common': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/common + version: link:../../../secure-exec/registry/software/common '@aws-sdk/client-s3': specifier: ^3.1019.0 version: 3.1020.0 @@ -339,8 +252,8 @@ importers: specifier: ^0.6.2 version: 0.6.2 '@secure-exec/core': - specifier: 'catalog:' - version: 0.0.0-main.f183ed2 + specifier: link:../../../secure-exec/packages/core + version: link:../../../secure-exec/packages/core '@xterm/headless': specifier: ^6.0.0 version: 6.0.0 @@ -370,71 +283,71 @@ importers: version: 3.25.2(zod@4.3.6) devDependencies: '@agentos-software/claude-code': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88(@cfworker/json-schema@4.1.1) + specifier: link:../../../secure-exec/registry/agent/claude + version: link:../../../secure-exec/registry/agent/claude '@agentos-software/codex': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/agent/codex + version: link:../../../secure-exec/registry/agent/codex '@agentos-software/codex-cli': - specifier: 'catalog:' - version: 0.0.0-codex-claude-runtime-fixes.9cbef3a + specifier: link:../../../secure-exec/registry/software/codex + version: link:../../../secure-exec/registry/software/codex '@agentos-software/coreutils': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/coreutils + version: link:../../../secure-exec/registry/software/coreutils '@agentos-software/curl': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/curl + version: link:../../../secure-exec/registry/software/curl '@agentos-software/diffutils': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/diffutils + version: link:../../../secure-exec/registry/software/diffutils '@agentos-software/fd': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/fd + version: link:../../../secure-exec/registry/software/fd '@agentos-software/file': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/file + version: link:../../../secure-exec/registry/software/file '@agentos-software/findutils': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/findutils + version: link:../../../secure-exec/registry/software/findutils '@agentos-software/gawk': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/gawk + version: link:../../../secure-exec/registry/software/gawk '@agentos-software/git': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/git + version: link:../../../secure-exec/registry/software/git '@agentos-software/grep': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/grep + version: link:../../../secure-exec/registry/software/grep '@agentos-software/gzip': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/gzip + version: link:../../../secure-exec/registry/software/gzip '@agentos-software/jq': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/jq + version: link:../../../secure-exec/registry/software/jq '@agentos-software/opencode': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88 + specifier: link:../../../secure-exec/registry/agent/opencode + version: link:../../../secure-exec/registry/agent/opencode '@agentos-software/pi': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) + specifier: link:../../../secure-exec/registry/agent/pi + version: link:../../../secure-exec/registry/agent/pi '@agentos-software/pi-cli': - specifier: 'catalog:' - version: 0.0.0-nathan-binding-workspace.9be0a88(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) + specifier: link:../../../secure-exec/registry/agent/pi-cli + version: link:../../../secure-exec/registry/agent/pi-cli '@agentos-software/ripgrep': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/ripgrep + version: link:../../../secure-exec/registry/software/ripgrep '@agentos-software/sed': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/sed + version: link:../../../secure-exec/registry/software/sed '@agentos-software/tar': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/tar + version: link:../../../secure-exec/registry/software/tar '@agentos-software/tree': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/tree + version: link:../../../secure-exec/registry/software/tree '@agentos-software/yq': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/yq + version: link:../../../secure-exec/registry/software/yq '@anthropic-ai/claude-agent-sdk': specifier: ^0.2.87 version: 0.2.87(@cfworker/json-schema@4.1.1)(zod@4.3.6) @@ -463,11 +376,11 @@ importers: specifier: link:../agentos-sandbox version: link:../agentos-sandbox '@secure-exec/google-drive': - specifier: 'catalog:' - version: 0.0.0-main.f183ed2 + specifier: link:../../../secure-exec/registry/file-system/google-drive + version: link:../../../secure-exec/registry/file-system/google-drive '@secure-exec/s3': - specifier: 'catalog:' - version: 0.0.0-main.f183ed2 + specifier: link:../../../secure-exec/registry/file-system/s3 + version: link:../../../secure-exec/registry/file-system/s3 '@types/node': specifier: ^22.10.2 version: 22.19.15 @@ -493,8 +406,8 @@ importers: packages/dev-shell: dependencies: '@agentos-software/common': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/common + version: link:../../../secure-exec/registry/software/common '@rivet-dev/agentos-core': specifier: workspace:* version: link:../core @@ -549,8 +462,8 @@ importers: packages/posix: dependencies: '@secure-exec/core': - specifier: 'catalog:' - version: 0.0.0-main.f183ed2 + specifier: link:../../../secure-exec/packages/core + version: link:../../../secure-exec/packages/core devDependencies: '@types/node': specifier: ^22.10.2 @@ -571,8 +484,8 @@ importers: packages/python: dependencies: '@secure-exec/core': - specifier: 'catalog:' - version: 0.0.0-main.f183ed2 + specifier: link:../../../secure-exec/packages/core + version: link:../../../secure-exec/packages/core pyodide: specifier: ^0.28.3 version: 0.28.3(bufferutil@4.1.0) @@ -653,35 +566,35 @@ importers: packages/shell: dependencies: '@agentos-software/codex-cli': - specifier: 'catalog:' - version: 0.0.0-codex-claude-runtime-fixes.9cbef3a + specifier: link:../../../secure-exec/registry/software/codex + version: link:../../../secure-exec/registry/software/codex '@agentos-software/common': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/common + version: link:../../../secure-exec/registry/software/common '@agentos-software/fd': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/fd + version: link:../../../secure-exec/registry/software/fd '@agentos-software/file': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/file + version: link:../../../secure-exec/registry/software/file '@agentos-software/jq': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/jq + version: link:../../../secure-exec/registry/software/jq '@agentos-software/ripgrep': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/ripgrep + version: link:../../../secure-exec/registry/software/ripgrep '@agentos-software/tree': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/tree + version: link:../../../secure-exec/registry/software/tree '@agentos-software/unzip': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/unzip + version: link:../../../secure-exec/registry/software/unzip '@agentos-software/yq': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/yq + version: link:../../../secure-exec/registry/software/yq '@agentos-software/zip': - specifier: 'catalog:' - version: 0.3.0-rc.2 + specifier: link:../../../secure-exec/registry/software/zip + version: link:../../../secure-exec/registry/software/zip '@rivet-dev/agentos-core': specifier: workspace:* version: link:../core @@ -701,98 +614,6 @@ importers: packages/sidecar-binary: {} - registry/agent/claude: - dependencies: - '@agentclientprotocol/sdk': - specifier: ^0.16.1 - version: 0.16.1(zod@4.3.6) - '@anthropic-ai/claude-agent-sdk': - specifier: ^0.2.87 - version: 0.2.87(@cfworker/json-schema@4.1.1)(zod@4.3.6) - '@rivet-dev/agentos-core': - specifier: workspace:* - version: link:../../../packages/core - zod: - specifier: ^4.1.11 - version: 4.3.6 - devDependencies: - '@types/node': - specifier: ^22.10.2 - version: 22.19.15 - typescript: - specifier: ^5.7.2 - version: 5.9.3 - - registry/agent/codex: - dependencies: - '@agentos-software/codex-cli': - specifier: 'catalog:' - version: 0.0.0-codex-claude-runtime-fixes.9cbef3a - devDependencies: - '@types/node': - specifier: ^22.10.2 - version: 22.19.15 - typescript: - specifier: ^5.7.2 - version: 5.9.3 - - registry/agent/opencode: - dependencies: - '@rivet-dev/agentos-core': - specifier: workspace:* - version: link:../../../packages/core - devDependencies: - '@types/node': - specifier: ^22.10.2 - version: 22.19.15 - bun: - specifier: 1.3.11 - version: 1.3.11 - typescript: - specifier: ^5.7.2 - version: 5.9.3 - - registry/agent/pi: - dependencies: - '@agentclientprotocol/sdk': - specifier: ^0.16.1 - version: 0.16.1(zod@4.3.6) - '@mariozechner/pi-ai': - specifier: 0.60.0 - version: 0.60.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) - '@mariozechner/pi-coding-agent': - specifier: 0.60.0 - version: 0.60.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) - '@rivet-dev/agentos-core': - specifier: workspace:* - version: link:../../../packages/core - devDependencies: - '@types/node': - specifier: ^22.10.2 - version: 22.19.15 - typescript: - specifier: ^5.7.2 - version: 5.9.3 - - registry/agent/pi-cli: - dependencies: - '@mariozechner/pi-coding-agent': - specifier: ^0.60.0 - version: 0.60.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) - '@rivet-dev/agentos-core': - specifier: workspace:* - version: link:../../../packages/core - pi-acp: - specifier: ^0.0.23 - version: 0.0.23 - devDependencies: - '@types/node': - specifier: ^22.10.2 - version: 22.19.15 - typescript: - specifier: ^5.7.2 - version: 5.9.3 - scripts/publish: dependencies: commander: @@ -888,84 +709,6 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - '@agentos-software/claude-code@0.0.0-nathan-binding-workspace.9be0a88': - resolution: {integrity: sha512-lSrWgqisbNFV3kkUpcyjH67UZsI/BWAWqlzr/B5SrHN0FcQU/1xVqYp+ooz24HdUuLlpp4Otx5sJHfb1NLRvDQ==} - hasBin: true - - '@agentos-software/codex-cli@0.0.0-codex-claude-runtime-fixes.9cbef3a': - resolution: {integrity: sha512-vxJ0T2h9O46oOzohDKChKZfduLGelpKz3p7AoatRUmz2n4pIntCcyvUyry6LeX0jcg1W5n0HaI5fX2b4C450Xw==} - - '@agentos-software/codex@0.3.0-rc.2': - resolution: {integrity: sha512-DKAb9Qs1+zV1sgtMEqVo7K60R4IryKMY24jZdH5NX5cKhIi5ED1o+AufINtNSMJxdrByz+MuX+2HkvWXCvHYgw==} - - '@agentos-software/common@0.3.0-rc.2': - resolution: {integrity: sha512-LWMGMFwjBbJDjbIAE8+hMDbbrOptxgXcgwJ6Ve5eFn0ncI6+T0fH5Fr3uTLZyxh0AUhSejbKDVjHwdQczFKnGQ==} - - '@agentos-software/coreutils@0.3.0-rc.2': - resolution: {integrity: sha512-NoGMK0RMTAWnoTBOCAjR7SAsumS8Sx/k8P+/Tg3VPnqy5kdIbMWF3z+3Tw3B626hQB+cuZhgj4nOHcO8UxrHjQ==} - - '@agentos-software/curl@0.3.0-rc.2': - resolution: {integrity: sha512-TRSzT29858IvHxQ6IxgyTNPNlLchmjtLhtqFss2UnKv/dRFK65/piouF0Ho53N2b0lvB3+eOkwV/A4zBLOqAuA==} - - '@agentos-software/diffutils@0.3.0-rc.2': - resolution: {integrity: sha512-0nL1mauroKHPD+HIQ0S+2SnYD4Z7h+IpCED9AlMnbuhCfN6ePjXUSUuuPOTJplcIUrFlT2RWC0OM7Q/0zifyXA==} - - '@agentos-software/fd@0.3.0-rc.2': - resolution: {integrity: sha512-Tdu0oJ/3gqPdrQKRl9tukUnRPD3ner7ZjMoStjnowlPCtLWYw+EGbH1yMBDlDInkrwM5ybzD8vUY6NoGJqTj4A==} - - '@agentos-software/file@0.3.0-rc.2': - resolution: {integrity: sha512-fK8EiM/QX9Rr5KBUsCViO3m5Myz/dA3sMYDVvC3D4H+/FqrzujW1BmBjYv3H3QwH9X1GsC9EpTI0MPE0QJzmtQ==} - - '@agentos-software/findutils@0.3.0-rc.2': - resolution: {integrity: sha512-v1HQbKHhhX4tC9HRYcV57COpYuZJfx8E8GBG2NAC2sj/fDTwhK9vnuNbRLWAy5kdnV/fK3vurXIst7DZfxSFUg==} - - '@agentos-software/gawk@0.3.0-rc.2': - resolution: {integrity: sha512-GJYYNhRulRGXw6mKwB+0/1pkrMeS78aSXcB/wM8IuWpcFCb9NLkZENbDa9Mnc5Bpf3GKKhRsqtvgIEQ4KvxHFg==} - - '@agentos-software/git@0.3.0-rc.2': - resolution: {integrity: sha512-fF6gMwRbkx77CeHKyuRigtasO/pHycQjGfJ2p5KK+/ygBXZJbpH3DyizLl1lBzARBYFhpcIDjLTr2GAKNLkV0A==} - - '@agentos-software/grep@0.3.0-rc.2': - resolution: {integrity: sha512-OwTEYN2fBUK18LYyJzHp0ryIxBINBuAYHI/RGuSCrH7ljKX9dLYYlLmqayMV33IAP7fpzV5s9s+Ad6wNxe19gQ==} - - '@agentos-software/gzip@0.3.0-rc.2': - resolution: {integrity: sha512-ylm/j9fqL5ykRxVZiGcwCDfwKUCb7nhP77bk5d4mX77vb/y0t/guX5CdAMoCEJbAUa7RZVxawKct2H+53TMWEg==} - - '@agentos-software/jq@0.3.0-rc.2': - resolution: {integrity: sha512-xNLHt7m7OaCPOQDX5OlocxqG0dPoCv2JCKkGld2W3uD3oOsO70WZl9sbfNBf8n3doHKgN2Xs04DGVhA/Ynabiw==} - - '@agentos-software/opencode@0.0.0-nathan-binding-workspace.9be0a88': - resolution: {integrity: sha512-CW4rrRKVLpnWY5pLM+JCLTROries+TMMLMIlr0XMwh07fcEJI0Asq8Z93mSmVR1uCFNcWJbB6NM7CRxcaxTq4w==} - hasBin: true - - '@agentos-software/pi-cli@0.0.0-nathan-binding-workspace.9be0a88': - resolution: {integrity: sha512-ga8fnQNr8pmkWS+MfCqPAMa97aFd/xkoL56Fowb5IGZ878vYkhlUMPagO4LBwNcTDpfmJVX16VSqBP76tLiHmw==} - - '@agentos-software/pi@0.0.0-nathan-binding-workspace.9be0a88': - resolution: {integrity: sha512-An4QkRI3K4uPonsjh6V85o0J/qUC7qJuVPaoSVP2KYT7DhDuQ2IYjrgrV6ggcBbDOKlMUIMY/m8GlPa4Skbatw==} - hasBin: true - - '@agentos-software/ripgrep@0.3.0-rc.2': - resolution: {integrity: sha512-OsT641m1kXkGON6D8HZaqlOPnqBN2O3MI1Uvqn+//s9n0MuQDmZ5ojg/jIaaSCTjadXOeFcrJzSASP/lajrxzA==} - - '@agentos-software/sed@0.3.0-rc.2': - resolution: {integrity: sha512-1Pgyt+ZUjBo2Bwdrq2unONw4+9xMmKnRE5xBm/jE7ECfN2L2JX4j7YvcKsN7j1wHrsnjlMuIZfyEBZpqbmH1eQ==} - - '@agentos-software/tar@0.3.0-rc.2': - resolution: {integrity: sha512-0pHAV+sf945JTPdFypp3Dml1k2v1sPK82el6u4PEJWVStuZ3FJl9N+joqdb6ozkIjhmLd7Yk5Dw+sZw6zUtzdg==} - - '@agentos-software/tree@0.3.0-rc.2': - resolution: {integrity: sha512-3hx2P8YOA9ndDPoNV6+ZN8UF75o/O6Uf2IDKCOQ06beamMnrCia1vADOZ2dakDRBYQY5wvf14ISQg0Vc8rTT+Q==} - - '@agentos-software/unzip@0.3.0-rc.2': - resolution: {integrity: sha512-2MrIUpyekfSmcDpG9lXUI5tPrOZx+BdK0WxN2PkgV90ZxUEUnCI1s71IUfMB+4tr6BaO28ZQQCUFW0PkcvpNyQ==} - - '@agentos-software/yq@0.3.0-rc.2': - resolution: {integrity: sha512-FfuElsvnBJXphb7v3MchMrvLpLrI31cOrrPd6KlIR1jvFb6Lv0aV39ksC19OFJDdnHrueutze9LXGDiXxJaGcA==} - - '@agentos-software/zip@0.3.0-rc.2': - resolution: {integrity: sha512-q4A7d/XegHccQV5PboGGDlKKEZZ+89XNWq/pSy6Fu9WWdwChwaO2EAyZdiaQ64+Biu8hRoXlgKuLsripeuEUpw==} - '@ai-sdk/amazon-bedrock@3.0.93': resolution: {integrity: sha512-57cP3Ume6DdQP05xPYl2g554EqPrQgKRW/eE3BGm1ktK1k71e35HGzNl1GZHIYKct82QrY/iQuheanSonI88Dg==} engines: {node: '>=18'} @@ -2502,66 +2245,6 @@ packages: '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} - '@oven/bun-darwin-aarch64@1.3.11': - resolution: {integrity: sha512-/8IzqSu4/OWGRs7Fs2ROzGVwJMFTBQkgAp6sAthkBYoN7OiM4rY/CpPVs2X9w9N1W61CHSkEdNKi8HrLZKfK3g==} - cpu: [arm64] - os: [darwin] - - '@oven/bun-darwin-x64-baseline@1.3.11': - resolution: {integrity: sha512-CYjIHWaQG7T4phfjErHr6BiXRs0K/9DqMeiohJmuYSBF+H2m56vFslOenLCguGYQL9jeiiCZBeoVCpwjxZrMgQ==} - cpu: [x64] - os: [darwin] - - '@oven/bun-darwin-x64@1.3.11': - resolution: {integrity: sha512-TT7eUihnAzxM2tlZesusuC75PAOYKvUBgVU/Nm/lakZ/DpyuqhNkzUfcxSgmmK9IjVWzMmezLIGZl16XGCGJng==} - cpu: [x64] - os: [darwin] - - '@oven/bun-linux-aarch64-musl@1.3.11': - resolution: {integrity: sha512-jBwYCLG5Eb+PqtFrc3Wp2WMYlw1Id75gUcsdP+ApCOpf5oQhHxkFWCjZmcDoioDmEhMWAiM3wtwSrTlPg+sI6Q==} - cpu: [arm64] - os: [linux] - - '@oven/bun-linux-aarch64@1.3.11': - resolution: {integrity: sha512-8XMLyRNxHF4jfLajkWt+F8UDxsWbzysyxQVMZKUXwoeGvaxB0rVd07r3YbgDtG8U6khhRFM3oaGp+CQ0whwmdA==} - cpu: [arm64] - os: [linux] - - '@oven/bun-linux-x64-baseline@1.3.11': - resolution: {integrity: sha512-KZlf1jKtf4jai8xiQv/0XRjxVVhHnw/HtUKtLdOeQpTOQ1fQFhLoz2FGGtVRd0LVa/yiRbSz9HlWIzWlmJClng==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64-musl-baseline@1.3.11': - resolution: {integrity: sha512-J+qz4Al05PrNIOdj7xsWVTyx0c/gjUauG5nKV3Rrx0Q+5JO+1pPVlnfNmWbOF9pKG4f3IGad8KXJUfGMORld+Q==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64-musl@1.3.11': - resolution: {integrity: sha512-ADImD4yCHNpqZu718E2chWcCaAHvua90yhmpzzV6fF4zOhwkGGbPCgUWmKyJ83uz+DXaPdYxX0ttDvtolrzx3Q==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64@1.3.11': - resolution: {integrity: sha512-z3GFCk1UBzDOOiEBHL32lVP7Edi26BhOjKb6bIc0nRyabbRiyON4++GR0zmd/H5zM5S0+UcXFgCGnD+b8avTLw==} - cpu: [x64] - os: [linux] - - '@oven/bun-windows-aarch64@1.3.11': - resolution: {integrity: sha512-UOdkwScHRkGPz+n9ZJU7sTkTvqV7rD1SLCLaru1xH8WRsV7tDorPqNCzEN1msOIiPRK825nvAtEm9UsomO1GsA==} - cpu: [arm64] - os: [win32] - - '@oven/bun-windows-x64-baseline@1.3.11': - resolution: {integrity: sha512-cCsXK9AQ9Zf18QlVnbrFu2IKfr4sf2sfbErkF2jfCzyCO9Bnhl0KRx63zlN+Ni1xU7gcBLAssgcui5R400N2eA==} - cpu: [x64] - os: [win32] - - '@oven/bun-windows-x64@1.3.11': - resolution: {integrity: sha512-E51tyWDP1l0CbjZYhiUxhDGPaY8Hf5YBREx0PHBff1LM1/q3qsJ6ZvRUa8YbbOO0Ax9QP6GHjD9vf3n6bXZ7QA==} - cpu: [x64] - os: [win32] - '@pagefind/darwin-arm64@1.5.2': resolution: {integrity: sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==} cpu: [arm64] @@ -2931,52 +2614,12 @@ packages: resolution: {integrity: sha512-trO//ypJBSt5xkewuol9LOykvDgHwUXq8R+yQVS+0CmpN3lYUtewHkb+At9RVGRhDMmJZY2oasaXDnhfurQ33w==} hasBin: true - '@secure-exec/core@0.0.0-main.f183ed2': - resolution: {integrity: sha512-Ledfki+92G8p8hpk3fgNEa33l72eQnD41nX12Aw0ZPrwHH+sa73m7YfWANO3F4JThPZD8H7S0BLx3Gj+3fgGtA==} - '@secure-exec/core@0.2.1': resolution: {integrity: sha512-HsnUv6gClpMA1BBRmX86j30TKTZtgJC/fO1tVavr7IpM2zNKbHU8LgSlBd7mv2SNy02ImTmU/GnQ3aYB4NSbEg==} - '@secure-exec/google-drive@0.0.0-main.f183ed2': - resolution: {integrity: sha512-CXE9Sz0XwEvv05Wbdg6w+dY3cXp/nPqQ3BkuBhqrErQlScdRXztcLbXrLsb7/qJ7MPnMJwMJb7sTvfxgQRzocA==} - '@secure-exec/nodejs@0.2.1': resolution: {integrity: sha512-UJMJqVFxexlHJV0Q9nWURvrz6GElj8673DDOOFln6FHR6JS+9SaSU3eISrN158DuNC3SFi4rgjb/scKnK4YOYQ==} - '@secure-exec/s3@0.0.0-main.f183ed2': - resolution: {integrity: sha512-L4zq0ACxxzzthU5E1QxwCsPkxFsx2wXKOHC6KHVzB4mDGmWG/eRchY2120zwiRy2KPqirWLYBgWeROZFcq0FbQ==} - - '@secure-exec/sandbox@0.0.0-main.f183ed2': - resolution: {integrity: sha512-1Za2qVPJuXoFp6fcXxTcBak0/H+0KjHM25qUKrTvmJ7C7vZNI7022ewXbnwfqGxpRRtf330JZ+EzlE8szi07ug==} - - '@secure-exec/sidecar-darwin-arm64@0.0.0-main.f183ed2': - resolution: {integrity: sha512-tYQ2tKGKnVzU/cY5VmmohlytuaQeoo929/jT19EeXgpuq0jRlgb3TAfSiqn9q2VlLJayK0tF1pF44qg3EL3+Lg==} - engines: {node: '>=20'} - cpu: [arm64] - os: [darwin] - - '@secure-exec/sidecar-darwin-x64@0.0.0-main.f183ed2': - resolution: {integrity: sha512-Y2tU6uouACP4QuC9EdELnjGTF1d7wj/8b7OuBTFlA0nBtf64ktwoXW8RJRCWXd1pEOUVZsmqh8mDCh4aopGH1w==} - engines: {node: '>=20'} - cpu: [x64] - os: [darwin] - - '@secure-exec/sidecar-linux-arm64-gnu@0.0.0-main.f183ed2': - resolution: {integrity: sha512-68enHSsL2Qm9ht9Xwf880W63jZOTpBCsGcSYBgUP8INg0p23pVtvmepmC3jQ6zEAXJTW5ehK4qNVtlYsbl7Qrg==} - engines: {node: '>=20'} - cpu: [arm64] - os: [linux] - - '@secure-exec/sidecar-linux-x64-gnu@0.0.0-main.f183ed2': - resolution: {integrity: sha512-dWvfVMoGCl96bLthDX4JIJ+5eD4XGGFHHD4i6ev64goBmxoSIHuIdllhVZ0BC3ckfSx/KxyuYaPcK5KSpczoPQ==} - engines: {node: '>=20'} - cpu: [x64] - os: [linux] - - '@secure-exec/sidecar@0.0.0-main.f183ed2': - resolution: {integrity: sha512-VXlUIFWOLuLN2tpzBs0hycoU/7GISi9Sl6MR7e0DQ5UZ4M2CGQDVye2fp8WvIrnj/TZuQb/eMpWqd82K/8X7Qw==} - engines: {node: '>=20'} - '@secure-exec/v8-darwin-arm64@0.2.1': resolution: {integrity: sha512-gEWhMHzUpLwzuBNAD0lVkZXE8wFlWMLp4IOZ+56FYwOW/C+m07cYxuW4TjHyPqZ+vPm3IkoaMqqH5yT9VhjX/Q==} cpu: [arm64] @@ -3777,12 +3420,6 @@ packages: builtin-status-codes@3.0.0: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} - bun@1.3.11: - resolution: {integrity: sha512-AvXWYFO6j/ZQ7bhGm4X6eilq2JHsDVC90ZM32k2B7/srhC2gs3Sdki1QTbwrdRCo8o7eT+167vcB1yzOvPdbjA==} - cpu: [arm64, x64] - os: [darwin, linux, win32] - hasBin: true - bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -7291,100 +6928,6 @@ snapshots: dependencies: zod: 4.3.6 - '@agentos-software/claude-code@0.0.0-nathan-binding-workspace.9be0a88(@cfworker/json-schema@4.1.1)': - dependencies: - '@agentclientprotocol/sdk': 0.16.1(zod@4.3.6) - '@anthropic-ai/claude-agent-sdk': 0.2.87(@cfworker/json-schema@4.1.1)(zod@4.3.6) - '@rivet-dev/agentos-core': link:packages/core - zod: 4.3.6 - transitivePeerDependencies: - - '@cfworker/json-schema' - - supports-color - - '@agentos-software/codex-cli@0.0.0-codex-claude-runtime-fixes.9cbef3a': {} - - '@agentos-software/codex@0.3.0-rc.2': {} - - '@agentos-software/common@0.3.0-rc.2': - dependencies: - '@agentos-software/coreutils': 0.3.0-rc.2 - '@agentos-software/diffutils': 0.3.0-rc.2 - '@agentos-software/findutils': 0.3.0-rc.2 - '@agentos-software/gawk': 0.3.0-rc.2 - '@agentos-software/grep': 0.3.0-rc.2 - '@agentos-software/gzip': 0.3.0-rc.2 - '@agentos-software/sed': 0.3.0-rc.2 - '@agentos-software/tar': 0.3.0-rc.2 - - '@agentos-software/coreutils@0.3.0-rc.2': {} - - '@agentos-software/curl@0.3.0-rc.2': {} - - '@agentos-software/diffutils@0.3.0-rc.2': {} - - '@agentos-software/fd@0.3.0-rc.2': {} - - '@agentos-software/file@0.3.0-rc.2': {} - - '@agentos-software/findutils@0.3.0-rc.2': {} - - '@agentos-software/gawk@0.3.0-rc.2': {} - - '@agentos-software/git@0.3.0-rc.2': {} - - '@agentos-software/grep@0.3.0-rc.2': {} - - '@agentos-software/gzip@0.3.0-rc.2': {} - - '@agentos-software/jq@0.3.0-rc.2': {} - - '@agentos-software/opencode@0.0.0-nathan-binding-workspace.9be0a88': - dependencies: - '@rivet-dev/agentos-core': link:packages/core - - '@agentos-software/pi-cli@0.0.0-nathan-binding-workspace.9be0a88(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6)': - dependencies: - '@mariozechner/pi-coding-agent': 0.60.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) - '@rivet-dev/agentos-core': link:packages/core - pi-acp: 0.0.23 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@agentos-software/pi@0.0.0-nathan-binding-workspace.9be0a88(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6)': - dependencies: - '@agentclientprotocol/sdk': 0.16.1(zod@4.3.6) - '@mariozechner/pi-ai': 0.60.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) - '@mariozechner/pi-coding-agent': 0.60.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)(ws@8.20.0(bufferutil@4.1.0))(zod@4.3.6) - '@rivet-dev/agentos-core': link:packages/core - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@agentos-software/ripgrep@0.3.0-rc.2': {} - - '@agentos-software/sed@0.3.0-rc.2': {} - - '@agentos-software/tar@0.3.0-rc.2': {} - - '@agentos-software/tree@0.3.0-rc.2': {} - - '@agentos-software/unzip@0.3.0-rc.2': {} - - '@agentos-software/yq@0.3.0-rc.2': {} - - '@agentos-software/zip@0.3.0-rc.2': {} - '@ai-sdk/amazon-bedrock@3.0.93(zod@4.3.6)': dependencies: '@ai-sdk/anthropic': 2.0.74(zod@4.3.6) @@ -9320,42 +8863,6 @@ snapshots: '@oslojs/encoding@1.1.0': {} - '@oven/bun-darwin-aarch64@1.3.11': - optional: true - - '@oven/bun-darwin-x64-baseline@1.3.11': - optional: true - - '@oven/bun-darwin-x64@1.3.11': - optional: true - - '@oven/bun-linux-aarch64-musl@1.3.11': - optional: true - - '@oven/bun-linux-aarch64@1.3.11': - optional: true - - '@oven/bun-linux-x64-baseline@1.3.11': - optional: true - - '@oven/bun-linux-x64-musl-baseline@1.3.11': - optional: true - - '@oven/bun-linux-x64-musl@1.3.11': - optional: true - - '@oven/bun-linux-x64@1.3.11': - optional: true - - '@oven/bun-windows-aarch64@1.3.11': - optional: true - - '@oven/bun-windows-x64-baseline@1.3.11': - optional: true - - '@oven/bun-windows-x64@1.3.11': - optional: true - '@pagefind/darwin-arm64@1.5.2': optional: true @@ -9711,20 +9218,10 @@ snapshots: '@sandbox-agent/cli-win32-x64': 0.4.2 optional: true - '@secure-exec/core@0.0.0-main.f183ed2': - dependencies: - '@rivetkit/bare-ts': 0.6.2 - '@secure-exec/sidecar': 0.0.0-main.f183ed2 - zod: 4.3.6 - '@secure-exec/core@0.2.1': dependencies: better-sqlite3: 12.8.0 - '@secure-exec/google-drive@0.0.0-main.f183ed2': - dependencies: - '@secure-exec/core': 0.0.0-main.f183ed2 - '@secure-exec/nodejs@0.2.1': dependencies: '@secure-exec/core': 0.2.1 @@ -9736,45 +9233,6 @@ snapshots: node-stdlib-browser: 1.3.1 web-streams-polyfill: 4.2.0 - '@secure-exec/s3@0.0.0-main.f183ed2': - dependencies: - '@secure-exec/core': 0.0.0-main.f183ed2 - - '@secure-exec/sandbox@0.0.0-main.f183ed2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6)': - dependencies: - '@secure-exec/core': 0.0.0-main.f183ed2 - sandbox-agent: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) - transitivePeerDependencies: - - '@cloudflare/sandbox' - - '@daytonaio/sdk' - - '@e2b/code-interpreter' - - '@fly/sprites' - - '@vercel/sandbox' - - computesdk - - dockerode - - get-port - - modal - - zod - - '@secure-exec/sidecar-darwin-arm64@0.0.0-main.f183ed2': - optional: true - - '@secure-exec/sidecar-darwin-x64@0.0.0-main.f183ed2': - optional: true - - '@secure-exec/sidecar-linux-arm64-gnu@0.0.0-main.f183ed2': - optional: true - - '@secure-exec/sidecar-linux-x64-gnu@0.0.0-main.f183ed2': - optional: true - - '@secure-exec/sidecar@0.0.0-main.f183ed2': - optionalDependencies: - '@secure-exec/sidecar-darwin-arm64': 0.0.0-main.f183ed2 - '@secure-exec/sidecar-darwin-x64': 0.0.0-main.f183ed2 - '@secure-exec/sidecar-linux-arm64-gnu': 0.0.0-main.f183ed2 - '@secure-exec/sidecar-linux-x64-gnu': 0.0.0-main.f183ed2 - '@secure-exec/v8-darwin-arm64@0.2.1': optional: true @@ -10836,21 +10294,6 @@ snapshots: builtin-status-codes@3.0.0: {} - bun@1.3.11: - optionalDependencies: - '@oven/bun-darwin-aarch64': 1.3.11 - '@oven/bun-darwin-x64': 1.3.11 - '@oven/bun-darwin-x64-baseline': 1.3.11 - '@oven/bun-linux-aarch64': 1.3.11 - '@oven/bun-linux-aarch64-musl': 1.3.11 - '@oven/bun-linux-x64': 1.3.11 - '@oven/bun-linux-x64-baseline': 1.3.11 - '@oven/bun-linux-x64-musl': 1.3.11 - '@oven/bun-linux-x64-musl-baseline': 1.3.11 - '@oven/bun-windows-aarch64': 1.3.11 - '@oven/bun-windows-x64': 1.3.11 - '@oven/bun-windows-x64-baseline': 1.3.11 - bundle-require@5.1.0(esbuild@0.27.4): dependencies: esbuild: 0.27.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index be26a521e..0bb1e2f3b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,7 +13,6 @@ packages: - packages/shell - packages/sidecar-binary - examples/* - - registry/agent/* - scripts/publish - website diff --git a/registry/.gitignore b/registry/.gitignore deleted file mode 100644 index ceddaa37f..000000000 --- a/registry/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.cache/ diff --git a/registry/agent/claude/package.json b/registry/agent/claude/package.json deleted file mode 100644 index ce6bf482e..000000000 --- a/registry/agent/claude/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@agentos-software/claude-code", - "version": "0.2.0-rc.3", - "type": "module", - "license": "Apache-2.0", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "bin": { - "claude-sdk-acp": "./dist/adapter.js" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc && node ./scripts/build-patched-cli.mjs", - "check-types": "tsc --noEmit", - "test": "pnpm build && node --test tests/*.test.mjs" - }, - "dependencies": { - "@agentclientprotocol/sdk": "^0.16.1", - "@anthropic-ai/claude-agent-sdk": "^0.2.87", - "@rivet-dev/agentos-core": "workspace:*", - "zod": "^4.1.11" - }, - "devDependencies": { - "@types/node": "^22.10.2", - "typescript": "^5.7.2" - } -} diff --git a/registry/agent/claude/scripts/build-patched-cli.mjs b/registry/agent/claude/scripts/build-patched-cli.mjs deleted file mode 100644 index af073a271..000000000 --- a/registry/agent/claude/scripts/build-patched-cli.mjs +++ /dev/null @@ -1,342 +0,0 @@ -#!/usr/bin/env node - -import { createHash } from "node:crypto"; -import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { createRequire } from "node:module"; -import { dirname, resolve as resolvePath } from "node:path"; - -const require = createRequire(import.meta.url); -const sdkPath = require.resolve("@anthropic-ai/claude-agent-sdk"); -const cliPath = resolvePath(dirname(sdkPath), "cli.js"); -const distDir = resolvePath(import.meta.dirname, "..", "dist"); -const manifestPath = resolvePath(distDir, "claude-cli-patched.json"); -const sdkManifestPath = resolvePath(distDir, "claude-sdk-patched.json"); - -const source = readFileSync(cliPath, "utf-8"); -const sdkSource = readFileSync(sdkPath, "utf-8"); -const patches = [ - { - name: "inject stdout normalization helpers", - needle: 'import{createRequire as H15}from"node:module";', - replacement: - 'import{createRequire as H15}from"node:module";if(process.env.CLAUDE_CODE_TRACE_STARTUP==="1")process.stderr.write("[agentos-claude] bootstrap_loaded\\n");function __agentOsTrimOutput(q){if(typeof q==="string")return q.trim();if(q==null)return"";if(typeof q.trim==="function")return q.trim();if(typeof Buffer!=="undefined"&&Buffer.isBuffer(q))return q.toString("utf8").trim();if(q instanceof Uint8Array)return Buffer.from(q).toString("utf8").trim();return String(q).trim()}function __agentOsTrimStdout(q){return __agentOsTrimOutput(q?.stdout)}function __agentOsTrimStderr(q){return __agentOsTrimOutput(q?.stderr)}const __agentOsOriginalRealpath=globalThis["Nkq"];async function __agentOsRealpath(q){return typeof __agentOsOriginalRealpath==="function"?__agentOsOriginalRealpath(q):q}function __agentOsEnsureAsyncIterable(q){if(q&&typeof q[Symbol.asyncIterator]==="function")return q;if(q&&typeof q[Symbol.iterator]==="function")return(async function*(){for(let K of q)yield K})();if(q&&typeof q.on==="function"){return{[Symbol.asyncIterator](){let K=[],_=[],Y=!1,z=null;let A=(X)=>{K.push(X);O()};let O=()=>{for(;_.length>0;){if(z){_.shift().reject(z);continue}if(K.length>0){_.shift().resolve({done:!1,value:K.shift()});continue}if(Y){_.shift().resolve({done:!0,value:void 0});continue}break}};let $=()=>{Y=!0;w()};let w=()=>{q.off?.("data",A);q.off?.("end",$);q.off?.("close",$);q.off?.("error",j)};let j=(X)=>{z=X,Y=!0,w(),O()};q.on("data",A);q.on("end",$);q.on("close",$);q.on("error",j);q.resume?.();return{next(){if(z)return Promise.reject(z);if(K.length>0)return Promise.resolve({done:!1,value:K.shift()});if(Y)return Promise.resolve({done:!0,value:void 0});return new Promise((X,M)=>{_.push({resolve:X,reject:M})})},return(){Y=!0,w(),O();return Promise.resolve({done:!0,value:void 0})},[Symbol.asyncIterator](){return this}}}}}throw new TypeError("expected input to be async iterable")}', - }, - { - name: "ignore startup exit code in headless mode", - needle: - 'if(process.exitCode!==void 0){k("Graceful shutdown initiated, skipping further initialization");return}', - replacement: - 'if(process.exitCode!==void 0){if(process.env.CLAUDE_CODE_IGNORE_STARTUP_EXIT_CODE!=="1"){k("Graceful shutdown initiated, skipping further initialization");return}process.stderr.write("[agentos-claude] ignoring_startup_exit_code "+String(process.exitCode)+"\\n");process.exitCode=void 0}', - }, - { - name: "guard EPIPE destroy calls for Agent OS stdio", - needle: - 'function ow7(q){return(K)=>{if(K.code==="EPIPE")q.destroy()}}', - replacement: - 'function ow7(q){return(K)=>{if(K.code==="EPIPE")typeof q.destroy=="function"?q.destroy():typeof q.end=="function"&&q.end()}}', - }, - { - name: "guard buffered stream destroy helper", - needle: - 'Mr8=async(q,K)=>{if(!q||K===void 0)return;await Kz5(0),q.destroy();try{return await K}catch(_){return _.bufferedData}}', - replacement: - 'Mr8=async(q,K)=>{if(!q||K===void 0)return;await Kz5(0),typeof q.destroy=="function"&&q.destroy();try{return await K}catch(_){return _.bufferedData}}', - }, - { - name: "force Agent OS ripgrep when requested", - needle: - 'KG8=Y1(()=>{if(V_(process.env.USE_BUILTIN_RIPGREP)){let{cmd:Y}=x_8("rg",[]);if(Y!=="rg")return{mode:"system",command:"rg",args:[]}}if(lw())return{mode:"embedded",command:process.execPath,args:["--no-config"],argv0:"rg"};let K=Z16.resolve(dy_,"vendor","ripgrep");return{mode:"builtin",command:process.platform==="win32"?Z16.resolve(K,`${process.arch}-win32`,"rg.exe"):Z16.resolve(K,`${process.arch}-${process.platform}`,"rg"),args:[]}});', - replacement: - 'KG8=Y1(()=>{if(process.env.CLAUDE_CODE_FORCE_AGENT_OS_RIPGREP==="1")return{mode:"system",command:"rg",args:[]};if(V_(process.env.USE_BUILTIN_RIPGREP)){let{cmd:Y}=x_8("rg",[]);if(Y!=="rg")return{mode:"system",command:"rg",args:[]}}if(lw())return{mode:"embedded",command:process.execPath,args:["--no-config"],argv0:"rg"};let K=Z16.resolve(dy_,"vendor","ripgrep");return{mode:"builtin",command:process.platform==="win32"?Z16.resolve(K,`${process.arch}-win32`,"rg.exe"):Z16.resolve(K,`${process.arch}-${process.platform}`,"rg"),args:[]}});', - }, - { - name: "guard missing events.setMaxListeners export", - needle: - 'function C3(q=Xi_){let K=new AbortController;return Ji_(q,K.signal),K}', - replacement: - 'function C3(q=Xi_){let K=new AbortController;return typeof Ji_=="function"&&Ji_(q,K.signal),K}', - }, - { - name: "fix shell snapshot login shell argument ordering", - needle: 'O9Y(q,["-c","-l",j],{env:{', - replacement: 'O9Y(q,["-l","-c",j],{env:{', - }, - { - name: "fix bash provider spawn argument ordering", - needle: - 'return["-c",...O?[]:["-l"],A]},async getEnvironmentOverrides(A){', - replacement: - 'return O?["-c",A]:["-l","-c",A]},async getEnvironmentOverrides(A){', - }, - { - name: "force pipe-mode bash output under Agent OS", - needle: - 'L=await J.getEnvironmentOverrides(q),S=!!j,h=JL("local_bash"),x=new iA(h,A??null,!S);', - replacement: - 'L=await J.getEnvironmentOverrides(q),S=!!j||process.env.CLAUDE_CODE_USE_PIPE_OUTPUT==="1",h=JL("local_bash"),x=new iA(h,A??null,!S);', - }, - { - name: "disable /dev/null shell redirection under Agent OS", - needle: 'if(K)return Gq([q,"<","/dev/null"]);return Gq([q])', - replacement: - 'if(process.env.CLAUDE_CODE_DISABLE_DEV_NULL_REDIRECT==="1")return Gq([q]);if(K)return Gq([q,"<","/dev/null"]);return Gq([q])', - }, - { - name: "disable cwd persistence checkpoint under Agent OS", - needle: 'let Z=await lNq();if(Z)W.push(Z);let f=v9Y(q);if(f)W.push(f);W.push(`eval ${P}`),W.push(`pwd -P >| ${Gq([J])}`);let G=W.join(" && ");', - replacement: - 'let Z=await lNq();if(Z)W.push(Z);let f=v9Y(q);if(f)W.push(f);W.push(`eval ${P}`),process.env.CLAUDE_CODE_DISABLE_CWD_PERSIST!=="1"&&W.push(`pwd -P >| ${Gq([J])}`);let G=W.join(" && ");', - }, - { - name: "use direct shell command execution under Agent OS", - needle: - 'let G=W.join(" && ");if(process.env.CLAUDE_CODE_SHELL_PREFIX)G=rN8(process.env.CLAUDE_CODE_SHELL_PREFIX,G);', - replacement: - 'let G=W.join(" && ");if(process.env.CLAUDE_CODE_SIMPLE_SHELL_EXEC==="1")G=A;if(process.env.CLAUDE_CODE_SHELL_PREFIX)G=rN8(process.env.CLAUDE_CODE_SHELL_PREFIX,G);', - }, - { - name: "trace bash shell spawn configuration", - needle: - 'let V=G?"/bin/sh":f,N=G?["-c",W]:J.getSpawnArgs(W),L=await J.getEnvironmentOverrides(q),S=!!j||process.env.CLAUDE_CODE_USE_PIPE_OUTPUT==="1",h=JL("local_bash"),x=new iA(h,A??null,!S);', - replacement: - 'let V=G?"/bin/sh":f,N=G?["-c",W]:J.getSpawnArgs(W),L=await J.getEnvironmentOverrides(q),S=!!j||process.env.CLAUDE_CODE_USE_PIPE_OUTPUT==="1";if(!G&&process.env.CLAUDE_CODE_FORCE_SH_FOR_BASH==="1")V="/bin/sh",N=["-c",W];if(process.env.CLAUDE_CODE_TRACE_BASH_SHELL==="1")process.stderr.write("[agentos-claude] bash_spawn_config "+JSON.stringify({shell:V,args:N,cwd:Z,command:W,envOverrides:L,pipeOutput:S})+"\\n");let h=JL("local_bash"),x=new iA(h,A??null,!S);', - }, - { - name: "trace bash shell child process output", - needle: - 'try{let p=h9Y(V,N,{env:{...Vu(),SHELL:_==="bash"?f:void 0,GIT_EDITOR:"true",CLAUDECODE:"1",...L,...{}},cwd:Z,stdio:S?["pipe","pipe","pipe"]:["pipe",I?.fd,I?.fd],detached:J.detached,windowsHide:!0}),B=aN8(p,K,H,x,w);', - replacement: - 'try{let BA=Vu(),BB=Object.entries(BA).filter(([Q,i])=>typeof i!=="string"&&i!==void 0),BC=Object.fromEntries(Object.entries(BA).filter(([Q,i])=>typeof i==="string")),BD={...BC,SHELL:_==="bash"?f:void 0,GIT_EDITOR:"true",CLAUDECODE:"1",...L,...{}};if(process.env.CLAUDE_CODE_TRACE_DIRECT_XU==="1"){let Q=h9Y("xu",["hello-agent-os"],{env:BD,cwd:Z,stdio:["pipe","pipe","pipe"],detached:!1,windowsHide:!0}),i="",R="";Q.stdout?.on("data",(k)=>i+=String(k)),Q.stderr?.on("data",(k)=>R+=String(k));let se=await new Promise((k)=>Q.on("close",(ve,ye)=>k({code:ve,signal:ye})));process.stderr.write("[agentos-claude] direct_xu "+JSON.stringify({stdout:i,stderr:R,...se})+"\\n")}let BE=V,BF=N,BG=BD;if(process.env.CLAUDE_CODE_NODE_SHELL_WRAPPER==="1"){let Q=\'const{spawn}=require("child_process");const cmd=process.env.CLAUDE_CODE_NODE_SHELL_COMMAND||"";const child=spawn(cmd,[],{cwd:process.env.CLAUDE_CODE_NODE_SHELL_CWD||process.cwd(),env:process.env,shell:true,stdio:["ignore","pipe","pipe"],windowsHide:true});child.stdout?.on("data",(c)=>process.stdout.write(c));child.stderr?.on("data",(c)=>process.stderr.write(c));child.on("close",(code)=>process.exit(typeof code==="number"?code:1));child.on("error",(error)=>{process.stderr.write(String(error?.stack??error)+"\\\\n");process.exit(126)});\';BE=process.execPath||"node",BF=["-e",Q],BG={...BD,CLAUDE_CODE_NODE_SHELL_COMMAND:W,CLAUDE_CODE_NODE_SHELL_CWD:Z};if(process.env.CLAUDE_CODE_TRACE_BASH_SHELL==="1")process.stderr.write("[agentos-claude] bash_node_wrapper "+JSON.stringify({command:W,cwd:Z,exec:BE})+"\\n")}let p=h9Y(BE,BF,{env:BG,cwd:Z,stdio:S?["pipe","pipe","pipe"]:["pipe",I?.fd,I?.fd],detached:J.detached,windowsHide:!0});if(process.env.CLAUDE_CODE_TRACE_BASH_SHELL==="1")(BB.length>0&&process.stderr.write("[agentos-claude] bash_non_string_env "+JSON.stringify(BB.map(([Q,i])=>[Q,typeof i]))+"\\n"),process.stderr.write("[agentos-claude] bash_spawned "+JSON.stringify({pid:p.pid,shell:BE,args:BF})+"\\n"),p.stdout?.on("data",(Q)=>process.stderr.write("[agentos-claude] bash_stdout "+JSON.stringify(String(Q))+"\\n")),p.stderr?.on("data",(Q)=>process.stderr.write("[agentos-claude] bash_stderr "+JSON.stringify(String(Q))+"\\n")),p.on("exit",(Q,i)=>process.stderr.write("[agentos-claude] bash_exit "+JSON.stringify({code:Q,signal:i})+"\\n")));let B=aN8(p,K,H,x,w);', - }, - { - name: "skip special CLI entrypoints under Agent OS", - needle: - 'if(K("cli_entry"),process.argv[2]==="--claude-in-chrome-mcp"){', - replacement: - 'let __agentOsArg2=process.argv[2];if(process.env.CLAUDE_CODE_TRACE_STARTUP==="1")process.stderr.write("[agentos-claude] cli_argv "+JSON.stringify(process.argv)+"\\n");if(K("cli_entry"),process.env.CLAUDE_CODE_SKIP_SPECIAL_ENTRYPOINTS==="1"&&(__agentOsArg2==="--claude-in-chrome-mcp"||__agentOsArg2==="--chrome-native-host"||__agentOsArg2==="--computer-use-mcp"))process.stderr.write("[agentos-claude] skip_special_entrypoint "+String(__agentOsArg2)+"\\n");else if(process.argv[2]==="--claude-in-chrome-mcp"){', - }, - { - name: "trace message loop startup", - needle: 'n8("info","cli_message_loop_started");', - replacement: - 'process.stderr.write("[agentos-claude] cli_message_loop_started\\n"),n8("info","cli_message_loop_started");', - }, - { - name: "trace stdin message parsing", - needle: 'if(z)n8("info","cli_stdin_message_parsed",{type:z.type}),yield z', - replacement: - 'if(z)(process.stderr.write("[agentos-claude] cli_stdin_message_parsed "+z.type+"\\n"),n8("info","cli_stdin_message_parsed",{type:z.type}),yield z)', - }, - { - name: "coerce structured IO input streams into async iterables", - needle: "yield*K();for await(let _ of this.input)q+=_,yield*K();if(q){", - replacement: - "yield*K();for await(let _ of __agentOsEnsureAsyncIterable(this.input))q+=_,yield*K();if(q){", - }, - { - name: "trace initialize request handling", - needle: - 'if(await Xcz(h6.request,h6.request_id,L6,f,_,I,q,!!H.enableAuthStatus,H,j,$),h6.request.promptSuggestions)', - replacement: - 'if(process.stderr.write("[agentos-claude] initialize_request_start\\n"),await Xcz(h6.request,h6.request_id,L6,f,_,I,q,!!H.enableAuthStatus,H,j,$),process.stderr.write("[agentos-claude] initialize_request_done\\n"),h6.request.promptSuggestions)', - }, - { - name: "trace pre-runHeadlessStreaming bootstrap", - needle: - 'aw7(),oJ("after_loadInitialMessages"),await jw8(),oJ("after_modelStrings");', - replacement: - 'aw7(),oJ("after_loadInitialMessages"),process.stderr.write("[agentos-claude] before_ensureModelStrings\\n"),await jw8(),process.stderr.write("[agentos-claude] after_ensureModelStrings\\n"),oJ("after_modelStrings");', - }, - { - name: "trace before runHeadlessStreaming consumption", - needle: 'oJ("before_runHeadlessStreaming");', - replacement: - 'process.stderr.write("[agentos-claude] before_runHeadlessStreaming\\n"),oJ("before_runHeadlessStreaming");', - }, - { - name: "trace runHeadless entry", - needle: 'oJ("runHeadless_entry"),', - replacement: - 'process.stderr.write("[agentos-claude] runHeadless_entry\\n"),oJ("runHeadless_entry"),', - }, - { - name: "trace after grove check", - needle: 'oJ("after_grove_check"),', - replacement: - 'process.stderr.write("[agentos-claude] after_grove_check\\n"),oJ("after_grove_check"),', - }, - { - name: "defer growthbook init when requested", - needle: - 'process.stderr.write("[agentos-claude] after_grove_check\\n"),oJ("after_grove_check"),Zi(),$.resumeSessionAt&&!$.resume){', - replacement: - 'process.stderr.write("[agentos-claude] after_grove_check\\n"),oJ("after_grove_check"),process.env.CLAUDE_CODE_DEFER_GROWTHBOOK_INIT==="1"?queueMicrotask(()=>{void Zi()}):Zi(),$.resumeSessionAt&&!$.resume){', - }, - { - name: "trace structured IO and stream-json guard setup", - needle: - 'let w=Wcz(q,$);if($.outputFormat==="stream-json")VeK();let j=w7.getSandboxUnavailableReason();', - replacement: - 'let w=Wcz(q,$);process.stderr.write("[agentos-claude] after_structured_io\\n");if($.outputFormat==="stream-json")(process.stderr.write("[agentos-claude] before_stream_json_guard\\n"),VeK(),process.stderr.write("[agentos-claude] after_stream_json_guard\\n"));let j=w7.getSandboxUnavailableReason();process.stderr.write("[agentos-claude] after_sandbox_reason\\n");', - }, - { - name: "allow skipping Claude sandbox initialization", - needle: - 'else if(w7.isSandboxingEnabled())try{await w7.initialize(w.createSandboxAskCallback())}catch(x){process.stderr.write(`\n❌ Sandbox Error: ${i6(x)}\n`),iK(1,"other");return}', - replacement: - 'else if(w7.isSandboxingEnabled())if(process.env.CLAUDE_CODE_SKIP_SANDBOX_INIT==="1")process.stderr.write("[agentos-claude] sandbox_init_skipped\\n");else try{process.stderr.write("[agentos-claude] before_sandbox_init\\n");await w7.initialize(w.createSandboxAskCallback());process.stderr.write("[agentos-claude] after_sandbox_init\\n")}catch(x){process.stderr.write(`\n❌ Sandbox Error: ${i6(x)}\n`),iK(1,"other");return}', - }, - { - name: "gate stream-json hook event forwarding behind opt-out env var", - needle: 'if($.outputFormat==="stream-json"&&$.verbose)JMK((x)=>{', - replacement: - 'if($.outputFormat==="stream-json"&&$.verbose&&process.env.AGENT_OS_CLAUDE_DISABLE_HOOK_EVENTS!=="1")JMK((x)=>{', - }, - { - name: "trace before loadInitialMessages", - needle: 'if($.setupTrigger)await cI8($.setupTrigger);oJ("before_loadInitialMessages");', - replacement: - 'process.stderr.write("[agentos-claude] after_hook_event_registration\\n");if($.setupTrigger)(process.stderr.write("[agentos-claude] before_setup_hooks\\n"),await cI8($.setupTrigger),process.stderr.write("[agentos-claude] after_setup_hooks\\n"));process.stderr.write("[agentos-claude] before_loadInitialMessages\\n"),oJ("before_loadInitialMessages");', - }, - { - name: "trace after loadInitialMessages returns", - needle: - 'let H=K(),{messages:J,turnInterruptionState:X,agentSetting:M}=await Pcz(_,{continue:$.continue,teleport:$.teleport,resume:$.resume,resumeSessionAt:$.resumeSessionAt,forkSession:$.forkSession,outputFormat:$.outputFormat,sessionStartHooksPromise:$.sessionStartHooksPromise,restoredWorkerState:w.restoredWorkerState}),D=cYK();', - replacement: - 'let H=K(),{messages:J,turnInterruptionState:X,agentSetting:M}=process.env.CLAUDE_CODE_SKIP_INITIAL_MESSAGES==="1"?(process.stderr.write("[agentos-claude] skip_initial_messages\\n"),{messages:[],turnInterruptionState:void 0,agentSetting:void 0}):await Pcz(_,{continue:$.continue,teleport:$.teleport,resume:$.resume,resumeSessionAt:$.resumeSessionAt,forkSession:$.forkSession,outputFormat:$.outputFormat,sessionStartHooksPromise:$.sessionStartHooksPromise,restoredWorkerState:w.restoredWorkerState}),D=(process.stderr.write("[agentos-claude] after_loadInitialMessages_return messages="+J.length+" exit="+String(process.exitCode)+" agent="+String(M)+"\\n"),cYK());', - }, - { - name: "trace prepend initial user message", - needle: "if(D)w.prependUserMessage(D);", - replacement: - 'if(D)(process.stderr.write("[agentos-claude] prepend_initial_user_message\\n"),w.prependUserMessage(D));', - }, - { - name: "trace after agent restore block", - needle: - 'if(!$.agent&&!NB()&&M){let{agentDefinition:x}=KJ6(M,void 0,{activeAgents:O,allAgents:O});if(x){if(_((I)=>({...I,agent:x.agentType})),!$.systemPrompt&&!Pw(x)){let I=x.getSystemPrompt();if(I)$.systemPrompt=I}$78(x.agentType)}}if(J.length===0&&process.exitCode!==void 0)return;', - replacement: - 'if(!$.agent&&!NB()&&M){let{agentDefinition:x}=KJ6(M,void 0,{activeAgents:O,allAgents:O});if(x){if(_((I)=>({...I,agent:x.agentType})),!$.systemPrompt&&!Pw(x)){let I=x.getSystemPrompt();if(I)$.systemPrompt=I}$78(x.agentType)}}process.stderr.write("[agentos-claude] after_agent_restore_block\\n");if(J.length===0&&process.exitCode!==void 0){if(process.env.CLAUDE_CODE_IGNORE_STARTUP_EXIT_CODE==="1"){process.stderr.write("[agentos-claude] ignoring_post_initial_exit_code "+String(process.exitCode)+"\\n");process.exitCode=void 0}else return;}', - }, - { - name: "trace before tool filtering", - needle: 'let Z=L68(H.mcp.tools,H.toolPermissionContext),', - replacement: - 'process.stderr.write("[agentos-claude] before_tool_filtering\\n");let Z=L68(H.mcp.tools,H.toolPermissionContext),', - }, - { - name: "trace after permission handler setup", - needle: - 'if($.permissionPromptToolName)f=f.filter((x)=>!L_(x,$.permissionPromptToolName));aw7(),', - replacement: - 'if($.permissionPromptToolName)f=f.filter((x)=>!L_(x,$.permissionPromptToolName));process.stderr.write("[agentos-claude] after_permission_handler_setup\\n"),aw7(),', - }, - { - name: "trace after registerProcessOutputErrorHandlers", - needle: 'aw7(),oJ("after_loadInitialMessages"),', - replacement: - 'aw7(),process.stderr.write("[agentos-claude] after_registerProcessOutputErrorHandlers\\n"),oJ("after_loadInitialMessages"),', - }, - { - name: "trace before connectMcp", - needle: 'xq("before_connectMcp"),await z5(H3,"regular"),', - replacement: - 'process.stderr.write("[agentos-claude] before_connectMcp\\n"),xq("before_connectMcp"),await z5(H3,"regular"),', - }, - { - name: "trace after connectMcp", - needle: 'xq("after_connectMcp"),await j6.then(', - replacement: - 'process.stderr.write("[agentos-claude] after_connectMcp\\n"),xq("after_connectMcp"),await j6.then(', - }, - { - name: "trace after claudeai MCP connect", - needle: 'xq("after_connectMcp_claudeai"),', - replacement: - 'process.stderr.write("[agentos-claude] after_connectMcp_claudeai\\n"),xq("after_connectMcp_claudeai"),', - }, - { - name: "trace before print import", - needle: 'S65(),xq("before_print_import");', - replacement: - 'S65(),process.stderr.write("[agentos-claude] before_print_import\\n"),xq("before_print_import");', - }, - { - name: "trace after print import", - needle: 'xq("after_print_import"),OK(', - replacement: - 'process.stderr.write("[agentos-claude] after_print_import\\n"),xq("after_print_import"),OK(', - }, - { - name: "trace early input capture and main import", - needle: - 'if(q.includes("--bare"))process.env.CLAUDE_CODE_SIMPLE="1";let{startCapturingEarlyInput:Y}=await Promise.resolve().then(() => (Jd6(),Dn4));Y(),K("cli_before_main_import");let{main:z}=await Promise.resolve().then(() => (eY7(),C65));K("cli_after_main_import"),await z(),K("cli_after_main_complete")', - replacement: - 'if(q.includes("--bare"))process.env.CLAUDE_CODE_SIMPLE="1";process.stderr.write("[agentos-claude] before_early_input_import\\n");let{startCapturingEarlyInput:Y}=await Promise.resolve().then(() => (Jd6(),Dn4));process.stderr.write("[agentos-claude] after_early_input_import\\n");if(process.env.CLAUDE_CODE_SKIP_EARLY_INPUT_CAPTURE==="1")process.stderr.write("[agentos-claude] skip_early_input_capture\\n");else{process.stderr.write("[agentos-claude] before_early_input_start\\n");Y();process.stderr.write("[agentos-claude] after_early_input_start\\n")}K("cli_before_main_import");process.stderr.write("[agentos-claude] before_main_import\\n");let{main:z}=await Promise.resolve().then(() => (eY7(),C65));process.stderr.write("[agentos-claude] after_main_import\\n");K("cli_after_main_import"),await z(),K("cli_after_main_complete")', - }, -]; - -let patched = source; -for (const patch of patches) { - if (!patched.includes(patch.needle)) { - throw new Error(`Could not find Claude CLI patch target: ${patch.name}`); - } - patched = patched.replace(patch.needle, patch.replacement); -} - -patched = patched.replace( - /\b([A-Za-z_$][\w$]*)\.stdout\.trim\(\)/g, - "__agentOsTrimStdout($1)", -); -patched = patched.replace( - /\b([A-Za-z_$][\w$]*)\.stderr\.trim\(\)/g, - "__agentOsTrimStderr($1)", -); -patched = patched.replace(/\bNkq\(/g, "__agentOsRealpath("); - -const streamJsonHookGuard = - 'if($.outputFormat==="stream-json"&&$.verbose&&process.env.AGENT_OS_CLAUDE_DISABLE_HOOK_EVENTS!=="1")JMK((x)=>{'; -if (!patched.includes(streamJsonHookGuard)) { - throw new Error( - "Patched Claude CLI is missing the AGENT_OS_CLAUDE_DISABLE_HOOK_EVENTS guard", - ); -} -if (patched.includes('if($.outputFormat==="stream-json"&&$.verbose&&false)JMK((x)=>{')) { - throw new Error( - "Patched Claude CLI still contains the disabled stream-json hook-event kill-switch", - ); -} - -const sdkNeedle = - 'function y1($=AL){let X=new AbortController;return ML($,X.signal),X}'; -const sdkReplacement = - 'function y1($=AL){let X=new AbortController;return typeof ML==="function"&&ML($,X.signal),X}'; -const patchedSdk = sdkSource.includes(sdkNeedle) - ? sdkSource.replace(sdkNeedle, sdkReplacement) - : sdkSource; - -mkdirSync(distDir, { recursive: true }); -const hash = createHash("sha256").update(patched).digest("hex").slice(0, 16); -const fileName = `claude-cli-patched-${hash}.mjs`; -const outputPath = resolvePath(distDir, fileName); -writeFileSync(outputPath, patched, "utf-8"); - -const sdkHash = createHash("sha256") - .update(patchedSdk) - .digest("hex") - .slice(0, 16); -const sdkFileName = `claude-sdk-patched-${sdkHash}.mjs`; -const sdkOutputPath = resolvePath(distDir, sdkFileName); -writeFileSync(sdkOutputPath, patchedSdk, "utf-8"); - -writeFileSync( - manifestPath, - JSON.stringify({ entry: `./${fileName}` }, null, 2) + "\n", - "utf-8", -); -writeFileSync( - sdkManifestPath, - JSON.stringify({ entry: `./${sdkFileName}` }, null, 2) + "\n", - "utf-8", -); -process.stdout.write(`Wrote ${outputPath}\n`); diff --git a/registry/agent/claude/src/adapter.ts b/registry/agent/claude/src/adapter.ts deleted file mode 100644 index 0f4017a8e..000000000 --- a/registry/agent/claude/src/adapter.ts +++ /dev/null @@ -1,1300 +0,0 @@ -#!/usr/bin/env node - -import { - type Agent, - AgentSideConnection, - type AuthenticateRequest, - type AuthenticateResponse, - type CancelNotification, - type InitializeRequest, - type InitializeResponse, - type NewSessionRequest, - type NewSessionResponse, - type PromptRequest, - type PromptResponse, - type RequestPermissionResponse, - type SessionUpdate, - type SetSessionModeRequest, - type SetSessionModeResponse, - ndJsonStream, -} from "@agentclientprotocol/sdk"; -import { - type CanUseTool, - type McpServerConfig, - type PermissionMode, - type PermissionResult, - type PermissionUpdate, - type Query, - type SDKUserMessage, -} from "@anthropic-ai/claude-agent-sdk"; -import { createHash, randomUUID } from "node:crypto"; -import { - appendFileSync, - existsSync, - mkdirSync, - writeFileSync, -} from "node:fs"; -import { spawn } from "node:child_process"; -import { createRequire } from "node:module"; -import { tmpdir } from "node:os"; -import { dirname, isAbsolute, resolve as resolvePath } from "node:path"; -import { PassThrough } from "node:stream"; -import { fileURLToPath } from "node:url"; -import { resolveClaudeCliPath, resolveClaudeSdkPath } from "./patched-cli.js"; - -type PromptPart = { type?: string; text?: string }; -type ClaudeSdkRuntime = Awaited; -type QueryFactory = ClaudeSdkRuntime["query"]; - -const ACP_MODES: PermissionMode[] = [ - "default", - "acceptEdits", - "bypassPermissions", - "plan", - "dontAsk", -]; - -let appendSystemPrompt: string | undefined; -const argv = process.argv.slice(2); -for (let i = 0; i < argv.length; i++) { - if (argv[i] === "--append-system-prompt" && i + 1 < argv.length) { - appendSystemPrompt = argv[i + 1]; - i++; - } -} - -const claudeSdkRuntimePromise = loadPatchedClaudeSdkRuntime(); -const traceAdapterMessages = - process.env.CLAUDE_CODE_TRACE_ADAPTER_MESSAGES === "1"; -const traceFile = process.env.CLAUDE_CODE_TRACE_FILE; - -function traceAdapter(message: string): void { - if (!traceAdapterMessages) return; - process.stderr.write(`[agentos-claude] ${message}\n`); - if (traceFile) { - appendFileSync(traceFile, `[agentos-claude] ${message}\n`); - } -} - -async function loadPatchedClaudeSdkRuntime(): Promise< - { - cliPath: string; - query: typeof import("@anthropic-ai/claude-agent-sdk").query; - } -> { - const require = createRequire(import.meta.url); - const sdkPath = require.resolve("@anthropic-ai/claude-agent-sdk"); - const packageDir = resolvePath(dirname(fileURLToPath(import.meta.url)), ".."); - const cliPath = resolveClaudeCliPath({ packageDir, sdkPath }); - const runtime = await import(resolveClaudeSdkPath({ packageDir, sdkPath })); - return { - cliPath, - query: runtime.query, - }; -} - -function ensureClaudeCliWrapper(originalCliPath: string): string { - const cacheDir = resolvePath(tmpdir(), "agentos-claude-sdk"); - mkdirSync(cacheDir, { recursive: true }); - -const wrapperSource = `#!/usr/bin/env node -import { appendFileSync as __agentOsAppendFileSync } from "node:fs"; -import { inspect } from "node:util"; -import { PassThrough } from "node:stream"; - -const originalCliPath = ${JSON.stringify(originalCliPath)}; -const swapStdio = process.env.CLAUDE_CODE_SWAP_STDIO === "1"; -const traceExit = process.env.CLAUDE_CODE_TRACE_EXIT === "1"; -const traceStdio = process.env.CLAUDE_CODE_TRACE_STDIO === "1"; -const traceFile = process.env.CLAUDE_CODE_TRACE_FILE; -const realStdout = process.stdout; -const realStderr = process.stderr; - -function wrapperTrace(message) { - if (traceFile) { - try { - __agentOsAppendFileSync(traceFile, message + "\\n"); - } catch {} - } -} - -if (swapStdio) { - Object.defineProperty(process, "stdout", { - configurable: true, - enumerable: true, - value: realStderr, - }); - Object.defineProperty(process, "stderr", { - configurable: true, - enumerable: true, - value: realStdout, - }); -} - -process.stderr.write( - "[agentos-claude] wrapper_start cli=" + originalCliPath + "\\n", -); -wrapperTrace( - "[agentos-claude] wrapper_start cli=" + originalCliPath, -); -process.stderr.write( - "[agentos-claude] wrapper_argv " + - JSON.stringify(process.argv) + - "\\n", -); -wrapperTrace( - "[agentos-claude] wrapper_argv " + JSON.stringify(process.argv), -); - -process.on("unhandledRejection", (error) => { - process.stderr.write( - "[agentos-claude] unhandledRejection " + inspect(error, { depth: 6 }) + "\\n", - ); -}); - -process.on("uncaughtException", (error) => { - process.stderr.write( - "[agentos-claude] uncaughtException " + inspect(error, { depth: 6 }) + "\\n", - ); -}); - -function writeTrace(label, value) { - if (!traceExit) return; - const stack = new Error().stack ?? ""; - process.stderr.write( - "[agentos-claude] " + - label + - " " + - inspect(value, { depth: 4, breakLength: Infinity }) + - "\\n" + - stack + - "\\n", - ); -} - -let exitCodeValue = process.exitCode; -if ( - process.env.CLAUDE_CODE_IGNORE_STARTUP_EXIT_CODE === "1" && - exitCodeValue === 0 -) { - exitCodeValue = undefined; -} -Object.defineProperty(process, "exitCode", { - configurable: true, - enumerable: true, - get() { - return exitCodeValue; - }, - set(value) { - writeTrace("process.exitCode =", value); - exitCodeValue = value; - }, -}); - -const originalExit = process.exit.bind(process); -process.exit = (code) => { - writeTrace("process.exit()", code); - return originalExit(code); -}; - -const realStdin = process.stdin; -const bufferedStdin = new PassThrough(); -bufferedStdin.isTTY = realStdin.isTTY; -bufferedStdin.fd = realStdin.fd; -if (typeof realStdin.setRawMode === "function") { - bufferedStdin.setRawMode = realStdin.setRawMode.bind(realStdin); -} -realStdin.on("data", (chunk) => { - bufferedStdin.write(chunk); -}); -realStdin.on("end", () => { - bufferedStdin.end(); -}); -realStdin.on("error", (error) => { - bufferedStdin.destroy(error); -}); -realStdin.resume(); -Object.defineProperty(process, "stdin", { - configurable: true, - enumerable: true, - value: bufferedStdin, -}); - -for (const stream of [process.stdout, process.stderr]) { - if (typeof stream.destroy !== "function") { - stream.destroy = (error) => { - if (error) { - stream.emit("error", error); - return stream; - } - if (typeof stream.end === "function") { - stream.end(); - } - return stream; - }; - } -} - -if (traceStdio) { - realStdin.on("data", (chunk) => { - process.stderr.write( - "[agentos-claude] stdin_chunk " + - JSON.stringify(String(chunk)).slice(0, 4000) + - "\\n", - ); - }); - - const originalStdoutWrite = process.stdout.write.bind(process.stdout); - process.stdout.write = function (chunk, encoding, callback) { - process.stderr.write( - "[agentos-claude] stdout_chunk " + - JSON.stringify( - typeof chunk === "string" - ? chunk - : Buffer.from(chunk).toString( - typeof encoding === "string" ? encoding : undefined, - ), - ).slice(0, 4000) + - "\\n", - ); - return originalStdoutWrite(chunk, encoding, callback); - }; -} - -await import(originalCliPath); -`; - - const wrapperHash = createHash("sha256") - .update(originalCliPath) - .update(wrapperSource) - .digest("hex") - .slice(0, 16); - const wrapperPath = resolvePath(cacheDir, `cli-wrapper-${wrapperHash}.mjs`); - if (!existsSync(wrapperPath)) { - writeFileSync(wrapperPath, wrapperSource, "utf-8"); - } - return wrapperPath; -} - -class AsyncQueue implements AsyncIterable { - private items: T[] = []; - private waiters: Array<(result: IteratorResult) => void> = []; - private closed = false; - - push(item: T): void { - if (this.closed) { - throw new Error("Queue is closed"); - } - const waiter = this.waiters.shift(); - if (waiter) { - waiter({ value: item, done: false }); - return; - } - this.items.push(item); - } - - end(): void { - if (this.closed) return; - this.closed = true; - for (const waiter of this.waiters.splice(0)) { - waiter({ value: undefined, done: true }); - } - } - - [Symbol.asyncIterator](): AsyncIterator { - return { - next: () => { - const item = this.items.shift(); - if (item !== undefined) { - return Promise.resolve({ value: item, done: false }); - } - if (this.closed) { - return Promise.resolve({ value: undefined, done: true }); - } - return new Promise>((resolve) => { - this.waiters.push(resolve); - }); - }, - }; - } -} - -type PendingTurn = { - resolve: (response: PromptResponse) => void; - reject: (error: Error) => void; - sawAssistantText: boolean; - sawToolCall: boolean; -}; - -class ClaudeQuerySession { - private promptQueue = new AsyncQueue(); - private query: Query; - private readyPromise: Promise; - private pendingTurn: PendingTurn | null = null; - private lastEmit: Promise = Promise.resolve(); - private pendingMcpServers: Record | undefined; - private mcpServersApplied = false; - private activeToolCalls = new Map< - string, - { toolName: string; rawInput?: Record } - >(); - private reader: Promise; - private closed = false; - private cancelled = false; - - constructor( - private readonly conn: AgentSideConnection, - readonly sessionId: string, - private readonly cwd: string, - private mode: PermissionMode, - params: NewSessionRequest, - private readonly pathToClaudeCodeExecutable: string, - queryFactory: QueryFactory, - ) { - traceAdapter( - `session_ctor id=${sessionId} cwd=${cwd} mode=${mode} cli=${pathToClaudeCodeExecutable}`, - ); - this.query = queryFactory({ - prompt: this.promptQueue, - options: { - canUseTool: this.createPermissionHandler(), - cwd, - env: { - ...process.env, - CLAUDE_CODE_SHELL: - process.env.CLAUDE_CODE_SHELL ?? "/bin/sh", - CLAUDE_CODE_IGNORE_STARTUP_EXIT_CODE: - process.env.CLAUDE_CODE_IGNORE_STARTUP_EXIT_CODE ?? "1", - CLAUDE_CODE_DISABLE_DEV_NULL_REDIRECT: - process.env.CLAUDE_CODE_DISABLE_DEV_NULL_REDIRECT ?? "1", - CLAUDE_CODE_DISABLE_CWD_PERSIST: - process.env.CLAUDE_CODE_DISABLE_CWD_PERSIST ?? "1", - CLAUDE_CODE_SIMPLE_SHELL_EXEC: - process.env.CLAUDE_CODE_SIMPLE_SHELL_EXEC ?? "1", - CLAUDE_CODE_SIMPLE: process.env.CLAUDE_CODE_SIMPLE ?? "1", - CLAUDE_CODE_NODE_SHELL_WRAPPER: - process.env.CLAUDE_CODE_NODE_SHELL_WRAPPER ?? "1", - CLAUDE_CODE_SKIP_INITIAL_MESSAGES: - process.env.CLAUDE_CODE_SKIP_INITIAL_MESSAGES ?? "1", - CLAUDE_CODE_SKIP_SPECIAL_ENTRYPOINTS: - process.env.CLAUDE_CODE_SKIP_SPECIAL_ENTRYPOINTS ?? "1", - CLAUDE_CODE_USE_PIPE_OUTPUT: - process.env.CLAUDE_CODE_USE_PIPE_OUTPUT ?? "1", - CLAUDE_CODE_TRACE_EXIT: - process.env.CLAUDE_CODE_TRACE_EXIT ?? "0", - CLAUDE_CODE_TRACE_STARTUP: - process.env.CLAUDE_CODE_TRACE_STARTUP ?? "0", - SHELL: process.env.SHELL ?? "/bin/sh", - }, - extraArgs: { - bare: null, - }, - includePartialMessages: true, - pathToClaudeCodeExecutable, - permissionMode: normalizeClaudePermissionMode(mode), - persistSession: false, - sandbox: { enabled: false }, - settingSources: ["project"], - spawnClaudeCodeProcess: ({ command, args, cwd, env }) => { - traceAdapter( - `spawn_child command=${command} args=${JSON.stringify(args)} cwd=${cwd}`, - ); - const childEnv: NodeJS.ProcessEnv = { - ...env, - CLAUDE_CODE_SWAP_STDIO: - env.CLAUDE_CODE_SWAP_STDIO ?? "0", - }; - const traceChildIo = - childEnv.CLAUDE_CODE_TRACE_CHILD_IO === "1" || - (traceAdapterMessages && Boolean(traceFile)); - const child = spawn(command, args, { - cwd, - env: childEnv, - stdio: ["pipe", "pipe", "pipe"], - }); - const stdout = new PassThrough(); - const lineBuffers: Record<"stdout" | "stderr", string> = { - stdout: "", - stderr: "", - }; - let openStreams = 2; - - const looksLikeProtocolLine = (line: string): boolean => { - const trimmed = line.trim(); - if (!trimmed.startsWith("{")) { - return false; - } - try { - const parsed = JSON.parse(trimmed) as { - type?: unknown; - subtype?: unknown; - response?: { request_id?: unknown } | unknown; - message?: unknown; - }; - return ( - typeof parsed.type === "string" || - typeof parsed.subtype === "string" || - (typeof parsed.response === "object" && - parsed.response !== null && - "request_id" in parsed.response) || - typeof parsed.message === "string" - ); - } catch { - return false; - } - }; - - const writeSideLog = (source: "stdout" | "stderr", text: string) => { - if (!text) return; - if (traceChildIo) { - traceAdapter( - `child_side_${source} ${JSON.stringify(text).slice(0, 4000)}`, - ); - } - process.stderr.write(text); - }; - - const flushBuffer = ( - source: "stdout" | "stderr", - final = false, - ): void => { - const buffer = lineBuffers[source]; - const lines = buffer.split("\n"); - if (!final) { - lineBuffers[source] = lines.pop() ?? ""; - } else { - lineBuffers[source] = ""; - } - for (const line of lines) { - const text = `${line}\n`; - if (looksLikeProtocolLine(line)) { - if (traceChildIo) { - traceAdapter( - `child_protocol_${source} ${JSON.stringify(text).slice(0, 4000)}`, - ); - } - stdout.write(text); - } else { - writeSideLog(source, text); - } - } - if (final && lineBuffers[source]) { - const text = lineBuffers[source]; - if (looksLikeProtocolLine(text)) { - if (traceChildIo) { - traceAdapter( - `child_protocol_${source} ${JSON.stringify(text).slice(0, 4000)}`, - ); - } - stdout.write(text); - } else { - writeSideLog(source, text); - } - lineBuffers[source] = ""; - } - }; - - const attachStream = ( - source: "stdout" | "stderr", - stream: NodeJS.ReadableStream | null, - ): void => { - if (!stream) { - openStreams -= 1; - if (openStreams <= 0) { - stdout.end(); - } - return; - } - stream.on("data", (chunk) => { - lineBuffers[source] += String(chunk); - flushBuffer(source); - }); - stream.on("end", () => { - flushBuffer(source, true); - openStreams -= 1; - if (openStreams <= 0) { - stdout.end(); - } - }); - stream.on("close", () => { - flushBuffer(source, true); - }); - stream.on("error", (error) => { - stdout.destroy(error); - }); - }; - - attachStream("stdout", child.stdout); - attachStream("stderr", child.stderr); - - return { - stdin: child.stdin, - stdout, - get killed() { - return child.killed; - }, - get exitCode() { - return child.exitCode; - }, - kill: child.kill.bind(child), - on: child.on.bind(child), - once: child.once.bind(child), - off: child.off.bind(child), - }; - }, - stderr: (data) => process.stderr.write(data), - systemPrompt: appendSystemPrompt - ? { - type: "preset", - preset: "claude_code", - append: appendSystemPrompt, - } - : { - type: "preset", - preset: "claude_code", - }, - tools: { type: "preset", preset: "claude_code" }, - }, - }); - this.readyPromise = this.initialize(params); - this.reader = this.consume(); - } - - get currentMode(): PermissionMode { - return this.mode; - } - - async ready(): Promise { - await this.readyPromise; - } - - async prompt(params: PromptRequest): Promise { - if (this.closed) { - throw new Error("Session is closed"); - } - if (this.pendingTurn) { - throw new Error("A Claude prompt is already running"); - } - await this.readyPromise; - - const text = joinPromptText(params.prompt as PromptPart[] | undefined); - traceAdapter( - `prompt_start session=${this.sessionId} textLength=${text.length}`, - ); - return new Promise((resolve, reject) => { - this.cancelled = false; - this.pendingTurn = { - resolve, - reject, - sawAssistantText: false, - sawToolCall: false, - }; - void this.applyPendingMcpServers() - .then(() => { - traceAdapter(`prompt_queue_push session=${this.sessionId}`); - this.promptQueue.push({ - type: "user", - session_id: "", - message: { - role: "user", - content: [{ type: "text", text }], - }, - parent_tool_use_id: null, - }); - traceAdapter(`prompt_queue_pushed session=${this.sessionId}`); - }) - .catch((error) => { - traceAdapter( - `prompt_setup_error session=${this.sessionId} error=${formatError(error)}`, - ); - if (this.pendingTurn) { - this.pendingTurn.reject(asError(error)); - this.pendingTurn = null; - } - }); - }); - } - - async cancel(): Promise { - if (this.closed) return; - this.cancelled = true; - await this.readyPromise.catch(() => {}); - await this.query.interrupt(); - } - - async setMode(mode: PermissionMode): Promise { - await this.readyPromise; - this.mode = mode; - await this.query.setPermissionMode(mode); - await this.emit({ - sessionUpdate: "current_mode_update" as const, - currentModeId: mode, - }); - } - - close(): void { - if (this.closed) return; - this.closed = true; - this.promptQueue.end(); - this.query.close(); - if (this.pendingTurn) { - this.pendingTurn.reject(new Error("Claude session closed")); - this.pendingTurn = null; - } - } - - private async initialize(params: NewSessionRequest): Promise { - this.pendingMcpServers = toSdkMcpServers(params.mcpServers); - traceAdapter( - `session_initialize id=${this.sessionId} mcpServers=${Object.keys( - this.pendingMcpServers ?? {}, - ).length}`, - ); - } - - private async applyPendingMcpServers(): Promise { - if (this.mcpServersApplied || !this.pendingMcpServers) { - traceAdapter( - `apply_mcp_skip session=${this.sessionId} applied=${String(this.mcpServersApplied)} pending=${String(Boolean(this.pendingMcpServers))}`, - ); - return; - } - traceAdapter(`apply_mcp_start session=${this.sessionId}`); - await this.query.setMcpServers(this.pendingMcpServers); - this.mcpServersApplied = true; - traceAdapter(`apply_mcp_done session=${this.sessionId}`); - } - - private async consume(): Promise { - traceAdapter(`consume_start session=${this.sessionId}`); - try { - for await (const message of this.query) { - traceAdapter( - `consume_message session=${this.sessionId} type=${String(message.type ?? "")}`, - ); - await this.handleMessage(message); - } - traceAdapter(`consume_done session=${this.sessionId}`); - } catch (error) { - traceAdapter( - `consume_error session=${this.sessionId} error=${formatError(error)}`, - ); - if (this.pendingTurn) { - this.pendingTurn.reject(asError(error)); - this.pendingTurn = null; - } - if (!this.closed) { - process.stderr.write(`${formatError(error)}\n`); - } - } finally { - traceAdapter(`consume_finally session=${this.sessionId}`); - if (this.pendingTurn) { - this.pendingTurn.reject( - new Error("Claude query ended before producing a result"), - ); - this.pendingTurn = null; - } - } - } - - private async handleMessage(message: Record): Promise { - if (traceAdapterMessages) { - const details = - message.type === "result" - ? ` result=${JSON.stringify({ - subtype: message.subtype, - result: message.result, - error: message.error, - errors: message.errors, - })}` - : ""; - traceAdapter( - `adapter_message type=${String(message.type ?? "")} subtype=${String( - message.subtype ?? "", - )} pendingTurn=${String(Boolean(this.pendingTurn))}${details}`, - ); - } - switch (message.type) { - case "stream_event": - await this.handleStreamEvent(message); - return; - case "assistant": - await this.handleAssistantMessage(message); - return; - case "tool_progress": - await this.handleToolProgress(message); - return; - case "system": - await this.handleSystemMessage(message); - return; - case "result": - await this.handleResult(message); - return; - case "tool_use_summary": - await this.emitText(String(message.summary ?? "")); - return; - default: - return; - } - } - - private async handleStreamEvent(message: Record): Promise { - if (!this.pendingTurn) return; - - const event = (message.event ?? {}) as Record; - const type = String(event.type ?? ""); - - if (type === "content_block_delta") { - const delta = (event.delta ?? {}) as Record; - if (delta.type === "text_delta" && typeof delta.text === "string") { - if (this.pendingTurn) this.pendingTurn.sawAssistantText = true; - await this.emit({ - sessionUpdate: "agent_message_chunk" as const, - content: { type: "text" as const, text: delta.text }, - }); - return; - } - if ( - delta.type === "thinking_delta" && - typeof delta.thinking === "string" - ) { - await this.emit({ - sessionUpdate: "agent_thought_chunk" as const, - content: { type: "text" as const, text: delta.thinking }, - }); - return; - } - if ( - delta.type === "input_json_delta" && - typeof event.index === "number" - ) { - const toolCall = this.findToolCallByIndex(Number(event.index)); - if (!toolCall) return; - const rawInput = { - ...(toolCall.rawInput ?? {}), - partial_json: String(delta.partial_json ?? ""), - }; - toolCall.rawInput = rawInput; - await this.emitToolCallUpdate(toolCall.toolName, toolCall.toolUseId, { - rawInput, - status: "pending", - }); - } - return; - } - - if (type === "content_block_start") { - const block = (event.content_block ?? {}) as Record; - if (block.type !== "tool_use") return; - - const toolUseId = String(block.id ?? ""); - if (!toolUseId) return; - - const toolName = String(block.name ?? "tool"); - const rawInput = isRecord(block.input) ? block.input : undefined; - this.activeToolCalls.set(toolUseId, { toolName, rawInput }); - if (this.pendingTurn) this.pendingTurn.sawToolCall = true; - - await this.emit({ - sessionUpdate: "tool_call" as const, - toolCallId: toolUseId, - kind: toToolKind(toolName), - locations: toToolCallLocations(rawInput, this.cwd), - rawInput, - status: "pending" as const, - title: toolName, - }); - } - } - - private async handleAssistantMessage( - message: Record, - ): Promise { - if (!this.pendingTurn) return; - - const content = Array.isArray((message.message as Record)?.content) - ? (((message.message as Record).content ?? - []) as Array>) - : []; - - for (const block of content) { - if (block.type === "tool_use") { - const toolUseId = String(block.id ?? ""); - if (!toolUseId) continue; - const toolName = String(block.name ?? "tool"); - const rawInput = isRecord(block.input) ? block.input : undefined; - this.activeToolCalls.set(toolUseId, { toolName, rawInput }); - if (this.pendingTurn) this.pendingTurn.sawToolCall = true; - - await this.emit({ - sessionUpdate: "tool_call" as const, - toolCallId: toolUseId, - kind: toToolKind(toolName), - locations: toToolCallLocations(rawInput, this.cwd), - rawInput, - status: "pending" as const, - title: toolName, - }); - } - } - - const text = extractTextContent(content); - if (text && this.pendingTurn && !this.pendingTurn.sawAssistantText) { - this.pendingTurn.sawAssistantText = true; - await this.emitText(text); - } - } - - private async handleToolProgress( - message: Record, - ): Promise { - if (!this.pendingTurn) return; - - const toolUseId = String(message.tool_use_id ?? ""); - const toolName = String(message.tool_name ?? "tool"); - if (!toolUseId) return; - - const existing = this.activeToolCalls.get(toolUseId); - this.activeToolCalls.set(toolUseId, { - toolName, - rawInput: existing?.rawInput, - }); - - await this.emitToolCallUpdate(toolName, toolUseId, { - status: "in_progress", - }); - } - - private async handleSystemMessage( - message: Record, - ): Promise { - if (!this.pendingTurn) return; - - if (message.subtype === "local_command_output") { - await this.emitText(String(message.content ?? "")); - } - } - - private async handleResult(message: Record): Promise { - const turn = this.pendingTurn; - if (!turn) return; - - const subtype = String(message.subtype ?? "success"); - const resultText = - typeof message.result === "string" ? message.result : undefined; - if (resultText && !turn.sawAssistantText) { - await this.emitText(resultText); - } - - for (const [toolUseId, info] of this.activeToolCalls) { - await this.emitToolCallUpdate(info.toolName, toolUseId, { - status: subtype === "success" ? "completed" : "failed", - }); - } - this.activeToolCalls.clear(); - - await this.lastEmit; - - this.pendingTurn = null; - turn.resolve({ - stopReason: this.cancelled ? "cancelled" : "end_turn", - }); - this.cancelled = false; - } - - private async emitText(text: string): Promise { - if (!text) return; - await this.emit({ - sessionUpdate: "agent_message_chunk" as const, - content: { - type: "text" as const, - text, - }, - }); - } - - private emit(update: SessionUpdate): Promise { - this.lastEmit = this.lastEmit - .then(() => - this.conn.sessionUpdate({ - sessionId: this.sessionId, - update, - }), - ) - .catch(() => {}); - return this.lastEmit; - } - - private async emitToolCallUpdate( - toolName: string, - toolUseId: string, - update: { - status: "pending" | "in_progress" | "completed" | "failed"; - rawInput?: Record; - }, - ): Promise { - await this.emit({ - sessionUpdate: "tool_call_update" as const, - toolCallId: toolUseId, - kind: toToolKind(toolName), - locations: toToolCallLocations(update.rawInput, this.cwd), - rawInput: update.rawInput, - status: update.status, - }); - } - - private createPermissionHandler(): CanUseTool { - return async (toolName, input, options) => { - traceAdapter( - `permission_request_start session=${this.sessionId} tool=${toolName} toolUseId=${options.toolUseID}`, - ); - const request = { - options: buildPermissionOptions(), - sessionId: this.sessionId, - toolCall: { - kind: toToolKind(toolName), - locations: toToolCallLocations(input, this.cwd), - rawInput: input, - status: "pending" as const, - title: options.title ?? toolName, - toolCallId: options.toolUseID, - }, - }; - const permissionFallbackMs = Number.parseInt( - process.env.CLAUDE_CODE_PERMISSION_FALLBACK_MS ?? "2000", - 10, - ); - const response = await Promise.race([ - this.conn.requestPermission(request), - new Promise((resolve) => { - setTimeout(() => { - traceAdapter( - `permission_request_fallback session=${this.sessionId} tool=${toolName} toolUseId=${options.toolUseID}`, - ); - resolve({ - outcome: { - outcome: "selected", - optionId: "allow_once", - }, - }); - }, Number.isFinite(permissionFallbackMs) ? permissionFallbackMs : 2000); - }), - ]); - traceAdapter( - `permission_request_done session=${this.sessionId} tool=${toolName} toolUseId=${options.toolUseID}`, - ); - return toPermissionResult( - response, - options.suggestions, - options.toolUseID, - input, - ); - }; - } - - private findToolCallByIndex(index: number): { - toolUseId: string; - toolName: string; - rawInput?: Record; - } | null { - const entry = [...this.activeToolCalls.entries()][index]; - if (!entry) return null; - const [toolUseId, value] = entry; - return { toolUseId, toolName: value.toolName, rawInput: value.rawInput }; - } -} - -class ClaudeSdkAgent implements Agent { - private sessions = new Map(); - - constructor(private readonly conn: AgentSideConnection) { - setTimeout(() => { - void this.conn.closed.then(() => { - for (const session of this.sessions.values()) { - session.close(); - } - this.sessions.clear(); - }); - }, 0); - } - - async initialize( - _params: InitializeRequest, - ): Promise { - return { - protocolVersion: 1, - agentInfo: { - name: "claude-sdk-acp", - title: "Claude Agent SDK ACP adapter", - version: "0.1.0", - }, - agentCapabilities: { - promptCapabilities: { - audio: false, - embeddedContext: false, - image: true, - }, - }, - }; - } - - async newSession(params: NewSessionRequest): Promise { - const sessionId = randomUUID(); - const sdk = await claudeSdkRuntimePromise; - const session = new ClaudeQuerySession( - this.conn, - sessionId, - params.cwd, - "default", - params, - sdk.cliPath, - sdk.query, - ); - await session.ready(); - this.sessions.set(sessionId, session); - return { - sessionId, - modes: { - currentModeId: "default", - availableModes: ACP_MODES.map((id) => ({ - id, - name: id, - })), - }, - }; - } - - async prompt(params: PromptRequest): Promise { - const session = this.requireSession(params.sessionId); - return session.prompt(params); - } - - async cancel(params: CancelNotification): Promise { - const session = this.requireSession(params.sessionId); - await session.cancel(); - } - - async setSessionMode( - params: SetSessionModeRequest, - ): Promise { - const session = this.requireSession(params.sessionId); - await session.setMode(params.modeId as PermissionMode); - return {}; - } - - async authenticate( - _params: AuthenticateRequest, - ): Promise { - } - - private requireSession(sessionId: string): ClaudeQuerySession { - const session = this.sessions.get(sessionId); - if (!session) { - throw new Error(`Unknown Claude session: ${sessionId}`); - } - return session; - } -} - -function joinPromptText(prompt: PromptPart[] | undefined): string { - return (prompt ?? []) - .map((part) => (part.type === "text" ? (part.text ?? "") : "")) - .join(""); -} - -function extractTextContent(content: Array>): string { - return content - .filter((block) => block.type === "text" && typeof block.text === "string") - .map((block) => String(block.text)) - .join(""); -} - -function toSdkMcpServers( - servers: NewSessionRequest["mcpServers"], -): Record | undefined { - if (!Array.isArray(servers) || servers.length === 0) { - return undefined; - } - - return Object.fromEntries( - servers.map((server, index) => { - const name = `mcp-${index + 1}`; - const record = server as Record; - if (record.type === "local") { - return [ - name, - { - args: Array.isArray(record.args) - ? (record.args as string[]) - : undefined, - command: String(record.command ?? ""), - env: isRecord(record.env) - ? (record.env as Record) - : undefined, - type: "stdio", - } satisfies McpServerConfig, - ]; - } - - return [ - name, - { - headers: isRecord(record.headers) - ? (record.headers as Record) - : undefined, - type: "http", - url: String(record.url ?? ""), - } satisfies McpServerConfig, - ]; - }), - ); -} - -function toToolKind( - toolName: string, -): "read" | "edit" | "execute" | "search" | "fetch" | "think" | "other" { - switch (toolName) { - case "Read": - return "read"; - case "Edit": - case "Write": - case "MultiEdit": - case "NotebookEdit": - return "edit"; - case "Bash": - case "Monitor": - return "execute"; - case "Grep": - case "Glob": - case "LS": - return "search"; - case "WebFetch": - case "WebSearch": - return "fetch"; - case "Think": - return "think"; - default: - return "other"; - } -} - -function toToolCallLocations( - rawInput: Record | undefined, - cwd: string, -): Array<{ path: string; line?: number }> | undefined { - const path = - typeof rawInput?.file_path === "string" - ? rawInput.file_path - : typeof rawInput?.path === "string" - ? rawInput.path - : undefined; - if (!path) return undefined; - - return [ - { - path: isAbsolute(path) ? path : resolvePath(cwd, path), - }, - ]; -} - -function buildPermissionOptions(): Array<{ - kind: "allow_once" | "allow_always" | "reject_once" | "reject_always"; - name: string; - optionId: string; -}> { - return [ - { kind: "allow_once", name: "Allow once", optionId: "allow_once" }, - { kind: "allow_always", name: "Always allow", optionId: "allow_always" }, - { kind: "reject_once", name: "Reject", optionId: "reject_once" }, - ]; -} - -function toPermissionResult( - response: RequestPermissionResponse, - suggestions: PermissionUpdate[] | undefined, - toolUseID: string, - input: Record, -): PermissionResult { - if (response.outcome.outcome === "cancelled") { - return { - behavior: "deny", - decisionClassification: "user_reject", - interrupt: true, - message: "Permission request cancelled", - toolUseID, - }; - } - - switch (response.outcome.optionId) { - case "allow_always": - return { - behavior: "allow", - decisionClassification: "user_permanent", - toolUseID, - updatedInput: input, - updatedPermissions: suggestions, - }; - case "allow_once": - return { - behavior: "allow", - decisionClassification: "user_temporary", - toolUseID, - updatedInput: input, - }; - default: - return { - behavior: "deny", - decisionClassification: "user_reject", - message: "Permission denied", - toolUseID, - }; - } -} - -function normalizeClaudePermissionMode(mode: PermissionMode): PermissionMode { - // Claude Code refuses bypassPermissions when running as root, which is the - // normal VM user in this workspace. Fall back to the standard interactive - // mode instead of letting startup abort. - return mode === "bypassPermissions" ? "default" : mode; -} - -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null; -} - -function asError(error: unknown): Error { - return error instanceof Error ? error : new Error(String(error)); -} - -function formatError(error: unknown): string { - return asError(error).stack ?? asError(error).message; -} - -const input = new WritableStream({ - write(chunk) { - return new Promise((resolve) => { - process.stdout.write(chunk, () => resolve()); - }); - }, -}); - -const output = new ReadableStream({ - start(controller) { - process.stdin.on("data", (chunk: Buffer) => { - controller.enqueue(new Uint8Array(chunk)); - }); - process.stdin.on("end", () => controller.close()); - process.stdin.on("error", (error: Error) => controller.error(error)); - }, -}); - -const stream = ndJsonStream(input, output); -const _connection = new AgentSideConnection( - (conn) => new ClaudeSdkAgent(conn), - stream, -); - -process.stdin.resume(); -process.stdin.on("end", () => { - process.exit(0); -}); diff --git a/registry/agent/claude/src/index.ts b/registry/agent/claude/src/index.ts deleted file mode 100644 index 6c32da48c..000000000 --- a/registry/agent/claude/src/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { defineSoftware } from "@rivet-dev/agentos-core"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const packageDir = resolve(__dirname, ".."); - -const claude = defineSoftware({ - name: "claude", - type: "agent" as const, - packageDir, - requires: ["@anthropic-ai/claude-agent-sdk"], - agent: { - id: "claude", - acpAdapter: "@agentos-software/claude-code", - agentPackage: "@anthropic-ai/claude-agent-sdk", - staticEnv: { - CLAUDE_AGENT_SDK_CLIENT_APP: "@rivet-dev/agentos", - CLAUDE_CODE_SIMPLE: "1", - CLAUDE_CODE_FORCE_AGENT_OS_RIPGREP: "1", - CLAUDE_CODE_DEFER_GROWTHBOOK_INIT: "1", - CLAUDE_CODE_DISABLE_CWD_PERSIST: "1", - CLAUDE_CODE_DISABLE_DEV_NULL_REDIRECT: "1", - CLAUDE_CODE_NODE_SHELL_WRAPPER: "1", - CLAUDE_CODE_DISABLE_STREAM_JSON_HOOK_EVENTS: "1", - CLAUDE_CODE_SHELL: "/bin/sh", - CLAUDE_CODE_SKIP_INITIAL_MESSAGES: "1", - CLAUDE_CODE_SKIP_SANDBOX_INIT: "1", - CLAUDE_CODE_SIMPLE_SHELL_EXEC: "1", - CLAUDE_CODE_SWAP_STDIO: "0", - CLAUDE_CODE_USE_PIPE_OUTPUT: "1", - DISABLE_TELEMETRY: "1", - SHELL: "/bin/sh", - USE_BUILTIN_RIPGREP: "0", - }, - }, -}); - -export default claude; diff --git a/registry/agent/claude/src/patched-cli.ts b/registry/agent/claude/src/patched-cli.ts deleted file mode 100644 index afe05a9ac..000000000 --- a/registry/agent/claude/src/patched-cli.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { readFileSync } from "node:fs"; -import { dirname, resolve as resolvePath } from "node:path"; - -type PatchedArtifactOptions = { - packageDir: string; - sdkPath: string; -}; - -type PatchedManifest = { - entry?: string; -}; - -function resolveManifestEntry( - packageDir: string, - manifestName: string, -): string | undefined { - const manifestPath = resolvePath(packageDir, "dist", manifestName); - try { - const manifest = JSON.parse(readFileSync(manifestPath, "utf-8")) as PatchedManifest; - if (typeof manifest.entry === "string" && manifest.entry.length > 0) { - return resolvePath(packageDir, "dist", manifest.entry.replace(/^\.\//, "")); - } - } catch { - } - return undefined; -} - -export function resolveClaudeCliPath({ - packageDir, - sdkPath, -}: PatchedArtifactOptions): string { - return ( - resolveManifestEntry(packageDir, "claude-cli-patched.json") ?? - resolvePath(dirname(sdkPath), "cli.js") - ); -} - -export function resolveClaudeSdkPath({ - packageDir, - sdkPath, -}: PatchedArtifactOptions): string { - return ( - resolveManifestEntry(packageDir, "claude-sdk-patched.json") ?? sdkPath - ); -} diff --git a/registry/agent/claude/tests/patched-cli.test.mjs b/registry/agent/claude/tests/patched-cli.test.mjs deleted file mode 100644 index b91b65f70..000000000 --- a/registry/agent/claude/tests/patched-cli.test.mjs +++ /dev/null @@ -1,119 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; -import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; -import { createRequire } from "node:module"; -import { dirname, resolve as resolvePath } from "node:path"; -import { tmpdir } from "node:os"; - -const require = createRequire(import.meta.url); -const sdkPath = require.resolve("@anthropic-ai/claude-agent-sdk"); -const packageDir = resolvePath(import.meta.dirname, ".."); -const cliManifestPath = resolvePath(packageDir, "dist", "claude-cli-patched.json"); -const sdkManifestPath = resolvePath(packageDir, "dist", "claude-sdk-patched.json"); -const { resolveClaudeCliPath, resolveClaudeSdkPath } = await import( - resolvePath(packageDir, "dist", "patched-cli.js") -); - -function readManifest(manifestPath) { - return JSON.parse(readFileSync(manifestPath, "utf-8")); -} - -test("build writes patched Claude CLI and SDK manifests to dist", () => { - const cliManifest = readManifest(cliManifestPath); - const sdkManifest = readManifest(sdkManifestPath); - - assert.equal(typeof cliManifest.entry, "string"); - assert.equal(typeof sdkManifest.entry, "string"); - assert.equal( - resolveClaudeCliPath({ packageDir, sdkPath }), - resolvePath(packageDir, "dist", cliManifest.entry.replace(/^\.\//, "")), - ); - assert.equal( - resolveClaudeSdkPath({ packageDir, sdkPath }), - resolvePath(packageDir, "dist", sdkManifest.entry.replace(/^\.\//, "")), - ); -}); - -test("patched-path helpers fall back to the upstream SDK when manifests are missing", () => { - const tempDir = mkdtempSync(resolvePath(tmpdir(), "agentos-claude-test-")); - try { - assert.equal( - resolveClaudeCliPath({ packageDir: tempDir, sdkPath }), - resolvePath(dirname(sdkPath), "cli.js"), - ); - assert.equal(resolveClaudeSdkPath({ packageDir: tempDir, sdkPath }), sdkPath); - } finally { - rmSync(tempDir, { recursive: true, force: true }); - } -}); - -test("patched-path helpers resolve custom manifest entries from dist", () => { - const tempDir = mkdtempSync(resolvePath(tmpdir(), "agentos-claude-test-")); - try { - const distDir = resolvePath(tempDir, "dist"); - mkdirSync(distDir, { recursive: true }); - writeFileSync( - resolvePath(distDir, "claude-cli-patched.json"), - JSON.stringify({ entry: "./custom-cli.mjs" }), - "utf-8", - ); - writeFileSync( - resolvePath(distDir, "claude-sdk-patched.json"), - JSON.stringify({ entry: "./custom-sdk.mjs" }), - "utf-8", - ); - - assert.equal( - resolveClaudeCliPath({ packageDir: tempDir, sdkPath }), - resolvePath(distDir, "custom-cli.mjs"), - ); - assert.equal( - resolveClaudeSdkPath({ packageDir: tempDir, sdkPath }), - resolvePath(distDir, "custom-sdk.mjs"), - ); - } finally { - rmSync(tempDir, { recursive: true, force: true }); - } -}); - -test("patched CLI keeps stream-json hook events enabled by default", () => { - const patchedCliPath = resolveClaudeCliPath({ packageDir, sdkPath }); - const patchedCliSource = readFileSync(patchedCliPath, "utf-8"); - const hookForwardingGuard = - 'if($.outputFormat==="stream-json"&&$.verbose&&process.env.AGENT_OS_CLAUDE_DISABLE_HOOK_EVENTS!=="1")JMK((x)=>{'; - - assert.ok( - patchedCliSource.includes(hookForwardingGuard), - "expected patched CLI to guard stream-json hook events with AGENT_OS_CLAUDE_DISABLE_HOOK_EVENTS", - ); - assert.ok( - !patchedCliSource.includes( - 'if($.outputFormat==="stream-json"&&$.verbose&&false)JMK((x)=>{', - ), - "expected patched CLI to remove the unconditional && false kill-switch", - ); - - const hookEvents = []; - const maybeRegisterHookEvents = (env, options, onHookEvent) => { - if ( - options.outputFormat === "stream-json" && - options.verbose && - env.AGENT_OS_CLAUDE_DISABLE_HOOK_EVENTS !== "1" - ) { - onHookEvent({ type: "hook_event" }); - } - }; - - maybeRegisterHookEvents({}, { outputFormat: "stream-json", verbose: true }, (event) => - hookEvents.push(event.type), - ); - assert.deepEqual(hookEvents, ["hook_event"]); - - const disabledHookEvents = []; - maybeRegisterHookEvents( - { AGENT_OS_CLAUDE_DISABLE_HOOK_EVENTS: "1" }, - { outputFormat: "stream-json", verbose: true }, - (event) => disabledHookEvents.push(event.type), - ); - assert.deepEqual(disabledHookEvents, []); -}); diff --git a/registry/agent/claude/tsconfig.json b/registry/agent/claude/tsconfig.json deleted file mode 100644 index bff731325..000000000 --- a/registry/agent/claude/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "declaration": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/registry/agent/codex/package.json b/registry/agent/codex/package.json deleted file mode 100644 index af01513bd..000000000 --- a/registry/agent/codex/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "@agentos-software/codex", - "version": "0.2.0-rc.3", - "type": "module", - "license": "Apache-2.0", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc", - "check-types": "tsc --noEmit", - "test": "pnpm build && node --test tests/*.test.mjs" - }, - "dependencies": { - "@agentos-software/codex-cli": "catalog:" - }, - "devDependencies": { - "@types/node": "^22.10.2", - "typescript": "^5.7.2" - } -} diff --git a/registry/agent/codex/src/index.ts b/registry/agent/codex/src/index.ts deleted file mode 100644 index 531b82084..000000000 --- a/registry/agent/codex/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import codexSoftware from "@agentos-software/codex-cli"; - -export default codexSoftware; diff --git a/registry/agent/codex/tests/package.test.mjs b/registry/agent/codex/tests/package.test.mjs deleted file mode 100644 index 79b4f1860..000000000 --- a/registry/agent/codex/tests/package.test.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import assert from "node:assert/strict"; -import { readFileSync } from "node:fs"; -import { dirname, join } from "node:path"; -import test from "node:test"; -import { fileURLToPath } from "node:url"; -import codex from "../dist/index.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -test("codex package does not advertise an ACP adapter until the real agent is wired", () => { - const manifest = JSON.parse( - readFileSync(join(__dirname, "..", "package.json"), "utf8"), - ); - - assert.equal(manifest.bin, undefined); - assert.equal(codex.name, "codex"); - assert.equal(typeof codex.commandDir, "string"); - assert.equal(codex.agent, undefined); -}); diff --git a/registry/agent/codex/tsconfig.json b/registry/agent/codex/tsconfig.json deleted file mode 100644 index bff731325..000000000 --- a/registry/agent/codex/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "declaration": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/registry/agent/opencode/package.json b/registry/agent/opencode/package.json deleted file mode 100644 index 3445e3390..000000000 --- a/registry/agent/opencode/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@agentos-software/opencode", - "version": "0.2.0-rc.3", - "type": "module", - "license": "Apache-2.0", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "bin": { - "agentos-opencode-acp": "./dist/adapter.js" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "node ./scripts/build-opencode-acp.mjs && tsc", - "check-types": "tsc --noEmit" - }, - "dependencies": { - "@rivet-dev/agentos-core": "workspace:*" - }, - "devDependencies": { - "bun": "1.3.11", - "@types/node": "^22.10.2", - "typescript": "^5.7.2" - } -} diff --git a/registry/agent/opencode/scripts/build-opencode-acp.mjs b/registry/agent/opencode/scripts/build-opencode-acp.mjs deleted file mode 100644 index afdee7947..000000000 --- a/registry/agent/opencode/scripts/build-opencode-acp.mjs +++ /dev/null @@ -1,3681 +0,0 @@ -#!/usr/bin/env node - -import { createHash } from "node:crypto"; -import { - copyFileSync, - existsSync, - mkdirSync, - readFileSync, - rmSync, - writeFileSync, -} from "node:fs"; -import { mkdtemp, readdir, readFile, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { dirname, join, resolve } from "node:path"; -import { spawnSync } from "node:child_process"; -import { fileURLToPath } from "node:url"; - -const SOURCE_REPOSITORY = "anomalyco/opencode"; -const SOURCE_VERSION = "1.3.13"; -const SOURCE_TARBALL_URL = `https://github.com/${SOURCE_REPOSITORY}/archive/refs/tags/v${SOURCE_VERSION}.tar.gz`; - -// Upstream `packages/app/package.json` pins `ghostty-web: github:anomalyco/ghostty-web#main`, -// but the bundled bun.lock snapshots the SHA below. Because `main` is a moving ref, a fresh -// `bun install --frozen-lockfile` resolves to whatever `main` points at *now* and fails the -// lockfile check. Rewriting the manifest to the lockfile's SHA before install keeps frozen- -// lockfile guarantees intact (i.e. CI still breaks loudly if either side drifts) without -// trusting arbitrary new HEADs on the ghostty-web branch. -const GHOSTTY_WEB_PINNED_SHA = "4af877d"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const packageDir = resolve(__dirname, ".."); -const distDir = resolve(packageDir, "dist"); -const cacheDir = resolve(packageDir, "node_modules", ".cache", "opencode-build"); -const patchPath = resolve(packageDir, "upstream", `opencode-v${SOURCE_VERSION}.patch`); -const bundleDir = resolve(distDir, "opencode-acp"); -const manifestPath = resolve(distDir, "opencode-acp.manifest.json"); -const SQL_JS_VERSION = "1.14.1"; -const bunBin = resolve( - packageDir, - "node_modules", - ".bin", - process.platform === "win32" ? "bun.cmd" : "bun", -); - -function run(command, args, options = {}) { - const result = spawnSync(command, args, { - stdio: "inherit", - ...options, - }); - if (result.status !== 0) { - throw new Error( - `Command failed (${result.status ?? "unknown"}): ${command} ${args.join(" ")}`, - ); - } - return result; -} - -function pinGhosttyWebRef(sourceRoot) { - const manifestPath = resolve(sourceRoot, "packages", "app", "package.json"); - const raw = readFileSync(manifestPath, "utf-8"); - const movingRef = '"ghostty-web": "github:anomalyco/ghostty-web#main"'; - const pinnedRef = `"ghostty-web": "github:anomalyco/ghostty-web#${GHOSTTY_WEB_PINNED_SHA}"`; - if (raw.includes(pinnedRef)) return; - if (!raw.includes(movingRef)) { - throw new Error( - `Expected ghostty-web ref ${movingRef} in ${manifestPath}; upstream layout changed — re-audit GHOSTTY_WEB_PINNED_SHA before updating.`, - ); - } - writeFileSync(manifestPath, raw.replace(movingRef, pinnedRef)); -} - -const PATCHED_SOURCE_FILES = [ - "packages/opencode/src/cli/cmd/acp.ts", - "packages/opencode/src/config/config.ts", - "packages/opencode/src/plugin/index.ts", - "packages/opencode/src/server/instance.ts", - "packages/opencode/src/server/server.ts", -]; - -async function ensureNodeAcpPatch(sourceRoot, tarballPath) { - const serverFile = resolve( - sourceRoot, - "packages", - "opencode", - "src", - "server", - "server.ts", - ); - const pluginFile = resolve( - sourceRoot, - "packages", - "opencode", - "src", - "plugin", - "index.ts", - ); - const acpFile = resolve( - sourceRoot, - "packages", - "opencode", - "src", - "cli", - "cmd", - "acp.ts", - ); - const serverSource = readFileSync(serverFile, "utf-8"); - const pluginSource = readFileSync(pluginFile, "utf-8"); - const acpSource = readFileSync(acpFile, "utf-8"); - const alreadyPatched = - serverSource.includes('from "node:http"') && - !serverSource.includes('from "hono/bun"') && - pluginSource.includes("Bun shell is unavailable in the Node ACP build") && - acpSource.includes("const server = await Server.listen(opts)"); - - if (alreadyPatched) { - return; - } - - const scratchRoot = await mkdtemp(join(tmpdir(), "agentos-opencode-patch-")); - try { - run("tar", ["-xzf", tarballPath, "--strip-components=1", "-C", scratchRoot]); - run("git", ["apply", "--whitespace=nowarn", patchPath], { cwd: scratchRoot }); - for (const relativePath of PATCHED_SOURCE_FILES) { - copyFileSync( - resolve(scratchRoot, relativePath), - resolve(sourceRoot, relativePath), - ); - } - } finally { - rmSync(scratchRoot, { recursive: true, force: true }); - } - - const verifiedServerSource = readFileSync(serverFile, "utf-8"); - const verifiedPluginSource = readFileSync(pluginFile, "utf-8"); - const verifiedAcpSource = readFileSync(acpFile, "utf-8"); - if ( - !verifiedServerSource.includes('from "node:http"') || - verifiedServerSource.includes('from "hono/bun"') || - !verifiedPluginSource.includes("Bun shell is unavailable in the Node ACP build") || - !verifiedAcpSource.includes("const server = await Server.listen(opts)") - ) { - throw new Error("Failed to stage the Node ACP patches into the prepared OpenCode source tree"); - } -} - -async function downloadFile(url, destination) { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`); - } - - const buffer = Buffer.from(await response.arrayBuffer()); - await writeFile(destination, buffer); -} - -async function readMigrations(sourceRoot) { - const migrationRoot = join(sourceRoot, "packages", "opencode", "migration"); - const entries = (await readdir(migrationRoot, { withFileTypes: true })) - .filter((entry) => entry.isDirectory() && /^\d{14}/.test(entry.name)) - .map((entry) => entry.name) - .sort(); - - return Promise.all( - entries.map(async (name) => { - const sql = await readFile( - join(migrationRoot, name, "migration.sql"), - "utf8", - ); - const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name); - const timestamp = match - ? Date.UTC( - Number(match[1]), - Number(match[2]) - 1, - Number(match[3]), - Number(match[4]), - Number(match[5]), - Number(match[6]), - ) - : 0; - return { name, sql, timestamp }; - }), - ); -} - -async function rewriteSourceFile(sourceRoot, relativePath, transform) { - const filePath = resolve(sourceRoot, relativePath); - const original = await readFile(filePath, "utf8"); - const next = transform(original); - if (next !== original) { - await writeFile(filePath, next); - } -} - -async function ensureSqlJsDependency(sourceRoot) { - const packageJsonPath = resolve( - sourceRoot, - "packages", - "opencode", - "package.json", - ); - const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")); - const dependencies = packageJson.dependencies ?? {}; - const bunStoreDir = resolve(sourceRoot, "node_modules", ".bun"); - const hasInstalledSqlJs = - existsSync(bunStoreDir) && - (await readdir(bunStoreDir)).some((entry) => entry.startsWith("sql.js@")); - if (dependencies["sql.js"] === SQL_JS_VERSION && hasInstalledSqlJs) { - return; - } - - packageJson.dependencies = { - ...dependencies, - "sql.js": SQL_JS_VERSION, - }; - await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`); - run(bunBin, ["install"], { cwd: sourceRoot }); -} - -async function applyNodeAcpRuntimeTweaks(sourceRoot) { - await writeFile( - resolve(sourceRoot, "packages/opencode/src/cli/cmd/acp.ts"), - `import type { Argv, InferredOptionTypes } from "yargs" -import { cmd } from "./cmd" -import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk" -import { Log } from "../../util/log" - -const options = { - port: { - type: "number" as const, - describe: "port to listen on", - default: 0, - }, - hostname: { - type: "string" as const, - describe: "hostname to listen on", - default: "127.0.0.1", - }, - mdns: { - type: "boolean" as const, - describe: "enable mDNS service discovery (defaults hostname to 0.0.0.0)", - default: false, - }, - "mdns-domain": { - type: "string" as const, - describe: "custom domain name for mDNS service (default: opencode.local)", - default: "opencode.local", - }, - cors: { - type: "string" as const, - array: true, - describe: "additional domains to allow for CORS", - default: [] as string[], - }, -} - -type NetworkOptions = InferredOptionTypes - -function withNetworkOptions(yargs: Argv) { - return yargs.options(options) -} - -async function resolveNetworkOptions(args: NetworkOptions) { - const { Config } = await import("../../config/config") - const config = await Config.getGlobal() - const portExplicitlySet = process.argv.includes("--port") - const hostnameExplicitlySet = process.argv.includes("--hostname") - const mdnsExplicitlySet = process.argv.includes("--mdns") - const mdnsDomainExplicitlySet = process.argv.includes("--mdns-domain") - const corsExplicitlySet = process.argv.includes("--cors") - - const mdns = mdnsExplicitlySet ? args.mdns : (config?.server?.mdns ?? args.mdns) - const mdnsDomain = mdnsDomainExplicitlySet ? args["mdns-domain"] : (config?.server?.mdnsDomain ?? args["mdns-domain"]) - const port = portExplicitlySet ? args.port : (config?.server?.port ?? args.port) - const hostname = hostnameExplicitlySet - ? args.hostname - : mdns && !config?.server?.hostname - ? "0.0.0.0" - : (config?.server?.hostname ?? args.hostname) - const configCors = config?.server?.cors ?? [] - const argsCors = Array.isArray(args.cors) ? args.cors : args.cors ? [args.cors] : [] - const cors = [...configCors, ...argsCors] - - return { hostname, port, mdns, mdnsDomain, cors } -} - -function wrapData(data: T) { - return { data } -} - -async function loadProviderCatalog(directory: string | undefined) { - const [{ Config }] = await Promise.all([import("../../config/config")]) - - return withDirectory(directory, async () => { - const config = await Config.get() - const providers: any[] = [] - - if (config?.provider?.anthropic || config?.model?.startsWith("anthropic/") || process.env.ANTHROPIC_API_KEY) { - providers.push({ - id: "anthropic", - name: "Anthropic", - source: "config", - env: ["ANTHROPIC_API_KEY"], - options: { - ...(config?.provider?.anthropic?.options ?? {}), - }, - models: { - "claude-sonnet-4-20250514": { - id: "claude-sonnet-4-20250514", - providerID: "anthropic", - api: { id: "claude-sonnet-4-20250514", url: "", npm: "@ai-sdk/anthropic" }, - name: "Claude Sonnet 4", - family: "claude-sonnet-4", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 32000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-05-14", - variants: {}, - }, - "claude-opus-4-1-20250805": { - id: "claude-opus-4-1-20250805", - providerID: "anthropic", - api: { id: "claude-opus-4-1-20250805", url: "", npm: "@ai-sdk/anthropic" }, - name: "Claude Opus 4.1", - family: "claude-opus-4-1", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 32000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-08-05", - variants: {}, - }, - "claude-haiku-4-5-20251001": { - id: "claude-haiku-4-5-20251001", - providerID: "anthropic", - api: { id: "claude-haiku-4-5-20251001", url: "", npm: "@ai-sdk/anthropic" }, - name: "Claude Haiku 4.5", - family: "claude-haiku-4-5", - capabilities: { - temperature: true, - reasoning: false, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 16000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-10-01", - variants: {}, - }, - }, - }) - } - - if (config?.provider?.openai || config?.model?.startsWith("openai/") || process.env.OPENAI_API_KEY) { - providers.push({ - id: "openai", - name: "OpenAI", - source: "config", - env: ["OPENAI_API_KEY"], - options: { - ...(config?.provider?.openai?.options ?? {}), - }, - models: { - "gpt-5": { - id: "gpt-5", - providerID: "openai", - api: { id: "gpt-5", url: "", npm: "@ai-sdk/openai" }, - name: "GPT-5", - family: "gpt-5", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 32000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-01-01", - variants: {}, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - providerID: "openai", - api: { id: "gpt-5-mini", url: "", npm: "@ai-sdk/openai" }, - name: "GPT-5 Mini", - family: "gpt-5-mini", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 16000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-01-01", - variants: {}, - }, - }, - }) - } - - if ( - config?.provider?.google || - config?.model?.startsWith("google/") || - process.env.GOOGLE_GENERATIVE_AI_API_KEY - ) { - providers.push({ - id: "google", - name: "Google", - source: "config", - env: ["GOOGLE_GENERATIVE_AI_API_KEY"], - options: { - ...(config?.provider?.google?.options ?? {}), - }, - models: { - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - providerID: "google", - api: { id: "gemini-2.5-pro", url: "", npm: "@ai-sdk/google" }, - name: "Gemini 2.5 Pro", - family: "gemini-2.5-pro", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 32000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-01-01", - variants: {}, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - providerID: "google", - api: { id: "gemini-2.5-flash", url: "", npm: "@ai-sdk/google" }, - name: "Gemini 2.5 Flash", - family: "gemini-2.5-flash", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 16000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-01-01", - variants: {}, - }, - }, - }) - } - - if ( - config?.provider?.["google-vertex"] || - config?.model?.startsWith("google-vertex/") || - (process.env.GOOGLE_VERTEX_PROJECT && process.env.GOOGLE_VERTEX_LOCATION) - ) { - providers.push({ - id: "google-vertex", - name: "Google Vertex", - source: "config", - env: [], - options: { - ...(config?.provider?.["google-vertex"]?.options ?? {}), - }, - models: { - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - providerID: "google-vertex", - api: { id: "gemini-2.5-pro", url: "", npm: "@ai-sdk/google-vertex" }, - name: "Gemini 2.5 Pro", - family: "gemini-2.5-pro", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 32000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-01-01", - variants: {}, - }, - }, - }) - } - - if (config?.provider?.groq || config?.model?.startsWith("groq/") || process.env.GROQ_API_KEY) { - providers.push({ - id: "groq", - name: "Groq", - source: "config", - env: ["GROQ_API_KEY"], - options: { - ...(config?.provider?.groq?.options ?? {}), - }, - models: { - "llama-3.3-70b-versatile": { - id: "llama-3.3-70b-versatile", - providerID: "groq", - api: { id: "llama-3.3-70b-versatile", url: "", npm: "@ai-sdk/groq" }, - name: "Llama 3.3 70B Versatile", - family: "llama-3.3-70b", - capabilities: { - temperature: true, - reasoning: true, - attachment: false, - toolcall: true, - input: { text: true, audio: false, image: false, video: false, pdf: false }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 32000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-01-01", - variants: {}, - }, - }, - }) - } - - if (config?.provider?.mistral || config?.model?.startsWith("mistral/") || process.env.MISTRAL_API_KEY) { - providers.push({ - id: "mistral", - name: "Mistral", - source: "config", - env: ["MISTRAL_API_KEY"], - options: { - ...(config?.provider?.mistral?.options ?? {}), - }, - models: { - "mistral-small-latest": { - id: "mistral-small-latest", - providerID: "mistral", - api: { id: "mistral-small-latest", url: "", npm: "@ai-sdk/mistral" }, - name: "Mistral Small Latest", - family: "mistral-small", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 32000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-01-01", - variants: {}, - }, - }, - }) - } - - if (providers.length === 0) { - providers.push({ - id: "anthropic", - name: "Anthropic", - source: "custom", - env: ["ANTHROPIC_API_KEY"], - options: {}, - models: { - "claude-sonnet-4-20250514": { - id: "claude-sonnet-4-20250514", - providerID: "anthropic", - api: { id: "claude-sonnet-4-20250514", url: "", npm: "@ai-sdk/anthropic" }, - name: "Claude Sonnet 4", - family: "claude-sonnet-4", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: true }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: 200000, output: 32000 }, - status: "active", - options: {}, - headers: {}, - release_date: "2025-05-14", - variants: {}, - }, - }, - }) - } - - const defaults = Object.fromEntries( - providers.map((provider) => { - const specifiedModel = - typeof config.model === "string" && config.model.startsWith(provider.id + "/") - ? config.model.slice(provider.id.length + 1) - : undefined - return [ - provider.id, - specifiedModel ?? Object.keys(provider.models)[0] ?? "", - ] - }), - ) - - return { providers, default: defaults } - }) -} - -async function runServiceWithCurrentInstance( - serviceModule: { Service: any; defaultLayer: any }, - ctx: any, - fn: (service: any) => any, -): Promise { - const [{ Effect, ManagedRuntime }, { InstanceRef }, { memoMap }, { Instance }] = await Promise.all([ - import("effect"), - import("../../effect/instance-ref"), - import("../../effect/run-service"), - import("../../project/instance"), - ]) - console.error("[opencode-acp] serviceRuntime:ctx", ctx.directory) - ;(globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown }).__agentosOpencodeInstanceFallback = - ctx - const runtime = ManagedRuntime.make(serviceModule.defaultLayer, { memoMap }) - try { - const result = await Instance.restore( - ctx, - () => - runtime.runPromise( - Effect.provideService(serviceModule.Service.use(fn), InstanceRef, ctx), - ), - ) - console.error("[opencode-acp] serviceRuntime:done", ctx.directory) - return result - } catch (error) { - console.error( - "[opencode-acp] serviceRuntime:error", - error instanceof Error ? error.stack ?? error.message : String(error), - ) - throw error - } -} - -async function loadAgentCatalog(directory: string | undefined) { - const { Config } = await import("../../config/config") - - return withDirectory(directory, async () => { - const cfg = await Config.get() - const agents = new Map([ - [ - "build", - { - name: "build", - description: "The default agent. Executes tools based on configured permissions.", - mode: "primary", - }, - ], - [ - "plan", - { - name: "plan", - description: "Plan mode. Disallows all edit tools.", - mode: "primary", - }, - ], - [ - "general", - { - name: "general", - description: - "General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel.", - mode: "subagent", - }, - ], - [ - "explore", - { - name: "explore", - description: - "Fast agent specialized for exploring codebases. Use this when you need to quickly find files, search code, or answer questions about the codebase.", - mode: "subagent", - }, - ], - ["compaction", { name: "compaction", mode: "primary", hidden: true }], - ["title", { name: "title", mode: "primary", hidden: true }], - ["summary", { name: "summary", mode: "primary", hidden: true }], - ]) - - for (const [key, value] of Object.entries(cfg.agent ?? {})) { - if (value?.disable) { - agents.delete(key) - continue - } - const current = agents.get(key) ?? { - name: key, - mode: "all", - } - agents.set(key, { - ...current, - ...(value?.name ? { name: value.name } : {}), - ...(value?.description ? { description: value.description } : {}), - ...(value?.mode ? { mode: value.mode } : {}), - ...(value?.hidden !== undefined ? { hidden: value.hidden } : {}), - }) - } - - const defaultAgent = cfg.default_agent ?? "build" - return Array.from(agents.values()).sort((a, b) => { - const aRank = a.name === defaultAgent ? 0 : 1 - const bRank = b.name === defaultAgent ? 0 : 1 - if (aRank !== bRank) return aRank - bRank - return a.name.localeCompare(b.name) - }) - }) -} - -async function loadCommandCatalog(directory: string | undefined) { - const { Config } = await import("../../config/config") - - return withDirectory(directory, async () => { - const cfg = await Config.get() - const commands = new Map([ - ["init", { name: "init", description: "create/update AGENTS.md" }], - ["review", { name: "review", description: "review changes [commit|branch|pr], defaults to uncommitted" }], - ]) - - for (const [name, command] of Object.entries(cfg.command ?? {})) { - commands.set(name, { - name, - ...(command?.description ? { description: command.description } : {}), - }) - } - - return Array.from(commands.values()).sort((a, b) => a.name.localeCompare(b.name)) - }) -} - -async function withDirectory( - directory: string | undefined, - fn: (ctx: any) => Promise, -): Promise { - console.error("[opencode-acp] withDirectory:start", directory ?? process.cwd()) - const [{ Instance }, { InstanceBootstrap }] = await Promise.all([ - import("../../project/instance"), - import("../../project/bootstrap"), - ]) - console.error("[opencode-acp] withDirectory:modules:done", directory ?? process.cwd()) - return Instance.provide({ - directory: directory ?? process.cwd(), - init: InstanceBootstrap, - fn: () => { - const ctx = Instance.current - ;(globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown }).__agentosOpencodeInstanceFallback = - ctx - return fn(ctx) - }, - }) -} - -async function ensureProjectors() { - if ((globalThis as any).__agentosOpencodeProjectorsReady) return - console.error("[opencode-acp] projectors:ensure:start") - const [{ SyncEvent }, { default: sessionProjectors }] = await Promise.all([ - import("../../sync"), - import("../../session/projectors"), - ]) - SyncEvent.init({ - projectors: sessionProjectors, - }) - ;(globalThis as any).__agentosOpencodeProjectorsReady = true - console.error("[opencode-acp] projectors:ensure:done") -} - -async function createGlobalEventStream(signal?: AbortSignal) { - const { GlobalBus } = await import("../../bus/global") - let closed = false - const queue: any[] = [] - let nextResolve: ((result: IteratorResult) => void) | undefined - - const close = () => { - if (closed) return - closed = true - GlobalBus.off("event", onEvent) - signal?.removeEventListener("abort", close) - nextResolve?.({ done: true, value: undefined }) - nextResolve = undefined - } - - const onEvent = (event: any) => { - if (closed) return - if (nextResolve) { - const resolve = nextResolve - nextResolve = undefined - resolve({ done: false, value: event }) - return - } - queue.push(event) - } - - GlobalBus.on("event", onEvent) - if (signal) { - if (signal.aborted) close() - else signal.addEventListener("abort", close, { once: true }) - } - - return { - stream: (async function* () { - try { - while (true) { - if (queue.length > 0) { - yield queue.shift() - continue - } - - const next = await new Promise>((resolve) => { - nextResolve = resolve - }) - if (next.done) return - yield next.value - } - } finally { - close() - } - })(), - } -} - -function createLocalSdk() { - return { - global: { - event: async ({ signal }: { signal?: AbortSignal }) => createGlobalEventStream(signal), - }, - permission: { - reply: async ({ requestID, reply, directory }: any) => - wrapData( - await withDirectory(directory, async () => { - const { Permission } = await import("../../permission") - await Permission.reply({ requestID, reply }) - return true - }), - ), - }, - config: { - get: async ({ directory }: any) => - wrapData( - await (async () => { - const { Config } = await import("../../config/config") - return withDirectory(directory, async () => { - console.error("[opencode-acp] sdk.config.get:start", directory ?? process.cwd()) - const result = await Config.get() - console.error("[opencode-acp] sdk.config.get:done", directory ?? process.cwd()) - return result - }) - })(), - ), - providers: async ({ directory }: any) => - wrapData( - await (async () => { - console.error("[opencode-acp] sdk.config.providers:start", directory ?? process.cwd()) - return withDirectory(directory, async () => { - const result = await loadProviderCatalog(directory) - console.error( - "[opencode-acp] sdk.config.providers:done", - directory ?? process.cwd(), - result.providers.length, - ) - return result - }) - })(), - ), - }, - app: { - agents: async ({ directory }: any) => - wrapData( - await loadAgentCatalog(directory), - ), - }, - command: { - list: async ({ directory }: any) => - wrapData( - await loadCommandCatalog(directory), - ), - }, - mcp: { - add: async ({ directory, name, config }: any) => - wrapData( - await (async () => { - const { MCP } = await import("../../mcp") - return withDirectory(directory, async () => MCP.add(name, config)) - })(), - ), - }, - session: { - create: async ({ directory, title, permission, workspaceID, parentID }: any) => - wrapData( - await (async () => { - await ensureProjectors() - return withDirectory(directory, async (ctx) => { - const { Session } = await import("../../session") - console.error("[opencode-acp] sdk.session.create:start", directory ?? process.cwd()) - try { - const result = await runServiceWithCurrentInstance(Session, ctx, (service) => - service.create({ - ...(title ? { title } : {}), - ...(permission ? { permission } : {}), - ...(workspaceID ? { workspaceID } : {}), - ...(parentID ? { parentID } : {}), - }), - ) - console.error("[opencode-acp] sdk.session.create:done", directory ?? process.cwd()) - return result - } catch (error) { - console.error( - "[opencode-acp] sdk.session.create:error", - error instanceof Error ? error.stack ?? error.message : String(error), - ) - throw error - } - }) - })(), - ), - get: async ({ sessionID, directory }: any) => - wrapData( - await (async () => { - const { Session } = await import("../../session") - return withDirectory(directory, async (ctx) => - runServiceWithCurrentInstance(Session, ctx, (service) => service.get(sessionID)), - ) - })(), - ), - list: async ({ directory, roots, start, search, limit }: any) => - wrapData( - await (async () => { - const { Session } = await import("../../session") - return withDirectory(directory, async (ctx) => { - return runServiceWithCurrentInstance(Session, ctx, (service) => - service.list({ directory, roots, start, search, limit }), - ) - }) - })(), - ), - fork: async ({ sessionID, messageID, directory }: any) => - wrapData( - await (async () => { - const { Session } = await import("../../session") - await ensureProjectors() - return withDirectory(directory, async (ctx) => - runServiceWithCurrentInstance(Session, ctx, (service) => - service.fork({ - sessionID, - ...(messageID ? { messageID } : {}), - }), - ), - ) - })(), - ), - messages: async ({ sessionID, limit, directory }: any) => - wrapData( - await (async () => { - const { Session } = await import("../../session") - return withDirectory(directory, async (ctx) => - runServiceWithCurrentInstance(Session, ctx, (service) => - service.messages({ sessionID, ...(limit ? { limit } : {}) }), - ), - ) - })(), - ), - message: async ({ sessionID, messageID, directory }: any) => - wrapData( - await (async () => { - const { MessageV2 } = await import("../../session/message-v2") - return withDirectory(directory, async () => MessageV2.get({ sessionID, messageID })) - })(), - ), - prompt: async ({ sessionID, directory, ...input }: any) => - wrapData( - await (async () => { - const { SessionPrompt } = await import("../../session/prompt") - await ensureProjectors() - return withDirectory(directory, async (ctx) => - runServiceWithCurrentInstance(SessionPrompt, ctx, (service) => - service.prompt({ sessionID, ...input }), - ), - ) - })(), - ), - command: async ({ sessionID, directory, ...input }: any) => - wrapData( - await (async () => { - const { SessionPrompt } = await import("../../session/prompt") - await ensureProjectors() - return withDirectory(directory, async (ctx) => - runServiceWithCurrentInstance(SessionPrompt, ctx, (service) => - service.command({ sessionID, ...input }), - ), - ) - })(), - ), - summarize: async ({ sessionID, directory, providerID, modelID, auto = false }: any) => - wrapData( - await (async () => { - const [{ Session }, { SessionRevert }, { SessionCompaction }, { SessionPrompt }, { Agent }] = - await Promise.all([ - import("../../session"), - import("../../session/revert"), - import("../../session/compaction"), - import("../../session/prompt"), - import("../../agent/agent"), - ]) - await ensureProjectors() - return withDirectory(directory, async (ctx) => { - const session = await Session.get(sessionID) - await SessionRevert.cleanup(session) - const messages = await Session.messages({ sessionID }) - let currentAgent = await Agent.defaultAgent() - for (let i = messages.length - 1; i >= 0; i--) { - const info = messages[i].info - if (info.role === "user") { - currentAgent = info.agent || (await Agent.defaultAgent()) - break - } - } - await SessionCompaction.create({ - sessionID, - agent: currentAgent, - model: { providerID, modelID }, - auto, - }) - await runServiceWithCurrentInstance(SessionPrompt, ctx, (service) => - service.loop({ sessionID }), - ) - return true - }) - })(), - ), - abort: async ({ sessionID, directory }: any) => - wrapData( - await (async () => { - const { SessionPrompt } = await import("../../session/prompt") - return withDirectory(directory, async (ctx) => { - await runServiceWithCurrentInstance(SessionPrompt, ctx, (service) => - service.cancel(sessionID), - ) - return true - }) - })(), - ), - }, - } as any -} - -export const AcpCommand = cmd({ - command: "acp", - describe: "start ACP (Agent Client Protocol) server", - builder: (yargs) => { - return withNetworkOptions(yargs).option("cwd", { - describe: "working directory", - type: "string", - default: process.cwd(), - }) - }, - handler: async (args: NetworkOptions) => { - process.env.OPENCODE_CLIENT = "acp" - process.env.OPENCODE_DISABLE_MODELS_FETCH = process.env.OPENCODE_DISABLE_MODELS_FETCH ?? "1" - process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS ?? "1" - const [{ ACP }] = await Promise.all([import("../../acp/agent")]) - - const log = Log.create({ service: "acp-command" }) - console.error("[opencode-acp] bootstrap:entered") - console.error("[opencode-acp] network:resolve:start") - const opts = await resolveNetworkOptions(args) - console.error("[opencode-acp] network:resolve:done", JSON.stringify(opts)) - - console.error("[opencode-acp] sdk:create:start") - const sdk = createLocalSdk() - console.error("[opencode-acp] sdk:create:done") - - console.error("[opencode-acp] streams:create:start") - const input = new WritableStream({ - write(chunk) { - return new Promise((resolve, reject) => { - process.stdout.write(chunk, (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - }, - }) - const output = new ReadableStream({ - start(controller) { - process.stdin.on("data", (chunk: Buffer) => { - controller.enqueue(new Uint8Array(chunk)) - }) - process.stdin.on("end", () => controller.close()) - process.stdin.on("error", (err) => controller.error(err)) - }, - }) - console.error("[opencode-acp] streams:create:done") - - console.error("[opencode-acp] ndjson:start") - const stream = ndJsonStream(input, output) - console.error("[opencode-acp] ndjson:done") - console.error("[opencode-acp] agent:init:start") - const agent = await ACP.init({ sdk }) - console.error("[opencode-acp] agent:init:done") - - console.error("[opencode-acp] connection:start") - new AgentSideConnection((conn) => { - return agent.create(conn, { sdk }) - }, stream) - console.error("[opencode-acp] connection:done") - - log.info("setup connection") - process.stdin.resume() - await new Promise((resolve, reject) => { - process.stdin.on("end", resolve) - process.stdin.on("error", reject) - }) - }, -}) -`, - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/server/instance.ts", - (contents) => - contents - .replace('import { TuiRoutes } from "./routes/tui"\n', "") - .replace('import { PtyRoutes } from "./routes/pty"\n', "") - .replace(' .route("/pty", PtyRoutes())\n', "") - .replace(' .route("/tui", TuiRoutes())\n', ""), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/agent/agent.ts", - (contents) => - contents.replace( - ` [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: - "allow",`, - ` [path.relative(ctx.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: - "allow",`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/shell/shell.ts", - (contents) => contents.replaceAll("Bun.which(", "which("), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/server/server.ts", - (contents) => - contents - .replace('import { MDNS } from "./mdns"\n', "") - .replace( - ` if (shouldPublishMDNS) { - MDNS.publish(server.port, opts.mdnsDomain) - } else if (opts.mdns) {`, - ` let mdns: - | { - publish(port: number, domain?: string): void - unpublish(): void - } - | undefined - if (shouldPublishMDNS) { - ;({ MDNS: mdns } = await import("./mdns")) - mdns.publish(server.port, opts.mdnsDomain) - } else if (opts.mdns) {`, - ) - .replace( - " if (shouldPublishMDNS) MDNS.unpublish()\n", - " if (shouldPublishMDNS) mdns?.unpublish()\n", - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/project/bootstrap.ts", - (contents) => - contents.replace( - ` Bus.subscribe(Command.Event.Executed, async (payload) => { - if (payload.properties.name === Command.Default.INIT) { - Project.setInitialized(Instance.project.id) - } - }) -`, - ` Log.Default.info("bootstrap step", { step: "bus.subscribe:skipped" }) -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/acp/agent.ts", - (contents) => - contents - .replaceAll( - ` console.error("[opencode-acp] agent.loadSessionMode:commands:done", directory, commands.length) -`, - "", - ) - .replace( - ` const defaultAgentName = await AgentModule.defaultAgent() - const resolvedModeId = - availableModes.find((mode) => mode.name === defaultAgentName)?.id ?? availableModes[0].id -`, - ` const resolvedModeId = availableModes[0].id -`, - ) - .replace( - ` const model = await defaultModel(this.config, directory) -`, - ` console.error("[opencode-acp] agent.newSession:defaultModel:start", directory) - const model = await defaultModel(this.config, directory) - console.error("[opencode-acp] agent.newSession:defaultModel:done", directory, model.providerID, model.modelID) -`, - ) - .replace( - ` const state = await this.sessionManager.create(params.cwd, params.mcpServers, model) -`, - ` console.error("[opencode-acp] agent.newSession:sessionManager.create:start", directory) - const state = await this.sessionManager.create(params.cwd, params.mcpServers, model) - console.error("[opencode-acp] agent.newSession:sessionManager.create:done", directory, state.id) -`, - ) - .replace( - ` const load = await this.loadSessionMode({ -`, - ` console.error("[opencode-acp] agent.newSession:loadSessionMode:start", directory) - const load = await this.loadSessionMode({ -`, - ) - .replace( - ` const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers) -`, - ` console.error("[opencode-acp] agent.loadSessionMode:providers:start", directory) - const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers) - console.error("[opencode-acp] agent.loadSessionMode:providers:done", directory, providers.length) -`, - ) - .replace( - ` const modeState = await this.resolveModeState(directory, sessionId) -`, - ` console.error("[opencode-acp] agent.loadSessionMode:resolveModeState:start", directory) - const modeState = await this.resolveModeState(directory, sessionId) - console.error("[opencode-acp] agent.loadSessionMode:resolveModeState:done", directory, modeState.availableModes.length) -`, - ) - .replace( - ` const commands = await this.config.sdk.command -`, - ` console.error("[opencode-acp] agent.loadSessionMode:commands:start", directory) - const commands = await this.config.sdk.command -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/project/bootstrap.ts", - (contents) => - contents.replace( - ` Log.Default.info("bootstrap step", { step: "snapshot.init:start" }) - Snapshot.init() - Log.Default.info("bootstrap step", { step: "snapshot.init:done" }) -`, - ` Log.Default.info("bootstrap step", { step: "snapshot.init:skipped" }) -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/util/filesystem.ts", - (contents) => { - if (contents.includes("cachedAgentOsGuestPathMappings")) { - return contents; - } - - return contents - .replace( - 'import { dirname, join, relative, resolve as pathResolve, win32 } from "path"\n', - `import { dirname, join, relative, resolve as pathResolve, win32 } from "path" - -type AgentOsGuestPathMapping = { - guestPath?: string - hostPath?: string -} - -let cachedAgentOsGuestPathMappings: - | Array<{ guestPath: string; hostPath: string }> - | undefined - -function runtimeWindowsPath(p: string): string { - if (process.platform !== "win32") return p - return p - .replace(/^\\/([a-zA-Z]):(?:[\\\\/]|$)/, (_, drive) => \`\${drive.toUpperCase()}:/\`) - .replace(/^\\/([a-zA-Z])(?:\\/|$)/, (_, drive) => \`\${drive.toUpperCase()}:/\`) - .replace(/^\\/cygdrive\\/([a-zA-Z])(?:\\/|$)/, (_, drive) => \`\${drive.toUpperCase()}:/\`) - .replace(/^\\/mnt\\/([a-zA-Z])(?:\\/|$)/, (_, drive) => \`\${drive.toUpperCase()}:/\`) -} - -function agentOsGuestPathMappings() { - if (cachedAgentOsGuestPathMappings) return cachedAgentOsGuestPathMappings - const raw = process.env.AGENT_OS_GUEST_PATH_MAPPINGS - if (!raw) { - cachedAgentOsGuestPathMappings = [] - return cachedAgentOsGuestPathMappings - } - - try { - const parsed = JSON.parse(raw) - if (!Array.isArray(parsed)) { - cachedAgentOsGuestPathMappings = [] - return cachedAgentOsGuestPathMappings - } - - cachedAgentOsGuestPathMappings = parsed - .filter( - (item): item is AgentOsGuestPathMapping => - typeof item === "object" && - item !== null && - typeof item.guestPath === "string" && - typeof item.hostPath === "string", - ) - .map((item) => ({ - guestPath: item.guestPath === "/" ? "/" : pathResolve(runtimeWindowsPath(item.guestPath)), - hostPath: pathResolve(runtimeWindowsPath(item.hostPath)), - })) - .sort((left, right) => right.guestPath.length - left.guestPath.length) - return cachedAgentOsGuestPathMappings - } catch { - cachedAgentOsGuestPathMappings = [] - return cachedAgentOsGuestPathMappings - } -} - -function runtimePath(p: string): string { - if (!p.startsWith("/")) return p - - const normalized = pathResolve(runtimeWindowsPath(p)) - for (const mapping of agentOsGuestPathMappings()) { - if ( - mapping.guestPath !== "/" && - normalized !== mapping.guestPath && - !normalized.startsWith(\`\${mapping.guestPath}/\`) - ) { - continue - } - - const suffix = - mapping.guestPath === "/" - ? normalized.slice(1) - : normalized.slice(mapping.guestPath.length).replace(/^[/\\\\]+/, "") - return suffix ? join(mapping.hostPath, suffix) : mapping.hostPath - } - - return p -} -`, - ) - .replace( - ` return existsSync(p) -`, - ` return existsSync(runtimePath(p)) -`, - ) - .replace( - ` return statSync(p).isDirectory() -`, - ` return statSync(runtimePath(p)).isDirectory() -`, - ) - .replace( - ` return statSync(p, { throwIfNoEntry: false }) ?? undefined -`, - ` return statSync(runtimePath(p), { throwIfNoEntry: false }) ?? undefined -`, - ) - .replace( - ` return statFile(p).catch((e) => { -`, - ` return statFile(runtimePath(p)).catch((e) => { -`, - ) - .replace( - ` return readFile(p, "utf-8") -`, - ` return readFile(runtimePath(p), "utf-8") -`, - ) - .replace( - ` return JSON.parse(await readFile(p, "utf-8")) -`, - ` return JSON.parse(await readFile(runtimePath(p), "utf-8")) -`, - ) - .replace( - ` return readFile(p) -`, - ` return readFile(runtimePath(p)) -`, - ) - .replace( - ` const buf = await readFile(p) -`, - ` const buf = await readFile(runtimePath(p)) -`, - ) - .replace( - ` try { - if (mode) { - await writeFile(p, content, { mode }) - } else { - await writeFile(p, content) - } - } catch (e) { - if (isEnoent(e)) { - await mkdir(dirname(p), { recursive: true }) - if (mode) { - await writeFile(p, content, { mode }) - } else { - await writeFile(p, content) - } - return - } - throw e - } -`, - ` const target = runtimePath(p) - try { - if (mode) { - await writeFile(target, content, { mode }) - } else { - await writeFile(target, content) - } - } catch (e) { - if (isEnoent(e)) { - await mkdir(dirname(target), { recursive: true }) - if (mode) { - await writeFile(target, content, { mode }) - } else { - await writeFile(target, content) - } - return - } - throw e - } -`, - ) - .replace( - ` const dir = dirname(p) -`, - ` const target = runtimePath(p) - const dir = dirname(target) -`, - ) - .replace( - ` const writeStream = createWriteStream(p) -`, - ` const writeStream = createWriteStream(target) -`, - ) - .replace( - ` await chmod(p, mode) -`, - ` await chmod(target, mode) -`, - ); - }, - ); - - for (const relativePath of [ - "packages/opencode/src/cli/cmd/mcp.ts", - "packages/opencode/src/config/config.ts", - "packages/opencode/src/config/migrate-tui-config.ts", - "packages/opencode/src/config/paths.ts", - "packages/opencode/src/plugin/install.ts", - ]) { - await rewriteSourceFile(sourceRoot, relativePath, (contents) => - contents.replaceAll( - '"jsonc-parser"', - '"jsonc-parser/lib/esm/main.js"', - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/plugin/index.ts", - (contents) => - contents.replace( - ` const state = Instance.state(async () => { - const client = createOpencodeClient({ -`, - ` const state = Instance.state(async () => { - if (Flag.OPENCODE_CLIENT === "acp") { - log.info("skipping plugin runtime in ACP mode") - return { - hooks: [], - input: { - client: undefined as any, - project: Instance.project, - worktree: Instance.worktree, - directory: Instance.directory, - serverUrl: "", - $: Bun.$, - } as PluginInput, - } - } - - const client = createOpencodeClient({ -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/session/index.ts", - (contents) => - contents - .replace('import { SessionPrompt } from "./prompt"\n', "") - .replace('import { Command } from "../command"\n', "") - .replace( - ` const initialize = Effect.fn("Session.initialize")(function* (input: { - sessionID: SessionID - modelID: ModelID - providerID: ProviderID - messageID: MessageID - }) { - yield* Effect.promise(() => - SessionPrompt.command({ - sessionID: input.sessionID, - messageID: input.messageID, - model: input.providerID + "/" + input.modelID, - command: Command.Default.INIT, - arguments: "", - }), - ) - }) -`, - ` const initialize = Effect.fn("Session.initialize")(function* (input: { - sessionID: SessionID - modelID: ModelID - providerID: ProviderID - messageID: MessageID - }) { - const [{ SessionPrompt }, { Command }] = yield* Effect.promise(() => - Promise.all([import("./prompt"), import("../command")]), - ) - yield* Effect.promise(() => - SessionPrompt.command({ - sessionID: input.sessionID, - messageID: input.messageID, - model: input.providerID + "/" + input.modelID, - command: Command.Default.INIT, - arguments: "", - }), - ) - }) -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/session/prompt.ts", - (contents) => - contents - .replace( - / const text = yield\* Effect\.promise\(async \(signal\) => \{\n[\s\S]*? return result\.text\n \}\)\n/, - ` const instanceCtx = yield* InstanceState.context - const text = yield* Effect.promise((signal) => - Instance.restore(instanceCtx, async () => { - const mdl = ag.model - ? await Provider.getModel(ag.model.providerID, ag.model.modelID) - : ((await Provider.getSmallModel(input.providerID)) ?? - (await Provider.getModel(input.providerID, input.modelID))) - const msgs = onlySubtasks - ? [{ role: "user" as const, content: subtasks.map((p) => p.prompt).join("\\n") }] - : await MessageV2.toModelMessages(context, mdl) - const result = await LLM.stream({ - agent: ag, - user: firstInfo, - system: [], - small: true, - tools: {}, - model: mdl, - abort: signal, - sessionID: input.session.id, - retries: 2, - messages: [{ role: "user", content: "Generate a title for this conversation:\\n" }, ...msgs], - }) - return result.text - }), - ) -`, - ) - .replace( - ` const getModel = (providerID: ProviderID, modelID: ModelID, sessionID: SessionID) => - Effect.promise(() => - Provider.getModel(providerID, modelID).catch((e) => { - if (Provider.ModelNotFoundError.isInstance(e)) { - const hint = e.data.suggestions?.length ? \` Did you mean: \${e.data.suggestions.join(", ")}?\` : "" - Bus.publish(Session.Event.Error, { - sessionID, - error: new NamedError.Unknown({ - message: \`Model not found: \${e.data.providerID}/\${e.data.modelID}.\${hint}\`, - }).toObject(), - }) - } - throw e - }), - ) -`, - ` const getModel = (providerID: ProviderID, modelID: ModelID, sessionID: SessionID) => - Effect.gen(function* () { - const instanceCtx = yield* InstanceState.context - return yield* Effect.promise(() => - Instance.restore(instanceCtx, () => - Provider.getModel(providerID, modelID).catch((e) => { - if (Provider.ModelNotFoundError.isInstance(e)) { - const hint = e.data.suggestions?.length ? \` Did you mean: \${e.data.suggestions.join(", ")}?\` : "" - Bus.publish(Session.Event.Error, { - sessionID, - error: new NamedError.Unknown({ - message: \`Model not found: \${e.data.providerID}/\${e.data.modelID}.\${hint}\`, - }).toObject(), - }) - } - throw e - }), - ), - ) - }) -`, - ) - .replace( - ` const model = input.model ?? ag.model ?? (yield* lastModel(input.sessionID)) - const full = - !input.variant && ag.variant - ? yield* Effect.promise(() => Provider.getModel(model.providerID, model.modelID).catch(() => undefined)) - : undefined -`, - ` const model = input.model ?? ag.model ?? (yield* lastModel(input.sessionID)) - const instanceCtx = yield* InstanceState.context - const full = - !input.variant && ag.variant - ? yield* Effect.promise(() => - Instance.restore(instanceCtx, () => - Provider.getModel(model.providerID, model.modelID).catch(() => undefined), - ), - ) - : undefined -`, - ) - .replace( - ` Effect.promise(() => Provider.getModel(info.model.providerID, info.model.modelID)).pipe( -`, - ` Effect.gen(function* () { - const instanceCtx = yield* InstanceState.context - return yield* Effect.promise(() => - Instance.restore(instanceCtx, () => - Provider.getModel(info.model.providerID, info.model.modelID), - ), - ) - }).pipe( -`, - ) - .replace( - ` const tools = yield* resolveTools({ - agent, - session, - model, - tools: lastUser.tools, - processor: handle, - bypassAgentCheck, - messages: msgs, - }) -`, - ` const tools = yield* resolveTools({ - agent, - session, - model, - tools: lastUser.tools, - processor: handle, - bypassAgentCheck, - messages: msgs, - }) -`, - ) - .replace( - ` const [skills, env, instructions, modelMsgs] = yield* Effect.promise(() => - Promise.all([ - SystemPrompt.skills(agent), - SystemPrompt.environment(model), - InstructionPrompt.system(), - MessageV2.toModelMessages(msgs, model), - ]), - ) -`, - ` const instanceCtx = yield* InstanceState.context - const [skills, env, instructions, modelMsgs] = yield* Effect.promise(() => - Instance.restore(instanceCtx, () => - (async () => { - console.error("[opencode-acp] prompt.system:skills:start", sessionID) - const skills = await Instance.restore(instanceCtx, () => SystemPrompt.skills(agent)) - console.error("[opencode-acp] prompt.system:skills:done", sessionID) - console.error("[opencode-acp] prompt.system:env:start", sessionID) - const env = await Instance.restore(instanceCtx, () => SystemPrompt.environment(model)) - console.error("[opencode-acp] prompt.system:env:done", sessionID) - console.error("[opencode-acp] prompt.system:instructions:start", sessionID) - const instructions = await Instance.restore(instanceCtx, () => InstructionPrompt.system()) - console.error("[opencode-acp] prompt.system:instructions:done", sessionID) - console.error("[opencode-acp] prompt.system:modelMessages:start", sessionID) - const modelMsgs = await Instance.restore(instanceCtx, () => - MessageV2.toModelMessages(msgs, model), - ) - console.error("[opencode-acp] prompt.system:modelMessages:done", sessionID) - return [skills, env, instructions, modelMsgs] as const - })(), - ), - ) -`, - ) - .replace( - ` const defaultLayer = Layer.unwrap( - Effect.sync(() => - layer.pipe( - Layer.provide(SessionStatus.layer), - Layer.provide(SessionCompaction.defaultLayer), - Layer.provide(SessionProcessor.defaultLayer), - Layer.provide(Command.defaultLayer), - Layer.provide(Permission.layer), - Layer.provide(MCP.defaultLayer), - Layer.provide(LSP.defaultLayer), - Layer.provide(FileTime.defaultLayer), - Layer.provide(ToolRegistry.defaultLayer), - Layer.provide(Truncate.layer), - Layer.provide(AppFileSystem.defaultLayer), - Layer.provide(Plugin.defaultLayer), - Layer.provide(Session.defaultLayer), - Layer.provide(Agent.defaultLayer), - Layer.provide(Bus.layer), - ), - ), - ) -`, - ` export const defaultLayer = Layer.unwrap( - Effect.sync(() => - layer.pipe( - Layer.provide(SessionStatus.layer), - Layer.provide(SessionCompaction.defaultLayer), - Layer.provide(SessionProcessor.defaultLayer), - Layer.provide(Command.defaultLayer), - Layer.provide(Permission.layer), - Layer.provide(MCP.defaultLayer), - Layer.provide(LSP.defaultLayer), - Layer.provide(FileTime.defaultLayer), - Layer.provide(ToolRegistry.defaultLayer), - Layer.provide(Truncate.layer), - Layer.provide(AppFileSystem.defaultLayer), - Layer.provide(Plugin.defaultLayer), - Layer.provide(Session.defaultLayer), - Layer.provide(Agent.defaultLayer), - Layer.provide(Bus.layer), - ), - ), - ) -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/session/llm.ts", - (contents) => { - let updated = contents; - if ( - !updated.includes( - 'import { InstanceState } from "@/effect/instance-state"\n', - ) - ) { - updated = updated.replace( - 'import { Installation } from "@/installation"\n', - 'import { Installation } from "@/installation"\nimport { InstanceState } from "@/effect/instance-state"\n', - ); - } - updated = updated - .replace( - ` stream(input) { - return Stream.scoped( - Stream.unwrap( - Effect.gen(function* () { - const ctrl = yield* Effect.acquireRelease( - Effect.sync(() => new AbortController()), - (ctrl) => Effect.sync(() => ctrl.abort()), - ) - - const result = yield* Effect.promise(() => LLM.stream({ ...input, abort: ctrl.signal })) - - return Stream.fromAsyncIterable(result.fullStream, (e) => - e instanceof Error ? e : new Error(String(e)), - ) - }), - ), - ) - }, -`, - ` stream(input) { - return Stream.scoped( - Stream.unwrap( - Effect.gen(function* () { - const instanceCtx = yield* InstanceState.context - const ctrl = yield* Effect.acquireRelease( - Effect.sync(() => new AbortController()), - (ctrl) => Effect.sync(() => ctrl.abort()), - ) - - const result = yield* Effect.promise(() => - Instance.restore(instanceCtx, () => LLM.stream({ ...input, abort: ctrl.signal })), - ) - - return Stream.fromAsyncIterable(result.fullStream, (e) => - e instanceof Error ? e : new Error(String(e)), - ) - }), - ), - ) - }, -`, - ) - .replace( - ` const [language, cfg, provider, auth] = await Promise.all([ - Provider.getLanguage(input.model), - Config.get(), - Provider.getProvider(input.model.providerID), - Auth.get(input.model.providerID), - ]) -`, - ` const instanceCtx = Instance.current - const [language, cfg, provider, auth] = await Instance.restore(instanceCtx, () => - Promise.all([ - Provider.getLanguage(input.model), - Config.get(), - Provider.getProvider(input.model.providerID), - Auth.get(input.model.providerID), - ]), - ) -`, - ) - .replace( - ` await Plugin.trigger( - "experimental.chat.system.transform", - { sessionID: input.sessionID, model: input.model }, - { system }, - ) -`, - ` await Instance.restore(instanceCtx, () => - Plugin.trigger( - "experimental.chat.system.transform", - { sessionID: input.sessionID, model: input.model }, - { system }, - ), - ) -`, - ) - .replace( - ` const params = await Plugin.trigger( - "chat.params", - { - sessionID: input.sessionID, - agent: input.agent.name, - model: input.model, - provider, - message: input.user, - }, - { - temperature: input.model.capabilities.temperature - ? (input.agent.temperature ?? ProviderTransform.temperature(input.model)) - : undefined, - topP: input.agent.topP ?? ProviderTransform.topP(input.model), - topK: ProviderTransform.topK(input.model), - options, - }, - ) -`, - ` const params = await Instance.restore(instanceCtx, () => - Plugin.trigger( - "chat.params", - { - sessionID: input.sessionID, - agent: input.agent.name, - model: input.model, - provider, - message: input.user, - }, - { - temperature: input.model.capabilities.temperature - ? (input.agent.temperature ?? ProviderTransform.temperature(input.model)) - : undefined, - topP: input.agent.topP ?? ProviderTransform.topP(input.model), - topK: ProviderTransform.topK(input.model), - options, - }, - ), - ) -`, - ) - .replace( - ` const { headers } = await Plugin.trigger( - "chat.headers", - { - sessionID: input.sessionID, - agent: input.agent.name, - model: input.model, - provider, - message: input.user, - }, - { - headers: {}, - }, - ) -`, - ` const { headers } = await Instance.restore(instanceCtx, () => - Plugin.trigger( - "chat.headers", - { - sessionID: input.sessionID, - agent: input.agent.name, - model: input.model, - provider, - message: input.user, - }, - { - headers: {}, - }, - ), - ) -`, - ) - .replace( - ` const tools = await resolveTools(input) -`, - ` const tools = await Instance.restore(instanceCtx, () => resolveTools(input)) -`, - ) - .replace( - ` headers: { - ...(input.model.providerID.startsWith("opencode") - ? { - "x-opencode-project": Instance.project.id, - "x-opencode-session": input.sessionID, - "x-opencode-request": input.user.id, - "x-opencode-client": Flag.OPENCODE_CLIENT, - } - : { - "User-Agent": \`opencode/\${Installation.VERSION}\`, - }), - ...input.model.headers, - ...headers, - }, -`, - ` headers: { - ...(input.model.providerID.startsWith("opencode") - ? Instance.restore(instanceCtx, () => ({ - "x-opencode-project": Instance.project.id, - "x-opencode-session": input.sessionID, - "x-opencode-request": input.user.id, - "x-opencode-client": Flag.OPENCODE_CLIENT, - })) - : { - "User-Agent": \`opencode/\${Installation.VERSION}\`, - }), - ...input.model.headers, - ...headers, - }, -`, - ) - .replace( - ` return streamText({ -`, - ` return Instance.restore(instanceCtx, () => streamText({ -`, - ) - .replace( - ` }) - } -`, - ` })) - } -`, - ); - return updated; - }, - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/session/prompt.ts", - (contents) => - contents.replace( - ` execute(args, options) { - return Effect.runPromise( - Effect.gen(function* () { - const ctx = context(args, options) - yield* plugin.trigger( - "tool.execute.before", - { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID }, - { args }, - ) - const result = yield* Effect.promise(() => item.execute(args, ctx)) - const output = { - ...result, - attachments: result.attachments?.map((attachment) => ({ - ...attachment, - id: PartID.ascending(), - sessionID: ctx.sessionID, - messageID: input.processor.message.id, - })), - } - yield* plugin.trigger( - "tool.execute.after", - { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID, args }, - output, - ) - return output - }), - ) - }, -`, - ` execute(args, options) { - const instanceCtx = - ((globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown }) - .__agentosOpencodeInstanceFallback ?? - Instance.current) as any - return Instance.restore(instanceCtx, () => - Effect.runPromise( - Effect.gen(function* () { - const ctx = context(args, options) - yield* plugin.trigger( - "tool.execute.before", - { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID }, - { args }, - ) - const result = yield* Effect.promise(() => item.execute(args, ctx)) - const output = { - ...result, - attachments: result.attachments?.map((attachment) => ({ - ...attachment, - id: PartID.ascending(), - sessionID: ctx.sessionID, - messageID: input.processor.message.id, - })), - } - yield* plugin.trigger( - "tool.execute.after", - { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID, args }, - output, - ) - return output - }), - ), - ) - }, -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/session/instruction.ts", - (contents) => - contents - .replace( - `async function resolveRelative(instruction: string): Promise { - if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) { - return Filesystem.globUp(instruction, Instance.directory, Instance.worktree).catch(() => []) - } - if (!Flag.OPENCODE_CONFIG_DIR) { - log.warn( - \`Skipping relative instruction "\${instruction}" - no OPENCODE_CONFIG_DIR set while project config is disabled\`, - ) - return [] - } - return Filesystem.globUp(instruction, Flag.OPENCODE_CONFIG_DIR, Flag.OPENCODE_CONFIG_DIR).catch(() => []) -} -`, - `async function resolveRelative(instruction: string): Promise { - const ctx = Instance.current - if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) { - return Instance.restore(ctx, () => - Filesystem.globUp(instruction, ctx.directory, ctx.worktree).catch(() => []), - ) - } - if (!Flag.OPENCODE_CONFIG_DIR) { - log.warn( - \`Skipping relative instruction "\${instruction}" - no OPENCODE_CONFIG_DIR set while project config is disabled\`, - ) - return [] - } - return Filesystem.globUp(instruction, Flag.OPENCODE_CONFIG_DIR, Flag.OPENCODE_CONFIG_DIR).catch(() => []) -} -`, - ) - .replace( - ` export async function systemPaths() { - const config = await Config.get() -`, - ` export async function systemPaths() { - const ctx = Instance.current - const config = await Instance.restore(ctx, () => Config.get()) -`, - ) - .replace( - ` const matches = await Filesystem.findUp(file, Instance.directory, Instance.worktree) -`, - ` const matches = await Instance.restore(ctx, () => - Filesystem.findUp(file, ctx.directory, ctx.worktree), - ) -`, - ) - .replace( - ` : await resolveRelative(instruction) -`, - ` : await Instance.restore(ctx, () => resolveRelative(instruction)) -`, - ) - .replace( - ` export async function system() { - const config = await Config.get() - const paths = await systemPaths() -`, - ` export async function system() { - const ctx = Instance.current - const config = await Instance.restore(ctx, () => Config.get()) - const paths = await Instance.restore(ctx, () => systemPaths()) -`, - ) - .replace( - ` export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: string) { - const system = await systemPaths() - const already = loaded(messages) - const results: { filepath: string; content: string }[] = [] - - const target = path.resolve(filepath) - let current = path.dirname(target) - const root = path.resolve(Instance.directory) -`, - ` export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: string) { - const ctx = Instance.current - const system = await Instance.restore(ctx, () => systemPaths()) - const already = loaded(messages) - const results: { filepath: string; content: string }[] = [] - - const target = path.resolve(filepath) - let current = path.dirname(target) - const root = path.resolve(ctx.directory) -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/provider/provider.ts", - (contents) => - contents - .replace( - 'import { Config } from "../config/config"\n', - 'import { Config } from "../config/config"\nimport { Instance } from "../project/instance"\n', - ) - .replace( - ` async function loadProviders() { - const cfg = await Config.get() -`, - ` async function loadProviders() { - const ctx = Instance.current - const cfg = await Instance.restore(ctx, () => Config.get()) -`, - ) - .replace( - ` export async function defaultModel() { - const cfg = await Config.get() -`, - ` export async function defaultModel() { - const ctx = Instance.current - const cfg = await Instance.restore(ctx, () => Config.get()) -`, - ) - .replace( - ` const providers = await loadProviders() - for (const provider of Object.values(providers)) { -`, - ` const providers = await Instance.restore(ctx, () => loadProviders()) - for (const provider of Object.values(providers)) { -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/effect/instance-state.ts", - (contents) => - contents.replace( - ` const fiber = Fiber.getCurrent() - const ctx = fiber ? ServiceMap.getReferenceUnsafe(fiber.services, InstanceRef) : undefined - if (!ctx) return fn - return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F -`, - ` const fiber = Fiber.getCurrent() - const ctx = fiber ? ServiceMap.getReferenceUnsafe(fiber.services, InstanceRef) : undefined - const fallback = (globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown }) - .__agentosOpencodeInstanceFallback as typeof ctx - const boundCtx = ctx ?? fallback - if (!boundCtx) return fn - return ((...args: any[]) => Instance.restore(boundCtx, () => fn(...args))) as F -`, - ).replace( - ` export const context = Effect.fnUntraced(function* () { - return (yield* InstanceRef) ?? Instance.current - })() -`, - ` export const context = Effect.fnUntraced(function* () { - const ref = yield* InstanceRef - if (ref) return ref - const fallback = (globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown }) - .__agentosOpencodeInstanceFallback - if (fallback) return fallback as typeof ref - try { - return Instance.current - } catch (error) { - console.error("[opencode-acp] missing-instance-context", new Error().stack) - throw error - } - })() -`, - ), - ); - - await writeFile( - resolve(sourceRoot, "packages/opencode/src/effect/run-service.ts"), - `import { Effect, Layer, ManagedRuntime } from "effect" -import * as ServiceMap from "effect/ServiceMap" -import { Instance } from "@/project/instance" -import { Context } from "@/util/context" -import { InstanceRef } from "./instance-ref" - -export const memoMap = Layer.makeMemoMapUnsafe() - -function attach(effect: Effect.Effect) { - try { - const ctx = Instance.current - return { - ctx, - effect: Effect.provideService(effect, InstanceRef, ctx), - } - } catch (err) { - if (!(err instanceof Context.NotFound)) throw err - } - return { ctx: undefined, effect } -} - -export function makeRuntime(service: ServiceMap.Service, layer: Layer.Layer) { - let rt: ManagedRuntime.ManagedRuntime | undefined - const getRuntime = () => (rt ??= ManagedRuntime.make(layer, { memoMap })) - - return { - runSync: (fn: (svc: S) => Effect.Effect) => { - const attached = attach(service.use(fn)) - return attached.ctx - ? Instance.restore(attached.ctx, () => getRuntime().runSync(attached.effect)) - : getRuntime().runSync(attached.effect) - }, - runPromiseExit: (fn: (svc: S) => Effect.Effect, options?: Effect.RunOptions) => { - const attached = attach(service.use(fn)) - return attached.ctx - ? Instance.restore(attached.ctx, () => getRuntime().runPromiseExit(attached.effect, options)) - : getRuntime().runPromiseExit(attached.effect, options) - }, - runPromise: (fn: (svc: S) => Effect.Effect, options?: Effect.RunOptions) => { - const attached = attach(service.use(fn)) - return attached.ctx - ? Instance.restore(attached.ctx, () => getRuntime().runPromise(attached.effect, options)) - : getRuntime().runPromise(attached.effect, options) - }, - runFork: (fn: (svc: S) => Effect.Effect) => { - const attached = attach(service.use(fn)) - return attached.ctx - ? Instance.restore(attached.ctx, () => getRuntime().runFork(attached.effect)) - : getRuntime().runFork(attached.effect) - }, - runCallback: (fn: (svc: S) => Effect.Effect) => { - const attached = attach(service.use(fn)) - return attached.ctx - ? Instance.restore(attached.ctx, () => getRuntime().runCallback(attached.effect)) - : getRuntime().runCallback(attached.effect) - }, - } -} -`, - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/tool/tool.ts", - (contents) => - contents - .replace( - 'import { Truncate } from "./truncate"\n', - 'import { Truncate } from "./truncate"\nimport { InstanceState } from "@/effect/instance-state"\n', - ) - .replace( - ` const toolInfo = init instanceof Function ? await init(initCtx) : init - const execute = toolInfo.execute - toolInfo.execute = async (args, ctx) => { -`, - ` const toolInfo = - init instanceof Function ? await InstanceState.bind(() => init(initCtx))() : init - const execute = toolInfo.execute - toolInfo.execute = InstanceState.bind(async (args, ctx) => { -`, - ) - .replace( - ` } - return toolInfo -`, - ` }) - return toolInfo -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/storage/db.ts", - (contents) => - contents.replace( - ` db.run("PRAGMA journal_mode = WAL") - db.run("PRAGMA synchronous = NORMAL") - db.run("PRAGMA busy_timeout = 5000") - db.run("PRAGMA cache_size = -64000") - db.run("PRAGMA foreign_keys = ON") - db.run("PRAGMA wal_checkpoint(PASSIVE)")`, - ` db.$client.exec("PRAGMA journal_mode = WAL") - db.$client.exec("PRAGMA synchronous = NORMAL") - db.$client.exec("PRAGMA busy_timeout = 5000") - db.$client.exec("PRAGMA cache_size = -64000") - db.$client.exec("PRAGMA foreign_keys = ON") - db.$client.exec("PRAGMA wal_checkpoint(PASSIVE)")`, - ), - ); -} - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/provider/provider.ts", - (contents) => - contents - .replace( - `// Direct imports for bundled providers -import { createAmazonBedrock, type AmazonBedrockProviderSettings } from "@ai-sdk/amazon-bedrock" -import { createAnthropic } from "@ai-sdk/anthropic" -import { createAzure } from "@ai-sdk/azure" -import { createGoogleGenerativeAI } from "@ai-sdk/google" -import { createVertex } from "@ai-sdk/google-vertex" -import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic" -import { createOpenAI } from "@ai-sdk/openai" -import { createOpenAICompatible } from "@ai-sdk/openai-compatible" -import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider" -import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/openai-compatible/src" -import { createXai } from "@ai-sdk/xai" -import { createMistral } from "@ai-sdk/mistral" -import { createGroq } from "@ai-sdk/groq" -import { createDeepInfra } from "@ai-sdk/deepinfra" -import { createCerebras } from "@ai-sdk/cerebras" -import { createCohere } from "@ai-sdk/cohere" -import { createGateway } from "@ai-sdk/gateway" -import { createTogetherAI } from "@ai-sdk/togetherai" -import { createPerplexity } from "@ai-sdk/perplexity" -import { createVercel } from "@ai-sdk/vercel" -import { createGitLab } from "@gitlab/gitlab-ai-provider" -import { ProviderTransform } from "./transform" -`, - `import type { LanguageModelV2 } from "@openrouter/ai-sdk-provider" -import { ProviderTransform } from "./transform" -`, - ) - .replace( - ` const BUNDLED_PROVIDERS: Record SDK> = { - "@ai-sdk/amazon-bedrock": createAmazonBedrock, - "@ai-sdk/anthropic": createAnthropic, - "@ai-sdk/azure": createAzure, - "@ai-sdk/google": createGoogleGenerativeAI, - "@ai-sdk/google-vertex": createVertex, - "@ai-sdk/google-vertex/anthropic": createVertexAnthropic, - "@ai-sdk/openai": createOpenAI, - "@ai-sdk/openai-compatible": createOpenAICompatible, - "@openrouter/ai-sdk-provider": createOpenRouter, - "@ai-sdk/xai": createXai, - "@ai-sdk/mistral": createMistral, - "@ai-sdk/groq": createGroq, - "@ai-sdk/deepinfra": createDeepInfra, - "@ai-sdk/cerebras": createCerebras, - "@ai-sdk/cohere": createCohere, - "@ai-sdk/gateway": createGateway, - "@ai-sdk/togetherai": createTogetherAI, - "@ai-sdk/perplexity": createPerplexity, - "@ai-sdk/vercel": createVercel, - "@gitlab/gitlab-ai-provider": createGitLab, - // @ts-ignore (TODO: kill this code so we dont have to maintain it) - "@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible, - } -`, - ` const BUNDLED_PROVIDERS: Record Promise> = { - "@ai-sdk/amazon-bedrock": async (options) => - (await import("@ai-sdk/amazon-bedrock")).createAmazonBedrock(options), - "@ai-sdk/anthropic": async (options) => (await import("@ai-sdk/anthropic")).createAnthropic(options), - "@ai-sdk/azure": async (options) => (await import("@ai-sdk/azure")).createAzure(options), - "@ai-sdk/google": async (options) => (await import("@ai-sdk/google")).createGoogleGenerativeAI(options), - "@ai-sdk/google-vertex": async (options) => (await import("@ai-sdk/google-vertex")).createVertex(options), - "@ai-sdk/google-vertex/anthropic": async (options) => - (await import("@ai-sdk/google-vertex/anthropic")).createVertexAnthropic(options), - "@ai-sdk/openai": async (options) => (await import("@ai-sdk/openai")).createOpenAI(options), - "@ai-sdk/openai-compatible": async (options) => - (await import("@ai-sdk/openai-compatible")).createOpenAICompatible(options), - "@openrouter/ai-sdk-provider": async (options) => - (await import("@openrouter/ai-sdk-provider")).createOpenRouter(options), - "@ai-sdk/xai": async (options) => (await import("@ai-sdk/xai")).createXai(options), - "@ai-sdk/mistral": async (options) => (await import("@ai-sdk/mistral")).createMistral(options), - "@ai-sdk/groq": async (options) => (await import("@ai-sdk/groq")).createGroq(options), - "@ai-sdk/deepinfra": async (options) => (await import("@ai-sdk/deepinfra")).createDeepInfra(options), - "@ai-sdk/cerebras": async (options) => (await import("@ai-sdk/cerebras")).createCerebras(options), - "@ai-sdk/cohere": async (options) => (await import("@ai-sdk/cohere")).createCohere(options), - "@ai-sdk/gateway": async (options) => (await import("@ai-sdk/gateway")).createGateway(options), - "@ai-sdk/togetherai": async (options) => (await import("@ai-sdk/togetherai")).createTogetherAI(options), - "@ai-sdk/perplexity": async (options) => (await import("@ai-sdk/perplexity")).createPerplexity(options), - "@ai-sdk/vercel": async (options) => (await import("@ai-sdk/vercel")).createVercel(options), - "@gitlab/gitlab-ai-provider": async (options) => - (await import("@gitlab/gitlab-ai-provider")).createGitLab(options), - "@ai-sdk/github-copilot": async (options) => - (await import("./sdk/openai-compatible/src")).createOpenaiCompatible(options), - } -`, - ) - .replace( - " async getModel(sdk: ReturnType, modelID: string) {\n", - " async getModel(sdk: any, modelID: string) {\n", - ) - .replace( - ` const loaded = bundledFn({ - name: model.providerID, - ...options, - }) -`, - ` const loaded = await bundledFn({ - name: model.providerID, - ...options, - }) -`, - ), - ); - - await writeFile( - resolve(sourceRoot, "packages/opencode/src/provider/provider.ts"), - `import z from "zod" -import { createAnthropic } from "@ai-sdk/anthropic" -import { createGoogleGenerativeAI } from "@ai-sdk/google" -import { createVertex } from "@ai-sdk/google-vertex" -import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic" -import { createGroq } from "@ai-sdk/groq" -import { createMistral } from "@ai-sdk/mistral" -import { createOpenAI } from "@ai-sdk/openai" -import type { LanguageModelV3 } from "@ai-sdk/provider" -import { NamedError } from "@opencode-ai/util/error" -import { Config } from "../config/config" -import { ModelID, ProviderID } from "./schema" - -type ProviderConfig = { - name?: string - env?: string[] - npm?: string - api?: string - options?: Record - models?: Record -} - -type ProviderSeed = { - name: string - env: string[] - npm: string - models: Array<{ - id: string - name: string - family?: string - reasoning?: boolean - attachment?: boolean - input?: Partial<{ text: boolean; audio: boolean; image: boolean; video: boolean; pdf: boolean }> - output?: Partial<{ text: boolean; audio: boolean; image: boolean; video: boolean; pdf: boolean }> - limit?: Partial<{ context: number; input: number; output: number }> - cost?: Partial<{ - input: number - output: number - cache: Partial<{ read: number; write: number }> - }> - releaseDate?: string - }> -} - -export namespace Provider { - export const Model = z - .object({ - id: ModelID.zod, - providerID: ProviderID.zod, - api: z.object({ - id: z.string(), - url: z.string(), - npm: z.string(), - }), - name: z.string(), - family: z.string().optional(), - capabilities: z.object({ - temperature: z.boolean(), - reasoning: z.boolean(), - attachment: z.boolean(), - toolcall: z.boolean(), - input: z.object({ - text: z.boolean(), - audio: z.boolean(), - image: z.boolean(), - video: z.boolean(), - pdf: z.boolean(), - }), - output: z.object({ - text: z.boolean(), - audio: z.boolean(), - image: z.boolean(), - video: z.boolean(), - pdf: z.boolean(), - }), - interleaved: z.union([ - z.boolean(), - z.object({ - field: z.enum(["reasoning_content", "reasoning_details"]), - }), - ]), - }), - cost: z.object({ - input: z.number(), - output: z.number(), - cache: z.object({ - read: z.number(), - write: z.number(), - }), - experimentalOver200K: z - .object({ - input: z.number(), - output: z.number(), - cache: z.object({ - read: z.number(), - write: z.number(), - }), - }) - .optional(), - }), - limit: z.object({ - context: z.number(), - input: z.number().optional(), - output: z.number(), - }), - status: z.enum(["alpha", "beta", "deprecated", "active"]), - options: z.record(z.string(), z.any()), - headers: z.record(z.string(), z.string()), - release_date: z.string(), - variants: z.record(z.string(), z.record(z.string(), z.any())).optional(), - }) - .meta({ - ref: "Model", - }) - export type Model = z.infer - - export const Info = z - .object({ - id: ProviderID.zod, - name: z.string(), - source: z.enum(["env", "config", "custom", "api"]), - env: z.string().array(), - key: z.string().optional(), - options: z.record(z.string(), z.any()), - models: z.record(z.string(), Model), - }) - .meta({ - ref: "Provider", - }) - export type Info = z.infer - - const DEFAULT_CONTEXT_LIMIT = 200_000 - const DEFAULT_OUTPUT_LIMIT = 32_000 - - const PROVIDER_SEEDS: Record = { - anthropic: { - name: "Anthropic", - env: ["ANTHROPIC_API_KEY"], - npm: "@ai-sdk/anthropic", - models: [ - { - id: "claude-sonnet-4-20250514", - name: "Claude Sonnet 4", - family: "claude-sonnet-4", - reasoning: true, - attachment: true, - input: { image: true, pdf: true }, - releaseDate: "2025-05-14", - }, - { - id: "claude-opus-4-1-20250805", - name: "Claude Opus 4.1", - family: "claude-opus-4-1", - reasoning: true, - attachment: true, - input: { image: true, pdf: true }, - releaseDate: "2025-08-05", - }, - { - id: "claude-haiku-4-5-20251001", - name: "Claude Haiku 4.5", - family: "claude-haiku-4-5", - reasoning: false, - attachment: true, - input: { image: true, pdf: true }, - limit: { output: 16_000 }, - releaseDate: "2025-10-01", - }, - ], - }, - openai: { - name: "OpenAI", - env: ["OPENAI_API_KEY"], - npm: "@ai-sdk/openai", - models: [ - { - id: "gpt-5", - name: "GPT-5", - family: "gpt-5", - reasoning: true, - attachment: true, - input: { image: true, pdf: true }, - releaseDate: "2025-01-01", - }, - { - id: "gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-5-mini", - reasoning: true, - attachment: true, - input: { image: true, pdf: true }, - limit: { output: 16_000 }, - releaseDate: "2025-01-01", - }, - { - id: "gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-5-nano", - reasoning: false, - attachment: true, - input: { image: true, pdf: true }, - limit: { output: 8_000 }, - releaseDate: "2025-01-01", - }, - ], - }, - google: { - name: "Google", - env: ["GOOGLE_GENERATIVE_AI_API_KEY"], - npm: "@ai-sdk/google", - models: [ - { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-2.5-pro", - reasoning: true, - attachment: true, - input: { image: true, pdf: true }, - releaseDate: "2025-01-01", - }, - { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-2.5-flash", - reasoning: true, - attachment: true, - input: { image: true, pdf: true }, - limit: { output: 16_000 }, - releaseDate: "2025-01-01", - }, - ], - }, - "google-vertex": { - name: "Google Vertex", - env: [], - npm: "@ai-sdk/google-vertex", - models: [ - { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-2.5-pro", - reasoning: true, - attachment: true, - input: { image: true, pdf: true }, - releaseDate: "2025-01-01", - }, - ], - }, - groq: { - name: "Groq", - env: ["GROQ_API_KEY"], - npm: "@ai-sdk/groq", - models: [ - { - id: "llama-3.3-70b-versatile", - name: "Llama 3.3 70B Versatile", - family: "llama-3.3-70b", - reasoning: true, - releaseDate: "2025-01-01", - }, - ], - }, - mistral: { - name: "Mistral", - env: ["MISTRAL_API_KEY"], - npm: "@ai-sdk/mistral", - models: [ - { - id: "mistral-small-latest", - name: "Mistral Small Latest", - family: "mistral-small", - reasoning: true, - attachment: true, - input: { image: true, pdf: true }, - releaseDate: "2025-01-01", - }, - ], - }, - } - - const providerCache = new Map() - const languageCache = new Map() - const SDK_FACTORIES: Record) => any> = { - "@ai-sdk/anthropic": createAnthropic, - "@ai-sdk/google": createGoogleGenerativeAI, - "@ai-sdk/google-vertex": createVertex, - "@ai-sdk/google-vertex/anthropic": createVertexAnthropic, - "@ai-sdk/groq": createGroq, - "@ai-sdk/mistral": createMistral, - "@ai-sdk/openai": createOpenAI, - } - const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"] - - function firstEnv(names: string[]) { - for (const name of names) { - const value = process.env[name] - if (typeof value === "string" && value.length > 0) { - return value - } - } - return undefined - } - - function cloneRecord>(value: T | undefined): T { - return { ...(value ?? ({} as T)) } - } - - function buildModel( - providerID: ProviderID, - seed: ProviderSeed, - input: { - id: string - name?: string - family?: string - reasoning?: boolean - attachment?: boolean - toolCall?: boolean - status?: "alpha" | "beta" | "deprecated" | "active" - input?: Partial<{ text: boolean; audio: boolean; image: boolean; video: boolean; pdf: boolean }> - output?: Partial<{ text: boolean; audio: boolean; image: boolean; video: boolean; pdf: boolean }> - limit?: Partial<{ context: number; input: number; output: number }> - cost?: Partial<{ - input: number - output: number - cache: Partial<{ read: number; write: number }> - }> - headers?: Record - options?: Record - api?: { - id?: string - url?: string - npm?: string - } - releaseDate?: string - variants?: Record> - }, - ): Model { - return { - id: ModelID.make(input.id), - providerID, - api: { - id: input.api?.id ?? input.id, - url: input.api?.url ?? "", - npm: input.api?.npm ?? seed.npm, - }, - name: input.name ?? input.id, - family: input.family, - capabilities: { - temperature: true, - reasoning: input.reasoning ?? false, - attachment: input.attachment ?? false, - toolcall: input.toolCall ?? true, - input: { - text: input.input?.text ?? true, - audio: input.input?.audio ?? false, - image: input.input?.image ?? false, - video: input.input?.video ?? false, - pdf: input.input?.pdf ?? false, - }, - output: { - text: input.output?.text ?? true, - audio: input.output?.audio ?? false, - image: input.output?.image ?? false, - video: input.output?.video ?? false, - pdf: input.output?.pdf ?? false, - }, - interleaved: false, - }, - cost: { - input: input.cost?.input ?? 0, - output: input.cost?.output ?? 0, - cache: { - read: input.cost?.cache?.read ?? 0, - write: input.cost?.cache?.write ?? 0, - }, - }, - limit: { - context: input.limit?.context ?? DEFAULT_CONTEXT_LIMIT, - ...(input.limit?.input !== undefined ? { input: input.limit.input } : {}), - output: input.limit?.output ?? DEFAULT_OUTPUT_LIMIT, - }, - status: input.status ?? "active", - options: cloneRecord(input.options), - headers: cloneRecord(input.headers), - release_date: input.releaseDate ?? "2025-01-01", - variants: input.variants ?? {}, - } - } - - function buildSeedModels(providerID: ProviderID, seed: ProviderSeed) { - return Object.fromEntries( - seed.models.map((model) => [model.id, buildModel(providerID, seed, model)]), - ) as Record - } - - function applyConfiguredModels( - providerID: ProviderID, - seed: ProviderSeed, - provider: Info, - configuredProvider: ProviderConfig | undefined, - ) { - for (const [modelID, raw] of Object.entries(configuredProvider?.models ?? {})) { - const existing = provider.models[modelID] - provider.models[modelID] = buildModel(providerID, seed, { - id: modelID, - name: raw?.name ?? existing?.name ?? modelID, - family: raw?.family ?? existing?.family, - reasoning: raw?.reasoning ?? existing?.capabilities.reasoning ?? false, - attachment: raw?.attachment ?? existing?.capabilities.attachment ?? false, - toolCall: raw?.tool_call ?? existing?.capabilities.toolcall ?? true, - status: raw?.status ?? existing?.status ?? "active", - input: { - text: raw?.modalities?.input?.includes("text") ?? existing?.capabilities.input.text ?? true, - audio: raw?.modalities?.input?.includes("audio") ?? existing?.capabilities.input.audio ?? false, - image: raw?.modalities?.input?.includes("image") ?? existing?.capabilities.input.image ?? false, - video: raw?.modalities?.input?.includes("video") ?? existing?.capabilities.input.video ?? false, - pdf: raw?.modalities?.input?.includes("pdf") ?? existing?.capabilities.input.pdf ?? false, - }, - output: { - text: raw?.modalities?.output?.includes("text") ?? existing?.capabilities.output.text ?? true, - audio: raw?.modalities?.output?.includes("audio") ?? existing?.capabilities.output.audio ?? false, - image: raw?.modalities?.output?.includes("image") ?? existing?.capabilities.output.image ?? false, - video: raw?.modalities?.output?.includes("video") ?? existing?.capabilities.output.video ?? false, - pdf: raw?.modalities?.output?.includes("pdf") ?? existing?.capabilities.output.pdf ?? false, - }, - limit: { - context: raw?.limit?.context ?? existing?.limit.context, - input: raw?.limit?.input ?? existing?.limit.input, - output: raw?.limit?.output ?? existing?.limit.output, - }, - cost: { - input: raw?.cost?.input ?? existing?.cost.input, - output: raw?.cost?.output ?? existing?.cost.output, - cache: { - read: raw?.cost?.cache_read ?? existing?.cost.cache.read, - write: raw?.cost?.cache_write ?? existing?.cost.cache.write, - }, - }, - headers: { - ...existing?.headers, - ...cloneRecord(raw?.headers), - }, - options: { - ...existing?.options, - ...cloneRecord(raw?.options), - }, - api: { - id: raw?.id ?? existing?.api.id ?? modelID, - url: raw?.provider?.api ?? configuredProvider?.api ?? existing?.api.url ?? "", - npm: raw?.provider?.npm ?? configuredProvider?.npm ?? existing?.api.npm ?? seed.npm, - }, - releaseDate: raw?.release_date ?? existing?.release_date, - variants: raw?.variants ?? existing?.variants, - }) - } - } - - async function loadProviders() { - const cfg = await Config.get() - const configured = (cfg.provider ?? {}) as Record - const providers: Record = {} - - for (const [providerName, seed] of Object.entries(PROVIDER_SEEDS)) { - const providerID = ProviderID.make(providerName) - const configuredProvider = configured[providerName] - const configuredModel = typeof cfg.model === "string" && cfg.model.startsWith(providerName + "/") - const key = firstEnv(configuredProvider?.env ?? seed.env) ?? configuredProvider?.options?.apiKey - - if (!configuredProvider && !configuredModel && !key) { - continue - } - - const info: Info = { - id: providerID, - name: configuredProvider?.name ?? seed.name, - source: configuredProvider ? "config" : key ? "env" : "custom", - env: configuredProvider?.env ?? seed.env, - ...(typeof key === "string" && key.length > 0 ? { key } : {}), - options: { - ...cloneRecord(configuredProvider?.options), - }, - models: buildSeedModels(providerID, seed), - } - - applyConfiguredModels(providerID, seed, info, configuredProvider) - providers[providerID] = info - } - - if (Object.keys(providers).length === 0) { - const fallback = PROVIDER_SEEDS.anthropic - providers[ProviderID.anthropic] = { - id: ProviderID.anthropic, - name: fallback.name, - source: "custom", - env: fallback.env, - options: {}, - models: buildSeedModels(ProviderID.anthropic, fallback), - } - } - - return providers as Record - } - - function modelKey(providerID: ProviderID, modelID: ModelID) { - return String(providerID) + "/" + String(modelID) - } - - export async function list() { - return loadProviders() - } - - export async function getProvider(providerID: ProviderID) { - const providers = await loadProviders() - const provider = providers[providerID] - if (!provider) { - throw new InitError({ providerID }) - } - return provider - } - - export async function getModel(providerID: ProviderID, modelID: ModelID) { - const provider = await getProvider(providerID) - const model = provider.models[modelID] - if (model) return model - - const suggestions = Object.keys(provider.models).filter( - (candidate) => candidate.includes(String(modelID)) || String(modelID).includes(candidate), - ) - throw new ModelNotFoundError({ - providerID, - modelID, - ...(suggestions.length ? { suggestions: suggestions.slice(0, 3) } : {}), - }) - } - - async function getSdk(provider: Info, model: Model) { - const cacheKey = JSON.stringify({ - providerID: provider.id, - apiId: model.api.id, - baseURL: provider.options?.baseURL, - headers: provider.options?.headers, - key: provider.key, - }) - if (providerCache.has(cacheKey)) { - return providerCache.get(cacheKey) - } - - const options = { - ...cloneRecord(provider.options), - ...(provider.key ? { apiKey: provider.key } : {}), - headers: { - ...cloneRecord(provider.options?.headers), - ...cloneRecord(model.headers), - }, - } - - const factory = SDK_FACTORIES[model.api.npm] - if (!factory) { - throw new InitError( - { providerID: provider.id }, - { - cause: new Error( - "Unsupported provider in ACP VM build: " + provider.id + " (" + model.api.npm + ")", - ), - }, - ) - } - - const sdk = factory(options) - - providerCache.set(cacheKey, sdk) - return sdk - } - - export async function getLanguage(model: Model) { - const key = modelKey(model.providerID, model.id) - const cached = languageCache.get(key) - if (cached) return cached - - try { - const provider = await getProvider(model.providerID) - const sdk = await getSdk(provider, model) - const language = - model.providerID === ProviderID.openai && typeof sdk.responses === "function" - ? sdk.responses(model.api.id) - : sdk.languageModel(model.api.id) - languageCache.set(key, language) - return language - } catch (cause) { - throw new InitError({ providerID: model.providerID }, { cause }) - } - } - - export async function closest(providerID: ProviderID, query: string[]) { - const provider = await getProvider(providerID).catch(() => undefined) - if (!provider) return undefined - for (const item of query) { - const match = Object.keys(provider.models).find((modelID) => modelID.includes(item)) - if (match) return { providerID, modelID: ModelID.make(match) } - } - return undefined - } - - export async function getSmallModel(providerID: ProviderID) { - const provider = await getProvider(providerID).catch(() => undefined) - if (!provider) return undefined - - const preferred = - providerID === ProviderID.anthropic - ? ["haiku", "mini", "nano"] - : ["mini", "nano", "haiku"] - - for (const token of preferred) { - const match = Object.values(provider.models).find((model) => model.id.includes(token)) - if (match) return match - } - - return undefined - } - - export async function defaultModel() { - const cfg = await Config.get() - if (cfg.model) { - const parsed = parseModel(cfg.model) - const model = await getModel(parsed.providerID, parsed.modelID).catch(() => undefined) - if (model) { - return { - providerID: parsed.providerID, - modelID: parsed.modelID, - } - } - } - - const providers = await loadProviders() - for (const provider of Object.values(providers)) { - const [model] = sort(Object.values(provider.models)) - if (model) { - return { - providerID: provider.id, - modelID: model.id, - } - } - } - - return { - providerID: ProviderID.anthropic, - modelID: ModelID.make("claude-sonnet-4-20250514"), - } - } - - export function sort(models: T[]) { - return [...models].sort((a, b) => { - const aPriority = priority.findIndex((item) => a.id.includes(item)) - const bPriority = priority.findIndex((item) => b.id.includes(item)) - const aRank = aPriority === -1 ? Number.MAX_SAFE_INTEGER : aPriority - const bRank = bPriority === -1 ? Number.MAX_SAFE_INTEGER : bPriority - if (aRank !== bRank) return aRank - bRank - - const aLatest = a.id.includes("latest") ? 1 : 0 - const bLatest = b.id.includes("latest") ? 1 : 0 - if (aLatest !== bLatest) return aLatest - bLatest - - return a.id.localeCompare(b.id) - }) - } - - export function parseModel(model: string) { - const [providerID, ...rest] = model.split("/") - return { - providerID: ProviderID.make(providerID), - modelID: ModelID.make(rest.join("/")), - } - } - - export const ModelNotFoundError = NamedError.create( - "ProviderModelNotFoundError", - z.object({ - providerID: ProviderID.zod, - modelID: ModelID.zod, - suggestions: z.array(z.string()).optional(), - }), - ) - - export const InitError = NamedError.create( - "ProviderInitError", - z.object({ - providerID: ProviderID.zod, - }), - ) -} -`, - ); - - await writeFile( - resolve(sourceRoot, "packages/opencode/src/plugin/index.ts"), - `import { Effect, Layer, ServiceMap } from "effect" - -export namespace Plugin { - export interface Interface { - readonly trigger: ( - name: Name, - input: Input, - output: Output, - ) => Effect.Effect - readonly list: () => Effect.Effect - readonly init: () => Effect.Effect - } - - export class Service extends ServiceMap.Service()("@opencode/Plugin") {} - - const noop = Service.of({ - trigger: (_name: Name, _input: Input, output: Output) => - Effect.succeed(output), - list: () => Effect.succeed([]), - init: () => Effect.void, - }) - - export const layer = Layer.succeed(Service, noop) - export const defaultLayer = layer - - export async function trigger( - _name: Name, - _input: Input, - output: Output, - ): Promise { - return output - } - - export async function list(): Promise { - return [] - } - - export async function init(): Promise {} -} -`, - ); - - await writeFile( - resolve(sourceRoot, "packages/opencode/src/project/bootstrap.ts"), - `import { Instance } from "./instance" -import { Log } from "@/util/log" - -export async function InstanceBootstrap() { - Log.Default.info("bootstrapping", { directory: Instance.directory }) - Log.Default.info("bootstrap step", { step: "minimal:init" }) -} -`, - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/project/instance.ts", - (contents) => - contents - .replace( - `function boot(input: { directory: string; init?: () => Promise; project?: Project.Info; worktree?: string }) { - return iife(async () => { -`, - `function boot(input: { directory: string; init?: () => Promise; project?: Project.Info; worktree?: string }) { - return iife(async () => { - Log.Default.info("instance boot:start", { directory: input.directory }) -`, - ) - .replace( - ` await context.provide(ctx, async () => { - await input.init?.() - }) - return ctx -`, - ` Log.Default.info("instance boot:ctx", { - directory: ctx.directory, - worktree: ctx.worktree, - projectID: ctx.project.id, - }) - await context.provide(ctx, async () => { - Log.Default.info("instance boot:init:start", { directory: ctx.directory }) - await input.init?.() - Log.Default.info("instance boot:init:done", { directory: ctx.directory }) - }) - Log.Default.info("instance boot:done", { directory: ctx.directory }) - return ctx -`, - ) - .replace( - ` const ctx = await existing - return context.provide(ctx, async () => { - return input.fn() - }) -`, - ` Log.Default.info("instance provide:await:start", { directory }) - const ctx = await existing - Log.Default.info("instance provide:await:done", { directory }) - return context.provide(ctx, async () => { - Log.Default.info("instance provide:fn:start", { directory }) - const result = await input.fn() - Log.Default.info("instance provide:fn:done", { directory }) - return result - }) -`, - ), - ); - - await rewriteSourceFile( - sourceRoot, - "packages/opencode/src/project/project.ts", - (contents) => - contents.includes('log.info("phase2 select project"') - ? contents - : contents - .replace( - ` // Phase 2: upsert - const row = yield* db((d) => d.select().from(ProjectTable).where(eq(ProjectTable.id, data.id)).get()) -`, - ` // Phase 2: upsert - log.info("phase2 select project", { projectID: data.id }) - const row = yield* db((d) => d.select().from(ProjectTable).where(eq(ProjectTable.id, data.id)).get()) - log.info("phase2 select project done", { projectID: data.id, found: !!row }) -`, - ) - .replace( - ` yield* db((d) => - d - .insert(ProjectTable) - .values({ -`, - ` log.info("phase2 upsert project", { - projectID: result.id, - sandboxes: result.sandboxes.length, - }) - yield* db((d) => - d - .insert(ProjectTable) - .values({ -`, - ) - .replace( - ` if (data.id !== ProjectID.global) { -`, - ` log.info("phase2 upsert project done", { projectID: result.id }) - if (data.id !== ProjectID.global) { -`, - ), - ); - - await writeFile( - resolve(sourceRoot, "packages/opencode/src/share/share-next.ts"), - `export namespace ShareNext { - const EMPTY_API = { - create: "", - sync: () => "", - remove: () => "", - data: () => "", - } - - export async function url() { - return "" - } - - export async function request() { - return { - headers: {}, - api: EMPTY_API, - baseUrl: "", - } - } - - export async function init() {} - - export async function create(_sessionID: string) { - return { id: "", url: "", secret: "" } - } - - export async function remove(_sessionID: string) {} -} -`, - ); - - await writeFile( - resolve(sourceRoot, "packages/opencode/src/cli/cmd/tui/win32.ts"), - `export function win32DisableProcessedInput() {} -export function win32FlushInputBuffer() {} -export function win32InstallCtrlCGuard() { - return -} -`, - ); -} - -function patchBuiltBundle(bundlePath) { - const original = readFileSync(bundlePath, "utf-8"); - if ( - original.includes( - "bash tool command scan failed, falling back to raw permission request", - ) - ) { - return; - } - - const updated = original.replace( - ` async execute(params, ctx) { - const cwd = params.workdir ? await resolvePath(params.workdir, Instance.directory, shell2) : Instance.directory; - if (params.timeout !== undefined && params.timeout < 0) { - throw new Error(\`Invalid timeout value: \${params.timeout}. Timeout must be a positive number.\`); - } - const timeout4 = params.timeout ?? DEFAULT_TIMEOUT; - const ps2 = PS.has(name21); - const root = await parse10(params.command, ps2); - const scan5 = await collect6(root, cwd, ps2, shell2); - if (!Instance.containsPath(cwd)) - scan5.dirs.add(cwd); - await ask(ctx, scan5); - return run7({ -`, - ` async execute(params, ctx) { - const cwd = params.workdir ? await resolvePath(params.workdir, Instance.directory, shell2) : Instance.directory; - if (params.timeout !== undefined && params.timeout < 0) { - throw new Error(\`Invalid timeout value: \${params.timeout}. Timeout must be a positive number.\`); - } - const timeout4 = params.timeout ?? DEFAULT_TIMEOUT; - const ps2 = PS.has(name21); - let scan5; - try { - const root = await parse10(params.command, ps2); - scan5 = await collect6(root, cwd, ps2, shell2); - } catch (error48) { - log7.warn("bash tool command scan failed, falling back to raw permission request", { - command: params.command, - error: error48 instanceof Error ? error48.message : String(error48) - }); - scan5 = { - dirs: new Set, - patterns: new Set([params.command]), - always: new Set([params.command]) - }; - } - if (!Instance.containsPath(cwd)) - scan5.dirs.add(cwd); - await ask(ctx, scan5); - return run7({ -`, - ); - - if (updated === original) { - throw new Error( - "Failed to patch built OpenCode ACP bundle for bash scan fallback", - ); - } - - writeFileSync(bundlePath, updated); -} - -async function assertPreparedSource(sourceRoot) { - const instanceSource = await readFile( - resolve(sourceRoot, "packages/opencode/src/server/instance.ts"), - "utf8", - ); - if ( - instanceSource.includes('import { PtyRoutes } from "./routes/pty"') || - instanceSource.includes('import { TuiRoutes } from "./routes/tui"') || - instanceSource.includes('.route("/pty", PtyRoutes())') || - instanceSource.includes('.route("/tui", TuiRoutes())') - ) { - throw new Error("Prepared OpenCode source still exposes PTY/TUI routes in the ACP build"); - } - - const shellSource = await readFile( - resolve(sourceRoot, "packages/opencode/src/shell/shell.ts"), - "utf8", - ); - if (shellSource.includes("Bun.which(")) { - throw new Error("Prepared OpenCode source still references Bun.which in shell.ts"); - } - - const win32Source = await readFile( - resolve(sourceRoot, "packages/opencode/src/cli/cmd/tui/win32.ts"), - "utf8", - ); - if (win32Source.includes("bun:ffi")) { - throw new Error("Prepared OpenCode source still references bun:ffi in the Win32 TUI shim"); - } - - const dbNodeSource = await readFile( - resolve(sourceRoot, "packages/opencode/src/storage/db.node.ts"), - "utf8", - ); - if ( - !dbNodeSource.includes('from "drizzle-orm/node-sqlite"') || - !dbNodeSource.includes('from "node:sqlite"') - ) { - throw new Error("Prepared OpenCode source does not use the native node:sqlite database path"); - } -} - -async function assertBundleClean(bundlePath) { - const bundle = await readFile(bundlePath, "utf8"); - for (const pattern of [ - "bun:ffi", - "bun-pty", - "hono/bun", - '.route("/pty", PtyRoutes())', - '.route("/tui", TuiRoutes())', - "Bun.which(", - "bun:sqlite", - ]) { - if (bundle.includes(pattern)) { - throw new Error( - `OpenCode ACP bundle still contains forbidden runtime dependency: ${pattern}`, - ); - } - } -} - -async function main() { - if (!existsSync(bunBin)) { - throw new Error( - `bun is not installed for @agentos-software/opencode (expected ${bunBin}). Run pnpm install first.`, - ); - } - - mkdirSync(distDir, { recursive: true }); - mkdirSync(cacheDir, { recursive: true }); - rmSync(bundleDir, { recursive: true, force: true }); - - const patch = readFileSync(patchPath, "utf-8"); - const buildScript = readFileSync(fileURLToPath(import.meta.url), "utf-8"); - const patchHash = createHash("sha256") - .update(`${SOURCE_VERSION}\n${patch}\n${buildScript}`) - .digest("hex") - .slice(0, 16); - const sourceRoot = join(cacheDir, `source-v${SOURCE_VERSION}-${patchHash}`); - const preparedMarker = join(sourceRoot, ".agentos-prepared.json"); - const tarballPath = join(cacheDir, `opencode-v${SOURCE_VERSION}.tar.gz`); - - if (!existsSync(preparedMarker)) { - rmSync(sourceRoot, { recursive: true, force: true }); - mkdirSync(sourceRoot, { recursive: true }); - - if (!existsSync(tarballPath)) { - process.stdout.write(`Downloading OpenCode v${SOURCE_VERSION} source...\n`); - await downloadFile(SOURCE_TARBALL_URL, tarballPath); - } - - run("tar", ["-xzf", tarballPath, "--strip-components=1", "-C", sourceRoot]); - pinGhosttyWebRef(sourceRoot); - run(bunBin, ["install", "--frozen-lockfile"], { cwd: sourceRoot }); - await ensureNodeAcpPatch(sourceRoot, tarballPath); - await applyNodeAcpRuntimeTweaks(sourceRoot); - await assertPreparedSource(sourceRoot); - - writeFileSync( - preparedMarker, - JSON.stringify( - { - sourceVersion: SOURCE_VERSION, - sourceRepository: SOURCE_REPOSITORY, - patchHash, - }, - null, - 2, - ) + "\n", - ); - } - - await ensureNodeAcpPatch(sourceRoot, tarballPath); - await applyNodeAcpRuntimeTweaks(sourceRoot); - await assertPreparedSource(sourceRoot); - - const migrations = await readMigrations(sourceRoot); - const buildHelperDir = await mkdtemp(join(tmpdir(), "agentos-opencode-build-")); - const buildHelperPath = join(buildHelperDir, "build-opencode-acp.mjs"); - const bunVersion = - spawnSync(bunBin, ["--version"], { encoding: "utf-8" }).stdout?.trim() ?? - "unknown"; - - try { - await writeFile( - buildHelperPath, - ` -import { mkdir } from "node:fs/promises"; -import { dirname, join } from "node:path"; - -const outdir = process.env.OUTDIR; -if (!outdir) { - throw new Error("OUTDIR is required"); -} - -const result = await Bun.build({ - target: "node", - format: "esm", - outdir, - entrypoints: ["./packages/opencode/src/cli/cmd/acp.ts"], - define: { - OPENCODE_MIGRATIONS: ${JSON.stringify(JSON.stringify(migrations))}, - OPENCODE_LIBC: ${JSON.stringify(JSON.stringify("glibc"))}, - }, -}); -if (!result.success) { - for (const log of result.logs) { - console.error(log); - } - throw new Error("OpenCode ACP bundle build failed"); -} -for (const output of result.outputs) { - const filePath = join(outdir, output.path); - await mkdir(dirname(filePath), { recursive: true }); - await Bun.write(filePath, output); -} -`, - ); - - run(bunBin, [buildHelperPath], { - cwd: sourceRoot, - env: { - ...process.env, - OUTDIR: bundleDir, - }, - }); - } finally { - rmSync(buildHelperDir, { recursive: true, force: true }); - } - - await assertBundleClean(join(bundleDir, "acp.js")); - patchBuiltBundle(join(bundleDir, "acp.js")); - - writeFileSync( - manifestPath, - JSON.stringify( - { - source: { - repository: SOURCE_REPOSITORY, - version: SOURCE_VERSION, - tarballUrl: SOURCE_TARBALL_URL, - }, - build: { - bunVersion, - patchHash, - externalDependencies: [], - entry: "./opencode-acp/acp.js", - }, - }, - null, - 2, - ) + "\n", - ); -} - -void main().catch((error) => { - process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`); - process.exitCode = 1; -}); diff --git a/registry/agent/opencode/src/adapter.ts b/registry/agent/opencode/src/adapter.ts deleted file mode 100644 index 866acdded..000000000 --- a/registry/agent/opencode/src/adapter.ts +++ /dev/null @@ -1,27 +0,0 @@ -process.env.OPENCODE_DISABLE_CONFIG_DEP_INSTALL ??= "1"; -process.env.OPENCODE_DISABLE_EMBEDDED_WEB_UI ??= "1"; - -// @ts-expect-error Generated at build time by scripts/build-opencode-acp.mjs. -const { AcpCommand } = (await import("./opencode-acp/acp.js")) as { - AcpCommand: { - handler(args: { - port: number; - hostname: string; - mdns: boolean; - "mdns-domain": string; - cors: string[]; - cwd: string; - }): Promise; - }; -}; - -await AcpCommand.handler({ - port: 0, - hostname: "127.0.0.1", - mdns: false, - "mdns-domain": "opencode.local", - cors: [], - cwd: process.cwd(), -}); - -export {}; diff --git a/registry/agent/opencode/src/index.ts b/registry/agent/opencode/src/index.ts deleted file mode 100644 index bf7d721a8..000000000 --- a/registry/agent/opencode/src/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { defineSoftware } from "@rivet-dev/agentos-core"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const packageDir = resolve(__dirname, ".."); - -const opencode = defineSoftware({ - name: "opencode", - type: "agent" as const, - packageDir, - requires: ["@agentos-software/opencode"], - agent: { - id: "opencode", - // OpenCode still speaks ACP natively, but Agent OS runs a source-built - // Node ACP bundle entirely inside the VM rather than a host binary wrapper. - acpAdapter: "@agentos-software/opencode", - agentPackage: "@agentos-software/opencode", - staticEnv: { - OPENCODE_DISABLE_CONFIG_DEP_INSTALL: "1", - OPENCODE_DISABLE_EMBEDDED_WEB_UI: "1", - }, - }, -}); - -export default opencode; diff --git a/registry/agent/opencode/src/opencode-acp.mjs.d.ts b/registry/agent/opencode/src/opencode-acp.mjs.d.ts deleted file mode 100644 index 06aac778f..000000000 --- a/registry/agent/opencode/src/opencode-acp.mjs.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -declare module "./opencode-acp.mjs" { - export const AcpCommand: { - handler(args: { - port: number; - hostname: string; - mdns: boolean; - "mdns-domain": string; - cors: string[]; - cwd: string; - }): Promise; - }; -} - -export {}; diff --git a/registry/agent/opencode/tsconfig.json b/registry/agent/opencode/tsconfig.json deleted file mode 100644 index bff731325..000000000 --- a/registry/agent/opencode/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "declaration": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/registry/agent/opencode/upstream/opencode-v1.3.13.patch b/registry/agent/opencode/upstream/opencode-v1.3.13.patch deleted file mode 100644 index 43e4fae95..000000000 --- a/registry/agent/opencode/upstream/opencode-v1.3.13.patch +++ /dev/null @@ -1,251 +0,0 @@ -diff --git a/packages/opencode/src/cli/cmd/acp.ts b/packages/opencode/src/cli/cmd/acp.ts -index 99a9a81ab..2fb9038b0 100644 ---- a/packages/opencode/src/cli/cmd/acp.ts -+++ b/packages/opencode/src/cli/cmd/acp.ts -@@ -23,7 +23,7 @@ export const AcpCommand = cmd({ - process.env.OPENCODE_CLIENT = "acp" - await bootstrap(process.cwd(), async () => { - const opts = await resolveNetworkOptions(args) -- const server = Server.listen(opts) -+ const server = await Server.listen(opts) - - const sdk = createOpencodeClient({ - baseUrl: `http://${server.hostname}:${server.port}`, -diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts -index f86d8d32a..fdb4dc107 100644 ---- a/packages/opencode/src/config/config.ts -+++ b/packages/opencode/src/config/config.ts -@@ -90,6 +90,11 @@ export namespace Config { - } - - export async function installDependencies(dir: string, input?: InstallInput) { -+ if (process.env.OPENCODE_DISABLE_CONFIG_DEP_INSTALL === "1") { -+ log.info("skipping dependency install", { dir, reason: "disabled_by_env" }) -+ return -+ } -+ - if (!(await needsInstall(dir))) return - - await using _ = await Flock.acquire(`config-install:${Filesystem.resolve(dir)}`, { -diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts -index b05dd8625..f2afc5979 100644 ---- a/packages/opencode/src/plugin/index.ts -+++ b/packages/opencode/src/plugin/index.ts -@@ -50,7 +50,10 @@ export namespace Plugin { - export class Service extends ServiceMap.Service()("@opencode/Plugin") {} - - // Built-in plugins that are directly imported (not installed from npm) -- const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin, GitlabAuthPlugin, PoeAuthPlugin] -+ const INTERNAL_PLUGINS: PluginInstance[] = -+ process.env.OPENCODE_ENABLE_INTERNAL_AUTH_PLUGINS === "1" -+ ? [CodexAuthPlugin, CopilotAuthPlugin, GitlabAuthPlugin, PoeAuthPlugin] -+ : [] - - function isServerPlugin(value: unknown): value is PluginInstance { - return typeof value === "function" -@@ -128,7 +131,9 @@ export namespace Plugin { - get serverUrl(): URL { - return Server.url ?? new URL("http://localhost:4096") - }, -- $: Bun.$, -+ $: ((..._args: unknown[]) => { -+ throw new Error("Bun shell is unavailable in the Node ACP build") -+ }) as PluginInput["$"], - } - - for (const plugin of INTERNAL_PLUGINS) { -diff --git a/packages/opencode/src/server/instance.ts b/packages/opencode/src/server/instance.ts -index 4bb6efaf9..fc634f5c2 100644 ---- a/packages/opencode/src/server/instance.ts -+++ b/packages/opencode/src/server/instance.ts -@@ -18,7 +18,6 @@ import { QuestionRoutes } from "./routes/question" - import { PermissionRoutes } from "./routes/permission" - import { ProjectRoutes } from "./routes/project" - import { SessionRoutes } from "./routes/session" --import { PtyRoutes } from "./routes/pty" - import { McpRoutes } from "./routes/mcp" - import { FileRoutes } from "./routes/file" - import { ConfigRoutes } from "./routes/config" -@@ -44,7 +43,6 @@ export const InstanceRoutes = (app?: Hono) => - (app ?? new Hono()) - .onError(errorHandler(log)) - .route("/project", ProjectRoutes()) -- .route("/pty", PtyRoutes()) - .route("/config", ConfigRoutes()) - .route("/experimental", ExperimentalRoutes()) - .route("/session", SessionRoutes()) -diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts -index ec245ed59..8df8fe679 100644 ---- a/packages/opencode/src/server/server.ts -+++ b/packages/opencode/src/server/server.ts -@@ -9,7 +9,6 @@ import { Auth } from "../auth" - import { Flag } from "../flag/flag" - import { ProviderID } from "../provider/schema" - import { WorkspaceRouterMiddleware } from "./router" --import { websocket } from "hono/bun" - import { errors } from "./error" - import { GlobalRoutes } from "./routes/global" - import { MDNS } from "./mdns" -@@ -17,6 +16,9 @@ import { lazy } from "@/util/lazy" - import { errorHandler } from "./middleware" - import { InstanceRoutes } from "./instance" - import { initProjectors } from "./projectors" -+import { createServer, type IncomingMessage, type ServerResponse } from "node:http" -+import type { AddressInfo } from "node:net" -+import { Readable } from "node:stream" - - // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 - globalThis.AI_SDK_LOG_WARNINGS = false -@@ -264,7 +266,106 @@ export namespace Server { - /** @deprecated do not use this dumb shit */ - export let url: URL - -- export function listen(opts: { -+ type NodeServeHandle = { -+ hostname: string -+ port: number -+ stop: (closeActiveConnections?: boolean) => Promise -+ } -+ -+ function toRequestUrl(req: IncomingMessage, fallbackHost: string, fallbackPort: number) { -+ const host = req.headers.host ?? `${fallbackHost}:${fallbackPort}` -+ return new URL(req.url ?? "/", `http://${host}`) -+ } -+ -+ function toHeaders(req: IncomingMessage) { -+ const headers = new Headers() -+ for (const [key, value] of Object.entries(req.headers)) { -+ if (Array.isArray(value)) { -+ for (const item of value) headers.append(key, item) -+ continue -+ } -+ if (typeof value === "string") headers.set(key, value) -+ } -+ return headers -+ } -+ -+ async function writeResponse(res: ServerResponse, response: Response) { -+ res.statusCode = response.status -+ res.statusMessage = response.statusText -+ response.headers.forEach((value, key) => { -+ res.setHeader(key, value) -+ }) -+ -+ if (!response.body) { -+ res.end() -+ return -+ } -+ -+ await new Promise((resolve, reject) => { -+ const body = Readable.fromWeb(response.body as globalThis.ReadableStream) -+ body.on("error", reject) -+ res.on("error", reject) -+ res.on("close", resolve) -+ body.pipe(res) -+ }) -+ } -+ -+ async function serveNode(opts: { -+ hostname: string -+ port: number -+ fetch: (request: Request) => Response | Promise -+ }): Promise { -+ const server = createServer(async (req, res) => { -+ try { -+ const request = new Request(toRequestUrl(req, opts.hostname, opts.port), { -+ method: req.method, -+ headers: toHeaders(req), -+ body: -+ req.method && req.method !== "GET" && req.method !== "HEAD" -+ ? (Readable.toWeb(req) as globalThis.ReadableStream) -+ : undefined, -+ duplex: "half", -+ }) -+ const response = await opts.fetch(request) -+ await writeResponse(res, response) -+ } catch (error) { -+ log.error("node server request failed", { error }) -+ if (!res.headersSent) { -+ res.statusCode = 500 -+ res.setHeader("content-type", "text/plain; charset=utf-8") -+ } -+ res.end("Internal Server Error") -+ } -+ }) -+ -+ await new Promise((resolve, reject) => { -+ server.once("error", reject) -+ server.listen(opts.port, opts.hostname, () => { -+ server.off("error", reject) -+ resolve() -+ }) -+ }) -+ -+ const address = server.address() -+ if (!address || typeof address === "string") { -+ throw new Error("Failed to resolve server address") -+ } -+ -+ return { -+ hostname: opts.hostname, -+ port: (address as AddressInfo).port, -+ stop() { -+ return new Promise((resolve, reject) => { -+ server.close((error) => { -+ if (error) reject(error) -+ else resolve() -+ }) -+ }) -+ }, -+ } -+ } -+ -+ export async function listen(opts: { - port: number - hostname: string - mdns?: boolean -@@ -273,20 +374,18 @@ export namespace Server { - }) { - url = new URL(`http://${opts.hostname}:${opts.port}`) - const app = ControlPlaneRoutes({ cors: opts.cors }) -- const args = { -- hostname: opts.hostname, -- idleTimeout: 0, -- fetch: app.fetch, -- websocket: websocket, -- } as const -- const tryServe = (port: number) => { -+ const tryServe = async (port: number) => { - try { -- return Bun.serve({ ...args, port }) -+ return await serveNode({ -+ hostname: opts.hostname, -+ port, -+ fetch: app.fetch, -+ }) - } catch { - return undefined - } - } -- const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port) -+ const server = opts.port === 0 ? ((await tryServe(4096)) ?? (await tryServe(0))) : await tryServe(opts.port) - if (!server) throw new Error(`Failed to start server on port ${opts.port}`) - - const shouldPublishMDNS = -@@ -296,7 +395,7 @@ export namespace Server { - opts.hostname !== "localhost" && - opts.hostname !== "::1" - if (shouldPublishMDNS) { -- MDNS.publish(server.port!, opts.mdnsDomain) -+ MDNS.publish(server.port, opts.mdnsDomain) - } else if (opts.mdns) { - log.warn("mDNS enabled but hostname is loopback; skipping mDNS publish") - } -@@ -307,6 +406,7 @@ export namespace Server { - return originalStop(closeActiveConnections) - } - -+ url = new URL(`http://${opts.hostname}:${server.port}`) - return server - } - } diff --git a/registry/agent/pi-cli/package.json b/registry/agent/pi-cli/package.json deleted file mode 100644 index d0d2f6cb9..000000000 --- a/registry/agent/pi-cli/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "@agentos-software/pi-cli", - "version": "0.2.0-rc.3", - "type": "module", - "license": "Apache-2.0", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc", - "check-types": "tsc --noEmit" - }, - "dependencies": { - "@rivet-dev/agentos-core": "workspace:*", - "@mariozechner/pi-coding-agent": "^0.60.0", - "pi-acp": "^0.0.23" - }, - "devDependencies": { - "@types/node": "^22.10.2", - "typescript": "^5.7.2" - } -} diff --git a/registry/agent/pi-cli/src/index.ts b/registry/agent/pi-cli/src/index.ts deleted file mode 100644 index 524785841..000000000 --- a/registry/agent/pi-cli/src/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { defineSoftware } from "@rivet-dev/agentos-core"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const packageDir = resolve(__dirname, ".."); - -const piCli = defineSoftware({ - name: "pi-cli", - type: "agent" as const, - packageDir, - requires: ["pi-acp", "@mariozechner/pi-coding-agent"], - agent: { - id: "pi-cli", - acpAdapter: "pi-acp", - agentPackage: "@mariozechner/pi-coding-agent", - env: (ctx) => ({ - PI_ACP_PI_COMMAND: ctx.resolveBin( - "@mariozechner/pi-coding-agent", - "pi", - ), - }), - }, -}); - -export default piCli; diff --git a/registry/agent/pi-cli/tsconfig.json b/registry/agent/pi-cli/tsconfig.json deleted file mode 100644 index bff731325..000000000 --- a/registry/agent/pi-cli/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "declaration": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/registry/agent/pi/package.json b/registry/agent/pi/package.json deleted file mode 100644 index ebe49c551..000000000 --- a/registry/agent/pi/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@agentos-software/pi", - "version": "0.2.0-rc.3", - "type": "module", - "license": "Apache-2.0", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "bin": { - "pi-sdk-acp": "./dist/adapter.js" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc && node scripts/build-snapshot-bundle.mjs", - "build:snapshot": "node scripts/build-snapshot-bundle.mjs", - "check-types": "tsc --noEmit" - }, - "dependencies": { - "@rivet-dev/agentos-core": "workspace:*", - "@agentclientprotocol/sdk": "^0.16.1", - "@mariozechner/pi-coding-agent": "0.60.0", - "@mariozechner/pi-ai": "0.60.0" - }, - "devDependencies": { - "@types/node": "^22.10.2", - "typescript": "^5.7.2" - } -} diff --git a/registry/agent/pi/scripts/build-snapshot-bundle.mjs b/registry/agent/pi/scripts/build-snapshot-bundle.mjs deleted file mode 100644 index 3ba5062e9..000000000 --- a/registry/agent/pi/scripts/build-snapshot-bundle.mjs +++ /dev/null @@ -1,226 +0,0 @@ -/** - * Builds the Pi SDK snapshot bundle (Step 2a). - * - * Bundles src/snapshot-entry.ts into a single IIFE at dist/sdk-snapshot.js that - * evaluates the SDK graph and publishes it on globalThis.__PI_SDK_RUNTIME__. node: - * builtins stay external (provided by the V8 runtime's bridge polyfills, already in - * the snapshot heap); heavy provider SDKs reached only via dynamic import() stay - * external so they remain lazy and load post-restore from the VFS. - * - * The build env intentionally clears DISPLAY/WAYLAND_DISPLAY (C0 mitigation): the - * optional @mariozechner/clipboard NAPI addon is behind a DISPLAY guard; with it - * unset the SDK bakes `clipboard = null` so no native pointer enters the snapshot. - */ -import { createHash } from "node:crypto"; -import { createRequire } from "node:module"; -import { readFileSync, writeFileSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { pathToFileURL } from "node:url"; - -const here = dirname(fileURLToPath(import.meta.url)); -const pkgRoot = join(here, ".."); - -// esbuild lives in the workspace pnpm store; resolve it from there. -const require = createRequire(import.meta.url); -const repoRoot = join(pkgRoot, "..", "..", ".."); -let esbuildPath; -try { - esbuildPath = require.resolve("esbuild", { paths: [pkgRoot, repoRoot] }); -} catch { - const { globSync } = await import("node:fs"); - const matches = globSync( - join(repoRoot, "node_modules/.pnpm/esbuild@*/node_modules/esbuild/lib/main.js"), - ); - if (matches.length === 0) throw new Error("esbuild not found in workspace"); - esbuildPath = matches.sort().reverse()[0]; -} -const { build } = await import(pathToFileURL(esbuildPath).href); -// Entry/outfile are overridable (PI_SNAPSHOT_ENTRY / PI_SNAPSHOT_OUTFILE) for -// bisection/testing; default to the committed entry + artifact. -const entryPoint = process.env.PI_SNAPSHOT_ENTRY || join(pkgRoot, "src", "snapshot-entry.ts"); -const outfile = process.env.PI_SNAPSHOT_OUTFILE || join(pkgRoot, "dist", "sdk-snapshot.js"); - -// Provider SDKs the pi-ai layer pulls only via dynamic import(); keep them lazy. -const lazyExternals = [ - "@anthropic-ai/sdk", - "openai", - "@google/genai", - "@mistralai/mistralai", - "@aws-sdk/client-bedrock-runtime", - "proxy-agent", - "@mariozechner/clipboard", -]; - -// The adapter deliberately imports deep `dist/core/*` paths to skip the SDK's TUI -// graph, but the package `exports` map only exposes `.`/`./hooks`. Mirror the -// adapter: resolve the deep specifiers to absolute file paths under the package's -// dist dir, bypassing the exports map (esbuild bundles absolute paths fine). -const { realpathSync, existsSync } = await import("node:fs"); -const piCodingRoot = [ - join(pkgRoot, "node_modules/@mariozechner/pi-coding-agent"), - join(repoRoot, "node_modules/@mariozechner/pi-coding-agent"), -].find((p) => existsSync(p)); -if (!piCodingRoot) throw new Error("pi-coding-agent not found in node_modules"); -const piCodingReal = realpathSync(piCodingRoot); -const piCodingDist = join(piCodingReal, "dist"); -// pnpm sibling layout: pi-coding-agent's own deps (incl. pi-agent-core) live in the -// same `.pnpm//node_modules/@mariozechner` dir. Resolve transitive -// @mariozechner/* deps from there so we bundle exactly what the SDK links against. -const piSiblingScope = dirname(piCodingReal); // .../node_modules/@mariozechner -const deepImportPlugin = { - name: "pi-deep-imports", - setup(b) { - b.onResolve({ filter: /^@mariozechner\/pi-coding-agent\/dist\// }, (a) => { - const rel = a.path.replace("@mariozechner/pi-coding-agent/dist/", ""); - return { path: join(piCodingDist, rel) }; - }); - b.onResolve({ filter: /^@mariozechner\/pi-agent-core/ }, (a) => { - const sub = a.path.replace("@mariozechner/pi-agent-core", "") || "/dist/index.js"; - return { path: join(piSiblingScope, "pi-agent-core", sub) }; - }); - }, -}; - -// Bisection helper (PI_SNAPSHOT_STUB_MODULES=a,b,c): alias the named bare specifiers -// to an empty module so they're elided from the snapshot graph — used to find which -// heavy dep creates an un-serializable native handle at module-init. -const stubModules = (process.env.PI_SNAPSHOT_STUB_MODULES || "") - .split(",") - .map((s) => s.trim()) - .filter(Boolean); -const stubPlugin = { - name: "pi-stub-modules", - setup(b) { - if (stubModules.length === 0) return; - // Substring match against the import specifier (catches bare specifiers AND - // relative paths like ../modes/interactive/theme/theme.js). - b.onResolve({ filter: /.*/ }, (a) => { - if (stubModules.some((m) => a.path.includes(m))) { - return { path: a.path, namespace: "pi-stub" }; - } - return undefined; - }); - b.onLoad({ filter: /.*/, namespace: "pi-stub" }, () => ({ - // Pure CJS so esbuild interop synthesizes ANY named import as a no-op. - contents: - "module.exports = new Proxy(function () {}, { get: function () { return function () {}; } });", - loader: "js", - })); - }, -}; - -// Make the bundle SNAPSHOT-SAFE by eliminating its two top-level I/O hazards at -// build time, so the IIFE evaluates as pure JS (no fs read, no pending promise) -// when run into the V8 snapshot where bridge fns are stubs. See the C0 scan. -const snapshotSafePlugin = { - name: "pi-snapshot-safe", - setup(b) { - // config.js bakes VERSION/APP_NAME from a top-level readFileSync(package.json). - // Inline the real package.json content so no fs read happens at eval. - const piCodingPkgJson = readFileSync( - join(piCodingReal, "package.json"), - "utf8", - ); - b.onLoad({ filter: /\/pi-coding-agent\/dist\/config\.js$/ }, (a) => { - let src = readFileSync(a.path, "utf8"); - const needle = 'readFileSync(getPackageJsonPath(), "utf-8")'; - if (!src.includes(needle)) { - throw new Error( - "snapshot-safe: config.js readFileSync(package.json) shape changed; update the transform", - ); - } - src = src.replace(needle, JSON.stringify(piCodingPkgJson)); - return { contents: src, loader: "js" }; - }); - // env-api-keys.js eagerly fires 3 fire-and-forget dynamicImport().then() - // at top level, leaving pending promises. Convert to synchronous require() - // (grabs the polyfill module reference only — no I/O, no pending promise), - // preserving the feature post-restore. - const ENV_API_KEYS_EAGER_IMPORTS = 3; - b.onLoad({ filter: /\/pi-ai\/dist\/env-api-keys\.js$/ }, (a) => { - let src = readFileSync(a.path, "utf8"); - const re = - /dynamicImport\((NODE_\w+_SPECIFIER)\)\.then\(\(m\)\s*=>\s*\{\s*(_\w+)\s*=\s*m\.(\w+);\s*\}\);/g; - // Count matches BEFORE replacing: a `src === before` check only catches a - // total shape change. If upstream adds a 4th eager import (or drops one), - // the regex would silently transform a subset and leave a pending-promise - // hazard at module-init — re-breaking snapshot-safety with no build error. - // Assert the exact expected count so any drift fails the build loudly. - const matchCount = (src.match(re) || []).length; - if (matchCount !== ENV_API_KEYS_EAGER_IMPORTS) { - throw new Error( - `snapshot-safe: env-api-keys.js eager dynamicImport shape changed ` + - `(found ${matchCount}, expected ${ENV_API_KEYS_EAGER_IMPORTS}); update the transform`, - ); - } - src = src.replace( - re, - (_m, spec, lhs, prop) => `try { ${lhs} = require(${spec}).${prop}; } catch {}`, - ); - return { contents: src, loader: "js" }; - }); - // pi-tui/dist/utils.js creates a module-level `new Intl.Segmenter(...)` — an - // ICU-backed native (External) object that V8's SnapshotCreator cannot - // serialize ("global handle not serialized: "). Lazy-init it behind - // a Proxy so the real segmenter is created on first use (post-restore, where - // Intl is fully available) instead of at module-init. The headless ACP adapter - // never renders the TUI, so the segmenter is only touched after restore anyway. - b.onLoad({ filter: /\/pi-tui\/dist\/utils\.js$/ }, (a) => { - let src = readFileSync(a.path, "utf8"); - const needle = - 'const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });'; - if (!src.includes(needle)) { - throw new Error( - "snapshot-safe: pi-tui utils.js Intl.Segmenter singleton shape changed; update the transform", - ); - } - src = src.replace( - needle, - "const segmenter = (() => { let s; const get = () => (s || (s = new Intl.Segmenter(undefined, { granularity: \"grapheme\" }))); return new Proxy({}, { get: (_, k) => { const v = get()[k]; return typeof v === \"function\" ? v.bind(get()) : v; } }); })();", - ); - return { contents: src, loader: "js" }; - }); - }, -}; - -// esbuild stubs `import.meta` to `{}` in IIFE output, so import.meta.url is -// undefined and the SDK's top-level install-detection (fileURLToPath(import.meta.url)) -// crashes. Pin it to a single deterministic URL: the SDK's projected guest path, so -// any top-level dir/version computation resolves to where the SDK actually lives in -// the VM. Override with PI_SNAPSHOT_BASE_URL (e.g. the real host path for testing). -const baseUrl = - process.env.PI_SNAPSHOT_BASE_URL || - "file:///root/node_modules/@mariozechner/pi-coding-agent/dist/index.js"; - -const result = await build({ - entryPoints: [entryPoint], - outfile, - bundle: true, - format: "iife", - platform: "node", // node: builtins become external require() calls - target: "esnext", - external: lazyExternals, - plugins: [stubPlugin, deepImportPlugin, snapshotSafePlugin], - define: { - "import.meta.url": JSON.stringify(baseUrl), - // C0 mitigation: force the headless path in the clipboard native-addon - // guard so `require("@mariozechner/clipboard")` (a NAPI addon) is never - // triggered at snapshot-eval time, regardless of the sidecar's env. - "process.env.DISPLAY": '""', - "process.env.WAYLAND_DISPLAY": '""', - "process.env.TERMUX_VERSION": '""', - }, - legalComments: "none", - logLevel: "info", - metafile: true, -}); - -const bytes = readFileSync(outfile); -const sha256 = createHash("sha256").update(bytes).digest("hex"); -writeFileSync(`${outfile}.sha256`, `${sha256}\n`); - -const inputs = Object.keys(result.metafile.inputs).length; -console.log( - `\nsdk-snapshot.js: ${(bytes.length / 1024).toFixed(0)} KiB · ${inputs} modules inlined · sha256 ${sha256.slice(0, 12)}…`, -); diff --git a/registry/agent/pi/src/adapter.ts b/registry/agent/pi/src/adapter.ts deleted file mode 100644 index f8aa7b7ee..000000000 --- a/registry/agent/pi/src/adapter.ts +++ /dev/null @@ -1,1506 +0,0 @@ -#!/usr/bin/env node - -/** - * Pi SDK ACP Adapter - * - * ACP-compliant adapter that embeds the Pi coding agent SDK directly - * instead of spawning a subprocess. This avoids loading ~100MB of TUI - * code that the CLI pulls in even in headless mode. - * - * Speaks ACP JSON-RPC over stdin/stdout using @agentclientprotocol/sdk. - * Internally builds a real Pi AgentSession without loading the CLI's - * resource loader path, which pulls jiti into the VM runtime. - */ - -import { - type Agent, - AgentSideConnection, - type RequestError, - ndJsonStream, -} from "@agentclientprotocol/sdk"; -import type { - AuthenticateRequest, - AuthenticateResponse, - CancelNotification, - InitializeRequest, - InitializeResponse, - NewSessionRequest, - NewSessionResponse, - PromptRequest, - PromptResponse, - SetSessionModeRequest, - SetSessionModeResponse, - SessionNotification, -} from "@agentclientprotocol/sdk"; -import type { - AgentSessionEvent, -} from "@mariozechner/pi-coding-agent"; -import { - existsSync, - readFileSync, - readdirSync, - writeFileSync, -} from "node:fs"; -import { createRequire } from "node:module"; -import { isAbsolute, join, resolve as resolvePath } from "node:path"; -import { PassThrough } from "node:stream"; - -// ── Phase tracing (opt-in via PI_TRACE_FILE) ──────────────────────── -// Emits Chrome-trace ("X") events for newSession sub-phases so the otherwise -// opaque ACP `session/new` span can be broken down and compared against the -// bare-node equivalent. The file is written in the guest VFS and read by the -// host (e.g. the benchmark) via vm.readFile. -function startPhaseTrace() { - const spans: { name: string; ts: number; dur: number }[] = []; - const t0 = Date.now(); - return { - async span(name: string, fn: () => Promise | T): Promise { - const start = Date.now(); - try { - return await fn(); - } finally { - spans.push({ name, ts: (start - t0) * 1000, dur: (Date.now() - start) * 1000 }); - } - }, - flush(extra: Record = {}) { - const file = process.env.PI_TRACE_FILE; - if (!file) return; - const events = [ - { name: "newSession", cat: "pi", ph: "X", pid: 1, tid: 1, ts: 0, dur: (Date.now() - t0) * 1000, args: extra }, - ...spans.map((s) => ({ name: s.name, cat: "pi", ph: "X", pid: 1, tid: 1, ts: s.ts, dur: s.dur })), - ]; - try { - writeFileSync(file, JSON.stringify(events)); - } catch { - /* tracing is best-effort */ - } - }, - }; -} - -const PI_SDK_PACKAGE = "@mariozechner/pi-coding-agent"; -const MODULE_ACCESS_NODE_MODULES = "/root/node_modules"; -const require = createRequire(import.meta.url); - -const realStdin = process.stdin; -const bufferedStdin = new PassThrough(); -(bufferedStdin as PassThrough & { isTTY?: boolean; fd?: number }).isTTY = - realStdin.isTTY; -(bufferedStdin as PassThrough & { isTTY?: boolean; fd?: number }).fd = - realStdin.fd; -if (typeof realStdin.setRawMode === "function") { - ( - bufferedStdin as PassThrough & { - setRawMode?: (mode: boolean) => void; - } - ).setRawMode = realStdin.setRawMode.bind(realStdin); -} -Object.defineProperty(process, "stdin", { - configurable: true, - enumerable: true, - value: bufferedStdin, -}); - -type SessionManagerLike = { - inMemory(cwd?: string): unknown; -}; - -type ModelLike = { - id: string; - provider: string; - baseUrl?: string; - reasoning?: boolean; -}; - -type MinimalResourceLoaderLike = { - reload(): Promise; - getExtensions(): { - extensions: unknown[]; - errors: unknown[]; - runtime: { - flagValues: Map; - pendingProviderRegistrations: Array<{ - name: string; - config: unknown; - }>; - }; - }; - getSkills(): { skills: unknown[]; diagnostics: unknown[] }; - getPrompts(): { prompts: unknown[]; diagnostics: unknown[] }; - getThemes(): { themes: unknown[]; diagnostics: unknown[] }; - getAgentsFiles(): { agentsFiles: unknown[] }; - getSystemPrompt(): string; - getAppendSystemPrompt(): string[]; - getPathMetadata(): Map; - extendResources(_paths: string[]): void; -}; - -type PiAgentCoreLike = new (config: { - initialState: { - systemPrompt: string; - model: ModelLike | undefined; - thinkingLevel: string; - tools: unknown[]; - }; - convertToLlm: (messages: unknown[]) => unknown[]; - onPayload: (payload: unknown, model: unknown) => Promise; - sessionId: string; - transformContext: (messages: unknown[]) => Promise; - steeringMode: unknown; - followUpMode: unknown; - transport: unknown; - thinkingBudgets: unknown; - maxRetryDelayMs: number; - getApiKey: (provider?: string) => Promise; -}) => { - state: { - model?: ModelLike; - thinkingLevel: string; - }; - subscribe(listener: (event: unknown) => void): () => void; - prompt(text: string): Promise; - abort(): void; - setThinkingLevel(level: string): void; - setTools(tools: PiToolLike[]): void; - setSystemPrompt(prompt: string): void; - replaceMessages(messages: unknown[]): void; -}; - -type SettingsManagerInstanceLike = { - getDefaultProvider(): string | undefined; - getDefaultModel(): string | undefined; - getDefaultThinkingLevel(): string | undefined; - getBlockImages(): boolean; - getSteeringMode(): unknown; - getFollowUpMode(): unknown; - getTransport(): unknown; - getThinkingBudgets(): unknown; - getRetrySettings(): { maxDelayMs: number }; - getShellCommandPrefix(): string | undefined; - getImageAutoResize(): boolean; -}; - -type ModelRegistryInstanceLike = { - find(provider: string, modelId: string): ModelLike | undefined; - getAvailable(): Promise; - getApiKey(model: ModelLike): Promise; - getApiKeyForProvider(provider: string): Promise; - isUsingOAuth(model: ModelLike): boolean; -}; - -type SessionManagerInstanceLike = { - buildSessionContext(): { - messages: unknown[]; - model?: { provider: string; modelId: string }; - thinkingLevel?: string; - }; - getBranch(): Array<{ type: string }>; - appendModelChange(provider: string, modelId: string): void; - appendThinkingLevelChange(thinkingLevel: string): void; - getSessionId(): string; -}; - -type PiToolLike = { - name: string; - description?: string; - parameters?: unknown; - execute( - toolCallId: string, - args: unknown, - signal: AbortSignal, - onUpdate?: (partialResult: unknown) => void, - ): Promise<{ - content: unknown; - details?: unknown; - }>; -}; - -type ExtensionFactoryLike = (api: unknown) => unknown; - -type PiSessionLike = { - readonly sessionId: string; - readonly thinkingLevel: string; - readonly messages: unknown[]; - subscribe( - listener: (event: AgentSessionEvent) => void, - ): () => void; - getAvailableThinkingLevels(): string[]; - prompt(text: string): Promise; - abort(): Promise; - setThinkingLevel(level: string): void; -}; - -type PiSdkRuntime = { - Agent: PiAgentCoreLike; - AuthStorage: { - create(authPath?: string): unknown; - }; - DefaultResourceLoader: new (options: { - cwd?: string; - agentDir?: string; - settingsManager?: SettingsManagerInstanceLike; - appendSystemPrompt?: string; - extensionFactories?: ExtensionFactoryLike[]; - noExtensions?: boolean; - }) => MinimalResourceLoaderLike; - DEFAULT_THINKING_LEVEL: string; - ModelRegistry: new (authStorage: unknown, modelsPath?: string) => { - find(provider: string, modelId: string): ModelLike | undefined; - getAvailable(): Promise; - getApiKey(model: ModelLike): Promise; - getApiKeyForProvider(provider: string): Promise; - isUsingOAuth(model: ModelLike): boolean; - }; - SettingsManager: { - create(cwd?: string, agentDir?: string): SettingsManagerInstanceLike; - }; - SessionManager: SessionManagerLike; - convertToLlm(messages: unknown[]): unknown[]; - getAgentDir(): string; - getDocsPath(): string; - createAgentSession(options?: { - cwd?: string; - agentDir?: string; - sessionManager?: unknown; - resourceLoader?: MinimalResourceLoaderLike; - settingsManager?: SettingsManagerInstanceLike; - tools?: PiToolLike[]; - customTools?: PiToolLike[]; - }): Promise<{ session: PiSessionLike; modelFallbackMessage?: string }>; - createCodingTools( - cwd: string, - options?: { - read?: { autoResizeImages?: boolean }; - bash?: { - commandPrefix?: string; - }; - }, - ): PiToolLike[]; - createAllTools( - cwd: string, - options?: { - read?: { autoResizeImages?: boolean }; - bash?: { - commandPrefix?: string; - }; - }, - ): Record; -}; - -let piSdkRuntimePromise: Promise | undefined; - -class MinimalPiSession implements PiSessionLike { - private readonly listeners = new Set< - (event: AgentSessionEvent) => void - >(); - - constructor( - private readonly agent: InstanceType, - private readonly sessionManager: SessionManagerInstanceLike, - private readonly settingsManager: SettingsManagerInstanceLike, - private readonly resourceLoader: MinimalResourceLoaderLike, - private readonly runtime: Pick, - private readonly cwd: string, - private readonly appendPrompt?: string, - ) { - this.agent.subscribe((event) => { - this.emit(event as AgentSessionEvent); - }); - this.rebuildRuntime(); - } - - get sessionId(): string { - return this.sessionManager.getSessionId(); - } - - get thinkingLevel(): string { - return this.agent.state.thinkingLevel; - } - - get messages(): unknown[] { - return (this.agent as { state: { messages?: unknown[] } }).state.messages ?? []; - } - - subscribe(listener: (event: AgentSessionEvent) => void): () => void { - this.listeners.add(listener); - return () => this.listeners.delete(listener); - } - - getAvailableThinkingLevels(): string[] { - return this.agent.state.model?.reasoning - ? ["off", "minimal", "low", "medium", "high"] - : ["off"]; - } - - async prompt(text: string): Promise { - await this.agent.prompt(text); - } - - async abort(): Promise { - this.agent.abort(); - } - - setThinkingLevel(level: string): void { - const nextLevel = this.agent.state.model?.reasoning ? level : "off"; - this.agent.setThinkingLevel(nextLevel); - this.sessionManager.appendThinkingLevelChange(nextLevel); - } - - private emit(event: AgentSessionEvent): void { - for (const listener of this.listeners) { - listener(event); - } - } - - private rebuildRuntime(): void { - const baseTools = this.runtime.createAllTools(this.cwd, { - read: { - autoResizeImages: this.settingsManager.getImageAutoResize(), - }, - bash: { - commandPrefix: this.settingsManager.getShellCommandPrefix(), - }, - }); - const activeToolNames = ["read", "bash", "edit", "write"].filter( - (name) => name in baseTools, - ); - this.agent.setTools( - activeToolNames.map((name) => baseTools[name]).filter(Boolean), - ); - this.agent.setSystemPrompt( - buildAdapterSystemPrompt(this.cwd, this.appendPrompt), - ); - } -} - -function buildAdapterSystemPrompt( - cwd: string, - appendPrompt?: string, -): string { - const date = new Date().toISOString().slice(0, 10); - const extra = appendPrompt ? `\n\n${appendPrompt}` : ""; - return ( - "You are an expert coding assistant operating inside Pi's ACP adapter.\n" + - "Use the available tools when they help complete the user's request.\n" + - "Be concise, prefer direct file and shell operations, and describe file paths clearly." + - `${extra}\nCurrent date: ${date}\nCurrent working directory: ${cwd}` - ); -} - -const DISCOVERED_EXTENSION_INDEX_CANDIDATES = [ - "index.js", - "index.mjs", - "index.cjs", -] as const; - -function isDiscoveredExtensionEntry(name: string): boolean { - return ( - name.endsWith(".js") || name.endsWith(".mjs") || name.endsWith(".cjs") - ); -} - -function discoverAutoExtensionPaths(cwd: string, agentDir: string): string[] { - const extensionRoots = [join(agentDir, "extensions"), join(cwd, ".pi", "extensions")]; - const discovered = new Set(); - - for (const root of extensionRoots) { - if (!existsSync(root)) { - continue; - } - for (const entry of readdirSync(root, { withFileTypes: true })) { - const entryPath = join(root, entry.name); - if (entry.isFile() && isDiscoveredExtensionEntry(entry.name)) { - discovered.add(entryPath); - continue; - } - if (!entry.isDirectory()) { - continue; - } - for (const candidate of DISCOVERED_EXTENSION_INDEX_CANDIDATES) { - const candidatePath = join(entryPath, candidate); - if (existsSync(candidatePath)) { - discovered.add(candidatePath); - break; - } - } - } - } - - return [...discovered].sort(); -} - -function readCommonJsExtensionFactory( - extensionPath: string, -): ExtensionFactoryLike | undefined { - const required = require(extensionPath); - if (typeof required === "function") { - return required as ExtensionFactoryLike; - } - if (typeof required?.default === "function") { - return required.default as ExtensionFactoryLike; - } - return undefined; -} - -// Temporary workaround: the V8 module loader currently fails dynamic -// import() of ESM `.js` extension files, so this evaluates a transformed -// copy of bare `export default` extensions. It cannot handle `import` -// statements or named exports. Delete this once the loader supports ESM -// `.js` dynamic import. -function readInlineDefaultExportFactory( - extensionPath: string, -): ExtensionFactoryLike | undefined { - const source = readFileSync(extensionPath, "utf8"); - if (!/\bexport\s+default\b/.test(source)) { - return undefined; - } - - const module = { exports: {} as { default?: unknown } }; - const transformed = source.replace( - /\bexport\s+default\b/, - "module.exports.default =", - ); - new Function("module", "exports", "require", transformed)( - module, - module.exports, - require, - ); - - return typeof module.exports.default === "function" - ? (module.exports.default as ExtensionFactoryLike) - : undefined; -} - -async function loadExtensionFactoryFromPath( - extensionPath: string, -): Promise { - if (extensionPath.endsWith(".cjs")) { - return readCommonJsExtensionFactory(extensionPath); - } - - if (extensionPath.endsWith(".mjs")) { - const module = await import(extensionPath); - return typeof module.default === "function" - ? (module.default as ExtensionFactoryLike) - : undefined; - } - - try { - return readCommonJsExtensionFactory(extensionPath); - } catch (error) { - let inlineFactory: ExtensionFactoryLike | undefined; - try { - inlineFactory = readInlineDefaultExportFactory(extensionPath); - } catch (inlineError) { - const inlineMessage = - inlineError instanceof Error ? inlineError.message : String(inlineError); - if (error instanceof Error) { - error.message = `${error.message} (inline default-export fallback also failed: ${inlineMessage})`; - } - throw error; - } - if (inlineFactory) { - return inlineFactory; - } - throw error; - } -} - -async function loadDiscoveredExtensionFactories( - cwd: string, - agentDir: string, -): Promise<{ - extensionFactories: ExtensionFactoryLike[]; - errors: Array<{ path: string; error: string }>; -}> { - const extensionFactories: ExtensionFactoryLike[] = []; - const errors: Array<{ path: string; error: string }> = []; - - for (const extensionPath of discoverAutoExtensionPaths(cwd, agentDir)) { - try { - const factory = await loadExtensionFactoryFromPath(extensionPath); - if (!factory) { - errors.push({ - path: extensionPath, - error: "Extension does not export a valid factory function", - }); - continue; - } - extensionFactories.push(factory); - } catch (error) { - errors.push({ - path: extensionPath, - error: error instanceof Error ? error.message : String(error), - }); - } - } - - return { extensionFactories, errors }; -} - -class MinimalResourceLoader implements MinimalResourceLoaderLike { - private readonly runtime = { - flagValues: new Map(), - pendingProviderRegistrations: [] as Array<{ - name: string; - config: unknown; - }>, - }; - - constructor(private readonly options: { appendSystemPrompt?: string }) {} - - async reload(): Promise {} - - getExtensions() { - return { - extensions: [], - errors: [], - runtime: this.runtime, - }; - } - - getSkills() { - return { skills: [], diagnostics: [] }; - } - - getPrompts() { - return { prompts: [], diagnostics: [] }; - } - - getThemes() { - return { themes: [], diagnostics: [] }; - } - - getAgentsFiles() { - return { agentsFiles: [] }; - } - - getSystemPrompt(): string { - return ""; - } - - getAppendSystemPrompt(): string[] { - return this.options.appendSystemPrompt ? [this.options.appendSystemPrompt] : []; - } - - getPathMetadata(): Map { - return new Map(); - } - - extendResources(_paths: string[]): void {} -} - -function findInstalledPackageRoot(packageName: string): string | null { - const searchPaths = require.resolve.paths(packageName) ?? []; - for (const basePath of searchPaths) { - const candidateRoot = join(basePath, packageName); - if (existsSync(join(candidateRoot, "package.json"))) { - return candidateRoot; - } - } - return null; -} - -function findProjectedPackageRoot(packageName: string): string { - const installedRoot = findInstalledPackageRoot(packageName); - if (installedRoot) { - return installedRoot; - } - - const directRoot = `${MODULE_ACCESS_NODE_MODULES}/${packageName}`; - const pnpmRoot = `${MODULE_ACCESS_NODE_MODULES}/.pnpm`; - const pnpmPrefix = `${packageName.replace("/", "+")}@`; - - if (existsSync(pnpmRoot)) { - for (const entry of readdirSync(pnpmRoot)) { - if (!entry.startsWith(pnpmPrefix)) continue; - const candidateRoot = join(pnpmRoot, entry, "node_modules", packageName); - if (existsSync(join(candidateRoot, "package.json"))) { - return candidateRoot; - } - } - } - - return directRoot; -} - -// When the agent SDK has been evaluated into the V8 startup snapshot (built once -// per sidecar), its runtime API is already present on this global — published by -// the snapshot build-entry (snapshot-entry.ts). Reading it skips the per-session -// dynamic-import + evaluate of the whole SDK graph entirely. -const PI_SDK_RUNTIME_GLOBAL = "__PI_SDK_RUNTIME__"; - -function readSnapshotRuntime(): PiSdkRuntime | undefined { - const candidate = (globalThis as Record)[ - PI_SDK_RUNTIME_GLOBAL - ]; - if ( - candidate && - typeof candidate === "object" && - typeof (candidate as PiSdkRuntime).createAgentSession === "function" - ) { - return candidate as PiSdkRuntime; - } - return undefined; -} - -async function loadPiSdkRuntime(): Promise { - if (!piSdkRuntimePromise) { - // Snapshot fast path: the SDK runtime is already on the global (evaluated - // into the snapshot at sidecar startup). No I/O, no module resolution. - const snapshotRuntime = readSnapshotRuntime(); - if (snapshotRuntime) { - piSdkRuntimePromise = Promise.resolve(snapshotRuntime); - return piSdkRuntimePromise; - } - // Fallback: load the SDK from the guest VFS via dynamic import (the path - // used when no snapshot is present, e.g. a cold/unsupported runtime). - piSdkRuntimePromise = (async () => { - const packageRoot = findProjectedPackageRoot(PI_SDK_PACKAGE); - const agentCoreRoot = findProjectedPackageRoot("@mariozechner/pi-agent-core"); - const [ - agentCoreModule, - authStorageModule, - configModule, - defaultsModule, - messagesModule, - modelRegistryModule, - resourceLoaderModule, - sdkModule, - sessionManagerModule, - settingsManagerModule, - toolsModule, - ] = - await Promise.all([ - import(`${agentCoreRoot}/dist/index.js`), - import(`${packageRoot}/dist/core/auth-storage.js`), - import(`${packageRoot}/dist/config.js`), - import(`${packageRoot}/dist/core/defaults.js`), - import(`${packageRoot}/dist/core/messages.js`), - import(`${packageRoot}/dist/core/model-registry.js`), - import(`${packageRoot}/dist/core/resource-loader.js`), - import(`${packageRoot}/dist/core/sdk.js`), - import(`${packageRoot}/dist/core/session-manager.js`), - import(`${packageRoot}/dist/core/settings-manager.js`), - import(`${packageRoot}/dist/core/tools/index.js`), - ]); - - return { - Agent: agentCoreModule.Agent as PiAgentCoreLike, - AuthStorage: authStorageModule.AuthStorage as PiSdkRuntime["AuthStorage"], - DefaultResourceLoader: - resourceLoaderModule.DefaultResourceLoader as PiSdkRuntime["DefaultResourceLoader"], - DEFAULT_THINKING_LEVEL: - defaultsModule.DEFAULT_THINKING_LEVEL as string, - ModelRegistry: - modelRegistryModule.ModelRegistry as PiSdkRuntime["ModelRegistry"], - SettingsManager: - settingsManagerModule.SettingsManager as PiSdkRuntime["SettingsManager"], - SessionManager: sessionManagerModule.SessionManager as SessionManagerLike, - convertToLlm: - messagesModule.convertToLlm as PiSdkRuntime["convertToLlm"], - getAgentDir: configModule.getAgentDir as PiSdkRuntime["getAgentDir"], - getDocsPath: configModule.getDocsPath as PiSdkRuntime["getDocsPath"], - createAgentSession: - sdkModule.createAgentSession as PiSdkRuntime["createAgentSession"], - createCodingTools: - sdkModule.createCodingTools as PiSdkRuntime["createCodingTools"], - createAllTools: - toolsModule.createAllTools as PiSdkRuntime["createAllTools"], - }; - })(); - } - - return piSdkRuntimePromise; -} - -async function createAgentSession(options: { - cwd: string; - sessionManager: unknown; - resourceLoader: MinimalResourceLoaderLike; - tools?: PiToolLike[]; -}): Promise<{ session: PiSessionLike; modelFallbackMessage?: string }> { - const { createAgentSession: createPiAgentSession, SettingsManager } = - await loadPiSdkRuntime(); - - const cwd = options.cwd; - const homeDir = process.env.HOME || "/home/agentos"; - const agentDir = join(homeDir, ".pi", "agent"); - const settingsManager = SettingsManager.create(cwd, agentDir); - const result = await createPiAgentSession({ - cwd, - agentDir, - sessionManager: options.sessionManager, - resourceLoader: options.resourceLoader, - settingsManager, - tools: options.tools, - customTools: options.tools, - }); - applyAnthropicBaseUrlOverride(result.session); - return result; -} - -function applyAnthropicBaseUrlOverride(session: PiSessionLike): void { - const baseUrl = process.env.ANTHROPIC_BASE_URL; - if (!baseUrl) return; - const agent = (session as { agent?: { state?: { model?: ModelLike } } }).agent; - const model = agent?.state?.model; - if (model?.provider !== "anthropic") return; - if (!agent?.state) return; - agent.state.model = { ...model, baseUrl }; -} - -// ── CLI argument parsing ──────────────────────────────────────────── - -let appendSystemPrompt: string | undefined; -const argv = process.argv.slice(2); -for (let i = 0; i < argv.length; i++) { - if (argv[i] === "--append-system-prompt" && i + 1 < argv.length) { - appendSystemPrompt = argv[i + 1]; - i++; - } -} - -// ── Agent implementation ──────────────────────────────────────────── - -class PiSdkAgent implements Agent { - private conn: AgentSideConnection; - private session: PiSessionLike | null = null; - private sessionId = ""; - private cwd = "/workspace"; - private cancelRequested = false; - private currentToolCalls = new Map(); - private emittedAssistantText = false; - private bufferingUpdates = false; - private pendingUpdates: SessionNotification["update"][] = []; - private streamedTextContent = new Set(); - private editSnapshots = new Map< - string, - { path: string; oldText: string } - >(); - private lastEmit: Promise = Promise.resolve(); - - constructor(conn: AgentSideConnection) { - this.conn = conn; - } - - async initialize( - _params: InitializeRequest, - ): Promise { - return { - protocolVersion: 1, - agentInfo: { - name: "pi-sdk-acp", - title: "Pi SDK ACP adapter", - version: "0.1.0", - }, - agentCapabilities: { - promptCapabilities: { - image: true, - audio: false, - embeddedContext: false, - }, - }, - }; - } - - async newSession( - params: NewSessionRequest, - ): Promise { - const __trace = startPhaseTrace(); - this.cwd = params.cwd; - process.chdir(params.cwd); - const agentDir = join(process.env.HOME || "/home/agentos", ".pi", "agent"); - const { - DefaultResourceLoader, - SessionManager, - SettingsManager, - createCodingTools, - } = await __trace.span("loadPiSdkRuntime", () => loadPiSdkRuntime()); - const { extensionFactories, errors: extensionLoadErrors } = - await __trace.span("loadExtensions", () => - loadDiscoveredExtensionFactories(params.cwd, agentDir), - ); - // Step 3: when no workspace extensions were discovered (the common case), - // skip DefaultResourceLoader entirely — it eagerly loads skills/themes/ - // prompts/agentsFiles (~250ms) the headless ACP adapter doesn't use. The - // MinimalResourceLoader's reload() is a no-op. Only the full loader (with its - // extension runtime) is constructed when extensions are actually present. - const resourceLoader: MinimalResourceLoaderLike = - extensionFactories.length > 0 - ? new DefaultResourceLoader({ - cwd: params.cwd, - agentDir, - noExtensions: true, - extensionFactories, - ...(appendSystemPrompt ? { appendSystemPrompt } : {}), - }) - : new MinimalResourceLoader({ - ...(appendSystemPrompt ? { appendSystemPrompt } : {}), - }); - await __trace.span("resourceLoader.reload", () => resourceLoader.reload()); - for (const { path, error } of extensionLoadErrors) { - console.warn(`[pi-sdk-acp] Failed to load extension ${path}: ${error}`); - } - const settingsManager = SettingsManager.create( - params.cwd, - agentDir, - ); - - const { session } = await __trace.span("createAgentSession", () => - createAgentSession({ - cwd: params.cwd, - sessionManager: SessionManager.inMemory(params.cwd), - resourceLoader, - tools: this.wrapTools( - createCodingTools(params.cwd, { - read: { - autoResizeImages: settingsManager.getImageAutoResize(), - }, - bash: { - commandPrefix: settingsManager.getShellCommandPrefix(), - }, - }), - ), - }), - ); - __trace.flush(); - - this.session = session; - this.sessionId = session.sessionId; - - // Subscribe to Pi SDK events and translate to ACP notifications - session.subscribe((event) => this.handlePiEvent(event)); - - // Build thinking modes - const thinkingLevels = session.getAvailableThinkingLevels(); - const modes = { - currentModeId: session.thinkingLevel, - availableModes: thinkingLevels.map((id) => ({ - id, - name: `Thinking: ${id}`, - })), - }; - - return { - sessionId: this.sessionId, - modes, - }; - } - - async prompt(params: PromptRequest): Promise { - const session = this.session; - if (!session) { - throw new Error("No session created"); - } - - this.cancelRequested = false; - this.bufferingUpdates = true; - this.currentToolCalls.clear(); - this.emittedAssistantText = false; - this.pendingUpdates = []; - this.streamedTextContent.clear(); - - // Extract text from prompt parts - const promptParts = params.prompt ?? []; - const text = promptParts - .map((p: { type?: string; text?: string }) => - p.type === "text" ? (p.text ?? "") : "", - ) - .join(""); - - // session.prompt() resolves when the agent loop completes. - // Events fire via subscribe() during execution and are translated - // to ACP notifications in handlePiEvent(). - try { - await session.prompt(text); - } catch (error) { - if (!this.cancelRequested) { - throw error; - } - } - - if (!this.emittedAssistantText) { - const latestText = this.latestAssistantText(); - await this.emitAssistantText(latestText); - } - - // The SDK resolves prompt() before its queued session event pipeline - // has necessarily drained through subscribe() listeners. - await new Promise((resolve) => setTimeout(resolve, 0)); - - await this.flushPendingUpdates(); - this.bufferingUpdates = false; - - const stopReason = this.cancelRequested ? "cancelled" : "end_turn"; - return { - stopReason: stopReason as PromptResponse["stopReason"], - }; - } - - async cancel(_params: CancelNotification): Promise { - this.cancelRequested = true; - await this.session?.abort(); - } - - async setSessionMode( - params: SetSessionModeRequest, - ): Promise { - if (!this.session) return; - - this.session.setThinkingLevel( - params.modeId as Parameters[0], - ); - - await this.emit({ - sessionUpdate: "current_mode_update" as const, - currentModeId: params.modeId, - }); - } - - async authenticate( - _params: AuthenticateRequest, - ): Promise { - // Auth handled via env vars (ANTHROPIC_API_KEY) - } - - // ── Event translation ─────────────────────────────────────────── - - private emit(update: SessionNotification["update"]): Promise { - if (this.bufferingUpdates) { - this.pendingUpdates.push(update); - return Promise.resolve(); - } - return this.sendUpdate(update); - } - - private sendUpdate(update: SessionNotification["update"]): Promise { - this.lastEmit = this.lastEmit - .then(() => - this.conn.sessionUpdate({ - sessionId: this.sessionId, - update, - }), - ) - .catch(() => {}); - return this.lastEmit; - } - - private async flushPendingUpdates(): Promise { - const updates = this.pendingUpdates; - this.pendingUpdates = []; - for (const update of updates) { - await this.sendUpdate(update); - } - } - - private emitAssistantText(text: string): Promise { - if (!text) { - return Promise.resolve(); - } - this.emittedAssistantText = true; - return this.emit({ - sessionUpdate: "agent_message_chunk", - content: { - type: "text", - text, - }, - }); - } - - private handlePiEvent(event: AgentSessionEvent): void { - switch (event.type) { - case "message_update": { - const ame = event.assistantMessageEvent; - if (!ame) break; - - if (ame.type === "text_delta" && "delta" in ame) { - this.streamedTextContent.add(this.textContentKey(ame)); - this.emitAssistantText(String((ame as { delta: string }).delta)); - } else if (ame.type === "text_end" && "content" in ame) { - const textKey = this.textContentKey(ame); - if (!this.streamedTextContent.has(textKey)) { - this.emitAssistantText(String((ame as { content: string }).content)); - } - } else if (ame.type === "thinking_delta" && "delta" in ame) { - this.emit({ - sessionUpdate: "agent_thought_chunk", - content: { - type: "text", - text: String((ame as { delta: string }).delta), - }, - }); - } else if ( - ame.type === "toolcall_start" || - ame.type === "toolcall_delta" || - ame.type === "toolcall_end" - ) { - this.handleToolCallMessage(ame); - } - break; - } - - case "tool_execution_start": - this.handleToolExecutionStart(event); - break; - - case "tool_execution_update": - this.handleToolExecutionUpdate(event); - break; - - case "tool_execution_end": - this.handleToolExecutionEnd(event); - break; - - case "agent_end": - // Agent loop finished. Notifications are flushed in prompt(). - break; - } - } - - private handleToolCallMessage(ame: Record): void { - const toolCall = - (ame.toolCall as Record) ?? - ( - (ame.partial as Record) - ?.content as Array> - )?.[(ame.contentIndex as number) ?? 0]; - - if (!toolCall) return; - - const toolCallId = String(toolCall.id ?? ""); - const toolName = String(toolCall.name ?? "tool"); - - if (!toolCallId) return; - - const rawInput = this.parseToolArgs(toolCall); - const locations = this.toToolCallLocations(rawInput); - const existingStatus = this.currentToolCalls.get(toolCallId); - const status = existingStatus ?? "pending"; - - if (!existingStatus) { - this.currentToolCalls.set(toolCallId, "pending"); - this.emit({ - sessionUpdate: "tool_call", - toolCallId, - title: toolName, - kind: toToolKind(toolName), - status: status as "pending", - locations, - rawInput, - }); - } else { - this.emit({ - sessionUpdate: "tool_call_update", - toolCallId, - status: status as "pending", - locations, - rawInput, - }); - } - } - - private handleToolExecutionStart(event: { - toolCallId: string; - toolName: string; - args: unknown; - }): void { - const { toolCallId, toolName, args } = event; - const rawInput = args as Record | undefined; - - // Snapshot for edit diff support - if (toolName === "edit" && rawInput) { - const p = - typeof rawInput.path === "string" ? rawInput.path : undefined; - if (p) { - try { - const abs = isAbsolute(p) - ? p - : resolvePath(this.cwd, p); - const oldText = readFileSync(abs, "utf8"); - this.editSnapshots.set(toolCallId, { - path: p, - oldText, - }); - } catch { - // File may not exist - } - } - } - - const locations = this.toToolCallLocations(rawInput); - - if (!this.currentToolCalls.has(toolCallId)) { - this.currentToolCalls.set(toolCallId, "in_progress"); - this.emit({ - sessionUpdate: "tool_call", - toolCallId, - title: toolName, - kind: toToolKind(toolName), - status: "in_progress", - locations, - rawInput, - }); - } else { - this.currentToolCalls.set(toolCallId, "in_progress"); - this.emit({ - sessionUpdate: "tool_call_update", - toolCallId, - status: "in_progress", - locations, - rawInput, - }); - } - } - - private handleToolExecutionUpdate(event: { - toolCallId: string; - partialResult: unknown; - }): void { - const { toolCallId, partialResult } = event; - const text = toolResultToText(partialResult); - - this.emit({ - sessionUpdate: "tool_call_update", - toolCallId, - status: "in_progress", - content: text - ? [{ type: "content", content: { type: "text", text } }] - : undefined, - rawOutput: partialResult as Record, - }); - } - - private handleToolExecutionEnd(event: { - toolCallId: string; - result: unknown; - isError: boolean; - }): void { - const { toolCallId, result, isError } = event; - const text = toolResultToText(result); - const snapshot = this.editSnapshots.get(toolCallId); - - let content: - | Array< - | { type: "diff"; path: string; oldText: string; newText: string } - | { type: "content"; content: { type: "text"; text: string } } - > - | undefined; - - // Generate diff for edit tool - if (!isError && snapshot) { - try { - const abs = isAbsolute(snapshot.path) - ? snapshot.path - : resolvePath(this.cwd, snapshot.path); - const newText = readFileSync(abs, "utf8"); - if (newText !== snapshot.oldText) { - content = [ - { - type: "diff" as const, - path: snapshot.path, - oldText: snapshot.oldText, - newText, - }, - ...(text - ? [ - { - type: "content" as const, - content: { type: "text" as const, text }, - }, - ] - : []), - ]; - } - } catch { - // File may have been deleted - } - } - - if (!content && text) { - content = [ - { type: "content" as const, content: { type: "text" as const, text } }, - ]; - } - - this.emit({ - sessionUpdate: "tool_call_update", - toolCallId, - status: isError ? "failed" : "completed", - content, - rawOutput: result as Record, - }); - - this.currentToolCalls.delete(toolCallId); - this.editSnapshots.delete(toolCallId); - } - - // ── Helpers ────────────────────────────────────────────────────── - - private parseToolArgs( - toolCall: Record, - ): Record | undefined { - if ( - toolCall.arguments && - typeof toolCall.arguments === "object" - ) { - return toolCall.arguments as Record; - } - const s = String(toolCall.partialArgs ?? ""); - if (!s) return undefined; - try { - return JSON.parse(s); - } catch { - return { partialArgs: s }; - } - } - - private toToolCallLocations( - args: Record | undefined, - ): Array<{ path: string; line?: number }> | undefined { - const path = - typeof args?.path === "string" ? args.path : undefined; - if (!path) return undefined; - const resolvedPath = isAbsolute(path) - ? path - : resolvePath(this.cwd, path); - return [{ path: resolvedPath }]; - } - - private textContentKey(ame: Record): string { - const contentIndex = - typeof ame.contentIndex === "number" ? ame.contentIndex : -1; - return String(contentIndex); - } - - private latestAssistantText(): string { - if (!this.session) { - return ""; - } - - for (let index = this.session.messages.length - 1; index >= 0; index--) { - const message = this.session.messages[index] as { - role?: string; - content?: unknown; - }; - if (message.role !== "assistant") { - continue; - } - - const content = message.content; - if (typeof content === "string") { - return content; - } - if (!Array.isArray(content)) { - const errorMessage = - typeof (message as { errorMessage?: unknown }).errorMessage === "string" - ? (message as { errorMessage: string }).errorMessage - : ""; - return errorMessage; - } - - const text = content - .map((part) => { - const block = part as { type?: string; text?: string }; - return block.type === "text" && typeof block.text === "string" - ? block.text - : ""; - }) - .filter(Boolean) - .join(""); - if (text) { - return text; - } - - const errorMessage = - typeof (message as { errorMessage?: unknown }).errorMessage === "string" - ? (message as { errorMessage: string }).errorMessage - : ""; - return errorMessage; - } - - return ""; - } - - private wrapTools(tools: PiToolLike[]): PiToolLike[] { - return tools.map((tool) => ({ - ...tool, - execute: async (toolCallId, args, signal, onUpdate) => { - const rawInput = - args && typeof args === "object" - ? (args as Record) - : undefined; - const locations = this.toToolCallLocations(rawInput); - - this.currentToolCalls.set(toolCallId, "in_progress"); - await this.emit({ - sessionUpdate: "tool_call", - toolCallId, - title: tool.name, - kind: toToolKind(tool.name), - status: "in_progress", - locations, - rawInput, - }); - - try { - const result = await tool.execute( - toolCallId, - args, - signal, - (partialResult) => { - void this.emit({ - sessionUpdate: "tool_call_update", - toolCallId, - status: "in_progress", - content: toTextContent(toolResultToText(partialResult)), - rawOutput: - partialResult && typeof partialResult === "object" - ? (partialResult as Record) - : undefined, - }); - onUpdate?.(partialResult); - }, - ); - - await this.emit({ - sessionUpdate: "tool_call_update", - toolCallId, - status: "completed", - content: toTextContent(toolResultToText(result)), - rawOutput: - result && typeof result === "object" - ? (result as Record) - : undefined, - }); - return result; - } catch (error) { - await this.emit({ - sessionUpdate: "tool_call_update", - toolCallId, - status: "failed", - content: - error instanceof Error - ? toTextContent(error.message) - : undefined, - }); - throw error; - } finally { - this.currentToolCalls.delete(toolCallId); - } - }, - })); - } -} - -// ── Standalone helpers ────────────────────────────────────────────── - -function toToolKind( - toolName: string, -): "read" | "edit" | "other" { - if (toolName === "read") return "read"; - if (toolName === "write" || toolName === "edit") return "edit"; - return "other"; -} - -function toTextContent(text: string): - | Array<{ type: "content"; content: { type: "text"; text: string } }> - | undefined { - if (!text) { - return undefined; - } - return [ - { - type: "content", - content: { - type: "text", - text, - }, - }, - ]; -} - -function toolResultToText(result: unknown): string { - if (!result) return ""; - const r = result as Record; - const content = r.content; - if (Array.isArray(content)) { - const texts = content - .map((c: Record) => - c?.type === "text" && typeof c.text === "string" - ? c.text - : "", - ) - .filter(Boolean); - if (texts.length) return texts.join(""); - } - const details = r.details as Record | undefined; - const stdout = - (typeof details?.stdout === "string" ? details.stdout : undefined) ?? - (typeof r.stdout === "string" ? r.stdout : undefined) ?? - (typeof details?.output === "string" ? details.output : undefined) ?? - (typeof r.output === "string" ? r.output : undefined); - const stderr = - (typeof details?.stderr === "string" ? details.stderr : undefined) ?? - (typeof r.stderr === "string" ? r.stderr : undefined); - const exitCode = - (typeof details?.exitCode === "number" - ? details.exitCode - : undefined) ?? - (typeof r.exitCode === "number" ? r.exitCode : undefined) ?? - (typeof details?.code === "number" ? details.code : undefined) ?? - (typeof r.code === "number" ? r.code : undefined); - - if ( - (typeof stdout === "string" && stdout.trim()) || - (typeof stderr === "string" && stderr.trim()) - ) { - const parts: string[] = []; - if (typeof stdout === "string" && stdout.trim()) parts.push(stdout); - if (typeof stderr === "string" && stderr.trim()) - parts.push(`stderr:\n${stderr}`); - if (typeof exitCode === "number") - parts.push(`exit code: ${exitCode}`); - return parts.join("\n\n").trimEnd(); - } - - try { - return JSON.stringify(result, null, 2); - } catch { - return String(result); - } -} - -// ── Entry point ───────────────────────────────────────────────────── - -const input = new WritableStream({ - write(chunk) { - return new Promise((resolve) => { - process.stdout.write(chunk, () => resolve()); - }); - }, -}); - -const output = new ReadableStream({ - start(controller) { - realStdin.on("data", (chunk: Buffer) => { - controller.enqueue(new Uint8Array(chunk)); - }); - realStdin.on("end", () => controller.close()); - realStdin.on("error", (error: Error) => controller.error(error)); - }, -}); - -const stream = ndJsonStream(input, output); -const _connection = new AgentSideConnection( - (conn) => new PiSdkAgent(conn), - stream, -); - -// Keep process alive -realStdin.resume(); - -// Shutdown on stdin close -realStdin.on("end", () => { - process.exit(0); -}); diff --git a/registry/agent/pi/src/index.ts b/registry/agent/pi/src/index.ts deleted file mode 100644 index 0b8bcf365..000000000 --- a/registry/agent/pi/src/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineSoftware } from "@rivet-dev/agentos-core"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const packageDir = resolve(__dirname, ".."); - -const pi = defineSoftware({ - name: "pi", - type: "agent" as const, - packageDir, - requires: ["@agentos-software/pi", "@mariozechner/pi-coding-agent"], - agent: { - id: "pi", - acpAdapter: "@agentos-software/pi", - agentPackage: "@mariozechner/pi-coding-agent", - // Evaluate the bundled Pi SDK into the per-sidecar V8 snapshot - // (dist/sdk-snapshot.js) so it loads once per sidecar and is reused - // across sessions. Falls back to per-session dynamic import if the - // snapshot can't be built. - snapshot: true, - }, -}); - -export default pi; diff --git a/registry/agent/pi/src/snapshot-entry.ts b/registry/agent/pi/src/snapshot-entry.ts deleted file mode 100644 index bc90b5d14..000000000 --- a/registry/agent/pi/src/snapshot-entry.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Pi SDK snapshot entry (Step 2a/2c — eval/entry split). - * - * This module is bundled by esbuild into a single IIFE (`dist/pi-sdk-snapshot.js`) - * whose ONLY job is to evaluate the Pi SDK module graph and publish its exports on - * a well-known global, `globalThis.__PI_SDK_RUNTIME__`. The bundle is run once at V8 - * snapshot-creation time; the evaluated SDK heap is captured into the startup blob - * and reused to seed every fresh per-session isolate. After restore the adapter - * reads the global instead of dynamically importing the SDK from the guest VFS, - * collapsing the ~830ms per-session module-load/eval tax. - * - * INVARIANTS (enforced by the C0 snapshot-safety scan — see - * ~/.agents/research/pi-snapshot-safety-scan.md): - * - This entry performs NO per-session work: it must not read cwd, model, - * ANTHROPIC env vars, HOME, argv, open fds/sockets, or start timers. It only - * binds the SDK's static exports. All per-session configuration is injected - * post-restore by the adapter (newSession), exactly as before. - * - Heavy provider SDKs (@anthropic-ai/sdk, openai, …) are loaded by the SDK via - * dynamic import() and are intentionally NOT part of this static graph; they - * stay lazy and load post-restore from the VFS on first prompt. - * - The bundle is keyed by the sha256 of its own (fully inlined) bytes, so any SDK - * dependency change invalidates the snapshot. - */ - -import { Agent } from "@mariozechner/pi-agent-core"; -import { AuthStorage } from "@mariozechner/pi-coding-agent/dist/core/auth-storage.js"; -import { - getAgentDir, - getDocsPath, -} from "@mariozechner/pi-coding-agent/dist/config.js"; -import { DEFAULT_THINKING_LEVEL } from "@mariozechner/pi-coding-agent/dist/core/defaults.js"; -import { convertToLlm } from "@mariozechner/pi-coding-agent/dist/core/messages.js"; -import { ModelRegistry } from "@mariozechner/pi-coding-agent/dist/core/model-registry.js"; -import { DefaultResourceLoader } from "@mariozechner/pi-coding-agent/dist/core/resource-loader.js"; -import { - createAgentSession, - createCodingTools, -} from "@mariozechner/pi-coding-agent/dist/core/sdk.js"; -import { SessionManager } from "@mariozechner/pi-coding-agent/dist/core/session-manager.js"; -import { SettingsManager } from "@mariozechner/pi-coding-agent/dist/core/settings-manager.js"; -import { createAllTools } from "@mariozechner/pi-coding-agent/dist/core/tools/index.js"; - -// The shape mirrors `PiSdkRuntime` in adapter.ts so the adapter can read the -// global with no behavioral difference from the dynamic-import path. -const runtime = { - Agent, - AuthStorage, - DefaultResourceLoader, - DEFAULT_THINKING_LEVEL, - ModelRegistry, - SettingsManager, - SessionManager, - convertToLlm, - getAgentDir, - getDocsPath, - createAgentSession, - createCodingTools, - createAllTools, -}; - -// Publish on the global so it survives into every fresh context cloned from the -// snapshotted default context. -(globalThis as Record).__PI_SDK_RUNTIME__ = runtime; diff --git a/registry/agent/pi/tsconfig.json b/registry/agent/pi/tsconfig.json deleted file mode 100644 index e15131c84..000000000 --- a/registry/agent/pi/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "declaration": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/snapshot-entry.ts"] -} diff --git a/registry/tests/helpers.ts b/registry/tests/helpers.ts deleted file mode 100644 index 6959a0863..000000000 --- a/registry/tests/helpers.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { existsSync } from "node:fs"; -import { resolve, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; -import { describe, it } from "vitest"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -/** Directory containing WASM command binaries built from Rust. */ -export const COMMANDS_DIR = resolve( - __dirname, - "../native/target/wasm32-wasip1/release/commands", -); - -/** Directory containing C-compiled WASM binaries. */ -export const C_BUILD_DIR = resolve(__dirname, "../native/c/build/"); - -/** Whether the main WASM command binaries are available (includes 'sh'). */ -export const hasWasmBinaries = - existsSync(COMMANDS_DIR) && existsSync(resolve(COMMANDS_DIR, "sh")); - -/** - * Check whether specific C WASM binaries are present. - * @param names - Binary names to check for inside C_BUILD_DIR. - * @returns true if all requested binaries exist. - */ -export function hasCWasmBinaries(...names: string[]): boolean { - if (!existsSync(C_BUILD_DIR)) return false; - return names.every((name) => existsSync(resolve(C_BUILD_DIR, name))); -} - -/** - * Returns a skip-reason string if WASM binaries are missing, or false if - * they are available and tests should run. - */ -export function skipReason(): string | false { - if (!hasWasmBinaries) { - return `WASM binaries not found at ${COMMANDS_DIR} — build with \`make wasm\` first`; - } - return false; -} - -export function describeIf( - condition: unknown, - ...args: Parameters -): void { - if (condition) { - // Vitest's overloaded tuple shape is awkward to preserve across helper forwarding. - // @ts-expect-error forwarded describe() arguments stay runtime-compatible. - describe(...args); - return; - } - const [name] = args; - describe.skip(`${String(name)} [environment prerequisites not met]`, () => {}); -} - -export function itIf( - condition: unknown, - ...args: Parameters -): void { - if (condition) { - // Vitest's overloaded tuple shape is awkward to preserve across helper forwarding. - // @ts-expect-error forwarded it() arguments stay runtime-compatible. - it(...args); - return; - } - const [name] = args; - it.skip(`${String(name)} [environment prerequisites not met]`, () => {}); -} - -// Re-exports from the repo-owned generic runtime surface. -export { - AF_INET, - AF_UNIX, - allowAll, - createInMemoryFileSystem, - SIGTERM, - SOCK_DGRAM, - SOCK_STREAM, -} from "@secure-exec/core/test-runtime"; -import { - allowAll, - createKernel as createKernelBase, -} from "@secure-exec/core/test-runtime"; -export type { - DriverProcess, - Kernel, - KernelInterface, - KernelRuntimeDriver, - ProcessContext, - VirtualFileSystem, -} from "@secure-exec/core/test-runtime"; -export { - createWasmVmRuntime, - DEFAULT_FIRST_PARTY_TIERS, - WASMVM_COMMANDS, - type PermissionTier, - type WasmVmRuntimeOptions, -} from "@secure-exec/core/test-runtime"; -export { - createNodeHostNetworkAdapter, - createNodeRuntime, - NodeFileSystem, -} from "@secure-exec/core/test-runtime"; -export { TerminalHarness } from "./terminal-harness.js"; - -/** - * Registry integration tests assume they can bootstrap runtimes and /bin stubs - * unless they explicitly opt into a stricter permission policy. - */ -export function createKernel( - options: Parameters[0], -): ReturnType { - return createKernelBase({ - ...options, - permissions: options.permissions ?? allowAll, - }); -} diff --git a/registry/tests/kernel/bridge-child-process.test.ts b/registry/tests/kernel/bridge-child-process.test.ts deleted file mode 100644 index 287fd4be1..000000000 --- a/registry/tests/kernel/bridge-child-process.test.ts +++ /dev/null @@ -1,719 +0,0 @@ -/** - * Integration tests: Node bridge child_process routing through kernel. - * - * Verifies that child_process.spawn/execSync/spawnSync calls from Node - * isolate code route through the kernel's command registry to the - * appropriate runtime driver (WasmVM for shell commands). - * - * Gracefully skipped when the WASM binary is not built. - */ - -import { chmodSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { dirname, join, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { describe, it, expect, afterEach } from 'vitest'; -import { - COMMANDS_DIR, - createKernel, - createNodeRuntime, - createWasmVmRuntime, - describeIf, - createIntegrationKernel, - NodeFileSystem, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const PACKAGED_COREUTILS_COMMANDS_DIR = resolve( - __dirname, - '../../software/coreutils/wasm', -); -const BRIDGE_COMMAND_DIRS = [ - COMMANDS_DIR, - PACKAGED_COREUTILS_COMMANDS_DIR, -].filter((commandDir, index, allDirs) => { - return ( - existsSync(join(commandDir, 'sh')) && allDirs.indexOf(commandDir) === index - ); -}); -const skipReason = - BRIDGE_COMMAND_DIRS.length === 0 - ? `WASM shell command not found at ${COMMANDS_DIR} or ${PACKAGED_COREUTILS_COMMANDS_DIR}` - : false; - -function createBridgeIntegrationKernel(): Promise { - return createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - commandDirs: BRIDGE_COMMAND_DIRS, - }); -} - -describeIf(!skipReason, 'bridge child_process → kernel routing', () => { - let ctx: IntegrationKernelResult; - const cleanupPaths: string[] = []; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - for (const cleanupPath of cleanupPaths.splice(0)) { - rmSync(cleanupPath, { recursive: true, force: true }); - } - }); - - it('execSync("echo hello") routes through kernel to WasmVM shell', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - const result = execSync('echo hello', { encoding: 'utf-8' }); - console.log(result.trim()); - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('hello'); - }); - - it('child_process.spawn("ls") resolves to WasmVM runtime', async () => { - ctx = await createBridgeIntegrationKernel(); - await ctx.vfs.writeFile('/tmp/test-file.txt', 'content'); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - const result = execSync('ls /tmp', { encoding: 'utf-8' }); - console.log(result.trim()); - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('test-file.txt'); - }); - - it('spawned processes get proper PIDs from kernel process table', async () => { - ctx = await createBridgeIntegrationKernel(); - - // The Node process itself gets a PID from the kernel - const proc = ctx.kernel.spawn('node', ['-e', 'console.log("pid-test")']); - expect(proc.pid).toBeGreaterThan(0); - - await proc.wait(); - }); - - it('stdout from spawned child processes pipes back to Node caller', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - const result = execSync('echo "piped-output"', { encoding: 'utf-8' }); - console.log('received:', result.trim()); - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('received: piped-output'); - }); - - it('async child_process.spawn("sh") can stream output and exit cleanly', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { spawn } = require('child_process'); - const child = spawn('sh', ['-lc', 'echo async-ok'], { - stdio: ['ignore', 'pipe', 'inherit'], - }); - child.stdout.on('data', (chunk) => process.stdout.write(chunk)); - child.on('close', (code) => process.exit(code ?? 0)); - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('async-ok'); - }); - - it('child_process.spawn with shell:true preserves shell builtin exit codes', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { spawn } = require('child_process'); - const child = spawn('exit 7', { shell: true }); - child.on('close', (code) => { - console.log('close:' + code); - process.exit(code ?? 0); - }); - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(7); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('close:7'); - }); - - it('stderr from spawned child processes pipes back to Node caller', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - try { - execSync('cat /nonexistent/path', { encoding: 'utf-8' }); - } catch (e) { - console.log('caught-error'); - } - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('caught-error'); - }); - - it('commands not in the registry return ENOENT-like error', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - try { - execSync('nonexistent-cmd-xyz', { encoding: 'utf-8' }); - console.log('SHOULD_NOT_REACH'); - } catch (e) { - console.log('error-caught'); - } - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - // execSync wraps the command in bash -c, so the shell handles unknown commands - // Either the shell returns non-zero (caught by execSync) or ENOENT propagates - expect(output).not.toContain('SHOULD_NOT_REACH'); - expect(output).toContain('error-caught'); - }); - - it('execSync with env passes environment through kernel', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - const result = execSync('echo $TEST_VAR', { - encoding: 'utf-8', - env: { TEST_VAR: 'kernel-env-test' }, - }); - console.log(result.trim()); - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('kernel-env-test'); - }); - - it('cat reads VFS file through kernel child_process', async () => { - ctx = await createBridgeIntegrationKernel(); - await ctx.vfs.writeFile('/tmp/bridge-test.txt', 'hello from vfs'); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - const result = execSync('cat /tmp/bridge-test.txt', { encoding: 'utf-8' }); - console.log(result.trim()); - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('hello from vfs'); - }); - - it('execSync shell redirection writes command stdout into the kernel VFS', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - execSync("printf 'bash-ok' > bash-output.txt", { encoding: 'utf-8' }); - console.log(execSync('cat /tmp/bash-output.txt', { encoding: 'utf-8' })); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - expect(output).toContain('bash-ok'); - expect(new TextDecoder().decode(await ctx.vfs.readFile('/tmp/bash-output.txt'))).toBe('bash-ok'); - }); - - it('execSync multi-statement shell syntax runs through the guest shell', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const fs = require('fs'); - const { execSync } = require('child_process'); - execSync("echo ignored; echo fallback-ok > fallback-output.txt", { encoding: 'utf-8' }); - console.log(fs.readFileSync('/tmp/fallback-output.txt', 'utf8')); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - expect(output).toContain('fallback-ok'); - expect(new TextDecoder().decode(await ctx.vfs.readFile('/tmp/fallback-output.txt'))).toBe('fallback-ok\n'); - }); - - it('execSync append redirection onto a write-only file succeeds like Linux', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const fs = require('fs'); - const { execSync } = require('child_process'); - fs.writeFileSync('/tmp/write-only.txt', 'original'); - fs.chmodSync('/tmp/write-only.txt', 0o200); - // A real shell opens the append target write-only, so a 0o200 file is - // appendable even though it cannot be read back until the chmod below. - execSync("printf changed >> /tmp/write-only.txt", { encoding: 'utf-8' }); - fs.chmodSync('/tmp/write-only.txt', 0o600); - console.log(JSON.stringify({ - mode: 'loaded', - file: fs.readFileSync('/tmp/write-only.txt', 'utf8') - })); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - const result = JSON.parse(output.trim()); - expect(result.mode).toBe('loaded'); - expect(result.file).toBe('originalchanged'); - }); - - it('execSync append redirection appends and creates missing files', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - execSync("printf a > append-base.txt"); - execSync("printf b >> append-base.txt"); - execSync("printf c >> append-fresh.txt"); - console.log('append-done'); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - expect(new TextDecoder().decode(await ctx.vfs.readFile('/tmp/append-base.txt'))).toBe('ab'); - expect(new TextDecoder().decode(await ctx.vfs.readFile('/tmp/append-fresh.txt'))).toBe('c'); - }); - - it('execSync stdin redirection feeds the kernel VFS file to the command', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const fs = require('fs'); - const { execSync } = require('child_process'); - fs.writeFileSync('/tmp/stdin-input.txt', 'stdin-redirect-content'); - const result = execSync('cat < stdin-input.txt', { encoding: 'utf-8' }); - console.log('read:' + result); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - expect(output).toContain('read:stdin-redirect-content'); - }); - - it('execSync redirection handles quoted target paths with spaces', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - execSync("printf hi > 'out file.txt'"); - execSync('printf hi > "out file2.txt"'); - console.log('quoted-done'); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - expect(new TextDecoder().decode(await ctx.vfs.readFile('/tmp/out file.txt'))).toBe('hi'); - expect(new TextDecoder().decode(await ctx.vfs.readFile('/tmp/out file2.txt'))).toBe('hi'); - }); - - it('execSync surfaces shell failure exit codes and truncates redirect targets', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const fs = require('fs'); - const { execSync } = require('child_process'); - let redirectFailure = null; - try { - execSync('cat /missing-input-file > fail-out.txt', { encoding: 'utf-8' }); - } catch (error) { - redirectFailure = { - status: error.status ?? null, - stderr: String(error.stderr ?? ''), - }; - } - let exitFailure = null; - try { - execSync('exit 7', { encoding: 'utf-8' }); - } catch (error) { - exitFailure = { status: error.status ?? null }; - } - console.log(JSON.stringify({ - redirectFailure, - exitFailure, - redirectTarget: fs.readFileSync('/tmp/fail-out.txt', 'utf8'), - })); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - const result = JSON.parse(output.trim()); - expect(result.redirectFailure).not.toBeNull(); - expect(result.redirectFailure.status).not.toBe(0); - expect(result.redirectFailure.stderr).toContain('missing-input-file'); - // A real shell truncates and creates the redirect target before exec runs. - expect(result.redirectTarget).toBe(''); - expect(result.exitFailure).toEqual({ status: 7 }); - }); - - it('async exec() redirection writes command stdout into the kernel VFS', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { exec } = require('child_process'); - exec('printf hi > async-out.txt', (error, stdout, stderr) => { - console.log(JSON.stringify({ - error: error ? String(error.message) : null, - stdout, - })); - process.exit(error ? 1 : 0); - }); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - const result = JSON.parse(output.trim()); - expect(result.error).toBeNull(); - expect(result.stdout).toBe(''); - expect(new TextDecoder().decode(await ctx.vfs.readFile('/tmp/async-out.txt'))).toBe('hi'); - }); - - it('spawn with shell:true performs redirection through the guest shell', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { spawn } = require('child_process'); - const child = spawn('printf hi > spawn-out.txt', { shell: true }); - child.on('close', (code) => { - console.log('close:' + code); - process.exit(code ?? 1); - }); - `], { - cwd: '/tmp', - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(code, `stdout:\n${output}\nstderr:\n${stderr}`).toBe(0); - expect(output).toContain('close:0'); - expect(new TextDecoder().decode(await ctx.vfs.readFile('/tmp/spawn-out.txt'))).toBe('hi'); - }); - - it('execFileSync on node_modules/.bin shell shims unwraps to the node entrypoint', async () => { - const projectRoot = mkdtempSync(join(tmpdir(), 'agentos-node-bin-shim-')); - cleanupPaths.push(projectRoot); - - mkdirSync(join(projectRoot, 'node_modules', '.bin'), { recursive: true }); - mkdirSync(join(projectRoot, 'node_modules', 'demo'), { recursive: true }); - writeFileSync( - join(projectRoot, 'node_modules', 'demo', 'index.js'), - '#!/usr/bin/env node\nconsole.log(JSON.stringify(process.argv.slice(2)));\n', - ); - chmodSync(join(projectRoot, 'node_modules', 'demo', 'index.js'), 0o755); - writeFileSync( - join(projectRoot, 'node_modules', '.bin', 'demo'), - [ - '#!/bin/sh', - 'basedir=$(dirname "$0")', - 'if [ -x "$basedir/node" ]; then', - ' exec "$basedir/node" "$basedir/../demo/index.js" "$@"', - 'else', - ' exec node "$basedir/../demo/index.js" "$@"', - 'fi', - '', - ].join('\n'), - ); - chmodSync(join(projectRoot, 'node_modules', '.bin', 'demo'), 0o755); - - const kernel = createKernel({ - filesystem: new NodeFileSystem({ root: projectRoot }), - }); - await kernel.mount(createWasmVmRuntime({ commandDirs: BRIDGE_COMMAND_DIRS })); - await kernel.mount(createNodeRuntime()); - ctx = { - kernel, - vfs: new NodeFileSystem({ root: projectRoot }), - dispose: () => kernel.dispose(), - }; - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execFileSync } = require('child_process'); - const result = execFileSync('/node_modules/.bin/demo', ['alpha', 'beta'], { - encoding: 'utf-8', - }); - process.stdout.write(result); - `], { - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(stderr).toBe(''); - expect(output.trim()).toBe(JSON.stringify(['alpha', 'beta'])); - }); - - it('execFileSync unwraps shell shims whose node entrypoint has no shebang or extension', async () => { - const projectRoot = mkdtempSync(join(tmpdir(), 'agentos-node-bin-shim-no-shebang-')); - cleanupPaths.push(projectRoot); - - mkdirSync(join(projectRoot, 'node_modules', '.bin'), { recursive: true }); - mkdirSync(join(projectRoot, 'node_modules', 'demo', 'dist', 'bin'), { recursive: true }); - writeFileSync( - join(projectRoot, 'node_modules', 'demo', 'dist', 'bin', 'demo'), - '"use strict";\nconsole.log(JSON.stringify(process.argv.slice(2)));\n', - ); - chmodSync(join(projectRoot, 'node_modules', 'demo', 'dist', 'bin', 'demo'), 0o755); - writeFileSync( - join(projectRoot, 'node_modules', '.bin', 'demo-no-shebang'), - [ - '#!/bin/sh', - 'basedir=$(dirname "$0")', - 'if [ -x "$basedir/node" ]; then', - ' exec "$basedir/node" "$basedir/../demo/dist/bin/demo" "$@"', - 'else', - ' exec node "$basedir/../demo/dist/bin/demo" "$@"', - 'fi', - '', - ].join('\n'), - ); - chmodSync(join(projectRoot, 'node_modules', '.bin', 'demo-no-shebang'), 0o755); - - const kernel = createKernel({ - filesystem: new NodeFileSystem({ root: projectRoot }), - }); - await kernel.mount(createWasmVmRuntime({ commandDirs: BRIDGE_COMMAND_DIRS })); - await kernel.mount(createNodeRuntime()); - ctx = { - kernel, - vfs: new NodeFileSystem({ root: projectRoot }), - dispose: () => kernel.dispose(), - }; - - const chunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execFileSync } = require('child_process'); - const result = execFileSync('/node_modules/.bin/demo-no-shebang', ['gamma', 'delta'], { - encoding: 'utf-8', - }); - process.stdout.write(result); - `], { - onStdout: (data) => chunks.push(data), - onStderr: (data) => stderrChunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - const stderr = stderrChunks.map(c => new TextDecoder().decode(c)).join(''); - expect(stderr).toBe(''); - expect(output.trim()).toBe(JSON.stringify(['gamma', 'delta'])); - }); -}); - -describeIf(!skipReason, 'bridge child_process exploit/abuse paths', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('child_process cannot escape to host shell', async () => { - ctx = await createBridgeIntegrationKernel(); - - // Use a command that produces different output in sandbox vs host: - // /etc/hostname exists on the host but not in the kernel VFS - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - try { - const result = execSync('cat /etc/hostname', { encoding: 'utf-8' }); - // If we get here, the command read a host-only file - console.log('ESCAPED:' + result.trim()); - } catch (e) { - // Expected: /etc/hostname doesn't exist in the sandbox VFS - console.log('sandbox:contained'); - } - `], { - onStdout: (data) => chunks.push(data), - }); - - await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - // Positive: command ran in sandbox and couldn't access host filesystem - expect(output).toContain('sandbox:contained'); - // Negative: no host data leaked - expect(output).not.toContain('ESCAPED:'); - }); - - it('child_process cannot read host filesystem', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - try { - // /etc/passwd doesn't exist in the kernel VFS - execSync('cat /etc/passwd', { encoding: 'utf-8' }); - console.log('SECURITY_BREACH'); - } catch (e) { - console.log('blocked'); - } - `], { - onStdout: (data) => chunks.push(data), - }); - - await proc.wait(); - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).not.toContain('SECURITY_BREACH'); - expect(output).toContain('blocked'); - }); - - it('child_process write goes to kernel VFS not host', async () => { - ctx = await createBridgeIntegrationKernel(); - - const chunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', ` - const { execSync } = require('child_process'); - execSync('echo "written-by-child" > /tmp/child-output.txt'); - const result = execSync('cat /tmp/child-output.txt', { encoding: 'utf-8' }); - console.log(result.trim()); - `], { - onStdout: (data) => chunks.push(data), - }); - - const code = await proc.wait(); - expect(code).toBe(0); - - const output = chunks.map(c => new TextDecoder().decode(c)).join(''); - expect(output).toContain('written-by-child'); - - // Verify the file was written to kernel VFS - const content = await ctx.vfs.readFile('/tmp/child-output.txt'); - expect(new TextDecoder().decode(content)).toContain('written-by-child'); - }); -}); diff --git a/registry/tests/kernel/ci-wasm-artifact-availability.test.ts b/registry/tests/kernel/ci-wasm-artifact-availability.test.ts deleted file mode 100644 index 5f4c42066..000000000 --- a/registry/tests/kernel/ci-wasm-artifact-availability.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * CI guard for cross-runtime network Wasm artifacts. - * - * The cross-runtime network suite now uses first-party command artifacts only. - * It may skip locally when those binaries are absent, but CI must fail before - * that suite can silently disappear behind skip guards. - */ - -import { describe, it, expect } from 'vitest'; -import { existsSync } from 'node:fs'; -import { join } from 'node:path'; -import { COMMANDS_DIR, itIf } from './helpers.ts'; - -const REQUIRED_ARTIFACTS = [ - { - label: 'Wasm command directory', - path: COMMANDS_DIR, - buildStep: 'run `make wasm` in `native/`', - }, - { - label: 'curl WASM binary', - path: join(COMMANDS_DIR, 'curl'), - buildStep: 'run `make wasm` in `native/`', - }, -] as const; - -function formatMissingArtifacts(): string { - return REQUIRED_ARTIFACTS - .filter((artifact) => !existsSync(artifact.path)) - .map((artifact) => `- ${artifact.label}: missing at ${artifact.path} (${artifact.buildStep})`) - .join('\n'); -} - -describe('Kernel cross-runtime CI Wasm artifact availability', () => { - itIf(Boolean(process.env.CI), 'requires cross-runtime Wasm fixtures in CI', () => { - const missing = formatMissingArtifacts(); - expect( - missing, - missing === '' - ? undefined - : `Missing required Wasm artifacts in CI:\n${missing}`, - ).toBe(''); - }); -}); diff --git a/registry/tests/kernel/cross-runtime-network.test.ts b/registry/tests/kernel/cross-runtime-network.test.ts deleted file mode 100644 index 9c8fe1abb..000000000 --- a/registry/tests/kernel/cross-runtime-network.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Cross-runtime network integration tests. - * - * These suites stay on the runtime matrix that currently routes through the - * shared kernel transport path in this repo: guest Node.js and guest WASM - * commands. They intentionally use shipped first-party command artifacts so the - * suite stays runnable without optional `native/c` fixture builds. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { existsSync } from 'node:fs'; -import { createServer as createHttpServer } from 'node:http'; -import { resolve } from 'node:path'; -import { createServer as createNetServer } from 'node:net'; -import { - describeIf, - COMMANDS_DIR, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult, Kernel } from './helpers.ts'; - -const WASM_CURL = resolve(COMMANDS_DIR, 'curl'); - -function skipReasonNetwork(): string | false { - const wasmSkipReason = skipUnlessWasmBuilt(); - if (wasmSkipReason) return wasmSkipReason; - if (!existsSync(WASM_CURL)) { - return `curl WASM binary not found at ${WASM_CURL} — rebuild registry command artifacts`; - } - return false; -} - -interface RunningGuestProgram { - process: ReturnType; - stdoutChunks: Uint8Array[]; - stderrChunks: Uint8Array[]; - getExitCode: () => number | null; -} - -function decodeChunks(chunks: Uint8Array[]): string { - return chunks.map((chunk) => new TextDecoder().decode(chunk)).join(''); -} - -function spawnGuestNodeProgram( - kernel: Kernel, - code: string, -): RunningGuestProgram { - const stdoutChunks: Uint8Array[] = []; - const stderrChunks: Uint8Array[] = []; - let exitCode: number | null = null; - const process = kernel.spawn('node', ['-e', code], { - onStdout: (chunk) => stdoutChunks.push(chunk), - onStderr: (chunk) => stderrChunks.push(chunk), - }); - void process.wait().then((code) => { - exitCode = code; - }); - return { - process, - stdoutChunks, - stderrChunks, - getExitCode: () => exitCode, - }; -} - -async function runGuestNodeProgram( - kernel: Kernel, - code: string, -): Promise<{ exitCode: number; stdout: string; stderr: string }> { - const program = spawnGuestNodeProgram(kernel, code); - const exitCode = await program.process.wait(); - return { - exitCode, - stdout: decodeChunks(program.stdoutChunks), - stderr: decodeChunks(program.stderrChunks), - }; -} - -describeIf(!skipReasonNetwork(), 'cross-runtime network integration', { timeout: 30_000 }, () => { - let ctx: IntegrationKernelResult; - let hostNetServer: ReturnType | undefined; - let hostHttpServer: ReturnType | undefined; - - afterEach(async () => { - if (hostNetServer) { - await new Promise((resolveClose) => hostNetServer!.close(() => resolveClose())); - hostNetServer = undefined; - } - if (hostHttpServer) { - await new Promise((resolveClose) => hostHttpServer!.close(() => resolveClose())); - hostHttpServer = undefined; - } - await ctx?.dispose(); - }); - - it('Node.js net.connect resolves localhost and exchanges TCP data over guest loopback', async () => { - hostNetServer = createNetServer((socket) => { - socket.on('data', (chunk) => { - socket.end(`pong:${chunk.toString()}`); - }); - }); - await new Promise((resolveListen) => { - hostNetServer!.listen(0, '127.0.0.1', () => resolveListen()); - }); - const port = (hostNetServer.address() as import('node:net').AddressInfo).port; - ctx = await createIntegrationKernel({ - runtimes: ['node'], - loopbackExemptPorts: [port], - }); - - const clientResult = await runGuestNodeProgram( - ctx.kernel, - [ - "const dns = require('dns');", - "const net = require('net');", - 'async function main() {', - " const lookup = await dns.promises.lookup('localhost', { family: 4 });", - ' const reply = await new Promise((resolve, reject) => {', - ` const client = net.connect({ host: 'localhost', port: ${port}, family: 4 }, () => {`, - " client.write('ping');", - ' });', - " client.on('data', (chunk) => {", - ' resolve(chunk.toString());', - ' client.end();', - ' });', - " client.on('error', reject);", - ' });', - ' console.log(JSON.stringify({ lookup, reply }));', - '}', - 'main().catch((error) => {', - ' console.error(error);', - ' process.exit(1);', - '});', - ].join('\n'), - ); - - expect(clientResult.exitCode).toBe(0); - expect(clientResult.stderr).toBe(''); - const parsed = JSON.parse(clientResult.stdout.trim()) as { - lookup: { address: string }; - reply: string; - }; - expect(parsed.lookup.address).toBe('127.0.0.1'); - expect(parsed.reply).toBe('pong:ping'); - }); - - it('Wasm curl reaches a guest Node.js HTTP server over 127.0.0.1 loopback', async () => { - hostHttpServer = createHttpServer((req, res) => { - res.writeHead(200, { 'content-type': 'application/json' }); - res.end( - JSON.stringify({ - host: req.headers.host ?? null, - url: req.url, - runtime: 'host', - }), - ); - }); - await new Promise((resolveListen) => { - hostHttpServer!.listen(0, '127.0.0.1', () => resolveListen()); - }); - const port = (hostHttpServer.address() as import('node:net').AddressInfo).port; - ctx = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - loopbackExemptPorts: [port], - }); - - const result = await ctx.kernel.exec(`curl -s http://127.0.0.1:${port}/loopback`); - - expect(result.exitCode).toBe(0); - expect(result.stderr).not.toContain('socket error'); - expect(result.stderr).not.toContain('ERROR'); - expect(result.stdout).toContain('"runtime":"host"'); - expect(result.stdout).toContain('"url":"/loopback"'); - expect(result.stdout).toContain(`"host":"127.0.0.1:${port}"`); - }); - - it('Wasm curl resolves localhost and reaches the loopback fixture through the same kernel path', async () => { - hostHttpServer = createHttpServer((req, res) => { - res.writeHead(200, { 'content-type': 'application/json' }); - res.end( - JSON.stringify({ - host: req.headers.host ?? null, - url: req.url, - runtime: 'host', - }), - ); - }); - await new Promise((resolveListen) => { - hostHttpServer!.listen(0, '127.0.0.1', () => resolveListen()); - }); - const port = (hostHttpServer.address() as import('node:net').AddressInfo).port; - ctx = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - loopbackExemptPorts: [port], - }); - - const result = await ctx.kernel.exec(`curl -s http://localhost:${port}/dns`); - - expect(result.exitCode).toBe(0); - expect(result.stderr).not.toContain('socket error'); - expect(result.stderr).not.toContain('ERROR'); - expect(result.stdout).toContain('"runtime":"host"'); - expect(result.stdout).toContain('"url":"/dns"'); - expect(result.stdout).toContain(`"host":"localhost:${port}"`); - }); -}); diff --git a/registry/tests/kernel/cross-runtime-pipes.test.ts b/registry/tests/kernel/cross-runtime-pipes.test.ts deleted file mode 100644 index 4db8328b2..000000000 --- a/registry/tests/kernel/cross-runtime-pipes.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Cross-runtime pipe tests. - * - * Tests kernel pipe infrastructure: FD allocation, pipe read/write, - * SpawnOptions FD overrides, cross-driver data flow, and EOF propagation. - * - * Integration tests with real WasmVM+Node are skipped when WASM binary - * is not built. - * - * NOTE: The kernel-level unit tests (MockRuntimeDriver, no WASM) are kept - * in the legacy runtime repo. Only the WasmVM-dependent integration tests - * are included here. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { Kernel } from './helpers.ts'; - -// --------------------------------------------------------------------------- -// Integration tests with real WasmVM + Node (skipped if WASM not built) -// --------------------------------------------------------------------------- - -describeIf(!skipUnlessWasmBuilt(), 'cross-runtime pipes (WasmVM + Node)', () => { - let kernel: Kernel; - let dispose: () => Promise; - - afterEach(async () => { - await dispose?.(); - }); - - it('WasmVM echo | cat pipe works', async () => { - ({ kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm'] })); - const result = await kernel.exec('echo hello | cat', { timeout: 15000 }); - expect(result.stdout.trim()).toBe('hello'); - expect(result.exitCode).toBe(0); - }, 30000); - - it('WasmVM echo | node -e pipe works', async () => { - ({ kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] })); - const script = 'let d="";process.stdin.on("data",c=>d+=c);process.stdin.on("end",()=>process.stdout.write(d.toUpperCase()))'; - const result = await kernel.exec(`echo hello | node -e '${script}'`, { timeout: 15000 }); - expect(result.stdout.trim()).toBe('HELLO'); - expect(result.exitCode).toBe(0); - }, 30000); - - it('WasmVM echo | WasmVM wc -c pipe works', async () => { - ({ kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm'] })); - const result = await kernel.exec('echo hello | wc -c', { timeout: 15000 }); - // "hello\n" is 6 bytes - expect(result.stdout.trim()).toBe('6'); - expect(result.exitCode).toBe(0); - }, 30000); -}); diff --git a/registry/tests/kernel/cross-runtime-terminal.test.ts b/registry/tests/kernel/cross-runtime-terminal.test.ts deleted file mode 100644 index 5a4f14b74..000000000 --- a/registry/tests/kernel/cross-runtime-terminal.test.ts +++ /dev/null @@ -1,267 +0,0 @@ -/** - * Cross-runtime terminal tests for the post-Python WasmVM + Node surface. - * - * Mounts WasmVM + Node into the same kernel and verifies interactive output - * through TerminalHarness. - * - * Gated: WasmVM binaries must be built. - * - * Uses the registry-owned TerminalHarness exported through shared helpers. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, - TerminalHarness, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -/** brush-shell interactive prompt. */ -const PROMPT = 'sh-0.4$ '; - -const wasmSkip = skipUnlessWasmBuilt(); - -/** - * Find a line in the screen output that exactly matches the expected text. - * Excludes lines containing the command echo (prompt line). - */ -function findOutputLine(screen: string, expected: string): string | undefined { - return screen.split('\n').find( - (l) => l.trim() === expected && !l.includes(PROMPT), - ); -} - -// --------------------------------------------------------------------------- -// Node cross-runtime terminal tests -// --------------------------------------------------------------------------- - -describeIf(!wasmSkip, 'cross-runtime terminal: node', () => { - let harness: TerminalHarness; - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await harness?.dispose(); - await ctx?.dispose(); - }); - - it('node -e stdout appears as actual output (not just command echo)', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - // Use XYZZY. Unique string that does NOT appear in the command text. - await harness.type('node -e "console.log(\'XYZZY\')"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - // Verify output on its own line (not just embedded in command echo) - expect(findOutputLine(screen, 'XYZZY')).toBeDefined(); - // Verify prompt returned - const lines = screen.split('\n'); - expect(lines[lines.length - 1]).toBe(PROMPT); - }, 15_000); - - it('node -e multiple console.log lines appear in order', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "console.log(\'AAA\'); console.log(\'BBB\')"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(findOutputLine(screen, 'AAA')).toBeDefined(); - expect(findOutputLine(screen, 'BBB')).toBeDefined(); - - // Verify order: AAA before BBB - const aaaIdx = screen.indexOf('AAA'); - const bbbIdx = screen.indexOf('BBB'); - // Both must appear after command echo - const promptIdx = screen.indexOf(PROMPT); - expect(aaaIdx).toBeGreaterThan(promptIdx); - expect(bbbIdx).toBeGreaterThan(aaaIdx); - }, 15_000); - - it('diagnostic WARN output does not suppress real stdout', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "console.log(\'HELLO\')"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - // Some runtime combinations emit an incidental WARN line here while others - // do not. The contract is that stdout remains visible either way. - expect(findOutputLine(screen, 'HELLO')).toBeDefined(); - }, 15_000); - - it('^C during node -e. Shell survives and prompt returns', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - // Start a long-running node process - harness.shell.write('node -e "setTimeout(() => {}, 60000)"\n'); - - // Give it a moment to start, then send ^C - await new Promise((r) => setTimeout(r, 500)); - harness.shell.write('\x03'); - - // Wait for prompt to return - await harness.waitFor(PROMPT, 2, 10_000); - - // Verify shell is still alive. Type another command. - await harness.type('echo alive\n'); - await harness.waitFor('alive', 1, 5_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('alive'); - }, 20_000); -}); - -// --------------------------------------------------------------------------- -// Node kernel.exec() stdout tests -// --------------------------------------------------------------------------- - -describeIf(!wasmSkip, 'cross-runtime exec: node', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('kernel.exec node -e stdout contains output', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "console.log(42)"'); - expect(result.stdout).toContain('42'); - expect(result.exitCode).toBe(0); - }); - - it('kernel.exec node -e multi-line stdout in order', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec( - 'node -e "console.log(1); console.log(2)"', - ); - const lines = result.stdout.split('\n').map((l: string) => l.trim()).filter(Boolean); - expect(lines).toContain('1'); - expect(lines).toContain('2'); - expect(lines.indexOf('1')).toBeLessThan(lines.indexOf('2')); - }); - - it('kernel.exec node -e large stdout does not truncate', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - // Generate >64KB of output (100 lines of 700 chars each = ~70KB) - const code = `for(let i=0;i<100;i++) console.log('L'+i+' '+'x'.repeat(700))`; - const result = await ctx.kernel.exec(`node -e "${code}"`); - // Verify first and last lines present - expect(result.stdout).toContain('L0 '); - expect(result.stdout).toContain('L99 '); - expect(result.exitCode).toBe(0); - }, 15_000); -}); - -// --------------------------------------------------------------------------- -// Node kernel.exec() stderr tests -// --------------------------------------------------------------------------- - -describeIf(!wasmSkip, 'cross-runtime exec: node stderr', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('kernel.exec node -e with undefined var returns ReferenceError on stderr', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "lskdjf"'); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toContain('ReferenceError'); - }); - - it('kernel.exec node -e throw Error returns message on stderr', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "throw new Error(\'boom\')"'); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toContain('boom'); - }); - - it('kernel.exec node -e with syntax error returns SyntaxError on stderr', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "({"'); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toContain('SyntaxError'); - }); - - it('kernel.exec node -e console.error returns stderr', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "console.error(\'ERRMSG\')"'); - expect(result.stderr).toContain('ERRMSG'); - expect(result.exitCode).toBe(0); - }); -}); - -// --------------------------------------------------------------------------- -// Node cross-runtime terminal: stderr tests -// --------------------------------------------------------------------------- - -describeIf(!wasmSkip, 'cross-runtime terminal: node stderr', () => { - let harness: TerminalHarness; - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await harness?.dispose(); - await ctx?.dispose(); - }); - - it('node -e with undefined var shows ReferenceError on terminal', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "lskdjf"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('ReferenceError'); - }, 15_000); - - it('node -e throw Error shows error message on terminal', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "throw new Error(\'boom\')"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('boom'); - }, 15_000); - - it('node -e syntax error shows SyntaxError on terminal', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "({"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('SyntaxError'); - }, 15_000); - - it('stderr callback chain: NodeRuntimeDriver -> ctx.onStderr -> PTY slave', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - // console.error goes through onStdio -> ctx.onStderr -> PTY write - await harness.type('node -e "console.error(\'STDERRTEST\')"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('STDERRTEST'); - }, 15_000); -}); diff --git a/registry/tests/kernel/ctrl-c-shell-behavior.test.ts b/registry/tests/kernel/ctrl-c-shell-behavior.test.ts deleted file mode 100644 index 08af122ec..000000000 --- a/registry/tests/kernel/ctrl-c-shell-behavior.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Ctrl+C at shell prompt behavior tests. - * - * Verifies that pressing Ctrl+C (SIGINT) at the interactive shell prompt: - * - Echoes ^C and shows a fresh prompt - * - Does NOT kill the shell process - * - Discards any partial input on the current line - * - Allows typing new commands afterward - * - * Uses real WasmVM brush-shell, gated by skipUnlessWasmBuilt(). - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, - TerminalHarness, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const PROMPT = 'sh-0.4$ '; -const wasmSkip = skipUnlessWasmBuilt(); - -describeIf(!wasmSkip, 'Ctrl+C at shell prompt', () => { - let harness: TerminalHarness; - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await harness?.dispose(); - await ctx?.dispose(); - }); - - it('partial input + ^C shows ^C, discards input, fresh prompt', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - harness.shell.write('partia\x03'); - await harness.waitFor(PROMPT, 2, 2_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('^C'); - - // Fresh prompt on new line - const lines = screen.split('\n'); - expect(lines[lines.length - 1]).toBe(PROMPT); - }, 10_000); - - it('empty prompt + ^C shows ^C, fresh prompt, no error', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - harness.shell.write('\x03'); - await harness.waitFor(PROMPT, 2, 2_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('^C'); - }, 10_000); - - it('after ^C at prompt, shell accepts and executes the next command', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - // Send ^C, then a real command - harness.shell.write('partial\x03'); - await harness.waitFor(PROMPT, 2, 2_000); - - await harness.type('echo hello\n'); - await harness.waitFor('hello', 1, 5_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('hello'); - }, 15_000); - - it('multiple ^C in a row does not crash the shell', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - - // Rapid-fire ^C - harness.shell.write('\x03'); - await harness.waitFor(PROMPT, 2, 2_000); - harness.shell.write('\x03'); - await harness.waitFor(PROMPT, 3, 2_000); - - // Shell still alive - await harness.type('echo still-alive\n'); - await harness.waitFor('still-alive', 1, 5_000); - }, 15_000); -}); diff --git a/registry/tests/kernel/dispose-behavior.test.ts b/registry/tests/kernel/dispose-behavior.test.ts deleted file mode 100644 index 299b08e3f..000000000 --- a/registry/tests/kernel/dispose-behavior.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Integration tests for kernel.dispose() with active processes. - * - * Verifies that dispose terminates running processes across WasmVM and Node - * runtimes, cleans up after crashes, disposes timers, propagates pipe EOF, - * and supports idempotent double-dispose. - * - * The pure kernel unit tests (MockRuntimeDriver, no WASM) remain in - * the legacy runtime repo. Only WasmVM-dependent integration tests are here. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createKernel, - createNodeRuntime, - createIntegrationKernel, - skipUnlessWasmBuilt, - createInMemoryFileSystem, -} from './helpers.ts'; -import type { Kernel } from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'dispose with active processes (integration)', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('dispose terminates active WasmVM sleep process within 5s', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - - // Spawn a long-running sleep. Would hang for 60s without dispose. - const proc = ctx.kernel.spawn('sleep', ['60']); - expect(proc.pid).toBeGreaterThan(0); - - const start = Date.now(); - await ctx.dispose(); - const elapsed = Date.now() - start; - - expect(elapsed).toBeLessThan(5000); - }, 10_000); - - it('dispose terminates active Node setTimeout process within 5s', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - // Spawn a Node process that hangs for 60s - const proc = ctx.kernel.spawn('node', ['-e', 'setTimeout(()=>{},60000)']); - expect(proc.pid).toBeGreaterThan(0); - - const start = Date.now(); - await ctx.dispose(); - const elapsed = Date.now() - start; - - expect(elapsed).toBeLessThan(5000); - }, 10_000); - - it('dispose terminates processes in BOTH WasmVM and Node simultaneously', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - // Spawn long-running processes in both runtimes - const wasmProc = ctx.kernel.spawn('sleep', ['60']); - const nodeProc = ctx.kernel.spawn('node', ['-e', 'setTimeout(()=>{},60000)']); - - expect(wasmProc.pid).toBeGreaterThan(0); - expect(nodeProc.pid).toBeGreaterThan(0); - expect(wasmProc.pid).not.toBe(nodeProc.pid); - - const start = Date.now(); - await ctx.dispose(); - const elapsed = Date.now() - start; - - expect(elapsed).toBeLessThan(5000); - }, 10_000); -}); diff --git a/registry/tests/kernel/e2e-concurrently.test.ts b/registry/tests/kernel/e2e-concurrently.test.ts deleted file mode 100644 index 4d983eb42..000000000 --- a/registry/tests/kernel/e2e-concurrently.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * E2E test: concurrently package runs parallel processes through kernel. - * - * Verifies that concurrently spawns multiple child processes simultaneously - * through the kernel, testing: - * 1. Kernel process table concurrent PID allocation without conflicts - * 2. Kernel command registry handling multiple simultaneous resolves - * 3. WasmVM running multiple workers in parallel - * 4. Kernel pipe multiplexing stdout from multiple processes - * - * Pre-installs concurrently on host via npm, then mounts NodeFileSystem - * so the kernel finds the binary in node_modules/.bin/. - */ - -import { mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { execSync } from 'node:child_process'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { - describeIf, - COMMANDS_DIR, - createKernel, - NodeFileSystem, - createWasmVmRuntime, - createNodeRuntime, - skipUnlessWasmBuilt, -} from './helpers.ts'; - -const wasmSkip = skipUnlessWasmBuilt(); - -/** Check if npm registry is reachable (5s timeout). */ -async function checkNetwork(): Promise { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5_000); - await fetch('https://registry.npmjs.org/', { - signal: controller.signal, - method: 'HEAD', - }); - clearTimeout(timeout); - return false; - } catch { - return 'network not available (cannot reach npm registry)'; - } -} - -const skipReason = wasmSkip || (await checkNetwork()); - -describeIf(!skipReason, 'e2e concurrently through kernel', () => { - let tempDir: string; - - // Pre-install concurrently on host so kernel has node_modules available - beforeAll(async () => { - tempDir = await mkdtemp(path.join(tmpdir(), 'kernel-concurrently-')); - - await writeFile( - path.join(tempDir, 'package.json'), - JSON.stringify({ - name: 'test-concurrently', - private: true, - dependencies: { concurrently: '^8.0.0' }, - }), - ); - - execSync('npm install --ignore-scripts', { - cwd: tempDir, - stdio: 'pipe', - timeout: 60_000, - }); - }, 90_000); - - afterAll(async () => { - if (tempDir) { - await rm(tempDir, { recursive: true, force: true }); - } - }); - - /** Create kernel with NodeFileSystem rooted at temp dir. */ - async function createConcurrentlyKernel() { - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - - return { kernel, dispose: () => kernel.dispose() }; - } - - it( - 'concurrently runs two echo commands in parallel', - async () => { - const { kernel, dispose } = await createConcurrentlyKernel(); - - try { - const result = await kernel.exec( - 'node /node_modules/concurrently/dist/bin/concurrently.js "echo hello" "echo world"', - { cwd: '/' }, - ); - expect(result.stdout).toContain('hello'); - expect(result.stdout).toContain('world'); - } finally { - await dispose(); - } - }, - 30_000, - ); - - it( - 'concurrently runs processes with shell operators', - async () => { - const { kernel, dispose } = await createConcurrentlyKernel(); - - try { - const result = await kernel.exec( - 'node /node_modules/concurrently/dist/bin/concurrently.js "echo one && echo two" "echo three"', - { cwd: '/' }, - ); - expect(result.stdout).toContain('one'); - expect(result.stdout).toContain('two'); - expect(result.stdout).toContain('three'); - } finally { - await dispose(); - } - }, - 30_000, - ); - - it( - 'concurrently --kill-others-on-fail returns non-zero on child failure', - async () => { - const { kernel, dispose } = await createConcurrentlyKernel(); - - try { - const result = await kernel.exec( - 'node /node_modules/concurrently/dist/bin/concurrently.js --success all --kill-others-on-fail "echo success" "exit 1"', - { cwd: '/' }, - ); - expect(result.exitCode).not.toBe(0); - } finally { - await dispose(); - } - }, - 30_000, - ); - - it( - 'concurrent process spawns get unique PIDs', - async () => { - const { kernel, dispose } = await createConcurrentlyKernel(); - - try { - // Spawn multiple processes concurrently through kernel - const procs = [ - kernel.spawn('echo', ['pid-a'], { cwd: '/' }), - kernel.spawn('echo', ['pid-b'], { cwd: '/' }), - kernel.spawn('echo', ['pid-c'], { cwd: '/' }), - ]; - - const pids = procs.map((p) => p.pid); - const uniquePids = new Set(pids); - expect(uniquePids.size).toBe(3); - - // Wait for all to complete - await Promise.all(procs.map((p) => p.wait())); - } finally { - await dispose(); - } - }, - 30_000, - ); -}); diff --git a/registry/tests/kernel/e2e-nextjs-build.test.ts b/registry/tests/kernel/e2e-nextjs-build.test.ts deleted file mode 100644 index cd58224c1..000000000 --- a/registry/tests/kernel/e2e-nextjs-build.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * E2E test: Next.js build through kernel. - * - * Verifies that 'next build' completes through the kernel on the repo-owned - * Next.js fixture, proving the kernel can handle a complex real-world - * build pipeline: - * 1. Host-side package install populates node_modules - * 2. NodeFileSystem mounts the project into the kernel - * 3. kernel.exec('node /run-next-build.cjs') runs Next.js through kernel - * 4. Build output directory exists after completion - * - * Known workarounds applied: - * - run-next-build.cjs preloads the fixture's WASM-compatible Next shim - * before invoking Next's build API. - * - The checked-in fixture writes normal Next.js build output to `.next` - */ - -import { cp, mkdtemp, rm } from 'node:fs/promises'; -import { execSync } from 'node:child_process'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { - describeIf, - COMMANDS_DIR, - createKernel, - NodeFileSystem, - createWasmVmRuntime, - createNodeRuntime, - skipUnlessWasmBuilt, -} from './helpers.ts'; - -const wasmSkip = skipUnlessWasmBuilt(); -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const NEXTJS_FIXTURE_DIR = path.resolve(__dirname, '../projects/nextjs-pass'); - -/** Check if npm registry is reachable (5s timeout). */ -async function checkNetwork(): Promise { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5_000); - await fetch('https://registry.npmjs.org/', { - signal: controller.signal, - method: 'HEAD', - }); - clearTimeout(timeout); - return false; - } catch { - return 'network not available (cannot reach npm registry)'; - } -} - -const skipReason = wasmSkip || (await checkNetwork()); - -describeIf(!skipReason, 'e2e Next.js build through kernel', () => { - let tempDir: string; - - // Copy the checked-in fixture so the build can mutate /.next without touching the repo. - beforeAll(async () => { - tempDir = await mkdtemp(path.join(tmpdir(), 'kernel-nextjs-build-')); - await cp(NEXTJS_FIXTURE_DIR, tempDir, { recursive: true }); - - // Match the registry fixture install path instead of doing a slow ad hoc npm install. - execSync('pnpm install --ignore-workspace --prefer-offline', { - cwd: tempDir, - stdio: 'pipe', - timeout: 60_000, - }); - }, 90_000); - - afterAll(async () => { - if (tempDir) { - await rm(tempDir, { recursive: true, force: true }); - } - }); - - it( - 'next build produces output directory', - async () => { - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - - try { - const result = await kernel.exec('node /run-next-build.cjs', { - cwd: '/', - env: { - NEXT_TELEMETRY_DISABLED: '1', - }, - }); - - expect( - result.exitCode, - `stdout:\n${result.stdout}\nstderr:\n${result.stderr}`, - ).toBe(0); - - // Some fixtures may emit a static export, but the checked-in Next.js - // kernel fixture currently writes its build artifacts to `.next`. - const outExists = await vfs - .stat('/out') - .then(() => true) - .catch(() => false); - - // Fallback: check .next/ if out/ doesn't exist (non-export mode) - const dotNextExists = await vfs - .stat('/.next') - .then(() => true) - .catch(() => false); - - expect(outExists || dotNextExists).toBe(true); - } finally { - await kernel.dispose(); - } - }, - 120_000, - ); -}); diff --git a/registry/tests/kernel/e2e-npm-install.test.ts b/registry/tests/kernel/e2e-npm-install.test.ts deleted file mode 100644 index 7a8365901..000000000 --- a/registry/tests/kernel/e2e-npm-install.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * E2E test: npm install a package through kernel. - * - * Verifies the full package installation flow: - * 1. Write package.json with left-pad dependency to temp dir - * 2. kernel.exec('npm install') downloads and extracts the package - * 3. Installed package is usable via require() in kernel Node - * - * Uses NodeFileSystem rooted at a temp directory so npm's filesystem - * operations (mkdir, symlink, writeFile) hit the real host filesystem. - */ - -import { mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { describe, expect, it } from 'vitest'; -import { - describeIf, - COMMANDS_DIR, - createKernel, - NodeFileSystem, - createWasmVmRuntime, - createNodeRuntime, - skipUnlessWasmBuilt, -} from './helpers.ts'; - -const wasmSkip = skipUnlessWasmBuilt(); - -/** Check if npm registry is reachable (5s timeout). */ -async function checkNetwork(): Promise { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5_000); - await fetch('https://registry.npmjs.org/', { - signal: controller.signal, - method: 'HEAD', - }); - clearTimeout(timeout); - return false; - } catch { - return 'network not available (cannot reach npm registry)'; - } -} - -const skipReason = wasmSkip || (await checkNetwork()); - -describeIf(!skipReason, 'e2e npm install through kernel', () => { - it( - 'npm install installs left-pad and it is usable by node', - async () => { - const tempDir = await mkdtemp( - path.join(tmpdir(), 'kernel-npm-install-'), - ); - - try { - // Write minimal package.json to temp dir - await writeFile( - path.join(tempDir, 'package.json'), - JSON.stringify({ - name: 'test-npm-install', - private: true, - dependencies: { 'left-pad': '1.3.0' }, - }), - ); - - // Kernel with NodeFileSystem rooted at temp dir - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - - try { - // Run npm install through kernel - const installResult = await kernel.exec('npm install', { - cwd: '/', - }); - expect(installResult.exitCode).toBe(0); - - // Verify node_modules/left-pad/ exists in the VFS - const stat = await vfs.stat('/node_modules/left-pad'); - expect(stat.isDirectory).toBe(true); - - // Verify installed package is usable via require() - const result = await kernel.exec( - `node -e "console.log(require('left-pad')('hi', 10))"`, - { cwd: '/' }, - ); - expect(result.stdout.trimEnd()).toBe(' hi'); - } finally { - await kernel.dispose(); - } - } finally { - await rm(tempDir, { recursive: true, force: true }); - } - }, - 30_000, - ); -}); diff --git a/registry/tests/kernel/e2e-npm-lifecycle.test.ts b/registry/tests/kernel/e2e-npm-lifecycle.test.ts deleted file mode 100644 index 3f8cb9ce4..000000000 --- a/registry/tests/kernel/e2e-npm-lifecycle.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * E2E test: npm postinstall lifecycle scripts through kernel. - * - * Verifies that npm lifecycle hooks (preinstall, postinstall) route through - * the kernel command registry to WasmVM shell: - * 1. npm install reads package.json lifecycle scripts - * 2. npm spawns 'sh -c "echo ..."' for each lifecycle hook - * 3. child_process.spawn routes through kernel -> WasmVM shell - * 4. Shell commands write marker files to kernel VFS - * - * Uses NodeFileSystem rooted at a temp directory so npm's filesystem - * operations hit the real host filesystem. - */ - -import { mkdtemp, rm, writeFile, mkdir } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { describe, expect, it } from 'vitest'; -import { - describeIf, - COMMANDS_DIR, - createKernel, - NodeFileSystem, - createWasmVmRuntime, - createNodeRuntime, - skipUnlessWasmBuilt, -} from './helpers.ts'; - -const wasmSkip = skipUnlessWasmBuilt(); - -/** Check if npm registry is reachable (5s timeout). */ -async function checkNetwork(): Promise { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5_000); - await fetch('https://registry.npmjs.org/', { - signal: controller.signal, - method: 'HEAD', - }); - clearTimeout(timeout); - return false; - } catch { - return 'network not available (cannot reach npm registry)'; - } -} - -const skipReason = wasmSkip || (await checkNetwork()); - -describeIf(!skipReason, 'e2e npm lifecycle scripts through kernel', () => { - it( - 'postinstall script writes marker file during npm install', - async () => { - const tempDir = await mkdtemp( - path.join(tmpdir(), 'kernel-npm-lifecycle-'), - ); - - try { - // Create /tmp inside the project root so lifecycle scripts can write there - await mkdir(path.join(tempDir, 'tmp'), { recursive: true }); - - // Package.json with postinstall lifecycle script - await writeFile( - path.join(tempDir, 'package.json'), - JSON.stringify({ - name: 'test-npm-lifecycle', - private: true, - scripts: { - postinstall: 'echo POSTINSTALL_RAN > /tmp/marker.txt', - }, - dependencies: { 'left-pad': '1.3.0' }, - }), - ); - - // Kernel with NodeFileSystem rooted at temp dir - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - - try { - const result = await kernel.exec('npm install', { cwd: '/' }); - expect(result.exitCode).toBe(0); - - // Verify postinstall marker was written through WasmVM shell - const markerBytes = await vfs.readFile('/tmp/marker.txt'); - const marker = new TextDecoder().decode(markerBytes).trim(); - expect(marker).toBe('POSTINSTALL_RAN'); - } finally { - await kernel.dispose(); - } - } finally { - await rm(tempDir, { recursive: true, force: true }); - } - }, - 45_000, - ); - - it( - 'preinstall script writes marker file before dependencies are fetched', - async () => { - const tempDir = await mkdtemp( - path.join(tmpdir(), 'kernel-npm-lifecycle-pre-'), - ); - - try { - // Create /tmp inside the project root - await mkdir(path.join(tempDir, 'tmp'), { recursive: true }); - - // Package.json with preinstall lifecycle script - await writeFile( - path.join(tempDir, 'package.json'), - JSON.stringify({ - name: 'test-npm-lifecycle-pre', - private: true, - scripts: { - preinstall: 'echo PREINSTALL_RAN > /tmp/pre-marker.txt', - }, - dependencies: { 'left-pad': '1.3.0' }, - }), - ); - - // Kernel with NodeFileSystem rooted at temp dir - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - - try { - const result = await kernel.exec('npm install', { cwd: '/' }); - expect(result.exitCode).toBe(0); - - // Verify preinstall marker was written through WasmVM shell - const markerBytes = await vfs.readFile('/tmp/pre-marker.txt'); - const marker = new TextDecoder().decode(markerBytes).trim(); - expect(marker).toBe('PREINSTALL_RAN'); - } finally { - await kernel.dispose(); - } - } finally { - await rm(tempDir, { recursive: true, force: true }); - } - }, - 45_000, - ); -}); diff --git a/registry/tests/kernel/e2e-npm-scripts.test.ts b/registry/tests/kernel/e2e-npm-scripts.test.ts deleted file mode 100644 index 97a8b5a32..000000000 --- a/registry/tests/kernel/e2e-npm-scripts.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * E2E test: npm run scripts execute shell commands through kernel. - * - * Exercises the full round-trip: kernel.exec('npm run greet') - * -> sh -c 'npm run greet' (WasmVM shell) - * -> proc_spawn('npm', ...) (kernel routes to Node driver) - * -> npm reads package.json, spawns 'sh -c "echo hello world"' - * -> child_process routes through kernel -> WasmVM shell -> output - */ - -import { describe, expect, it } from 'vitest'; -import { describeIf, createIntegrationKernel, skipUnlessWasmBuilt } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'e2e npm run scripts through kernel', () => { - it('npm run greet echoes hello world', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.writeFile( - '/package.json', - JSON.stringify({ - name: 'test-npm-scripts', - scripts: { greet: 'echo hello world' }, - }), - ); - - const result = await kernel.exec('npm run greet', { cwd: '/' }); - expect(result.stdout).toContain('hello world'); - } finally { - await dispose(); - } - }, 30_000); - - it('npm run count runs sequential shell commands (&&)', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.writeFile( - '/package.json', - JSON.stringify({ - name: 'test-npm-scripts', - scripts: { count: 'echo one && echo two && echo three' }, - }), - ); - - const result = await kernel.exec('npm run count', { cwd: '/' }); - expect(result.stdout).toContain('one'); - expect(result.stdout).toContain('two'); - expect(result.stdout).toContain('three'); - } finally { - await dispose(); - } - }, 30_000); - - it('npm run env-check passes npm env variables through shell', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.writeFile( - '/package.json', - JSON.stringify({ - name: 'my-cool-project', - scripts: { 'env-check': 'echo $npm_package_name' }, - }), - ); - - const result = await kernel.exec('npm run env-check', { cwd: '/' }); - expect(result.stdout).toContain('my-cool-project'); - } finally { - await dispose(); - } - }, 30_000); - - it('npm run nonexistent returns non-zero exit code', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.writeFile( - '/package.json', - JSON.stringify({ - name: 'test-npm-scripts', - scripts: {}, - }), - ); - - const result = await kernel.exec('npm run nonexistent', { cwd: '/' }); - expect(result.exitCode).not.toBe(0); - } finally { - await dispose(); - } - }, 30_000); -}); diff --git a/registry/tests/kernel/e2e-npm-suite.test.ts b/registry/tests/kernel/e2e-npm-suite.test.ts deleted file mode 100644 index e9f5580bf..000000000 --- a/registry/tests/kernel/e2e-npm-suite.test.ts +++ /dev/null @@ -1,344 +0,0 @@ -/** - * E2E test suite: npm operations through kernel. - * - * Covers the core npm workflow: init, install, list, run, npx. - * Tests are split into offline (no network) and online (network-dependent) - * sections, with network availability guarded by a registry check. - * - * Known limitation: npm commands that trigger the update-notifier / pacote / - * @sigstore/sign module chain fail in the V8 isolate sandbox because - * http2.constants is not yet polyfilled. This affects npm install, npm init -y, - * and npx. These tests are guarded and will pass once http2 bridge support - * is added. - */ - -import { existsSync } from 'node:fs'; -import { mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { describe, expect, it } from 'vitest'; -import { - describeIf, - COMMANDS_DIR, - createKernel, - createWasmVmRuntime, - createNodeRuntime, - createIntegrationKernel, - NodeFileSystem, - skipUnlessWasmBuilt, -} from './helpers.ts'; - -const wasmSkip = skipUnlessWasmBuilt(); - -/** Check if npm registry is reachable (5s timeout). */ -async function checkNetwork(): Promise { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5_000); - await fetch('https://registry.npmjs.org/', { - signal: controller.signal, - method: 'HEAD', - }); - clearTimeout(timeout); - return false; - } catch { - return 'network not available (cannot reach npm registry)'; - } -} - -/** - * Check if npm install works in the kernel sandbox. - * npm's pacote -> @sigstore/sign chain requires http2.constants which is not - * yet polyfilled. Returns a skip reason if npm install is broken. - */ -async function checkNpmInstallWorks(): Promise { - if (wasmSkip) return wasmSkip; - const tempDir = await mkdtemp(path.join(tmpdir(), 'kernel-npm-probe-')); - try { - await writeFile( - path.join(tempDir, 'package.json'), - JSON.stringify({ - name: 'npm-probe', - private: true, - dependencies: { 'left-pad': '1.3.0' }, - }), - ); - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - try { - const result = await kernel.exec('npm install', { cwd: '/' }); - if (existsSync(path.join(tempDir, 'node_modules', 'left-pad'))) { - return false; - } - return 'npm install fails in sandbox (http2/@sigstore/sign not polyfilled)'; - } finally { - await kernel.dispose(); - } - } finally { - await rm(tempDir, { recursive: true, force: true }); - } -} - -// --- Offline tests (no network required) --- - -describeIf(!wasmSkip, 'npm suite - offline', () => { - it('npm init -y creates package.json with default values', async () => { - const tempDir = await mkdtemp(path.join(tmpdir(), 'kernel-npm-init-')); - - try { - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - - try { - await kernel.exec('npm init -y', { cwd: '/' }); - - const exists = await vfs.exists('/package.json'); - expect(exists).toBe(true); - - const content = await vfs.readTextFile('/package.json'); - const pkg = JSON.parse(content); - expect(pkg).toHaveProperty('name'); - expect(pkg).toHaveProperty('version'); - expect(pkg.version).toBe('1.0.0'); - } finally { - await kernel.dispose(); - } - } finally { - await rm(tempDir, { recursive: true, force: true }); - } - }, 30_000); - - it('npm list shows installed packages (empty project)', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.writeFile( - '/package.json', - JSON.stringify({ - name: 'test-npm-list', - version: '1.0.0', - private: true, - }), - ); - - const result = await kernel.exec('npm list', { cwd: '/' }); - expect(result.stdout).toContain('test-npm-list'); - } finally { - await dispose(); - } - }, 30_000); - - it('npm run test executes script from package.json', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.writeFile( - '/package.json', - JSON.stringify({ - name: 'test-npm-run', - scripts: { test: 'echo npm-test-output' }, - }), - ); - - const result = await kernel.exec('npm run test', { cwd: '/' }); - expect(result.stdout).toContain('npm-test-output'); - } finally { - await dispose(); - } - }, 30_000); - - it('npm run with missing script shows error and hint', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.writeFile( - '/package.json', - JSON.stringify({ - name: 'test-npm-run-missing', - scripts: { - build: 'echo building', - start: 'echo starting', - }, - }), - ); - - const result = await kernel.exec('npm run nonexistent', { cwd: '/' }); - expect(result.exitCode).not.toBe(0); - - // npm reports "Missing script" and suggests running "npm run" to list scripts - const output = result.stdout + result.stderr; - expect(output).toMatch(/Missing script/i); - expect(output).toContain('npm run'); - } finally { - await dispose(); - } - }, 30_000); -}); - -// --- Online tests (require network + working npm install) --- - -const networkSkip = await checkNetwork(); -const npmInstallSkip = wasmSkip || networkSkip || (await checkNpmInstallWorks()); - -describeIf(!npmInstallSkip, 'npm suite - online', () => { - it( - 'npm install left-pad installs package to node_modules', - async () => { - const tempDir = await mkdtemp( - path.join(tmpdir(), 'kernel-npm-install-suite-'), - ); - - try { - await writeFile( - path.join(tempDir, 'package.json'), - JSON.stringify({ - name: 'test-npm-install-suite', - private: true, - dependencies: { 'left-pad': '1.3.0' }, - }), - ); - - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - - try { - const installResult = await kernel.exec('npm install', { - cwd: '/', - }); - - // Verify node_modules/left-pad/ exists - const stat = await vfs.stat('/node_modules/left-pad'); - expect(stat.isDirectory).toBe(true); - } finally { - await kernel.dispose(); - } - } finally { - await rm(tempDir, { recursive: true, force: true }); - } - }, - 30_000, - ); - - it( - 'npm list shows installed packages after install', - async () => { - const tempDir = await mkdtemp( - path.join(tmpdir(), 'kernel-npm-list-suite-'), - ); - - try { - await writeFile( - path.join(tempDir, 'package.json'), - JSON.stringify({ - name: 'test-npm-list-suite', - private: true, - dependencies: { 'left-pad': '1.3.0' }, - }), - ); - - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount( - createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }), - ); - await kernel.mount(createNodeRuntime()); - - try { - // Install first - await kernel.exec('npm install', { cwd: '/' }); - - // npm list should show left-pad - const listResult = await kernel.exec('npm list', { cwd: '/' }); - expect(listResult.stdout).toContain('left-pad'); - } finally { - await kernel.dispose(); - } - } finally { - await rm(tempDir, { recursive: true, force: true }); - } - }, - 30_000, - ); - - it( - 'npx -y cowsay hello runs cowsay without prior install', - async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - const result = await kernel.exec('npx -y cowsay hello', { cwd: '/' }); - expect(result.stdout).toContain('hello'); - } finally { - await dispose(); - } - }, - 45_000, - ); -}); - -// --- Error handling --- - -describeIf(!wasmSkip, 'npm suite - error handling', () => { - it( - 'npm install with unreachable registry returns clear error', - async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.writeFile( - '/package.json', - JSON.stringify({ - name: 'test-npm-no-network', - private: true, - dependencies: { 'left-pad': '1.3.0' }, - }), - ); - - // Use an unreachable registry to simulate no network - const result = await kernel.exec( - [ - 'npm install', - '--registry=http://127.0.0.1:1', - '--fetch-retries=0', - '--fetch-timeout=1000', - '--fetch-retry-mintimeout=1', - '--fetch-retry-maxtimeout=1', - ].join(' '), - { cwd: '/' }, - ); - expect(result.exitCode).not.toBe(0); - - const output = result.stdout + result.stderr; - expect(output).toMatch(/ERR|error|ECONNREFUSED|fetch failed/i); - } finally { - await dispose(); - } - }, - 30_000, - ); -}); diff --git a/registry/tests/kernel/e2e-npm-version-init.test.ts b/registry/tests/kernel/e2e-npm-version-init.test.ts deleted file mode 100644 index 034c4e166..000000000 --- a/registry/tests/kernel/e2e-npm-version-init.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * E2E test: npm/npx version and npm init through kernel. - * - * Verifies: - * - npm --version outputs valid semver - * - npx --version outputs valid semver - * - npm init -y creates package.json with default values - * - * These are offline tests (no network required). - * - * Note: kernel.exec() wraps commands in sh -c; brush-shell returns exit - * code 17 for spawned children. Test stdout content, not exit code. - */ - -import { describe, expect, it } from 'vitest'; -import { describeIf, createIntegrationKernel, skipUnlessWasmBuilt } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'e2e npm/npx version and init', () => { - it('npm --version returns valid semver', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - const result = await kernel.exec('npm --version', { cwd: '/' }); - const version = result.stdout.trim(); - // Valid semver: major.minor.patch (optionally with pre-release) - expect(version).toMatch(/\d+\.\d+\.\d+/); - } finally { - await dispose(); - } - }, 30_000); - - it('npx --version returns valid semver', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - const result = await kernel.exec('npx --version', { cwd: '/' }); - const version = result.stdout.trim(); - expect(version).toMatch(/\d+\.\d+\.\d+/); - } finally { - await dispose(); - } - }, 30_000); - - it('npm init -y creates package.json with default values', async () => { - const { kernel, vfs, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - await kernel.exec('npm init -y', { cwd: '/' }); - - const exists = await vfs.exists('/package.json'); - expect(exists).toBe(true); - - const content = await vfs.readTextFile('/package.json'); - const pkg = JSON.parse(content); - expect(pkg).toHaveProperty('name'); - expect(pkg).toHaveProperty('version'); - } finally { - await dispose(); - } - }, 30_000); -}); diff --git a/registry/tests/kernel/e2e-npx-and-pipes.test.ts b/registry/tests/kernel/e2e-npx-and-pipes.test.ts deleted file mode 100644 index e0f206f9b..000000000 --- a/registry/tests/kernel/e2e-npx-and-pipes.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * E2E test: npx execution and pipe-based Node eval through kernel. - * - * npx tests verify the npm/npx command resolution chain through the kernel - * command registry. Pipe tests exercise the WasmVM -> kernel PipeManager -> Node - * stdin path, complementing the unit-level pipe tests. - */ - -import { describe, expect, it } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - itIf, - skipUnlessWasmBuilt, -} from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); -const networkSkip = await checkNetwork(); - -/** Check if npm registry is reachable (5s timeout). */ -async function checkNetwork(): Promise { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5_000); - await fetch('https://registry.npmjs.org/', { - signal: controller.signal, - method: 'HEAD', - }); - clearTimeout(timeout); - return false; - } catch { - return 'network not available (cannot reach npm registry)'; - } -} - -describeIf(!skipReason, 'e2e npx and pipes through kernel', () => { - describe('npx execution', () => { - itIf(!networkSkip, 'npx semver outputs parsed version', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - const result = await kernel.exec('npx -y semver 1.2.3', { cwd: '/' }); - expect(result.stdout.trim()).toBe('1.2.3'); - } finally { - await dispose(); - } - }, 30_000); - }); - - describe('pipe-based Node eval', () => { - it('echo piped to node -e evaluates expression', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - const result = await kernel.exec( - `echo 1+2 | node -e "process.stdin.on('data', d => console.log(eval(d.toString().trim())))"`, - { cwd: '/' }, - ); - expect(result.stdout.trim()).toBe('3'); - } finally { - await dispose(); - } - }, 30_000); - - it('echo piped to node -e with end event transforms to uppercase', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - const result = await kernel.exec( - `echo hello world | node -e "let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>console.log(d.trim().toUpperCase()))"`, - { cwd: '/' }, - ); - expect(result.stdout.trim()).toBe('HELLO WORLD'); - } finally { - await dispose(); - } - }, 30_000); - - it('cat VFS file piped to node -e processes file content', async () => { - const { kernel, dispose } = await createIntegrationKernel({ - runtimes: ['wasmvm', 'node'], - }); - - try { - // Pre-write data file to VFS - await kernel.mkdir('/tmp'); - await kernel.writeFile('/tmp/data.txt', 'hello from file'); - - const result = await kernel.exec( - `cat /tmp/data.txt | node -e "let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>console.log(d.trim().toUpperCase()))"`, - { cwd: '/' }, - ); - expect(result.stdout.trim()).toBe('HELLO FROM FILE'); - } finally { - await dispose(); - } - }, 30_000); - }); -}); diff --git a/registry/tests/kernel/e2e-project-matrix.test.ts b/registry/tests/kernel/e2e-project-matrix.test.ts deleted file mode 100644 index 138ebf782..000000000 --- a/registry/tests/kernel/e2e-project-matrix.test.ts +++ /dev/null @@ -1,498 +0,0 @@ -/** - * E2E project-matrix test: run existing fixture projects through the kernel. - * - * For each fixture in the repo-owned tests/projects/ directory: - * 1. Prepare project (npm install, cached by content hash) - * 2. Run entry via host Node (baseline) - * 3. Run entry via kernel (NodeFileSystem rooted at project dir, WasmVM + Node) - * 4. Compare output parity - * - * Adapted from the legacy runtime suite to use package imports and - * repo-local fixtures. - */ - -import { execFile } from 'node:child_process'; -import { createHash } from 'node:crypto'; -import { access, cp, mkdir, readFile, readdir, rename, rm, symlink, writeFile } from 'node:fs/promises'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { promisify } from 'node:util'; -import { describe, expect, it } from 'vitest'; -import { - describeIf, - COMMANDS_DIR, - createKernel, - NodeFileSystem, - createWasmVmRuntime, - createNodeRuntime, - skipUnlessWasmBuilt, -} from './helpers.ts'; - -const execFileAsync = promisify(execFile); -const TEST_TIMEOUT_MS = 55_000; -const COMMAND_TIMEOUT_MS = 45_000; -const CACHE_READY_MARKER = '.ready'; -const WORKTREE_SCHEMA_VERSION = 'v2-isolated-worktrees'; -const TRANSIENT_OUTPUT_DIRS = new Set([ - '.astro', - '.next', - 'build', - 'coverage', - 'dist', - 'out', -]); - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -const WORKSPACE_ROOT = path.resolve(__dirname, '../../..'); -const FIXTURES_ROOT = path.resolve(__dirname, '../projects'); -const CACHE_ROOT = path.join(__dirname, '../../.cache', 'project-matrix'); - -// --------------------------------------------------------------------------- -// Types (same schema as project-matrix.test.ts) -// --------------------------------------------------------------------------- - -type PackageManager = 'pnpm' | 'npm' | 'bun' | 'yarn'; -type PassFixtureMetadata = { entry: string; expectation: 'pass'; packageManager?: PackageManager }; -type FailFixtureMetadata = { - entry: string; - expectation: 'fail'; - fail: { code: number; stderrIncludes: string }; - packageManager?: PackageManager; -}; -type FixtureMetadata = PassFixtureMetadata | FailFixtureMetadata; -type FixtureProject = { name: string; sourceDir: string; metadata: FixtureMetadata }; -type PreparedFixture = { cacheHit: boolean; cacheKey: string; projectDir: string }; -type WorkingFixtureProject = { projectDir: string; dispose: () => Promise }; -type ResultEnvelope = { code: number; stdout: string; stderr: string }; - -// --------------------------------------------------------------------------- -// Fixture discovery -// --------------------------------------------------------------------------- - -async function discoverFixtures(): Promise { - let entries; - try { - entries = await readdir(FIXTURES_ROOT, { withFileTypes: true }); - } catch { - // Fixtures directory doesn't exist in registry. Return empty. - return []; - } - const fixtureDirs = entries - .filter((e) => e.isDirectory()) - .map((e) => e.name) - .sort((a, b) => a.localeCompare(b)); - - const fixtures: FixtureProject[] = []; - for (const name of fixtureDirs) { - const sourceDir = path.join(FIXTURES_ROOT, name); - const metaPath = path.join(sourceDir, 'fixture.json'); - const packageJsonPath = path.join(sourceDir, 'package.json'); - if (!(await pathExists(metaPath)) || !(await pathExists(packageJsonPath))) { - continue; - } - const raw = JSON.parse(await readFile(metaPath, 'utf8')); - const metadata = parseMetadata(raw, name); - fixtures.push({ name, sourceDir, metadata }); - } - return fixtures; -} - -function parseMetadata(raw: Record, name: string): FixtureMetadata { - const entry = raw.entry as string; - const packageManager = raw.packageManager as PackageManager | undefined; - if (raw.expectation === 'pass') return { entry, expectation: 'pass', ...(packageManager && { packageManager }) }; - const fail = raw.fail as { code: number; stderrIncludes: string }; - return { entry, expectation: 'fail', fail, ...(packageManager && { packageManager }) }; -} - -// --------------------------------------------------------------------------- -// Fixture preparation -// --------------------------------------------------------------------------- - -async function prepareFixtureProject(fixture: FixtureProject): Promise { - await mkdir(CACHE_ROOT, { recursive: true }); - const cacheKey = await createFixtureCacheKey(fixture); - const cacheDir = path.join(CACHE_ROOT, `${fixture.name}-${cacheKey}`); - const readyMarker = path.join(cacheDir, CACHE_READY_MARKER); - - if (await pathExists(readyMarker) && await cacheHasRequiredInstallArtifacts(fixture, cacheDir)) { - return { cacheHit: true, cacheKey, projectDir: cacheDir }; - } - - // Reset stale entries - if (await pathExists(cacheDir)) { - await rm(cacheDir, { recursive: true, force: true }); - } - - // Stage and install - const staging = `${cacheDir}.tmp-${process.pid}-${Date.now()}`; - await rm(staging, { recursive: true, force: true }); - await cp(fixture.sourceDir, staging, { - recursive: true, - filter: (src) => !src.split(path.sep).includes('node_modules'), - }); - const pm = fixture.metadata.packageManager ?? 'pnpm'; - const installCmd = - pm === 'npm' - ? { cmd: 'npm', args: ['install', '--prefer-offline'] } - : pm === 'bun' - ? { cmd: 'bun', args: ['install'] } - : pm === 'yarn' - ? await getYarnInstallCmd(staging) - : { cmd: 'pnpm', args: ['install', '--ignore-workspace', '--prefer-offline'] }; - await execFileAsync(installCmd.cmd, installCmd.args, { - cwd: staging, - timeout: COMMAND_TIMEOUT_MS, - maxBuffer: 10 * 1024 * 1024, - ...(pm === 'yarn' && { env: yarnEnv }), - }); - await writeFile(path.join(staging, CACHE_READY_MARKER), `${new Date().toISOString()}\n`); - - // Promote - try { - await rename(staging, cacheDir); - } catch (err: unknown) { - const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : ''; - if (code !== 'EEXIST') throw err; - await rm(staging, { recursive: true, force: true }); - if (!(await pathExists(readyMarker))) { - throw new Error(`Cache race: missing ready marker at ${cacheDir}`); - } - } - - return { cacheHit: false, cacheKey, projectDir: cacheDir }; -} - -async function createFixtureCacheKey(fixture: FixtureProject): Promise { - const hash = createHash('sha256'); - const nodeMajor = process.versions.node.split('.')[0] ?? '0'; - const pm = fixture.metadata.packageManager ?? 'pnpm'; - const pmVersion = - pm === 'npm' - ? await getNpmVersion() - : pm === 'bun' - ? await getBunVersion() - : pm === 'yarn' - ? await getYarnVersion() - : await getPnpmVersion(); - hash.update(`node-major:${nodeMajor}\n`); - hash.update(`pm:${pm}\n`); - hash.update(`pm-version:${pmVersion}\n`); - hash.update(`platform:${process.platform}\n`); - hash.update(`arch:${process.arch}\n`); - hash.update(`worktree-schema:${WORKTREE_SCHEMA_VERSION}\n`); - - const lockFile = - pm === 'npm' - ? 'package-lock.json' - : pm === 'bun' - ? 'bun.lock' - : pm === 'yarn' - ? 'yarn.lock' - : 'pnpm-lock.yaml'; - for (const [label, filePath] of [ - ['workspace-lock', path.join(WORKSPACE_ROOT, 'pnpm-lock.yaml')], - ['workspace-package', path.join(WORKSPACE_ROOT, 'package.json')], - ['fixture-package', path.join(fixture.sourceDir, 'package.json')], - ['fixture-lock', path.join(fixture.sourceDir, lockFile)], - ]) { - hash.update(`${label}:`); - try { hash.update(await readFile(filePath)); } catch { hash.update(''); } - hash.update('\n'); - } - - const files = await listFiles(fixture.sourceDir); - for (const rel of files) { - hash.update(`fixture-file:${rel.split(path.sep).join('/')}\n`); - hash.update(await readFile(path.join(fixture.sourceDir, rel))); - hash.update('\n'); - } - - return hash.digest('hex').slice(0, 16); -} - -async function cacheHasRequiredInstallArtifacts( - fixture: FixtureProject, - cacheDir: string, -): Promise { - if (!(await fixtureDeclaresDependencies(fixture))) { - return true; - } - return pathExists(path.join(cacheDir, 'node_modules')); -} - -async function fixtureDeclaresDependencies(fixture: FixtureProject): Promise { - const packageJson = JSON.parse( - await readFile(path.join(fixture.sourceDir, 'package.json'), 'utf8'), - ) as Record; - return [ - 'dependencies', - 'devDependencies', - 'optionalDependencies', - 'peerDependencies', - ].some((key) => { - const value = packageJson[key]; - return ( - value !== null && - typeof value === 'object' && - Object.keys(value).length > 0 - ); - }); -} - -async function createWorkingFixtureProject( - fixture: FixtureProject, - prepared: PreparedFixture, - label: string, -): Promise { - const workingRoot = path.join(CACHE_ROOT, '.worktrees'); - await mkdir(workingRoot, { recursive: true }); - const projectDir = path.join( - workingRoot, - `${fixture.name}-${prepared.cacheKey}-${label}-${process.pid}-${Date.now()}`, - ); - - await cp(prepared.projectDir, projectDir, { - recursive: true, - filter: (src) => { - const relative = path.relative(prepared.projectDir, src); - if (!relative) return true; - const segments = relative.split(path.sep); - return !segments.some((segment) => - segment === 'node_modules' || TRANSIENT_OUTPUT_DIRS.has(segment), - ); - }, - }); - - const installedNodeModulesDir = path.join(prepared.projectDir, 'node_modules'); - if (await pathExists(installedNodeModulesDir)) { - await symlink(installedNodeModulesDir, path.join(projectDir, 'node_modules'), 'dir'); - } - - return { - projectDir, - dispose: () => rm(projectDir, { recursive: true, force: true }), - }; -} - -let _pnpmVersionPromise: Promise | undefined; -function getPnpmVersion(): Promise { - if (!_pnpmVersionPromise) { - _pnpmVersionPromise = execFileAsync('pnpm', ['--version'], { - cwd: WORKSPACE_ROOT, - timeout: COMMAND_TIMEOUT_MS, - }).then((r) => r.stdout.trim()); - } - return _pnpmVersionPromise; -} - -let _npmVersionPromise: Promise | undefined; -function getNpmVersion(): Promise { - if (!_npmVersionPromise) { - _npmVersionPromise = execFileAsync('npm', ['--version'], { - cwd: WORKSPACE_ROOT, - timeout: COMMAND_TIMEOUT_MS, - }).then((r) => r.stdout.trim()); - } - return _npmVersionPromise; -} - -let _bunVersionPromise: Promise | undefined; -function getBunVersion(): Promise { - if (!_bunVersionPromise) { - _bunVersionPromise = execFileAsync('bun', ['--version'], { - cwd: WORKSPACE_ROOT, - timeout: COMMAND_TIMEOUT_MS, - }).then((r) => r.stdout.trim()); - } - return _bunVersionPromise; -} - -let _yarnVersionPromise: Promise | undefined; -// Bypass corepack packageManager enforcement so yarn runs in a pnpm workspace. -const yarnEnv = { ...process.env, COREPACK_ENABLE_STRICT: '0' }; -function getYarnVersion(): Promise { - if (!_yarnVersionPromise) { - _yarnVersionPromise = execFileAsync('yarn', ['--version'], { - cwd: WORKSPACE_ROOT, - timeout: COMMAND_TIMEOUT_MS, - env: yarnEnv, - }).then((r) => r.stdout.trim()); - } - return _yarnVersionPromise; -} - -async function getYarnInstallCmd( - projectDir: string, -): Promise<{ cmd: string; args: string[] }> { - const isBerry = await pathExists(path.join(projectDir, '.yarnrc.yml')); - return isBerry - ? { cmd: 'yarn', args: ['install', '--immutable'] } - : { cmd: 'yarn', args: ['install'] }; -} - -async function listFiles(root: string): Promise { - const result: string[] = []; - async function walk(rel: string): Promise { - const dir = path.join(root, rel); - const entries = await readdir(dir, { withFileTypes: true }); - for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) { - if (e.name === 'node_modules') continue; - const p = rel ? path.join(rel, e.name) : e.name; - if (e.isDirectory()) await walk(p); - else if (e.isFile()) result.push(p); - } - } - await walk(''); - return result.sort((a, b) => a.localeCompare(b)); -} - -// --------------------------------------------------------------------------- -// Host execution (baseline) -// --------------------------------------------------------------------------- - -async function runHostExecution(projectDir: string, entryRel: string): Promise { - const entryPath = path.join(projectDir, entryRel); - return normalizeEnvelope(await runCommand(process.execPath, [entryPath], projectDir), projectDir); -} - -async function runCommand(cmd: string, args: string[], cwd: string): Promise { - try { - const r = await execFileAsync(cmd, args, { cwd, timeout: COMMAND_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 }); - return { code: 0, stdout: r.stdout, stderr: r.stderr }; - } catch (err: unknown) { - if (err && typeof err === 'object' && 'stdout' in err) { - const e = err as { code?: number; stdout?: string; stderr?: string }; - return { - code: typeof e.code === 'number' ? e.code : 1, - stdout: typeof e.stdout === 'string' ? e.stdout : '', - stderr: typeof e.stderr === 'string' ? e.stderr : '', - }; - } - throw err; - } -} - -// --------------------------------------------------------------------------- -// Kernel execution -// --------------------------------------------------------------------------- - -async function runKernelExecution(projectDir: string, entryRel: string): Promise { - // NodeFileSystem rooted at projectDir. require() resolves from node_modules on disk. - const vfs = new NodeFileSystem({ root: projectDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - - await kernel.mount(createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] })); - await kernel.mount(createNodeRuntime()); - - try { - const vfsEntry = '/' + entryRel.replace(/\\/g, '/'); - const result = await kernel.exec(`node ${vfsEntry}`, { cwd: '/' }); - return normalizeEnvelope( - { code: result.exitCode, stdout: result.stdout, stderr: result.stderr }, - projectDir, - ); - } finally { - await kernel.dispose(); - } -} - -// --------------------------------------------------------------------------- -// Output normalization -// --------------------------------------------------------------------------- - -function normalizeEnvelope(envelope: ResultEnvelope, projectDir: string): ResultEnvelope { - return { - code: envelope.code, - stdout: normalizeText(envelope.stdout, projectDir), - stderr: normalizeText(envelope.stderr, projectDir), - }; -} - -function normalizeText(value: string, projectDir: string): string { - const normalized = value.replace(/\r\n/g, '\n'); - const posixDir = projectDir.split(path.sep).join(path.posix.sep); - return normalizeModuleNotFoundText( - normalized.split(projectDir).join('').split(posixDir).join(''), - ); -} - -function normalizeModuleNotFoundText(value: string): string { - if (!value.includes('Cannot find module')) return value; - const quoted = value.match(/Cannot find module '([^']+)'/); - if (quoted) return `Cannot find module '${quoted[1]}'\n`; - const from = value.match(/Cannot find module:\s*([^\s]+)\s+from\s+/); - if (from) return `Cannot find module '${from[1]}'\n`; - return value; -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -async function pathExists(p: string): Promise { - try { await access(p); return true; } catch { return false; } -} - -async function commandAvailable(cmd: string): Promise { - try { - await execFileAsync(cmd, ['--version'], { - cwd: WORKSPACE_ROOT, - timeout: COMMAND_TIMEOUT_MS, - }); - return true; - } catch { - return false; - } -} - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -const skipReason = skipUnlessWasmBuilt(); -const discoveredFixtures = await discoverFixtures(); -const hasHostBun = await commandAvailable('bun'); - -describeIf(!(skipReason || discoveredFixtures.length === 0), 'e2e project-matrix through kernel', () => { - it('discovers at least one fixture project', () => { - expect(discoveredFixtures.length).toBeGreaterThan(0); - }); - - for (const fixture of discoveredFixtures) { - const testFixture = fixture.metadata.packageManager === 'bun' && !hasHostBun - ? it.skip - : it; - testFixture( - `runs fixture ${fixture.name} through kernel with host-node parity`, - async () => { - const prepared = await prepareFixtureProject(fixture); - const hostProject = await createWorkingFixtureProject(fixture, prepared, 'host'); - const kernelProject = await createWorkingFixtureProject(fixture, prepared, 'kernel'); - let host: ResultEnvelope; - let kernel: ResultEnvelope; - - try { - host = await runHostExecution(hostProject.projectDir, fixture.metadata.entry); - kernel = await runKernelExecution(kernelProject.projectDir, fixture.metadata.entry); - } finally { - await Promise.all([hostProject.dispose(), kernelProject.dispose()]); - } - - if (fixture.metadata.expectation === 'pass') { - expect(kernel.code).toBe(host.code); - expect(kernel.stdout).toBe(host.stdout); - expect(kernel.stderr).toBe(host.stderr); - return; - } - - // Fail fixtures: host succeeds, kernel enforces sandbox restrictions - expect(host.code).toBe(0); - expect(kernel.code).toBe(fixture.metadata.fail.code); - expect(kernel.stderr).toContain(fixture.metadata.fail.stderrIncludes); - }, - TEST_TIMEOUT_MS, - ); - } -}); diff --git a/registry/tests/kernel/error-propagation.test.ts b/registry/tests/kernel/error-propagation.test.ts deleted file mode 100644 index c71c3c580..000000000 --- a/registry/tests/kernel/error-propagation.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Cross-runtime error and exit code propagation tests. - * - * Verifies that exit codes and stderr flow correctly across runtime - * boundaries, including through nested cross-runtime spawns (e.g. - * WasmVM shell -> kernel -> Node -> exit(42) -> kernel -> WasmVM). - * - * Gracefully skipped when the WASM binary is not built. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'cross-runtime error propagation', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('Node non-zero exit propagates', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "process.exit(42)"'); - expect(result.exitCode).toBe(42); - }); - - it('WasmVM non-zero exit propagates', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - const result = await ctx.kernel.exec('sh -c "exit 7"'); - expect(result.exitCode).toBe(7); - }); - - it('Node stderr captured by kernel', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "console.error(\'err-msg\')"'); - expect(result.stderr).toContain('err-msg'); - }); - - it('WasmVM stderr captured by kernel', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - const result = await ctx.kernel.exec('echo error >&2'); - expect(result.stderr).toContain('error'); - }); - - it('nested cross-runtime exit code propagation', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - // WasmVM shell spawns Node via kernel, Node exits 42, shell propagates - // Tests exit code flowing through TWO runtime boundary crossings: - // kernel->WasmVM->kernel->Node->exit(42)->kernel->WasmVM->kernel - const result = await ctx.kernel.exec('sh -c "node -e \\"process.exit(42)\\""'); - expect(result.exitCode).toBe(42); - }); - - it('Node captures WasmVM child failure', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - // Node runs child_process.execSync which routes through kernel to WasmVM shell - // The shell exits 3, Node catches the error and logs the exit code - const code = [ - 'try {', - ' require("child_process").execSync("sh -c \\"exit 3\\"");', - '} catch (e) {', - ' console.log(e.status);', - '}', - ].join(' '); - const result = await ctx.kernel.exec(`node -e '${code}'`); - expect(result.stdout).toContain('3'); - }); -}); diff --git a/registry/tests/kernel/exec-integration.test.ts b/registry/tests/kernel/exec-integration.test.ts deleted file mode 100644 index a02216d71..000000000 --- a/registry/tests/kernel/exec-integration.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Cross-runtime integration tests for kernel.exec() and kernel.spawn(). - * - * Exercises real WasmVM driver end-to-end: shell parsing, coreutils - * execution, VFS reads/writes, and error handling. Each test creates - * a fresh kernel. No shared state between tests. - * - * Gracefully skipped when the WASM binary is not built. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'kernel.exec() integration', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('exec echo returns stdout', async () => { - ctx = await createIntegrationKernel(); - const result = await ctx.kernel.exec('echo hello'); - expect(result.exitCode).toBe(0); - expect(result.stdout.trim()).toBe('hello'); - }); - - it('exec ls lists files in directory', async () => { - ctx = await createIntegrationKernel(); - // Write a file into VFS, then list the directory - await ctx.vfs.writeFile('/tmp/test-file.txt', 'content'); - const result = await ctx.kernel.exec('ls /tmp'); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('test-file.txt'); - }); - - it('exec cat reads file contents', async () => { - ctx = await createIntegrationKernel(); - await ctx.vfs.writeFile('/tmp/test.txt', 'hello from vfs'); - const result = await ctx.kernel.exec('cat /tmp/test.txt'); - expect(result.exitCode).toBe(0); - expect(result.stdout.trim()).toBe('hello from vfs'); - }); - - it('exec nonexistent command returns non-zero', async () => { - ctx = await createIntegrationKernel(); - const result = await ctx.kernel.exec('nonexistent-command-xyz'); - expect(result.exitCode).not.toBe(0); - }); - - it('exec with env passes environment to process', async () => { - ctx = await createIntegrationKernel(); - const result = await ctx.kernel.exec('echo $MY_VAR', { - env: { MY_VAR: 'kernel-test' }, - }); - expect(result.exitCode).toBe(0); - expect(result.stdout.trim()).toBe('kernel-test'); - }); - - it('exec pipeline with pipe operator', async () => { - ctx = await createIntegrationKernel(); - await ctx.vfs.writeFile('/tmp/data.txt', 'foo\nbar\nbaz\n'); - const result = await ctx.kernel.exec('cat /tmp/data.txt | wc -l'); - expect(result.exitCode).toBe(0); - expect(result.stdout.trim()).toBe('3'); - }); - - it('exec writes to VFS via redirection', async () => { - ctx = await createIntegrationKernel(); - const result = await ctx.kernel.exec('echo "written by shell" > /tmp/out.txt'); - expect(result.exitCode).toBe(0); - const content = await ctx.vfs.readFile('/tmp/out.txt'); - expect(new TextDecoder().decode(content).trim()).toBe('written by shell'); - }); - - it('exec stderr captured on error', async () => { - ctx = await createIntegrationKernel(); - const result = await ctx.kernel.exec('cat /nonexistent/path'); - expect(result.exitCode).not.toBe(0); - expect(result.stderr.length).toBeGreaterThan(0); - }); - - it('shell test builtin honors precedence and grouping', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - const script = [ - "if /bin/[ 1 -eq 1 -o 2 -eq 3 -a 4 -eq 5 ]; then echo precedence; else echo bad; fi", - "if /bin/[ '(' 1 -eq 0 -o 2 -eq 2 ')' -a 3 -eq 3 ]; then echo grouping; else echo bad; fi", - ].join('\n'); - - const result = await ctx.kernel.exec(script); - expect(result.exitCode).toBe(0); - expect(result.stdout.trim().split('\n')).toEqual(['precedence', 'grouping']); - }); -}); - -describeIf(!skipReason, 'kernel.spawn() integration', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('spawn returns ManagedProcess with PID', async () => { - ctx = await createIntegrationKernel(); - const proc = ctx.kernel.spawn('echo', ['spawn-test']); - expect(proc.pid).toBeGreaterThan(0); - const exitCode = await proc.wait(); - expect(exitCode).toBe(0); - }); - - it('spawn stdout fires onData via options callback', async () => { - ctx = await createIntegrationKernel(); - const chunks: string[] = []; - const proc = ctx.kernel.spawn('echo', ['spawn-output'], { - onStdout: (data) => chunks.push(new TextDecoder().decode(data)), - }); - await proc.wait(); - const output = chunks.join(''); - expect(output.trim()).toBe('spawn-output'); - }); - - it('spawn exitCode resolves', async () => { - ctx = await createIntegrationKernel(); - const proc = ctx.kernel.spawn('false', []); - const exitCode = await proc.wait(); - expect(exitCode).not.toBe(0); - }); - - it('each test gets a fresh kernel with independent state', async () => { - ctx = await createIntegrationKernel(); - // Write a file in this test's VFS - await ctx.vfs.writeFile('/tmp/isolation-check.txt', 'exists'); - const result = await ctx.kernel.exec('cat /tmp/isolation-check.txt'); - expect(result.exitCode).toBe(0); - - // Create a second kernel. It should NOT see the first kernel's file. - const ctx2 = await createIntegrationKernel(); - const result2 = await ctx2.kernel.exec('cat /tmp/isolation-check.txt'); - expect(result2.exitCode).not.toBe(0); - await ctx2.dispose(); - }); -}); diff --git a/registry/tests/kernel/fd-inheritance.test.ts b/registry/tests/kernel/fd-inheritance.test.ts deleted file mode 100644 index b99412009..000000000 --- a/registry/tests/kernel/fd-inheritance.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Integration tests for FD inheritance across process boundaries. - * - * Exercises shell redirections (> and <) which rely on FD table forking - * and stdio overrides to work correctly. Each test creates a fresh kernel. - * - * Gracefully skipped when the WASM binary is not built. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'FD inheritance', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('exec echo hello > /tmp/out.txt writes to VFS file', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - const result = await ctx.kernel.exec('echo hello > /tmp/out.txt'); - expect(result.exitCode).toBe(0); - - const content = await ctx.vfs.readTextFile('/tmp/out.txt'); - expect(content.trim()).toBe('hello'); - }); - - it('exec cat < /tmp/in.txt reads from VFS file', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - await ctx.vfs.writeFile('/tmp/in.txt', 'input data\n'); - - const result = await ctx.kernel.exec('cat < /tmp/in.txt'); - expect(result.exitCode).toBe(0); - expect(result.stdout.trim()).toBe('input data'); - }); - - it('exec with append >> accumulates output', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - await ctx.kernel.exec('echo first > /tmp/append.txt'); - await ctx.kernel.exec('echo second >> /tmp/append.txt'); - - const content = await ctx.vfs.readTextFile('/tmp/append.txt'); - expect(content).toContain('first'); - expect(content).toContain('second'); - }); - - it('piped output preserves data across redirection', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - await ctx.vfs.writeFile('/tmp/nums.txt', 'a\nb\nc\n'); - - const result = await ctx.kernel.exec('cat /tmp/nums.txt | wc -l'); - expect(result.exitCode).toBe(0); - expect(result.stdout.trim()).toBe('3'); - }); - - it('exec with combined stdin redirect and stdout redirect', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - await ctx.vfs.writeFile('/tmp/data.txt', 'vfs-content\n'); - - // Read from file via < and write to file via >. Full FD inheritance chain. - const result = await ctx.kernel.exec( - 'cat < /tmp/data.txt > /tmp/out.txt', - ); - expect(result.exitCode).toBe(0); - - const content = await ctx.vfs.readTextFile('/tmp/out.txt'); - expect(content.trim()).toBe('vfs-content'); - }); -}); diff --git a/registry/tests/kernel/helpers.ts b/registry/tests/kernel/helpers.ts deleted file mode 100644 index e6205ac22..000000000 --- a/registry/tests/kernel/helpers.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Integration test helpers for kernel tests that depend on WASM command binaries. - * - * Re-exports infrastructure from the parent helpers.ts and provides - * createIntegrationKernel / skipUnlessWasmBuilt for cross-runtime tests. - */ - -import { - AF_INET, - AF_UNIX, - COMMANDS_DIR, - C_BUILD_DIR, - describeIf, - hasWasmBinaries, - itIf, - NodeFileSystem, - SIGTERM, - SOCK_DGRAM, - SOCK_STREAM, - TerminalHarness, - skipReason, - createInMemoryFileSystem, - createKernel, - createWasmVmRuntime, - createNodeRuntime, -} from "../helpers.js"; -import type { Kernel, VirtualFileSystem } from "../helpers.js"; - -export { - AF_INET, - AF_UNIX, - COMMANDS_DIR, - C_BUILD_DIR, - describeIf, - hasWasmBinaries, - itIf, - NodeFileSystem, - SIGTERM, - SOCK_DGRAM, - SOCK_STREAM, - TerminalHarness, - skipReason, - createInMemoryFileSystem, - createKernel, - createWasmVmRuntime, - createNodeRuntime, -} from "../helpers.js"; -export type { Kernel, VirtualFileSystem } from "../helpers.js"; - -export interface IntegrationKernelResult { - kernel: Kernel; - vfs: VirtualFileSystem; - dispose: () => Promise; -} - -export interface IntegrationKernelOptions { - runtimes?: ("wasmvm" | "node")[]; - loopbackExemptPorts?: number[]; - commandDirs?: string[]; -} - -/** - * Create a kernel with the in-scope runtime drivers for integration testing. - * - * Mount order matters. Last-mounted driver wins for overlapping commands: - * 1. WasmVM first: provides sh/bash/coreutils (90+ commands) - * 2. Node second: overrides WasmVM's 'node' stub with real V8 - */ -export async function createIntegrationKernel( - options?: IntegrationKernelOptions, -): Promise { - const runtimes = options?.runtimes ?? ["wasmvm"]; - const vfs = createInMemoryFileSystem(); - const kernel = createKernel({ - filesystem: vfs, - loopbackExemptPorts: options?.loopbackExemptPorts, - }); - - if (runtimes.includes("wasmvm")) { - await kernel.mount( - createWasmVmRuntime({ commandDirs: options?.commandDirs ?? [COMMANDS_DIR] }), - ); - } - if (runtimes.includes("node")) { - await kernel.mount(createNodeRuntime()); - } - - return { - kernel, - vfs, - dispose: () => kernel.dispose(), - }; -} - -/** - * Skip helper: returns a reason string if the WASM binaries are not built, - * or false if the commands directory exists and tests can run. - */ -export function skipUnlessWasmBuilt(): string | false { - return skipReason(); -} diff --git a/registry/tests/kernel/module-resolution.test.ts b/registry/tests/kernel/module-resolution.test.ts deleted file mode 100644 index 829f75714..000000000 --- a/registry/tests/kernel/module-resolution.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Module resolution through kernel VFS tests. - * - * Verifies that Node's require() resolves modules via the kernel VFS, - * enabling CJS modules, node_modules, nested paths, and JSON imports. - * - * Gracefully skipped when the WASM binary is not built. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'module resolution through kernel VFS', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('require relative CJS module', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - await ctx.kernel.writeFile( - '/app/lib/utils.js', - 'module.exports.add = (a, b) => a + b;', - ); - await ctx.kernel.writeFile( - '/app/index.js', - "console.log(require('./lib/utils').add(1, 2));", - ); - - const result = await ctx.kernel.exec('node /app/index.js'); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('3'); - }); - - it('require from node_modules', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - await ctx.kernel.writeFile( - '/app/node_modules/my-pkg/index.js', - "module.exports.greet = () => 'hello';", - ); - await ctx.kernel.writeFile( - '/app/main.js', - "console.log(require('my-pkg').greet());", - ); - - const result = await ctx.kernel.exec('node /app/main.js'); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('hello'); - }); - - it('require missing module gives MODULE_NOT_FOUND', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - // Ensure /app exists as cwd - await ctx.kernel.writeFile('/app/.keep', ''); - - const result = await ctx.kernel.exec( - `node -e "require('./nonexistent')"`, - { cwd: '/app' }, - ); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toMatch(/Cannot find module|MODULE_NOT_FOUND/); - }); - - it('require nested relative path', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - await ctx.kernel.writeFile( - '/app/lib/utils.js', - 'module.exports.add = (a, b) => a + b;', - ); - await ctx.kernel.writeFile( - '/app/src/handlers/index.js', - "console.log(require('../../lib/utils').add(3, 4));", - ); - - const result = await ctx.kernel.exec('node /app/src/handlers/index.js'); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('7'); - }); - - it('require JSON file', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - await ctx.kernel.writeFile( - '/app/config.json', - JSON.stringify({ port: 3000 }), - ); - - const result = await ctx.kernel.exec( - `node -e "console.log(require('./config.json').port)"`, - { cwd: '/app' }, - ); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('3000'); - }); -}); diff --git a/registry/tests/kernel/node-binary-behavior.test.ts b/registry/tests/kernel/node-binary-behavior.test.ts deleted file mode 100644 index 0f71bd648..000000000 --- a/registry/tests/kernel/node-binary-behavior.test.ts +++ /dev/null @@ -1,436 +0,0 @@ -/** - * Comprehensive node binary integration tests. - * - * Covers all node CLI behaviors through the kernel: stdout, stderr, - * exit codes, error types, delayed output, stdin pipes, VFS access, - * cross-runtime child_process, --version, and no-args behavior. - * - * Each scenario is tested via kernel.exec() (non-PTY path) and key - * stdout/error scenarios are also verified through TerminalHarness - * (interactive PTY path). - * - * Gracefully skipped when WASM binaries are not built. - */ - -import { mkdtemp, mkdir, rm, symlink, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, - TerminalHarness, - createKernel, - createNodeRuntime, - createWasmVmRuntime, - NodeFileSystem, - COMMANDS_DIR, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -/** brush-shell interactive prompt. */ -const PROMPT = 'sh-0.4$ '; - -/** - * Find a line in the screen output that exactly matches the expected text. - * Excludes lines containing the command echo (prompt line). - */ -function findOutputLine(screen: string, expected: string): string | undefined { - return screen.split('\n').find( - (l) => l.trim() === expected && !l.includes(PROMPT), - ); -} - -function decodeChunks(chunks: Uint8Array[]): string { - const decoder = new TextDecoder(); - return chunks.map((chunk) => decoder.decode(chunk)).join(""); -} - -// --------------------------------------------------------------------------- -// kernel.exec() -- stdout -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: exec stdout', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('node -e console.log produces stdout with exit 0', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "console.log(\'hello\')"'); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('hello'); - }); - - it('node -e setTimeout delayed output appears', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec( - 'node -e "setTimeout(()=>console.log(\'delayed\'),100)"', - ); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('delayed'); - }, 10_000); -}); - -describeIf(!skipReason, 'node binary: spawn callback routing', () => { - let ctxA: IntegrationKernelResult; - let ctxB: IntegrationKernelResult; - - afterEach(async () => { - await ctxA?.dispose(); - await ctxB?.dispose(); - }); - - it('concurrent kernels keep stdout callbacks isolated per VM marker', async () => { - ctxA = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - ctxB = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - const stdoutA: Uint8Array[] = []; - const stdoutB: Uint8Array[] = []; - const stderrA: Uint8Array[] = []; - const stderrB: Uint8Array[] = []; - - const procA = ctxA.kernel.spawn('node', ['-e', "console.log('VM_A_MARKER')"], { - onStdout: (chunk) => stdoutA.push(chunk), - onStderr: (chunk) => stderrA.push(chunk), - }); - const procB = ctxB.kernel.spawn('node', ['-e', "console.log('VM_B_MARKER')"], { - onStdout: (chunk) => stdoutB.push(chunk), - onStderr: (chunk) => stderrB.push(chunk), - }); - - const [exitA, exitB] = await Promise.all([procA.wait(), procB.wait()]); - expect(exitA).toBe(0); - expect(exitB).toBe(0); - - const stdoutAText = decodeChunks(stdoutA); - const stdoutBText = decodeChunks(stdoutB); - expect(stdoutAText).toContain('VM_A_MARKER'); - expect(stdoutAText).not.toContain('VM_B_MARKER'); - expect(stdoutBText).toContain('VM_B_MARKER'); - expect(stdoutBText).not.toContain('VM_A_MARKER'); - expect(decodeChunks(stderrA)).toBe(''); - expect(decodeChunks(stderrB)).toBe(''); - }, 15_000); -}); - -// --------------------------------------------------------------------------- -// kernel.exec() -- exit codes -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: exec exit codes', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('node -e process.exit(42) returns exit code 42', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "process.exit(42)"'); - expect(result.exitCode).toBe(42); - }); - - it('node -e process.exit(0) returns exit code 0', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "process.exit(0)"'); - expect(result.exitCode).toBe(0); - }); -}); - -// --------------------------------------------------------------------------- -// kernel.exec() -- stderr and error types -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: exec stderr', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('node -e console.error routes to stderr', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "console.error(\'err\')"'); - expect(result.stderr).toContain('err'); - expect(result.exitCode).toBe(0); - }); - - it('node -e syntax error returns SyntaxError on stderr', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "({" '); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toMatch(/SyntaxError|Unexpected/); - }); - - it('node -e ReferenceError on undefined variable', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "unknownVar"'); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toContain('ReferenceError'); - }); - - it('node -e throw new Error returns message on stderr', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node -e "throw new Error(\'boom\')"'); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toContain('boom'); - }); -}); - -// --------------------------------------------------------------------------- -// kernel.exec() -- stdin -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: exec stdin', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('node -e reads from stdin pipe when data provided', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const code = [ - 'let d = "";', - 'process.stdin.setEncoding("utf8");', - 'process.stdin.on("data", c => d += c);', - 'process.stdin.on("end", () => console.log(d.trim()));', - ].join(' '); - const result = await ctx.kernel.exec(`echo "piped-input" | node -e '${code}'`); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('piped-input'); - }, 15_000); -}); - -// --------------------------------------------------------------------------- -// kernel.exec() -- VFS access -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: exec VFS access', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('node -e fs.readdirSync("/") returns VFS root listing', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec( - 'node -e "console.log(require(\'fs\').readdirSync(\'/\').join(\',\'))"', - ); - expect(result.exitCode).toBe(0); - // VFS root should contain at least /bin and /tmp - expect(result.stdout).toContain('bin'); - expect(result.stdout).toContain('tmp'); - }); -}); - -// --------------------------------------------------------------------------- -// kernel.exec() -- cross-runtime child_process -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: exec child_process', () => { - let ctx: IntegrationKernelResult; - let tempDir: string | undefined; - - afterEach(async () => { - await ctx?.dispose(); - if (tempDir) { - await rm(tempDir, { recursive: true, force: true }); - tempDir = undefined; - } - }); - - it('node -e execSync("echo sub") captures child stdout', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const code = - 'console.log(require("child_process").execSync("echo sub").toString().trim())'; - const result = await ctx.kernel.exec(`node -e '${code}'`); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('sub'); - }, 15_000); - - it('node -e spawnSync resolves shebang-backed node_modules/.bin commands through JavaScript runtime', async () => { - tempDir = await mkdtemp(path.join(tmpdir(), 'kernel-node-bin-')); - await writeFile( - path.join(tempDir, 'package.json'), - JSON.stringify({ name: 'node-bin-repro', private: true }), - ); - await mkdir(path.join(tempDir, 'node_modules', 'hello-pkg', 'bin'), { - recursive: true, - }); - await writeFile( - path.join(tempDir, 'node_modules', 'hello-pkg', 'bin', 'hello.js'), - [ - '#!/usr/bin/env node', - "console.log(`hello ${process.argv.slice(2).join(' ')}`);", - ].join('\n'), - ); - await mkdir(path.join(tempDir, 'node_modules', '.bin'), { recursive: true }); - await symlink( - '../hello-pkg/bin/hello.js', - path.join(tempDir, 'node_modules', '.bin', 'hello-js'), - ); - - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - await kernel.mount(createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] })); - await kernel.mount(createNodeRuntime()); - ctx = { kernel, vfs, dispose: () => kernel.dispose() }; - - const guestScriptPath = '/tmp/spawn-bin-check.js'; - const code = [ - "const { spawnSync } = require('child_process');", - "const env = { ...process.env, PATH: `/node_modules/.bin:${process.env.PATH}` };", - "const result = spawnSync('hello-js', ['from-child'], { encoding: 'utf8', env });", - "console.log(JSON.stringify({ status: result.status, stdout: result.stdout, stderr: result.stderr }));", - ].join(' '); - await ctx.kernel.writeFile(guestScriptPath, code); - const result = await ctx.kernel.exec(`node ${guestScriptPath}`); - expect(result.exitCode).toBe(0); - - const payload = JSON.parse(result.stdout.trim()) as { - status: number | null; - stdout: string; - stderr: string; - }; - expect(payload.status).toBe(0); - expect(payload.stdout.trim()).toBe('hello from-child'); - expect(payload.stderr).toBe(''); - }, 20_000); -}); - -// --------------------------------------------------------------------------- -// kernel.exec() -- node --version -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: exec --version', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('node --version outputs semver pattern', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const result = await ctx.kernel.exec('node --version'); - expect(result.exitCode).toBe(0); - // Node version format: vNN.NN.NN - expect(result.stdout.trim()).toMatch(/^v\d+\.\d+\.\d+/); - }); -}); - -// --------------------------------------------------------------------------- -// kernel.exec() -- node with no args + closed stdin -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: exec no args', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await ctx?.dispose(); - }); - - it('node with no args and closed stdin exits cleanly', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - // Pipe empty input so stdin is immediately closed - const result = await ctx.kernel.exec('echo -n "" | node', { timeout: 10_000 }); - // Should exit without hanging. Any exit code is acceptable. - // (real Node exits 0 in this case) - expect(typeof result.exitCode).toBe('number'); - }, 15_000); -}); - -// --------------------------------------------------------------------------- -// TerminalHarness (PTY path) -- stdout verification -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: terminal stdout', () => { - let harness: TerminalHarness; - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await harness?.dispose(); - await ctx?.dispose(); - }); - - it('node -e console.log output visible on terminal', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "console.log(\'MARKER\')"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(findOutputLine(screen, 'MARKER')).toBeDefined(); - }, 15_000); - - it('node -e delayed output visible on terminal', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "setTimeout(()=>console.log(\'LATE\'),100)"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(findOutputLine(screen, 'LATE')).toBeDefined(); - }, 15_000); -}); - -// --------------------------------------------------------------------------- -// TerminalHarness (PTY path) -- stderr verification -// --------------------------------------------------------------------------- - -describeIf(!skipReason, 'node binary: terminal stderr', () => { - let harness: TerminalHarness; - let ctx: IntegrationKernelResult; - - afterEach(async () => { - await harness?.dispose(); - await ctx?.dispose(); - }); - - it('node -e ReferenceError visible on terminal', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "unknownVar"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toContain('ReferenceError'); - }, 15_000); - - it('node -e throw Error visible on terminal', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - const stderrChunks: Uint8Array[] = []; - const proc = ctx.kernel.spawn('node', ['-e', "throw new Error('boom')"], { - onStderr: (chunk) => stderrChunks.push(chunk), - }); - - const exitCode = await proc.wait(); - expect(exitCode).not.toBe(0); - expect(decodeChunks(stderrChunks)).toContain('boom'); - }, 15_000); - - it('node -e SyntaxError visible on terminal', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - harness = new TerminalHarness(ctx.kernel); - - await harness.waitFor(PROMPT); - await harness.type('node -e "({"\n'); - await harness.waitFor(PROMPT, 2, 10_000); - - const screen = harness.screenshotTrimmed(); - expect(screen).toMatch(/SyntaxError|Unexpected/); - }, 15_000); -}); diff --git a/registry/tests/kernel/repro-npm-install.ts b/registry/tests/kernel/repro-npm-install.ts deleted file mode 100644 index 03358f920..000000000 --- a/registry/tests/kernel/repro-npm-install.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { COMMANDS_DIR, createKernel, NodeFileSystem, createWasmVmRuntime, createNodeRuntime } from '../helpers.ts'; - -const tempDir = await mkdtemp(path.join(tmpdir(), 'kernel-npm-install-repro-')); -console.log('tempDir', tempDir); -try { - await writeFile(path.join(tempDir, 'package.json'), JSON.stringify({name:'test-npm-install', private:true, dependencies:{'left-pad':'1.3.0'}})); - const vfs = new NodeFileSystem({ root: tempDir }); - const kernel = createKernel({ filesystem: vfs, cwd: '/' }); - await kernel.mount(createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] })); - await kernel.mount(createNodeRuntime()); - try { - const installResult = await kernel.exec('npm install', { cwd: '/' }); - console.log('exitCode', installResult.exitCode); - console.log('stdout >>>\n' + installResult.stdout + '\n<<<'); - console.log('stderr >>>\n' + installResult.stderr + '\n<<<'); - } finally { - await kernel.dispose(); - } -} finally { - await rm(tempDir, { recursive: true, force: true }); -} diff --git a/registry/tests/kernel/shim-streaming.test.ts b/registry/tests/kernel/shim-streaming.test.ts deleted file mode 100644 index aad4e4de5..000000000 --- a/registry/tests/kernel/shim-streaming.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest'; -import { - createIntegrationKernel, - describeIf, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'WASM shim command smoke', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) { - await ctx.dispose(); - } - }); - - it('nohup and stdbuf execute guest shell commands through WasmVM', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - - const nohupResult = await ctx.kernel.exec("nohup sh -c 'printf alpha'"); - expect(nohupResult.exitCode).toBe(0); - expect(nohupResult.stdout).toBe('alpha'); - - const stdbufResult = await ctx.kernel.exec("stdbuf -oL sh -c 'printf beta'"); - expect(stdbufResult.exitCode).toBe(0); - expect(stdbufResult.stdout).toBe('beta'); - }); -}); diff --git a/registry/tests/kernel/signal-forwarding.test.ts b/registry/tests/kernel/signal-forwarding.test.ts deleted file mode 100644 index 117b079da..000000000 --- a/registry/tests/kernel/signal-forwarding.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Integration tests for signal forwarding across runtimes. - * - * Verifies that kill(pid, signal) routes correctly through the kernel - * to the owning runtime driver, regardless of which runtime spawned - * the process. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'signal forwarding (integration)', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('SIGTERM terminates a long-running WasmVM process', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - - // Spawn a process that would run for 60 seconds - const proc = ctx.kernel.spawn('sleep', ['60']); - expect(proc.pid).toBeGreaterThan(0); - expect(ctx.kernel.processes.get(proc.pid)?.status).toBe('running'); - - // Send SIGTERM - proc.kill(15); - const code = await proc.wait(); - - // Process should exit with non-zero code (signal termination) - expect(code).not.toBe(0); - }, 10_000); - - it('SIGKILL terminates a WasmVM process', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - - const proc = ctx.kernel.spawn('sleep', ['60']); - proc.kill(9); // SIGKILL - const code = await proc.wait(); - - expect(code).not.toBe(0); - }, 10_000); - - it('Node process can be killed via SIGTERM', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - // Spawn a Node process that hangs - const proc = ctx.kernel.spawn('node', ['-e', 'setTimeout(()=>{},60000)']); - expect(proc.pid).toBeGreaterThan(0); - - proc.kill(15); - const code = await proc.wait(); - - expect(code).not.toBe(0); - }, 10_000); - - it('cross-runtime: WasmVM process killed while Node process runs', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - // Spawn processes in both runtimes - const wasmProc = ctx.kernel.spawn('sleep', ['60']); - const nodeProc = ctx.kernel.spawn('node', ['-e', 'setTimeout(()=>{},60000)']); - - expect(wasmProc.pid).not.toBe(nodeProc.pid); - - // Kill only the WasmVM process - wasmProc.kill(15); - const wasmCode = await wasmProc.wait(); - expect(wasmCode).not.toBe(0); - - // Node process should still be running - expect(ctx.kernel.processes.get(nodeProc.pid)?.status).toBe('running'); - - // Clean up the Node process - nodeProc.kill(15); - await nodeProc.wait(); - }, 15_000); - - it('killing a non-existent PID returns ESRCH', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm'] }); - - // Spawn and wait for a process to exit - const result = await ctx.kernel.exec('echo done'); - expect(result.exitCode).toBe(0); - - // Try to kill a PID that doesn't exist - expect(() => ctx.kernel.spawn('true', []).kill.call( - { kill: () => {} }, // dummy - )).not.toThrow(); // sanity check - - // Direct kernel interface kill on bad PID. Spawn a helper to get KI access. - // Since Kernel doesn't expose kill() directly, verify through the process table - // by spawning and killing a process that already exited. - const proc = ctx.kernel.spawn('true', []); - await proc.wait(); - // kill after exit is a no-op (not an error) - proc.kill(15); - }, 10_000); -}); diff --git a/registry/tests/kernel/tree-test.test.ts b/registry/tests/kernel/tree-test.test.ts deleted file mode 100644 index 850b5e371..000000000 --- a/registry/tests/kernel/tree-test.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Tree command behavior tests. - * - * Verifies that `tree` works correctly in both kernel.exec() and - * interactive shell modes, including edge cases like nonexistent paths, - * nested directories, and empty directories. - */ -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const wasmSkip = skipUnlessWasmBuilt(); -const TREE_COMMAND_TIMEOUT_MS = 20_000; -const TREE_TEST_TIMEOUT_MS = 30_000; - -describeIf(!wasmSkip, 'tree command behavior', () => { - let ctx: IntegrationKernelResult; - afterEach(async () => { await ctx?.dispose().catch(() => {}); }); - - // ----------------------------------------------------------------------- - // kernel.exec tests - // ----------------------------------------------------------------------- - - it('kernel.exec tree / returns with directory listing', async () => { - ctx = await createIntegrationKernel(); - const result = await ctx.kernel.exec('tree /', { - timeout: TREE_COMMAND_TIMEOUT_MS, - }); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('bin'); - // Tree summary line - expect(result.stdout).toMatch(/\d+ director/); - expect(result.stdout).toMatch(/\d+ file/); - }, TREE_TEST_TIMEOUT_MS); - - it('kernel.exec tree /nonexistent returns non-zero with error', async () => { - ctx = await createIntegrationKernel(); - const result = await ctx.kernel.exec('tree /nonexistent', { - timeout: TREE_COMMAND_TIMEOUT_MS, - }); - // tree should report an error for non-existent path - const combined = result.stdout + result.stderr; - expect(combined).toContain('nonexistent'); - }, TREE_TEST_TIMEOUT_MS); - - it('tree on VFS with 3-level nested directories renders correct structure', async () => { - ctx = await createIntegrationKernel(); - const enc = new TextEncoder(); - ctx.vfs.writeFile('/project/src/lib/utils.ts', enc.encode('export {}')); - ctx.vfs.writeFile('/project/src/lib/types.ts', enc.encode('export {}')); - ctx.vfs.writeFile('/project/src/index.ts', enc.encode('export {}')); - ctx.vfs.writeFile('/project/README.md', enc.encode('# project')); - - const result = await ctx.kernel.exec('tree /project', { - timeout: TREE_COMMAND_TIMEOUT_MS, - }); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('src'); - expect(result.stdout).toContain('lib'); - expect(result.stdout).toContain('utils.ts'); - expect(result.stdout).toContain('types.ts'); - expect(result.stdout).toContain('index.ts'); - expect(result.stdout).toContain('README.md'); - // Should show 2 directories (src, lib) and 4 files - expect(result.stdout).toMatch(/2 director/); - expect(result.stdout).toMatch(/4 file/); - }, TREE_TEST_TIMEOUT_MS); - - it('tree on empty directory shows minimal output', async () => { - ctx = await createIntegrationKernel(); - ctx.vfs.mkdir('/empty'); - - const result = await ctx.kernel.exec('tree /empty', { - timeout: TREE_COMMAND_TIMEOUT_MS, - }); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('/empty'); - // Empty directory: 0 directories, 0 files - expect(result.stdout).toMatch(/0 director/); - expect(result.stdout).toMatch(/0 file/); - }, TREE_TEST_TIMEOUT_MS); - - // ----------------------------------------------------------------------- - // Interactive shell tests - // ----------------------------------------------------------------------- - - it('interactive shell: tree / produces output and prompt returns', async () => { - ctx = await createIntegrationKernel(); - const shell = ctx.kernel.openShell(); - - let output = ''; - shell.onData = (data) => { output += new TextDecoder().decode(data); }; - - // Wait for initial prompt - await new Promise((r) => setTimeout(r, 1500)); - output = ''; - - shell.write('tree /\n'); - - // Wait for tree completion (summary line + new prompt) - const start = Date.now(); - while (Date.now() - start < TREE_COMMAND_TIMEOUT_MS) { - await new Promise((r) => setTimeout(r, 200)); - if (output.includes('file') && output.includes('$ ')) break; - } - - expect(output).toContain('bin'); - expect(output).toMatch(/\d+ file/); - // Prompt returned - expect(output).toContain('$ '); - - shell.write('exit\n'); - await Promise.race([ - shell.wait(), - new Promise((_, rej) => setTimeout(() => rej('timeout'), 3000)), - ]).catch(() => {}); - }, TREE_TEST_TIMEOUT_MS); - - it('tree does not hang when stdin is an empty PTY', async () => { - ctx = await createIntegrationKernel(); - const shell = ctx.kernel.openShell(); - - let output = ''; - shell.onData = (data) => { output += new TextDecoder().decode(data); }; - - await new Promise((r) => setTimeout(r, 1500)); - output = ''; - - // tree never reads stdin. It should complete regardless of PTY stdin state. - shell.write('tree /\n'); - - const start = Date.now(); - while (Date.now() - start < TREE_COMMAND_TIMEOUT_MS) { - await new Promise((r) => setTimeout(r, 200)); - if (output.includes('file') && output.includes('$ ')) break; - } - - const elapsed = Date.now() - start; - expect(elapsed).toBeLessThan(TREE_COMMAND_TIMEOUT_MS); - expect(output).toContain('bin'); - - shell.write('exit\n'); - await Promise.race([ - shell.wait(), - new Promise((_, rej) => setTimeout(() => rej('timeout'), 3000)), - ]).catch(() => {}); - }, TREE_TEST_TIMEOUT_MS); -}); diff --git a/registry/tests/kernel/vfs-consistency.test.ts b/registry/tests/kernel/vfs-consistency.test.ts deleted file mode 100644 index c4a1fa04a..000000000 --- a/registry/tests/kernel/vfs-consistency.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Cross-runtime VFS consistency tests. - * - * Verifies that file writes in one runtime are immediately visible to - * reads in another runtime, since all runtimes share the kernel VFS. - * - * Gracefully skipped when the WASM binary is not built. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { - describeIf, - createIntegrationKernel, - skipUnlessWasmBuilt, -} from './helpers.ts'; -import type { IntegrationKernelResult } from './helpers.ts'; - -const skipReason = skipUnlessWasmBuilt(); - -describeIf(!skipReason, 'cross-runtime VFS consistency', () => { - let ctx: IntegrationKernelResult; - - afterEach(async () => { - if (ctx) await ctx.dispose(); - }); - - it('kernel write visible to Node', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - await ctx.kernel.writeFile('/tmp/test.txt', 'hello'); - - const result = await ctx.kernel.exec( - `node -e "process.stdout.write(require('fs').readFileSync('/tmp/test.txt','utf8'))"`, - ); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('hello'); - }); - - it('Node write visible to WasmVM', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - // Node writes a file - const writeResult = await ctx.kernel.exec( - `node -e "require('fs').writeFileSync('/tmp/node-wrote.txt','from-node')"`, - ); - expect(writeResult.exitCode).toBe(0); - - // WasmVM reads it via cat - const readResult = await ctx.kernel.exec('cat /tmp/node-wrote.txt'); - expect(readResult.exitCode).toBe(0); - expect(readResult.stdout).toContain('from-node'); - }); - - it('Node write visible to kernel API', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - const writeResult = await ctx.kernel.exec( - `node -e "require('fs').writeFileSync('/tmp/k.txt','data')"`, - ); - expect(writeResult.exitCode).toBe(0); - - const content = await ctx.vfs.readTextFile('/tmp/k.txt'); - expect(content).toBe('data'); - }); - - it('directory listing consistent across runtimes', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - // Create 3 files via kernel API - await ctx.kernel.writeFile('/tmp/a.txt', 'a'); - await ctx.kernel.writeFile('/tmp/b.txt', 'b'); - await ctx.kernel.writeFile('/tmp/c.txt', 'c'); - - // WasmVM ls - const lsResult = await ctx.kernel.exec('ls /tmp'); - expect(lsResult.exitCode).toBe(0); - - // Node readdirSync - const nodeResult = await ctx.kernel.exec( - `node -e "console.log(require('fs').readdirSync('/tmp').sort().join(','))"`, - ); - expect(nodeResult.exitCode).toBe(0); - - // Both should list the same files - const lsFiles = lsResult.stdout - .trim() - .split(/\s+/) - .filter(Boolean) - .sort(); - const nodeFiles = nodeResult.stdout.trim().split(',').filter(Boolean).sort(); - - expect(lsFiles).toContain('a.txt'); - expect(lsFiles).toContain('b.txt'); - expect(lsFiles).toContain('c.txt'); - expect(nodeFiles).toContain('a.txt'); - expect(nodeFiles).toContain('b.txt'); - expect(nodeFiles).toContain('c.txt'); - }); - - it('ENOENT consistent across runtimes', async () => { - ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] }); - - // WasmVM cat nonexistent file - const catResult = await ctx.kernel.exec('cat /nonexistent'); - expect(catResult.exitCode).not.toBe(0); - - // Node readFileSync nonexistent file - const nodeResult = await ctx.kernel.exec( - `node -e "require('fs').readFileSync('/nonexistent')"`, - ); - expect(nodeResult.exitCode).not.toBe(0); - }); -}); diff --git a/registry/tests/projects/astro-pass/astro.config.mjs b/registry/tests/projects/astro-pass/astro.config.mjs deleted file mode 100644 index ce7d8d2b3..000000000 --- a/registry/tests/projects/astro-pass/astro.config.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from "astro/config"; -import react from "@astrojs/react"; - -export default defineConfig({ - integrations: [react()], -}); diff --git a/registry/tests/projects/astro-pass/fixture.json b/registry/tests/projects/astro-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/astro-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/astro-pass/package.json b/registry/tests/projects/astro-pass/package.json deleted file mode 100644 index 7fe1496c1..000000000 --- a/registry/tests/projects/astro-pass/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "project-matrix-astro-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "@astrojs/react": "3.6.2", - "astro": "4.15.9", - "react": "18.3.1", - "react-dom": "18.3.1" - }, - "pnpm": { - "overrides": { - "esbuild": "npm:esbuild-wasm@0.21.5", - "rollup": "npm:@rollup/wasm-node@4.61.0" - } - } -} diff --git a/registry/tests/projects/astro-pass/src/components/Counter.jsx b/registry/tests/projects/astro-pass/src/components/Counter.jsx deleted file mode 100644 index a735e52d8..000000000 --- a/registry/tests/projects/astro-pass/src/components/Counter.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useState } from "react"; - -export default function Counter() { - const [count, setCount] = useState(0); - return ( - - ); -} diff --git a/registry/tests/projects/astro-pass/src/index.js b/registry/tests/projects/astro-pass/src/index.js deleted file mode 100644 index b69c0ef1d..000000000 --- a/registry/tests/projects/astro-pass/src/index.js +++ /dev/null @@ -1,71 +0,0 @@ -"use strict"; - -var fs = require("fs"); -var path = require("path"); - -var projectDir = path.resolve(__dirname, ".."); -var distDir = path.join(projectDir, "dist"); - -function ensureBuild() { - try { - fs.statSync(path.join(distDir, "index.html")); - return; - } catch (e) { - // Build output missing — run build - } - var execFileSync = require("child_process").execFileSync; - var astroBin = path.join(projectDir, "node_modules", "astro", "astro.js"); - var buildEnv = Object.assign({}, process.env); - if (!buildEnv.PATH) { - buildEnv.PATH = - "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; - } - buildEnv.ASTRO_TELEMETRY_DISABLED = "1"; - execFileSync(process.execPath, [astroBin, "build"], { - cwd: projectDir, - stdio: "pipe", - timeout: 60000, - env: buildEnv, - }); -} - -function main() { - ensureBuild(); - - var results = []; - - // Check index.html was generated - var indexHtml = fs.readFileSync(path.join(distDir, "index.html"), "utf8"); - results.push({ - check: "index-html", - exists: true, - hasContent: indexHtml.indexOf("Hello from Astro") !== -1, - hasScript: indexHtml.indexOf(" - - - Astro App - - -

Hello from Astro

- - - diff --git a/registry/tests/projects/axios-pass/fixture.json b/registry/tests/projects/axios-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/axios-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/axios-pass/package.json b/registry/tests/projects/axios-pass/package.json deleted file mode 100644 index cab0ed0ec..000000000 --- a/registry/tests/projects/axios-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-axios-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "axios": "1.7.9" - } -} diff --git a/registry/tests/projects/axios-pass/src/index.js b/registry/tests/projects/axios-pass/src/index.js deleted file mode 100644 index 9df311fb3..000000000 --- a/registry/tests/projects/axios-pass/src/index.js +++ /dev/null @@ -1,54 +0,0 @@ -"use strict"; - -const http = require("http"); -const axios = require("axios"); - -const client = axios.create({ adapter: "fetch" }); - -const server = http.createServer((req, res) => { - if (req.method === "GET" && req.url === "/hello") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ message: "hello" })); - } else if (req.method === "GET" && req.url === "/users/42") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ id: "42", name: "test-user" })); - } else if (req.method === "POST" && req.url === "/data") { - let body = ""; - req.on("data", (chunk) => (body += chunk)); - req.on("end", () => { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ method: "POST", received: JSON.parse(body) })); - }); - } else { - res.writeHead(404); - res.end(); - } -}); - -async function main() { - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); - const port = server.address().port; - const base = "http://127.0.0.1:" + port; - - try { - const results = []; - - const r1 = await client.get(base + "/hello"); - results.push({ route: "GET /hello", status: r1.status, body: r1.data }); - - const r2 = await client.get(base + "/users/42"); - results.push({ route: "GET /users/42", status: r2.status, body: r2.data }); - - const r3 = await client.post(base + "/data", { key: "value" }); - results.push({ route: "POST /data", status: r3.status, body: r3.data }); - - console.log(JSON.stringify(results)); - } finally { - await new Promise((resolve) => server.close(resolve)); - } -} - -main().catch((err) => { - console.error(err.message); - process.exit(1); -}); diff --git a/registry/tests/projects/bcryptjs-pass/fixture.json b/registry/tests/projects/bcryptjs-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/bcryptjs-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/bcryptjs-pass/package.json b/registry/tests/projects/bcryptjs-pass/package.json deleted file mode 100644 index 379988725..000000000 --- a/registry/tests/projects/bcryptjs-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-bcryptjs-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "bcryptjs": "2.4.3" - } -} diff --git a/registry/tests/projects/bcryptjs-pass/pnpm-lock.yaml b/registry/tests/projects/bcryptjs-pass/pnpm-lock.yaml deleted file mode 100644 index a2a764912..000000000 --- a/registry/tests/projects/bcryptjs-pass/pnpm-lock.yaml +++ /dev/null @@ -1,22 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - bcryptjs: - specifier: 2.4.3 - version: 2.4.3 - -packages: - - bcryptjs@2.4.3: - resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} - -snapshots: - - bcryptjs@2.4.3: {} diff --git a/registry/tests/projects/bcryptjs-pass/src/index.js b/registry/tests/projects/bcryptjs-pass/src/index.js deleted file mode 100644 index 78441d2c9..000000000 --- a/registry/tests/projects/bcryptjs-pass/src/index.js +++ /dev/null @@ -1,26 +0,0 @@ -"use strict"; - -const bcrypt = require("bcryptjs"); - -// Hash a password with explicit salt rounds -const password = "testPassword123"; -const salt = bcrypt.genSaltSync(4); -const hash = bcrypt.hashSync(password, salt); - -// Verify correct password -const correctMatch = bcrypt.compareSync(password, hash); - -// Verify wrong password -const wrongMatch = bcrypt.compareSync("wrongPassword", hash); - -// Hash format validation -const isValidHash = hash.startsWith("$2a$04$") && hash.length === 60; - -const result = { - hashLength: hash.length, - correctMatch, - wrongMatch, - isValidHash, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/bun-layout-pass/bun.lock b/registry/tests/projects/bun-layout-pass/bun.lock deleted file mode 100644 index 230026f9d..000000000 --- a/registry/tests/projects/bun-layout-pass/bun.lock +++ /dev/null @@ -1,15 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "project-matrix-bun-layout-pass", - "dependencies": { - "left-pad": "0.0.3", - }, - }, - }, - "packages": { - "left-pad": ["left-pad@0.0.3", "", {}, "sha512-Qli5dSpAXQOSw1y/M+uBKT37rj6iZAQMz6Uy5/ZYGIhBLS/ODRHqL4XIDvSAtYpjfia0XKNztlPFa806TWw5Gw=="], - } -} diff --git a/registry/tests/projects/bun-layout-pass/fixture.json b/registry/tests/projects/bun-layout-pass/fixture.json deleted file mode 100644 index 895c6330a..000000000 --- a/registry/tests/projects/bun-layout-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "bun" -} diff --git a/registry/tests/projects/bun-layout-pass/package.json b/registry/tests/projects/bun-layout-pass/package.json deleted file mode 100644 index 60d39f727..000000000 --- a/registry/tests/projects/bun-layout-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-bun-layout-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "left-pad": "0.0.3" - } -} diff --git a/registry/tests/projects/bun-layout-pass/src/index.js b/registry/tests/projects/bun-layout-pass/src/index.js deleted file mode 100644 index 6ab481e2f..000000000 --- a/registry/tests/projects/bun-layout-pass/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const leftPad = require("left-pad"); - -const results = [ - { input: "hello", width: 10, padded: leftPad("hello", 10) }, - { input: "42", width: 5, fill: "0", padded: leftPad("42", 5, "0") }, - { input: "", width: 3, padded: leftPad("", 3) }, -]; - -console.log(JSON.stringify(results)); diff --git a/registry/tests/projects/chalk-pass/fixture.json b/registry/tests/projects/chalk-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/chalk-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/chalk-pass/package.json b/registry/tests/projects/chalk-pass/package.json deleted file mode 100644 index f08c340d5..000000000 --- a/registry/tests/projects/chalk-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-chalk-pass", - "private": true, - "type": "module", - "dependencies": { - "chalk": "5.4.1" - } -} diff --git a/registry/tests/projects/chalk-pass/src/index.js b/registry/tests/projects/chalk-pass/src/index.js deleted file mode 100644 index 7d4a196cb..000000000 --- a/registry/tests/projects/chalk-pass/src/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import { Chalk } from "chalk"; - -// Force color level 1 (basic ANSI) for deterministic output across environments -const c = new Chalk({ level: 1 }); - -const red = c.red("red"); -const green = c.green("green"); -const blue = c.blue("blue"); -const bold = c.bold("bold"); -const underline = c.underline("underline"); -const nested = c.red.bold.underline("nested"); -const bg = c.bgYellow.black("highlight"); -const combined = c.italic(c.cyan("italic-cyan")); - -const result = { - red, - green, - blue, - bold, - underline, - nested, - bg, - combined, - supportsLevel: typeof c.level, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/conditional-exports-pass/fixture.json b/registry/tests/projects/conditional-exports-pass/fixture.json deleted file mode 100644 index a534708f5..000000000 --- a/registry/tests/projects/conditional-exports-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "npm" -} diff --git a/registry/tests/projects/conditional-exports-pass/package-lock.json b/registry/tests/projects/conditional-exports-pass/package-lock.json deleted file mode 100644 index a91b38356..000000000 --- a/registry/tests/projects/conditional-exports-pass/package-lock.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "project-matrix-conditional-exports-pass", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "project-matrix-conditional-exports-pass", - "dependencies": { - "@cond-test/lib": "file:packages/cond-exports-lib" - } - }, - "node_modules/@cond-test/lib": { - "resolved": "packages/cond-exports-lib", - "link": true - }, - "packages/cond-exports-lib": { - "name": "@cond-test/lib", - "version": "1.0.0" - } - } -} diff --git a/registry/tests/projects/conditional-exports-pass/package.json b/registry/tests/projects/conditional-exports-pass/package.json deleted file mode 100644 index f4fa74bef..000000000 --- a/registry/tests/projects/conditional-exports-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-conditional-exports-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "@cond-test/lib": "file:packages/cond-exports-lib" - } -} diff --git a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/feature-cjs.js b/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/feature-cjs.js deleted file mode 100644 index bd42e585e..000000000 --- a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/feature-cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -module.exports = { name: "feature-cjs", enabled: true }; diff --git a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/feature-default.js b/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/feature-default.js deleted file mode 100644 index ebb7fa197..000000000 --- a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/feature-default.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -module.exports = { name: "feature-default", enabled: true }; diff --git a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/main-cjs.js b/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/main-cjs.js deleted file mode 100644 index 3b61bdc4e..000000000 --- a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/main-cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -module.exports = { entry: "main-cjs", version: "1.0.0" }; diff --git a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/main-default.js b/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/main-default.js deleted file mode 100644 index 37cfc368e..000000000 --- a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/lib/main-default.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -module.exports = { entry: "main-default", version: "1.0.0" }; diff --git a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/package.json b/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/package.json deleted file mode 100644 index abebeb237..000000000 --- a/registry/tests/projects/conditional-exports-pass/packages/cond-exports-lib/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@cond-test/lib", - "version": "1.0.0", - "exports": { - ".": { - "require": "./lib/main-cjs.js", - "default": "./lib/main-default.js" - }, - "./feature": { - "require": "./lib/feature-cjs.js", - "default": "./lib/feature-default.js" - } - } -} diff --git a/registry/tests/projects/conditional-exports-pass/src/index.js b/registry/tests/projects/conditional-exports-pass/src/index.js deleted file mode 100644 index c7382ed51..000000000 --- a/registry/tests/projects/conditional-exports-pass/src/index.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; - -const main = require("@cond-test/lib"); -const feature = require("@cond-test/lib/feature"); - -const result = { - mainEntry: main.entry, - mainVersion: main.version, - featureName: feature.name, - featureEnabled: feature.enabled -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/crypto-random-pass/fixture.json b/registry/tests/projects/crypto-random-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/crypto-random-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/crypto-random-pass/package.json b/registry/tests/projects/crypto-random-pass/package.json deleted file mode 100644 index 20cbb8795..000000000 --- a/registry/tests/projects/crypto-random-pass/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "project-matrix-crypto-random-pass", - "private": true, - "type": "commonjs" -} diff --git a/registry/tests/projects/crypto-random-pass/src/index.js b/registry/tests/projects/crypto-random-pass/src/index.js deleted file mode 100644 index df8301dc9..000000000 --- a/registry/tests/projects/crypto-random-pass/src/index.js +++ /dev/null @@ -1,15 +0,0 @@ -const bytes = new Uint8Array(16); -crypto.getRandomValues(bytes); - -const uuid = crypto.randomUUID(); -const uuidV4Pattern = - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; - -console.log( - JSON.stringify({ - uuidV4: uuidV4Pattern.test(uuid), - uuidLength: uuid.length, - randomValuesLength: bytes.length, - arrayTag: Object.prototype.toString.call(bytes), - }), -); diff --git a/registry/tests/projects/dotenv-pass/.env b/registry/tests/projects/dotenv-pass/.env deleted file mode 100644 index 542d15888..000000000 --- a/registry/tests/projects/dotenv-pass/.env +++ /dev/null @@ -1 +0,0 @@ -GREETING=hello-from-dotenv diff --git a/registry/tests/projects/dotenv-pass/fixture.json b/registry/tests/projects/dotenv-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/dotenv-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/dotenv-pass/package.json b/registry/tests/projects/dotenv-pass/package.json deleted file mode 100644 index e17ab2a36..000000000 --- a/registry/tests/projects/dotenv-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-dotenv-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "dotenv": "16.6.1" - } -} diff --git a/registry/tests/projects/dotenv-pass/src/index.js b/registry/tests/projects/dotenv-pass/src/index.js deleted file mode 100644 index e43a90fd1..000000000 --- a/registry/tests/projects/dotenv-pass/src/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const path = require("node:path"); -const dotenv = require("dotenv"); - -const result = dotenv.config({ - path: path.join(__dirname, "..", ".env"), -}); - -if (result.error) { - throw result.error; -} - -console.log(`GREETING=${process.env.GREETING}`); diff --git a/registry/tests/projects/drizzle-pass/fixture.json b/registry/tests/projects/drizzle-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/drizzle-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/drizzle-pass/package.json b/registry/tests/projects/drizzle-pass/package.json deleted file mode 100644 index 473df90e7..000000000 --- a/registry/tests/projects/drizzle-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-drizzle-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "drizzle-orm": "0.45.1" - } -} diff --git a/registry/tests/projects/drizzle-pass/src/index.js b/registry/tests/projects/drizzle-pass/src/index.js deleted file mode 100644 index 83a31b2b8..000000000 --- a/registry/tests/projects/drizzle-pass/src/index.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; - -const { pgTable, text, integer, serial, varchar, boolean } = require("drizzle-orm/pg-core"); -const { eq, and, sql } = require("drizzle-orm"); - -// Define a table schema without connecting to a database -const users = pgTable("users", { - id: serial("id").primaryKey(), - name: text("name").notNull(), - email: varchar("email", { length: 255 }).notNull(), - age: integer("age"), - active: boolean("active").default(true), -}); - -// Inspect schema shape -const tableName = users[Symbol.for("drizzle:Name")]; -const columnNames = Object.keys(users) - .filter((k) => typeof k === "string" && !k.startsWith("_")) - .sort(); -const idIsPrimary = users.id.primary; -const nameNotNull = users.name.notNull; -const emailLength = users.email.config ? users.email.config.length : null; - -// Verify operators exist -const eqExists = typeof eq === "function"; -const andExists = typeof and === "function"; -const sqlExists = typeof sql === "function"; - -// Verify sql template tag produces a fragment object -const fragment = sql`${users.id} = 1`; -const fragmentExists = fragment !== null && typeof fragment === "object"; - -const result = { - tableName, - columnNames, - idIsPrimary, - nameNotNull, - emailLength, - eqExists, - andExists, - sqlExists, - fragmentExists, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/esm-import-pass/fixture.json b/registry/tests/projects/esm-import-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/esm-import-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/esm-import-pass/package.json b/registry/tests/projects/esm-import-pass/package.json deleted file mode 100644 index 275266d7c..000000000 --- a/registry/tests/projects/esm-import-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-esm-import-pass", - "private": true, - "type": "module", - "dependencies": { - "hono": "4.7.5" - } -} diff --git a/registry/tests/projects/esm-import-pass/src/index.js b/registry/tests/projects/esm-import-pass/src/index.js deleted file mode 100644 index e39aaf03b..000000000 --- a/registry/tests/projects/esm-import-pass/src/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Hono } from "hono"; - -const app = new Hono(); - -const result = { - fetchType: typeof app.fetch, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/express-pass/fixture.json b/registry/tests/projects/express-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/express-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/express-pass/package.json b/registry/tests/projects/express-pass/package.json deleted file mode 100644 index a54ebe8e9..000000000 --- a/registry/tests/projects/express-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-express-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "express": "4.21.2" - } -} diff --git a/registry/tests/projects/express-pass/pnpm-lock.yaml b/registry/tests/projects/express-pass/pnpm-lock.yaml deleted file mode 100644 index b4cdc131a..000000000 --- a/registry/tests/projects/express-pass/pnpm-lock.yaml +++ /dev/null @@ -1,585 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - express: - specifier: 4.21.2 - version: 4.21.2 - -packages: - - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - express@4.21.2: - resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} - engines: {node: '>= 0.10.0'} - - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} - engines: {node: '>= 0.8'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - send@0.19.0: - resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} - engines: {node: '>= 0.8.0'} - - serve-static@1.16.2: - resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} - engines: {node: '>= 0.8.0'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - -snapshots: - - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - - array-flatten@1.1.1: {} - - body-parser@1.20.3: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - - cookie-signature@1.0.6: {} - - cookie@0.7.1: {} - - debug@2.6.9: - dependencies: - ms: 2.0.0 - - depd@2.0.0: {} - - destroy@1.2.0: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ee-first@1.1.1: {} - - encodeurl@1.0.2: {} - - encodeurl@2.0.0: {} - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - escape-html@1.0.3: {} - - etag@1.8.1: {} - - express@4.21.2: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.12 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - finalhandler@1.3.1: - dependencies: - debug: 2.6.9 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - forwarded@0.2.0: {} - - fresh@0.5.2: {} - - function-bind@1.1.2: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - gopd@1.2.0: {} - - has-symbols@1.1.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - inherits@2.0.4: {} - - ipaddr.js@1.9.1: {} - - math-intrinsics@1.1.0: {} - - media-typer@0.3.0: {} - - merge-descriptors@1.0.3: {} - - methods@1.1.2: {} - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime@1.6.0: {} - - ms@2.0.0: {} - - ms@2.1.3: {} - - negotiator@0.6.3: {} - - object-inspect@1.13.4: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - parseurl@1.3.3: {} - - path-to-regexp@0.1.12: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - qs@6.13.0: - dependencies: - side-channel: 1.1.0 - - range-parser@1.2.1: {} - - raw-body@2.5.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - - safe-buffer@5.2.1: {} - - safer-buffer@2.1.2: {} - - send@0.19.0: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - - serve-static@1.16.2: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.0 - transitivePeerDependencies: - - supports-color - - setprototypeof@1.2.0: {} - - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - statuses@2.0.1: {} - - toidentifier@1.0.1: {} - - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - - unpipe@1.0.0: {} - - utils-merge@1.0.1: {} - - vary@1.1.2: {} diff --git a/registry/tests/projects/express-pass/src/index.js b/registry/tests/projects/express-pass/src/index.js deleted file mode 100644 index 6bd051177..000000000 --- a/registry/tests/projects/express-pass/src/index.js +++ /dev/null @@ -1,64 +0,0 @@ -"use strict"; - -const http = require("http"); -const express = require("express"); - -const app = express(); - -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); - -app.get("/hello", (req, res) => { - res.json({ message: "hello" }); -}); - -app.get("/users/:id", (req, res) => { - res.json({ id: req.params.id, name: "test-user" }); -}); - -app.post("/data", (req, res) => { - res.json({ method: req.method, url: req.url }); -}); - -function request(method, path, port) { - return new Promise((resolve, reject) => { - const req = http.request( - { hostname: "127.0.0.1", port, path, method }, - (res) => { - let body = ""; - res.on("data", (chunk) => (body += chunk)); - res.on("end", () => resolve({ status: res.statusCode, body })); - }, - ); - req.on("error", reject); - req.end(); - }); -} - -async function main() { - const server = http.createServer(app); - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); - const port = server.address().port; - - try { - const results = []; - - const r1 = await request("GET", "/hello", port); - results.push({ route: "GET /hello", status: r1.status, body: JSON.parse(r1.body) }); - - const r2 = await request("GET", "/users/42", port); - results.push({ route: "GET /users/42", status: r2.status, body: JSON.parse(r2.body) }); - - const r3 = await request("POST", "/data", port); - results.push({ route: "POST /data", status: r3.status, body: JSON.parse(r3.body) }); - - console.log(JSON.stringify(results)); - } finally { - await new Promise((resolve) => server.close(resolve)); - } -} - -main().catch((err) => { - console.error(err.message); - process.exit(1); -}); diff --git a/registry/tests/projects/fastify-pass/fixture.json b/registry/tests/projects/fastify-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/fastify-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/fastify-pass/package.json b/registry/tests/projects/fastify-pass/package.json deleted file mode 100644 index 2dd44e9ba..000000000 --- a/registry/tests/projects/fastify-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-fastify-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "fastify": "5.3.3" - } -} diff --git a/registry/tests/projects/fastify-pass/pnpm-lock.yaml b/registry/tests/projects/fastify-pass/pnpm-lock.yaml deleted file mode 100644 index 15ee2bf2c..000000000 --- a/registry/tests/projects/fastify-pass/pnpm-lock.yaml +++ /dev/null @@ -1,352 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - fastify: - specifier: 5.3.3 - version: 5.3.3 - -packages: - - '@fastify/ajv-compiler@4.0.5': - resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} - - '@fastify/error@4.2.0': - resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} - - '@fastify/fast-json-stringify-compiler@5.0.3': - resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} - - '@fastify/forwarded@3.0.1': - resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} - - '@fastify/merge-json-schemas@0.2.1': - resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} - - '@fastify/proxy-addr@5.1.0': - resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} - - '@pinojs/redact@0.4.0': - resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - - abstract-logging@2.0.1: - resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - - avvio@9.2.0: - resolution: {integrity: sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==} - - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} - engines: {node: '>=18'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - fast-decode-uri-component@1.0.1: - resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-json-stringify@6.3.0: - resolution: {integrity: sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==} - - fast-querystring@1.1.2: - resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} - - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - - fastify@5.3.3: - resolution: {integrity: sha512-nCBiBCw9q6jPx+JJNVgO8JVnTXeUyrGcyTKPQikRkA/PanrFcOIo4R+ZnLeOLPZPGgzjomqfVarzE0kYx7qWiQ==} - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - find-my-way@9.5.0: - resolution: {integrity: sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==} - engines: {node: '>=20'} - - ipaddr.js@2.3.0: - resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} - engines: {node: '>= 10'} - - json-schema-ref-resolver@3.0.0: - resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - light-my-request@6.6.0: - resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} - - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - - pino-std-serializers@7.1.0: - resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} - - pino@9.14.0: - resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} - hasBin: true - - process-warning@4.0.1: - resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} - - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - ret@0.5.0: - resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} - engines: {node: '>=10'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - safe-regex2@5.1.0: - resolution: {integrity: sha512-pNHAuBW7TrcleFHsxBr5QMi/Iyp0ENjUKz7GCcX1UO7cMh+NmVK6HxQckNL1tJp1XAJVjG6B8OKIPqodqj9rtw==} - hasBin: true - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - - secure-json-parse@4.1.0: - resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - set-cookie-parser@2.7.2: - resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} - - sonic-boom@4.2.1: - resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - - toad-cache@3.7.0: - resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} - engines: {node: '>=12'} - -snapshots: - - '@fastify/ajv-compiler@4.0.5': - dependencies: - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) - fast-uri: 3.1.0 - - '@fastify/error@4.2.0': {} - - '@fastify/fast-json-stringify-compiler@5.0.3': - dependencies: - fast-json-stringify: 6.3.0 - - '@fastify/forwarded@3.0.1': {} - - '@fastify/merge-json-schemas@0.2.1': - dependencies: - dequal: 2.0.3 - - '@fastify/proxy-addr@5.1.0': - dependencies: - '@fastify/forwarded': 3.0.1 - ipaddr.js: 2.3.0 - - '@pinojs/redact@0.4.0': {} - - abstract-logging@2.0.1: {} - - ajv-formats@3.0.1(ajv@8.18.0): - optionalDependencies: - ajv: 8.18.0 - - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - atomic-sleep@1.0.0: {} - - avvio@9.2.0: - dependencies: - '@fastify/error': 4.2.0 - fastq: 1.20.1 - - cookie@1.1.1: {} - - dequal@2.0.3: {} - - fast-decode-uri-component@1.0.1: {} - - fast-deep-equal@3.1.3: {} - - fast-json-stringify@6.3.0: - dependencies: - '@fastify/merge-json-schemas': 0.2.1 - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) - fast-uri: 3.1.0 - json-schema-ref-resolver: 3.0.0 - rfdc: 1.4.1 - - fast-querystring@1.1.2: - dependencies: - fast-decode-uri-component: 1.0.1 - - fast-uri@3.1.0: {} - - fastify@5.3.3: - dependencies: - '@fastify/ajv-compiler': 4.0.5 - '@fastify/error': 4.2.0 - '@fastify/fast-json-stringify-compiler': 5.0.3 - '@fastify/proxy-addr': 5.1.0 - abstract-logging: 2.0.1 - avvio: 9.2.0 - fast-json-stringify: 6.3.0 - find-my-way: 9.5.0 - light-my-request: 6.6.0 - pino: 9.14.0 - process-warning: 5.0.0 - rfdc: 1.4.1 - secure-json-parse: 4.1.0 - semver: 7.7.4 - toad-cache: 3.7.0 - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - find-my-way@9.5.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-querystring: 1.1.2 - safe-regex2: 5.1.0 - - ipaddr.js@2.3.0: {} - - json-schema-ref-resolver@3.0.0: - dependencies: - dequal: 2.0.3 - - json-schema-traverse@1.0.0: {} - - light-my-request@6.6.0: - dependencies: - cookie: 1.1.1 - process-warning: 4.0.1 - set-cookie-parser: 2.7.2 - - on-exit-leak-free@2.1.2: {} - - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - - pino-std-serializers@7.1.0: {} - - pino@9.14.0: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.1.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.1 - thread-stream: 3.1.0 - - process-warning@4.0.1: {} - - process-warning@5.0.0: {} - - quick-format-unescaped@4.0.4: {} - - real-require@0.2.0: {} - - require-from-string@2.0.2: {} - - ret@0.5.0: {} - - reusify@1.1.0: {} - - rfdc@1.4.1: {} - - safe-regex2@5.1.0: - dependencies: - ret: 0.5.0 - - safe-stable-stringify@2.5.0: {} - - secure-json-parse@4.1.0: {} - - semver@7.7.4: {} - - set-cookie-parser@2.7.2: {} - - sonic-boom@4.2.1: - dependencies: - atomic-sleep: 1.0.0 - - split2@4.2.0: {} - - thread-stream@3.1.0: - dependencies: - real-require: 0.2.0 - - toad-cache@3.7.0: {} diff --git a/registry/tests/projects/fastify-pass/src/index.js b/registry/tests/projects/fastify-pass/src/index.js deleted file mode 100644 index 9626c186f..000000000 --- a/registry/tests/projects/fastify-pass/src/index.js +++ /dev/null @@ -1,76 +0,0 @@ -"use strict"; - -const http = require("http"); -const Fastify = require("fastify"); - -const app = Fastify({ logger: false }); - -app.get("/hello", async () => { - return { message: "hello" }; -}); - -app.get("/users/:id", async (request) => { - return { id: request.params.id, name: "test-user" }; -}); - -app.post("/data", async (request) => { - return { method: request.method, url: request.url, body: request.body }; -}); - -app.get("/async", async () => { - const value = await Promise.resolve(42); - return { value }; -}); - -function request(method, path, port, options) { - return new Promise((resolve, reject) => { - const headers = (options && options.headers) || {}; - const bodyData = options && options.body; - const req = http.request( - { hostname: "127.0.0.1", port, path, method, headers }, - (res) => { - let body = ""; - res.on("data", (chunk) => (body += chunk)); - res.on("end", () => resolve({ status: res.statusCode, body })); - }, - ); - req.on("error", reject); - if (bodyData) { - req.write(typeof bodyData === "string" ? bodyData : JSON.stringify(bodyData)); - } - req.end(); - }); -} - -async function main() { - await app.listen({ port: 0, host: "127.0.0.1" }); - const port = app.server.address().port; - - try { - const results = []; - - const r1 = await request("GET", "/hello", port); - results.push({ route: "GET /hello", status: r1.status, body: JSON.parse(r1.body) }); - - const r2 = await request("GET", "/users/42", port); - results.push({ route: "GET /users/42", status: r2.status, body: JSON.parse(r2.body) }); - - const r3 = await request("POST", "/data", port, { - headers: { "Content-Type": "application/json" }, - body: { key: "value" }, - }); - results.push({ route: "POST /data", status: r3.status, body: JSON.parse(r3.body) }); - - const r4 = await request("GET", "/async", port); - results.push({ route: "GET /async", status: r4.status, body: JSON.parse(r4.body) }); - - console.log(JSON.stringify(results)); - } finally { - await app.close(); - } -} - -main().catch((err) => { - console.error(err.message); - process.exit(1); -}); diff --git a/registry/tests/projects/fs-metadata-rename-pass/fixture.json b/registry/tests/projects/fs-metadata-rename-pass/fixture.json deleted file mode 100644 index 1509fc6e8..000000000 --- a/registry/tests/projects/fs-metadata-rename-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/fs-metadata-rename-pass/package.json b/registry/tests/projects/fs-metadata-rename-pass/package.json deleted file mode 100644 index 15da98079..000000000 --- a/registry/tests/projects/fs-metadata-rename-pass/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "fs-metadata-rename-pass", - "version": "1.0.0", - "private": true -} diff --git a/registry/tests/projects/fs-metadata-rename-pass/src/index.js b/registry/tests/projects/fs-metadata-rename-pass/src/index.js deleted file mode 100644 index 68ddf5472..000000000 --- a/registry/tests/projects/fs-metadata-rename-pass/src/index.js +++ /dev/null @@ -1,33 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -const root = path.join(process.cwd(), "tmp-fs-metadata-rename"); -fs.rmSync(root, { recursive: true, force: true }); -fs.mkdirSync(root, { recursive: true }); -fs.mkdirSync(path.join(root, "sub")); -fs.writeFileSync(path.join(root, "file.txt"), "x".repeat(2048)); - -const entries = fs - .readdirSync(root, { withFileTypes: true }) - .map((entry) => [entry.name, entry.isDirectory()]) - .sort((a, b) => a[0].localeCompare(b[0])); - -const filePath = path.join(root, "file.txt"); -const renamedPath = path.join(root, "renamed.txt"); -const statSize = fs.statSync(filePath).size; -const beforeExists = fs.existsSync(filePath); - -fs.renameSync(filePath, renamedPath); - -const summary = { - entries, - statSize, - beforeExists, - afterOldExists: fs.existsSync(filePath), - afterNewExists: fs.existsSync(renamedPath), - renamedSize: fs.statSync(renamedPath).size, -}; - -console.log(JSON.stringify(summary)); - -fs.rmSync(root, { recursive: true, force: true }); diff --git a/registry/tests/projects/ioredis-pass/fixture.json b/registry/tests/projects/ioredis-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/ioredis-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/ioredis-pass/package.json b/registry/tests/projects/ioredis-pass/package.json deleted file mode 100644 index 467640674..000000000 --- a/registry/tests/projects/ioredis-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-ioredis-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "ioredis": "5.4.2" - } -} diff --git a/registry/tests/projects/ioredis-pass/pnpm-lock.yaml b/registry/tests/projects/ioredis-pass/pnpm-lock.yaml deleted file mode 100644 index 07c8d0276..000000000 --- a/registry/tests/projects/ioredis-pass/pnpm-lock.yaml +++ /dev/null @@ -1,99 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - ioredis: - specifier: 5.4.2 - version: 5.4.2 - -packages: - - '@ioredis/commands@1.5.1': - resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} - - cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - - ioredis@5.4.2: - resolution: {integrity: sha512-0SZXGNGZ+WzISQ67QDyZ2x0+wVxjjUndtD8oSeik/4ajifeiRufed8fCb8QW8VMyi4MXcS+UO1k/0NGhvq1PAg==} - engines: {node: '>=12.22.0'} - - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - redis-errors@1.2.0: - resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} - engines: {node: '>=4'} - - redis-parser@3.0.0: - resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} - engines: {node: '>=4'} - - standard-as-callback@2.1.0: - resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - -snapshots: - - '@ioredis/commands@1.5.1': {} - - cluster-key-slot@1.1.2: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - denque@2.1.0: {} - - ioredis@5.4.2: - dependencies: - '@ioredis/commands': 1.5.1 - cluster-key-slot: 1.1.2 - debug: 4.4.3 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - - lodash.defaults@4.2.0: {} - - lodash.isarguments@3.1.0: {} - - ms@2.1.3: {} - - redis-errors@1.2.0: {} - - redis-parser@3.0.0: - dependencies: - redis-errors: 1.2.0 - - standard-as-callback@2.1.0: {} diff --git a/registry/tests/projects/ioredis-pass/src/index.js b/registry/tests/projects/ioredis-pass/src/index.js deleted file mode 100644 index 775b1183a..000000000 --- a/registry/tests/projects/ioredis-pass/src/index.js +++ /dev/null @@ -1,74 +0,0 @@ -"use strict"; - -var Redis = require("ioredis"); - -var result = {}; - -// Verify Redis constructor -result.redisExists = typeof Redis === "function"; - -// Verify key prototype methods -result.instanceMethods = [ - "connect", - "disconnect", - "quit", - "get", - "set", - "del", - "lpush", - "lrange", - "subscribe", - "unsubscribe", - "publish", - "pipeline", - "multi", -].filter(function (m) { - return typeof Redis.prototype[m] === "function"; -}); - -// Verify Cluster class -result.clusterExists = typeof Redis.Cluster === "function"; - -// Verify Command class -result.commandExists = typeof Redis.Command === "function"; - -// Create instance without connecting -var redis = new Redis({ - lazyConnect: true, - enableReadyCheck: false, - retryStrategy: function () { - return null; - }, -}); -result.instanceCreated = redis instanceof Redis; -result.hasOptions = typeof redis.options === "object" && redis.options !== null; -result.optionLazyConnect = redis.options.lazyConnect === true; - -// Event emitter functionality -result.hasOn = typeof redis.on === "function"; -result.hasEmit = typeof redis.emit === "function"; - -// Pipeline creation (no connection needed) -var pipeline = redis.pipeline(); -result.pipelineCreated = pipeline !== null && typeof pipeline === "object"; -result.pipelineMethods = ["set", "get", "del", "lpush", "lrange", "exec"].filter( - function (m) { - return typeof pipeline[m] === "function"; - }, -); - -// Multi/transaction creation (no connection needed) -var multi = redis.multi(); -result.multiCreated = multi !== null && typeof multi === "object"; -result.multiMethods = ["set", "get", "del", "exec"].filter(function (m) { - return typeof multi[m] === "function"; -}); - -// Verify Command can build commands -var cmd = new Redis.Command("SET", ["key", "value"]); -result.commandBuilt = cmd !== null && typeof cmd === "object"; -result.commandName = cmd.name; - -redis.disconnect(); - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/jsonwebtoken-pass/fixture.json b/registry/tests/projects/jsonwebtoken-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/jsonwebtoken-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/jsonwebtoken-pass/package.json b/registry/tests/projects/jsonwebtoken-pass/package.json deleted file mode 100644 index b49648881..000000000 --- a/registry/tests/projects/jsonwebtoken-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-jsonwebtoken-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "jsonwebtoken": "9.0.2" - } -} diff --git a/registry/tests/projects/jsonwebtoken-pass/src/index.js b/registry/tests/projects/jsonwebtoken-pass/src/index.js deleted file mode 100644 index 375e0a518..000000000 --- a/registry/tests/projects/jsonwebtoken-pass/src/index.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; - -const jwt = require("jsonwebtoken"); - -const secret = "test-secret-key-for-fixture"; - -// Sign a JWT with HS256 (default algorithm) -const payload = { sub: "user-123", name: "Alice", admin: true }; -const token = jwt.sign(payload, secret, { algorithm: "HS256", noTimestamp: true }); - -// Verify the token -const decoded = jwt.verify(token, secret); - -// Decode without verification -const unverified = jwt.decode(token, { complete: true }); - -// Verify with wrong secret fails -let verifyError = null; -try { - jwt.verify(token, "wrong-secret"); -} catch (err) { - verifyError = { name: err.name, message: err.message }; -} - -const result = { - token, - decoded: { sub: decoded.sub, name: decoded.name, admin: decoded.admin }, - header: unverified.header, - verifyError, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/lodash-es-pass/fixture.json b/registry/tests/projects/lodash-es-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/lodash-es-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/lodash-es-pass/package.json b/registry/tests/projects/lodash-es-pass/package.json deleted file mode 100644 index 6b5406fc8..000000000 --- a/registry/tests/projects/lodash-es-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-lodash-es-pass", - "private": true, - "type": "module", - "dependencies": { - "lodash-es": "4.17.21" - } -} diff --git a/registry/tests/projects/lodash-es-pass/pnpm-lock.yaml b/registry/tests/projects/lodash-es-pass/pnpm-lock.yaml deleted file mode 100644 index 42e1e9917..000000000 --- a/registry/tests/projects/lodash-es-pass/pnpm-lock.yaml +++ /dev/null @@ -1,22 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - lodash-es: - specifier: 4.17.21 - version: 4.17.21 - -packages: - - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - -snapshots: - - lodash-es@4.17.21: {} diff --git a/registry/tests/projects/lodash-es-pass/src/index.js b/registry/tests/projects/lodash-es-pass/src/index.js deleted file mode 100644 index 2d086a2c0..000000000 --- a/registry/tests/projects/lodash-es-pass/src/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import map from "lodash-es/map.js"; -import filter from "lodash-es/filter.js"; -import groupBy from "lodash-es/groupBy.js"; -import debounce from "lodash-es/debounce.js"; -import sortBy from "lodash-es/sortBy.js"; -import uniq from "lodash-es/uniq.js"; - -const items = [ - { name: "Alice", group: "A", score: 90 }, - { name: "Bob", group: "B", score: 85 }, - { name: "Carol", group: "A", score: 95 }, - { name: "Dave", group: "B", score: 80 }, -]; - -const names = map(items, "name"); -const highScores = filter(items, (i) => i.score >= 90); -const grouped = groupBy(items, "group"); -const sorted = sortBy(items, "score").map((i) => i.name); -const unique = uniq([1, 2, 2, 3, 3, 3]); - -const result = { - names, - highScoreNames: map(highScores, "name"), - groupKeys: Object.keys(grouped).sort(), - groupACount: grouped["A"].length, - sorted, - unique, - debounceType: typeof debounce, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/module-access-pass/fixture.json b/registry/tests/projects/module-access-pass/fixture.json deleted file mode 100644 index 1509fc6e8..000000000 --- a/registry/tests/projects/module-access-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/module-access-pass/package.json b/registry/tests/projects/module-access-pass/package.json deleted file mode 100644 index 543a01326..000000000 --- a/registry/tests/projects/module-access-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "module-access-pass-fixture", - "private": true, - "type": "commonjs", - "dependencies": { - "entry-lib": "file:./vendor/entry-lib" - } -} diff --git a/registry/tests/projects/module-access-pass/src/index.js b/registry/tests/projects/module-access-pass/src/index.js deleted file mode 100644 index cc51e3c9c..000000000 --- a/registry/tests/projects/module-access-pass/src/index.js +++ /dev/null @@ -1,6 +0,0 @@ -const entryLib = require("entry-lib"); - -console.log(JSON.stringify({ - value: entryLib.value, - message: entryLib.message, -})); diff --git a/registry/tests/projects/module-access-pass/vendor/entry-lib/index.js b/registry/tests/projects/module-access-pass/vendor/entry-lib/index.js deleted file mode 100644 index d0f5808a8..000000000 --- a/registry/tests/projects/module-access-pass/vendor/entry-lib/index.js +++ /dev/null @@ -1,6 +0,0 @@ -const transitive = require("transitive-lib"); - -module.exports = { - value: transitive.base + 2, - message: transitive.message, -}; diff --git a/registry/tests/projects/module-access-pass/vendor/entry-lib/package.json b/registry/tests/projects/module-access-pass/vendor/entry-lib/package.json deleted file mode 100644 index 694f8c0ea..000000000 --- a/registry/tests/projects/module-access-pass/vendor/entry-lib/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "entry-lib", - "version": "1.0.0", - "main": "index.js", - "dependencies": { - "transitive-lib": "file:../transitive-lib" - } -} diff --git a/registry/tests/projects/module-access-pass/vendor/transitive-lib/index.js b/registry/tests/projects/module-access-pass/vendor/transitive-lib/index.js deleted file mode 100644 index bc9c69109..000000000 --- a/registry/tests/projects/module-access-pass/vendor/transitive-lib/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - base: 40, - message: "module-access-fixture", -}; diff --git a/registry/tests/projects/module-access-pass/vendor/transitive-lib/package.json b/registry/tests/projects/module-access-pass/vendor/transitive-lib/package.json deleted file mode 100644 index 5dac99090..000000000 --- a/registry/tests/projects/module-access-pass/vendor/transitive-lib/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "transitive-lib", - "version": "1.0.0", - "main": "index.js" -} diff --git a/registry/tests/projects/mysql2-pass/fixture.json b/registry/tests/projects/mysql2-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/mysql2-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/mysql2-pass/package.json b/registry/tests/projects/mysql2-pass/package.json deleted file mode 100644 index 0ff6cea05..000000000 --- a/registry/tests/projects/mysql2-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-mysql2-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "mysql2": "3.12.0" - } -} diff --git a/registry/tests/projects/mysql2-pass/src/index.js b/registry/tests/projects/mysql2-pass/src/index.js deleted file mode 100644 index 9965db702..000000000 --- a/registry/tests/projects/mysql2-pass/src/index.js +++ /dev/null @@ -1,173 +0,0 @@ -"use strict"; - -var mysql = require("mysql2"); -var mysqlPromise = require("mysql2/promise"); - -var result = {}; - -// Core factory functions -result.createConnectionExists = typeof mysql.createConnection === "function"; -result.createPoolExists = typeof mysql.createPool === "function"; -result.createPoolClusterExists = typeof mysql.createPoolCluster === "function"; - -// Protocol types and charsets -var Types = mysql.Types; -result.typesExists = typeof Types === "object" && Types !== null; -result.hasCharsets = typeof mysql.Charsets === "object" && mysql.Charsets !== null; - -// Escape and format utilities — comprehensive coverage -result.escapeString = mysql.escape("hello 'world'"); -result.escapeNumber = mysql.escape(42); -result.escapeNull = mysql.escape(null); -result.escapeBool = mysql.escape(true); -result.escapeArray = mysql.escape([1, "two", null]); -result.escapeNested = mysql.escape([[1, 2], [3, 4]]); -result.escapeId = mysql.escapeId("table name"); -result.escapeIdQualified = mysql.escapeId("db.table"); -result.formatSql = mysql.format("SELECT ? FROM ??", ["value", "table"]); -result.formatMulti = mysql.format("INSERT INTO ?? SET ?", [ - "users", - { name: "test", age: 30 }, -]); - -// raw() for prepared statement placeholders -result.hasRaw = typeof mysql.raw === "function"; -var rawVal = mysql.raw("NOW()"); -result.rawEscape = mysql.escape(rawVal); - -// Connection pool configuration (no connection needed — exercises config parsing) -var pool = mysql.createPool({ - host: "127.0.0.1", - port: 0, - user: "root", - password: "test", - database: "testdb", - waitForConnections: true, - connectionLimit: 5, - queueLimit: 0, - enableKeepAlive: true, - keepAliveInitialDelay: 10000, -}); -result.poolCreated = pool !== null && typeof pool === "object"; -result.poolMethods = [ - "getConnection", - "query", - "execute", - "end", - "on", - "promise", -].filter(function (m) { - return typeof pool[m] === "function"; -}); - -// Pool event emitter interface -result.poolHasOn = typeof pool.on === "function"; -result.poolHasEmit = typeof pool.emit === "function"; - -// Pool cluster configuration -var cluster = mysql.createPoolCluster({ - canRetry: true, - removeNodeErrorCount: 5, - defaultSelector: "RR", -}); -result.clusterCreated = cluster !== null && typeof cluster === "object"; -result.clusterMethods = ["add", "remove", "getConnection", "of", "end", "on"].filter( - function (m) { - return typeof cluster[m] === "function"; - }, -); - -// Add nodes to cluster (exercises config validation — no connections made) -cluster.add("MASTER", { - host: "127.0.0.1", - port: 0, - user: "root", - password: "test", - database: "testdb", -}); -cluster.add("REPLICA1", { - host: "127.0.0.1", - port: 0, - user: "root", - password: "test", - database: "testdb", -}); - -// Cluster pattern selector -var clusterOf = cluster.of("REPLICA*"); -result.clusterOfCreated = clusterOf !== null && typeof clusterOf === "object"; - -// Promise wrapper — deeper coverage -result.promiseCreateConnection = typeof mysqlPromise.createConnection === "function"; -result.promiseCreatePool = typeof mysqlPromise.createPool === "function"; -result.promiseCreatePoolCluster = - typeof mysqlPromise.createPoolCluster === "function"; - -// Promise pool with same config shape -var promisePool = mysqlPromise.createPool({ - host: "127.0.0.1", - port: 0, - user: "root", - password: "test", - database: "testdb", - connectionLimit: 2, -}); -result.promisePoolCreated = promisePool !== null && typeof promisePool === "object"; -result.promisePoolMethods = ["getConnection", "query", "execute", "end"].filter( - function (m) { - return typeof promisePool[m] === "function"; - }, -); - -// Type casting and field metadata -result.typeNames = [ - "DECIMAL", - "TINY", - "SHORT", - "LONG", - "FLOAT", - "DOUBLE", - "TIMESTAMP", - "LONGLONG", - "INT24", - "DATE", - "TIME", - "DATETIME", - "YEAR", - "NEWDATE", - "VARCHAR", - "BIT", - "JSON", - "NEWDECIMAL", - "ENUM", - "SET", - "TINY_BLOB", - "MEDIUM_BLOB", - "LONG_BLOB", - "BLOB", - "VAR_STRING", - "STRING", - "GEOMETRY", -].filter(function (t) { - return typeof Types[t] === "number"; -}); - -// Format with Date objects (use epoch 0 for timezone-stable output) -var d = new Date(0); -result.formatDateType = typeof mysql.format("SELECT ?", [d]); - -// Format with Buffer -result.formatBuffer = mysql.format("SELECT ?", [Buffer.from("binary")]); - -// Format with nested object (SET clause) -result.formatObject = mysql.format("UPDATE ?? SET ?", [ - "tbl", - { name: "test", active: true, score: null }, -]); - -// Clean up pools (no connections to close — releases internal timers) -pool.end(function () {}); -cluster.end(function () {}); -promisePool.end().catch(function () {}); - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/net-create-server-pass/fixture.json b/registry/tests/projects/net-create-server-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/net-create-server-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/net-create-server-pass/package.json b/registry/tests/projects/net-create-server-pass/package.json deleted file mode 100644 index e0bbaa863..000000000 --- a/registry/tests/projects/net-create-server-pass/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "project-matrix-net-create-server-pass", - "private": true, - "type": "commonjs" -} diff --git a/registry/tests/projects/net-create-server-pass/src/index.js b/registry/tests/projects/net-create-server-pass/src/index.js deleted file mode 100644 index 9e0f2d12b..000000000 --- a/registry/tests/projects/net-create-server-pass/src/index.js +++ /dev/null @@ -1,3 +0,0 @@ -const net = require("net"); - -net.createServer(); diff --git a/registry/tests/projects/nextjs-pass/.babelrc b/registry/tests/projects/nextjs-pass/.babelrc deleted file mode 100644 index 6e701d884..000000000 --- a/registry/tests/projects/nextjs-pass/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["next/babel"] -} diff --git a/registry/tests/projects/nextjs-pass/fixture.json b/registry/tests/projects/nextjs-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/nextjs-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/nextjs-pass/next-wasm-shim.cjs b/registry/tests/projects/nextjs-pass/next-wasm-shim.cjs deleted file mode 100644 index a3e92bb22..000000000 --- a/registry/tests/projects/nextjs-pass/next-wasm-shim.cjs +++ /dev/null @@ -1,4 +0,0 @@ -Object.defineProperty(process.versions, "webcontainer", { - configurable: true, - value: "agent-os", -}); diff --git a/registry/tests/projects/nextjs-pass/next.config.js b/registry/tests/projects/nextjs-pass/next.config.js deleted file mode 100644 index decf0c37e..000000000 --- a/registry/tests/projects/nextjs-pass/next.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('next').NextConfig} */ -module.exports = { - eslint: { - ignoreDuringBuilds: true, - }, - experimental: { - webpackBuildWorker: false, - }, - typescript: { - ignoreBuildErrors: true, - }, -}; diff --git a/registry/tests/projects/nextjs-pass/package.json b/registry/tests/projects/nextjs-pass/package.json deleted file mode 100644 index 7485da608..000000000 --- a/registry/tests/projects/nextjs-pass/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "project-matrix-nextjs-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "@babel/runtime": "7.26.0", - "@next/swc-wasm-nodejs": "14.2.15", - "next": "14.2.15", - "react": "18.3.1", - "react-dom": "18.3.1" - } -} diff --git a/registry/tests/projects/nextjs-pass/pages/api/hello.js b/registry/tests/projects/nextjs-pass/pages/api/hello.js deleted file mode 100644 index d9741d5d7..000000000 --- a/registry/tests/projects/nextjs-pass/pages/api/hello.js +++ /dev/null @@ -1,5 +0,0 @@ -Object.defineProperty(exports, "__esModule", { value: true }); - -exports.default = function handler(req, res) { - res.status(200).json({ message: "hello", method: req.method }); -}; diff --git a/registry/tests/projects/nextjs-pass/pages/index.js b/registry/tests/projects/nextjs-pass/pages/index.js deleted file mode 100644 index ece29c752..000000000 --- a/registry/tests/projects/nextjs-pass/pages/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const React = require("react"); - -function Home() { - return React.createElement("div", null, "Hello from Next.js"); -} - -module.exports = Home; diff --git a/registry/tests/projects/nextjs-pass/run-next-build.cjs b/registry/tests/projects/nextjs-pass/run-next-build.cjs deleted file mode 100644 index 00cdea646..000000000 --- a/registry/tests/projects/nextjs-pass/run-next-build.cjs +++ /dev/null @@ -1,19 +0,0 @@ -const projectDir = __dirname; - -require("./next-wasm-shim.cjs"); - -const { nextBuild } = require("next/dist/cli/next-build"); - -nextBuild( - { - debug: false, - experimentalAppOnly: false, - experimentalBuildMode: "compile", - experimentalDebugMemoryUsage: false, - experimentalTurbo: false, - lint: true, - mangling: true, - profile: false, - }, - projectDir, -); diff --git a/registry/tests/projects/nextjs-pass/src/index.js b/registry/tests/projects/nextjs-pass/src/index.js deleted file mode 100644 index c32ffe9bf..000000000 --- a/registry/tests/projects/nextjs-pass/src/index.js +++ /dev/null @@ -1,93 +0,0 @@ -"use strict"; - -var fs = require("fs"); -var path = require("path"); - -var projectDir = path.resolve(__dirname, ".."); -var buildManifestPath = path.join( - projectDir, - ".next", - "build-manifest.json", -); -var pagesManifestPath = path.join( - projectDir, - ".next", - "server", - "pages-manifest.json", -); - -function readManifest() { - return JSON.parse(fs.readFileSync(buildManifestPath, "utf8")); -} - -function ensureBuild() { - try { - readManifest(); - return; - } catch (e) { - // Build manifest missing — run build - } - var execSync = require("child_process").execSync; - var buildEnv = Object.assign({}, process.env); - if (!buildEnv.PATH) { - buildEnv.PATH = - "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; - } - buildEnv.NEXT_TELEMETRY_DISABLED = "1"; - var buildCommand = "node " + JSON.stringify(path.join(projectDir, "run-next-build.cjs")); - execSync(buildCommand, { - cwd: projectDir, - stdio: "pipe", - timeout: 30000, - env: buildEnv, - }); -} - -function main() { - ensureBuild(); - - var manifest = readManifest(); - var pages = Object.keys(manifest.pages).sort(); - - var results = []; - - results.push({ check: "build-manifest", pages: pages }); - - var pagesManifest = JSON.parse(fs.readFileSync(pagesManifestPath, "utf8")); - results.push({ - check: "pages-manifest", - hasIndex: pagesManifest["/"] === "pages/index.js", - hasApiRoute: pagesManifest["/api/hello"] === "pages/api/hello.js", - }); - - var indexModule = fs.readFileSync( - path.join(projectDir, ".next", "server", "pages", "index.js"), - "utf8", - ); - results.push({ - check: "compiled-page", - rendered: indexModule.indexOf("Hello from Next.js") !== -1, - }); - - var apiRouteExists = true; - try { - fs.readFileSync( - path.join( - projectDir, - ".next", - "server", - "pages", - "api", - "hello.js", - ), - "utf8", - ); - } catch (e) { - apiRouteExists = false; - } - results.push({ check: "api-route", compiled: apiRouteExists }); - - console.log(JSON.stringify(results)); -} - -main(); diff --git a/registry/tests/projects/node-fetch-pass/fixture.json b/registry/tests/projects/node-fetch-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/node-fetch-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/node-fetch-pass/package.json b/registry/tests/projects/node-fetch-pass/package.json deleted file mode 100644 index 67c147b6a..000000000 --- a/registry/tests/projects/node-fetch-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-node-fetch-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "node-fetch": "2.7.0" - } -} diff --git a/registry/tests/projects/node-fetch-pass/src/index.js b/registry/tests/projects/node-fetch-pass/src/index.js deleted file mode 100644 index ee15c2a1a..000000000 --- a/registry/tests/projects/node-fetch-pass/src/index.js +++ /dev/null @@ -1,59 +0,0 @@ -"use strict"; - -const http = require("http"); -const fetch = require("node-fetch"); - -const server = http.createServer((req, res) => { - if (req.method === "GET" && req.url === "/hello") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ message: "hello" })); - } else if (req.method === "GET" && req.url === "/users/42") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ id: "42", name: "test-user" })); - } else if (req.method === "POST" && req.url === "/data") { - let body = ""; - req.on("data", (chunk) => (body += chunk)); - req.on("end", () => { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ method: "POST", received: JSON.parse(body) })); - }); - } else { - res.writeHead(404); - res.end(); - } -}); - -async function main() { - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); - const port = server.address().port; - const base = "http://127.0.0.1:" + port; - - try { - const results = []; - - const r1 = await fetch(base + "/hello"); - const b1 = await r1.json(); - results.push({ route: "GET /hello", status: r1.status, body: b1 }); - - const r2 = await fetch(base + "/users/42"); - const b2 = await r2.json(); - results.push({ route: "GET /users/42", status: r2.status, body: b2 }); - - const r3 = await fetch(base + "/data", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ key: "value" }), - }); - const b3 = await r3.json(); - results.push({ route: "POST /data", status: r3.status, body: b3 }); - - console.log(JSON.stringify(results)); - } finally { - await new Promise((resolve) => server.close(resolve)); - } -} - -main().catch((err) => { - console.error(err.message); - process.exit(1); -}); diff --git a/registry/tests/projects/npm-layout-pass/fixture.json b/registry/tests/projects/npm-layout-pass/fixture.json deleted file mode 100644 index a534708f5..000000000 --- a/registry/tests/projects/npm-layout-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "npm" -} diff --git a/registry/tests/projects/npm-layout-pass/package-lock.json b/registry/tests/projects/npm-layout-pass/package-lock.json deleted file mode 100644 index 3aa4afafc..000000000 --- a/registry/tests/projects/npm-layout-pass/package-lock.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "project-matrix-npm-layout-pass", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "project-matrix-npm-layout-pass", - "dependencies": { - "left-pad": "0.0.3" - } - }, - "node_modules/left-pad": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-0.0.3.tgz", - "integrity": "sha512-Qli5dSpAXQOSw1y/M+uBKT37rj6iZAQMz6Uy5/ZYGIhBLS/ODRHqL4XIDvSAtYpjfia0XKNztlPFa806TWw5Gw==", - "deprecated": "use String.prototype.padStart()", - "license": "WTFPL" - } - } -} diff --git a/registry/tests/projects/npm-layout-pass/package.json b/registry/tests/projects/npm-layout-pass/package.json deleted file mode 100644 index 576bbb70e..000000000 --- a/registry/tests/projects/npm-layout-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-npm-layout-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "left-pad": "0.0.3" - } -} diff --git a/registry/tests/projects/npm-layout-pass/src/index.js b/registry/tests/projects/npm-layout-pass/src/index.js deleted file mode 100644 index 6ab481e2f..000000000 --- a/registry/tests/projects/npm-layout-pass/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const leftPad = require("left-pad"); - -const results = [ - { input: "hello", width: 10, padded: leftPad("hello", 10) }, - { input: "42", width: 5, fill: "0", padded: leftPad("42", 5, "0") }, - { input: "", width: 3, padded: leftPad("", 3) }, -]; - -console.log(JSON.stringify(results)); diff --git a/registry/tests/projects/optional-deps-pass/fixture.json b/registry/tests/projects/optional-deps-pass/fixture.json deleted file mode 100644 index a534708f5..000000000 --- a/registry/tests/projects/optional-deps-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "npm" -} diff --git a/registry/tests/projects/optional-deps-pass/package-lock.json b/registry/tests/projects/optional-deps-pass/package-lock.json deleted file mode 100644 index 6cf7d3d8a..000000000 --- a/registry/tests/projects/optional-deps-pass/package-lock.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "project-matrix-optional-deps-pass", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "project-matrix-optional-deps-pass", - "dependencies": { - "semver": "7.7.3" - }, - "optionalDependencies": { - "@anthropic-internal/nonexistent-optional-pkg": "99.0.0" - } - }, - "node_modules/@anthropic-internal/nonexistent-optional-pkg": { - "optional": true - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - } - } -} diff --git a/registry/tests/projects/optional-deps-pass/package.json b/registry/tests/projects/optional-deps-pass/package.json deleted file mode 100644 index 2f768a07f..000000000 --- a/registry/tests/projects/optional-deps-pass/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "project-matrix-optional-deps-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "semver": "7.7.3" - }, - "optionalDependencies": { - "@anthropic-internal/nonexistent-optional-pkg": "99.0.0" - } -} diff --git a/registry/tests/projects/optional-deps-pass/src/index.js b/registry/tests/projects/optional-deps-pass/src/index.js deleted file mode 100644 index fb5d71443..000000000 --- a/registry/tests/projects/optional-deps-pass/src/index.js +++ /dev/null @@ -1,18 +0,0 @@ -"use strict"; - -const semver = require("semver"); - -let optionalAvailable; -try { - require("@anthropic-internal/nonexistent-optional-pkg"); - optionalAvailable = true; -} catch (e) { - optionalAvailable = false; -} - -const result = { - semverValid: semver.valid("1.0.0") !== null, - optionalAvailable: optionalAvailable -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/peer-deps-pass/fixture.json b/registry/tests/projects/peer-deps-pass/fixture.json deleted file mode 100644 index a534708f5..000000000 --- a/registry/tests/projects/peer-deps-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "npm" -} diff --git a/registry/tests/projects/peer-deps-pass/package-lock.json b/registry/tests/projects/peer-deps-pass/package-lock.json deleted file mode 100644 index 0499e2550..000000000 --- a/registry/tests/projects/peer-deps-pass/package-lock.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "project-matrix-peer-deps-pass", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "project-matrix-peer-deps-pass", - "dependencies": { - "@peer-test/host": "file:packages/host", - "@peer-test/plugin": "file:packages/plugin" - } - }, - "node_modules/@peer-test/host": { - "resolved": "packages/host", - "link": true - }, - "node_modules/@peer-test/plugin": { - "resolved": "packages/plugin", - "link": true - }, - "packages/host": { - "name": "@peer-test/host", - "version": "1.0.0", - "peer": true - }, - "packages/plugin": { - "name": "@peer-test/plugin", - "version": "1.0.0", - "peerDependencies": { - "@peer-test/host": ">=1.0.0" - } - } - } -} diff --git a/registry/tests/projects/peer-deps-pass/package.json b/registry/tests/projects/peer-deps-pass/package.json deleted file mode 100644 index 46349f0ed..000000000 --- a/registry/tests/projects/peer-deps-pass/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "project-matrix-peer-deps-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "@peer-test/host": "file:packages/host", - "@peer-test/plugin": "file:packages/plugin" - } -} diff --git a/registry/tests/projects/peer-deps-pass/packages/host/index.js b/registry/tests/projects/peer-deps-pass/packages/host/index.js deleted file mode 100644 index 25c483b78..000000000 --- a/registry/tests/projects/peer-deps-pass/packages/host/index.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; - -module.exports = { name: "@peer-test/host", version: "1.0.0" }; diff --git a/registry/tests/projects/peer-deps-pass/packages/host/package.json b/registry/tests/projects/peer-deps-pass/packages/host/package.json deleted file mode 100644 index 41459caaa..000000000 --- a/registry/tests/projects/peer-deps-pass/packages/host/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@peer-test/host", - "version": "1.0.0", - "main": "index.js" -} diff --git a/registry/tests/projects/peer-deps-pass/packages/plugin/index.js b/registry/tests/projects/peer-deps-pass/packages/plugin/index.js deleted file mode 100644 index 46c5a05c9..000000000 --- a/registry/tests/projects/peer-deps-pass/packages/plugin/index.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; - -const host = require("@peer-test/host"); - -module.exports = { - pluginName: "@peer-test/plugin", - resolvedHost: host -}; diff --git a/registry/tests/projects/peer-deps-pass/packages/plugin/package.json b/registry/tests/projects/peer-deps-pass/packages/plugin/package.json deleted file mode 100644 index 8e8b713c8..000000000 --- a/registry/tests/projects/peer-deps-pass/packages/plugin/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@peer-test/plugin", - "version": "1.0.0", - "main": "index.js", - "peerDependencies": { - "@peer-test/host": ">=1.0.0" - } -} diff --git a/registry/tests/projects/peer-deps-pass/src/index.js b/registry/tests/projects/peer-deps-pass/src/index.js deleted file mode 100644 index 31cf9c3be..000000000 --- a/registry/tests/projects/peer-deps-pass/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const plugin = require("@peer-test/plugin"); - -const result = { - plugin: plugin.pluginName, - host: plugin.resolvedHost.name, - hostVersion: plugin.resolvedHost.version -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/pg-pass/fixture.json b/registry/tests/projects/pg-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/pg-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/pg-pass/package.json b/registry/tests/projects/pg-pass/package.json deleted file mode 100644 index 033b701d5..000000000 --- a/registry/tests/projects/pg-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-pg-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "pg": "8.13.1" - } -} diff --git a/registry/tests/projects/pg-pass/pnpm-lock.yaml b/registry/tests/projects/pg-pass/pnpm-lock.yaml deleted file mode 100644 index 17a97f033..000000000 --- a/registry/tests/projects/pg-pass/pnpm-lock.yaml +++ /dev/null @@ -1,124 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - pg: - specifier: 8.13.1 - version: 8.13.1 - -packages: - - pg-cloudflare@1.3.0: - resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} - - pg-connection-string@2.12.0: - resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-pool@3.13.0: - resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.13.0: - resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg@8.13.1: - resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} - engines: {node: '>= 8.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-bytea@1.0.1: - resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} - engines: {node: '>=0.10.0'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - -snapshots: - - pg-cloudflare@1.3.0: - optional: true - - pg-connection-string@2.12.0: {} - - pg-int8@1.0.1: {} - - pg-pool@3.13.0(pg@8.13.1): - dependencies: - pg: 8.13.1 - - pg-protocol@1.13.0: {} - - pg-types@2.2.0: - dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.1 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 - - pg@8.13.1: - dependencies: - pg-connection-string: 2.12.0 - pg-pool: 3.13.0(pg@8.13.1) - pg-protocol: 1.13.0 - pg-types: 2.2.0 - pgpass: 1.0.5 - optionalDependencies: - pg-cloudflare: 1.3.0 - - pgpass@1.0.5: - dependencies: - split2: 4.2.0 - - postgres-array@2.0.0: {} - - postgres-bytea@1.0.1: {} - - postgres-date@1.0.7: {} - - postgres-interval@1.2.0: - dependencies: - xtend: 4.0.2 - - split2@4.2.0: {} - - xtend@4.0.2: {} diff --git a/registry/tests/projects/pg-pass/src/index.js b/registry/tests/projects/pg-pass/src/index.js deleted file mode 100644 index 4de7e8338..000000000 --- a/registry/tests/projects/pg-pass/src/index.js +++ /dev/null @@ -1,37 +0,0 @@ -"use strict"; - -const { Pool, Client, types } = require("pg"); - -const result = { - poolExists: typeof Pool === "function", - clientExists: typeof Client === "function", - typesExists: typeof types === "object" && types !== null, - poolMethods: [ - "connect", - "end", - "query", - "on", - ].filter((m) => typeof Pool.prototype[m] === "function"), - clientMethods: [ - "connect", - "end", - "query", - "on", - ].filter((m) => typeof Client.prototype[m] === "function"), -}; - -// Verify type parsers exist -result.hasSetTypeParser = typeof types.setTypeParser === "function"; -result.hasGetTypeParser = typeof types.getTypeParser === "function"; - -// Verify query builder can produce query config objects -const { Query } = require("pg"); -result.queryExists = typeof Query === "function"; - -// Verify pg-pool defaults class exists and can be configured -const defaults = require("pg/lib/defaults"); -result.defaultsExists = typeof defaults === "object" && defaults !== null; -result.defaultPort = defaults.port; -result.defaultHost = defaults.host; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/pino-pass/fixture.json b/registry/tests/projects/pino-pass/fixture.json deleted file mode 100644 index 1509fc6e8..000000000 --- a/registry/tests/projects/pino-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/pino-pass/package.json b/registry/tests/projects/pino-pass/package.json deleted file mode 100644 index cbfd5f499..000000000 --- a/registry/tests/projects/pino-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-pino-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "pino": "^9.0.0" - } -} diff --git a/registry/tests/projects/pino-pass/pnpm-lock.yaml b/registry/tests/projects/pino-pass/pnpm-lock.yaml deleted file mode 100644 index 205f214ac..000000000 --- a/registry/tests/projects/pino-pass/pnpm-lock.yaml +++ /dev/null @@ -1,106 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - pino: - specifier: ^9.0.0 - version: 9.14.0 - -packages: - - '@pinojs/redact@0.4.0': - resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - - pino-std-serializers@7.1.0: - resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} - - pino@9.14.0: - resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} - hasBin: true - - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - - sonic-boom@4.2.1: - resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - -snapshots: - - '@pinojs/redact@0.4.0': {} - - atomic-sleep@1.0.0: {} - - on-exit-leak-free@2.1.2: {} - - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - - pino-std-serializers@7.1.0: {} - - pino@9.14.0: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.1.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.1 - thread-stream: 3.1.0 - - process-warning@5.0.0: {} - - quick-format-unescaped@4.0.4: {} - - real-require@0.2.0: {} - - safe-stable-stringify@2.5.0: {} - - sonic-boom@4.2.1: - dependencies: - atomic-sleep: 1.0.0 - - split2@4.2.0: {} - - thread-stream@3.1.0: - dependencies: - real-require: 0.2.0 diff --git a/registry/tests/projects/pino-pass/src/index.js b/registry/tests/projects/pino-pass/src/index.js deleted file mode 100644 index 49403902f..000000000 --- a/registry/tests/projects/pino-pass/src/index.js +++ /dev/null @@ -1,68 +0,0 @@ -"use strict"; - -const pino = require("pino"); - -// Use process.stdout as destination for sandbox compatibility -// Disable variable fields (timestamp, pid, hostname) for deterministic output -const logger = pino( - { - timestamp: false, - base: undefined, - }, - process.stdout -); - -// Basic logging at different levels -logger.info("hello from pino"); -logger.warn("this is a warning"); -logger.error("something went wrong"); - -// Structured data -logger.info({ user: "alice", action: "login" }, "user event"); - -// Child logger with bound properties -const child = logger.child({ module: "auth" }); -child.info("child logger message"); -child.info({ detail: "extra" }, "child with data"); - -// Custom serializers -const custom = pino( - { - timestamp: false, - base: undefined, - serializers: { - req: (val) => ({ method: val.method, url: val.url }), - }, - }, - process.stdout -); -custom.info( - { req: { method: "GET", url: "/api", headers: { host: "localhost" } } }, - "request received" -); - -// Silent level (should not output) -const silent = pino( - { - timestamp: false, - base: undefined, - level: "error", - }, - process.stdout -); -silent.info("this should not appear"); -silent.error("only errors visible"); - -// Log levels are numeric -console.log( - JSON.stringify({ - levels: { - trace: logger.levels.values.trace, - debug: logger.levels.values.debug, - info: logger.levels.values.info, - warn: logger.levels.values.warn, - error: logger.levels.values.error, - fatal: logger.levels.values.fatal, - }, - }) -); diff --git a/registry/tests/projects/pnpm-layout-pass/fixture.json b/registry/tests/projects/pnpm-layout-pass/fixture.json deleted file mode 100644 index 8727b5a93..000000000 --- a/registry/tests/projects/pnpm-layout-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "pnpm" -} diff --git a/registry/tests/projects/pnpm-layout-pass/package.json b/registry/tests/projects/pnpm-layout-pass/package.json deleted file mode 100644 index 354b0bbcf..000000000 --- a/registry/tests/projects/pnpm-layout-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-pnpm-layout-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "left-pad": "0.0.3" - } -} diff --git a/registry/tests/projects/pnpm-layout-pass/pnpm-lock.yaml b/registry/tests/projects/pnpm-layout-pass/pnpm-lock.yaml deleted file mode 100644 index f52b6e1da..000000000 --- a/registry/tests/projects/pnpm-layout-pass/pnpm-lock.yaml +++ /dev/null @@ -1,23 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - left-pad: - specifier: 0.0.3 - version: 0.0.3 - -packages: - - left-pad@0.0.3: - resolution: {integrity: sha512-Qli5dSpAXQOSw1y/M+uBKT37rj6iZAQMz6Uy5/ZYGIhBLS/ODRHqL4XIDvSAtYpjfia0XKNztlPFa806TWw5Gw==} - deprecated: use String.prototype.padStart() - -snapshots: - - left-pad@0.0.3: {} diff --git a/registry/tests/projects/pnpm-layout-pass/src/index.js b/registry/tests/projects/pnpm-layout-pass/src/index.js deleted file mode 100644 index 6ab481e2f..000000000 --- a/registry/tests/projects/pnpm-layout-pass/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const leftPad = require("left-pad"); - -const results = [ - { input: "hello", width: 10, padded: leftPad("hello", 10) }, - { input: "42", width: 5, fill: "0", padded: leftPad("42", 5, "0") }, - { input: "", width: 3, padded: leftPad("", 3) }, -]; - -console.log(JSON.stringify(results)); diff --git a/registry/tests/projects/rivetkit/fixture.json b/registry/tests/projects/rivetkit/fixture.json deleted file mode 100644 index 1509fc6e8..000000000 --- a/registry/tests/projects/rivetkit/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/rivetkit/package.json b/registry/tests/projects/rivetkit/package.json deleted file mode 100644 index cd067c2b6..000000000 --- a/registry/tests/projects/rivetkit/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "rivetkit-fixture-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "rivetkit": "file:./vendor/rivetkit" - } -} diff --git a/registry/tests/projects/rivetkit/src/index.js b/registry/tests/projects/rivetkit/src/index.js deleted file mode 100644 index 0d72e4630..000000000 --- a/registry/tests/projects/rivetkit/src/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const rivetkit = require("rivetkit"); - -if (typeof rivetkit.actor !== "function") { - throw new Error("expected rivetkit.actor to be a function"); -} - -const definition = rivetkit.actor({ actions: {} }); -if (!definition || typeof definition !== "object") { - throw new Error("expected actor() to return a definition object"); -} - -console.log("rivetkit fixture ok"); diff --git a/registry/tests/projects/rivetkit/vendor/rivetkit/package.json b/registry/tests/projects/rivetkit/vendor/rivetkit/package.json deleted file mode 100644 index 3ee54fcaa..000000000 --- a/registry/tests/projects/rivetkit/vendor/rivetkit/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "rivetkit", - "version": "0.0.0-fixture", - "type": "module", - "exports": { - ".": { - "import": "./dist/mod.js", - "require": "./dist/mod.cjs" - } - } -} diff --git a/registry/tests/projects/semver-pass/fixture.json b/registry/tests/projects/semver-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/semver-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/semver-pass/package.json b/registry/tests/projects/semver-pass/package.json deleted file mode 100644 index 285114713..000000000 --- a/registry/tests/projects/semver-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-semver-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "semver": "7.7.3" - } -} diff --git a/registry/tests/projects/semver-pass/src/index.js b/registry/tests/projects/semver-pass/src/index.js deleted file mode 100644 index 47e3cdbbd..000000000 --- a/registry/tests/projects/semver-pass/src/index.js +++ /dev/null @@ -1,9 +0,0 @@ -const semver = require("semver"); - -const result = { - valid: semver.valid("1.2.3"), - satisfies: semver.satisfies("1.2.3", "^1.0.0"), - compare: semver.compare("1.2.3", "1.2.4"), -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/sse-streaming-pass/fixture.json b/registry/tests/projects/sse-streaming-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/sse-streaming-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/sse-streaming-pass/package.json b/registry/tests/projects/sse-streaming-pass/package.json deleted file mode 100644 index 7a317ea49..000000000 --- a/registry/tests/projects/sse-streaming-pass/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "project-matrix-sse-streaming-pass", - "private": true, - "type": "commonjs" -} diff --git a/registry/tests/projects/sse-streaming-pass/src/index.js b/registry/tests/projects/sse-streaming-pass/src/index.js deleted file mode 100644 index b319a9f4e..000000000 --- a/registry/tests/projects/sse-streaming-pass/src/index.js +++ /dev/null @@ -1,128 +0,0 @@ -"use strict"; - -const http = require("http"); - -// SSE events to send — exercises data-only, named events, id field, retry field -const sseEvents = [ - "retry: 3000\n\n", - "data: hello-world\n\n", - "event: status\ndata: {\"connected\":true}\n\n", - "id: msg-3\nevent: update\ndata: first line\ndata: second line\n\n", - "id: msg-4\ndata: final-event\n\n", -]; - -function createSSEServer() { - return http.createServer((req, res) => { - if (req.url !== "/events") { - res.writeHead(404); - res.end(); - return; - } - - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - Connection: "keep-alive", - }); - - // Send all events then close - for (const event of sseEvents) { - res.write(event); - } - res.end(); - }); -} - -// Parse SSE text/event-stream format into structured events -function parseSSEStream(raw) { - const events = []; - let current = {}; - - for (const line of raw.split("\n")) { - if (line === "") { - // Empty line = event boundary - if (Object.keys(current).length > 0) { - events.push(current); - current = {}; - } - continue; - } - - const colonIdx = line.indexOf(":"); - if (colonIdx === 0) continue; // comment line - - let field, value; - if (colonIdx > 0) { - field = line.slice(0, colonIdx); - // Strip single leading space after colon per SSE spec - value = line.slice(colonIdx + 1); - if (value.startsWith(" ")) value = value.slice(1); - } else { - field = line; - value = ""; - } - - if (field === "data") { - // Multiple data fields are joined with newline - current.data = current.data != null ? current.data + "\n" + value : value; - } else { - current[field] = value; - } - } - - // Trailing event without final blank line - if (Object.keys(current).length > 0) { - events.push(current); - } - - return events; -} - -async function main() { - const server = createSSEServer(); - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); - const port = server.address().port; - - try { - const response = await new Promise((resolve, reject) => { - http.get( - { hostname: "127.0.0.1", port, path: "/events" }, - (res) => { - let body = ""; - res.on("data", (chunk) => (body += chunk)); - res.on("end", () => - resolve({ - statusCode: res.statusCode, - headers: res.headers, - body, - }), - ); - }, - ).on("error", reject); - }); - - const headers = { - contentType: response.headers["content-type"], - connection: response.headers["connection"], - cacheControl: response.headers["cache-control"], - }; - - const events = parseSSEStream(response.body); - - const result = { - statusCode: response.statusCode, - headers, - eventCount: events.length, - events, - }; - - console.log(JSON.stringify(result)); - } finally { - await new Promise((resolve) => server.close(resolve)); - } -} - -main().catch((err) => { - console.error(err.message); - process.exit(1); -}); diff --git a/registry/tests/projects/ssh2-pass/fixture.json b/registry/tests/projects/ssh2-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/ssh2-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/ssh2-pass/package.json b/registry/tests/projects/ssh2-pass/package.json deleted file mode 100644 index d01582506..000000000 --- a/registry/tests/projects/ssh2-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-ssh2-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "ssh2": "1.17.0" - } -} diff --git a/registry/tests/projects/ssh2-pass/src/index.js b/registry/tests/projects/ssh2-pass/src/index.js deleted file mode 100644 index 8d155d0bb..000000000 --- a/registry/tests/projects/ssh2-pass/src/index.js +++ /dev/null @@ -1,28 +0,0 @@ -"use strict"; - -const { Client, Server, utils } = require("ssh2"); - -const result = { - clientExists: typeof Client === "function", - clientMethods: [ - "connect", - "end", - "exec", - "sftp", - "shell", - "forwardIn", - "forwardOut", - ].filter((m) => typeof Client.prototype[m] === "function"), - serverExists: typeof Server === "function", - utilsExists: typeof utils === "object" && utils !== null, - parseKey: typeof utils.parseKey === "function", -}; - -// Create a Client instance and verify it has expected properties -const client = new Client(); -result.instanceCreated = client instanceof Client; -result.hasOn = typeof client.on === "function"; -result.hasEmit = typeof client.emit === "function"; -client.removeAllListeners(); - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/ssh2-sftp-client-pass/fixture.json b/registry/tests/projects/ssh2-sftp-client-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/ssh2-sftp-client-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/ssh2-sftp-client-pass/package.json b/registry/tests/projects/ssh2-sftp-client-pass/package.json deleted file mode 100644 index 7e39e881a..000000000 --- a/registry/tests/projects/ssh2-sftp-client-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-ssh2-sftp-client-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "ssh2-sftp-client": "12.1.0" - } -} diff --git a/registry/tests/projects/ssh2-sftp-client-pass/src/index.js b/registry/tests/projects/ssh2-sftp-client-pass/src/index.js deleted file mode 100644 index 5e85c51a1..000000000 --- a/registry/tests/projects/ssh2-sftp-client-pass/src/index.js +++ /dev/null @@ -1,26 +0,0 @@ -"use strict"; - -const SftpClient = require("ssh2-sftp-client"); - -const result = { - classExists: typeof SftpClient === "function", - methods: [ - "connect", - "list", - "get", - "put", - "mkdir", - "rmdir", - "delete", - "rename", - "exists", - "stat", - "end", - ].filter((m) => typeof SftpClient.prototype[m] === "function"), -}; - -// Create a Client instance and verify it has expected properties -const client = new SftpClient(); -result.instanceCreated = client instanceof SftpClient; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/transitive-deps-pass/fixture.json b/registry/tests/projects/transitive-deps-pass/fixture.json deleted file mode 100644 index a534708f5..000000000 --- a/registry/tests/projects/transitive-deps-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "npm" -} diff --git a/registry/tests/projects/transitive-deps-pass/package-lock.json b/registry/tests/projects/transitive-deps-pass/package-lock.json deleted file mode 100644 index ac98b5a51..000000000 --- a/registry/tests/projects/transitive-deps-pass/package-lock.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "project-matrix-transitive-deps-pass", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "project-matrix-transitive-deps-pass", - "dependencies": { - "@chain-test/level-a": "file:packages/level-a" - } - }, - "node_modules/@chain-test/level-a": { - "resolved": "packages/level-a", - "link": true - }, - "node_modules/@chain-test/level-b": { - "resolved": "packages/level-b", - "link": true - }, - "node_modules/@chain-test/level-c": { - "resolved": "packages/level-c", - "link": true - }, - "packages/level-a": { - "name": "@chain-test/level-a", - "version": "1.0.0", - "dependencies": { - "@chain-test/level-b": "file:../level-b" - } - }, - "packages/level-b": { - "name": "@chain-test/level-b", - "version": "1.0.0", - "dependencies": { - "@chain-test/level-c": "file:../level-c" - } - }, - "packages/level-c": { - "name": "@chain-test/level-c", - "version": "1.0.0" - } - } -} diff --git a/registry/tests/projects/transitive-deps-pass/package.json b/registry/tests/projects/transitive-deps-pass/package.json deleted file mode 100644 index 15d0eaea7..000000000 --- a/registry/tests/projects/transitive-deps-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-transitive-deps-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "@chain-test/level-a": "file:packages/level-a" - } -} diff --git a/registry/tests/projects/transitive-deps-pass/packages/level-a/index.js b/registry/tests/projects/transitive-deps-pass/packages/level-a/index.js deleted file mode 100644 index e62bd86f1..000000000 --- a/registry/tests/projects/transitive-deps-pass/packages/level-a/index.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; - -const levelB = require("@chain-test/level-b"); - -module.exports = { - name: "level-a", - depth: 1, - child: levelB, - greet(who) { - return "level-a wraps: " + levelB.greet(who); - } -}; diff --git a/registry/tests/projects/transitive-deps-pass/packages/level-a/package.json b/registry/tests/projects/transitive-deps-pass/packages/level-a/package.json deleted file mode 100644 index 4364d6953..000000000 --- a/registry/tests/projects/transitive-deps-pass/packages/level-a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@chain-test/level-a", - "version": "1.0.0", - "main": "index.js", - "dependencies": { - "@chain-test/level-b": "file:../level-b" - } -} diff --git a/registry/tests/projects/transitive-deps-pass/packages/level-b/index.js b/registry/tests/projects/transitive-deps-pass/packages/level-b/index.js deleted file mode 100644 index 530c87b5f..000000000 --- a/registry/tests/projects/transitive-deps-pass/packages/level-b/index.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; - -const levelC = require("@chain-test/level-c"); - -module.exports = { - name: "level-b", - depth: 2, - child: levelC, - greet(who) { - return "level-b wraps: " + levelC.greet(who); - } -}; diff --git a/registry/tests/projects/transitive-deps-pass/packages/level-b/package.json b/registry/tests/projects/transitive-deps-pass/packages/level-b/package.json deleted file mode 100644 index ab1bdd165..000000000 --- a/registry/tests/projects/transitive-deps-pass/packages/level-b/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@chain-test/level-b", - "version": "1.0.0", - "main": "index.js", - "dependencies": { - "@chain-test/level-c": "file:../level-c" - } -} diff --git a/registry/tests/projects/transitive-deps-pass/packages/level-c/index.js b/registry/tests/projects/transitive-deps-pass/packages/level-c/index.js deleted file mode 100644 index bcb951de0..000000000 --- a/registry/tests/projects/transitive-deps-pass/packages/level-c/index.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -module.exports = { - name: "level-c", - depth: 3, - greet(who) { - return "hello from level-c to " + who; - } -}; diff --git a/registry/tests/projects/transitive-deps-pass/packages/level-c/package.json b/registry/tests/projects/transitive-deps-pass/packages/level-c/package.json deleted file mode 100644 index 4dc05099a..000000000 --- a/registry/tests/projects/transitive-deps-pass/packages/level-c/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@chain-test/level-c", - "version": "1.0.0", - "main": "index.js" -} diff --git a/registry/tests/projects/transitive-deps-pass/src/index.js b/registry/tests/projects/transitive-deps-pass/src/index.js deleted file mode 100644 index 808d617c4..000000000 --- a/registry/tests/projects/transitive-deps-pass/src/index.js +++ /dev/null @@ -1,18 +0,0 @@ -"use strict"; - -const levelA = require("@chain-test/level-a"); - -const chain = []; -let current = levelA; -while (current) { - chain.push({ name: current.name, depth: current.depth }); - current = current.child; -} - -const result = { - chain: chain, - greeting: levelA.greet("world"), - levels: chain.length -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/uuid-pass/fixture.json b/registry/tests/projects/uuid-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/uuid-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/uuid-pass/package.json b/registry/tests/projects/uuid-pass/package.json deleted file mode 100644 index fea1064f9..000000000 --- a/registry/tests/projects/uuid-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-uuid-pass", - "private": true, - "type": "module", - "dependencies": { - "uuid": "11.1.0" - } -} diff --git a/registry/tests/projects/uuid-pass/src/index.js b/registry/tests/projects/uuid-pass/src/index.js deleted file mode 100644 index 71a64d75c..000000000 --- a/registry/tests/projects/uuid-pass/src/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import { v4, v5, validate, version, NIL } from "uuid"; - -// Generate a random v4 UUID and validate its format -const id4 = v4(); -const isValid4 = validate(id4); -const ver4 = version(id4); - -// Deterministic v5 UUID with DNS namespace -const DNS_NAMESPACE = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"; -const id5 = v5("agent-os.test", DNS_NAMESPACE); -const isValid5 = validate(id5); -const ver5 = version(id5); - -// Validate the nil UUID -const nilValid = validate(NIL); - -const result = { - v4: { valid: isValid4, version: ver4 }, - v5: { value: id5, valid: isValid5, version: ver5 }, - nil: { value: NIL, valid: nilValid }, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/vite-pass/app/main.jsx b/registry/tests/projects/vite-pass/app/main.jsx deleted file mode 100644 index 44ac2be2c..000000000 --- a/registry/tests/projects/vite-pass/app/main.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import { createRoot } from "react-dom/client"; - -function App() { - return React.createElement("div", null, "Hello from Vite"); -} - -createRoot(document.getElementById("root")).render(React.createElement(App)); diff --git a/registry/tests/projects/vite-pass/fixture.json b/registry/tests/projects/vite-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/vite-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/vite-pass/index.html b/registry/tests/projects/vite-pass/index.html deleted file mode 100644 index 2b7be9e9a..000000000 --- a/registry/tests/projects/vite-pass/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Vite App - - -
- - - diff --git a/registry/tests/projects/vite-pass/package.json b/registry/tests/projects/vite-pass/package.json deleted file mode 100644 index 1d97fa401..000000000 --- a/registry/tests/projects/vite-pass/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "project-matrix-vite-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "@vitejs/plugin-react": "4.3.1", - "react": "18.3.1", - "react-dom": "18.3.1", - "vite": "5.4.2" - }, - "pnpm": { - "overrides": { - "esbuild": "npm:esbuild-wasm@0.21.5", - "rollup": "npm:@rollup/wasm-node@4.61.0" - } - } -} diff --git a/registry/tests/projects/vite-pass/src/index.js b/registry/tests/projects/vite-pass/src/index.js deleted file mode 100644 index e544ef2e5..000000000 --- a/registry/tests/projects/vite-pass/src/index.js +++ /dev/null @@ -1,71 +0,0 @@ -"use strict"; - -var fs = require("fs"); -var path = require("path"); - -var projectDir = path.resolve(__dirname, ".."); -var distDir = path.join(projectDir, "dist"); - -function ensureBuild() { - try { - fs.statSync(path.join(distDir, "index.html")); - return; - } catch (e) { - // Build output missing — run build - } - var execSync = require("child_process").execSync; - var viteBin = path.join(projectDir, "node_modules", ".bin", "vite"); - var buildEnv = Object.assign({}, process.env); - if (!buildEnv.PATH) { - buildEnv.PATH = - "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; - } - execSync(viteBin + " build", { - cwd: projectDir, - stdio: "pipe", - timeout: 30000, - env: buildEnv, - }); -} - -function main() { - ensureBuild(); - - var results = []; - - // Check index.html was generated - var indexHtml = fs.readFileSync(path.join(distDir, "index.html"), "utf8"); - results.push({ - check: "index-html", - exists: true, - hasReactRoot: indexHtml.indexOf('id="root"') !== -1, - hasScript: indexHtml.indexOf(".js") !== -1, - }); - - // Check assets directory - var assetsDir = path.join(distDir, "assets"); - var assets = fs.readdirSync(assetsDir).sort(); - var hasJs = assets.some(function (f) { - return f.endsWith(".js"); - }); - results.push({ - check: "assets", - hasJs: hasJs, - }); - - // Check compiled JS contains React component - var jsContent = ""; - assets.forEach(function (f) { - if (f.endsWith(".js")) { - jsContent += fs.readFileSync(path.join(assetsDir, f), "utf8"); - } - }); - results.push({ - check: "react-compiled", - hasComponent: jsContent.indexOf("Hello from Vite") !== -1, - }); - - console.log(JSON.stringify(results)); -} - -main(); diff --git a/registry/tests/projects/vite-pass/vite.config.mjs b/registry/tests/projects/vite-pass/vite.config.mjs deleted file mode 100644 index f9f0d5ec2..000000000 --- a/registry/tests/projects/vite-pass/vite.config.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; - -export default defineConfig({ - plugins: [react()], -}); diff --git a/registry/tests/projects/workspace-layout-pass/fixture.json b/registry/tests/projects/workspace-layout-pass/fixture.json deleted file mode 100644 index 431762972..000000000 --- a/registry/tests/projects/workspace-layout-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "packages/app/src/index.js", - "expectation": "pass", - "packageManager": "npm" -} diff --git a/registry/tests/projects/workspace-layout-pass/package.json b/registry/tests/projects/workspace-layout-pass/package.json deleted file mode 100644 index 61a4b864c..000000000 --- a/registry/tests/projects/workspace-layout-pass/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "project-matrix-workspace-layout-pass", - "private": true, - "workspaces": [ - "packages/*" - ] -} diff --git a/registry/tests/projects/workspace-layout-pass/packages/app/package.json b/registry/tests/projects/workspace-layout-pass/packages/app/package.json deleted file mode 100644 index 0ab91a496..000000000 --- a/registry/tests/projects/workspace-layout-pass/packages/app/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@workspace-test/app", - "version": "1.0.0", - "dependencies": { - "@workspace-test/lib": "*" - } -} diff --git a/registry/tests/projects/workspace-layout-pass/packages/app/src/index.js b/registry/tests/projects/workspace-layout-pass/packages/app/src/index.js deleted file mode 100644 index 72792c52e..000000000 --- a/registry/tests/projects/workspace-layout-pass/packages/app/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const { add, multiply } = require("@workspace-test/lib"); - -const results = [ - { op: "add", a: 2, b: 3, result: add(2, 3) }, - { op: "multiply", a: 4, b: 5, result: multiply(4, 5) }, - { op: "add", a: 0, b: 0, result: add(0, 0) }, -]; - -console.log(JSON.stringify(results)); diff --git a/registry/tests/projects/workspace-layout-pass/packages/lib/package.json b/registry/tests/projects/workspace-layout-pass/packages/lib/package.json deleted file mode 100644 index e7df130e9..000000000 --- a/registry/tests/projects/workspace-layout-pass/packages/lib/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@workspace-test/lib", - "version": "1.0.0", - "main": "src/index.js" -} diff --git a/registry/tests/projects/workspace-layout-pass/packages/lib/src/index.js b/registry/tests/projects/workspace-layout-pass/packages/lib/src/index.js deleted file mode 100644 index b96ff40d0..000000000 --- a/registry/tests/projects/workspace-layout-pass/packages/lib/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -function add(a, b) { - return a + b; -} - -function multiply(a, b) { - return a * b; -} - -module.exports = { add, multiply }; diff --git a/registry/tests/projects/ws-pass/fixture.json b/registry/tests/projects/ws-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/ws-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/ws-pass/package-lock.json b/registry/tests/projects/ws-pass/package-lock.json deleted file mode 100644 index 04684593e..000000000 --- a/registry/tests/projects/ws-pass/package-lock.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "project-matrix-ws-pass", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "project-matrix-ws-pass", - "dependencies": { - "ws": "8.18.0" - } - }, - "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/registry/tests/projects/ws-pass/package.json b/registry/tests/projects/ws-pass/package.json deleted file mode 100644 index c6f5494ae..000000000 --- a/registry/tests/projects/ws-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-ws-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "ws": "8.18.0" - } -} diff --git a/registry/tests/projects/ws-pass/src/index.js b/registry/tests/projects/ws-pass/src/index.js deleted file mode 100644 index e41981a60..000000000 --- a/registry/tests/projects/ws-pass/src/index.js +++ /dev/null @@ -1,97 +0,0 @@ -"use strict"; - -const { WebSocket, WebSocketServer } = require("ws"); - -async function main() { - const serverEvents = []; - const clientEvents = []; - - // Start server on random port - const wss = new WebSocketServer({ port: 0 }); - - wss.on("connection", (ws) => { - serverEvents.push("connection"); - - ws.on("message", (data, isBinary) => { - serverEvents.push(isBinary ? "binary-message" : "text-message"); - // Echo back - ws.send(data, { binary: isBinary }); - }); - - ws.on("close", () => { - serverEvents.push("close"); - }); - }); - - await new Promise((resolve) => wss.on("listening", resolve)); - const port = wss.address().port; - - try { - const textEcho = await new Promise((resolve, reject) => { - const ws = new WebSocket(`ws://127.0.0.1:${port}`); - - ws.on("open", () => { - clientEvents.push("open"); - ws.send("hello-ws"); - }); - - ws.on("message", (data) => { - clientEvents.push("text-message"); - ws.close(); - resolve(data.toString()); - }); - - ws.on("close", () => { - clientEvents.push("text-close"); - }); - - ws.on("error", reject); - }); - - // Wait briefly for server close event - await new Promise((resolve) => setTimeout(resolve, 50)); - - const binaryEcho = await new Promise((resolve, reject) => { - const ws = new WebSocket(`ws://127.0.0.1:${port}`); - - ws.on("open", () => { - clientEvents.push("binary-open"); - ws.send(Buffer.from([0xde, 0xad, 0xbe, 0xef])); - }); - - ws.on("message", (data, isBinary) => { - clientEvents.push("binary-message"); - ws.close(); - resolve({ - isBinary, - hex: Buffer.from(data).toString("hex"), - }); - }); - - ws.on("close", () => { - clientEvents.push("binary-close"); - }); - - ws.on("error", reject); - }); - - // Wait briefly for server close event - await new Promise((resolve) => setTimeout(resolve, 50)); - - const result = { - textEcho, - binaryEcho, - serverEvents: serverEvents.sort(), - clientEvents: clientEvents.sort(), - }; - - console.log(JSON.stringify(result)); - } finally { - await new Promise((resolve) => wss.close(resolve)); - } -} - -main().catch((err) => { - console.error(err.message); - process.exit(1); -}); diff --git a/registry/tests/projects/yaml-pass/fixture.json b/registry/tests/projects/yaml-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/yaml-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/yaml-pass/package.json b/registry/tests/projects/yaml-pass/package.json deleted file mode 100644 index f584183f2..000000000 --- a/registry/tests/projects/yaml-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-yaml-pass", - "private": true, - "type": "module", - "dependencies": { - "yaml": "2.8.0" - } -} diff --git a/registry/tests/projects/yaml-pass/src/index.js b/registry/tests/projects/yaml-pass/src/index.js deleted file mode 100644 index e10aad02b..000000000 --- a/registry/tests/projects/yaml-pass/src/index.js +++ /dev/null @@ -1,51 +0,0 @@ -import { parse, stringify, parseDocument } from "yaml"; - -// Parse a YAML string -const yamlStr = ` -name: agent-os -version: 1.0.0 -features: - - sandboxing - - isolation - - compatibility -config: - timeout: 30 - retries: 3 - nested: - enabled: true - level: 2 -`; - -const parsed = parse(yamlStr); - -// Stringify a JS object back to YAML -const obj = { - database: { - host: "localhost", - port: 5432, - credentials: { - user: "admin", - pass: "secret", - }, - }, - tags: ["prod", "us-east"], -}; - -const stringified = stringify(obj); - -// Re-parse the stringified output to verify round-trip -const roundTrip = parse(stringified); - -// Parse a document for node-level access -const doc = parseDocument("key: value\nlist:\n - a\n - b"); -const docJSON = doc.toJSON(); - -const result = { - parsed, - stringified, - roundTrip, - roundTripMatch: JSON.stringify(obj) === JSON.stringify(roundTrip), - docJSON, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/projects/yarn-berry-layout-pass/.yarnrc.yml b/registry/tests/projects/yarn-berry-layout-pass/.yarnrc.yml deleted file mode 100644 index 3186f3f07..000000000 --- a/registry/tests/projects/yarn-berry-layout-pass/.yarnrc.yml +++ /dev/null @@ -1 +0,0 @@ -nodeLinker: node-modules diff --git a/registry/tests/projects/yarn-berry-layout-pass/fixture.json b/registry/tests/projects/yarn-berry-layout-pass/fixture.json deleted file mode 100644 index 198b477b6..000000000 --- a/registry/tests/projects/yarn-berry-layout-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "yarn" -} diff --git a/registry/tests/projects/yarn-berry-layout-pass/package.json b/registry/tests/projects/yarn-berry-layout-pass/package.json deleted file mode 100644 index 10d105206..000000000 --- a/registry/tests/projects/yarn-berry-layout-pass/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "project-matrix-yarn-berry-layout-pass", - "private": true, - "type": "commonjs", - "packageManager": "yarn@4.13.0", - "dependencies": { - "left-pad": "0.0.3" - } -} diff --git a/registry/tests/projects/yarn-berry-layout-pass/src/index.js b/registry/tests/projects/yarn-berry-layout-pass/src/index.js deleted file mode 100644 index 6ab481e2f..000000000 --- a/registry/tests/projects/yarn-berry-layout-pass/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const leftPad = require("left-pad"); - -const results = [ - { input: "hello", width: 10, padded: leftPad("hello", 10) }, - { input: "42", width: 5, fill: "0", padded: leftPad("42", 5, "0") }, - { input: "", width: 3, padded: leftPad("", 3) }, -]; - -console.log(JSON.stringify(results)); diff --git a/registry/tests/projects/yarn-berry-layout-pass/yarn.lock b/registry/tests/projects/yarn-berry-layout-pass/yarn.lock deleted file mode 100644 index 487883fc4..000000000 --- a/registry/tests/projects/yarn-berry-layout-pass/yarn.lock +++ /dev/null @@ -1,21 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10c0 - -"left-pad@npm:0.0.3": - version: 0.0.3 - resolution: "left-pad@npm:0.0.3" - checksum: 10c0/ff34f59ffd2e550f5b660f850fd3096b7a058d609406ae24a04bbbdaee3893a804b5c6bf9e32fb725808e7aced6b13881c047a68e052f10c66180eee68e91e33 - languageName: node - linkType: hard - -"project-matrix-yarn-berry-layout-pass@workspace:.": - version: 0.0.0-use.local - resolution: "project-matrix-yarn-berry-layout-pass@workspace:." - dependencies: - left-pad: "npm:0.0.3" - languageName: unknown - linkType: soft diff --git a/registry/tests/projects/yarn-classic-layout-pass/fixture.json b/registry/tests/projects/yarn-classic-layout-pass/fixture.json deleted file mode 100644 index 198b477b6..000000000 --- a/registry/tests/projects/yarn-classic-layout-pass/fixture.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass", - "packageManager": "yarn" -} diff --git a/registry/tests/projects/yarn-classic-layout-pass/package.json b/registry/tests/projects/yarn-classic-layout-pass/package.json deleted file mode 100644 index ea64d44a8..000000000 --- a/registry/tests/projects/yarn-classic-layout-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-yarn-classic-layout-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "left-pad": "0.0.3" - } -} diff --git a/registry/tests/projects/yarn-classic-layout-pass/src/index.js b/registry/tests/projects/yarn-classic-layout-pass/src/index.js deleted file mode 100644 index 6ab481e2f..000000000 --- a/registry/tests/projects/yarn-classic-layout-pass/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const leftPad = require("left-pad"); - -const results = [ - { input: "hello", width: 10, padded: leftPad("hello", 10) }, - { input: "42", width: 5, fill: "0", padded: leftPad("42", 5, "0") }, - { input: "", width: 3, padded: leftPad("", 3) }, -]; - -console.log(JSON.stringify(results)); diff --git a/registry/tests/projects/yarn-classic-layout-pass/yarn.lock b/registry/tests/projects/yarn-classic-layout-pass/yarn.lock deleted file mode 100644 index d51a18b74..000000000 --- a/registry/tests/projects/yarn-classic-layout-pass/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -left-pad@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-0.0.3.tgz#04d99b4a1eaf9e5f79c05e5d745d53edd1aa8aa1" - integrity sha512-Qli5dSpAXQOSw1y/M+uBKT37rj6iZAQMz6Uy5/ZYGIhBLS/ODRHqL4XIDvSAtYpjfia0XKNztlPFa806TWw5Gw== diff --git a/registry/tests/projects/zod-pass/fixture.json b/registry/tests/projects/zod-pass/fixture.json deleted file mode 100644 index b365bf6f2..000000000 --- a/registry/tests/projects/zod-pass/fixture.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": "src/index.js", - "expectation": "pass" -} diff --git a/registry/tests/projects/zod-pass/package.json b/registry/tests/projects/zod-pass/package.json deleted file mode 100644 index c90fc5e8d..000000000 --- a/registry/tests/projects/zod-pass/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "project-matrix-zod-pass", - "private": true, - "type": "commonjs", - "dependencies": { - "zod": "3.24.2" - } -} diff --git a/registry/tests/projects/zod-pass/pnpm-lock.yaml b/registry/tests/projects/zod-pass/pnpm-lock.yaml deleted file mode 100644 index d8c36e5d9..000000000 --- a/registry/tests/projects/zod-pass/pnpm-lock.yaml +++ /dev/null @@ -1,22 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - zod: - specifier: 3.24.2 - version: 3.24.2 - -packages: - - zod@3.24.2: - resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} - -snapshots: - - zod@3.24.2: {} diff --git a/registry/tests/projects/zod-pass/src/index.js b/registry/tests/projects/zod-pass/src/index.js deleted file mode 100644 index 0b8c0bf9b..000000000 --- a/registry/tests/projects/zod-pass/src/index.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; - -const { z } = require("zod"); - -// Define schemas -const userSchema = z.object({ - name: z.string().min(1), - age: z.number().int().positive(), - email: z.string().email(), - tags: z.array(z.string()).optional(), -}); - -const statusSchema = z.enum(["active", "inactive", "pending"]); - -// Successful validation -const validUser = userSchema.parse({ - name: "Alice", - age: 30, - email: "alice@example.com", - tags: ["admin"], -}); - -// Failed validation -let validationError = null; -try { - userSchema.parse({ name: "", age: -1, email: "bad" }); -} catch (err) { - validationError = { - issueCount: err.issues.length, - codes: err.issues.map((i) => i.code).sort(), - }; -} - -// Safe parse -const safeResult = userSchema.safeParse({ name: "Bob", age: 25, email: "bob@test.com" }); -const safeFail = userSchema.safeParse({ name: 123 }); - -// Enum -const enumResult = statusSchema.safeParse("active"); -const enumFail = statusSchema.safeParse("unknown"); - -// Transform and refine -const doubled = z.number().transform((n) => n * 2).parse(5); - -const result = { - validUser: { name: validUser.name, age: validUser.age, hasTags: Array.isArray(validUser.tags) }, - validationError, - safeParseSuccess: safeResult.success, - safeParseFail: safeFail.success, - enumSuccess: enumResult.success, - enumFail: enumFail.success, - transformed: doubled, -}; - -console.log(JSON.stringify(result)); diff --git a/registry/tests/terminal-harness.ts b/registry/tests/terminal-harness.ts deleted file mode 100644 index b01ad66a9..000000000 --- a/registry/tests/terminal-harness.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * TerminalHarness wires openShell() to a headless xterm Terminal so registry - * tests can assert against deterministic terminal screen state. - */ - -import { Terminal } from "@xterm/headless"; -import type { Kernel } from "./helpers.js"; - -type ShellHandle = ReturnType; - -const SETTLE_MS = 50; -const POLL_MS = 20; -const DEFAULT_WAIT_TIMEOUT_MS = 5_000; - -export class TerminalHarness { - readonly term: Terminal; - readonly shell: ShellHandle; - private typing = false; - private disposed = false; - - constructor( - kernel: Kernel, - options?: { - cols?: number; - rows?: number; - env?: Record; - cwd?: string; - }, - ) { - const cols = options?.cols ?? 80; - const rows = options?.rows ?? 24; - - this.term = new Terminal({ cols, rows, allowProposedApi: true }); - this.shell = kernel.openShell({ - cols, - rows, - env: options?.env, - cwd: options?.cwd, - onStderr: (data: Uint8Array) => { - this.term.write(data); - }, - }); - this.shell.onData = (data: Uint8Array) => { - this.term.write(data); - }; - } - - async type(input: string): Promise { - if (this.typing) { - throw new Error( - "TerminalHarness.type() called while previous type() is still in-flight", - ); - } - this.typing = true; - try { - await this.typeInternal(input); - } finally { - this.typing = false; - } - } - - private typeInternal(input: string): Promise { - return new Promise((resolve) => { - let timer: ReturnType | null = null; - const originalOnData = this.shell.onData; - - const resetTimer = () => { - if (timer !== null) clearTimeout(timer); - timer = setTimeout(() => { - this.shell.onData = originalOnData; - resolve(); - }, SETTLE_MS); - }; - - this.shell.onData = (data: Uint8Array) => { - this.term.write(data); - resetTimer(); - }; - - resetTimer(); - this.shell.write(input); - }); - } - - screenshotTrimmed(): string { - const buf = this.term.buffer.active; - const lines: string[] = []; - - for (let row = 0; row < this.term.rows; row++) { - const line = buf.getLine(buf.viewportY + row); - lines.push(line ? line.translateToString(true) : ""); - } - - while (lines.length > 0 && lines[lines.length - 1] === "") { - lines.pop(); - } - - return lines.join("\n"); - } - - line(row: number): string { - const buf = this.term.buffer.active; - const line = buf.getLine(buf.viewportY + row); - return line ? line.translateToString(true) : ""; - } - - async waitFor( - text: string, - occurrence: number = 1, - timeoutMs: number = DEFAULT_WAIT_TIMEOUT_MS, - ): Promise { - const deadline = Date.now() + timeoutMs; - - while (true) { - const screen = this.screenshotTrimmed(); - - let count = 0; - let idx = -1; - while (true) { - idx = screen.indexOf(text, idx + 1); - if (idx === -1) break; - count++; - if (count >= occurrence) return; - } - - if (Date.now() >= deadline) { - throw new Error( - `waitFor("${text}", ${occurrence}) timed out after ${timeoutMs}ms.\n` + - `Expected: "${text}" (occurrence ${occurrence})\n` + - `Screen:\n${screen}`, - ); - } - - await new Promise((resolve) => setTimeout(resolve, POLL_MS)); - } - } - - async exit(): Promise { - this.shell.write("\x04"); - return this.shell.wait(); - } - - async dispose(): Promise { - if (this.disposed) return; - this.disposed = true; - - try { - this.shell.kill(); - await Promise.race([ - this.shell.wait(), - new Promise((resolve) => setTimeout(resolve, 500)), - ]); - } catch { - // Shell may already be gone. - } - - this.term.dispose(); - } -} diff --git a/scripts/benchmarks/README.md b/scripts/benchmarks/README.md index 4eb1e33b1..5f8230dea 100644 --- a/scripts/benchmarks/README.md +++ b/scripts/benchmarks/README.md @@ -19,7 +19,7 @@ Two lanes, same llmock, same timer: - **`vm`** — `AgentOs.create()` (`vmCreate`) + `createSession("pi")` (`sessionCreate`). - **`bare-node`** — the *same* pi-SDK session construction on host node, **no VM** (`sessionCreate` = load pi SDK + `createAgentSession`). This is the "Node.js - equivalent" baseline; it mirrors `registry/agent/pi/src/adapter.ts` `newSession`. + equivalent" baseline; it mirrors `../secure-exec/registry/agent/pi/src/adapter.ts` `newSession`. Derived metrics: diff --git a/scripts/benchmarks/session.bench.ts b/scripts/benchmarks/session.bench.ts index 08ef7efe3..893a8e5ba 100644 --- a/scripts/benchmarks/session.bench.ts +++ b/scripts/benchmarks/session.bench.ts @@ -92,7 +92,10 @@ async function loadPiSoftware(): Promise { // dist/sdk-snapshot.js bundle). The published @agentos-software/pi may predate // the snapshot work, so benchmarking against it silently measures the // non-snapshot fallback path and hides the optimization entirely. - const local = join(import.meta.dirname, "../../registry/agent/pi/dist/index.js"); + const local = join( + import.meta.dirname, + "../../../secure-exec/registry/agent/pi/dist/index.js", + ); if (existsSync(local)) return (await import(local)).default; // Fallback: the published/installed software package. Variable specifier so // this typechecks even when the package isn't installed in the dev workspace. @@ -101,7 +104,7 @@ async function loadPiSoftware(): Promise { return (await import(piPkg)).default; } catch { throw new Error( - "Could not resolve the pi software package (registry/agent/pi/dist or @agentos-software/pi). Build it first.", + "Could not resolve the pi software package (../secure-exec/registry/agent/pi/dist or @agentos-software/pi). Build it first.", ); } } @@ -112,7 +115,12 @@ const PI_SDK_PKG = "@mariozechner/pi-coding-agent"; function findPiSdkRoot(): string | null { const reqs = [ createRequire(join(import.meta.dirname, "../../package.json")), - createRequire(join(import.meta.dirname, "../../registry/agent/pi/package.json")), + createRequire( + join( + import.meta.dirname, + "../../../secure-exec/registry/agent/pi/package.json", + ), + ), ]; for (const req of reqs) { for (const base of req.resolve.paths(PI_SDK_PKG) ?? []) { @@ -160,7 +168,7 @@ function resolvePiSdkRootOrThrow(): string { * The bare-node session-creation script, run in a FRESH node process per sample * so each pays the full cold SDK load (the VM lane reloads the SDK in a fresh V8 * isolate every session, so this is the apples-to-apples "Node.js equivalent"). - * Mirrors registry/agent/pi/src/adapter.ts `newSession`, with no VM. It times the + * Mirrors secure-exec registry/agent/pi/src/adapter.ts `newSession`, with no VM. It times the * SDK load + session construction internally and prints `__MS__=`. */ function bareNodeScript(root: string): string { diff --git a/scripts/check-registry-software-split.mjs b/scripts/check-registry-software-split.mjs deleted file mode 100644 index 7e35f210f..000000000 --- a/scripts/check-registry-software-split.mjs +++ /dev/null @@ -1,150 +0,0 @@ -import { - existsSync, - readdirSync, - readFileSync, - statSync, -} from "node:fs"; -import { dirname, join, relative, resolve } from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; - -const defaultRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const agentOsPackagePattern = /^@rivet-dev\/agent-os(?:-|$)/; -const dependencySections = [ - "dependencies", - "devDependencies", - "peerDependencies", - "optionalDependencies", -]; - -function formatPath(root, path) { - return relative(root, path).replaceAll("\\", "/") || "."; -} - -function readJson(path) { - return JSON.parse(readFileSync(path, "utf8")); -} - -function collectSoftwareDirs(root) { - const softwareRoot = join(root, "registry", "software"); - if (!existsSync(softwareRoot)) { - return []; - } - - return readdirSync(softwareRoot, { withFileTypes: true }) - .filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")) - .map((entry) => join(softwareRoot, entry.name)) - .filter((packageDir) => { - const manifestPath = join(packageDir, "package.json"); - return existsSync(manifestPath) && statSync(manifestPath).isFile(); - }) - .sort((left, right) => left.localeCompare(right)); -} - -function checkDependencies(packageName, manifest, errors) { - for (const section of dependencySections) { - const dependencies = manifest[section]; - if (!dependencies || typeof dependencies !== "object") { - continue; - } - for (const dependencyName of Object.keys(dependencies)) { - if (agentOsPackagePattern.test(dependencyName)) { - errors.push( - `${packageName} must not depend on Agent OS package ${dependencyName} in registry software ${section}`, - ); - } - } - } -} - -export function checkRegistrySoftwareSplit(options = {}) { - const root = resolve(options.root ?? defaultRoot); - const errors = []; - - for (const packageDir of collectSoftwareDirs(root)) { - const dirName = packageDir.split(/[\\/]/).at(-1); - const manifestPath = join(packageDir, "package.json"); - const metadataPath = join(packageDir, "secure-exec-package.json"); - const staleMetadataPath = join(packageDir, "agentos-package.json"); - const staleArtifactMetadataPath = join( - packageDir, - "agentos-package.meta.json", - ); - - const manifest = readJson(manifestPath); - const expectedName = `@agentos-software/${dirName}`; - if (manifest.name !== expectedName) { - errors.push( - `${formatPath(root, manifestPath)} must be named ${expectedName}, found ${manifest.name}`, - ); - } - - if (existsSync(staleMetadataPath)) { - errors.push( - `${formatPath(root, staleMetadataPath)} must be renamed to secure-exec-package.json`, - ); - } - if (existsSync(staleArtifactMetadataPath)) { - errors.push( - `${formatPath(root, staleArtifactMetadataPath)} must be renamed to secure-exec-package.meta.json`, - ); - } - if (!existsSync(metadataPath)) { - errors.push(`${formatPath(root, metadataPath)} is required`); - } else { - const metadata = readJson(metadataPath); - if (metadata.name !== manifest.name) { - errors.push( - `${formatPath(root, metadataPath)} name must match package.json (${manifest.name}), found ${metadata.name}`, - ); - } - } - - checkDependencies(manifest.name ?? expectedName, manifest, errors); - } - - return errors; -} - -function parseArgs(argv) { - let root = defaultRoot; - for (let i = 0; i < argv.length; i++) { - const arg = argv[i]; - if (arg === "--root") { - const value = argv[++i]; - if (!value) { - throw new Error("--root requires a path"); - } - root = value; - continue; - } - if (arg.startsWith("--root=")) { - root = arg.slice("--root=".length); - continue; - } - throw new Error(`unknown argument: ${arg}`); - } - return { root }; -} - -export function main(argv = process.argv.slice(2)) { - const { root } = parseArgs(argv); - const resolvedRoot = resolve(root); - if (!existsSync(resolvedRoot) || !statSync(resolvedRoot).isDirectory()) { - throw new Error(`root is not a directory: ${resolvedRoot}`); - } - - const errors = checkRegistrySoftwareSplit({ root: resolvedRoot }); - if (errors.length > 0) { - for (const error of errors) { - console.error(error); - } - process.exitCode = 1; - return; - } - - console.log("registry software split check ok"); -} - -if (import.meta.url === pathToFileURL(process.argv[1]).href) { - main(); -} diff --git a/scripts/check-registry-software-split.test.mjs b/scripts/check-registry-software-split.test.mjs deleted file mode 100644 index ad7b62070..000000000 --- a/scripts/check-registry-software-split.test.mjs +++ /dev/null @@ -1,87 +0,0 @@ -import assert from "node:assert/strict"; -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import test from "node:test"; -import { checkRegistrySoftwareSplit } from "./check-registry-software-split.mjs"; - -function withFixture(fn) { - const root = mkdtempSync(join(tmpdir(), "registry-software-split-")); - try { - return fn(root); - } finally { - rmSync(root, { recursive: true, force: true }); - } -} - -function writeJson(root, rel, value) { - const path = join(root, rel); - mkdirSync(join(path, ".."), { recursive: true }); - writeFileSync(path, `${JSON.stringify(value, null, "\t")}\n`); -} - -test("accepts agentos-software registry software package metadata", () => { - withFixture((root) => { - writeJson(root, "registry/software/coreutils/package.json", { - name: "@agentos-software/coreutils", - dependencies: { - "@secure-exec/registry-types": "workspace:*", - }, - }); - writeJson(root, "registry/software/coreutils/secure-exec-package.json", { - name: "@agentos-software/coreutils", - }); - - assert.deepEqual(checkRegistrySoftwareSplit({ root }), []); - }); -}); - -test("rejects stale Agent OS package names and metadata files", () => { - withFixture((root) => { - writeJson(root, "registry/software/grep/package.json", { - name: "@rivet-dev/agent-os-pkg-grep", - }); - writeJson(root, "registry/software/grep/agentos-package.json", { - name: "@rivet-dev/agent-os-pkg-grep", - }); - - assert.deepEqual(checkRegistrySoftwareSplit({ root }), [ - "registry/software/grep/package.json must be named @agentos-software/grep, found @rivet-dev/agent-os-pkg-grep", - "registry/software/grep/agentos-package.json must be renamed to secure-exec-package.json", - "registry/software/grep/secure-exec-package.json is required", - ]); - }); -}); - -test("rejects metadata name drift", () => { - withFixture((root) => { - writeJson(root, "registry/software/sed/package.json", { - name: "@agentos-software/sed", - }); - writeJson(root, "registry/software/sed/secure-exec-package.json", { - name: "@agentos-software/grep", - }); - - assert.deepEqual(checkRegistrySoftwareSplit({ root }), [ - "registry/software/sed/secure-exec-package.json name must match package.json (@agentos-software/sed), found @agentos-software/grep", - ]); - }); -}); - -test("rejects Agent OS dependencies inside software manifests", () => { - withFixture((root) => { - writeJson(root, "registry/software/common/package.json", { - name: "@agentos-software/common", - dependencies: { - "@rivet-dev/agent-os-core": "workspace:*", - }, - }); - writeJson(root, "registry/software/common/secure-exec-package.json", { - name: "@agentos-software/common", - }); - - assert.deepEqual(checkRegistrySoftwareSplit({ root }), [ - "@agentos-software/common must not depend on Agent OS package @rivet-dev/agent-os-core in registry software dependencies", - ]); - }); -}); diff --git a/scripts/check-registry-test-runtime-boundary.mjs b/scripts/check-registry-test-runtime-boundary.mjs deleted file mode 100644 index 9a4ca782e..000000000 --- a/scripts/check-registry-test-runtime-boundary.mjs +++ /dev/null @@ -1,155 +0,0 @@ -import { - existsSync, - readdirSync, - readFileSync, - statSync, -} from "node:fs"; -import { dirname, join, relative, resolve } from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; - -const defaultRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const forbiddenSpecifiers = new Set([ - "@rivet-dev/agentos-core/test/runtime", - "@rivet-dev/agentos-core/internal/runtime-compat", - "@secure-exec/core/test-runtime", -]); -const allowedRegistryHelper = "registry/tests/helpers.ts"; -const scannedExtensions = new Set([ - ".js", - ".jsx", - ".mjs", - ".cjs", - ".ts", - ".tsx", - ".mts", - ".cts", -]); -const ignoredDirectories = new Set([ - "dist", - "node_modules", - "coverage", - ".turbo", - ".vitest", -]); -const importSpecifierPatterns = [ - /\bimport\s+(?:type\s+)?(?:[^"'()]*?\s+from\s+)?["']([^"']+)["']/g, - /\bexport\s+(?:type\s+)?[^"'()]*?\s+from\s+["']([^"']+)["']/g, - /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g, - /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g, -]; - -function formatPath(root, path) { - return relative(root, path).replaceAll("\\", "/") || "."; -} - -function shouldScanFile(path) { - for (const extension of scannedExtensions) { - if (path.endsWith(extension)) { - return true; - } - } - return false; -} - -function collectFiles(root, dir) { - if (!existsSync(dir)) { - return []; - } - - const files = []; - for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => - a.name.localeCompare(b.name), - )) { - const path = join(dir, entry.name); - if (entry.isDirectory()) { - if (!ignoredDirectories.has(entry.name)) { - files.push(...collectFiles(root, path)); - } - continue; - } - if (entry.isFile() && shouldScanFile(path)) { - files.push(path); - } - } - return files; -} - -function collectImportSpecifiers(source) { - const specifiers = []; - for (const pattern of importSpecifierPatterns) { - pattern.lastIndex = 0; - for (const match of source.matchAll(pattern)) { - specifiers.push(match[1]); - } - } - return specifiers; -} - -export function checkRegistryTestRuntimeBoundary(options = {}) { - const root = resolve(options.root ?? defaultRoot); - const registryTestsDir = join(root, "registry", "tests"); - const files = (options.files ?? collectFiles(root, registryTestsDir)).toSorted(); - const errors = []; - - for (const filePath of files) { - const rel = formatPath(root, filePath); - if (rel === allowedRegistryHelper) { - continue; - } - const source = readFileSync(filePath, "utf8"); - const forbiddenImport = collectImportSpecifiers(source).find((specifier) => - forbiddenSpecifiers.has(specifier), - ); - if (forbiddenImport) { - errors.push( - `${rel} must import registry test runtime helpers from ../helpers.js instead of ${forbiddenImport}`, - ); - } - } - - return errors; -} - -function parseArgs(argv) { - let root = defaultRoot; - for (let i = 0; i < argv.length; i++) { - const arg = argv[i]; - if (arg === "--root") { - const value = argv[++i]; - if (!value) { - throw new Error("--root requires a path"); - } - root = value; - continue; - } - if (arg.startsWith("--root=")) { - root = arg.slice("--root=".length); - continue; - } - throw new Error(`unknown argument: ${arg}`); - } - return { root }; -} - -export function main(argv = process.argv.slice(2)) { - const { root } = parseArgs(argv); - const resolvedRoot = resolve(root); - if (!existsSync(resolvedRoot) || !statSync(resolvedRoot).isDirectory()) { - throw new Error(`root is not a directory: ${resolvedRoot}`); - } - - const errors = checkRegistryTestRuntimeBoundary({ root: resolvedRoot }); - if (errors.length > 0) { - for (const error of errors) { - console.error(error); - } - process.exitCode = 1; - return; - } - - console.log("registry test runtime boundary ok"); -} - -if (import.meta.url === pathToFileURL(process.argv[1]).href) { - main(); -} diff --git a/scripts/check-registry-test-runtime-boundary.test.mjs b/scripts/check-registry-test-runtime-boundary.test.mjs deleted file mode 100644 index 6cb03b67f..000000000 --- a/scripts/check-registry-test-runtime-boundary.test.mjs +++ /dev/null @@ -1,94 +0,0 @@ -import assert from "node:assert/strict"; -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import test from "node:test"; -import { checkRegistryTestRuntimeBoundary } from "./check-registry-test-runtime-boundary.mjs"; - -function withFixture(fn) { - const root = mkdtempSync(join(tmpdir(), "registry-test-runtime-boundary-")); - try { - return fn(root); - } finally { - rmSync(root, { recursive: true, force: true }); - } -} - -function write(root, rel, contents) { - const path = join(root, rel); - mkdirSync(join(path, ".."), { recursive: true }); - writeFileSync(path, contents); -} - -test("accepts the central registry test runtime helper", () => { - withFixture((root) => { - write( - root, - "registry/tests/helpers.ts", - 'export { createWasmVmRuntime } from "@secure-exec/core/test-runtime";\n', - ); - write( - root, - "registry/tests/wasmvm/example.test.ts", - 'import { createWasmVmRuntime } from "../helpers.js";\n', - ); - - assert.deepEqual(checkRegistryTestRuntimeBoundary({ root }), []); - }); -}); - -test("rejects direct Agent OS test runtime imports in registry tests", () => { - withFixture((root) => { - write( - root, - "registry/tests/wasmvm/example.test.ts", - 'import { createWasmVmRuntime } from "@rivet-dev/agentos-core/test/runtime";\n', - ); - - assert.deepEqual(checkRegistryTestRuntimeBoundary({ root }), [ - "registry/tests/wasmvm/example.test.ts must import registry test runtime helpers from ../helpers.js instead of @rivet-dev/agentos-core/test/runtime", - ]); - }); -}); - -test("rejects direct re-exports and requires", () => { - withFixture((root) => { - write( - root, - "registry/tests/kernel/example.test.ts", - 'export { createKernel } from "@rivet-dev/agentos-core/test/runtime";\nconst rt = require("@rivet-dev/agentos-core/test/runtime");\n', - ); - - assert.deepEqual(checkRegistryTestRuntimeBoundary({ root }), [ - "registry/tests/kernel/example.test.ts must import registry test runtime helpers from ../helpers.js instead of @rivet-dev/agentos-core/test/runtime", - ]); - }); -}); - -test("rejects direct Agent OS runtime compat imports in registry tests", () => { - withFixture((root) => { - write( - root, - "registry/tests/wasmvm/example.test.ts", - 'import { createWasmVmRuntime } from "@rivet-dev/agentos-core/internal/runtime-compat";\n', - ); - - assert.deepEqual(checkRegistryTestRuntimeBoundary({ root }), [ - "registry/tests/wasmvm/example.test.ts must import registry test runtime helpers from ../helpers.js instead of @rivet-dev/agentos-core/internal/runtime-compat", - ]); - }); -}); - -test("rejects direct secure-exec test runtime imports in registry tests", () => { - withFixture((root) => { - write( - root, - "registry/tests/wasmvm/example.test.ts", - 'import { createWasmVmRuntime } from "@secure-exec/core/test-runtime";\n', - ); - - assert.deepEqual(checkRegistryTestRuntimeBoundary({ root }), [ - "registry/tests/wasmvm/example.test.ts must import registry test runtime helpers from ../helpers.js instead of @secure-exec/core/test-runtime", - ]); - }); -}); diff --git a/scripts/secure-exec-dep.mjs b/scripts/secure-exec-dep.mjs index 3ffa68254..b710477fd 100644 --- a/scripts/secure-exec-dep.mjs +++ b/scripts/secure-exec-dep.mjs @@ -59,8 +59,20 @@ const SWAPPABLE_SCOPED = { "@secure-exec/google-drive": "registry/file-system/google-drive", "@secure-exec/sandbox": "registry/tool/sandbox", }; -// @agentos-software/ always maps to registry/software/ (renamed 3rd-party pkgs). -const softwareSubpath = (name) => `registry/software/${name.split("/")[1]}`; +// Agent packages are owned by secure-exec under registry/agent/*; generic VM +// software packages are owned under registry/software/*. +const AGENT_PACKAGE_SUBPATHS = { + "@agentos-software/claude-code": "registry/agent/claude", + "@agentos-software/codex": "registry/agent/codex", + "@agentos-software/opencode": "registry/agent/opencode", + "@agentos-software/pi": "registry/agent/pi", + "@agentos-software/pi-cli": "registry/agent/pi-cli", +}; +const SOFTWARE_PACKAGE_SUBPATHS = { + "@agentos-software/codex-cli": "registry/software/codex", +}; +const softwareSubpath = (name) => + SOFTWARE_PACKAGE_SUBPATHS[name] ?? `registry/software/${name.split("/")[1]}`; // Published-only deps with no local source: always resolved from the registry. const REGISTRY_ONLY = new Set(["@secure-exec/nodejs"]); @@ -95,7 +107,7 @@ const CATALOG_END = "# <<< secure-exec catalog <<<"; // --------------------------------------------------------------------------- function consumerManifests() { const dirs = [ROOT]; - for (const group of ["packages", "examples", "registry/agent"]) { + for (const group of ["packages", "examples"]) { const base = path.join(ROOT, group); if (!existsSync(base)) continue; for (const entry of readdirSync(base, { withFileTypes: true })) { @@ -118,7 +130,9 @@ function isSwappable(name) { return name.startsWith("@agentos-software/") || name in SWAPPABLE_SCOPED; } function localSubpath(name) { - if (name.startsWith("@agentos-software/")) return softwareSubpath(name); + if (name.startsWith("@agentos-software/")) { + return AGENT_PACKAGE_SUBPATHS[name] ?? softwareSubpath(name); + } return SWAPPABLE_SCOPED[name]; } @@ -201,10 +215,10 @@ function versionFor(name, pinned) { return SEED_VERSIONS[name] ?? SEED_SOFTWARE_VERSION; } // Which managed group a catalog package belongs to. secure-exec (the runtime) -// and the @agentos-software/* software packages publish on independent cadences, so +// and the @agentos-software/* registry packages publish on independent cadences, so // versions are set per scope. // "secure-exec" -> @secure-exec/* swappable scope (core, s3, google-drive, sandbox) -// "agentos-pkgs" -> @agentos-software/* renamed third-party software packages +// "agentos-pkgs" -> @agentos-software/* registry packages // "registry-only" -> published-only deps pinned independently (e.g. @secure-exec/nodejs) function catalogScope(name) { if (REGISTRY_ONLY.has(name)) return "registry-only";