Skip to content
Closed
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
34 changes: 26 additions & 8 deletions .agents/skills/tui-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,14 +469,20 @@ Proto types come from `openshell-core` which generates them from `OUT_DIR` via `

```rust
use openshell_core::proto::openshell_client::OpenShellClient;
use openshell_core::proto::{ListSandboxesRequest, GetSandboxLogsRequest, ...};
use openshell_core::proto::{
all_workspaces_selector, workspace_selector, GetSandboxLogsRequest,
ListSandboxesRequest, ...
};
```

### Proto field gotchas

- `DeleteSandboxRequest` uses the `name` field (not `id`):
```rust
let req = openshell_core::proto::DeleteSandboxRequest { name: sandbox_name };
let req = openshell_core::proto::DeleteSandboxRequest {
name: sandbox_name,
workspace_scope: Some(workspace_selector(workspace)),
};
```
- `WatchSandboxRequest` has extra fields beyond what you might need — always use `..Default::default()`:
```rust
Expand All @@ -490,12 +496,24 @@ use openshell_core::proto::{ListSandboxesRequest, GetSandboxLogsRequest, ...};
};
```
- `SandboxLogLine` proto fields: `sandbox_id`, `timestamp_ms`, `level`, `target`, `message`, `source`, `fields` (HashMap<String, String>).
- `GetSandboxLogsRequest` fields: `sandbox_id`, `lines` (u32), `since_ms` (i64), `sources` (Vec<String>), `min_level` (String), `workspace` (String).
- `ListSandboxesRequest` fields: `limit` (i64), `offset` (i64), `label_selector` (String), `workspace` (String), `all_workspaces` (bool).
- `ListProvidersRequest` fields: `limit` (i64), `offset` (i64), `workspace` (String), `all_workspaces` (bool).
- `ListWorkspacesRequest` fields: `limit` (i64), `offset` (i64), `label_selector` (String).
- `UpdateConfigRequest` fields: `name` (String, sandbox name or empty for global), `setting_key`, `setting_value`, `delete_setting` (bool), `global` (bool), `workspace`.
- Most resource requests include a `workspace` field that scopes the operation to the current workspace.
- Workspace-scoped request fields use `workspace_scope: Option<WorkspaceSelector>`.
Select one workspace with `Some(workspace_selector(name))`. List requests that
explicitly support cross-workspace access also accept
`Some(all_workspaces_selector())`; do not use that marker on other requests.
- `GetSandboxLogsRequest` fields: `sandbox_id`, `lines` (u32), `since_ms` (i64),
`sources` (Vec<String>), `min_level` (String), `workspace_scope`.
- `ListSandboxesRequest` fields: `limit` (u32), `offset` (u32),
`label_selector` (String), `workspace_scope`.
- `ListProvidersRequest` fields: `limit` (u32), `offset` (u32),
`workspace_scope`.
- `ListWorkspacesRequest` fields: `limit` (u32), `offset` (u32),
`label_selector` (String).
- `UpdateConfigRequest` fields include `name` (String, sandbox name or empty for
global), `setting_key`, `setting_value`, `delete_setting` (bool), `global`
(bool), and `workspace_scope`. Sandbox-scoped updates require a named selector;
gateway-global updates must leave `workspace_scope` as `None`.
- Most resource requests require an explicit named `workspace_scope`, including
the `default` workspace. An omitted selector is not an implicit default.

### gRPC timeouts

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/publish-docs-website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ permissions:
concurrency:
group: docs-website
cancel-in-progress: false
queue: max

defaults:
run:
Expand Down
31 changes: 26 additions & 5 deletions .github/workflows/release-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
outputs:
python_version: ${{ steps.v.outputs.python }}
docs_version: ${{ steps.v.outputs.docs_version }}
cargo_version: ${{ steps.v.outputs.cargo }}
deb_version: ${{ steps.v.outputs.deb }}
rpm_version: ${{ steps.v.outputs.rpm_version }}
Expand All @@ -47,11 +48,15 @@ jobs:
id: v
run: |
set -euo pipefail
echo "python=$(uv run python tasks/scripts/release.py get-version --dev --python)" >> "$GITHUB_OUTPUT"
echo "cargo=$(uv run python tasks/scripts/release.py get-version --dev --cargo)" >> "$GITHUB_OUTPUT"
echo "deb=$(uv run python tasks/scripts/release.py get-version --dev --deb)" >> "$GITHUB_OUTPUT"
echo "rpm_version=$(uv run python tasks/scripts/release.py get-version --dev --rpm-version)" >> "$GITHUB_OUTPUT"
echo "rpm_release=$(uv run python tasks/scripts/release.py get-version --dev --rpm-release)" >> "$GITHUB_OUTPUT"
python_version=$(uv run python tasks/scripts/release.py get-version --dev --python)
{
echo "python=${python_version}"
echo "docs_version=${python_version%%+*}"
echo "cargo=$(uv run python tasks/scripts/release.py get-version --dev --cargo)"
echo "deb=$(uv run python tasks/scripts/release.py get-version --dev --deb)"
echo "rpm_version=$(uv run python tasks/scripts/release.py get-version --dev --rpm-version)"
echo "rpm_release=$(uv run python tasks/scripts/release.py get-version --dev --rpm-release)"
} >> "$GITHUB_OUTPUT"

build-cli:
needs: compute-versions
Expand Down Expand Up @@ -628,6 +633,22 @@ jobs:
release-kind: dev
pin-sha: ${{ github.sha }}

publish-fern-docs:
name: Sync and Publish Fern Docs
needs: [compute-versions, release-dev, release-helm, trigger-wheel-publish]
permissions:
contents: write
uses: ./.github/workflows/sync-docs.yml
with:
operation: sync
channel: dev
source_ref: ${{ github.sha }}
release_version: ${{ needs.compute-versions.outputs.docs_version }}
display_name: Dev
availability: beta
publish: true
secrets: inherit

trigger-wheel-publish:
name: Trigger Wheel Publish
needs: [compute-versions, release-dev]
Expand Down
37 changes: 13 additions & 24 deletions .github/workflows/release-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -664,31 +664,20 @@ jobs:
if-no-files-found: error

publish-fern-docs:
name: Publish Fern Docs
needs: [compute-versions, release]
name: Sync and Publish Fern Docs
needs: [compute-versions, release, publish-sdk-typescript, release-helm, trigger-wheel-publish]
if: needs.compute-versions.outputs.is_prerelease != 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag || github.ref }}

- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"

- name: Install Fern CLI
run: |
FERN_VERSION=$(node -p "require('./fern/fern.config.json').version")
npm install -g "fern-api@${FERN_VERSION}"

- name: Publish Fern docs
env:
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
working-directory: ./fern
run: fern generate --docs
permissions:
contents: write
uses: ./.github/workflows/sync-docs.yml
with:
operation: sync
channel: latest
source_ref: ${{ needs.compute-versions.outputs.source_sha }}
release_version: ${{ needs.compute-versions.outputs.semver }}
display_name: Latest (v${{ needs.compute-versions.outputs.semver }})
publish: true
secrets: inherit

publish-sdk-typescript:
name: Publish TypeScript SDK
Expand Down
99 changes: 95 additions & 4 deletions .github/workflows/sync-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,49 @@
name: Sync Docs Website

on:
workflow_call:
inputs:
operation:
description: "Whether to sync or remove a docs snapshot"
required: true
type: string
channel:
description: "Docs channel to update or remove"
required: true
type: string
source_ref:
description: "Source commit SHA, branch, or tag to snapshot when operation=sync"
required: false
type: string
release_version:
description: "Release version used to order mutable channels"
required: false
type: string
version_slug:
description: "Version slug when channel=version"
required: false
type: string
display_name:
description: "Optional version selector display name"
required: false
type: string
availability:
description: "Optional Fern availability status"
required: false
type: string
publish:
description: "Publish production docs after syncing"
required: false
default: false
type: boolean
allow_rollback:
description: "Allow an explicitly requested mutable-channel rollback"
required: false
default: false
type: boolean
secrets:
FERN_TOKEN:
required: false
workflow_dispatch:
inputs:
operation:
Expand All @@ -21,26 +64,46 @@ on:
options:
- dev
- latest
- stable
- version
source_ref:
description: "Source commit SHA, branch, or tag to snapshot when operation=sync"
required: false
type: string
release_version:
description: "Release version, e.g. 0.1.2 or 0.1.3.dev4"
required: false
type: string
version_slug:
description: "Version slug when channel=version, e.g. v0.0.36"
required: false
type: string
display_name:
description: "Optional version selector display name"
description: "Optional selector name, e.g. Dev"
required: false
type: string
availability:
description: "Optional Fern status: beta, deprecated, ga, or stable"
required: false
type: string
publish:
description: "Publish production docs after syncing"
required: false
default: false
type: boolean
allow_rollback:
description: "Allow an explicitly requested mutable-channel rollback"
required: false
default: false
type: boolean

permissions:
contents: write

concurrency:
group: docs-website
cancel-in-progress: false
queue: max

defaults:
run:
Expand All @@ -65,15 +128,20 @@ jobs:
OPERATION: ${{ inputs.operation }}
CHANNEL: ${{ inputs.channel }}
SOURCE_REF: ${{ inputs.source_ref }}
RELEASE_VERSION: ${{ inputs.release_version }}
VERSION_SLUG: ${{ inputs.version_slug }}
run: |
set -euo pipefail
if [[ "$OPERATION" == "sync" && -z "$SOURCE_REF" ]]; then
echo "source_ref is required when operation=sync" >&2
exit 1
fi
if [[ "$CHANNEL" == "version" && -z "$VERSION_SLUG" ]]; then
echo "version_slug is required when channel=version" >&2
if [[ "$CHANNEL" =~ ^(dev|latest|stable)$ && -z "$RELEASE_VERSION" ]]; then
echo "release_version is required for dev, latest, and stable channels" >&2
exit 1
fi
if [[ "$CHANNEL" =~ ^(stable|version)$ && -z "$VERSION_SLUG" ]]; then
echo "version_slug is required for stable and version channels" >&2
exit 1
fi

Expand All @@ -96,6 +164,7 @@ jobs:
uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
with:
version: "0.10.12"
python-version: "3.13"

- name: Update docs snapshot
# Inputs flow in as quoted env vars to avoid shell injection; see the
Expand All @@ -104,17 +173,32 @@ jobs:
OPERATION: ${{ inputs.operation }}
CHANNEL: ${{ inputs.channel }}
SOURCE_REF: ${{ inputs.source_ref }}
RELEASE_VERSION: ${{ inputs.release_version }}
VERSION_SLUG: ${{ inputs.version_slug }}
DISPLAY_NAME: ${{ inputs.display_name }}
AVAILABILITY: ${{ inputs.availability }}
ALLOW_ROLLBACK: ${{ inputs.allow_rollback }}
run: |
SOURCE_SHA=""
if [[ "$OPERATION" == "sync" ]]; then
SOURCE_SHA=$(git -C source rev-parse HEAD)
fi
rollback_args=()
if [[ "$ALLOW_ROLLBACK" == "true" ]]; then
rollback_args+=(--allow-rollback)
fi
uv run automation/tasks/scripts/sync_docs_website.py \
--operation "$OPERATION" \
--source-root source \
--docs-website-root docs-website \
--channel "$CHANNEL" \
--source-ref "$SOURCE_REF" \
--source-sha "$SOURCE_SHA" \
--release-version "$RELEASE_VERSION" \
--version-slug "$VERSION_SLUG" \
--display-name "$DISPLAY_NAME"
--display-name "$DISPLAY_NAME" \
--availability "$AVAILABILITY" \
"${rollback_args[@]}"

- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
Expand Down Expand Up @@ -160,3 +244,10 @@ jobs:
git commit -m "docs(website): remove ${target} docs"
fi
git push origin HEAD:docs-website

- name: Publish Fern docs
if: ${{ inputs.publish }}
env:
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
working-directory: docs-website/fern
run: fern generate --docs
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,9 @@ When behavior, commands, or development workflows change, review the related age
- When making changes, update the relevant documentation in the `architecture/` directory.
- When changes affect user-facing behavior, update the relevant published docs pages under `docs/` and navigation in `docs/index.yml`.
- When changing gateway TOML fields, driver-specific config options, config defaults, or Helm rendering of `gateway.toml`, update `docs/reference/gateway-config.mdx` in the same branch.
- `fern/` contains the Fern site config, components, preview workflow inputs, and publish settings.
- `fern/` contains the Fern site config, components, preview workflow inputs, publish settings, and publishing documentation in `fern/README.md`.
- Follow the docs style guide in [docs/CONTRIBUTING.mdx](docs/CONTRIBUTING.mdx): active voice, minimal formatting, no filler introductions, `shell` fences for copyable commands, and no duplicate body H1.
- Fern PR previews run through `.github/workflows/branch-docs.yml`, and production publish runs through the `publish-fern-docs` job in `.github/workflows/release-tag.yml` for stable release tags.
- Fern PR previews run through `.github/workflows/branch-docs.yml`. Release Dev publishes `dev`, and Release Tag publishes an immutable stable version plus `latest`. Both production paths call `.github/workflows/sync-docs.yml` once.
- Use the `update-docs-from-commits` skill to scan recent commits and draft doc updates.

### Architecture Docs
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,9 +488,9 @@ mise run docs

PRs that touch `docs/**` or `fern/**` are validated by `.github/workflows/branch-docs.yml`, and they get a preview when `FERN_TOKEN` is available to the workflow.

Fern docs publishing is handled by the `publish-fern-docs` job in `.github/workflows/release-tag.yml` when a stable release tag is created.
Release Dev publishes the `dev` docs version from `main`. Release Tag publishes an immutable stable version and updates `latest`. See [fern/README.md](fern/README.md) for the source layout, version model, and publishing workflows.

`docs/` is the source-of-truth docs tree. `fern/` contains the site config, components, and theme assets that publish those pages.
`docs/` is the source-of-truth docs tree. `fern/` contains the site configuration, components, theme assets, and its README.

See [docs/CONTRIBUTING.mdx](docs/CONTRIBUTING.mdx) for the current docs authoring guide.

Expand Down
8 changes: 1 addition & 7 deletions architecture/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,13 +487,7 @@ settings, exceptions, and the contributor and maintainer workflows.

## Docs Site

Published docs live in `docs/`. Navigation lives in `docs/index.yml`. Fern site
configuration, components, theme assets, and publish settings live in `fern/`.

Use `mise run docs` for strict validation and `mise run docs:serve` for local
preview. PR previews are produced by `.github/workflows/branch-docs.yml` when
Fern credentials are available. Production docs publish from the release tag
workflow.
Published docs live in `docs/`, and Fern site configuration lives in `fern/`. See [fern/README.md](../fern/README.md) for the source layout, local development commands, version model, and publishing workflows.

## Validation Expectations

Expand Down
15 changes: 15 additions & 0 deletions architecture/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ health, metrics, or tunnel routes. The plaintext service router also rejects
browser requests whose Fetch Metadata, Origin, or Referer headers indicate a
cross-origin or sibling-subdomain request.

Public workspace-scoped RPCs carry a typed `WorkspaceSelector`. A request must
select one non-empty workspace explicitly; `default` is an ordinary explicit
name, not an omitted-value fallback. Every public sandbox-scoped RPC identifies
the sandbox with a string `sandbox_name` and carries its workspace selector as
a separate request field. Canonical sandbox IDs remain internal metadata used
at authentication, persistence, and compute-driver boundaries; public callers
do not use them as sandbox references. The gateway resolves the name to the
persisted sandbox record and authorizes that record's workspace. Missing and
unauthorized references use the same response within each principal class so
the resolver does not expose an object-existence oracle. Sandbox, sandbox
template, provider, and service list RPCs also accept an all-workspaces marker
after Platform Admin authorization. Single-workspace handlers reject that
marker. Platform-global policy operations require `sandbox_name` and
`workspace_scope` to be absent, while sandbox policy operations require both.

Docker and Podman report the local address through which their sandboxes can
reach the gateway. When the primary listener covers that address, the gateway
reuses it; sandbox JWT authentication and its RPC allowlist remain the callback
Expand Down
Loading
Loading