Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
89b0948
fix: darken settings overlay
shiqimei Sep 1, 2026
98f279f
feat: link empty provider picker to dashboard
shiqimei Sep 1, 2026
050d1af
fix: tighten provider popover spacing
shiqimei Sep 1, 2026
f90aff6
fix: align provider action with settings controls
shiqimei Sep 1, 2026
85ce586
feat: poll for newly added model providers
shiqimei Sep 1, 2026
f075b3d
fix: migrate public API endpoints to v2
shiqimei Sep 3, 2026
083e26c
fix: restore v2 device profile lookup
shiqimei Sep 3, 2026
6dd5391
fix: preserve IME composition in composer
shiqimei Sep 3, 2026
c6acb2f
feat: polish settings and error feedback
shiqimei Sep 3, 2026
2d466a2
feat: animate working crew avatars
shiqimei Sep 3, 2026
9c08e9c
fix: give working avatar a dimensional tumble
shiqimei Sep 3, 2026
87186e6
fix: keep working avatar animation to hopping
shiqimei Sep 3, 2026
14635e1
fix: preserve working avatar shape
shiqimei Sep 3, 2026
37b5cf6
fix: restore circular working avatar
shiqimei Sep 3, 2026
4f0f814
fix: surface live Cloud Agent progress
shiqimei Sep 3, 2026
ac61e80
fix: prevent duplicate Cloud Agent history
shiqimei Sep 3, 2026
1ef4216
feat: preview artifacts and expand work details
shiqimei Sep 3, 2026
fc5ec4a
test: cover Cloud Agent artifact history
shiqimei Sep 3, 2026
d129a99
fix: unify run progress lifecycle
shiqimei Sep 3, 2026
4a34226
fix: align working duration and chevron
shiqimei Sep 3, 2026
ca29a23
fix: space working duration label
shiqimei Sep 3, 2026
3542b58
fix: merge incremental Cloud Agent progress
shiqimei Sep 3, 2026
cf47d06
fix: route messages through Cloud Agent follow-ups
shiqimei Sep 3, 2026
8e952ba
fix: clear stale working placeholders on completion
shiqimei Sep 3, 2026
3e125ba
feat: greet newly created agents
shiqimei Sep 3, 2026
936d2e6
feat: bootstrap Crew agents through create API
shiqimei Sep 3, 2026
331b891
fix: keep Crew agent identity out of runtime internals
shiqimei Sep 3, 2026
3f92a50
fix: preserve concurrent messages and complete previews
shiqimei Sep 3, 2026
45a2f85
feat: defer Agent focus until introduction
shiqimei Sep 3, 2026
20bddb9
feat: stage Agent creation feedback
shiqimei Sep 3, 2026
3b68fc9
fix: preserve Agent creation jump overflow
shiqimei Sep 3, 2026
6555cc9
fix: hydrate greeting before Agent focus
shiqimei Sep 3, 2026
eb2e941
feat: distinguish creating and typing indicators
shiqimei Sep 3, 2026
036ef25
fix: stabilize scroll-to-latest visibility
shiqimei Sep 3, 2026
a76bec9
refactor: consume native Pi RPC activity events
shiqimei Sep 3, 2026
3a3cb43
fix: populate greeting preview on Agent focus
shiqimei Sep 3, 2026
63de1db
fix: collapse Working details by default
shiqimei Sep 3, 2026
6088116
feat(crew): embed cloud computer sessions
shiqimei Sep 3, 2026
c8fc237
fix(crew): polish activity and screen fallbacks
shiqimei Sep 3, 2026
e334055
fix(crew): darken offline computer preview
shiqimei Sep 3, 2026
e3ac47c
fix(crew): remove offline screen hotspot
shiqimei Sep 3, 2026
66884c9
fix(crew): reserve titlebar above VNC modal
shiqimei Sep 3, 2026
4683265
fix(crew): keep details closed after agent creation
shiqimei Sep 3, 2026
61a938c
fix(crew): make VNC overlay fully modal
shiqimei Sep 3, 2026
e76e24d
fix(crew): make VNC opening responsive
shiqimei Sep 3, 2026
208e051
fix(crew): move VNC close into titlebar
shiqimei Sep 3, 2026
7122e16
fix(crew): stabilize live VNC preview
shiqimei Sep 3, 2026
be6c5e3
fix(crew): preserve live previews across agents
shiqimei Sep 3, 2026
da0b664
fix(crew): preserve unacknowledged steering messages
shiqimei Sep 3, 2026
fb74b1a
fix(crew): mark superseded work interrupted
shiqimei Sep 3, 2026
dd3853a
fix(crew): advertise browser capabilities
shiqimei Sep 3, 2026
fd93fff
fix(crew): retain working reply during tools
shiqimei Sep 3, 2026
71b822f
fix(crew): refresh computer readiness
shiqimei Sep 3, 2026
2ce4790
fix(crew): keep details panel in narrow viewport overlay
shiqimei Sep 4, 2026
9a187eb
fix(crew): keep composer inside split layout
shiqimei Sep 4, 2026
f51fc09
fix(crew): preserve working timer across reconciliation
shiqimei Sep 4, 2026
4852228
fix(crew): hide computer trigger while details are open
shiqimei Sep 4, 2026
a16953c
fix(crew): persist input images as cloud artifacts
shiqimei Sep 4, 2026
bc458ee
fix(crew): keep agent greeting concise
shiqimei Sep 4, 2026
4170695
fix(crew): finalize image sends safely
shiqimei Sep 4, 2026
c6e8807
feat(crew): preview message images
shiqimei Sep 4, 2026
55f7306
fix(crew): make image previews immediate
shiqimei Sep 4, 2026
27da18c
fix(crew): keep loading window draggable
shiqimei Sep 4, 2026
310219c
perf(crew): show greeting when runtime is ready
shiqimei Sep 4, 2026
42fa7a7
feat(crew): vary instant greetings
shiqimei Sep 4, 2026
7b27efd
feat(crew): expand instant greeting variants
shiqimei Sep 4, 2026
4bb1c63
fix(crew): stabilize VNC preview size
shiqimei Sep 4, 2026
f470fcc
fix(crew): preserve retries until the run finishes
shiqimei Sep 9, 2026
b92cfa4
fix(crew): authorize desktop VNC sessions and bound retries
shiqimei Sep 9, 2026
838d73b
fix(crew): default to public Runta endpoints
shiqimei Sep 9, 2026
a1d1d40
fix(crew): repair packaged VNC and compress DMG releases
shiqimei Sep 9, 2026
067e1c2
ci(crew): sign and notarize macOS releases
shiqimei Sep 10, 2026
e31f07d
docs(crew): configure Apple credentials at organization scope
shiqimei Sep 10, 2026
a7cc91c
docs(crew): use repository Apple release credentials
shiqimei Sep 10, 2026
0fdec5a
ci(crew): allow signed release dry runs from feature branches
shiqimei Sep 10, 2026
5933025
ci(crew): identify signing credential failures safely
shiqimei Sep 10, 2026
decaeb8
ci(crew): retain redacted macOS import diagnostics
shiqimei Sep 10, 2026
fda6902
build(crew): declare repository for worktree packaging
shiqimei Sep 10, 2026
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
44 changes: 25 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@ name: CI

