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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 26 additions & 26 deletions .agents/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
name: release
description: Prepare and publish the @onkernel/loop npm release from kernel/cua. Use when checking release readiness, choosing a package version, writing the package changelog, committing release metadata to main, pushing package-prefixed tags, or monitoring the release workflow.
description: Prepare and publish the @onkernel/browser-loop npm release from kernel/browser-loop. Use when checking release readiness, choosing a package version, writing the package changelog, committing release metadata to main, pushing package-prefixed tags, or monitoring the release workflow.
---

# Release

Use this workflow to release `@onkernel/loop`.
Use this workflow to release `@onkernel/browser-loop`.

The first publish under the `@onkernel/loop` name is manual, because npm binds a
The first publish under the `@onkernel/browser-loop` name is manual, because npm binds a
trusted publisher to a (repository, workflow filename) pair and a brand-new
package name has none. See `docs/npm-releases.md`.

Expand All @@ -20,7 +20,7 @@ error-prone.

| package | directory | tag prefix | workflow |
| --- | --- | --- | --- |
| `@onkernel/loop` | `packages/loop` | `loop/v` | `release-loop.yml` |
| `@onkernel/browser-loop` | `packages/browser-loop` | `browser-loop/v` | `release-browser-loop.yml` |
| `@onkernel/ptywright` | `packages/ptywright` | — | not published |

## Quick Start
Expand All @@ -37,14 +37,14 @@ git fetch --tags origin

```bash
git status --short
npm view @onkernel/loop versions --json
test -f .github/workflows/release-loop.yml
npm view @onkernel/browser-loop versions --json
test -f .github/workflows/release-browser-loop.yml
```

3. Find the previous release tag:

```bash
git tag --list "loop/v*" --sort=-v:refname | head -1
git tag --list "browser-loop/v*" --sort=-v:refname | head -1
```

If no tag exists, treat the next release as the current `package.json` version
Expand All @@ -53,8 +53,8 @@ unless npm already has that version.
4. Inspect changes since the last tag:

```bash
git log --oneline <last-tag>..HEAD -- packages/loop package.json package-lock.json tsconfig.base.json
git diff --name-status <last-tag>..HEAD -- packages/loop package.json package-lock.json tsconfig.base.json
git log --oneline <last-tag>..HEAD -- packages/browser-loop package.json package-lock.json tsconfig.base.json
git diff --name-status <last-tag>..HEAD -- packages/browser-loop package.json package-lock.json tsconfig.base.json
```

## Version Choice
Expand All @@ -71,11 +71,11 @@ tag:
minor bump for breaking changes unless it is intentionally moving to `1.0.0`.

The candidate version must be greater than both the last tag and every version
returned by `npm view @onkernel/loop versions --json`.
returned by `npm view @onkernel/browser-loop versions --json`.

## Changelog

Changes land under a `## Unreleased` heading in `packages/loop/CHANGELOG.md` as
Changes land under a `## Unreleased` heading in `packages/browser-loop/CHANGELOG.md` as
they merge, so the changelog has at most one unreleased section.

Releasing renames that heading in place — do not add a second top entry:
Expand Down Expand Up @@ -105,7 +105,7 @@ per-merge heading claims a release that never happened.
Set the version explicitly:

```bash
npm pkg set version=<version> --workspace @onkernel/loop
npm pkg set version=<version> --workspace @onkernel/browser-loop
```

Refresh the lockfile:
Expand All @@ -118,16 +118,16 @@ npm install --package-lock-only

```bash
npm ci
npm run build --workspace @onkernel/loop
npm run build --workspace @onkernel/browser-loop
npm run typecheck
npm test --workspace @onkernel/loop
npm pack --workspace @onkernel/loop --dry-run
npm test --workspace @onkernel/browser-loop
npm pack --workspace @onkernel/browser-loop --dry-run
```

