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
87 changes: 87 additions & 0 deletions .github/workflows/desktop-native-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,39 @@ jobs:
- platform: windows
runner: windows-2022
expected_to_fail: false
env:
HERDR_PLUGIN_SOURCE: ${{ (github.event.pull_request.head.repo.full_name || github.repository) }}/packages/petdex-desktop-native/integrations/herdr
HERDR_PLUGIN_REF: ${{ github.event.pull_request.head.sha || github.sha }}

steps:
- name: Checkout petdex
uses: actions/checkout@v7

- name: Install Bun
uses: oven-sh/setup-bun@v2

- name: Install Herdr (Unix)
if: matrix.platform != 'windows'
shell: bash
run: |
curl -fsSL https://herdr.dev/install.sh -o "$RUNNER_TEMP/install-herdr.sh"
HERDR_INSTALL_DIR="$RUNNER_TEMP/herdr" sh "$RUNNER_TEMP/install-herdr.sh"
echo "$RUNNER_TEMP/herdr" >> "$GITHUB_PATH"

- name: Install Herdr (Windows)
if: matrix.platform == 'windows'
shell: pwsh
run: |
$installer = "$env:RUNNER_TEMP\install-herdr.ps1"
Invoke-WebRequest -Uri "https://herdr.dev/install.ps1" -OutFile $installer
& $installer -InstallDir "$env:RUNNER_TEMP\herdr"
"$env:RUNNER_TEMP\herdr" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Install managed Herdr plugin
shell: bash
working-directory: packages/petdex-desktop-native/integrations/herdr
run: bun run managed-smoke.ts install

- name: Install Linux GUI libraries
# The SDK's Linux host links gtk4. Cross-compiling this from
# macOS fails on a missing gtk4 that says nothing about the code,
Expand Down Expand Up @@ -202,6 +230,24 @@ jobs:
Set-Content -Path "$dir\pet.json" -NoNewline
Get-ChildItem $dir | Select-Object Name, Length | Format-Table -AutoSize

- name: Exercise managed Herdr plugin (macOS)
if: matrix.platform == 'macos'
shell: bash
working-directory: packages/petdex-desktop-native
run: |
set -euo pipefail
PETDEX_PET=ci-pet ./zig-out/bin/petdex-desktop-native > herdr-smoke.log 2>&1 &
APP_PID=$!
for _ in $(seq 1 60); do
test -s "$HOME/.petdex/runtime/update-token" && break
kill -0 "$APP_PID" || { cat herdr-smoke.log; exit 1; }
sleep 1
done
test -s "$HOME/.petdex/runtime/update-token"
bun run integrations/herdr/managed-smoke.ts action
kill "$APP_PID" || true
wait "$APP_PID" 2>/dev/null || true

