Files
tutti/docs/conventions/npm-package-release.md
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

21 KiB

npm Package Release

This document defines the durable release conventions for public npm packages in this repository.

Scope

The npm package release flow covers public packages intended for consumption outside this repository.

All public npm packages in this repository publish under the npm organization scope @tutti-os. Do not introduce new public packages under a different scope, personal scopes, or unscoped names unless this document and the release automation are updated together.

The current fixed release group is:

@tutti-os/analytics
@tutti-os/analytics-debug
@tutti-os/event-stream-core
@tutti-os/connector-contracts
@tutti-os/connector-renderer
@tutti-os/workspace-file-manager
@tutti-os/workspace-file-reference
@tutti-os/workspace-issue-manager
@tutti-os/workspace-user-project
@tutti-os/workspace-app-center
@tutti-os/workspace-external-core
@tutti-os/workspace-terminal
@tutti-os/agent-activity-core
@tutti-os/agent-gui
@tutti-os/agent-session-replay
@tutti-os/agent-session-replay-runner
@tutti-os/commerce
@tutti-os/claude-sdk-sidecar
@tutti-os/browser-node
@tutti-os/workspace-file-preview
@tutti-os/workbench-snapshot
@tutti-os/workbench-electron
@tutti-os/workbench-host
@tutti-os/workbench-launchpad
@tutti-os/workbench-surface
@tutti-os/app-release-tools
@tutti-os/auth-bridge
@tutti-os/desktop-update-admission
@tutti-os/ui-i18n-runtime
@tutti-os/ui-notifications
@tutti-os/ui-rich-text
@tutti-os/ui-react-hooks
@tutti-os/ui-system

These packages use one shared version number and are released together. Do not introduce independent package versions until there is a clear compatibility need and a migration plan.

This document covers public npm packages only. Shared non-npm modules such as packages/workspace/files follow their owning language and module conventions and are not part of the npm release flow unless a separate release contract is defined for them.

This document is the durable source of truth for which workspace packages participate in the npm release flow. Do not duplicate the participating package roster in architecture docs.

This document does not cover the desktop app release flow. Desktop releases are defined in Desktop Release.

Stable Releases

Stable package releases use one manual GitHub Actions workflow dispatch on main.

Each stable run resolves the next shared package version from the latest existing stable package tag. Stable versions are limited to 0.0.x during this iteration:

  • if there is no prior stable package tag, the workflow publishes 0.0.1
  • if the latest stable package tag is packages-v0.0.7, the next run publishes 0.0.8
  • if any existing packages-v* tag is outside the 0.0.x line, the workflow must fail rather than guessing

The workflow applies the resolved version to the fixed release group inside the CI checkout, validates tarballs, and prepares a release-only commit. In that commit, internal Go module requirements use the resolved shared version and workspace-relative replacements are removed. The workflow publishes the npm packages with the latest dist-tag, then points the matching npm release tag and every packages/**/go.mod module tag at that release commit. The commit is reachable through those tags only and is not pushed to main. Stable releases do not open version PRs and do not require Changeset files.

Package publication uses bounded concurrency so registry and provenance round-trips do not serialize the full fixed release group. The workflow pins TUTTI_NPM_PUBLISH_CONCURRENCY=4; release tooling accepts an integer from 1 through 8 for controlled CI tuning and rejects unbounded or invalid values. Each worker checks only the exact immutable package version before publishing, preserves the bounded provenance retry, and stops admitting new packages after the first terminal failure. Release and Go module tags are created and pushed only after every package publish has completed successfully.

Because the stable release version is applied only in CI, repository manifests on main may lag behind the latest published 0.0.x version. The durable release history is the sequence of packages-v0.0.x git tags plus the published npm versions.

When the participating package roster changes, keep these release surfaces aligned with this document:

  • /.changeset/config.json
  • package.json package release scripts
  • tools/scripts/apply-ci-package-release-version.mjs
  • tools/scripts/check-package-packs.mjs
  • tools/scripts/next-package-release-version.mjs
  • tools/scripts/release-beta.mjs
  • tools/scripts/publish-packages.mjs

