Commit Graph

777 Commits

Author SHA1 Message Date
Jordan Ritter 0ff68241ae fix(vscode-extension): use ovsx verify-pat for Open VSX auth check 2026-04-21 19:10:24 -07:00
Jordan Ritter 1638c6f845 ci(showcase): pin linux/amd64 for showcase Docker builds
Add platforms: linux/amd64 to the depot/build-push-action invocation in
showcase_deploy.yml. Railway and GHCR serve x86 hosts, so an arm64-only
image crashes on pull. Matches the platform requirement enforced for
local docker build invocations (documented in
showcase/starters/template/README.md).
2026-04-21 18:42:00 -07:00
Jordan Ritter b64980d0aa style(vscode-extension): preformat workflow yaml and RELEASING.md to match CI formatter 2026-04-21 18:41:48 -07:00
Jordan Ritter 7784802884 fix(vscode-extension): run azure/login in dry-run mode
Verify Marketplace credential expects an active Azure session via azure/login, but login was gated only on published==false. When dry_run=true and version is already published, the verify step ran without a session and failed. Widening the guard to include dry_run.
2026-04-21 18:31:11 -07:00
Jordan Ritter 0f76dc11f2 ci(vscode-extension): sync package.json description from README 2026-04-21 18:21:42 -07:00
Jordan Ritter 8145f0699a ci(vscode-extension): fix stale script reference in dry-run summary
The dry-run summary referenced scripts/release/vscode-extension-release.sh,
which was removed in 17c4daee3 (drop release helper script). Point
maintainers at the new CHANGELOG-edit flow instead.
2026-04-21 18:19:20 -07:00
Jordan Ritter b4ed648b21 ci(vscode-extension): auto-sync package.json version from CHANGELOG top entry
Adds .github/workflows/vscode-extension-changelog-sync.yml: on PRs that
touch packages/vscode-extension/CHANGELOG.md, read the top '## X.Y.Z'
entry and bump packages/vscode-extension/package.json to match via an
auto-committed 'chore: release vX.Y.Z' on the PR branch.

