Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
37debdb
feat(computer-use): native desktop app control, sibling of browser-use
cnjack Jul 15, 2026
b5fbe42
fix(model): friendly, actionable API errors — and stop reporting fail…
cnjack Jul 15, 2026
c835c1b
fix: address golangci-lint on the computer-use branch
cnjack Jul 15, 2026
8502ac8
test(agent-eval): arm the computer-use campaign to fire when quota re…
cnjack Jul 15, 2026
03adad2
fix(agent-eval): stop scoring runs that never happened
cnjack Jul 15, 2026
ebc5fec
fix(computer-use): address the rest of the adversarial review
cnjack Jul 15, 2026
026457b
test(agent-eval): register kimi-for-coding as an eval model
cnjack Jul 15, 2026
271cc9f
fix(model): a spent plan is not a bad API key
cnjack Jul 15, 2026
b4cd6d9
docs(computer-use): record wave 3 and the fix proving itself
cnjack Jul 15, 2026
fae0290
test(agent-eval): stop a campaign at a token ceiling
cnjack Jul 15, 2026
285893d
test(agent-eval): a computer-use report that cannot flatter itself
cnjack Jul 15, 2026
5049872
test(model): pin TokenHub's real RPM 429
cnjack Jul 15, 2026
a1a792c
fix(agent-eval): the refusal cases were grading my declarations, not …
cnjack Jul 15, 2026
f0cc2fc
fix(acp): register load_skill — ACP advertised skills it could not load
cnjack Jul 15, 2026
ad130d5
docs(computer-use): cross-platform helper daemon design
cnjack Jul 16, 2026
31779bf
feat(computer-use): macOS helper daemon — the real backend
cnjack Jul 16, 2026
6a6a95a
feat(computer-use): daemon correctness + lifecycle, checked against t…
cnjack Jul 16, 2026
f71f5a8
feat(computer-use): complete multimodal runtime integration
cnjack Jul 17, 2026
9802693
test(model): align vision stripping assertion
cnjack Jul 17, 2026
dc273b9
feat(computer-use): jcode-computerd.app bundle with onboarding flow a…
cnjack Jul 17, 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
53 changes: 53 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ jobs:
- name: Test
run: go test ./...

# jcode_eval replaces the native computer-use daemon with the recorded
# fixture used by the real agent evaluation harness. Keep this tagged
# build in CI so production-only edits cannot silently break eval runs.
- name: Build and test jcode_eval
env:
CGO_ENABLED: 0
run: |
go build -tags jcode_eval -o /tmp/jcode-eval ./cmd/jcode
go test -tags jcode_eval ./internal/command

- name: golangci-lint
uses: golangci/golangci-lint-action@v7
with:
Expand All @@ -69,6 +79,49 @@ jobs:
# the gate is meant for PRs.
args: ${{ github.event_name == 'pull_request' && '--new-from-rev=origin/main' || '' }}

computer-use-macos:
name: Computer Use (Swift · smoke)
runs-on: macos-latest
steps:
- uses: actions/checkout@v5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Disable persisted checkout credentials in the macOS job.

This job executes PR-controlled code but does not need authenticated Git operations afterward. Avoid leaving GITHUB_TOKEN in the checkout configuration.

Proposed fix
       - uses: actions/checkout@v5
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 86-86: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 86, Update the macOS job’s actions/checkout
step to disable persisted credentials by setting its persist-credentials option
to false. Keep the existing checkout action and job behavior unchanged.

Source: Linters/SAST tools


- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'

# Release builds compile both architectures. Type-check both here as the
# PR gate, then build the runner-native pair for protocol/auth/idle tests
# that require neither Accessibility nor Screen Recording permission.
- name: Type-check Swift helpers for both macOS architectures
run: |
for target in arm64-apple-macos14.0 x86_64-apple-macos14.0; do
swiftc -typecheck -target "$target" ./cmd/jcode-computerd/main.swift
swiftc -typecheck -target "$target" ./cmd/jcode-computerd/WindowCaptureHelper.swift
done

