Commit Graph
7 Commits
Author SHA1 Message Date
Renn F eac065e504 fix(grok): deliver intake draft via the relay + correct opencode-mechanism docs
Live end-to-end verification (opencode 1.17.8 + grok-build-0.1) of the WHOLE
integration, then fixes for what it surfaced:

1) Intake draft card (FUNCTIONAL): opencode's synchronous serve reply
   (POST /session/:id/message) returns only [step-start, text, step-finish] — it
   does NOT include tool-call parts, so the driver could never extract the
   propose_draft draft. intake-tools.js now POSTs the draft straight to the
   prompter-live relay (/api/prompter/live/{session}/events, the same endpoint
   the driver's relay sink uses), so the panel renders the card regardless.
   Verified live: grok calls propose_draft -> the relay receives the draft.

2) Correct misattributed opencode "bugs" (DOCS): earlier comments asserted as
   general opencode behavior that a provider.xai block / npm override / config
   plugin:-array "break" registration. Re-testing showed those were artifacts of
   a PROJECT-level .opencode/opencode.json; from the GLOBAL config (which
   opencode_config writes) the built-in provider, model resolution, the plugin
   array AND the auto-discovery dir all work, and MCP gateway verbs register
   (delivery agents verified). Reframed the comments as design choices (built-in
   provider + XAI_API_KEY env + plugins baked in the auto-discovery dir with
   named exports) and dropped the false claims.

3) Reasoning --variant: passing it does not error, but whether opencode applies a
   named reasoning variant to grok-build-0.1 (no provider-defined variants) is
   UNVERIFIED — comment softened from a "~54% cut" claim to best-effort,
   measure-on-NAS.

