fix(grok): make the opencode runtime actually load — proven live on grok-build-0.1

Live verification (opencode 1.17.8 + grok-build-0.1, funded key) showed the Grok
runtime was loading INERT, three ways:

1. The provider override `provider.xai.npm=@ai-sdk/openai` failed model
   resolution (ProviderModelNotFoundError) — opencode can't resolve that package
   from its module path. Worse, ANY custom `provider.xai` block (even just
   options) breaks plugin-tool registration. opencode's BUILT-IN xai provider
   drives grok-build-0.1 with working tool-calls, so emit NO provider block; the
   key + base reach it via XAI_API_KEY / XAI_BASE_URL env (provider.options.apiKey
   alone does NOT authenticate).
2. Plugins referenced by absolute path in the config `plugin:` array never
   registered their hooks/tools. opencode 1.17.8 only registers from the plugin
   AUTO-DISCOVERY dir (~/.config/opencode/plugin/). Bake all plugins there.
3. Plugins must use a NAMED export, not `export default`.

Changes:
- opencode_config: no `provider` block, no `plugin` array; drop the dead
  XaiTarget + timeout machinery; build_opencode_config now takes a model string.
- GrokProvider / orchestrator interactive env: inject XAI_API_KEY + XAI_BASE_URL
  (drop the now-unused OPENAI_*).
- secret-scrub / budget-feed / secretary-tools / intake-tools: named exports;
  baked into /home/agent/.config/opencode/plugin/ (drop the EXTRA_PLUGINS env).
- agent-grok* Dockerfiles: plugin dir + agent ownership; drop the unneeded
  @ai-sdk/openai global install.

Verified live end-to-end: grok-build-0.1 calls read_company_state AND
submit_directive through secretary-tools.js and the backend receives both with
the agent token; a tool.execute.before guard fires; built-in tool-calls work.
Targeted gate green (ruff/mypy/xenon + opencode_config/providers/interactive
tests; node --check the plugins).
This commit is contained in:
Renn F
2026-06-18 21:14:06 +02:00
parent f314723c98
commit e29168653a
13 changed files with 150 additions and 251 deletions
+19 -16
View File
@@ -12,35 +12,38 @@ FROM roboco-agent-base
USER root
# opencode — the OpenAI-protocol agent runtime. grok-build-0.1 is driven via the
# OpenAI Responses API, so the provider package is @ai-sdk/openai (NOT
# @ai-sdk/openai-compatible, which is chat/completions only and errors with
# "responses is not a function"). opencode resolves it at runtime, but
# pre-installing keeps first spawn off the network.
RUN npm install -g opencode-ai @ai-sdk/openai \
# opencode — the OpenAI-protocol agent runtime. grok-build-0.1 runs on opencode's
# BUILT-IN xai provider (no custom provider npm — that breaks model resolution),
# so only opencode-ai is installed; it resolves the provider SDK at runtime.
RUN npm install -g opencode-ai \
&& npm cache clean --force \
&& rm -rf /root/.npm /tmp/*
# opencode plugins (referenced from the generated opencode.json `plugin:` array):
# opencode plugins, baked into the AUTO-DISCOVERY dir (~/.config/opencode/plugin/).
# opencode 1.17.8 does NOT register a plugin's hooks/tools from a config
# `plugin:`-array absolute path — only from this directory (verified live). Each
# plugin uses a NAMED export.
# secret-scrub — bash-guard parity (PAT/credential deny on tool.execute.before)
# budget-feed — POSTs budget/loop/terminal counters to the in-container SDK
# server (tool.execute.{before,after}); the entrypoint starts
# that server (roboco.agent_sdk.server) for Claude-parity.
COPY docker/grok/secret-scrub.js /app/opencode-plugins/secret-scrub.js
COPY docker/grok/budget-feed.js /app/opencode-plugins/budget-feed.js
COPY docker/grok/secret-scrub.js /home/agent/.config/opencode/plugin/secret-scrub.js
COPY docker/grok/budget-feed.js /home/agent/.config/opencode/plugin/budget-feed.js
# Entrypoint: render opencode.json, then run opencode (overrides base's `claude`).
COPY docker/scripts/grok-agent-entrypoint.sh /app/scripts/grok-agent-entrypoint.sh
RUN chmod 0755 /app/scripts/grok-agent-entrypoint.sh
# opencode persists data under ~/.local/share and state under ~/.local/state.
# When the orchestrator bind-mounts the opencode store at
# ~/.local/share/opencode, docker creates the intermediate ~/.local AS ROOT, so
# the non-root agent can no longer create its sibling ~/.local/state and opencode
# EACCESes at boot. Pre-create the tree agent-owned so the mount leaves the
# parents writable (complements the orchestrator's 0777 host-source pre-create).
# opencode persists data under ~/.local/share and state under ~/.local/state, and
# reads config + plugins from ~/.config/opencode. When the orchestrator
# bind-mounts the opencode store at ~/.local/share/opencode, docker creates the
# intermediate ~/.local AS ROOT, so the non-root agent can no longer create its
# siblings and opencode EACCESes at boot. Pre-create the trees agent-owned so the
# mount leaves the parents writable (complements the orchestrator's 0777
# host-source pre-create), and so the baked plugin dir is agent-owned.
RUN mkdir -p /home/agent/.local/share/opencode /home/agent/.local/state \
&& chown -R agent:agent /home/agent/.local
/home/agent/.config/opencode/plugin \
&& chown -R agent:agent /home/agent/.local /home/agent/.config
USER agent