Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5679192
feat(opencrabs): port OpenCrabs ACP harness adapter to v0.1.52 archit…
moneyacademyKE Sep 21, 2026
edd5e69
fix(opencrabs): resolve CodeRabbit review findings
moneyacademyKE Sep 21, 2026
6d8e273
fix(opencrabs): close five interaction gaps from the round-3 audit
moneyacademyKE Sep 21, 2026
9409e6e
fix(opencrabs): make the resume window honest
moneyacademyKE Sep 21, 2026
400705d
fix(opencrabs): translate the ACP usage size field into the meter's w…
moneyacademyKE Sep 21, 2026
2cc0fef
fix(opencrabs): address review — defer resume notice until fallback s…
moneyacademyKE Sep 21, 2026
bdddfff
fix(opencrabs): omit --model for the Default catalog placeholder
moneyacademyKE Sep 22, 2026
2c3b9c4
feat(opencrabs): align adapter with v0.3.0 configurable-binary archit…
moneyacademyKE Sep 28, 2026
c0aaed8
fix(opencrabs): name connect failures, surface mode-switch errors; sp…
moneyacademyKE Sep 28, 2026
d9ad2ac
fix(models): label the OpenCrabs pre-connect entry Configured model
moneyacademyKE Sep 28, 2026
23f70f5
fix(assets): redraw the OpenCrabs crab for 16px legibility
moneyacademyKE Sep 28, 2026
6951c7a
ci(opencrabs): build windows and linux packages on the fork without u…
moneyacademyKE Sep 29, 2026
a8ce328
ci(opencrabs): trigger cross builds from fork release tags
moneyacademyKE Sep 29, 2026
55e939a
opencrabs: subagent cards, nested child steps, inline images
moneyacademyKE Oct 2, 2026
e815cba
feat(opencrabs): render cross-surface mirror pushes in resumed sessions
moneyacademyKE Oct 5, 2026
f003594
fix(opencrabs): read image metadata off the wire, not node:fs
moneyacademyKE Oct 5, 2026
c99433e
ci(release): build rpm packages alongside deb and AppImage
moneyacademyKE Oct 6, 2026
8ce3e2a
ci(release): build the rpm inside an EL 10 container, not on Ubuntu
moneyacademyKE Oct 6, 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
138 changes: 138 additions & 0 deletions .github/workflows/release-cross.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: Cross release (fork)

on:
workflow_dispatch:
push:
tags:
- "v*-opencrabs.*"

permissions:
contents: read

jobs:
linux:
name: Linux packages
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- uses: dtolnay/rust-toolchain@stable

- name: Install Linux system dependencies
run: bash scripts/install-linux-deps-debian.sh

- run: npm ci

- name: Build Linux packages
run: |
set -euo pipefail
printf '%s\n' '{"bundle":{"createUpdaterArtifacts":false}}' > "$RUNNER_TEMP/tauri.fork.conf.json"
npm run build:linux -- --config "$RUNNER_TEMP/tauri.fork.conf.json"

