refactor(modules): extract approvals + interactive as registry-based modules

Phase 2 / PR #3 of the module refactor. Moves the approval and interactive-
question flows out of core and into src/modules/, wired through the response
dispatcher and delivery action registries.

New modules:
- src/modules/interactive/ — registers a response handler that claims
  pending_questions rows, writes question_response to the session DB, wakes
  the container. createPendingQuestion call stays inline in delivery.ts
  (guarded by hasTable) per plan.
- src/modules/approvals/ — registers 3 delivery actions (install_packages,
  request_rebuild, add_mcp_server), a response handler for pending_approvals
  (including OneCLI action fall-through), an adapter-ready hook that boots
  the OneCLI manual-approval handler, and a shutdown hook that stops it.
  OneCLI implementation (src/onecli-approvals.ts) moves into the module.

Core lifecycle hooks added (narrow, not registries):
- onDeliveryAdapterReady(cb) in delivery.ts — fires when setDeliveryAdapter
  runs (or immediately if already set). Used by approvals for OneCLI boot.
- onShutdown(cb) in index.ts — fires on SIGTERM/SIGINT. Used by approvals
  for OneCLI teardown.
- getDeliveryAdapter() getter in delivery.ts — for live-flow adapter access
  in registered delivery actions.

Core shrinks: delivery.ts 911 → 665 lines, index.ts 405 → 224 lines.
dispatchResponse now logs "Unclaimed response" instead of falling through
to an inline handler — the inline fallback moved into the two modules.

Migration files renamed to the module-<name>-<short>.ts convention:
- 003-pending-approvals.ts → module-approvals-pending-approvals.ts
- 007-pending-approvals-title-options.ts → module-approvals-title-options.ts
Migration.name fields unchanged so existing DBs treat them as already-applied.

Degradation verified: emptying the modules barrel builds clean and 137/137
tests pass. Actions would log "Unknown system action"; button clicks would
log "Unclaimed response".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-18 15:16:53 +03:00
parent a612c2ca24
commit a4573395d9
15 changed files with 666 additions and 404 deletions

View File

@@ -0,0 +1,53 @@
## Self-modification tools (require admin approval)
Three fire-and-forget tools change your container image or config. Each sends an approval card to an admin's DM; you get notified via system chat on approve/reject.
### install_packages
Add apt and/or npm packages to your container image. On approval, the config is updated AND the image is rebuilt in the same step — you'll get a follow-up prompt ~5s after rebuild telling you to verify the packages are available.
```
install_packages({
apt: ["ripgrep", "jq"], // names only, no version specs or flags
npm: ["@anthropic-ai/sdk"], // global install
reason: "need rg for fast code search"
})
```
- Max 20 packages per request.
- Names must match strict regex (blocks shell injection via `vim; curl evil.com`).
- After approval: rebuild runs automatically. You do NOT need to call `request_rebuild` separately.
### add_mcp_server
Wire an EXISTING third-party MCP server into your runtime config. You must already know the exact `command` and `args`.
```
add_mcp_server({
name: "github",
command: "npx",
args: ["@modelcontextprotocol/server-github"],
env: { GITHUB_TOKEN: "..." }
})
```
- Does NOT install packages. Use `install_packages` first if the command isn't already available.
- On approval, container is killed so the next message wakes it with the new server wired up.
### request_rebuild
Rebuild your container image. Only useful if you've already landed `install_packages` approvals whose rebuild step failed, or if you're recovering from a bad config edit.
```
request_rebuild({ reason: "previous install_packages rebuild failed" })
```
### How approval works
You won't see the admin's response in your current turn. After approval, the container is killed and next time a message arrives your container starts fresh on the new image. If a follow-up system prompt fires (as with `install_packages`), you'll see it and should act on it — verify the change, report to the user.
If denied, you'll get a chat message telling you the request was rejected. Do not retry automatically; explain to the user what was denied.
## Credential approvals (OneCLI)
When you call an external API that requires credentials, OneCLI may prompt an admin for approval before releasing the token. This happens transparently: the HTTP call blocks until admin approves or denies. No action needed from you — just make the call. If it errors out with a credential failure, tell the user and stop.