Collapses the release maintainer flow to a single CHANGELOG edit.
Forks are skipped (GITHUB_TOKEN can't push to fork branches). A
guardrail rejects CHANGELOG entries older than the current package.json
version (catches history edits vs. prepends).
2026-04-21 18:19:20 -07:00
Jordan Ritter 1fe8a83343 ci(vscode-extension): add workflow_dispatch dry-run for auth validation
Lets us validate the Marketplace-OIDC and Open VSX auth chains end-to-end
without actually publishing. workflow_dispatch now accepts a dry_run input
(default true); when set, the job runs checkout, Azure login,
vsce verify-pat, and a new Open VSX /api/user token introspection, then
stops before any publish, tag, release, or Slack success notify.

Push trigger is unchanged — dry_run is only meaningful for manual runs.
2026-04-21 18:19:19 -07:00
Jordan Ritter 7e23c5bf93 ci(vscode-extension): align Slack notification with canonical pattern
- Use slackapi/slack-github-action@v2.1.0 matching 8+ existing workflows
- Switch from SLACK_WEBHOOK to SLACK_WEBHOOK_OSS_ALERTS (the secret
  actually wired up in the repo, routed to #oss-alerts)
- Guard with env.SLACK_WEBHOOK_OSS_ALERTS != '' for repo-fork safety
- Add failure-path notification with failed job + per-registry outcomes
  + run URL (follows memory rule: red alerts carry actionable detail)
- Expose publisher as a step output to construct Open VSX URL cleanly
  (https://open-vsx.org/extension/<publisher>/<name>) instead of inline
  tr substitution
2026-04-21 18:19:19 -07:00
Jordan Ritter b018869676 ci(vscode-extension): swap Marketplace auth to OIDC (Entra federated SP)
- Use azure/login@v2 + vsce --azure-credential instead of VSCE_PAT
- Add id-token: write permission on publish job
- Add verify-pat --azure-credential pre-flight to catch auth issues before publish
- Retain OVSX_PAT for Open VSX (OIDC not yet supported there)
- Update RELEASING.md: new auth flow + rollback guidance
2026-04-21 18:19:19 -07:00
Jordan Ritter 7c31fd077f ci(vscode-extension): switch to main-push self-gated publish (aimock pattern)
Replace tag-triggered publish with push-to-main + version-on-Marketplace
self-gate, matching CopilotKit/aimock. CI reads version from package.json,
queries vsce show for that version, and no-ops if already published. On a
new version it builds once, dual-publishes (Marketplace + Open VSX with
the existing retry wrappers and idempotent 'already exists' handling),
tags vscode-extension-vX.Y.Z, cuts a GitHub Release from the CHANGELOG
section, and posts to SLACK_WEBHOOK if configured. Path-scoped to
packages/vscode-extension/** so unrelated pushes don't trigger the job.
2026-04-21 18:19:18 -07:00
Jordan Ritter 2670b041db chore(vscode-extension): retry publish on transient registry failures
Wrap both registry publish steps in a bash retry helper that retries up
to 5 times with staggered backoff (10s/20s/40s/60s/90s) on transient
conditions (5xx, timeouts, connection resets, DNS). Auth and validation
errors still fail fast with no retry.

Critically, 'version already exists' is treated as idempotent success:
if attempt N-1 landed on the registry but its response was lost to a
502 after commit, attempt N sees the already-published version and
short-circuits rather than failing the job.

Motivated by Open VSX /publish returning intermittent 502 Bad Gateway
errors from Eclipse Foundation infra. Each attempt is wrapped in
::group:: markers so per-attempt logs are collapsible in the Actions
UI. Reconciliation step updated to reflect retry semantics and to
tell the operator to rerun the job (not bump the version) on exhausted
retries. RELEASING.md gets a 'Transient registry failures' section
documenting the behavior for both CI and manual publish paths.
2026-04-21 18:19:18 -07:00
Jordan Ritter 28927aad53 ci(vscode-extension): publish VSIX to Marketplace + Open VSX on tag push
Trigger: push of vscode-extension-v* tags.

Builds the extension once, packages a single VSIX, uploads it as a
workflow artifact, then publishes the same VSIX to the VS Code
Marketplace and Open VSX. Both registry publishes use continue-on-error
with a final reconciliation step so a partial failure is visible
instead of silently succeeding on one registry only.

Secrets VSCE_PAT and OVSX_PAT are read from the 'production'
environment so rotation and approval flow are scoped correctly.
2026-04-21 18:19:18 -07:00
Jordan Ritter 4b4923561b feat: VS Code extension — Hook Explorer, AG-UI Inspector, webview-first sidebars (#3935)
## Summary

Three connected features land together so the CopilotKit VS Code
extension becomes a coherent debugger/preview surface:

1. **Hook Explorer** — every V1 + V2 render hook can be discovered and
previewed live with auto-generated controls, an inline `▶️ Preview
Component` CodeLens, and a sidebar that lists every captured site.
2. **AG-UI Event Inspector** — live SSE debug stream of all AG-UI
events, filterable and color-coded, in a sidebar view + editor panel.
3. **A2UI Catalog sidebar → webview** — the last native TreeView gets
replaced with a Tailwind-styled webview that matches the other two, now
with a proper **Go to source** action on components and fixtures.

## Hook Explorer

### Discovery + preview
- oxc-based scanner walks the workspace and finds every call-site of any
hook in the registry (17 across V1 + V2, render + data).
- Preview panel bundles the user's source via Rolldown (IIFE format,
React externalized, CSS collected per `@copilotkit/a2ui-renderer`
pattern), executes it in the webview with a capture-only **stub** for
`@copilotkit/react-core` (+ `/v2`), and mounts the user's component just
long enough to record each hook's config.
- Auto-generated form on the left/top drives the `render` prop's
args/parameters/state/event live. V1 parameter arrays and V2 Zod /
Standard Schema all map through a unified `FormSchema` derived at
runtime from the captured config.
- `useCopilotAction`, `useCopilotAuthenticatedAction_c`,
`useCoAgentStateRender`, `useLangGraphInterrupt`, `useRenderTool`,
`useRenderToolCall`, `useDefaultRenderTool`, `useLazyToolRenderer`,
`useRenderCustomMessages`, `useRenderActivityMessage`,
`useHumanInTheLoop`, `useInterrupt`, `useFrontendTool`, `useComponent`,
`useDefaultTool` all previewable.
- Inline `▶️ Preview Component` CodeLens above every render-hook call
site, backed by the same `copilotkit.hooks.preview` command as the
sidebar.
- Imported render components work: rolldown walks transitive imports
from the hook's `render` prop through any number of sibling files.
- Cross-file hook switches are robust: controls are reset on load,
Harness only mounts once the real HostRoot is ready, a top-level error
boundary auto-recovers when you pick a different hook.

### Why the stub approach
Bundling the real `@copilotkit/react-core` through rolldown's IIFE
output hit a `__commonJSMin` TDZ chain (`require_clipboard`,
`require_graphql`, `require_context_helpers`, …) because the
chat/runtime-client/markdown graph has circular imports. Externalizing
react-core + routing to a Proxy-backed stub that captures hook configs
avoids the whole CJS wrapping problem, shrinks the preview bundle from
~24 MB to ~1.3 KB, and keeps the preview runtime path completely
runnable without a live CopilotKit backend. Tradeoff documented in
`copilotkit-stubs.ts`.

### Weather-themed fixtures
14+ fixtures under `packages/vscode-extension/test-workspace/hooks`,
each a distinct visual scenario (forecast card, severity-palette alerts
with imported CSS, forecast strip, live radar grid, conic-gradient
precipitation gauge, air-quality badge with imported render, pollen
report with a 2-hop import graph, HITL evacuation confirm,
sunrise/sunset gradient, etc.). Used both as regression fixtures and as
the demo surface for video.

### Styling
- Tailwind-via-CDN + VS Code CSS variables for theme-aware chrome.
- User-provided CSS imports collected by rolldown and injected as a
`<style>` tag per load.
- Controls + form fields converted to Tailwind; textarea matches input
styling.
- Framed "Rendered output" card so the render prop is visually
unmistakable.

## AG-UI Event Inspector

### Runtime (`@copilotkit/runtime` + `@copilotkit/shared`)
- `DebugEventBus` — in-memory pub/sub on `BaseCopilotRuntime`, only
instantiated when `NODE_ENV != production`.
- Event tap in `createSseEventResponse` broadcasts every AG-UI event
with metadata (agentId, threadId, runId, timestamp).
- `GET /debug-events` SSE endpoint — returns 404 in production, streams
`DebugEventEnvelope` JSON to connected clients, initial `: connected`
comment flushes headers immediately.

### VSCode Extension
- `DebugStream` — Node SSE client with auto-reconnect, exponential
backoff, URL validation, error surfacing.
- `InspectorPanel` (editor panel, command `CopilotKit: Open AG-UI
Inspector`) and `InspectorViewProvider` (sidebar view) both use a shared
`DebugStream` instance — events persist when switching tabs.
- Inspector React app: `ConnectionBar`, `FilterBar`, `EventList`,
`EventDetail`.
- Color scheme: purple (lifecycle), red (errors), blue (text), orange
(tools), green (reasoning), teal (state), yellow (activity), gray
(unknown).

## A2UI Catalog → webview

- Replaces `ComponentPreviewProvider` (native TreeDataProvider) with
`CatalogListViewProvider` (WebviewViewProvider), matching the Hooks and
Inspector sidebars.
- New React webview with refresh header, component rows (name + relative
path + `auto` badge when no fixture), expandable fixtures list.
- Click a component row → preview (or toggle if it has fixtures); click
a fixture row → preview that fixture.
- Hover action buttons: `▷` preview + `</>` go-to-source on every row.
- "Go to source" opens the component file for component rows; for
fixture rows it opens the fixture file and jumps the cursor to the named
fixture key.

## Test coverage
- Runtime: DebugEventBus unit tests (8), handleDebugEvents endpoint (5),
fetch-router routes (4), integration across Express/Hono/Node/Fetch (9).
- Hooks: scanner + 16 fixture bundle-smoke test, regression guard
against `node_<builtin>` self-references, CSS collector test, stub-based
capture E2E, cross-kind controls remount, FormRenderer defensive
rendering.
- Inspector + webview: DebugStream reconnect (10), inspector components
(17), colors (9).
- Total: **178 tests** passing for the vscode-extension package; runtime
suite unchanged.

## Test plan

- [ ] `pnpm nx run copilotkit-vscode-extension:build` and `pnpm nx run
copilotkit-vscode-extension:test` both green
- [ ] F5 launches the Extension Dev Host with `test-workspace` open
- [ ] Hooks sidebar lists every fixture hook; click a row → preview
opens; `</>` button opens the source
- [ ] `▶️ Preview Component` CodeLens shows above every render hook in a
`.tsx` file; clicking it opens the preview
- [ ] Form controls drive the render live; cross-kind hook switches
(action ↔ custom-message) don't crash; a forced render-prop throw
recovers when a different hook is picked
- [ ] Imported-render fixtures (`ImportedAirQuality`,
`ImportedPollenReport`) bundle and preview correctly
- [ ] A2UI Catalog sidebar is the new webview, refresh works, `</>` on a
fixture opens the fixture file and reveals the named key
- [ ] AG-UI Inspector connects to `GET /debug-events`, filters + detail
work, events survive sidebar/panel switch, invalid URL shows red error
2026-04-21 17:09:45 -07:00
Jordan Ritter e2a6cc2bfe Regenerate shell-dojo registry at build and expand CI trigger paths
The dojo app was missing items under the langgraph column because
shell-dojo shipped a stale committed registry.json. The generator
only wrote to shell/, the dojo Dockerfile didn't run the generator
at build, and the CI path filter didn't rebuild the dojo when
manifest files changed.

Fix: emit from generate-registry.ts to shell, shell-dojo, and
shell-docs; add the generator step to shell-dojo's Dockerfile;
expand the deploy workflow's path filter to include packages/**
and shared/**; and refresh the committed registry/demo-content
JSON so files on disk match what the generator produces today.
2026-04-21 13:10:52 -07:00
Jordan Ritter aa4af554a0 Fix shell-dashboard shell links by plumbing NEXT_PUBLIC_SHELL_URL through build
The shell-dashboard app baked http://localhost:3000 into every demo and code link because NEXT_PUBLIC_SHELL_URL was never provided at build time and the source defaulted to localhost. Next.js inlines NEXT_PUBLIC_* at next build, so setting the value on Railway at runtime does nothing.

Fix: remove the silent localhost fallback, pass NEXT_PUBLIC_SHELL_URL as a Docker build arg from showcase_deploy.yml, and fail loudly if it's unset at build so this can't regress silently.
2026-04-21 13:09:47 -07:00
Jordan Ritter 30c6811519 chore(ci): add interim keep-alive cron for showcase starters
Adds a GitHub Actions workflow that curls /api/health on each of the
17 showcase starter services every 5 minutes to keep the agent's
in-memory state warm (JIT, module cache, connection pools). Railway
Pro tier doesn't sleep containers, but warm-state decays over idle.

Intentionally minimal: no Slack, no metrics, no state. Observability
is already covered by showcase_smoke-monitor.yml — this is strictly
keep-alive.

INTERIM — will be superseded by showcase-ops smoke probe cadence
once that service lands (see Notion proposal §2a).
2026-04-21 13:01:38 -07:00
Jordan Ritter 189c45fb4d fix(workflows): update docs-sync staging path after shell restructure (#4117)
## Summary
- Docs Sync workflow has been failing since 2026-04-21 with `fatal:
pathspec 'showcase/shell/src/content/' did not match any files` (exit
128) on the "Create PR for docs sync" step (example: run 24726678848).
- The path was moved by the shell platform restructure (#4109, #4112) —
the docs content now lives under `showcase/shell-docs/src/content/`, and
the sync marker at `showcase/shell-docs/.docs-sync-sha`. The sync script
(`showcase/scripts/sync-docs-from-main.ts`) already writes to those
locations; only the workflow's `git add` was still referencing the old
monolithic `showcase/shell/` path.
- One-line path fix.

## Test plan
- [ ] Verify yaml parses.
- [ ] Confirm next docs-sync run succeeds end-to-end (opens a sync PR
for the 11 pending review items from run 24726678848).
2026-04-21 10:13:31 -07:00
Jordan Ritter 7dd930cf32 fix(showcase): add Railway image-ref drift assertion
Regression from the 2026-04-21 incident: 18 production Railway services
were found with malformed image refs of the form
`ghcr.io/copilotkit/showcase-<slug>atest` (missing the `:` before
`latest`, so Docker treats `...atest` as the tag). Root cause was an
out-of-band MCP/manual mutation — no committed code touched those refs,
so the data has been fixed but no source-controlled guardrail exists.

Add a standalone script that queries Railway's GraphQL API for every
service in the CopilotKit Showcase project and asserts each image ref
matches the canonical shape `ghcr.io/copilotkit/<service-name>:latest`.
Wire it into showcase_deploy.yml as a pre-build job so any drift aborts
the workflow before the build matrix fans out.

On violation the script prints the service name, the current image, the
expected shape, and the reason, so the fix is obvious in the run log.
Slack classification in the notify job distinguishes a drift failure
from other pre-build failures.

Verified locally: 41 services pass against current Railway state; the
exported `validateImage` function rejects the exact `...atest`
corruption, mismatched service/image names, missing tags, wrong
registries, wrong tag values, and null sources (9/9 simulated cases).
2026-04-21 10:02:35 -07:00
Jordan Ritter 51afc3823b fix(workflows): update docs-sync staging path after shell restructure
The "Showcase: Docs Sync" workflow has been failing with
`fatal: pathspec 'showcase/shell/src/content/' did not match any files`
since the shell platform restructure (#4109, #4112) split the monolithic
`showcase/shell/` package into `showcase/shell-docs/`,
`showcase/shell-dashboard/`, and `showcase/shell-dojo/`.

The sync-docs-from-main.ts script already writes to
`showcase/shell-docs/src/content/` and maintains the sync marker at
`showcase/shell-docs/.docs-sync-sha`. Update the "Create PR for docs
sync" step to stage from the new location so the workflow can commit
synced content again.
2026-04-21 09:32:10 -07:00
Jordan Ritter 0dadff324c fix(ci): drop dead HTTP_CODE sentinel and redundant fromJSON in showcase_deploy
matrix.service.timeout is already a number in the generated matrix
JSON, so wrapping it in fromJSON() was a no-op that only obscured
the expression. Drop the wrapper.

The HTTP_CODE="000" sentinel assigned immediately before the probe
loop is dead: the loop's first iteration unconditionally overwrites
HTTP_CODE before any reader runs. Remove it to avoid implying a
meaningful default where there isn't one.
2026-04-20 22:17:29 -07:00
Jordan Ritter 136f36a178 fix(ci): extend shell-dashboard and shell-docs path filters to shared and scripts
The shell-family starters bundle shared demo content and tooling at
build time via showcase/scripts/bundle-demo-content.ts, which walks
showcase/shared and showcase/packages/*/manifest.yaml. A change to
any of those inputs can alter the generated bundle without touching
the package directory, so path-filter was under-reporting changes
and skipping deploys that actually needed to rebuild. Extend the
filters to shared/**, scripts/**, and packages/*/manifest.yaml so
those inputs trigger the correct downstream deploys.
2026-04-20 22:17:06 -07:00
Jordan Ritter edbc2b0331 fix(ci): harden showcase_deploy Slack notify, concurrency, shared copy scope
- Promote SLACK_WEBHOOK_OSS_ALERTS to job-level env so the step-level
  if: gate can actually see it. Step-level env: is not visible to the
  same step's if:, which silently disabled every Slack post.
- Drop the service suffix from the concurrency group so two manual
  dispatches for different services don't serialize unexpectedly and
  push-triggered runs cancel each other deterministically.
- Rewrite the shared-module copy skip guard as an allowlist scoped to
  showcase/packages/*. Starters, shell-family, aimock, and shell-dojo
  don't consume shared modules, so the previous denylist missed every
  new self-contained context (context=".") and polluted the repo root.
2026-04-20 22:16:52 -07:00
Jordan Ritter 183cae2a43 fix(ci): check-binaries extends extension regex + set -euo pipefail + SIZE validation
- Extend the binary-extension regex to cover .class, .jar, .pyd,
  .pyc, .node, .bin, .pdb, .zip, .whl — any of which a build tool
  can drop into a PR and none of which the previous regex caught.
- Add set -euo pipefail at the top of the run block. Previously
  a transient wc failure (file deleted mid-diff) produced an empty
  SIZE, which then failed [ "$SIZE" -gt ... ] with "integer
  expression expected" on stderr while the script marched on —
  silent false-green possible. Now errors fail the job loudly.
- Guard the wc -c call with an explicit numeric-regex check on
  SIZE. A non-numeric or blank result now emits a ::warning:: and
  skips the file rather than falling into the arithmetic
  comparison and crashing under set -e (finding #17, #18).
2026-04-20 19:42:18 -07:00
Jordan Ritter a5461a5872 fix(ci): showcase_deploy documents prior-deploy ordering + hardens truncate_csv
- Add a comment to the prior-deploy Railway GraphQL query explaining
  that deployments(first: 1) relies on the API default of
  createdAt DESC ordering. Railway has no explicit orderBy argument
  on this field; documenting the assumption means a future schema
  change (or an operator reading the file cold) has something to
  point at before debugging stale-deployment guard regressions
  (finding #15).
- Harden both truncate_csv shell helpers (one per run block): disable
  pathname globbing via set -f for the iteration so a slug
  containing a glob char (*, ?, [) cannot expand against the
  filesystem, and lock IFS to whitespace for deterministic
  word-splitting regardless of caller env. Restore the prior glob
  state on the way out so other shell blocks see no side effect
  (finding #16).
2026-04-20 19:42:08 -07:00
Jordan Ritter 05cd01ec04 Finish the grid: #4085 + shell platform restructure (shell-docs, shell-dashboard, shell-dojo) (#4109)
## Summary

- Merges #4085 (Atai's no-docker-restructure feature port — merge-ready
variant) as the primary base.
- Renames `showcase/shell-internal/` → `showcase/shell-dashboard/` +
provisions Railway service at `dashboard.showcase.copilotkit.ai`.
- Renames `showcase/shell-dojolike/` → `showcase/shell-dojo/` + renames
Railway service in place (domain unchanged).
- Extracts MDX-docs infrastructure (`/docs`, `/[framework]`, `/ag-ui`,
`/reference` + components, libs, content) from `showcase/shell/` into
new standalone `showcase/shell-docs/` package; provisions Railway
service at `docs.showcase.copilotkit.ai`; adds 301 redirects on shell
for every moved route (including 17 per-framework slugs enumerated from
`registry.json`).
- Fixes `starter-langroid` `/api/health` 503 (sed stdout wrappers +
readiness gate); adds red-green regression guards in
`starter-consistency.test.ts`.

## DNS state

- `dashboard.showcase.copilotkit.ai` CNAME → `6u9icvje.up.railway.app` —
✅ live in Route53.
- `docs.showcase.copilotkit.ai` CNAME → `t6ge0qwv.up.railway.app` —
pending Route53 add.

## Railway services

- `showcase-shell-dashboard` (ID `4d5dfd74-be61-40b2-8564-b53b7dd4c15b`)
— provisioned, image pending first deploy.
- `showcase-shell-docs` (ID `7badfb8d-4228-414c-9145-b4026803714f`) —
provisioned, image pending first deploy.
- `showcase-shell-dojo` (ID `7ad1ece7-2228-49cd-8a78-bddf30322907`) —
renamed from shell-dojolike; already live.

## Closes / supersedes

- Supersedes #4084 (docker-restructure variant; preserved on
`jpr5/ftg-shell-ops` for future consideration, not merged here).
- True-merges #4085 via `--no-ff` — that PR auto-closes on this PR's
merge.

## Known non-blockers

- `/ag-ui/introduction` throws a server-component `onMouseEnter` runtime
error on `next start`. Same issue exists in shell today — pre-existing,
not a regression. Follow-up needed.
- `showcase/shell-dojo` lacks `package-lock.json` and `.gitignore`;
trivial follow-up for parity with siblings.
- `search-modal` retains the flat `search-index.json` shape (not the
extensible `{version:1, sources:[...]}` shape). Generator is the single
point of change if multi-shell search is wanted later.
- oxfmt has a pre-existing oscillation bug on 14 `.mdx` files with
`<Steps>` structures; sidestepped by mirroring the existing
`showcase/shell/src/content/**` ignore policy to the new
`showcase/shell-docs/src/content/**` path.

## Test plan

- [ ] CI green end-to-end (Validate Showcase, unit tests on 20/22/24,
format, oxlint, commitlint, starter-smoke).
- [ ] \`showcase_deploy.yml\` builds shell-dashboard + shell-docs images
to GHCR and Railway pulls them successfully.
- [ ] Add \`docs.showcase\` CNAME in Route53
(\`t6ge0qwv.up.railway.app\`) — without this,
docs.showcase.copilotkit.ai won't resolve and Railway won't provision
its TLS cert.
- [ ] Verify \`dashboard.showcase.copilotkit.ai/\` returns 200.
- [ ] Verify \`docs.showcase.copilotkit.ai/\` returns 200 and
\`/langgraph-python/quickstart\` renders.
- [ ] Verify \`showcase.copilotkit.ai/\` returns grid (200);
\`/docs/foo\` 301s to \`docs.showcase.copilotkit.ai/foo\`;
\`/react/foo\` 301s; \`/integrations\` and \`/matrix\` do NOT redirect.
- [ ] \`starter-langroid\` redeploy via \`showcase-deploy\` workflow:
\`/api/health\` returns 200.
2026-04-20 19:25:39 -07:00
Jordan Ritter 5964b44b57 fix(ci): shell-dashboard ALL_SERVICES entry mirrors shell-docs structure 2026-04-20 18:58:57 -07:00
Jordan Ritter c3ad48a8af fix: clarify pin-drift alert wording (DRIFT vs FAIL)
The weekly Showcase pin-drift Slack alert previously read
"📉 Showcase pin-drift (weekly): FAIL=N
(baseline N) [ok]". The token "FAIL=N" read as a regression even when N
matched the ratchet baseline exactly (a stable week), generating recurring
false-alarm fatigue on #oss-alerts.

Reframe the notification text into three explicit states:

- [stable]   — DRIFT=N matches baseline AND hash matches
- [REGRESSION] — DRIFT=N grew OR hash mismatch (set drifted); keeps the
  🚨 emoji + run URL for actionable alerts
- [IMPROVED, ratchet me] — DRIFT=N shrank; reminder to update the baseline

Presentation-only. The ratchet logic in validate-pins.ts, the baseline file
(showcase/scripts/fail-baseline.json), and the FAIL-line hashing are
unchanged.
2026-04-20 18:14:13 -07:00
Jordan Ritter 1bc90bc9ec fix(ci): showcase_deploy prior-deploy jq uses // empty for null normalization
The main-deploy step already uses '// empty' on PRIOR_DEPLOY_ID but
the verify step extracted DEPLOY_ID / STATUS / DOMAIN without it, so
when Railway's edges array was transiently empty these became the
literal string 'null'. That breaks the prior-vs-fresh comparison and
the DOMAIN emptiness guard in inconsistent ways. Add '// empty' to
all three jq filters so empty-edge responses normalize to '' the
same way everywhere.
2026-04-20 17:49:59 -07:00
Jordan Ritter 1c1a26b3cb fix(ci): showcase_deploy notify surfaces jq errors; drop top-level 'local'
Two small but load-bearing hygiene fixes in the notify job's shell:

1) The matrix-legs collection pipeline used `jq -r '...' 2>/dev/null`,
   swallowing any jq parse or schema errors before the surrounding
   pipeline substitution could see them. If the gh-api payload shape
   ever shifts (schema rev, partial response, encoding glitch), we
   now want the error in the job log so an operator knows WHY notify
   fell back to softer wording — the existing `-z "$pairs"` guard
   still handles the empty-output path without relying on suppression.

2) The duplicated inline `truncate_csv` helper was declaring
   `local ws=...` on its OWN line AFTER `local budget=...` on the
   function's first line. Redeclaring `local` inside the same
   function body is legal but prints 'local: not in a function'
   warnings on some bash versions (3.x, noexec edge cases) and is
   outright forbidden by `set -eu` hardening profiles. Collapse `ws`
   into the single `local` declaration at the top and use a plain
   assignment where it's computed.
2026-04-20 17:39:49 -07:00
Jordan Ritter a6b7941cd0 fix(ci): showcase_deploy classifier — skipped no longer misclassified as failure
The BUILD outcome classifier read:

    elif [ "$BUILD" = "failure" ] || [ -n "$BUILD" ]; then

After the earlier `success` + `skipped && detect=success` branches
had claimed the obvious green cases, any non-empty BUILD string fell
through the `-n` test and was tagged FAILURE — including values like
"skipped" that reach this branch when detect-changes did NOT emit
`success` (e.g. cancelled pre-stage races or a partial matrix skip).
That means the Slack bot would occasionally cry red at runs that never
failed.

Simplify to an exact string match on "failure"; the final `else`
branch already handles the empty/unknown case by emitting OUTCOME=skip,
which is the correct disposition for indeterminate state.
2026-04-20 17:39:23 -07:00
Jordan Ritter b4b3a80515 ci(workflows): bump actions/checkout to v4 in check-binaries
The rest of the showcase workflows already pin `actions/checkout@v4`.
This one lagged on v3, which is now long-deprecated (Node 16 runner
EOL). Bringing it in line with the fleet avoids a future forced bump
when GitHub retires v3 runners entirely.
2026-04-20 17:39:08 -07:00
Jordan Ritter 28e74e2a9f chore(ci): purge test-integration-tmp pollution from workflows
Remove every trace of the defunct test-integration-tmp service from the
showcase deploy workflow:

- drop the `test-integration-tmp` entry from the workflow_dispatch
  inputs.service dropdown,
- drop the detect-changes `test_integration_tmp` output + paths-filter
  entry, and
- delete the entire build-test_integration_tmp job (including its
  placeholder RAILWAY_SERVICE_ID literal).

Matches the jpr5/test_integration_tmp_leak cleanup — the test harness
leak has been patched at its source (create-integration.test.ts), but
these workflow vestiges were never part of that fix and only survived
because nothing read them after the regression guard landed.
2026-04-20 17:39:02 -07:00
Jordan Ritter 7c09e25ca3 ci(showcase): allowlist shell-docs/src/data/* in check-binaries 2026-04-20 14:28:54 -07:00
Jordan Ritter a0daa21166 ci(showcase): add shell-docs to ALL_SERVICES; provision Railway service showcase-shell-docs
Adds the shell-docs package to the deploy matrix, paths-filter, and
dispatch options. Railway service `showcase-shell-docs` (ID
7badfb8d-4228-414c-9145-b4026803714f) has been provisioned with the
custom domain `docs.showcase.copilotkit.ai` and the Railway-generated
CNAME target `t6ge0qwv.up.railway.app`.

Env vars seeded on the Railway service: OPENAI_API_KEY,
ANTHROPIC_API_KEY, GOOGLE_API_KEY, POSTHOG_PROJECT_KEY, PORT=10000,
NEXT_PUBLIC_BASE_URL=https://docs.showcase.copilotkit.ai,
NEXT_PUBLIC_SHELL_URL=https://showcase.copilotkit.ai.

Matrix entry uses lfs:true + context:"." + explicit Dockerfile path
(mirrors shell) because the image pulls in shared/ scripts/ packages/
at build time. build_args_sha/branch are included so the build surface
embeds commit metadata, matching shell's pattern.
2026-04-20 14:00:28 -07:00
Jordan Ritter 878ac3af2d chore(showcase): rename shell-dojolike → shell-dojo for naming consistency with dojo.showcase.copilotkit.ai
- Renamed showcase/shell-dojolike/ → showcase/shell-dojo/ (git mv for history).
- Updated package name to @copilotkit/showcase-shell-dojo.
- Updated .github/workflows/showcase_deploy.yml: dispatch option, paths-filter key, ALL_SERVICES entry (context, image, comment).
- Updated .github/workflows/static_check-binaries.yml size allowlist path.
- Renamed Railway service showcase-shell-dojolike → showcase-shell-dojo (id 7ad1ece7-2228-49cd-8a78-bddf30322907 preserved; domain dojo.showcase.copilotkit.ai unchanged).
- Updated image source to ghcr.io/copilotkit/showcase-shell-dojo:latest.
2026-04-20 13:54:42 -07:00
Jordan Ritter daf09358c4 chore(showcase): rename shell-internal → shell-dashboard and provision Railway service @ dashboard.showcase.copilotkit.ai
- Renamed showcase/shell-internal/ → showcase/shell-dashboard/ (git mv for history).
- Updated package name to @copilotkit/showcase-shell-dashboard (+ lockfile).
- Updated external references: showcase/scripts/{probe-docs,generate-status}.ts comments and showcase/README.md.
- Added dispatch + filter + ALL_SERVICES entry for shell-dashboard in .github/workflows/showcase_deploy.yml.
- Provisioned Railway service showcase-shell-dashboard (id 4d5dfd74-be61-40b2-8564-b53b7dd4c15b):
  - image source ghcr.io/copilotkit/showcase-shell-dashboard:latest
  - env vars OPENAI_API_KEY, ANTHROPIC_API_KEY, PORT copied from showcase-shell
  - custom domain dashboard.showcase.copilotkit.ai attached (CNAME → 6u9icvje.up.railway.app)
  - image will be built and pushed via showcase_deploy.yml on merge
2026-04-20 13:54:17 -07:00
Alem Tuzlak ecf79393e4 ci(vscode-extension): least-privilege perms, concurrency guard, drop dead lint step
- Add top-level 'permissions: contents: read' — the workflow performs
  no repo writes, so declaring the minimum explicitly closes a
  default-token hardening gap.
- Serialize publishes with concurrency: group: vscode-extension-publish,
  cancel-in-progress: false. Two rapid pushes to main used to race
  straight into vsce publish; the second failed noisily on duplicate
  version or, worse, published out of order.
- Remove the 'Lint' step. The Nx target 'copilotkit-vscode-extension:lint'
  doesn't exist and there's no 'lint' npm script either, so the step
  always failed — 'continue-on-error: true' just hid it while never
  actually linting anything. Re-add once a real lint target lands.

check-binaries.sh:
- Replace '[ ] && exit 1 / exit 0' with an explicit 'if [ ]; then
  exit 1; fi; exit 0' block. Under 'set -e' the old form was safe
  only because the trailing 'exit 0' existed; the explicit form is
  robust regardless of what follows.
2026-04-20 19:13:50 +02:00
Alem Tuzlak 7f3a8c27c7 fix: address PR review feedback (@ranst91)
Critical
- scripts/hooks/check-binaries.sh: restore showcase data-file exclusions
  (demo-content / search-index / starter-content >1 MB) that the inline
  refactor dropped; add 'set -eu' so silent shell failures don't hide
  policy violations.
- packages/vscode-extension/src/extension/preview-panel.ts: drop 'blob:'
  from CSP script-src — it lets arbitrary-string JS execute via Blob URL
  and defeats most of CSP's XSS protection.
- packages/vscode-extension/package.json: set private: true. The extension
  ships as a .vsix via vsce, not npm, and workspace:* devDependencies
  would break an accidental 'npm publish'.

Important
- runtime: forward the real agentId from handleConnectAgent into
  handleSseConnect / createSseEventResponse so DebugEventBus envelopes
  on /connect carry the actual agent name instead of the literal
  'connect'. Updates handle-connect.ts, sse/connect.ts.
- hooks/panel.ts CSP: narrow connect-src from 'https:' to just the
  Tailwind CDN. The preview path never drives a real CopilotKit runtime
  — all hook calls route through the stub — so there's no legitimate
  https: fetch to allow from inside bundled user code.
- extension/utils.ts getNonce(): switch to crypto.randomBytes. Math.random()
  is not acceptable for a value that gates inline-script execution.
- .github/workflows/vscode-extension.yml:
  * Build step uses 'nx run copilotkit-vscode-extension:build' instead
    of 'pnpm run build' (targeted build with Nx caching, not full
    monorepo rebuild).
  * Added explicit 'Type check' step (tsc --noEmit).
  * Added Lint step gated with continue-on-error until the Nx target
    exists, so a missing target doesn't break the pipeline.
  * Publish job now queries the Marketplace for the current published
    version and skips 'vsce publish' when the local package.json
    version matches — stops every docs/CI-only push to main from
    failing on duplicate-version errors.
- hooks/hook-scanner.ts: bound the synchronous walk at 20 000 files so a
  pathologically large workspace can't freeze the extension host; flag
  kept in module-level constant with a rationale comment.
- sse-response.ts: document that debugEventBus.broadcast intentionally
  runs before the stream-closed gate so debug subscribers see trailing
  events even after the SSE client disconnects.
- inspector-panel.ts: subscribe to DebugStream lazily on show() rather
  than in the constructor; unsubscribe on panel dispose. Avoids firing
  the event callback on every envelope when no panel is open.

Suggestions
- fetch-router.ts: document 'debug-events' as a reserved route so it
  can't be shadowed by an agent literally named 'debug-events'.
- activate.ts findValuePosition: add optional startOffset parameter,
  document the first-occurrence limitation + the follow-up path for
  per-fixture precision.
- activate-hook-explorer.ts isInsideWorkspace: fix JSDoc to reflect the
  code (root itself is excluded).
2026-04-20 17:56:09 +02:00
Alem Tuzlak 9496984449 Merge remote-tracking branch 'origin/main' into worktree-mutable-discovering-valiant
# Conflicts:
#	docs/content/docs/integrations/langgraph/doctest.json
#	lefthook.yml
#	pnpm-lock.yaml
2026-04-20 17:03:47 +02:00
Atai Barkai f4d4e6388a fix(ci): remove test-integration-tmp from starter-smoke matrix
I added it to the matrix in c3d105ae0 but the corresponding
examples/integrations/test-integration-tmp/ directory was never
created on this branch — the job fails on every PR run with
"No such file or directory".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:41:54 -07:00
Atai Barkai 62a05003c1 Merge remote-tracking branch 'origin/main' into atai/2026-04-18/feature-port-no-docker-restructure
# Conflicts:
#	.github/workflows/showcase_deploy.yml
2026-04-19 21:29:07 -07:00
Atai Barkai a1ed347faa Merge remote-tracking branch 'origin/main' into atai/2026-04-18/feature-port-no-docker-restructure
# Conflicts:
#	showcase/scripts/__tests__/bundle-demo-content.test.ts
#	showcase/scripts/lib/manifest.ts
#	showcase/scripts/validate-parity.ts
2026-04-19 16:40:48 -07:00
Jordan Ritter e1e163886b ci(showcase): emit red→green transition alerts for deploy + smoke workflows
Teams get pinged on every failure but never see a positive recovery
signal when these workflows flip back to passing. Mirror the per-service
transition pattern already implemented in showcase_smoke-monitor.yml.

showcase_deploy.yml (notify job):
  - Restore previous run's status from actions/cache (key pattern
    matches smoke-monitor: prefix-based restore-keys + run_id save key)
  - Classify outcome as failure | success | skip
  - failure → post existing red alert + update state
  - success AND prev=failure → post recovery + clear state
  - success AND prev=ok → silent (green→green, matches prior behavior)
  - skip (no-changes or cancellation) → no state change, no post

starter_deployed_smoke.yml:
  - Restore state before tests run
  - After test step, classify via steps.playwright.outcome
  - On red→green, post recovery message alongside existing failure path
  - On any terminal outcome, save new state to cache

First-ever run (or cache miss): state initializes to ok so we don't
emit a false recovery on the first green run after this ships.

Recovery wording:
  ✅ *Showcase deploy*: recovered (was down since <iso8601>)
  ✅ *Starter Deployed Smoke Tests*: recovered (was down since <iso8601>)

Existing red-alert wording is unchanged.
2026-04-19 14:37:38 -07:00
Atai Barkai c3d105ae00 feat(showcase/langgraph-python): port 4084 cells into 4085 demos layout
Port 15+ cells from PR 4084 (per-demo workspace) into PR 4085's
shared-column layout (src/agents/*.py + src/app/demos/<cell>/*.tsx).

New cells:
- beautiful-chat (port scaffold — full component tree deferred)
- declarative-gen-ui-hardcoded (single-file catalog variant)
- interrupt-headless (useHeadlessInterrupt from primitives)
- open-gen-ui-advanced (sandbox functions + app-tool-calling)
- readonly-state-agent-context (useAgentContext one-way channel)
- reasoning-default-render (built-in CopilotChatReasoningMessage)
- shared-state-read-write (preferences write + notes read)
- tool-rendering-reasoning-chain (reasoning emitter + chaining loop)

Restructured cells:
- declarative-gen-ui: BYOC with 3-file a2ui/{definitions,renderers,catalog}
- a2ui-fixed-schema: compositional tree with Title/Airport/Arrow/
  AirlineBadge/PriceTag + 3-file catalog; search_flight renamed
  display_flight
- mcp-apps: simplified to mcpApps.servers runtime config targeting
  Excalidraw; drops custom MCPAppsStubMiddleware
- open-gen-ui: minimal provider + openGenerativeUI runtime flag
- tool-rendering + 3 variants: per-tool WeatherCard + FlightListCard
  plus useDefaultRenderTool wildcard

Runtime wiring:
- /api/copilotkit: registers new agents + a2ui.injectA2UITool for
  declarative + fixed-schema cells
- /api/copilotkit-ogui: adds open-gen-ui-advanced
- /api/copilotkit-mcp-apps: NEW dedicated endpoint for mcpApps

Backend graphs:
- shared_state_read_write.py (PreferencesInjectorMiddleware + set_notes)
- readonly_state_agent_context.py
- hitl_in_chat_agent.py (tools=[] + tailored prompt)
- a2ui_dynamic_hardcoded.py (secondary-LLM dynamic schema)
- open_gen_ui_advanced_agent.py
- tool_rendering_reasoning_chain_agent.py (reason→tool loop)

Flight fixed-schema JSON now assembles Card>Column>[Title,Row,Row,Button].

All `@region[...]` markers ported to drive docs <Snippet> resolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:45:21 -07:00
Jordan Ritter 2c73a3480b ci: close showcase deploy automation gap for starters (#4082)
## Summary

Two related defects in the showcase deploy pipeline let stale images sit
live on Railway while Slack stayed green. This PR fixes both.

### Defect 1 — Drift detector skipped all starter services

`.github/workflows/showcase_smoke-monitor.yml` listed only 19
**package** slugs in its `SERVICES=(...)` array (ag2, mastra,
llamaindex, ...). Zero **starter** slugs. As a result:

- GHCR `showcase-starter-<svc>` tags were never checked for drift.
- `gh workflow run showcase_deploy.yml -f service=starter-*` was never
auto-dispatched.
- Starter services could run with weeks-old images and no alert would
fire.

`showcase_deploy.yml` already supports `starter-*` dispatch names and
already calls `serviceInstanceRedeploy` for any service with a
`railway_id`, so no change is required there. The fix is extending
`SERVICES=(...)` to include all 17 starter slugs via a
sparse-checkout-driven filesystem enumeration (no more literal
duplication between workflow and `showcase/starters/`).

### Defect 2 — Silent deploy failures reported green

`.github/workflows/showcase_deploy.yml` emitted `::warning::` and exited
0 when a service never returned 200 on its health path within 360s. The
legacy justification (`# Don't fail — sleep-on-idle services take time
to wake`) no longer applies: Railway is on the Pro tier with no
sleep-on-idle, so a 6-minute failure to become healthy is a real
failure. Changed to `::error::` + `exit 1`.

## Round 2 fixes

Round 2 CR raised six findings against the original smoke-monitor +
validator changes. All fixed in this PR:

- **BLOCKING 1/2 — smoke-monitor guard.** Replaced the magic `-eq 19`
sentinel with `grep -c '^starter-'` so adds/removes to the literal
non-starter list can't silently disable the guard. Added `shopt -s
nullglob` around the `showcase/starters/*/` loop so an empty starters
tree no longer corrupts `SERVICES` with a `starter-*` literal.
- **BLOCKING 3 — GHCR stderr isolation.** Dropped `2>&1` on the `gh api
-i` call; captured stderr to a temp file and surfaced it only when `gh`
returns a non-zero RC with no HTTP status. Auth / rate-limit / network
noise can no longer splice into the HTTP header block and poison
`HTTP_STATUS` / `API_BODY` parsing.
- **BLOCKING 4 — validator tests.** Added
`showcase/scripts/__tests__/validate-workflow-starters.test.ts` (12
specs): happy path, missing-from-options-only, missing-from-matrix-only,
missing-from-both, empty starters dir (exit 3), template/ excluded,
substring-spoof (starter-ag2 vs starter-ag2-extended), missing workflow
file (exit 3). Also extended `VALIDATE_WORKFLOW_STARTERS_REPO_ROOT` to
re-home the starters dir for testability.
- **MEDIUM 1 — YAML parsing.** Replaced the fragile regex-over-YAML
options scanner with a real `yaml.parse()` + typed navigation down
`on.workflow_dispatch.inputs.service.options`. ALL_SERVICES stays
regex-scanned (embedded JSON in a bash heredoc, with `${{ ... }}`
interpolations that aren't valid JSON pre-execution), but the
surrounding step is now located via YAML.
- **MEDIUM 2 — Slack list truncation.** Replaced `cut -c1-200` with a
`truncate_csv` helper that drops whole comma-separated entries until
under budget and appends `…` when truncated. No more
`starter-claude-sdk-pyth` mid-slug corruption.
- **MEDIUM 3 — template/ exclusion cross-references.** Both the TS
validator's `EXCLUDED_DIRS` and `showcase_smoke-monitor.yml`'s `[
"$slug" = "template" ] && continue` now carry `# keep in sync with ...`
comments pointing at each other.
- **NIT 2 — entry-check simplification.** Dropped the
belt-and-suspenders `import.meta.url === \`file://${argv[1]}\`` branch;
kept only the canonical `fileURLToPath(import.meta.url)` form.
- **NIT 3 — jq pipeline collapse.** Single-pass `.jobs[]? | select |
"\(...)"` replaces the three-pass `map | map | .[]` chain in the notify
step.

## Files changed

- `.github/workflows/showcase_deploy.yml` — warning → error + exit 1 on
unhealthy deploy; `truncate_csv` replaces `cut -c1-200` (3 sites);
single-pass jq pipeline in notify step.
- `.github/workflows/showcase_smoke-monitor.yml` — filesystem-driven
`SERVICES=(...)`, starter-count guard, `nullglob` loop, stderr-isolated
`gh api` call, cross-reference comment.
- `.github/workflows/showcase_validate.yml` — wires
`validate-workflow-starters` into CI.
- `showcase/scripts/validate-workflow-starters.ts` — YAML-aware presence
checks; env-var override homes both starters dir and workflow path.
- `showcase/scripts/tsconfig.json` — scripts-local tsconfig for LSP type
resolution.
- `showcase/scripts/__tests__/validate-workflow-starters.test.ts` — 12
specs covering the full matrix of drift scenarios.

## Test plan

- [ ] Next scheduled `showcase_smoke-monitor` run includes starter
services in its drift scan.
- [ ] A deliberately-unhealthy deploy (simulate by pointing health_path
at a 404) fails the job and fires the Slack alert.
- [ ] `showcase_validate` CI job runs `validate-workflow-starters` and
`npx vitest run scripts/__tests__/validate-workflow-starters.test.ts`
green.
2026-04-19 07:43:42 -07:00
Jordan Ritter a05ccfbc68 fix(ci): skip langroid on Python 3.10 (tests import typing.Self) 2026-04-18 21:03:00 -07:00
Jordan Ritter 4439b79fba fix(ci): install pytest-asyncio + skip strands on Python 3.10
Two CI fixes for the showcase_validate python-unit-tests job on PR #4083:

1. Install pytest-asyncio (and pytest-mock) in the shared "minimal test
   deps" step. langroid/tests/python/test_agui_adapter.py has 16 tests
   annotated with @pytest.mark.asyncio; without the plugin, pytest
   reports "async def functions are not natively supported" and skips
   them. Locally the user's venv has it; CI did not.

2. Surgically skip the strands package on Python 3.10.
   ag_ui_strands==0.1.0 declares requires-python >=3.12,<3.14, so
   pip install fails on 3.10 before pytest runs. Prefer the surgical
   skip over dropping 3.10 from the matrix so the typing_extensions
   NotRequired fallback path in aimock_toggle.py keeps getting
   exercised.
2026-04-18 20:56:12 -07:00
Jordan Ritter d5fab09687 fix(ci): install package requirements before running showcase Python unit tests
Python unit-test workflow previously installed only pytest +
typing_extensions, assuming stdlib-only tests. Tests now import runtime
deps (openai, google.genai, httpx, opentelemetry, etc.) at module load
time, causing ModuleNotFoundError at pytest collection before
conftest-based stub finders run.

Loop each showcase/packages/*/ with a tests/python/ dir, pip install the
package's requirements.txt (if present) before pytest. Re-enable pip
cache keyed on those requirements files. Matches real-runtime parity.

Refs blocker 2 on PR #4083 (fix/showcase-smoke-failures).
2026-04-18 20:44:39 -07:00
Jordan Ritter 36133efa53 ci(test-runtime-servers): migrate runtime server tests to Depot runners
Depot Startup plan (unlimited minutes) for consistent runner quality
across test workflows. Bun setup action is runner-agnostic on linux.
Extends the pattern from PR #4018.

Also adds the id-token: write permission required for Depot OIDC
auth, alongside contents: read for least-privilege defaults.
2026-04-18 18:57:18 -07:00