Skip to content

feat(integrations): shared foundation + Notion core plumbing (T1)#73

Merged
madarco merged 1 commit into
add-ticketing-integrationsfrom
agentbox/notion-t1-2
Jun 6, 2026
Merged

feat(integrations): shared foundation + Notion core plumbing (T1)#73
madarco merged 1 commit into
add-ticketing-integrationsfrom
agentbox/notion-t1-2

Conversation

@madarco
Copy link
Copy Markdown
Owner

@madarco madarco commented Jun 6, 2026

Summary

T1 of the Notion integration: generalize the proven gh relay pattern (in-box ctl → host relay → host-authed CLI, with read/write classification + write gating via askPrompt) into a reusable connector model so future ticketing integrations (Notion, Linear, Trello, ClickUp) share one spine. The box never holds a service token; the host runs the real CLI.

  • New package @agentbox/integrationsIntegrationConnector / IntegrationOp descriptor model, getConnector / ALL_CONNECTORS registry, and a Notion descriptor wrapping ntn. Conservative starter allowlist: api (GET passthrough, with a refuseCall that mirrors refuseGhApiCall's -X/--method/-f/-F/--input detection so the read classification stays honest), plus three write-gated ops: page.create, page.update, comment.add. NOTION_KEYRING=0 forced via the connector env so ntn reads file-based auth on Linux boxes — harmless on the macOS host.
  • packages/relay/src/integrations.tsrunHostIntegration, assertIntegrationReady (60s readiness cache, same shape as assertGhReady), parseIntegrationMethod (integration.<svc>.<op> regex), refuseIntegrationCall (lifts the descriptor's refuseCall into the relay's envelope), and mergeConnectorEnv namespace guard (a descriptor that tries to set an env key outside its <SERVICE>_* namespace yields a typed exit-78 envelope rather than throwing an opaque 500).
  • Generic integration.<svc>.<op> dispatch wired into BOTH packages/relay/src/server.ts (docker POST /rpc) AND packages/relay/src/host-actions.ts (cloud path), so docker, daytona, hetzner, vercel, and e2b all get it for free — same "fix across all providers" rule we apply to every relay change. Reads bypass the prompt; writes go through askPrompt (with the existing host-initiated token path skipping the prompt when a valid scope+params-hash-matched token is presented).
  • packages/ctl/src/commands/integration.ts — builds one commander subtree per connector from its descriptor (so a new connector is one file in @agentbox/integrations, no surgery here), each op calling postRpcAndExit through the existing relay-rpc.ts transport. Registered next to ghCommand.

Reused, not rebuilt

  • askPrompt / PendingPrompts / PromptSubscribers — the write gate, verbatim.
  • HostInitiatedTokens.consume + hashRpcParams — same scope-key shape as gh.pr.<op>, just integration.<svc>.<op>.
  • resolveWorktree (server.ts), lookupCloudBox + cloudWriteConfirm (host-actions.ts).
  • postRpcAndExit from relay-rpc.ts.

Out of scope (T2–T4)

  • In-box notion shim, Dockerfile/stage-runtime staging, hetzner install-box.sh + cloud runtime list mirrors (T2).
  • integrations config block + per-service enable flags (T2).
  • agentbox doctor detection of host ntn (T3).
  • Docs site (docs/integrations.md, apps/web/content/docs/..., docs/host-relay.md RPC method list, docs/features.md) (T3).
  • Nested-box e2e + ground-truth verification + Notion-token-not-in-box assertion (T4).

Test plan

  • pnpm typecheck green across all 25 packages.
  • pnpm test green: 168 relay tests (9 new, +5 baseline), 12 integration tests, 201 ctl tests, 485 CLI tests.
  • pnpm build green.
  • pnpm lint green.
  • api op honesty: pure unit tests assert -X DELETE, -X PATCH, --method=PUT, -f body=…, --field=…, glued -XDELETE, --input all yield exit 65 with notion api: stderr — on docker POST /rpc AND on the cloud executeCloudAction path.
  • Read/write classification: integration.notion.api (read) with AGENTBOX_PROMPT=off runs the stub without enqueueing an askPrompt. integration.notion.page.create (write) enqueues a prompt; denying via /admin/prompts/answer returns exit 10 denied by user.
  • Allowlist default-deny: integration.notion.bogus returns exit 65; integration.linear.api (unknown service) returns exit 64 on both docker and cloud.
  • NOTION_KEYRING=0 reaches the spawn: the stub asserts the env var on every invocation.
  • Env namespace guard: a synthetic descriptor with env: { AGENTBOX_PROMPT: 'off' } returns exit 78 not in 'NOTION_*' namespace instead of disabling the prompt gate.
  • Docker/cloud envelope parity: unknown method shape / unknown service / op not on allowlist / api -X DELETE all return the same {exitCode, stdout, stderr} shape on POST /rpc and on executeCloudAction.
  • Live ntn round-trip is deferred to T2 (no shim yet); the orchestrator confirmed host ntn is authed for that pass.

Notes

  • docs/notion_backlog.md T1 entry flipped to done with a status-log line; the PR link will be added by the merge tooling.
  • No emojis. Comments only where the WHY is non-obvious (security boundary, divergence rationale, cycle-avoidance reasoning).

Note

Medium Risk
Touches host-action approval and spawns external CLIs with credentials on the host; mitigated by allowlists, GET-only api checks, env namespace guard, and parity with the existing gh relay pattern plus broad tests.

Overview
Introduces a shared integrations spine (same model as gh): in-box agentbox-ctl integration … → relay integration.<service>.<op> → host CLI with host creds, so the box never holds service tokens.

Adds @agentbox/integrations with connector descriptors, a registry, and a Notion allowlist (api read-only with GET-only refuseCall, plus gated writes page.create / page.update / comment.add). packages/relay/src/integrations.ts handles parsing, readiness probing, host spawn (NOTION_KEYRING=0), and env overrides limited to SERVICE_*.

Dispatch is wired on both docker POST /rpc (server.ts) and cloud (host-actions.ts): reads skip askPrompt; writes prompt (with host-initiated token bypass). agentbox-ctl integration builds subcommands from descriptors. Unit and relay e2e tests cover allowlist denial, GET-only api, prompt gating, and docker/cloud envelope parity.

Reviewed by Cursor Bugbot for commit 2b345a6. Configure here.

Generalize the gh relay pattern (in-box ctl -> host relay -> host-authed
CLI with read/write classification + write gating) into a reusable
connector model so future ticketing integrations (Notion, Linear, Trello,
ClickUp) all share one spine. Box never holds a service token; host runs
the real CLI.

- New package @agentbox/integrations: IntegrationConnector / IntegrationOp
  descriptor model, registry (getConnector / ALL_CONNECTORS), and a
  Notion descriptor wrapping ntn. Conservative starter allowlist: api
  (GET passthrough, refuseCall mirrors gh.api's -X/-f detection so the
  read classification stays honest) plus page.create / page.update /
  comment.add (write-gated). NOTION_KEYRING=0 forced via the connector
  env so ntn reads file-based auth on Linux boxes; harmless on macOS.

- packages/relay/src/integrations.ts: runHostIntegration +
  assertIntegrationReady + parseIntegrationMethod, plus
  refuseIntegrationCall and a mergeConnectorEnv namespace guard that
  prevents a descriptor from setting env vars outside <SERVICE>_*
  (returns a typed exit-78 envelope when violated).

- Generic integration.<svc>.<op> dispatch wired into BOTH
  packages/relay/src/server.ts (POST /rpc) AND
  packages/relay/src/host-actions.ts (cloud path) so docker, daytona,
  hetzner, vercel, and e2b all get it for free.

- packages/ctl/src/commands/integration.ts: builds one commander subtree
  per connector from its descriptor, each op calling postRpcAndExit
  through the existing relay-rpc transport. Registered next to ghCommand.

Tests (pure vitest, no docker/network): allowlist denies unknown ops;
api refuseCall blocks POST/PATCH/DELETE/-f field flags/--input on
docker AND cloud paths; reads bypass the prompt; writes enqueue an
askPrompt and a denied prompt yields exit 10; descriptor env outside
its SERVICE_ namespace surfaces as exit 78; cloud and docker paths
emit identical envelopes for unknown shape / unknown service / op
not on allowlist.

Out of scope (T2-T4): in-box notion shim, Dockerfile/stage-runtime
staging, hetzner/cloud install-script mirror, config flags, doctor
detection, docs site, nested-box e2e.
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentbox-web Ready Ready Preview, Comment Jun 6, 2026 1:46pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant