Two related changes to the /usage quota route, committed together because they
are entangled in the same code path.
1. Stop refreshing the Kimi OAuth token from this route (kb#87). Was already
present as uncommitted working-tree WIP, not authored in this commit's
session. Kimi rotates the refresh_token on every refresh (single-use), so an
independent refresh here invalidated the copy the CLI's creds file holds ->
the CLI's next refresh failed invalid_grant and wiped the whole login (the
recurring Adolf logout, incl. the 2026-07-17 06:15 wipe / task #86). Removes
KIMI_OAUTH_HOST, KIMI_CLIENT_ID, refreshKimiToken() and the kimiMemToken
cache; the CLI is now the sole refresher and this route only ever READS.
2. Serve the last good reading when the token is stale, instead of erroring.
Measured 2026-07-22: the access token's expires_in is 900s, so it is only
valid for 15 minutes after the CLI last refreshed it -- i.e. only within 15
minutes of an actual Adolf turn. Adolf is idle most of the day, so bare
reads failed far more often than they succeeded and quota gating was
effectively blind. /usage now caches every success and, on a stale token,
returns that payload with stale/as_of/age_s/stale_reason so callers can
judge whether it is fresh enough. Cache is mirrored to the workspace volume
so it survives restarts, and writes are best-effort so an unwritable volume
cannot break the route. Auth behaviour is unchanged by this half.
Payload shape is additive only -- existing kimi-usage -q filters keep working.
Verified live after rebuild: fresh read returns weekly 16% / 5h 5% with
stale:false; cache file written to /workspace/.adolf-llm/usage-cache.json;
kimi-usage -q '.window_5h.pct' returns 5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- adolf-llm/server.js now loads SHARED_MCP_SERVERS from the mounted
/shared-mcp.json instead of a hardcoded stub, so adding a shared MCP
server is a one-file change. Verified end-to-end: a real chat-completions
turn writes a session .mcp.json containing both cognee and openclaw-tools
entries (kimi itself still needs `kimi login` in adolf-llm-home, unrelated
to this change).
- Documented the Gate-1 transport reconciliation: decompiled the installed
@moonshot-ai/kimi-code package to confirm its .mcp.json schema keys remote
servers on `transport` ("stdio"/"http"/"sse", inferred as "http" from a
bare `url`, never "sse"), while OpenClaw's own canonical mcp.servers schema
uses different literals ("streamable-http"/"sse") for the same field name
and treats `type` as a CLI-native alias it normalizes itself. `type: "http"`
is the one shape both consumers tolerate, so shared-mcp.json keeps it.
- New openai/openclaw-tools/ service: a stateless MCP-over-Streamable-HTTP
bridge (Node, @modelcontextprotocol/sdk) exposing message_send, cron_create,
cron_list, nodes_invoke, and browser_invoke, each proxying to the OpenClaw
gateway's POST /tools/invoke. Verified initialize + tools/list handshake and
a tools/call against the not-yet-running `adolf` gateway returns a clean
isError content instead of breaking the MCP connection. Documented that
cron/nodes are hard-denied on that HTTP surface by default until P6 adds
them to gateway.tools.allow; message/browser are not similarly restricted.
- Wired openclaw-tools into docker-compose.yml (openai network, :8020) and
added its shared-mcp.json entry alongside cognee.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
Model backend for the Adolf gateway. OpenAI-compatible (model 'adolf'), real
SSE streaming, chat_id session-keying (parsed from OpenClaw's untrusted-metadata
block per SPIKE gate 2) -> 1:1 kimi -r resume, media persistence for the CLI's
ReadMediaFile, per-session project-root .mcp.json (gate 1; no --mcp-config-file).
Cognee auto-memory hooks and shared-MCP server list are non-blocking stubs with
TODO(P4/P5) markers. New service + workspace/home volumes in compose.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2