- name: Stage Linux packages
run: |
set -euo pipefail
shopt -s nullglob
mkdir -p release-artifacts
cp target/release/bundle/deb/*.deb release-artifacts/
cp target/release/bundle/appimage/*.AppImage release-artifacts/

- name: Upload Linux packages
uses: actions/upload-artifact@v4
with:
name: linux-packages
path: release-artifacts/
if-no-files-found: error

linux-rpm:
name: Linux rpm package
runs-on: ubuntu-latest
# Same recipe as upstream: build inside EL 10 so the rpm's glibc
# requirement matches the oldest advertised target; an Ubuntu-linked
# rpm would install on EL 10 and then fail to load.
container: almalinux:10
steps:
- name: Install git
run: dnf install -y git

- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- uses: dtolnay/rust-toolchain@stable

- name: Install Enterprise Linux system dependencies
run: bash scripts/install-linux-deps-fedora.sh

- run: npm ci

- name: Build rpm package
run: npm run build:fedora

- name: Verify and stage the rpm
run: |
set -euo pipefail
shopt -s nullglob
bundle=(target/release/bundle/rpm/*.rpm)
if (( ${#bundle[@]} != 1 )); then
echo "Expected exactly one .rpm" >&2
exit 1
fi
requires="$(rpm -qp --requires "${bundle[0]}")"
for dep in webkit2gtk4.1 gtk3 libappindicator-gtk3 librsvg2 openssl-libs; do
if ! grep -qx "$dep" <<<"$requires"; then
echo "rpm is missing Requires: $dep" >&2
exit 1
fi
done
mkdir -p release-artifacts
cp "${bundle[0]}" release-artifacts/

- name: Upload rpm package
uses: actions/upload-artifact@v4
with:
name: linux-rpm-package
path: release-artifacts/
if-no-files-found: error

windows:
name: Windows packages
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- uses: dtolnay/rust-toolchain@stable

- run: npm ci

- name: Build Windows installer
shell: pwsh
run: |
'{"bundle":{"createUpdaterArtifacts":false}}' | Set-Content -Encoding utf8 "$env:RUNNER_TEMP\tauri.fork.conf.json"
npx tauri build --bundles nsis --config "$env:RUNNER_TEMP\tauri.fork.conf.json"

- name: Stage Windows packages
shell: pwsh
run: |
New-Item -ItemType Directory -Force "release-artifacts" | Out-Null
Copy-Item "target/release/bundle/nsis/*.exe" "release-artifacts/"

- name: Upload Windows packages
uses: actions/upload-artifact@v4
with:
name: windows-packages
path: release-artifacts/
if-no-files-found: error
43 changes: 43 additions & 0 deletions src-tauri/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,19 @@ pub fn harness_resolve_antigravity() -> Result<AntigravityBinary, String> {
})
}

/// Resolve the OpenCrabs CLI (`opencrabs`).
#[tauri::command(async)]
pub fn harness_resolve_opencrabs() -> Result<CursorBinary, String> {
resolve_opencrabs()
.map(|path| CursorBinary {
path: path.to_string_lossy().into_owned(),
})
.ok_or_else(|| {
"OpenCrabs CLI not found. Install OpenCrabs from https://github.com/adolfousier/opencrabs, then retry."
.into()
})
}

/// Bind an ephemeral loopback port for `opencode serve`.
#[tauri::command]
pub fn harness_free_port() -> Result<u16, String> {
Expand Down Expand Up @@ -1916,6 +1929,7 @@ fn resolve_harness_binary_default(provider: &str) -> Option<PathBuf> {
"fx" => resolve_fx(),
"hermes" => resolve_hermes(),
"antigravity" => resolve_antigravity(),
"opencrabs" => resolve_opencrabs(),
_ => None,
}
}
Expand Down Expand Up @@ -2344,6 +2358,35 @@ fn resolve_antigravity() -> Option<PathBuf> {
first_binary(candidates)
}

fn resolve_opencrabs() -> Option<PathBuf> {
let home = dirs_home().map(PathBuf::from);
let mut candidates: Vec<PathBuf> = Vec::new();

if let Some(home) = &home {
candidates.push(home.join(".opencrabs/bin/opencrabs"));
candidates.push(home.join(".local/bin/opencrabs"));
candidates.push(home.join(".cargo/bin/opencrabs"));
candidates.push(home.join(".npm-global/bin/opencrabs"));
candidates.push(home.join("n/bin/opencrabs"));
}
#[cfg(target_os = "macos")]
candidates.push(PathBuf::from("/opt/homebrew/bin/opencrabs"));
candidates.push(PathBuf::from("/usr/local/bin/opencrabs"));
candidates.push(PathBuf::from("/usr/bin/opencrabs"));
candidates.push(PathBuf::from("/snap/bin/opencrabs"));
if let Some(from_shell) = which_via_login_shell("opencrabs") {
candidates.push(from_shell);
}

first_binary_matching(candidates, is_opencrabs_binary)
}

/// No same-named collision is known for `opencrabs`; an executable with the
/// right name is the agent CLI.
fn is_opencrabs_binary(path: &Path) -> bool {
binary_name_eq(path, "opencrabs")
}

fn is_pi_coding_agent(path: &Path) -> bool {
if !path.is_file() {
return false;
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ pub fn run() {
harness::harness_resolve_grok,
harness::harness_resolve_hermes,
harness::harness_resolve_antigravity,
harness::harness_resolve_opencrabs,
harness::harness_free_port,
harness::harness_spawn,
harness::harness_write,
Expand Down
20 changes: 20 additions & 0 deletions src/assets/providers/opencrabs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/features/sessions/model/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ function fallbackName(mimeType: string): string {
return "attachment";
}

function fileUri(path: string): string {
export function fileUri(path: string): string {
const normalized = path.replace(/\\/g, "/");
const abs = normalized.startsWith("/") ? normalized : `/${normalized}`;
return `file://${abs.split("/").map(encodeURIComponent).join("/")}`;
Expand Down
8 changes: 8 additions & 0 deletions src/features/sessions/model/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ export const MODELS: AgentModel[] = [
name: "Gemini 3.8 Flash (High)",
nativeId: "gemini-3.8-flash-high",
},
{
id: "opencrabs:default",
harness: "opencrabs",
name: "Configured model",
nativeId: "",
},
];

export const DEFAULT_MODEL_ID: Record<HarnessId, string> = {
Expand All @@ -211,6 +217,7 @@ export const DEFAULT_MODEL_ID: Record<HarnessId, string> = {
fx: "fx:zai/glm-5.2-fast",
hermes: "hermes:default",
antigravity: "antigravity:gemini-3.8-flash-high",
opencrabs: "opencrabs:default",
};

const FAVORITES_KEY = "monocode.favoriteModels";
Expand Down Expand Up @@ -240,6 +247,7 @@ const HARNESS_ORDER: HarnessId[] = [
"fx",
"hermes",
"antigravity",
"opencrabs",
];

const EMPTY_MODELS: AgentModel[] = [];
Expand Down
6 changes: 5 additions & 1 deletion src/features/sessions/model/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export type HarnessId =
| "omp"
| "fx"
| "hermes"
| "antigravity";
| "antigravity"
| "opencrabs";

export const HARNESSES: HarnessId[] = [
"claude",
Expand All @@ -38,6 +39,7 @@ export const HARNESSES: HarnessId[] = [
"fx",
"hermes",
"antigravity",
"opencrabs",
];

export type BlockRole =
Expand Down Expand Up @@ -536,6 +538,7 @@ export const HARNESS_LABEL: Record<HarnessId, string> = {
fx: "fx",
hermes: "hermes",
antigravity: "antigravity",
opencrabs: "opencrabs",
};

export const HARNESS_TITLE: Record<HarnessId, string> = {
Expand All @@ -549,6 +552,7 @@ export const HARNESS_TITLE: Record<HarnessId, string> = {
fx: "fx",
hermes: "Hermes Agent",
antigravity: "Antigravity",
opencrabs: "OpenCrabs",
};

/** fx ACP rejects attachment prompt blocks. */
Expand Down
2 changes: 2 additions & 0 deletions src/features/sessions/ui/HarnessIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import omp from "../../../assets/providers/omp.svg";
import opencode from "../../../assets/providers/opencode.svg";
import pi from "../../../assets/providers/pi.svg";
import antigravity from "../../../assets/providers/antigravity.svg";
import opencrabs from "../../../assets/providers/opencrabs.svg";
import type { HarnessId } from "../model/session";