The npm package release workflow should be triggered manually only after the target package changes have already landed on main.

Stable package tags use this shape:

packages-v0.0.1

Do not reuse desktop release tags such as tutti-desktop-v* for npm packages.

Go module tags for modules under packages/ are created during the same stable release and use the Go submodule tag shape:

packages/workbench/service/v0.0.1
packages/appcli/core/v0.0.1
packages/workspace/files/v0.0.1
packages/workspace/issues/v0.0.1

Adding a new packages/**/go.mod opts that module into this shared stable package release tag sequence. Do not add package Go modules that require an independent release cadence unless this convention and the release automation are updated together.

packages/device-link participates in the same stable Go module tag sequence after the Personal Android/Desktop path validated its authenticated connection lifecycle and AAR consumer build. TSH and other consumers must install its released cohort version; do not consume a pseudo-version or add a workspace replacement.

Local Beta Releases

Use local beta releases for temporary cross-repository or external integration debugging.

The local command is:

pnpm release:beta

The beta command publishes the fixed package group with the beta dist-tag and a unique prerelease version such as:

0.1.1-beta-20260521143012

The command must not publish to latest, create git tags, or commit files. It must restore temporary package manifest changes before it exits.

Install beta packages explicitly:

pnpm add @tutti-os/browser-node@beta
pnpm add @tutti-os/agent-session-replay-runner@beta
pnpm add @tutti-os/workspace-file-reference@beta
pnpm add @tutti-os/workspace-file-preview@beta
pnpm add @tutti-os/workspace-file-manager@beta
pnpm add @tutti-os/workspace-issue-manager@beta
pnpm add @tutti-os/connector-renderer@beta
pnpm add @tutti-os/connector-contracts@beta
pnpm add @tutti-os/workspace-app-center@beta
pnpm add @tutti-os/workspace-terminal@beta
pnpm add @tutti-os/workbench-electron@beta
pnpm add @tutti-os/workbench-host@beta
pnpm add @tutti-os/workbench-surface@beta
pnpm add @tutti-os/workbench-snapshot@beta
pnpm add @tutti-os/desktop-update-admission@beta
pnpm add @tutti-os/ui-i18n-runtime@beta
pnpm add @tutti-os/ui-notifications@beta
pnpm add @tutti-os/ui-rich-text@beta
pnpm add @tutti-os/ui-react-hooks@beta
pnpm add @tutti-os/ui-system@beta

Do not add a second stable release path that bypasses the shared package roster or the stable tag sequence.

Package Shape