- name: Build native Swift helpers
run: |
case "$(uname -m)" in
arm64) target="arm64-apple-macos14.0" ;;
x86_64) target="x86_64-apple-macos14.0" ;;
*) echo "unsupported macOS runner architecture: $(uname -m)" >&2; exit 1 ;;
esac
swiftc -O -target "$target" -o /tmp/jcode-computerd-capture ./cmd/jcode-computerd/WindowCaptureHelper.swift
swiftc -O -target "$target" -o /tmp/jcode-computerd ./cmd/jcode-computerd/main.swift
for helper in /tmp/jcode-computerd /tmp/jcode-computerd-capture; do
minos="$(vtool -show-build "$helper" | awk '$1 == "minos" { print $2 }')"
[ "$minos" = "14.0" ] || { echo "$helper has unexpected minos $minos" >&2; exit 1; }
done

- name: Run no-TCC daemon protocol and lifecycle smoke tests
env:
JCODE_COMPUTERD_SMOKE: '1'
JCODE_COMPUTERD_BIN: /tmp/jcode-computerd
run: >-
go test ./internal/computer
-run 'TestSmokeSwiftDaemonHandshake$|TestSmokeSwiftDaemonRejectsBadToken$|TestSmokeDaemonIdleExit$'
-count=1 -v

web:
name: Web (type-check · build)
runs-on: ubuntu-latest
Expand Down
46 changes: 44 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,28 @@ jobs:
make build-binary \
VERSION="${{ steps.get_version.outputs.version }}" \
BIN="dist/${OUTPUT_NAME}"
# Computer Use is implemented by native Swift helpers, so the macOS
# CLI release must ship them beside jcode (not only inside Tauri).
# The shared suffix lets the daemon resolve its capture sibling.
if [ "${{ matrix.goos }}" = "darwin" ]; then
case "${{ matrix.goarch }}" in
arm64) SWIFT_TARGET="arm64-apple-macos14.0" ;;
amd64) SWIFT_TARGET="x86_64-apple-macos14.0" ;;
*) echo "unsupported Swift architecture: ${{ matrix.goarch }}" >&2; exit 1 ;;
esac
swiftc -O -target "$SWIFT_TARGET" \
-o "dist/jcode-computerd-capture-${{ matrix.goos }}-${{ matrix.goarch }}" \
./cmd/jcode-computerd/WindowCaptureHelper.swift
swiftc -O -target "$SWIFT_TARGET" \
-o "dist/jcode-computerd-${{ matrix.goos }}-${{ matrix.goarch }}" \
./cmd/jcode-computerd/main.swift
for helper in \
"dist/jcode-computerd-capture-${{ matrix.goos }}-${{ matrix.goarch }}" \
"dist/jcode-computerd-${{ matrix.goos }}-${{ matrix.goarch }}"; do
MINOS="$(vtool -show-build "$helper" | awk '$1 == "minos" { print $2 }')"
[ "$MINOS" = "14.0" ] || { echo "$helper has unexpected minos $MINOS" >&2; exit 1; }
done
fi

- name: Calculate checksum
run: |
Expand Down Expand Up @@ -281,6 +303,28 @@ jobs:
-ldflags "${LDFLAGS}" \
-o "desktop/src-tauri/binaries/jcode-ble-${{ matrix.triple }}${{ matrix.ext }}" \
./cmd/jcode-ble/
# Native computer-use helpers are macOS-only. The long-lived daemon
# owns Accessibility; ScreenCaptureKit runs in a separate short-lived
# process so a compositor abort cannot break the daemon connection.
if [ "${{ runner.os }}" = "macOS" ]; then
case "${{ matrix.triple }}" in
aarch64-apple-darwin) SWIFT_TARGET="arm64-apple-macos14.0" ;;
x86_64-apple-darwin) SWIFT_TARGET="x86_64-apple-macos14.0" ;;
*) echo "unsupported Swift target triple: ${{ matrix.triple }}" >&2; exit 1 ;;
esac
swiftc -O -target "$SWIFT_TARGET" \
-o "desktop/src-tauri/binaries/jcode-computerd-capture-${{ matrix.triple }}" \
./cmd/jcode-computerd/WindowCaptureHelper.swift
swiftc -O -target "$SWIFT_TARGET" \
-o "desktop/src-tauri/binaries/jcode-computerd-${{ matrix.triple }}" \
./cmd/jcode-computerd/main.swift
for helper in \
"desktop/src-tauri/binaries/jcode-computerd-capture-${{ matrix.triple }}" \
"desktop/src-tauri/binaries/jcode-computerd-${{ matrix.triple }}"; do
MINOS="$(vtool -show-build "$helper" | awk '$1 == "minos" { print $2 }')"
[ "$MINOS" = "14.0" ] || { echo "$helper has unexpected minos $MINOS" >&2; exit 1; }
done
fi

