# Plan: Full AI CLI Integration Through Real Opencode ## Goal Run the real ComputerCraft `ai` CLI against a real `opencode serve` process through the WebSocket bridge proxy, while using the fake provider/model fixture proven by `opencode-fake-provider-direct-plan.md`. This test should cover the actual runtime chain used in-game. ## Dependency Do not implement this plan until `.plans/opencode-fake-provider-direct-plan.md` has produced a working fake provider fixture. Update this plan first with the concrete results from plan 1: - Working fake provider plugin code shape. - Working opencode startup command/env. - Working readiness endpoint. - Any internal prompt behavior discovered. ## Desired Boundary Real: - CraftOS-PC harness - `/programs/ai.lua` - `/apis/libai.lua` - `/apis/libhttpws.lua` - `tools/mcp-bridge` opencode proxy - `opencode serve` - opencode sessions/messages/agents/model selection Fake: - The provider/model response behavior only, through the reusable fake provider fixture from plan 1 ## Runtime Chain ```text CraftOS /programs/ai.lua -> libai.lua -> libhttpws.lua -> mcp-bridge opencode proxy -> real opencode serve -> fake provider/model ``` ## Test Fixture Reuse the fake provider workspace generator from plan 1. Response mappings needed for the CLI cases: ```json [ { "match": "reply with exactly: pong", "reply": "pong" }, { "match": "fresh start", "reply": "new reply" }, { "match": "continue please", "reply": "plain reply" } ] ``` Keep the mapping fixture easy to extend so future CLI cases can add entries without changing provider code. ## CraftOS Wrapper Create or update a Lua wrapper under: - `tools/mcp-bridge/test-integration/lua/ai-cli-check.lua` The wrapper should: 1. Accept the WebSocket proxy URL as its first argument. 2. Clear stale settings: - `opencc.server_url` - `opencc.session_id` 3. Set: - `opencc.bridge_url` - `opencc.request_timeout_seconds` 4. Run: - `ai sessions` - `ai ping` - `ai new fresh start` - `ai continue please` 5. Print markers around each command. 6. Print persisted session markers after commands. 7. Call `os.shutdown()`. Expected marker examples: ```lua print('--- sessions ---'); shell.run('/programs/ai.lua', 'sessions'); print('--- ping ---'); shell.run('/programs/ai.lua', 'ping'); print('SESSION_AFTER_PING=' .. tostring(settings.get('opencc.session_id'))); print('--- new ---'); shell.run('/programs/ai.lua', 'new', 'fresh', 'start'); print('SESSION_AFTER_NEW=' .. tostring(settings.get('opencc.session_id'))); print('--- ask ---'); shell.run('/programs/ai.lua', 'continue', 'please'); print('SESSION_AFTER_ASK=' .. tostring(settings.get('opencc.session_id'))); ``` ## Node Test Implementation Add or replace the current CLI integration test under: - `tools/mcp-bridge/test-integration/ai-cli.test.ts` Test steps: 1. Create temp fake-provider opencode workspace using the plan 1 fixture. 2. Start `opencode serve` on a random local port. 3. Poll until opencode is ready. 4. Start `startOpencodeProxy({ opencodeUrl })`. 5. Start CraftOS with: - `mountRepo: true` - `shellArgs: [proxyUrl]` - a generous timeout, likely `30_000` or higher depending on measured opencode startup time 6. Assert CLI output includes: - `pong` - `new reply` - `plain reply` - session markers proving `ai new` replaces the session and plain `ai ...` reuses it 7. Stop CraftOS, proxy, and opencode in `finally`. ## Useful Assertions - `ai sessions` exits without an opencode transport/config error. - `ai ping` prints `pong`. - `ai new fresh start` prints `new reply`. - Plain `ai continue please` prints `plain reply`. - `SESSION_AFTER_NEW` is non-empty. - `SESSION_AFTER_ASK` equals `SESSION_AFTER_NEW`. - If `SESSION_AFTER_PING` is printed, decide whether ping should persist a session or whether `ai ping` should become non-persistent in a separate behavior change. ## Current Open Questions - Should `ai ping` persist `opencc.session_id`? Current `programs/ai.lua` calls `ai.ping(askOptions())`, and `libai.ping` behavior must be checked before asserting this too tightly. - Should `ai sessions` be expected to show no sessions, one session, or just avoid failing before messages are created? Real opencode behavior may differ from the old fake HTTP server. - Does opencode generate a title/summary for each message during the synchronous `/message` call? If yes, the fake provider fallback must make that harmless. - What is the most stable way to choose a free opencode port in CI? ## Verification After implementation, run: ```sh npx tsx --test test-integration/opencode-fake-provider.test.ts npx tsx --test test-integration/ai-cli.test.ts npm run check just check ``` If the full test is too slow for the default integration suite, keep it as a separately named test command or document why it is excluded from `npm run test:integration`.