Build before testing: the pi print/RPC test loads the extension the way pi does,
through the package's own entry points. Run the full unit suite — do not pass
individual test files. Integration tests run separately (`npm run
test:integration --workspace @onkernel/loop`), and live e2e tests skip unless
test:integration --workspace @onkernel/browser-loop`), and live e2e tests skip unless
`LOOP_E2E_LIVE=1` is set.

Confirm the packed tarball ships `dist` and the extension source pi loads
Expand All @@ -142,8 +142,8 @@ limited to the package version, changelog, and `package-lock.json`.

```bash
git status --short
git add package-lock.json packages/loop/package.json packages/loop/CHANGELOG.md
git commit -m "Release @onkernel/loop v<version>"
git add package-lock.json packages/browser-loop/package.json packages/browser-loop/CHANGELOG.md
git commit -m "Release @onkernel/browser-loop v<version>"
git push origin main
```

Expand All @@ -152,24 +152,24 @@ git push origin main
After the release commit is on `main`, create an annotated tag at that commit:

```bash
git tag -a loop/v<version> -m "@onkernel/loop v<version>"
git push origin loop/v<version>
git tag -a browser-loop/v<version> -m "@onkernel/browser-loop v<version>"
git push origin browser-loop/v<version>
```

## Monitor

Find and watch the workflow run triggered by the tag:

```bash
gh run list --workflow release-loop.yml --json databaseId,status,conclusion,headBranch,displayTitle,url --limit 10
gh run list --workflow release-browser-loop.yml --json databaseId,status,conclusion,headBranch,displayTitle,url --limit 10
gh run watch <run-id> --exit-status
```

After the workflow succeeds, verify npm:

```bash
npm view @onkernel/loop@<version> version
npm dist-tag ls @onkernel/loop
npm view @onkernel/browser-loop@<version> version
npm dist-tag ls @onkernel/browser-loop
```

Then verify the published artifact actually imports — `npm view` only proves
Expand All @@ -178,9 +178,9 @@ the version exists, not that the tarball is loadable:
```bash
cd "$(mktemp -d)"
npm init -y
npm install @onkernel/loop@<version>
node --input-type=module -e "import('@onkernel/loop').then((m) => { if (typeof m.compileLoopToolCatalog !== 'function') process.exit(1); })"
node --input-type=module -e "import('@onkernel/loop/pi').then((m) => { if (typeof m.attach !== 'function') process.exit(1); })"
npm install @onkernel/browser-loop@<version>
node --input-type=module -e "import('@onkernel/browser-loop').then((m) => { if (typeof m.compileLoopToolCatalog !== 'function') process.exit(1); })"
node --input-type=module -e "import('@onkernel/browser-loop/pi').then((m) => { if (typeof m.attach !== 'function') process.exit(1); })"
```

If the workflow fails after a tag is pushed, do not reuse the same version
Expand Down
10 changes: 5 additions & 5 deletions .agents/skills/update-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ For every doc update:

Start with these source-of-truth checks:

- Package topology: `package.json`, `tsconfig.json`, and `packages/*/package.json`. `@onkernel/loop` is one package with two entry points (`.` and `./pi`) and a `pi.extensions` field.
- Design invariants: `packages/loop/src/core` is the framework-neutral core (canonical actions, tool declarations, catalog compilation, tool menu, tool manager, Kernel-browser execution); `packages/loop/src/pi` owns provider-specific policy (model resolution, transport derivation, adapters, payload transforms, headers, retry); provider differences reach execution as compiled `LoopToolCatalog` data rather than provider conditionals in the translator.
- Neutral core: `packages/loop/src/index.ts`, `core/tools.ts`, `core/actions/`, `core/tool-catalog.ts`, `core/menu.ts`, `core/tool-manager.ts`, `core/resources.ts`, and `core/translator/`.
- pi binding: `packages/loop/src/pi/index.ts`, `pi/attach.ts`, `pi/models.ts` (`getLoopModel`/`listLoopModels`/`parseLoopModelRef`), `pi/providers/`, `pi/provider-retry.ts`, and `pi/api-keys.ts`.
- Extension runtime flow: `packages/loop/src/pi-extension/index.ts`, `selection.ts`, `browser-runtime.ts`, `state.ts`, and `render.ts`.
- Package topology: `package.json`, `tsconfig.json`, and `packages/*/package.json`. `@onkernel/browser-loop` is one package with two entry points (`.` and `./pi`) and a `pi.extensions` field.
- Design invariants: `packages/browser-loop/src/core` is the framework-neutral core (canonical actions, tool declarations, catalog compilation, tool menu, tool manager, Kernel-browser execution); `packages/browser-loop/src/pi` owns provider-specific policy (model resolution, transport derivation, adapters, payload transforms, headers, retry); provider differences reach execution as compiled `LoopToolCatalog` data rather than provider conditionals in the translator.
- Neutral core: `packages/browser-loop/src/index.ts`, `core/tools.ts`, `core/actions/`, `core/tool-catalog.ts`, `core/menu.ts`, `core/tool-manager.ts`, `core/resources.ts`, and `core/translator/`.
- pi binding: `packages/browser-loop/src/pi/index.ts`, `pi/attach.ts`, `pi/models.ts` (`getLoopModel`/`listLoopModels`/`parseLoopModelRef`), `pi/providers/`, `pi/provider-retry.ts`, and `pi/api-keys.ts`.
- Extension runtime flow: `packages/browser-loop/src/pi-extension/index.ts`, `selection.ts`, `browser-runtime.ts`, `state.ts`, and `render.ts`.
- TUI test infrastructure: `packages/ptywright/package.json`, `src/index.ts`, `src/session.ts`, `src/terminal.ts`, and `README.md`.
- External drift: provider computer-use docs, `@earendil-works/pi-*` versions, and `@onkernel/sdk` versions in package manifests.

Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ jobs:
- run: npm ci
# The pi print/RPC test loads the extension the way pi does, through the
# package's own entry points, so dist has to exist before the unit run.
- run: npm run build --workspace @onkernel/loop
- run: npm run build --workspace @onkernel/browser-loop
- name: Unit tests
run: npm test --workspace @onkernel/loop
run: npm test --workspace @onkernel/browser-loop
- name: Pack tarball
run: npm pack --workspace @onkernel/loop --pack-destination "$RUNNER_TEMP"
run: npm pack --workspace @onkernel/browser-loop --pack-destination "$RUNNER_TEMP"
- name: ESM import smoke test
run: |
mkdir -p "$RUNNER_TEMP/esm-smoke"
cd "$RUNNER_TEMP/esm-smoke"
npm init -y
npm install "$RUNNER_TEMP"/onkernel-loop-*.tgz
node --input-type=module -e "import('@onkernel/loop').then((m) => { if (typeof m.compileLoopToolCatalog !== 'function') process.exit(1); })"
node --input-type=module -e "import('@onkernel/loop/pi').then((m) => { if (typeof m.attach !== 'function') process.exit(1); })"
npm install "$RUNNER_TEMP"/onkernel-browser-loop-*.tgz
node --input-type=module -e "import('@onkernel/browser-loop').then((m) => { if (typeof m.compileLoopToolCatalog !== 'function') process.exit(1); })"
node --input-type=module -e "import('@onkernel/browser-loop/pi').then((m) => { if (typeof m.attach !== 'function') process.exit(1); })"

typecheck-and-ptywright:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -100,7 +100,7 @@ jobs:
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
run: npm run test:integration --workspace @onkernel/loop
run: npm run test:integration --workspace @onkernel/browser-loop

live-e2e:
runs-on: ubuntu-latest
Expand All @@ -124,4 +124,4 @@ jobs:
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
run: npm run test:integration --workspace @onkernel/loop -- test/e2e.live.test.ts
run: npm run test:integration --workspace @onkernel/browser-loop -- test/e2e.live.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: Release Loop
name: Release Browser Loop

on:
push:
tags:
- "loop/v*"
- "browser-loop/v*"

permissions:
contents: read
id-token: write

concurrency:
group: release-loop-${{ github.ref_name }}
group: release-browser-loop-${{ github.ref_name }}
cancel-in-progress: false

jobs:
Expand Down Expand Up @@ -43,38 +43,38 @@ jobs:
import { readFileSync } from "node:fs";

const tag = process.env.GITHUB_REF_NAME;
const prefix = "loop/v";
const prefix = "browser-loop/v";
if (!tag?.startsWith(prefix)) {
throw new Error(`Expected tag to start with ${prefix}, got ${tag}`);
}

const tagVersion = tag.slice(prefix.length);
const pkg = JSON.parse(readFileSync("packages/loop/package.json", "utf8"));
const pkg = JSON.parse(readFileSync("packages/browser-loop/package.json", "utf8"));
if (pkg.version !== tagVersion) {
throw new Error(`Tag version ${tagVersion} does not match ${pkg.name} package.json version ${pkg.version}`);
}

console.log(`${pkg.name}@${pkg.version}`);
NODE

- run: npm run build --workspace @onkernel/loop
- run: npm run typecheck --workspace @onkernel/loop
- run: npm run build --workspace @onkernel/browser-loop
- run: npm run typecheck --workspace @onkernel/browser-loop

- name: Unit tests
run: npm test --workspace @onkernel/loop
run: npm test --workspace @onkernel/browser-loop

- name: Pack tarball
run: npm pack --workspace @onkernel/loop --pack-destination "$RUNNER_TEMP"
run: npm pack --workspace @onkernel/browser-loop --pack-destination "$RUNNER_TEMP"

- name: ESM import smoke test
run: |
SMOKE_DIR=$(mktemp -d)
cd "$SMOKE_DIR"
npm init -y > /dev/null
npm install "$RUNNER_TEMP"/onkernel-loop-*.tgz
npm install "$RUNNER_TEMP"/onkernel-browser-loop-*.tgz
cat > smoke.mjs <<'NODE'
import { compileLoopToolCatalog, formatBrowserActResult, loop } from "@onkernel/loop";
import * as loopPi from "@onkernel/loop/pi";
import { compileLoopToolCatalog, formatBrowserActResult, loop } from "@onkernel/browser-loop";
import * as loopPi from "@onkernel/browser-loop/pi";

const { attach, createLoopModels, getLoopModel } = loopPi;
for (const [name, value] of Object.entries({ compileLoopToolCatalog, formatBrowserActResult, attach, createLoopModels, getLoopModel })) {
Expand All @@ -86,11 +86,11 @@ jobs:
throw new Error(`expected loop to be an object, got ${typeof loop}`);
}
if ("Agent" in loopPi || "NodeExecutionEnv" in loopPi) {
throw new Error("expected @onkernel/loop/pi not to re-export pi-agent-core");
throw new Error("expected @onkernel/browser-loop/pi not to re-export pi-agent-core");
}
console.log("ESM import smoke OK");
NODE
node smoke.mjs

- name: Publish to npm
run: npm publish --workspace @onkernel/loop --access public
run: npm publish --workspace @onkernel/browser-loop --access public
Loading
Loading