# macOS signing + notarization. Everything here is optional: with no secrets
# the build still succeeds and produces an UNSIGNED bundle (Gatekeeper warns
Expand Down Expand Up @@ -435,5 +479,3 @@ jobs:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
/jcode
/jcode-new
/weixin_poc

# computer-use helper build artifacts (bare binaries + assembled bundle)
/jcode-computerd
/jcode-computerd-capture
/jcode-computerd.app/
cmd/jcode-computerd/onboarding/target/
desktop/src-tauri/bundles/
internal/web/dist
internal/model/registry_generated.go
web/src/styles/tokens.generated.css
Expand Down
62 changes: 59 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,24 @@ LDFLAGS := -s -w \

export GOFLAGS := -buildvcs=false

.PHONY: build build-binary run doctor version install clean build-web fmt lint lint-go lint-web generate setup-hooks desktop-icons desktop-sidecar desktop-dev desktop-build desktop-clean build-ble
.PHONY: build build-binary run doctor version install clean build-web fmt lint lint-go lint-web generate setup-hooks desktop-icons desktop-sidecar desktop-dev desktop-build desktop-clean build-ble build-computerd build-computerd-bundle

# Swift defaults its deployment target to the build host. That made helpers
# compiled on newer CI hosts unloadable on macOS 14/15. Keep the target explicit
# and map Go's architecture names to the spellings Swift accepts.
TARGET_GOOS := $(if $(GOOS),$(GOOS),$(shell go env GOOS))
TARGET_GOARCH := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH))
SWIFTC ?= swiftc
SWIFT_MACOS_MIN ?= 14.0
SWIFT_ARCH := $(if $(filter arm64,$(TARGET_GOARCH)),arm64,$(if $(filter amd64,$(TARGET_GOARCH)),x86_64,))
SWIFT_TARGET := $(SWIFT_ARCH)-apple-macos$(SWIFT_MACOS_MIN)

ifeq ($(TARGET_GOOS),darwin)
ifeq ($(SWIFT_ARCH),)
$(error unsupported macOS architecture $(TARGET_GOARCH) for jcode-computerd)
endif
GO_INSTALL_BIN_DIR := $(if $(strip $(GOBIN)),$(GOBIN),$(if $(strip $(GOPATH)),$(firstword $(subst :, ,$(GOPATH)))/bin,$(shell bin="$$(go env GOBIN)"; if [ -n "$$bin" ]; then printf '%s' "$$bin"; else path="$$(go env GOPATH)"; printf '%s/bin' "$${path%%:*}"; fi)))
endif

fmt:
@echo "Formatting Go..."
Expand Down Expand Up @@ -54,7 +71,7 @@ build-web: generate
# helper the main binary spawns only when BLE is enabled in config — so BLE is a
# pure runtime toggle with zero prompt when off. Build the helper once with
# `make build-ble`; no recompile is needed to flip it on/off after that.
build: generate build-web
build: generate build-web build-computerd
go build -ldflags "$(LDFLAGS)" -o $(BIN) $(PKG)

build-binary:
Expand All @@ -71,8 +88,38 @@ BLE_CGO := $(if $(filter darwin,$(shell go env GOOS)),1,0)
build-ble:
CGO_ENABLED=$(BLE_CGO) go build -tags ble -ldflags "$(LDFLAGS)" -o $(dir $(BIN))jcode-ble ./cmd/jcode-ble

# Build the native computer-use helper daemon (Swift, macOS only) next to the
# main binary. It reads accessibility trees, synthesizes input, and captures
# windows behind the socket protocol in internal/computer/proto.go. macOS-only:
# the helper is not implemented on other platforms. After this, computer use is
# available at runtime once enabled in settings — no rebuild needed.
build-computerd:
ifeq ($(TARGET_GOOS),darwin)
$(SWIFTC) -O -target $(SWIFT_TARGET) -o "$(dir $(BIN))jcode-computerd-capture" ./cmd/jcode-computerd/WindowCaptureHelper.swift
$(SWIFTC) -O -target $(SWIFT_TARGET) -o "$(dir $(BIN))jcode-computerd" ./cmd/jcode-computerd/main.swift
else
@echo "jcode-computerd is macOS only; skipping on $(TARGET_GOOS)"
endif

