Commit Graph

3 Commits

Author SHA1 Message Date
gavrielc
95fdec335a refactor(modules): re-tier approvals as default; extract self-mod as optional
Promotes approvals to the default tier with a public API (requestApproval +
registerApprovalHandler) that other modules consume. Self-modification
(install_packages / request_rebuild / add_mcp_server) moves into a new
optional module that registers delivery actions + matching approval handlers
via the new API.

## Approvals (default tier)

- Adds `src/modules/approvals/primitive.ts` exporting `requestApproval`,
  `registerApprovalHandler`, `notifyAgent`. Absorbs `pickApprover` /
  `pickApprovalDelivery` / `channelTypeOf` from the deleted `src/access.ts`.
- Rewrites `response-handler.ts` to dispatch to registered approval handlers
  on approve (action-keyed Map). Reject path is centralized.
- Drops the three self-mod-specific delivery-action registrations from
  `approvals/index.ts`; they belong to self-mod now.
- `onecli-approvals.ts` now imports picks from the primitive instead of
  `src/access.ts`.

## Self-mod (optional tier)

- New `src/modules/self-mod/` with request handlers (validate input + call
  requestApproval) and apply handlers (orchestration on approve).
- `apply.ts` owns updateContainerConfig + buildAgentGroupImage + killContainer
  calls. Self-mod depends on approvals (via registerApprovalHandler +
  requestApproval + notifyAgent) and on core (container-runner, container-config).
- Registers 3 delivery actions + 3 approval handlers at import time.

## Other changes

- `src/access.ts` and `src/access.test.ts` deleted. Tests split across
  `src/modules/approvals/picks.test.ts` (approver selection) and
  `src/modules/permissions/permissions.test.ts` (access + roles + DM).
- `src/modules/index.ts` barrel: approvals loads before self-mod so
  registerApprovalHandler is bound when self-mod registers at import time.

## Validation

- `pnpm run build` clean
- `pnpm test` — 137 host tests pass
- `bun test` in container/agent-runner — 17 tests pass
- Service starts; boot log shows `OneCLI approval handler started`,
  `NanoClaw running`; clean SIGTERM shutdown

Resolves the transitional tier violation flagged in PR #5 where core
imported from the permissions optional module via `src/access.ts`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:41:26 +03:00
gavrielc
626c565a70 fix(modules): break circular import TDZ between index.ts and modules
PR #3 introduced a circular-import temporal-dead-zone bug that didn't
surface in unit tests but crashed the service at startup:

  src/index.ts imports './modules/index.js' for side effects
  → src/modules/interactive/index.ts calls registerResponseHandler()
  → that function is declared in src/index.ts
  → but src/index.ts's const responseHandlers = [] hasn't been
    initialized yet (we're in the middle of its module-init)
  → ReferenceError: Cannot access 'responseHandlers' before initialization

Same issue for registerResponseHandler itself (the function reference
resolves to undefined) and for onShutdown in the approvals module.

Caught when the operator started the service and systemd flagged the
process as crashing in auto-restart loop.

Fix: extract responseHandlers + registerResponseHandler + shutdownCallbacks
+ onShutdown into src/response-registry.ts, which has no dependencies on
src/index.ts or on modules. index.ts re-exports the same surface for any
existing consumers; modules import directly from response-registry.js.

The bug was latent because:
- Unit tests import pieces, never src/index.ts's main() flow.
- Host builds clean because TypeScript doesn't catch runtime circular
  init order.
- Only surfaces when the ES module loader actually executes src/index.ts
  as the entry point.

Verified: service boots on Linux host with approvals + interactive
loaded; OneCLI handler starts via onDeliveryAdapterReady callback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:48:43 +03:00
gavrielc
a4573395d9 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>
2026-04-18 15:16:53 +03:00