- name: Run and exercise the hook server (Linux)
if: matrix.platform == 'linux'
shell: bash
Expand All @@ -219,7 +265,14 @@ jobs:
APP_PID=$!
sleep 10
kill -0 "$APP_PID" || { echo "app died"; cat run.log; exit 1; }
for _ in $(seq 1 60); do
test -s ~/.petdex/runtime/update-token && break
kill -0 "$APP_PID" || { echo "app died"; cat run.log; exit 1; }
sleep 1
done
test -s ~/.petdex/runtime/update-token || { cat run.log; exit 1; }
TOKEN=$(cat ~/.petdex/runtime/update-token)
bun run integrations/herdr/managed-smoke.ts action
curl -fsS -X POST -H "x-petdex-update-token: $TOKEN" \
-H "Content-Type: application/json" -d '{"state":"running"}' \
http://127.0.0.1:7777/state
Expand Down Expand Up @@ -462,11 +515,45 @@ jobs:
$tokenPath = "$env:USERPROFILE\.petdex\runtime\update-token"
if (Test-Path $tokenPath) {
$token = Get-Content $tokenPath -Raw
"=== pre-Herdr health probe ==="
Invoke-RestMethod -Uri "http://127.0.0.1:7777/health" | ConvertTo-Json -Compress
"=== pre-Herdr stderr ==="
Get-Content run.err -ErrorAction SilentlyContinue
$herdrServer = Start-Process -FilePath (Get-Command herdr).Source `
-ArgumentList "server" -RedirectStandardOutput herdr.log `
-RedirectStandardError herdr.err -PassThru
$herdrReady = $false
for ($attempt = 0; $attempt -lt 80; $attempt++) {
if ((herdr status server) -match "status:\s+running") {
$herdrReady = $true
break
}
Start-Sleep -Milliseconds 100
}
if (-not $herdrReady) {
Get-Content herdr.log -ErrorAction SilentlyContinue
Get-Content herdr.err -ErrorAction SilentlyContinue
throw "Herdr server did not start"
}
bun run integrations/herdr/managed-smoke.ts action
$bridgeExit = $LASTEXITCODE
"=== Herdr plugin logs ==="
herdr plugin log list --plugin dev.petdex.bridge --limit 20
"=== hook round trip ==="
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:7777/state" `
-Headers @{ "x-petdex-update-token" = $token } `
-ContentType "application/json" -Body '{"state":"running"}' | ConvertTo-Json -Compress
Invoke-RestMethod -Uri "http://127.0.0.1:7777/state" | ConvertTo-Json -Compress
"=== runtime mirrors after requests ==="
Get-Content "$env:USERPROFILE\.petdex\runtime\state.json" -ErrorAction SilentlyContinue
Get-Content "$env:USERPROFILE\.petdex\runtime\bubble.json" -ErrorAction SilentlyContinue
"=== stderr after requests ==="
Get-Content run.err -ErrorAction SilentlyContinue
"=== process after requests ==="
if ($app.HasExited) { "EXITED, code $($app.ExitCode)" } else { "alive, pid $($app.Id)" }
herdr server stop
$herdrServer.WaitForExit(10000) | Out-Null
if ($bridgeExit -ne 0) { throw "managed Herdr action failed" }
} else {
"NO TOKEN at $tokenPath"
}
Expand Down
7 changes: 7 additions & 0 deletions packages/petdex-desktop-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ CLI and SDK checkout used by the matching release workflow. The build scripts
apply the Petdex-owned macOS Mach-O headerpad patch before compiling; they
fail if the SDK source no longer matches the pinned patch.

## Herdr

The local Herdr plugin mirrors agent attention from Herdr into Petdex and
preserves the exact pane ID so clicking the pet can focus that pane. Direct
Petdex hooks remain preferred for supported agents. See
[`integrations/herdr`](integrations/herdr/README.md) for setup and filtering.

## Remote agents (SSH)

Agents running on other machines can drive the same pet. Declare remotes in
Expand Down
65 changes: 65 additions & 0 deletions packages/petdex-desktop-native/integrations/herdr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Petdex for Herdr

This Herdr plugin mirrors normalized agent attention into the local Petdex desktop app.

It requires `bun` and `herdr` on the Herdr process PATH. Petdex must be running with hooks enabled.

Direct Petdex hooks remain the richer source for Claude, Codex, Gemini, OpenCode, Qoder, Kimi, CodeBuddy, OMP, and Hermes. The bridge defaults to agents outside that set so it does not replace tool names, approval events, failures, or assistant previews with coarse Herdr states.

## Install from GitHub

```bash
herdr plugin install crafter-station/petdex/packages/petdex-desktop-native/integrations/herdr
herdr plugin list --plugin dev.petdex.bridge --json
```

Review Herdr's trust preview before confirming the install. Use `--ref desktop-vX.Y.Z` to pin a released Petdex desktop version.

## Local development

```bash
herdr plugin link packages/petdex-desktop-native/integrations/herdr
herdr plugin list
```

Start Petdex and test the bridge:

```bash
herdr plugin action invoke test
```

To opt a directly supported agent into the bridge, create `config.json` in the plugin config directory:

```bash
herdr plugin config-dir dev.petdex.bridge
```

```json
{
"includeAgents": ["claude", "codex", "opencode"]
}
```

When `includeAgents` is present, only those normalized names are bridged. `"*"` enables every detected agent. Without it, the bridge covers agents that do not have direct Petdex hooks. `excludeAgents` can add names to the default exclusion set.

## State mapping

| Herdr | Petdex |
| --- | --- |
| `working` | `running`, busy card |
| `blocked` | `waiting`, attention card |
| `idle` | `idle`, expiring card |
| `done` | `idle`, expiring card |
| `unknown` | ignored |

Herdr `done` means idle and not yet seen. It is not treated as verified task completion.

## Runtime behavior

Herdr starts one bridge process per status event. The plugin serializes those processes through `HERDR_PLUGIN_STATE_DIR` and queries Herdr again after taking the lock, so an older event cannot overwrite a newer live state.

The startup snapshot always publishes the aggregate state, including `idle` when Herdr has no active agents, before it restores active cards.

The aggregate includes every agent visible to Herdr. A directly hooked agent running outside Herdr is not visible to that aggregate and can briefly have its global state replaced by a bridged agent. Its next direct hook restores the richer state.

Click-to-focus targets active Herdr agent panes. If the agent was released or Herdr cannot resolve the pane, Petdex falls back to the originating application.
Loading
Loading