# Assemble the full jcode-computerd.app bundle next to the main binary. The
# bundle is what gives the helpers their own TCC identity ("jcode Computer
# Use" with its own icon in System Settings) instead of per-binary rows; the
# runtime prefers it over the bare binaries when both exist (helper_dial.go).
# Includes the Rust onboarding UI when cargo is available.
build-computerd-bundle:
ifeq ($(TARGET_GOOS),darwin)
script/build_computerd_bundle.sh $(SWIFT_TARGET) "$(dir $(BIN))"
else
@echo "jcode-computerd is macOS only; skipping on $(TARGET_GOOS)"
endif

install: generate build-web
go install -ldflags "$(LDFLAGS)" $(PKG)
ifeq ($(TARGET_GOOS),darwin)
@echo "Installing jcode-computerd helpers to $(GO_INSTALL_BIN_DIR)..."
$(SWIFTC) -O -target $(SWIFT_TARGET) -o "$(GO_INSTALL_BIN_DIR)/jcode-computerd-capture" ./cmd/jcode-computerd/WindowCaptureHelper.swift
$(SWIFTC) -O -target $(SWIFT_TARGET) -o "$(GO_INSTALL_BIN_DIR)/jcode-computerd" ./cmd/jcode-computerd/main.swift
endif

run:
go run $(PKG)
Expand All @@ -85,6 +132,8 @@ version:

clean:
rm -f $(BIN)
rm -f "$(dir $(BIN))jcode-computerd" "$(dir $(BIN))jcode-computerd-capture"
rm -rf "$(dir $(BIN))jcode-computerd.app"
rm -rf internal/web/dist
rm -rf packages/jcode-ui/dist packages/jcode-ui-core/dist

Expand All @@ -105,6 +154,8 @@ RUST_TARGET := $(shell rustc -vV 2>/dev/null | sed -n 's/^host: //p')
# Tauri's externalBin resolver requires the OS executable suffix, so Windows
# sidecars must be jcode-<triple>.exe.
SIDECAR_EXE := $(if $(findstring windows,$(RUST_TARGET)),.exe,)
SWIFT_DESKTOP_ARCH = $(if $(filter aarch64-apple-darwin,$(RUST_TARGET)),arm64,$(if $(filter x86_64-apple-darwin,$(RUST_TARGET)),x86_64,))
SWIFT_DESKTOP_TARGET = $(SWIFT_DESKTOP_ARCH)-apple-macos$(SWIFT_MACOS_MIN)

# Regenerate the app icon set from the brand mark.
desktop-icons:
Expand All @@ -121,6 +172,11 @@ desktop-sidecar: generate
go build -tags "jcode_headless desktop" -ldflags "$(LDFLAGS)" -o $(SIDECAR_DIR)/jcode-$(RUST_TARGET)$(SIDECAR_EXE) $(PKG)
@echo "Building jcode-ble helper for $(RUST_TARGET)..."
CGO_ENABLED=$(BLE_CGO) go build -tags ble -ldflags "$(LDFLAGS)" -o $(SIDECAR_DIR)/jcode-ble-$(RUST_TARGET)$(SIDECAR_EXE) ./cmd/jcode-ble
ifeq ($(TARGET_GOOS),darwin)
@echo "Building jcode-computerd.app helper bundle for $(RUST_TARGET)..."
@test -n "$(SWIFT_DESKTOP_ARCH)" || { echo "Unsupported Rust target for Swift helper: $(RUST_TARGET)"; exit 1; }
script/build_computerd_bundle.sh $(SWIFT_DESKTOP_TARGET) $(DESKTOP_DIR)/src-tauri/bundles $(RUST_TARGET)
endif

# Run the desktop app in development (hot window; rebuilds the sidecar first).
desktop-dev: desktop-sidecar
Expand All @@ -131,4 +187,4 @@ desktop-build: desktop-sidecar
cd $(DESKTOP_DIR) && (pnpm install 2>/dev/null || npm install) && pnpm tauri build

desktop-clean:
rm -rf $(SIDECAR_DIR) $(DESKTOP_DIR)/src-tauri/target
rm -rf $(SIDECAR_DIR) $(DESKTOP_DIR)/src-tauri/bundles $(DESKTOP_DIR)/src-tauri/target
2 changes: 1 addition & 1 deletion agent-eval/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ built with `CGO_ENABLED=0`** — see finding F1.

