feat(integrations): shared foundation + Notion core plumbing (T1)#73
Merged
Merged
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Jun 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
T1 of the Notion integration: generalize the proven
ghrelay pattern (in-box ctl → host relay → host-authed CLI, with read/write classification + write gating viaaskPrompt) 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.@agentbox/integrations—IntegrationConnector/IntegrationOpdescriptor model,getConnector/ALL_CONNECTORSregistry, and a Notion descriptor wrappingntn. Conservative starter allowlist:api(GET passthrough, with arefuseCallthat mirrorsrefuseGhApiCall's-X/--method/-f/-F/--inputdetection so the read classification stays honest), plus three write-gated ops:page.create,page.update,comment.add.NOTION_KEYRING=0forced via the connector env sontnreads file-based auth on Linux boxes — harmless on the macOS host.packages/relay/src/integrations.ts—runHostIntegration,assertIntegrationReady(60s readiness cache, same shape asassertGhReady),parseIntegrationMethod(integration.<svc>.<op>regex),refuseIntegrationCall(lifts the descriptor'srefuseCallinto the relay's envelope), andmergeConnectorEnvnamespace 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).integration.<svc>.<op>dispatch wired into BOTHpackages/relay/src/server.ts(dockerPOST /rpc) ANDpackages/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 throughaskPrompt(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 callingpostRpcAndExitthrough the existingrelay-rpc.tstransport. Registered next toghCommand.Reused, not rebuilt
askPrompt/PendingPrompts/PromptSubscribers— the write gate, verbatim.HostInitiatedTokens.consume+hashRpcParams— same scope-key shape asgh.pr.<op>, justintegration.<svc>.<op>.resolveWorktree(server.ts),lookupCloudBox+cloudWriteConfirm(host-actions.ts).postRpcAndExitfromrelay-rpc.ts.Out of scope (T2–T4)
notionshim, Dockerfile/stage-runtimestaging, hetznerinstall-box.sh+ cloud runtime list mirrors (T2).integrationsconfig block + per-service enable flags (T2).agentbox doctordetection of hostntn(T3).docs/integrations.md,apps/web/content/docs/...,docs/host-relay.mdRPC method list,docs/features.md) (T3).Test plan
pnpm typecheckgreen across all 25 packages.pnpm testgreen: 168 relay tests (9 new, +5 baseline), 12 integration tests, 201 ctl tests, 485 CLI tests.pnpm buildgreen.pnpm lintgreen.apiop honesty: pure unit tests assert-X DELETE,-X PATCH,--method=PUT,-f body=…,--field=…, glued-XDELETE,--inputall yield exit 65 withnotion api:stderr — on dockerPOST /rpcAND on the cloudexecuteCloudActionpath.integration.notion.api(read) withAGENTBOX_PROMPT=offruns the stub without enqueueing anaskPrompt.integration.notion.page.create(write) enqueues a prompt; denying via/admin/prompts/answerreturns exit 10denied by user.integration.notion.bogusreturns exit 65;integration.linear.api(unknown service) returns exit 64 on both docker and cloud.NOTION_KEYRING=0reaches the spawn: the stub asserts the env var on every invocation.env: { AGENTBOX_PROMPT: 'off' }returns exit 78not in 'NOTION_*' namespaceinstead of disabling the prompt gate.api -X DELETEall return the same{exitCode, stdout, stderr}shape onPOST /rpcand onexecuteCloudAction.ntnround-trip is deferred to T2 (no shim yet); the orchestrator confirmed hostntnis authed for that pass.Notes
docs/notion_backlog.mdT1 entry flipped to done with a status-log line; the PR link will be added by the merge tooling.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
ghrelay pattern plus broad tests.Overview
Introduces a shared integrations spine (same model as
gh): in-boxagentbox-ctl integration …→ relayintegration.<service>.<op>→ host CLI with host creds, so the box never holds service tokens.Adds
@agentbox/integrationswith connector descriptors, a registry, and a Notion allowlist (apiread-only with GET-onlyrefuseCall, plus gated writespage.create/page.update/comment.add).packages/relay/src/integrations.tshandles parsing, readiness probing, host spawn (NOTION_KEYRING=0), and env overrides limited toSERVICE_*.Dispatch is wired on both docker
POST /rpc(server.ts) and cloud (host-actions.ts): reads skipaskPrompt; writes prompt (with host-initiated token bypass).agentbox-ctl integrationbuilds subcommands from descriptors. Unit and relay e2e tests cover allowlist denial, GET-onlyapi, prompt gating, and docker/cloud envelope parity.Reviewed by Cursor Bugbot for commit 2b345a6. Configure here.