* refactor: extract reusable AI runtime package * refactor: complete AI provider relocation * refactor: keep llm core internal * refactor(ai): make @openclaw/ai self-contained with host policy ports Move pure transport helpers (tool projections, strict-schema normalization, prompt-cache boundary, stream guards, anthropic/openai compat, request activity) from src into packages/ai; move utf16-slice into normalization-core. Inject host policy (guarded fetch, redaction, strict-tool defaults, diagnostics logging) through AiTransportHost with inert library defaults installed by src/llm/stream.ts. Narrow the public barrel to instance-scoped createApiRegistry/createLlmRuntime; the process-default runtime moves behind internal/ and registerBuiltInApiProviders takes an explicit registry. Delete the src/llm/api-registry re-export facade. * fix(ai): teach node, jiti, and vite resolvers the @openclaw/ai and utf16-slice subpaths The workspace alias tables in root-alias.cjs, plugin-sdk-native-resolver, sdk-alias, the shared vitest config, and the Control UI vite config only knew @openclaw/llm-core; Node-side plugin loading resolved @openclaw/ai through the pnpm symlink to the unbuilt dist (checks-node-compact CI failures), and the Control UI build broke on the new normalization-core/utf16-slice subpath. * chore(ui): drop leftover service-worker debug logging * build(release): ship @openclaw/ai with its own shrinkwrap and honest dependency set packages/ai declares only its six real runtime deps (kysely, chalk, json5, tslog, zod, fs-safe, and proxyline were never imported); orphaned root deps removed. generate-npm-shrinkwrap now treats publishable packages/* like publishable plugins so the AI tarball pins its transitive tree even though workspace deps are omitted from the root shrinkwrap. knip learns the package entry points; the tsdown dts neverBundle option moves to its documented deps.dts home; the README documents the no-semver internal/* contract and host ports. * docs(ai): add minimal external-consumer example app examples/ai-chat consumes only the public @openclaw/ai surface (built dist via the workspace link): isolated runtime, built-in provider registration, one streamed completion. Supports Anthropic/OpenAI via env keys and a keyless local Ollama target; live-verified against Ollama. * docs(ai): document the @openclaw/ai package and workspace shrinkwrap boundary * chore(check): include examples/ in duplicate-scan targets * fix: emit normalization package subpaths * fix: complete AI package boundary artifacts * fix: align AI package boundary contracts * fix(ci): stabilize package release contracts * test: align documentation contract checks * test: keep cron docs guard aligned * test: align restored docs contract guards * test: follow upstream docs contracts * docs: drop superseded talk wording
4.3 KiB
| summary | read_when | title | |||
|---|---|---|---|---|---|
| Plain-English and technical explanation of npm shrinkwrap in OpenClaw releases |
|
npm shrinkwrap |
OpenClaw source checkouts use pnpm-lock.yaml. Published OpenClaw npm packages use npm-shrinkwrap.json, npm's publishable dependency lockfile, so package installs use the dependency graph reviewed during release.
Why it matters
Shrinkwrap is a receipt for the dependency tree that ships with an npm package: it tells npm which exact transitive versions to install.
| File | Where it matters | What it means |
|---|---|---|
pnpm-lock.yaml |
OpenClaw source checkout | Maintainer dependency graph |
npm-shrinkwrap.json |
Published npm package | npm install graph for users |
package-lock.json |
Local npm apps | Not the OpenClaw publish contract |
For OpenClaw releases this means:
- the published package does not ask npm to invent a fresh dependency graph at install time;
- dependency changes are reviewable because they land in a lockfile diff;
- release validation tests the same graph users will install;
- package-size or native-dependency surprises surface before publishing.
Shrinkwrap is not a sandbox. It does not make a dependency safe by itself, and it does not replace host isolation, openclaw security audit, package provenance, or install smoke tests.
OpenClaw is a gateway, plugin host, model router, and agent runtime, so a default install affects startup time, disk use, native package downloads, and supply-chain exposure. Shrinkwrap gives release review a stable boundary: reviewers see transitive dependency movement, validators reject unexpected lockfile drift, and plugin packages carry their own locked dependency graph instead of relying on the root package.
Generating and checking
The root openclaw npm package, OpenClaw-owned npm plugin packages (for example @openclaw/discord), and publishable workspace packages such as @openclaw/ai include npm-shrinkwrap.json when they publish. Workspace dependencies are omitted from the root shrinkwrap because they publish beside the root package; each publishable workspace package pins its own transitive tree instead. Suitable plugin packages can also publish with explicit bundledDependencies, carrying their runtime dependency files in the plugin tarball instead of relying only on install-time resolution.
# All shrinkwrap-managed packages (root + publishable plugins)
pnpm deps:shrinkwrap:generate
pnpm deps:shrinkwrap:check
# Root package only
pnpm deps:shrinkwrap:root:generate
pnpm deps:shrinkwrap:root:check
# Only packages affected by the current changeset
pnpm deps:shrinkwrap:changed:generate
pnpm deps:shrinkwrap:changed:check
The generator resolves npm's publishable lock format but rejects generated package versions that are not already present in pnpm-lock.yaml. That keeps the pnpm dependency age, override, and patch-review boundary intact.
Review these as security-sensitive:
pnpm-lock.yamlnpm-shrinkwrap.json- bundled plugin dependency payloads
- any
package-lock.jsondiff
OpenClaw package validators require shrinkwrap in new root package tarballs and reject package-lock.json for published packages. The plugin npm publish path checks plugin-local shrinkwrap, installs package-local bundled dependencies, then packs or publishes.
Inspecting a published package
Root package:
npm pack openclaw@<version> --json --pack-destination /tmp/openclaw-pack
tar -tf /tmp/openclaw-pack/openclaw-<version>.tgz | grep '^package/npm-shrinkwrap.json$'
Plugin package:
npm pack @openclaw/discord@<version> --json --pack-destination /tmp/openclaw-plugin-pack
tar -tf /tmp/openclaw-plugin-pack/openclaw-discord-<version>.tgz | grep '^package/npm-shrinkwrap.json$'
tar -tf /tmp/openclaw-plugin-pack/openclaw-discord-<version>.tgz | grep '^package/node_modules/'
Background: npm-shrinkwrap.json.