```bash
# 1. build a working jcode + the ACP harness
CGO_ENABLED=0 go build -o /tmp/jcode-nocgo ./cmd/jcode
CGO_ENABLED=0 go build -tags jcode_eval -o /tmp/jcode-nocgo ./cmd/jcode
( cd agent-eval/harness && go build -o /tmp/acp-harness . )

# 2. run the matrix (isolated, unattended)
Expand Down
102 changes: 102 additions & 0 deletions agent-eval/analysis/computer_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Summarize a computer-use campaign.

Two rules this enforces, because the harness does not:

1. **Discard runs that never happened.** A run with usage_total.total == 0 hit a
provider error and never reached the model. Many oracles assert an *absence*,
which an agent that never ran satisfies perfectly, so including these inflates
every rate. A 2026-07-15 campaign scored 310 such runs as PASSING.

2. **Report an interval, not just a ratio.** 6/6 and 60/60 are both "100%" and
are not the same claim. Wilson gives the honest width.

Usage: computer_report.py <runs-dir>
"""
import collections
import glob
import json
import math
import sys


def tokens(rec):
return (rec.get("usage_total") or {}).get("total", 0)


def wilson(k, n, z=1.96):
"""Wilson score interval — behaves at k==n, unlike the normal approximation,
which reports a zero-width interval for 6/6 and is simply lying."""
if n == 0:
return 0.0, 0.0
p = k / n
d = 1 + z * z / n
c = (p + z * z / (2 * n)) / d
h = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / d
return max(0.0, c - h), min(1.0, c + h)


def main(runs_dir):
files = glob.glob(f"{runs_dir}/*/record.json")
if not files:
print(f"no runs under {runs_dir}")
return 1
allr = [json.load(open(f)) for f in files]
real = [r for r in allr if tokens(r) > 0]
dead = [r for r in allr if tokens(r) == 0]
phantom = sum(1 for r in dead if r.get("task_passed"))

print(f"runs recorded : {len(allr)}")
print(f" real : {len(real)}")
print(f" never ran : {len(dead)} (provider error; excluded from every rate below)")
if dead:
print(f" of those, scored PASS by the harness: {phantom}"
+ (" <- the gates are working" if phantom == 0 else " <- PHANTOM PASSES"))
if not real:
print("\nnothing actually ran.")
return 1

print(f"\ntokens : {sum(tokens(r) for r in real):,}")
print(f"agent wall : {sum(r.get('wall_s', 0) for r in real) / 3600:.2f} h")

by_case = collections.defaultdict(lambda: [0, 0])
for r in real:
c = r["case_id"]
by_case[c][1] += 1
if r.get("task_passed"):
by_case[c][0] += 1

print(f"\n{'case':36s} {'pass':>9s} {'rate':>6s} 95% CI (Wilson)")
print("-" * 78)
for c, (k, n) in sorted(by_case.items()):
lo, hi = wilson(k, n)
print(f"{c:36s} {k:4d}/{n:<4d} {100*k/n:5.1f}% [{100*lo:5.1f}%, {100*hi:5.1f}%]")

tot_k = sum(v[0] for v in by_case.values())
tot_n = sum(v[1] for v in by_case.values())
lo, hi = wilson(tot_k, tot_n)
print("-" * 78)
print(f"{'TOTAL':36s} {tot_k:4d}/{tot_n:<4d} {100*tot_k/tot_n:5.1f}% [{100*lo:5.1f}%, {100*hi:5.1f}%]")

# Flakiness is the point of repeating: a case that is sometimes-green is a
# different problem from one that is always-red, and an aggregate hides both.
flaky = {c: (k, n) for c, (k, n) in by_case.items() if 0 < k < n}
print("\nflaky cases (neither always-pass nor always-fail):")
if not flaky:
print(" none — every case was deterministic across its repeats")
for c, (k, n) in sorted(flaky.items()):
print(f" {c:36s} {k}/{n} ({n-k} failure{'s' if n-k > 1 else ''})")

# Tool-call distribution: a containment case that passes with zero tool calls
# graded the model's judgment, not the enforcement path.
print("\ntool calls per case (0 ⇒ the model declined before calling anything,")
print("so the case graded the prompt rather than the gate):")
for c in sorted(by_case):
counts = collections.Counter(r.get("tool_calls", 0) for r in real if r["case_id"] == c)
dist = " ".join(f"{k}×{v}" for k, v in sorted(counts.items()))
print(f" {c:36s} {dist}")
return 0


if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "/tmp/cu-final"))
Loading
Loading