on:
workflow_dispatch:
inputs:
signed_release:
description: Build signed and notarized macOS dry-run artifacts after verification.
type: boolean
required: false
default: false
release_tag:
description: Required for signed_release; must match package.json, with or without v.
type: string
required: false
default: ''
pull_request:
push:
branches: [main]
tags: ["*"]

permissions:
contents: read

jobs:
verify:
runs-on: macos-15
Expand All @@ -22,23 +36,15 @@ jobs:
- run: npm test
- run: npm run build

artifact:
if: startsWith(github.ref, 'refs/tags/')
signed_release:
name: Signed macOS dry-run
if: github.event_name == 'workflow_dispatch' && inputs.signed_release
needs: verify
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run package
- run: npm run smoke
- uses: actions/upload-artifact@v4
with:
name: runta-crew-macos-arm64
path: |
release/*.dmg
release/*.zip
if-no-files-found: error
permissions:
contents: write
uses: ./.github/workflows/publish-release.yml
with:
tag: ${{ inputs.release_tag }}
ref: ${{ github.sha }}
dry_run: true
secrets: inherit
200 changes: 200 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
name: Publish macOS Release

on:
release:
types: [published]
workflow_call:
inputs:
tag:
type: string
required: true
ref:
type: string
required: false
default: ''
dry_run:
type: boolean
required: false
default: true
workflow_dispatch:
inputs:
tag:
description: Release tag matching package.json, with or without a v prefix.
type: string
required: true
ref:
description: Git ref for a manual dry-run; defaults to the selected workflow ref.
type: string
required: false
default: ''
dry_run:
description: Sign, notarize, and validate Actions artifacts without uploading Release assets.
type: boolean
required: true
default: true

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ inputs.tag || github.event.release.tag_name }}
cancel-in-progress: false

jobs:
package_macos:
name: Sign and notarize macOS arm64
runs-on: macos-15
environment: apple-release
timeout-minutes: 90
outputs:
version: ${{ steps.release.outputs.version }}
release_tag: ${{ steps.release.outputs.release_tag }}
dry_run: ${{ steps.release.outputs.dry_run }}
source_sha: ${{ steps.release.outputs.source_sha }}

steps:
- name: Require Apple release configuration
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ vars.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ vars.APPLE_NOTARY_ISSUER_ID }}
run: |
set -euo pipefail
[[ "$(uname -m)" == arm64 ]] || { echo '::error::An ARM64 macOS runner is required'; exit 1; }
missing=()
for name in APPLE_CERTIFICATE_BASE64 APPLE_CERTIFICATE_PASSWORD APPLE_NOTARY_KEY_BASE64 APPLE_NOTARY_KEY_ID APPLE_NOTARY_ISSUER_ID; do
if [[ -z "${!name}" ]]; then missing+=("$name"); fi
done
if (( ${#missing[@]} )); then
echo "::error::Missing Apple release configuration: ${missing[*]}"
exit 1
fi

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.dry_run && (inputs.ref || github.sha) || inputs.tag || github.event.release.tag_name }}
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 22
architecture: arm64
cache: npm

- name: Validate release source and version
id: release
env:
RELEASE_TAG: ${{ inputs.tag || github.event.release.tag_name }}
MANUAL_REF: ${{ inputs.ref }}
DRY_RUN: ${{ inputs.dry_run && 'true' || 'false' }}
run: |
set -euo pipefail
node --input-type=module <<'JS'
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
const { version } = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const tag = process.env.RELEASE_TAG;
const dryRun = process.env.DRY_RUN === 'true';
assert.match(version, /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/);
assert.equal(tag?.replace(/^v/, ''), version, 'Release tag must match package.json version');
assert.ok(dryRun || !process.env.MANUAL_REF, 'A manual ref override requires dry_run=true');
const source = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
if (!dryRun) {
const tagged = execFileSync('git', ['rev-parse', '--verify', `refs/tags/${tag}^{commit}`], { encoding: 'utf8' }).trim();
assert.equal(source, tagged, 'Publishing must build the exact release tag');
}
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\nrelease_tag=${tag}\ndry_run=${dryRun}\nsource_sha=${source}\n`);
console.log(`Release ${tag}; source ${source}; dry-run ${dryRun}`);
JS

- name: Install dependencies
run: npm ci

- name: Verify source
run: |
set -euo pipefail
log_file="$RUNNER_TEMP/runta-crew-verification.log"
if (npm run typecheck && npm run lint && npm test) >"$log_file" 2>&1; then
cat "$log_file"
else
cat "$log_file" >&2
exit 1
fi

- name: Build signed and notarized release
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ vars.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ vars.APPLE_NOTARY_ISSUER_ID }}
run: npm run package:release

- name: Smoke-test packaged app and require final artifacts
run: |
set -euo pipefail
log_file="$RUNNER_TEMP/runta-crew-release-smoke.log"
if npm run smoke >"$log_file" 2>&1; then
cat "$log_file"
else
cat "$log_file" >&2
exit 1
fi
shopt -s nullglob
dmgs=(release/*.dmg)
zips=(release/*.zip)
(( ${#dmgs[@]} > 0 && ${#zips[@]} > 0 ))
for file in "${dmgs[@]}" "${zips[@]}"; do
[[ -s "$file" && -s "$file.blockmap" ]]
done
[[ -s release/latest-mac.yml ]]

- name: Upload verified macOS artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: runta-crew-macos-arm64-${{ steps.release.outputs.version }}
path: |
release/*.dmg
release/*.zip
release/*.blockmap
release/latest-mac.yml
if-no-files-found: error
compression-level: 0
retention-days: 7

publish_assets:
name: Upload GitHub Release assets
needs: package_macos
if: needs.package_macos.outputs.dry_run == 'false'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: runta-crew-macos-arm64-${{ needs.package_macos.outputs.version }}
path: release

- name: Upload assets to the existing release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.package_macos.outputs.release_tag }}
SOURCE_SHA: ${{ needs.package_macos.outputs.source_sha }}
run: |
set -euo pipefail
gh release view --repo "$GITHUB_REPOSITORY" "$RELEASE_TAG" >/dev/null
[[ "$(gh api "repos/$GITHUB_REPOSITORY/commits/$RELEASE_TAG" --jq .sha)" == "$SOURCE_SHA" ]] || { echo '::error::Release tag moved after the build'; exit 1; }
shopt -s nullglob
assets=(release/*.dmg release/*.zip release/*.blockmap release/latest-mac.yml)
destination="$RUNNER_TEMP/runta-crew-release-assets"
mkdir -p "$destination"
for asset in "${assets[@]}"; do
[[ -s "$asset" ]]
name="${asset##*/}"
cp "$asset" "$destination/${name// /-}"
done
gh release upload --repo "$GITHUB_REPOSITORY" --clobber "$RELEASE_TAG" "$destination"/*
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ npm ci
npm run dev
```

The development app connects to:
The app defaults to:

- Cloud Agents API: `https://api.runta.com`
- Runta Dashboard: `https://dashboard.runta.com`
- Cloud Agents API: `https://api.runta.me`
- Runta Dashboard: `https://dashboard.runta.me`

Development builds can override these addresses in Connection settings. The E2E script accepts a `RUNTA_CREW_E2E_ENDPOINT` override.

Runta Crew uses the real Cloud Agents API. There is no local demo transport or silent mock fallback.

Expand All @@ -67,7 +69,43 @@ npm run package
npm run smoke
```

Artifacts are written to `release/`. Local builds are ad-hoc signed; public distribution requires Runta signing, notarization, and an approved update channel.
Artifacts are written to `release/`. `npm run package` creates ad-hoc signed builds for local testing; these are not public distribution artifacts.

Packaging converts the DMG to native ULMO (LZMA level 9), verifies its contents, and refreshes its blockmap and update metadata. ULMO requires macOS 10.15 or later; Runta Crew requires macOS 13 or later. The DMG compression step leaves ZIP artifacts unchanged.

## Signed macOS releases

The [release workflow](.github/workflows/publish-release.yml) runs `npm run package:release`. Public releases require a real **Developer ID Application** certificate, successful Apple notarization, and stapled app and DMG tickets. ULMO level 9 compression happens before final DMG signing; release hashes, blockmaps, and update metadata must describe the final artifacts.

Configure these values in **runta-dev/runta-crew → Settings → Secrets and variables → Actions**, using **New repository secret** and **New repository variable**:

| Kind | Name | Value |
| --- | --- | --- |
| Secret | `APPLE_CERTIFICATE_BASE64` | Base64 of a Developer ID Application `.p12` export containing its private key. |
| Secret | `APPLE_CERTIFICATE_PASSWORD` | Password protecting that `.p12` export. |
| Secret | `APPLE_NOTARY_KEY_BASE64` | Base64 of an App Store Connect team API key `.p8` file. |
| Variable | `APPLE_NOTARY_KEY_ID` | The API key's Key ID. |
| Variable | `APPLE_NOTARY_ISSUER_ID` | The team's Issuer ID. |

These names follow the [Runta CLI release workflow](https://github.com/runta-dev/runta/blob/integration/.github/workflows/publish-release.yml). Store all five values at repository scope in `runta-crew`; the workflow reads them through its existing `secrets` and `vars` contexts. Its `apple-release` environment remains the release job's environment, with no duplicate values required there.

On macOS, copy each encoded file directly to the clipboard, then paste it into the matching GitHub secret before running the next command:

```bash
# Paste into APPLE_CERTIFICATE_BASE64.
base64 < "/path/to/DeveloperIDApplication.p12" | tr -d '\n' | pbcopy

# Paste into APPLE_NOTARY_KEY_BASE64.
base64 < "/path/to/AuthKey.p8" | tr -d '\n' | pbcopy
```

Enter the certificate password directly in GitHub's secret form. Keep private keys and passwords out of commits, logs, terminal arguments, and chat.

Once the workflow is on the repository's default branch, **Actions → Publish macOS Release** provides manual runs with `dry_run=true` by default. Enter a tag matching `package.json`'s version, with an optional `v` prefix. The optional `ref` override is available only for dry runs; otherwise the workflow builds the release tag.

To validate a feature branch before the release workflow reaches the default branch, dispatch the existing **CI** workflow on that branch with `signed_release=true` and `release_tag` matching `package.json`. This entry reuses the release workflow in dry-run mode, including signing, notarization, and artifact verification.

A dry run still performs signing, notarization, stapling, and verification, then saves DMG, ZIP, blockmaps, and `latest-mac.yml` in the Actions artifact `runta-crew-macos-arm64-<version>`. It skips GitHub Release uploads. Publishing a GitHub Release triggers the workflow automatically; a manual run with `dry_run=false` uploads to an existing release. Use a dry run to validate the environment first.

## Under the hood

Expand Down
8 changes: 8 additions & 0 deletions build/entitlements.mac.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
Loading
Loading