mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
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:
@@ -13,10 +13,11 @@ FROM roboco-agent-grok
|
||||
USER root
|
||||
|
||||
# The intake propose_draft tool plugin (the model calls it; the driver turns the
|
||||
# call into the panel's draft card). Scoped to THIS image via
|
||||
# ROBOCO_OPENCODE_EXTRA_PLUGINS so only the intake role carries it.
|
||||
COPY docker/grok/intake-tools.js /app/opencode-plugins/intake-tools.js
|
||||
ENV ROBOCO_OPENCODE_EXTRA_PLUGINS=/app/opencode-plugins/intake-tools.js
|
||||
# call into the panel's draft card), baked into the auto-discovery dir so only
|
||||
# the intake image carries it. opencode registers tools from this directory, not
|
||||
# from a config `plugin:`-array path (verified live).
|
||||
COPY docker/grok/intake-tools.js /home/agent/.config/opencode/plugin/intake-tools.js
|
||||
RUN chown agent:agent /home/agent/.config/opencode/plugin/intake-tools.js
|
||||
|
||||
USER agent
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@ FROM roboco-agent-grok
|
||||
|
||||
USER root
|
||||
|
||||
# The CEO-authority tool plugin (read_company_state / read_task / submit_directive).
|
||||
# Scoped to THIS image via ROBOCO_OPENCODE_EXTRA_PLUGINS so only the Secretary
|
||||
# carries CEO authority; opencode_config appends it to the plugin array.
|
||||
COPY docker/grok/secretary-tools.js /app/opencode-plugins/secretary-tools.js
|
||||
ENV ROBOCO_OPENCODE_EXTRA_PLUGINS=/app/opencode-plugins/secretary-tools.js
|
||||
# The CEO-authority tool plugin (read_company_state / read_task / submit_directive),
|
||||
# baked into the auto-discovery dir so ONLY the Secretary image carries it (no
|
||||
# other role gets CEO authority). opencode registers it from this directory; a
|
||||
# config `plugin:`-array path would not register its tools (verified live).
|
||||
COPY docker/grok/secretary-tools.js /home/agent/.config/opencode/plugin/secretary-tools.js
|
||||
RUN chown agent:agent /home/agent/.config/opencode/plugin/secretary-tools.js
|
||||
|
||||
USER agent
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -77,7 +77,10 @@ function bareVerb(tool) {
|
||||
return tool;
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
// Named export + loaded from the plugin auto-discovery dir
|
||||
// (~/.config/opencode/plugin/) — opencode 1.17.8 ignores config `plugin:`-array
|
||||
// absolute paths for hook/tool registration (verified live).
|
||||
export const RobocoBudgetFeed = async () => {
|
||||
return {
|
||||
"tool.execute.before": async (input) => {
|
||||
const status = await sdk("GET", "/budget/status", null);
|
||||
|
||||
@@ -20,7 +20,11 @@
|
||||
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
|
||||
export default async () => ({
|
||||
// Named export + loaded from the plugin auto-discovery dir
|
||||
// (~/.config/opencode/plugin/) — opencode 1.17.8 only registers Hooks.tool from
|
||||
// directory auto-discovery, not a config `plugin:`-array absolute path
|
||||
// (verified live).
|
||||
export const RobocoIntakeTools = async () => ({
|
||||
tool: {
|
||||
propose_draft: tool({
|
||||
description:
|
||||
|
||||
@@ -164,7 +164,11 @@ function denyBash(command) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
// Named export + loaded from opencode's plugin auto-discovery dir
|
||||
// (~/.config/opencode/plugin/). opencode 1.17.8 does NOT register a plugin's
|
||||
// hooks/tools when it's listed by absolute path in the config `plugin:` array —
|
||||
// only directory auto-discovery works (verified live against grok-build-0.1).
|
||||
export const RobocoSecretScrub = async () => {
|
||||
return {
|
||||
"tool.execute.before": async (input, output) => {
|
||||
const tool = input?.tool;
|
||||
|
||||
@@ -66,7 +66,12 @@ async function callBackend(method, path, body) {
|
||||
|
||||
const asText = (data) => JSON.stringify(data);
|
||||
|
||||
export default async () => ({
|
||||
// Named export + loaded from the plugin auto-discovery dir
|
||||
// (~/.config/opencode/plugin/) — opencode 1.17.8 does NOT register tools from a
|
||||
// config `plugin:`-array absolute path; only directory auto-discovery + a named
|
||||
// export registers Hooks.tool (verified live: the model called the tool and the
|
||||
// backend received the request).
|
||||
export const RobocoSecretaryTools = async () => ({
|
||||
tool: {
|
||||
read_company_state: tool({
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user