v2.1.0 β Give cloud AI agents hands on your local machine β hardened, modular, injection-proof β plus an orchestrator for the local coding agents you already use
This bridge exists for cloud-side AI agents that speak MCP but have no access to your machine β platforms where the agent lives in someone else's product and reaches out through an MCP connector:
- Notion Custom Agents (the original use case β a scheduled agent driving a task board)
- Claude.ai custom connectors (web/desktop/mobile, not Claude Code)
- ChatGPT with MCP connectors / custom GPTs
- Microsoft Copilot Studio agents
- Mistral Le Chat connectors, Zapier/agent-builder platforms, or anything else that can call a remote MCP server
It is not needed by AI tools that already run natively on your machine β Claude Code, OpenAI Codex CLI, Gemini CLI / Antigravity, GitHub Copilot CLI, opencode, Cursor, Aider β those have their own local access. Here they play a different role: they're the workforce this bridge orchestrates.
The control surface is pluggable too. The bridge knows nothing about any particular board: a dispatcher β Notion custom agent, an Obsidian plugin, a cron script, any MCP client β supplies which repo, which local agent, which brief through the MCP tools, and the bridge does the rest. Notion is the worked example throughout these docs, not a dependency: the bridge is a generic build executor whose entire vocabulary is directory + agent + mode + prompt + jobId β grep -ri notion src/ returns nothing. Whatever holds your queue (a Notion board, an Obsidian vault, a cron script) polls and dispatches through the six MCP tools.
- Hands β 19 master tools give a cloud agent direct control of the machine: terminal, filesystem, git, macOS system control, browser automation, media processing, documents, networking. The cloud agent is the brain; these tools are the hands.
- Orchestrator β the build bridge (
plan/start/get_status/answer/cancel/merge) turns those local coding agents into dispatchable workers. A cloud agent doesn't have to write code through raw tool calls β it hands the task to Claude Code, Codex, opencode, or any CLI in the registry, and the bridge runs the whole engagement: isolated worktree, build, verify, local commit, human-gated delivery (merge to main on owned repos, a real PR on anyone else's), revert window.
Example β end-to-end development from a task board: a Notion Custom Agent (mine is named Dispatcher Assistant) wakes on a schedule, reads project tasks from the board, plans them (plan), dispatches each to the coding agent named on the card (start), posts progress and failures back as board comments (get_status), and β after a human approves β merges to main (merge). One agent in Notion, every coding CLI on your machine, a full development cycle with two human gates and no terminal in sight.
Notion board ββ> Dispatcher Assistant (cloud, MCP connector)
β ngrok HTTPS + Bearer token
βΌ
Computer Access MCP (this repo, local)
ββ hands: fs / shell / git / browser / macOS β¦
ββ orchestrator: Build Board bridge
ββ claude-code ββ
ββ codex ββ isolated worktrees β branch β PR β gated merge
ββ opencode β¦ ββ
- Build Bridge β
plan/start/get_status/answer/cancel/merge: durable, dispatcher-agnostic coding-agent orchestration (SQLite job store, FIFO queue, isolated git worktrees, question relay, heartbeat crash detection, local-only builds with human-gated delivery:--no-ffmerges on owned repos, real PRs on anyone else's) - 8 Master Tools β Consolidated tool architecture for deep machine control
- Multi-Agent Parallelism β Session-isolated state allows multiple AI models to work together
- 19 Master Tools β Consolidated tool architecture for deep machine control
- Two transports β Legacy SSE (
/sse) and modern Streamable HTTP (/mcp) - Multi-Agent Parallelism β Session-isolated state lets multiple models work together
- Ngrok HTTPS Tunneling β Auto-tunnels localhost for cloud agent access
- Ripgrep-Powered Search β Fast recursive search with graceful
grep/fast-globfallback - Session-Aware Auditing β Every action logged (with rotation) with session ID and CWD
- Safety & Guardrails β Path sandboxing (symlink-aware), shell-free execution, feature toggles, kill switch
- Node.js >= 18
- ngrok account (free tier works) β dashboard.ngrok.com
npm installcp .env.example .env
# Edit .env with your NGROK_AUTHTOKEN, BRIDGE_AUTH_TOKEN, and ALLOWED_DIRSMCP_TOKEN is required β the server refuses to open a tunnel without it (override for trusted local testing only via ALLOW_UNAUTHENTICATED_TUNNEL=true).
npm start # build + boot server + ngrok tunnel
npm test # run the unit tests (vitest)
npm run lint # eslint
npm run typechecknpm start prints two connection URLs β paste the one your agent supports:
- Streamable HTTP β
<url>/mcp(preferred for new clients) - Legacy SSE β
<url>/sse
Each tool is action-dispatched: pass an action plus the fields that action needs.
| Tool | Purpose | Key actions |
|---|---|---|
fs-manage |
Filesystem | read, read-media, batch-read, write, smart-edit, patch, list, list-with-sizes, tree, file-info, move, copy, delete, mkdir |
fs-search |
Codebase search | regex-search, file-search, code-definitions |
sys-manage |
System & macOS | exec, info, ps-list, ps-kill, list-apps, open-app/url/file, clipboard-read/write, screenshot, notification, say, volume, brightness, caffeinate, lock-screen, active-app, window-list, test-run, lint |
git-manage |
Version control | status, add, commit, push, pull, branch, log, diff, stash, merge, tag, raw |
media-manage |
FFmpeg/FFprobe | transcode, convert-image, extract-audio, metadata |
doc-manage |
Documents | pdf, docx, spreadsheet, csv, markdown-preview |
browser-manage |
Headless browser | navigate, click, type, get-text, get-html, screenshot-page, pdf, evaluate, wait |
net-manage |
Network | http-request, download, web-search, port-check |
task-manage |
Background jobs | run, status, logs, cancel, list |
watch-manage |
FS watch / log tail | watch, poll, unwatch, list-watchers, tail-log |
secret-manage |
macOS Keychain (off by default) | get, set, delete, list |
archive-manage |
Archives | zip, unzip, tar, untar, list-contents |
db-manage |
SQLite | query, execute, schema, list-tables |
diff-manage |
Diff & patch | file-diff, dir-diff, apply-patch, three-way-merge |
code-format |
Formatters | format, check, list-formatters |
test-manage |
Structured tests | run, run-file, coverage |
audit-manage |
Audit log explorer | tail, search, stats, session-history |
env-manage |
.env files |
read, set, unset, validate, diff |
window-manage |
macOS windows | list, focus, resize, move, screenshot-window, applescript |
Six MCP tools that let any dispatcher (a Notion custom agent, an Obsidian plugin, a cron script β anything that speaks MCP) drive local coding-agent CLIs β the orchestrator role described above. The bridge's entire vocabulary is jobId + repoPath + agent + mode + prompt; it knows nothing about whichever tool dispatches the work. Every response uses the generic states planning Β· planned Β· queued Β· running Β· awaiting_input Β· paused Β· in_review Β· failed Β· merged Β· cancelled. State is durable (SQLite at data/jobs.sqlite + per-job logs at data/logs/<jobId>.log) and survives restarts.
| Tool | Description |
|---|---|
plan |
Dispatch a READ-ONLY plan job (no branch, no worktree, no writes) and return immediately β a slow plan never hangs the caller. Every provider plans in its cheapest viable mode (planMode in the registry): headless (flags, plan on stdout), oneshot (plan captured from a generated file like plan.md, artifact cleaned so the repo stays pristine), or interactive (the CLI's TUI driven through a pty β mode toggles, slash-commands, the brief). Thin/empty one-shot plans (< PLAN_MIN_CHARS) escalate to an interactive session for the same provider, and complex: true skips straight there. Plans are jobs in the same store as builds (state planning β planned, result in plan) sharing the concurrency pool, heartbeat/stale-kill, a PLAN_TIMEOUT_MS ceiling, and orphan-on-restart recovery. PLAN_FALLBACK_AGENT is last-resort only β it plans when a provider's mode can't be driven at all (pty failure, unknown flags, no mode), attributed in the plan text; the job's own agent always does the build. planUnsupported only when neither the provider nor a valid fallback can plan. |
answer |
Relay a human's reply into a job paused on awaiting_input. A live interactive session gets the answer written straight into its pty (the session continues); a dead session (or one lost to a bridge restart, or a headless question) gets the answer folded into the brief and the job cleanly re-runs. Loop until planned. |
start |
Dispatch a build: creates branch job/<jobId> (or a caller-supplied branch) in an isolated worktree under WORKTREE_ROOT, runs the agent CLI asynchronously, returns immediately. On success: verify β local commit only β in_review with a diffStat. Nothing leaves the machine at build time β no push, no PR (localOnly: true); the work is reviewed from the diff and delivered by merge after human approval. Structured returns: alreadyRunning (idempotent on jobId), queued: true (capacity full β waits in a durable FIFO queue and starts itself when a slot frees), invalidRepo (exact reason, never a guessed path), and awaiting_input when the directory has no git repo β the bridge asks permission and answer("yes") runs git init and continues. Empty diff after a build β in_review with an empty diffStat so the caller sees nothing changed. Re-dispatch of a failed/in_review/paused/cancelled job re-runs on the same branch/worktree. |
get_status |
Always an array: one job (with log excerpt) or the 50 most recent. Key fields: state, question (on awaiting_input), pausedReason (quota auto-retries; session/blocked wait for a human), deliveryMode (merge/pr, resolved at merge time), prUrl (pr-mode deliveries), localOnly, diffStat, plan, error, lastHeartbeat. |
cancel |
Kills a running/held job, removes the worktree, keeps the branch β later re-dispatch resumes from it. |
merge |
Deliver an approved (in_review) job. Ownership decides the mode, not push capability β resolved per repo at merge time (deliveryMode in get_status): a .bridge.json {"deliver": "merge" | "pr"} override wins; else gh decides β viewerPermission ADMIN or the authenticated gh user owning the repo β merge mode; MAINTAIN/WRITE/TRIAGE/READ/none β pr mode (a WRITE collaborator who could technically push still goes through a PR); gh missing/unauthenticated or no origin β merge mode (local merge, push skipped without origin). merge mode (owned repo): sync main (--ff-only β a diverged local main returns {error:"mainDiverged"}, never guessed), rebase the job branch onto main in the worktree (conflict β {conflict:true, conflictFiles} with the worktree intact β the dispatcher re-dispatches the agent to reconcile), --no-ff merge, push only main, delete the branch + worktree immediately (no retention window). A rejected main-push (protected branch) resets main to origin/<main> and falls back once to pr mode. pr mode (anyone else's repo): local main is never checked out or modified; the job branch is rebased onto origin/<main>, pushed (to origin, or to a fork created idempotently via gh repo fork when the viewer can't push branches), and a real PR is opened via gh with the repo's PR template filled by the job's own provider (structured Summary/Changes/Testing fallback) β {merged:true, deliveredVia:"pr", prUrl}. Branch + worktree are kept while the PR is open; the sweep polls the PR state and cleans up on MERGED/CLOSED. Refuses when tracked files have uncommitted changes (untracked junk like .DS_Store never blocks) or gh is unauthenticated in pr mode (ghUnauthenticated). action:"revert" reverts a merge-mode delivery's merge commit within REVERT_WINDOW_HOURS (works after branch deletion; operator use) β pr-mode deliveries are reverted upstream. |
The bridge runs forever as a per-user launchd service β starts at login, auto-restarts on crash β with sleep/wake resilience built to how macOS actually behaves: sleep freezes the process and it resumes on wake (no relaunch needed), so the bridge (a) holds a caffeinate power assertion only while jobs are running, (b) detects wake via a monotonic-clock gap and immediately runs tunnel check β reconcile in-flight jobs β drain the queue.
npm run build && ./scripts/install-service.sh # bridge (+ ngrok tunnel service if NGROK_DOMAIN set)
./scripts/buildboard status|logs|stop|start # stop = real stop (bootout); KeepAlive won't fight you
./scripts/uninstall-service.sh- Stable URL: ngrok runs as its own KeepAlive service (
com.buildboard.tunnel) on your reserved domain, so the MCP URL registered in your dispatcher never changes; the bridge monitors it via the ngrok local API and reports it in/statusβ a tunnel blip never crashes anything. - Durable FIFO queue: dispatches beyond
MAX_CONCURRENT_JOBSwait in SQLite (state: queued) and are drained at boot, by the sweep, and by the wake routine β jobs queued while the Mac slept start within seconds of wake, and the dispatcher never has to re-callstart. - Recovery, not routine resume: on bridge start, non-terminal jobs are triaged β provider pid still alive β reattach (pid-liveness becomes the heartbeat; the pipeline finishes when it exits); pid dead β
RESUME_STRATEGY:resume(provider session viaresumeArgsTemplateβ claude/opencode/grok/agy--continue, worktree-scoped),rerun(original brief, same worktree), orrework(fail with a note). - Service logs live in
~/.bridge/logs/with copy-truncate rotation at 10 MB.
Per-job Mode (mode param β build posture): auto (default, configurable via DEFAULT_MODE) runs the agent in its skip-permissions posture β full autonomy, installs allowed. accept_edits runs the agent's buildAcceptEditsArgs posture under a pty: file edits auto-apply, but higher-risk actions (shell/installs/deletes/network) pause the CLI β the bridge captures the exact prompt, parks the job in awaiting_input, and answer feeds the human's reply back into the live session. Both postures are data in providers.json.
Package installs are user-authorized by design (ALLOW_PACKAGE_INSTALLS, default true): these are your own repos, so npm/pnpm/yarn/bun/pip/cargo/brew install run freely inside allowlisted repo paths β nothing classifies them as dangerous. Setting it false refuses install commands on the bridge's exec surfaces (verifyCommand, sys-manage exec, task-manage); the allowlist and prompt-injection scoping are unchanged either way.
Launch-context-proof: the bridge augments its PATH at startup with the well-known agent install dirs (~/.local/bin, ~/.claude/local, ~/.opencode/bin, homebrew, β¦), so providers resolve identically whether it was launched from a login shell, an IDE terminal, or launchd β and a boot agent preflight line reports exactly where each configured CLI resolves (or that it's missing) so a broken agent shows up at startup, not as a failed job.
Optional pty upgrade: interactive/supervised sessions ship on a dependency-free
expect(1)relay. If you want the more robust native pty, runnpm install node-ptyyourself β the bridge auto-detects and prefers it. The bridge never installs it for you.
Hold & auto-retry: provider output is classified on failure β rate-limit/quota β paused(quota) and the sweep auto-retries after HOLD_RETRY_MS, restoring the previous state; auth/session errors β paused(session) (human re-login needed); a trailing question β awaiting_input with the question captured. Builds never push, so push failures can only happen at merge time β where they surface as structured results (mainDiverged, the once-only pr fallback, ghUnauthenticated), never a silent success.
Providers are data, not code β providers.json maps an agent name to {command, buildAutoArgs, buildAcceptEditsArgs, planMode, planArgs, resumeArgsTemplate, promptVia} with {brief}/{workspace}/{nudge} placeholders. Shipped: claude-code, codex, opencode, antigravity-agy, grok-build, github-copilot-cli β each with auto, accept-edits, and plan postures. Adding a provider = one JSON entry (see the _TODO notes in the file for what's verified vs. per-spec).
β οΈ Unattended-run flags: the shipped entries run fully auto-approved (claude --dangerously-skip-permissions,codex exec --dangerously-bypass-approvals-and-sandbox) β otherwise a bash prompt or sandbox denial stalls/fails the job until the stale-kill fires. Containment comes from the bridge, not the CLI: allowlisted repos (ALLOWED_DIRS), isolated worktrees, two human gates before main, and the revert window. To tighten codex later, swap the flag for--sandbox workspace-write(note: its sandbox blocks network, so dep installs fail).opencode runis non-interactive with allow-by-default permissions; if your opencode config sets permissions to "ask", switch them to "allow" for headless use.
Crash safety: heartbeat bumps on every output chunk (stdout and stderr); jobs silent past HEARTBEAT_TIMEOUT_MS or running past JOB_MAX_RUNTIME_MS are killed and marked failed; jobs interrupted by a bridge restart are marked failed on boot.
Verification: verifyCommand param > .bridge.json {"verifyCommand": "..."} in the repo > skip. A failing verify commits the work locally (not pushed) and fails the task for Rework.
Per-repo config (.bridge.json in the repo root): {"verifyCommand": "...", "deliver": "merge" | "pr"} β the optional deliver key overrides the delivery-mode resolution for that repo: "merge" forces a direct main merge, "pr" forces PR delivery regardless of ownership.
- Path isolation (symlink-aware) β All file operations are validated against
ALLOWED_DIRS. Paths are fully symlink-resolved, so a symlink inside an allowed directory that points outside it is rejected. - Shell-free execution β Tools invoke binaries via
execFilewith explicit argument vectors, so filenames/queries containing shell metacharacters cannot inject commands. - Bearer token auth β Every MCP request requires
MCP_TOKENin theAuthorizationheader (constant-time comparison; never accepted as a query parameter). - Kill switch β Create
~/.mcp_killto block all tool calls (GET and POST) with a 503 until removed. - Feature toggles β Disable write, shell, git, media, browser, net, db, or keychain independently.
- Confirmation gate β With
ENABLE_CONFIRMATION_GATE=true, actions marked dangerous must be re-called withconfirm:true. - Tool allowlist β
TOOLS=fs-manage,fs-searchregisters only those tools (least privilege). - Audit logs β Every tool execution is recorded in
audit.log(JSONL, auto-rotated) with session ID.
β οΈ Honest limitation: the command blocklist (rm -rf /,curl β¦ | sh, β¦) is a tripwire, not a boundary. OnceENABLE_RUN_COMMANDis on, thesys-manage execandtask-managetools can run anything your user account can, anywhere on disk βALLOWED_DIRSonly sandboxes the structured file tools and the command working directory. For real containment, disable shell execution or scopeTOOLS.
| Variable | Default | Description |
|---|---|---|
PORT |
8123 |
Local MCP server port (shared by server and ngrok) |
NGROK_DOMAIN |
β | Ngrok static domain (optional, for stable URLs) |
NGROK_AUTHTOKEN |
β | From dashboard.ngrok.com |
ALLOWED_DIRS |
~/Documents |
Repo allowlist. Add via --add-dir <path> / buildboard add-dir or allowed-dirs.txt (hot-reloaded); remove/edit via the file only |
COMMAND_TIMEOUT |
30000 |
Shell command timeout in ms |
BRIDGE_AUTH_TOKEN |
β | Required bearer for the internet-exposed MCP endpoint (MCP_TOKEN legacy alias; BRIDGE_ALLOW_NO_AUTH=true for local-only testing) |
CORS_ORIGINS |
β | Comma-separated allowlist; permissive if unset |
ENABLE_SECRETS |
false |
Keychain access (largest exfiltration surface) β off by default |
TOOLS |
β | Optional least-privilege allowlist: only register these tools |
MAX_EXEC_BUFFER |
64 MB |
Child-process stdout/stderr buffer cap |
AUDIT_LOG_MAX_BYTES |
10 MB |
Audit log rotation threshold |
ENABLE_WRITE_EDIT |
true |
Allow file write/edit operations |
ENABLE_RUN_COMMAND |
true |
Allow shell command execution |
ENABLE_GIT |
true |
Allow git operations |
ENABLE_FFMPEG |
true |
Allow FFmpeg media processing |
DEFAULT_AGENT |
claude-code |
Agent used when a job doesn't name one (DEFAULT_PROVIDER legacy alias) |
WORKTREE_ROOT |
~/.bridge/worktrees |
Where task worktrees are created (outside repo trees) |
MAX_CONCURRENT_JOBS |
2 |
Parallel job cap; excess dispatches queue FIFO and start themselves |
HEARTBEAT_TIMEOUT_MS |
900000 |
Kill + fail a job with no output for this long (15 min) |
JOB_MAX_RUNTIME_MS |
7200000 |
Hard kill for any job running longer (2 h) |
REVERT_WINDOW_HOURS |
168 |
How long after a merge-mode delivery revert stays eligible (time since the merge commit). No longer implies branch retention: merge-mode branches are deleted at merge; pr-mode branches live until their PR closes |
PLAN_FALLBACK_AGENT |
claude-code |
Last-resort planner, used only when a provider's plan mode can't be driven (empty = disable) |
PLAN_MIN_CHARS |
200 |
One-shot plans shorter than this escalate to an interactive session |
PLAN_IDLE_MS |
20000 |
Interactive session idle window before question/completion detection |
PLAN_TIMEOUT_MS |
600000 |
Hard runtime ceiling for plan jobs (the plan call itself never blocks) |
HOLD_RETRY_MS |
1800000 |
Delay before a paused(quota) job auto-retries |
RESUME_STRATEGY |
resume |
Dead-job recovery: resume | rerun | rework |
WAKE_GAP_MS |
120000 |
Wall-clock gap treated as a wake from sleep |
TUNNEL_API_URL |
http://127.0.0.1:4040/api/tunnels |
ngrok local API for tunnel liveness |
ALLOW_PACKAGE_INSTALLS |
true |
User-authorized installs in allowlisted repos; false refuses them on exec surfaces |
DEFAULT_MODE |
auto |
Posture when a card has no Mode: auto | accept_edits |
ENABLE_CONFIRMATION_GATE |
false |
Break-glass: when true, merge refuses outright (autonomous merges disabled) |
computer-access/
βββ .env # Active config (secrets, gitignored)
βββ .env.example # Configuration template
βββ .github/workflows/ci.yml
βββ package.json
βββ tsconfig.json
βββ providers.json # Coding-agent CLI registry (auto/accept-edits/plan/resume postures)
βββ allowed-dirs.txt # Optional allowlist additions (gitignored, hot-reloaded)
βββ agent-instructions.md # Example dispatcher instructions (Notion custom agent)
βββ README.md
βββ data/ # Bridge state: jobs.sqlite + logs/ (gitignored)
βββ scripts/ # install-service.sh Β· uninstall-service.sh Β· buildboard CLI
βββ service/ # launchd plist templates (bridge + ngrok tunnel)
βββ tests/ # bridge-smoke.ts (184 assertions) Β· service-smoke.sh
βββ src/
βββ start.ts # Ngrok boot orchestrator (embedded-tunnel mode)
βββ server.ts # Composition root: transports (SSE + /mcp), auth, guarded registrar
βββ config.ts # Environment parsing + allowed-dirs machinery (single source of truth)
βββ security.ts # Symlink-aware sandbox, blocklists, confirmation gate
βββ audit.ts / exec.ts / runtime.ts # Rotating audit log Β· shell-free exec Β· shared state
βββ tools/ # One module per master tool (19) + bridgeTools.ts (the 6 bridge tools)
βββ bridge.ts # Job orchestration: dispatch, queue, sessions, recovery, sweeps
βββ jobs.ts # Durable SQLite job store (self-healing schema)
βββ providers.ts # Provider registry loader/validator
βββ pty.ts # Dependency-free pty layer (expect(1); node-pty auto-preferred)
βββ power.ts # caffeinate assertion, held only while jobs run
βββ wake.ts # Monotonic-clock wake detection
Workflow v2: local-only builds + delivery modes
- Nothing leaves the machine until the human approves: a finished build is committed locally on its job branch and ends
in_reviewwith adiffStat(localOnly: true) β no push, no PR at build time, ever. The build-timepaused(blocked)push-failure path is gone (builds no longer push) - Delivery happens at
mergetime, and ownership decides the mode β not push capability: repos the user owns/administers (ghviewerPermissionADMINor repo owner) β merge mode (sync main--ff-only, rebase,--no-ffmerge, push only main, delete the branch immediately); anyone else's repo β even with write/push access β β pr mode (rebase onorigin/<main>, push the branch β to origin or an idempotently-created fork β fill the repo's PR template via the job's own provider, open a real PR withgh)..bridge.json{"deliver": "merge" | "pr"}overrides per repo; resolved mode exposed asdeliveryModeinget_status - Main is sacred in both directions: on owned repos only main is ever pushed (a rejected main-push resets main to
origin/<main>and falls back once to pr mode); on others' repos local main is never even modified. Diverged local main β{error: "mainDiverged"}; rebase conflicts β{conflict: true, conflictFiles}with the worktree intact for agent-driven reconciliation; unauthenticatedghin pr mode β{error: "ghUnauthenticated"} - Branch lifecycle: merge-mode branches are deleted at merge (no retention window β
REVERT_WINDOW_HOURSnow only boundsreverteligibility, andrevertworks from the stored merge commit after deletion); pr-mode branches + worktrees are kept while the PR is open and cleaned by the sweep oncegh pr viewreports MERGED/CLOSED. The sweep never deletes branches on origin
The build bridge (dispatcher-agnostic orchestration):
- Build bridge:
plan/start/get_status/answer/cancel/mergeMCP tools β a generic, dispatcher-blind build executor (vocabulary:jobId + repoPath + agent + mode + prompt; states:planning/planned/queued/running/awaiting_input/paused/in_review/failed/merged/cancelled). All calls return immediately; nothing blocks on a slow run - Durable SQLite job store with a self-healing schema guard, FIFO queueing past the concurrency cap (drained at boot, sweep, and wake), and per-job log files
- Provider registry (
providers.json): per-agent auto / accept-edits / plan / resume postures for claude-code, codex, opencode, antigravity-agy, grok-build, github-copilot-cli; plan modes headless / oneshot (generated-file capture) / interactive (pty-driven TUI) with thin-plan escalation and last-resortPLAN_FALLBACK_AGENT - Question relay: supervised (
accept_edits) builds and interactive plans pause asawaiting_inputwith the CLI's exact prompt;answerfeeds the reply into the live pty session or cleanly re-runs. A directory without a git repo parks the job asking permission togit initβ never initialized silently - Isolated per-job git worktrees under
WORKTREE_ROOT, branchjob/<jobId>kept through a configurable revert window; empty diffs finishin_reviewwith an emptydiffStat; merge guard ignores untracked files; push failures pause with the commit kept local (localOnly) - Always-on service: launchd LaunchAgents (bridge + ngrok reserved-domain tunnel) with the
buildboardCLI, power assertion held only while jobs run, monotonic-clock wake detection β tunnel check / reconcile / queue drain, and pid-liveness recovery (reattach alive orphans;RESUME_STRATEGYresume/rerun/rework via per-provider--continuetemplates) - Hardening:
BRIDGE_AUTH_TOKENrequired on the internet-exposed endpoint;ALLOWED_DIRSdefaults to~/Documentswith non-interactive--add-dirand a hot-reloadedallowed-dirs.txt(removals file-only); launch-context-proof PATH augmentation + boot agent preflight; user-authorized package installs (ALLOW_PACKAGE_INSTALLS); heartbeat stale-kill on stdout+stderr; break-glassENABLE_CONFIRMATION_GATEThe hardened core (security & structure): - Security: shell-free execution across all tools (execFile + argv), symlink-aware path sandbox, constant-time token check (header only), kill switch on POST routes, keychain off by default, tunnel refuses to start without
MCP_TOKEN, confirmation gate wired into dispatch. - Robustness: JSON body parsing (webhook tool names, pre-parsed transport bodies), single
/healthroute,smart-edituniqueness check without$-corruption,patchvia systempatch(1), depth-capped/symlink-safetree, background-task buffer caps + eviction + process-group kill, size caps on media reads, browser crash recovery, larger exec buffer. - Structure: extracted
config/security/exec/audit/runtimemodules and split the 19 tool handlers into one module each undersrc/tools/(server.ts is now a ~350-line composition root); added vitest tests, eslint, and CI. - Enhancements: Streamable HTTP transport (
/mcp) alongside SSE, audit-log rotation, per-tool allowlist (TOOLS). - Dependencies: replaced abandoned
xlsx@0.18.5(ReDoS / prototype-pollution CVEs) with the vendor-distributed SheetJS0.20.3(same API, patched); dropped dead@types/axiosand@types/express-rate-limit.
- Standardized terminal print pattern; tunnel health check monitoring; renamed to "Computer Access".
MIT