# gori-agent Multi-IM gateway that drives coding agents through the official Agent Client Protocol (ACP): ```text IM Adapter -> Gateway -> Role/Session Manager -> ACP Client -> kimi acp ``` The MVP backend is Kimi Code ACP. Gateway code contains no Kimi CLI prompt fallback and no Pi/Codex private protocol. Future agents must enter through an ACP adapter such as `codex-acp` or a Pi ACP adapter. ## Requirements and quick start Requires Node.js 20+ and an authenticated Kimi Code installation. ```bash npm install npm run build ./install.sh gori-agent setup gori-agent doctor --config ./config.json gori-agent start --config ./config.json ``` `setup` probes ACP backends, creates an assistant role, optionally creates the `ops` role, preserves all existing platform settings, prints a migration summary, and only writes after confirmation. A v1 config is accepted at runtime only when its default agent is Kimi; other CLI agents require an explicit ACP backend. Convenience wrapper commands: ```bash ./gori-agent.sh start ./gori-agent.sh start-daemon ./gori-agent.sh status ./gori-agent.sh logs ./gori-agent.sh stop ``` `stop` sends SIGTERM and waits up to 10 seconds for graceful shutdown. For production, use systemd with `Restart=always` and `KillMode=control-group` so an unexpected gateway crash also cleans up ACP children. ## CLI ```bash gori-agent setup [--config path] gori-agent discover-backends [--json] gori-agent start [--config path] gori-agent status [--config path] gori-agent doctor [--config path] gori-agent print feishu [--config path] ``` `discover-agents` remains a deprecated alias for `discover-backends`. Discovery does not send a prompt. Kimi is ready when `kimi acp` is available; Codex and Pi report `needs-adapter` unless their ACP adapter executable exists. ## Roles, sessions, and commands A conversation binding is keyed by `platform + chatId + role`. Each binding stores the ACP backend, native session ID, workspace, and role fingerprint. The selected role and bindings survive gateway restart. - A new session receives a hidden bootstrap prompt containing persona, workspace, skill content, and policy. The binding is saved only after bootstrap succeeds. - A role persona, workspace, policy, or skill-content change changes its fingerprint and causes a new native session. - Idle workers are stopped and later cold-resumed with `session/resume` (or `session/load` when resume is unavailable). - Failed prompts are never replayed automatically. - `/new` cancels the current turn, unbinds the current chat/role, and creates a native session on the next prompt. It does not delete Kimi history. - `/cancel` bypasses the per-chat lock and sends ACP `session/cancel`; an unresponsive worker is force-recycled after the grace period. Chat commands: - `/help` - `/roles` - `/role ` - `/status` - `/cancel` - `/new` - `/agents` and `/agent ` (deprecated aliases) Messages in one chat are serialized; different chats run concurrently. ## Configuration v2 See `config.example.json`. Important sections: - `backends[]`: ACP spawn command and arguments. The default is `/home/ubuntu/.kimi-code/bin/kimi acp`. - `roles[]`: backend, absolute workspace, persona, skills, and permission policy. - `skills[]`: readable skill files with a byte limit. Skill contents participate in the role fingerprint. - `acp.stateFile`: defaults to `$GORI_AGENT_HOME/state/acp-sessions.json`. - `acp.initializeTimeoutMs`, `promptTimeoutMs`, `cancelGraceMs`: request lifecycle limits. - `acp.idleTimeoutMs`, `sweepIntervalMs`, `maxProcesses`: worker pool limits. - `policy.allowedUsers`, `allowedChats`, `requireMentionInGroup`: inbound IM policy. - `platforms`: Feishu, WeCom, QQ, generic webhook, and Weixin settings. Migration preserves this object, including credentials. State writes use a serial promise queue, temporary file, fsync, and atomic rename. A single-instance lock protects the state file. Corrupt state is preserved and startup fails explicitly instead of overwriting it. ## Permission policy ACP permission requests are decided by role policy, not by persona: - `deny`: reject every permission request. - `allowlist`: require both an allowed tool name and, for bash/terminal, a matching raw command pattern. If the ACP request lacks enough command detail, it is denied. - `auto`: approve everything; `doctor` prints a high-risk warning. The example `ops` role loads the existing read-only skill file at `/home/ubuntu/gori-space/gori-deploy/.kimi-code/skills/gori-update/SKILL.md`. Its allowlist covers selected `git`, build/deploy entry scripts, and read-only Docker status/log commands. `sudo`, force push, and arbitrary deletion are not allowed. Script-internal operations cannot be inspected by ACP once an explicitly allowed deployment script starts, so script paths must remain trusted. Kimi 0.36.1 does not advertise a generic model config option during ACP initialize; this MVP uses the model configured as Kimi's default and `doctor` reports that limitation. ## Platform behavior | Platform | Inbound | Outbound | Notes | | --- | --- | --- | --- | | Feishu/Lark | Implemented | Implemented | `im.message.receive_v1` and message reply API. | | WeCom | 501 scaffold | Implemented | Inbound verification/encryption is not implemented. | | Weixin | External webhook scaffold | Synchronous | Native personal WeChat is not included. | | QQ | Implemented | Implemented | WebSocket gateway and HTTP callback modes. | | Generic webhook | Implemented | Synchronous JSON | HMAC-SHA256 signed JSON. | QQ WebSocket mode uses intent `33554432` for `GROUP_AT_MESSAGE_CREATE` and `C2C_MESSAGE_CREATE`. The adapter supports top-level and nested `author.user_openid` fields, preserves callback ACK `{ "op": 12 }` even if ACP work later fails, and logs receive/send routing. No external QQ message is sent by the automated tests. Endpoints: - `GET /health` — aggregate ACP counters only; native session IDs are not exposed. - `GET /platforms` - `POST /webhook/feishu` - `POST /webhook/wecom` - `POST /webhook/qq` - `POST /webhook/generic` - `POST /webhook/weixin` Generic webhook payload: ```json { "chat_id": "demo-chat", "user_id": "demo-user", "text": "/status", "message_id": "optional", "is_group": false, "mentions_bot": true } ``` When `platforms.webhook.secret` is set, provide `X-Gori-Signature: sha256=` over the exact JSON body. ## Lifecycle and diagnostics Shutdown order is QQ WebSocket stop, active ACP cancellation, worker termination, state flush/unlock. SIGINT and SIGTERM share the same idempotent shutdown path. Health statistics include active workers, in-flight turns, worker crashes, persisted bindings, and locked chats. `doctor` checks role/workspace/skill validity, ACP initialize and restore capabilities, state directory access, permission warnings, configured platforms, and QQ requirements. It performs no prompt and no external QQ test. ## Development ```bash npm run typecheck npm test npm run build git diff --check ``` Tests use Node `node:test` through `tsx`. The fake ACP subprocess covers initialize/new/resume/prompt/cancel, persistence, idle cold resume, Gateway commands, and QQ normalization/ACK behavior. Legacy `src/agents/*` source remains only for compatibility/reference and is not imported by Gateway or Server. There is no runtime `CliAgent` fallback.