export const HARNESS_ICONS: Record<HarnessId, string> = {
Expand All @@ -22,6 +23,7 @@ export const HARNESS_ICONS: Record<HarnessId, string> = {
fx,
hermes,
antigravity,
opencrabs,
};

/** White marks that must follow `currentColor` so they stay visible in light mode. */
Expand Down
13 changes: 13 additions & 0 deletions src/integrations/harness/core/availability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
resolveHermesBinary,
resolveOmpBinary,
resolveOpenCodeBinary,
resolveOpenCrabsBinary,
resolvePiBinary,
} from "./child";
import { isLiveHarness } from "./registry";
Expand Down Expand Up @@ -51,6 +52,10 @@ const CLI: Record<HarnessId, { name: string; install?: string }> = {
"Install from hermes-agent.nousresearch.com, then run hermes model",
},
antigravity: { name: "Antigravity ACP server (agy_acp_server.par)" },
opencrabs: {
name: "OpenCrabs CLI",
install: "cargo install --git https://github.com/adolfousier/opencrabs",
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

let inflight: Promise<void> | null = null;
Expand Down Expand Up @@ -160,6 +165,14 @@ export function probeHarnessAvailability(
return [id, false] as const;
}
}
if (id === "opencrabs") {
try {
await resolveOpenCrabsBinary();
return [id, true] as const;
} catch {
return [id, false] as const;
}
}
return [id, false] as const;
}),
)
Expand Down
1 change: 1 addition & 0 deletions src/integrations/harness/core/availabilityState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ let availability: HarnessAvailability = {
fx: false,
hermes: false,
antigravity: false,
opencrabs: false,
};
let version = 0;
let probedAt = 0;
Expand Down
5 changes: 5 additions & 0 deletions src/integrations/harness/core/child.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ async function resolveHarnessBinary(
fx: "harness_resolve_fx",
hermes: "harness_resolve_hermes",
antigravity: "harness_resolve_antigravity",
opencrabs: "harness_resolve_opencrabs",
};
return invoke(command[provider]);
}
Expand Down Expand Up @@ -444,6 +445,10 @@ export function resolveAntigravityBinary(
}>;
}

export function resolveOpenCrabsBinary(): Promise<{ path: string }> {
return invoke("harness_resolve_opencrabs");
}

export function freeHarnessPort(): Promise<number> {
return invoke("harness_free_port");
}
Expand Down
2 changes: 2 additions & 0 deletions src/integrations/harness/core/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ensureOpenCodeRegistered } from "../providers/opencode/opencodeAdapter"
import { ensureOmpRegistered } from "../providers/omp/ompAdapter";
import { ensurePiRegistered } from "../providers/pi/piAdapter";
import { ensureAntigravityRegistered } from "../providers/antigravity/antigravityAdapter";
import { ensureOpenCrabsRegistered } from "../providers/opencrabs/opencrabsAdapter";

/** Register all known live harness adapters. Idempotent. */
export function registerBuiltinHarnesses(): void {
Expand All @@ -21,4 +22,5 @@ export function registerBuiltinHarnesses(): void {
ensureFxRegistered();
ensureHermesRegistered();
ensureAntigravityRegistered();
ensureOpenCrabsRegistered();
}
Loading