Published packages build into dist and expose built artifacts through package exports. Do not expose src/* as public npm API.

Inside the workspace, package manifests may point exports and types at src so local development does not require a package build before every typecheck or desktop dev run. Use publishConfig to override those fields to dist when pnpm packs or publishes the package.

Published packages should include:

  • dist
  • README.md
  • package.json

Published packages should not include:

  • src
  • package-local tsconfig.json
  • package-local build config
  • generated junk files

Packages that expose Tiptap runtime objects or types must declare their shared Tiptap runtime packages as peer dependencies, with the same ranges repeated as development dependencies for workspace builds. Tiptap packages require exact peer versions, so component-internal extensions must be bundled from development dependencies rather than resolved independently in the consumer. A package-private copy of the shared runtime can make structurally identical extensions type-incompatible in consumers.

Platform-specific peers used only by an optional public subpath must remain peer dependencies and set peerDependenciesMeta.<name>.optional to true. The platform app that imports that subpath must declare the concrete runtime dependency itself. This keeps web and desktop consumers from installing unused native dependency chains.

Runtime assets that are rendered or referenced by public entrypoints must also survive the packed package shape. When a public runtime entrypoint such as ./workbench or ./ui renders a package-local image, icon bitmap, schema, or similar asset, keep the main runtime entrypoint asset-free whenever possible. Prefer an explicit public asset subpath such as ./assets/workspace-dock-website.png, let the business consumer import that asset only when it actually uses the default visual, and ship the asset file through an explicit package build rule.

Use this command to inspect publish contents:

pnpm release:pack:check

Without arguments the command builds and inspects every configured public npm package. Repository-managed changed-file gates may pass a validated package subset:

pnpm release:pack:check -- --packages-json '["@tutti-os/agent-gui"]'

The subset build includes the selected packages' workspace dependencies, but only the selected package tarballs are inspected. Use the unfiltered command for release infrastructure, workspace dependency, lockfile, Changesets, or fixed release-group changes.

Before publishing a package that exposes runtime assets, verify all of the following:

  • pnpm release:pack:check shows the packed output that consumers will install
  • the packed tarball includes every exported asset subpath such as ./dist/assets/workspace-dock-website.png
  • the main runtime entrypoint no longer hard-depends on a package-local image unless that dependency is an intentional part of the public contract
  • a consumer-facing build emits or copies the asset only when the consumer explicitly imports the asset subpath
  • packed JavaScript does not contain raw XML SVG data URLs or bare relative SVG strings; SVGs interpolated into runtime CSS url(...) values must use a CSS-safe data URL or an absolute URL constructed relative to the module
  • every asset referenced through new URL(relativePath, import.meta.url) exists at that exact module-relative location inside the packed tarball
  • published code that may be dependency-prebundled must import fallback images through explicit public asset subpaths; do not leave new URL("./assets/...", import.meta.url) in a bundled runtime entrypoint, because a consumer optimizer may relocate the JavaScript without relocating the adjacent asset

Emitting an SVG next to a bundled JavaScript file is not sufficient when the bundle only exports a string such as ./icon-HASH.svg. CSS resolves that value relative to the consuming page, and consumer bundlers cannot discover the runtime string as an asset dependency.

Do not use a workspace application build as the only validation for asset changes. Workspace exports can point at source while published exports point at dist, so the two paths may apply different asset transforms. The packed tarball is the consumer contract.

Package Entrypoints

The stable package entrypoints are:

@tutti-os/agent-activity-core
@tutti-os/agent-session-replay-runner
@tutti-os/agent-session-replay-runner/activity-contract.json
@tutti-os/agent-session-replay-runner/cassette-policy.json
@tutti-os/agent-session-replay-runner/cassette
@tutti-os/agent-session-replay-runner/cassette-policy
@tutti-os/agent-session-replay-runner/checkpoint-plan
@tutti-os/agent-session-replay-runner/evidence-helpers
@tutti-os/agent-session-replay-runner/managed-log-prefixes
@tutti-os/agent-session-replay-runner/managed-shutdown
@tutti-os/agent-session-replay-runner/playback-controller
@tutti-os/agent-session-replay-runner/playback-helpers
@tutti-os/agent-session-replay-runner/product-ports
@tutti-os/agent-session-replay-runner/recording
@tutti-os/agent-session-replay-runner/replay-stimuli
@tutti-os/agent-session-replay-runner/serial-queue
@tutti-os/agent-session-replay-runner/stimulus
@tutti-os/agent-session-replay-runner/turn-freshness
@tutti-os/agent-session-replay-runner/turn-identity-tracker
@tutti-os/agent-session-replay-runner/wait-diagnostics
@tutti-os/analytics
@tutti-os/analytics-debug
@tutti-os/analytics-debug/react
@tutti-os/agent-gui
@tutti-os/agent-gui/agent-conversation
@tutti-os/agent-gui/agent-env
@tutti-os/agent-gui/agent-env-ui
@tutti-os/agent-gui/agent-message-center
@tutti-os/agent-gui/context-mention-palette
@tutti-os/agent-gui/i18n
@tutti-os/agent-gui/quick-composer
@tutti-os/agent-gui/styles.css
@tutti-os/agent-gui/workbench
@tutti-os/agent-gui/workbench/browser-element-context
@tutti-os/agent-gui/workbench/tool-sidebar
@tutti-os/agent-gui/workspace-settings-panel
@tutti-os/commerce
@tutti-os/commerce/react
@tutti-os/commerce/assets/star-free.png
@tutti-os/commerce/assets/star-lite.png
@tutti-os/commerce/assets/star-pro.png
@tutti-os/commerce/assets/star-ultra.png
@tutti-os/commerce/assets/registration-credits-bg.png
@tutti-os/browser-node
@tutti-os/browser-node/assets/workspace-dock-website.png
@tutti-os/browser-node/bridge
@tutti-os/browser-node/chrome-cookie-import/macos
@tutti-os/browser-node/electron-main
@tutti-os/browser-node/electron-preload
@tutti-os/browser-node/electron-renderer
@tutti-os/browser-node/i18n
@tutti-os/browser-node/react
@tutti-os/browser-node/workbench
@tutti-os/ui-i18n-runtime
@tutti-os/ui-notifications
@tutti-os/ui-rich-text
@tutti-os/ui-rich-text/core
@tutti-os/ui-rich-text/editor
@tutti-os/ui-rich-text/plugins
@tutti-os/ui-rich-text/types
@tutti-os/ui-react-hooks
@tutti-os/connector-contracts/authorization/v1
@tutti-os/connector-contracts/openapi/connector-market.v1.yaml
@tutti-os/connector-renderer/application
@tutti-os/connector-renderer/i18n
@tutti-os/connector-renderer/ui
@tutti-os/workspace-app-center
@tutti-os/workspace-app-center/contracts
@tutti-os/workspace-app-center/core
@tutti-os/workspace-app-center/i18n
@tutti-os/workspace-app-center/ui
@tutti-os/workspace-file-preview
@tutti-os/workspace-file-preview/core
@tutti-os/workspace-file-preview/react
@tutti-os/workspace-file-manager
@tutti-os/workspace-file-manager/assets/workspace-archive-fallback.png
@tutti-os/workspace-file-manager/assets/workspace-folder-fallback.png
@tutti-os/workspace-file-manager/services
@tutti-os/workspace-file-reference
@tutti-os/workspace-file-reference/contracts
@tutti-os/workspace-file-reference/core
@tutti-os/workspace-file-reference/react
@tutti-os/workspace-file-reference/ui
@tutti-os/workspace-issue-manager
@tutti-os/workspace-issue-manager/assets/workspace-dock-task.png
@tutti-os/workspace-issue-manager/contracts
@tutti-os/workspace-issue-manager/core
@tutti-os/workspace-issue-manager/i18n
@tutti-os/workspace-issue-manager/openapi/issue-manager.v1.yaml
@tutti-os/workspace-issue-manager/services
@tutti-os/workspace-issue-manager/ui
@tutti-os/workspace-issue-manager/workbench
@tutti-os/workspace-user-project
@tutti-os/workspace-user-project/contracts
@tutti-os/workspace-user-project/core
@tutti-os/workspace-user-project/i18n
@tutti-os/workspace-user-project/ui
@tutti-os/workspace-terminal
@tutti-os/workspace-terminal/contracts
@tutti-os/workspace-terminal/i18n
@tutti-os/workspace-terminal/react
@tutti-os/workspace-terminal/styles.css
@tutti-os/workspace-terminal/workbench
@tutti-os/workbench-snapshot
@tutti-os/workbench-snapshot/schema.json
@tutti-os/workbench-electron
@tutti-os/workbench-host
@tutti-os/workbench-host/conformance
@tutti-os/workbench-surface
@tutti-os/workbench-surface/i18n
@tutti-os/workbench-surface/styles.css
@tutti-os/ui-system
@tutti-os/ui-system/components
@tutti-os/ui-system/dev-vite
@tutti-os/ui-system/icons
@tutti-os/ui-system/metadata
@tutti-os/ui-system/styles.css
@tutti-os/ui-system/utils

Connector packages intentionally expose no root barrel. Import wire protocols from @tutti-os/connector-contracts/authorization/v1, frontend services from @tutti-os/connector-renderer/application, and React components from @tutti-os/connector-renderer/ui.

New public subpaths should be added only when a real consumer needs them.

@tutti-os/ui-system/dev-vite is a development-tooling subpath for external Vite consumers. It is part of the package release surface so external projects can configure local source sync without manual linking, but it must not be imported from application runtime code.

@tutti-os/ui-system/metadata is a tooling metadata subpath used by the storyboard, dev server, and agent skills. It should describe stable public exports rather than exposing private source layout as the consumer API.

Review Guidance

When reviewing package extraction or release-surface changes, keep these rules separate:

  • packages/* still exists for real shared seams rather than for code that only looks reusable.
  • a public npm package is an explicit external contract and may be valid even before there are two in-repository consumers
  • when a package does not yet have a second in-repository host, the PR should make the intended external contract and release ownership explicit rather than treating publishability as implied
  • shared non-npm modules such as packages/workspace/files should not be counted as public npm packages unless they are added to this document and the release configuration

CSS Boundary

@tutti-os/ui-system owns shared tokens, theme styles, and primitive styles. Other reusable packages should consume UI system primitives and token-backed Tailwind utilities before introducing package-local CSS.

Reusable packages should not publish their own stylesheet entrypoint by default. Prefer React components and Tailwind utilities that resolve through the consumer's Tailwind build and the shared UI system token layer.

The current stylesheet entrypoint exceptions are explicit package contracts:

  • @tutti-os/agent-gui/styles.css for the carried agent GUI, session chrome, session transcript, and workspace-agent status panel selectors that remain package-owned during the migration
  • @tutti-os/workbench-surface/styles.css for surface layout, window frames, resize handles, snap previews, and dock/chrome positioning
  • @tutti-os/workspace-terminal/styles.css for the terminal node structural shell, close guard, and xterm host container selectors

Tailwind consumers should include reusable packages in their source scan when they consume packages that render Tailwind utility classes. For example:

@source "../node_modules/@tutti-os/workbench-surface/dist";

For monorepo packages that render runtime Tailwind utility classes, declare the source root in package.json so repository checks can enforce consumer setup:

{
  "tutti": {
    "tailwindSourceRoot": "src"
  }
}

Desktop renderer consumers should then add the matching package source path to their Tailwind entrypoint. pnpm check:ui-boundaries validates this for workspace packages imported by apps/desktop, including mismatched @source paths that point at the wrong directory.

Package-local CSS should be rare. Add it only when a reusable package needs selectors, keyframes, or structure that is awkward to express through primitives and Tailwind utilities. When package-local CSS is necessary, it must use UI system tokens and must not contain app-specific product styling or user-visible copy.

Published stylesheet sources use standard CSS properties. In particular, author backdrop-filter without a handwritten -webkit-backdrop-filter pair. The consuming application's CSS build owns browser-target prefix generation; the packed stylesheet does not promise a second, package-owned legacy-browser prefixing pass. Consumer production builds should assert that optimization did not leave prefix-only declarations.

Do not add a public ./styles.css export for a reusable package unless that CSS is genuinely part of the package's stable contract. If the same need would help multiple packages, add the token, primitive, or utility support to @tutti-os/ui-system instead.

Access And Credentials

All scoped public packages must set public access through package publishConfig.access.

Stable publishing should prefer npm trusted publishing through GitHub Actions OIDC. If a token fallback is needed, use only a GitHub Actions secret and keep it out of local scripts.

When the workflow uses an NPM_TOKEN fallback, that token must belong to an npm account with publish access to the @tutti-os organization scope.

Local beta publishing uses the developer's local npm login.

Validation

For changes that affect published package surfaces, run:

pnpm lint:ts
pnpm typecheck
pnpm check:ui-boundaries
pnpm release:pack:check

For shared UI export or CSS boundary changes, keep pnpm check:ui-boundaries aligned with package exports and allowed style locations.