Verified live this session: one-shot delivery (model + MCP verbs + plugins +
hooks), secretary tools (read_company_state + submit_directive -> backend with
token), intake draft (relay), grok built-in-provider tool-calling. Remaining
NAS-only: full container assembly (SDK :9000 startup, entrypoint hooks, 429
parking) + the --variant cost measurement. Gate green (ruff/mypy + 51 tests;
node --check the plugins).
2026-06-18 21:59:25 +02:00
Renn F e29168653a 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).
2026-06-18 21:14:06 +02:00
Renn F 82945ae023 feat(grok): start the in-container SDK server + budget feed (Claude parity)
The keystone of the Grok parity work (CEO's "take Claude as baseline, create
what's missing" call): the one-shot Grok container now starts the same SDK
server the Claude path runs, so the per-verb circuit breaker (the flow/do MCP
servers already POST /verb/attempted to it), the per-session budget/loop
counters, the terminal-verb tracking, and the SessionEnd post-mortem all work
on Grok instead of being silently absent.

- entrypoint: launch roboco.agent_sdk.server (bare venv python, not `uv run`
  which would re-sync the drifted clone lock and stall), wait for /health,
  reset counters; run opencode WITHOUT exec so the script regains control to
  run the post-mortem and the silent-exit substitute after the run returns.
- budget-feed.js: opencode plugin that gates on /budget/status in
  tool.execute.before (halt/loop deny — the only place to stop a runaway
  one-shot run; opencode has no PostToolUse-deny) and records the executed
  tool + args-hash in tool.execute.after. Fail-open; bare-verb normalization
  for MCP-namespaced terminal verbs.
- silent-exit substitute: on a graceful exit with no terminal verb the
  entrypoint posts /terminal/force_substitute so the task isn't left stuck
  claimed/in_progress (Stop-hook parity at the boundary).
- opencode_config: wire budget-feed into the plugin array; add
  ROBOCO_OPENCODE_EXTRA_PLUGINS so per-image role tool plugins load scoped to
  one role; read the per-role ROBOCO_GROK_EDIT_PERMISSION.

Targeted gate green (ruff/mypy/xenon + opencode_config tests; node --check on
the plugins; bash -n on the entrypoint).
2026-06-18 19:53:22 +02:00
Renn F 433a85784e fix(grok): pre-create agent-owned ~/.local in the grok image (opencode state EACCES)
Running the built grok-prompter container surfaced a second EACCES the
mechanism analysis missed: bind-mounting the opencode store at
~/.local/share/opencode makes docker create the intermediate ~/.local AS ROOT,
so the non-root agent user then cannot create its sibling ~/.local/state and
opencode dies at boot. Pre-create the ~/.local tree agent-owned in the image so
the mount leaves the parents writable. Complements the orchestrator 0777
host-source pre-create (which covers the bind source on Linux).

Verified live: with this fix the container starts clean, opencode serve opens
the session, a POST /turn produces a real grok reply, and all chunks
(thinking/text/turn_end) reach the relay endpoint.
2026-06-18 18:19:45 +02:00
Renn F b0857915a1 fix(grok): correct opencode provider (Responses API), stdin, reasoning cost
A live opencode run against api.x.ai/v1 surfaced three real bugs:

1. Provider package — grok-build-0.1 is driven via the OpenAI Responses API
   (opencode calls model.responses()). @ai-sdk/openai-compatible is
   chat/completions only and errors "responses is not a function". Switch the
   generated opencode.json provider + the grok image to @ai-sdk/openai.
2. Headless hang — `opencode run` blocks after init without a TTY; close stdin
   (`< /dev/null`) in the entrypoint so it proceeds to the model call.
3. Reasoning-token cost — grok-build-0.1 is a reasoning model; reasoning tokens
   bill as output but opencode stores them in a separate column. cost_for_session
   folds tokens_reasoning into output (else ~22x undercount).

Verified end-to-end against a real session row (input=6120, output=1,
reasoning=226, cache_read=1856): our pricing reproduces opencode's stored USD
cost ($0.0069452) exactly. Tests anchored to that real row.
2026-06-18 09:57:20 +02:00
Renn F 07f55117de feat(grok): price grok-build-0.1 + secret-scrub opencode plugin
- pricing.py: add grok-build-0.1 rates ($1/1M input, $0.20 cached, $2/1M
  output), verified against xAI's published pricing. Grok is a priced
  non-Anthropic model, so cost computes the moment usage is captured.
- secret-scrub.js: an opencode tool.execute.before plugin porting the
  security-critical bash-guard deny rules (git network ops, credential-file
  reads, /proc env, internal-host HTTP, roboco.* imports, ROBOCO_AGENT_ID
  forgery, env dumps, destructive rm) to the opencode runtime — restoring the
  guard the Claude Code hook can't provide there. Throwing denies the call
  (confirmed by opencode's env-protection example). Wired into the generated
  opencode.json plugin array + baked into the grok image.

Deny logic verified via node (9 deny + 5 allow cases). UNVALIDATED against a
live opencode runtime: confirm it fires in the live E2E spawn before a Grok
dev-agent touches a real repo; the bash permission is operator-tunable as a
second gate.

Cost CAPTURE (distinct from pricing) is intentionally NOT built yet: opencode's
plugin hooks expose model info but no token/usage object, so the capture path
is unconfirmed and needs the live spawn to settle.
2026-06-18 08:36:36 +02:00
Renn F 085414dcf5 feat(providers): native Grok runtime — opencode image, config gen, panel key
Complete the native Grok (xAI) path so grok-build-0.1 runs as a real
RoboCo agent, not just the provider seam.

- roboco-agent-grok image (docker/agent-grok.Dockerfile): FROM agent-base
  + opencode (the OpenAI-protocol runtime). One image serves every role;
  role behaviour comes from the mounted manifest / mcp-config / system
  prompt, exactly as on the Claude path.
- Entrypoint renders opencode.json at spawn from the GrokProvider env
  contract + the mounted Claude Code mcp-config.json
  (roboco.llm.providers.opencode_config): translates RoboCo's gateway
  servers (roboco-flow / roboco-do / ...) into opencode's mcp block,
  declares the xAI OpenAI-compatible provider + model, and wires
  permissions + instructions. Pure, unit-tested translation.
- Orchestrator registers GrokProvider with the registry-qualified image
  (_qualify_agent_image) so it resolves in local and registry deploys.
- Compose (both files + the registry compose) gain an agent-grok-image
  builder service.
- Panel: a Grok (xAI) API key card on the AI Providers page, plus the
  grok ModelProvider value.

KNOWN PARITY GAP (opencode runtime): the bash-guard PAT-scrub and the
transcript-based usage/cost capture are Claude Code hooks and do not
transfer to opencode. bash permission is operator-tunable
(ROBOCO_GROK_BASH_PERMISSION) so a deployment can fail closed until a
security/usage-parity opencode plugin lands. That plugin and live E2E
validation are the remaining work to finalize with xAI.
2026-06-18 07:12:57 +02:00