{"_id":"@4xeoz/re-entry-sdk","_rev":"3-6a7f7f55522db52d13abd297064bccad","name":"@4xeoz/re-entry-sdk","dist-tags":{"latest":"0.3.2"},"versions":{"0.3.0":{"name":"@4xeoz/re-entry-sdk","version":"0.3.0","_id":"@4xeoz/re-entry-sdk@0.3.0","maintainers":[{"name":"4xeoz","email":"iyadchirifi940@gmail.com"}],"dist":{"shasum":"cbe52daf128475450c5276db902916a0f4e02113","tarball":"https://registry.npmjs.org/@4xeoz/re-entry-sdk/-/re-entry-sdk-0.3.0.tgz","fileCount":21,"integrity":"sha512-Bmc1Z1ioMlm1llyDv80nNFwMzmNinncr9CoKWX5iQG9HSe9mT84lDr9FHWyJMQyJqh6Gvd2HIYRNdoOU5lmvsQ==","signatures":[{"sig":"MEUCIQCJ+0bBsML6YbHriQYl5a3Z3MgdJFvQkkC4J0dQ2z4QqQIgXP4OKO9GyIgRxv8jrvdBjcXvZRzaGMST0fOEbwgPKWY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":252310},"type":"module","engines":{"node":">=24"},"exports":{"./next":"./src/next.mjs","./client":"./src/client.mjs","./server":"./src/server.mjs"},"gitHead":"f71c78d17725cc19c921bf9f945caebda53ae6db","scripts":{"test":"node --test test/*.test.mjs","verify":"npm run check:syntax && npm test","check:syntax":"node scripts/check-syntax.mjs"},"_npmUser":{"name":"4xeoz","email":"iyadchirifi940@gmail.com"},"_npmVersion":"11.19.0","description":"Small Next.js-compatible Host SDK for the application-neutral Re-entry protocol.","directories":{},"_nodeVersion":"26.8.1","dependencies":{"@webmcp-challenge/reentry-core":"file:../../reentry-core"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"bundleDependencies":["@webmcp-challenge/reentry-core"],"_npmOperationalInternal":{"tmp":"tmp/re-entry-sdk_0.3.0_1788285803781_0.22846672372148835","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@4xeoz/re-entry-sdk","version":"0.3.1","_id":"@4xeoz/re-entry-sdk@0.3.1","maintainers":[{"name":"4xeoz","email":"iyadchirifi940@gmail.com"}],"dist":{"shasum":"2d5573b4ce73e4c9f97e40078d06f0480eefbf57","tarball":"https://registry.npmjs.org/@4xeoz/re-entry-sdk/-/re-entry-sdk-0.3.1.tgz","fileCount":21,"integrity":"sha512-RULzfdyW97jrlbXkHeOc8fOOEEViPrdd04zwifOqrKjs5bgi3+IgyB6nilAB46ypIZBR/sGi8TULnkGUpedkMg==","signatures":[{"sig":"MEUCIF91f9dEgVaRLXuYUSZrKMuOWqlrkjOIFKbtqrdKawKRAiEAwKr61XD+8Chx3f/XqiYFffKj7P7YCHypDeVI8qXe2fk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":277258},"type":"module","engines":{"node":">=24"},"exports":{"./next":"./src/next.mjs","./client":"./src/client.mjs","./server":"./src/server.mjs"},"gitHead":"9864ba09b79a76641d8662502ccf918cd3fd4b3b","scripts":{"test":"node --test test/*.test.mjs","verify":"npm run check:syntax && npm test","check:syntax":"node scripts/check-syntax.mjs"},"_npmUser":{"name":"4xeoz","email":"iyadchirifi940@gmail.com"},"_npmVersion":"11.19.0","description":"Small Next.js-compatible Host SDK for the application-neutral Re-entry protocol.","directories":{},"_nodeVersion":"26.8.1","dependencies":{"@webmcp-challenge/reentry-core":"file:../../reentry-core"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"bundleDependencies":["@webmcp-challenge/reentry-core"],"_npmOperationalInternal":{"tmp":"tmp/re-entry-sdk_0.3.1_1788380774029_0.9285600242797183","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@4xeoz/re-entry-sdk","version":"0.3.2","type":"module","description":"Small Next.js-compatible Host SDK for the application-neutral Re-entry protocol.","publishConfig":{"access":"public"},"exports":{"./server":"./src/server.mjs","./client":"./src/client.mjs","./next":"./src/next.mjs"},"dependencies":{"@webmcp-challenge/reentry-core":"file:../../reentry-core"},"bundleDependencies":["@webmcp-challenge/reentry-core"],"scripts":{"check:syntax":"node scripts/check-syntax.mjs","test":"node --test test/*.test.mjs","verify":"npm run check:syntax && npm test"},"engines":{"node":">=24"},"gitHead":"928debcbe6ed8fda9d165ac17318fd30a57f0361","_id":"@4xeoz/re-entry-sdk@0.3.2","_nodeVersion":"26.8.1","_npmVersion":"11.19.0","dist":{"integrity":"sha512-daDtb2CRxkFxDMyvF374U67vWY5FwOUFo5wY0LizaPtlKT0RAS7di59kvju93ctYQAU7H5bbZjoKj9vzMY+Thg==","shasum":"ca40fd641dc213e51507555f678cbfcf3580b86c","tarball":"https://registry.npmjs.org/@4xeoz/re-entry-sdk/-/re-entry-sdk-0.3.2.tgz","fileCount":21,"unpackedSize":297498,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAfr9s8yhIZGL/JrqV79GjnZEcNF0EJmr1URf+rZY2lbAiEA93H9WzpTibRIaxXVvbHiTy8WC/s5PwNiU4Clg1Z1B4s="}]},"_npmUser":{"name":"4xeoz","email":"iyadchirifi940@gmail.com"},"directories":{},"maintainers":[{"name":"4xeoz","email":"iyadchirifi940@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/re-entry-sdk_0.3.2_1788481089550_0.6895235673319473"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-01T18:03:22.306Z","modified":"2026-09-04T00:18:09.842Z","0.3.0":"2026-09-01T18:03:23.918Z","0.3.1":"2026-09-02T20:26:14.176Z","0.3.2":"2026-09-04T00:18:09.686Z"},"description":"Small Next.js-compatible Host SDK for the application-neutral Re-entry protocol.","maintainers":[{"name":"4xeoz","email":"iyadchirifi940@gmail.com"}],"readme":"# Re-entry Host SDK\n\n> **Receiver boundary — 2026-09-03:** New integrations target the active Cloud Receiver v2 source\n> under `saas-boilerplate/`. The older `runtime/cloud-receiver/` implementation is deprecated.\n> Local verification does not by itself prove a deployed Receiver or production release.\n\nA small Next.js-compatible library that bundles Host signing and Receiver calls with the browser\naction that joins ordinary page UI, WebMCP, and Re-entry-owned consent.\n\n> Current boundary: this package is locally verified through Cloud Receiver v2, a separately\n> spawned Local Connector worker, a distinct test-only effect/ack worker, and Receiver restart\n> replay. The default Connector does not acknowledge dispatch. This evidence does not replace Host\n> authentication, durable application storage, current business state, page-specific WebMCP tools,\n> deployment, or a selected product's real effect authority.\n\n## The complete idea\n\n```text\npage button ---------\\\n                     -> requestReentry() -> Host route -> signed Manifest -> Re-entry consent\nWebMCP Site Tool ----/\n\nauthenticated Re-entry approval -> Host confirms status -> Host stores opaque binding\n\nlater Host business event -> signed Event -> Re-entry -> Local Connector -> fresh Codex session\n```\n\nThe first action creates permission to return later; it does not start the later work. A WebMCP\ninvocation may receive the Browser's normal safety review, but that review is not a Re-entry Grant.\nOnly the authenticated Re-entry approval can create the Grant and binding.\n\n## Developer-agent integration guide\n\nThis section is the practical contract for an agent integrating Re-entry into an existing Host\napplication. Read it before changing the Host application. The current path is **account-first**:\nthe developer owns the Host organization credentials, while the end user owns the Re-entry account\nand connected Mac. The older Host-issued pairing and Host-forwarded consent examples remain in the\nlegacy appendix at the end of this file and must not be used for a new integration.\n\n### The one-sentence goal\n\nLet a person approve one bounded future continuation in Re-entry, then let the Host application\nsend a later business Event that causes the user's Local Connector to open a fresh Codex session on\nthe authoritative Host page.\n\n### The four roles\n\n| Role | Lives where | Owns | Must not receive |\n| --- | --- | --- | --- |\n| Host application | Developer's website and backend | User identity, workflow state, business rules, Host signing key, continuation database | Connector bearer, Re-entry browser cookie |\n| Host SDK | Installed inside the Host application | Signing, Receiver HTTP calls, browser handoff UI, WebMCP registration | Nothing beyond what its runtime requires |\n| Re-entry Cloud Receiver | Re-entry service | Account consent, connected devices, Grants, bindings, Events, delivery leases | Host private signing key |\n| Local Connector | User's Mac | Outbound polling, delivery claim, local Codex launch | Organization API key, Host private key |\n\nThe SDK is not a replacement for the Host backend. It is a small library inside it. The Cloud\nReceiver and Local Connector are separate services in the overall product.\n\n### The credential split\n\nThere are three different credentials. Keeping them separate is part of the protocol:\n\n1. **Organization API key** — belongs to the developer's Re-entry organization. It authenticates\n   Host setup and consent-session/status calls. It stays on the Host backend.\n2. **Host signing key** — the Host backend signs Manifests and Events with its Ed25519 private key.\n   The Receiver stores only the derived public key. The private key stays on the Host backend.\n3. **Connector credential** — belongs to the end user's connected Mac. It is issued once after the\n   user approves the Mac and is stored locally with restrictive permissions. It is used only by\n   the Local Connector to claim delivery.\n\nThe browser may see a consent URL, a consent-session ID, and a safe continuation ID. It must never\nsee an organization API key, Host private key, Connector credential, or private binding.\n\n### The complete lifecycle\n\n```text\nDEVELOPER SETUP\n──────────────────────────────────────────────────────────────────────────────\nDeveloper dashboard\n  create Re-entry account -> create organization -> reveal organization API key once\n                                      │\n                                      ▼\nHost backend\n  keeps Ed25519 private key -> SDK registers derived public key\n                                      │\n                                      └── POST /v0.1/host-keys\n\nEND-USER MAC SETUP\n──────────────────────────────────────────────────────────────────────────────\nUser runs `npx @4xeoz/re-entry install --codex-cd /absolute/project`\n  -> Connector starts device authorization\n  -> Re-entry opens in the user's browser\n  -> user signs in or registers\n  -> user approves \"Connect this Mac\"\n  -> Connector polls until approved\n  -> Connector stores its credential and installs a macOS LaunchAgent\n\nFIRST ACTION: ASK FOR FUTURE CONSENT\n──────────────────────────────────────────────────────────────────────────────\nHost button OR WebMCP Site Tool\n  -> one browser function: requestReentry()\n  -> Host route: POST /api/reentry/consent\n  -> server loads Host user + current workflow\n  -> SDK signs a Manifest\n  -> SDK calls Receiver: POST /v0.1/consent-sessions\n  <- Receiver returns consent_url + consent_session_id\n  -> SDK shows its top-layer handoff dialog\n  -> user clicks \"Review in Re-entry\"\n  -> Receiver renders the real consent page\n  -> user approves/declines and chooses a connected Mac\n  -> Receiver creates a Grant and binding only after approval\n  -> popup sends completion message to the Host page\n  -> Host route checks Receiver: GET /v0.1/consent-sessions/:id\n  -> Host stores the binding and returns only continuation_id to the browser\n\nLATER BUSINESS EVENT\n──────────────────────────────────────────────────────────────────────────────\nHost business logic decides that the event really happened\n  -> Host loads the stored binding and current workflow from its database\n  -> SDK signs an Event\n  -> SDK calls Receiver: POST /v0.1/events\n  -> Receiver verifies the Event, spends the Grant's run, and creates delivery\n  -> Local Connector polls: POST /v0.1/delivery-claims\n  <- Connector receives a short lease; the Codex activation receives credential-free context\n  -> Connector starts a fresh `codex exec` process\n  -> Codex opens the canonical page, reads current state, and stops at the human boundary\n```\n\nThe Host page's dialog is only a handoff surface. The consent page rendered by Re-entry is the\nauthority that knows the account and connected devices. A popup `postMessage` only tells the Host\npage that the popup changed; the Host backend must still confirm the status with the Receiver.\n\n### What the integrating agent should build\n\nFor a normal Host application, the smallest useful integration contains:\n\n1. One server-only SDK module that reads environment variables and calls `createReentry`.\n2. One authenticated server route that supplies only `subject`, `prompt`, and the canonical Host\n   URL, then stores the returned request handle.\n3. One server route that confirms approval and stores the approved continuation.\n4. One browser action used by both the normal button and the top-level WebMCP Site Tool.\n5. One separate trusted business-event handler that loads the approved continuation and calls\n   `reentry.trigger`.\n\nThe agent must use the Host application's existing authentication, workflow database, page routes,\nand business transitions. The SDK does not invent those things. It only provides the Re-entry\nconnection and enforces the protocol boundary.\n\nDo not build these as part of the Host integration:\n\n- a second pairing-code system;\n- a browser-side Receiver client with server credentials;\n- a generic \"click this page\" Site Tool;\n- a browser-side binding store;\n- an Event endpoint that trusts a binding or state version supplied by the browser; or\n- a final consequential business action that bypasses the Host application's human boundary.\n\n## Install\n\nThe npm registry currently serves `@4xeoz/re-entry-sdk@0.3.1` from Git commit `9864ba0`. That\npublished artifact predates the uncommitted `createReentry()` simple facade shown below. The\npublished advanced API remains usable, but the normal facade must be exercised from this checkout\nuntil a new exact-source package release is verified:\n\n```sh\nnpm install @4xeoz/re-entry-sdk\n# For local simple-facade verification instead:\nnpm install /absolute/path/to/OpenAI-Web-MCP-Challenge/runtime/host-sdk\n```\n\n## Quickstart: normal server path\n\nUse `createReentry` from server code when the Host only needs to request consent, confirm it, and\ntrigger the later Event. The facade registers the Host key idempotently, supplies the strict\nManifest/Event defaults, and keeps the handle and continuation server-only.\n\n```js\nimport { createReentry } from \"@4xeoz/re-entry-sdk/server\";\n\nconst reentry = createReentry({\n  origin: process.env.HOST_ORIGIN,\n  receiverOrigin: process.env.RECEIVER_ORIGIN,\n  privateKey: process.env.REENTRY_PRIVATE_KEY,\n  keyId: process.env.REENTRY_KEY_ID,\n  organizationApiKey: process.env.REENTRY_ORGANIZATION_API_KEY,\n});\n\n// Authenticated consent-request handler.\nconst request = await reentry.request({\n  subject: authenticatedUser.id,\n  prompt: \"Review the completed report and prepare the next safe step.\",\n  url: currentReport.canonicalUrl,\n});\nawait saveRequestHandle(request.consentSessionId, request.handle);\n// Return only request.consentUrl and request.consentSessionId to the browser.\n\n// Separate callback/status handler, after the Re-entry window completes.\nconst handle = await loadRequestHandle(consentSessionId);\nconst confirmation = await reentry.confirm(handle, {\n  onApproved: (continuation) => saveApprovedContinuation(consentSessionId, continuation),\n});\n\n// Separate trusted handler, called only after the real Host business event.\nasync function handleReportReady(consentSessionId) {\n  const continuation = await loadApprovedContinuation(consentSessionId);\n  return reentry.trigger(continuation);\n}\n```\n\n`confirm` returns `{ status: \"pending\" | \"declined\" | \"expired\" | \"revoked\" }` until approval;\nit returns the server-only continuation after approval. The callback is optional and runs only for\nan approved continuation. Confirmation persists authority but does not trigger the later Event;\nthe trusted business-event handler loads the saved continuation and calls `trigger`. The facade\nfixes `max_runs` to one and treats Event `202` as queued acceptance, not delivery or\nacknowledgement. The consented prompt becomes bounded, untrusted Connector instruction; it never\nreplaces the canonical URL, current Host state, available WebMCP tools, or human boundary.\n\nThe package name in this checkout is `@4xeoz/re-entry-sdk`. Registry publication of `0.3.1` does\nnot publish later working-tree changes under the same version. Use the included sample or an\nexplicit local file dependency for the current simple facade; do not copy SDK source into the Host\napplication or edit the bundled Core dependency by hand. TASK-031 owns a future versioned release.\n\nThe package has three entrypoints:\n\n| Import | Runtime | Responsibility |\n| --- | --- | --- |\n| `@4xeoz/re-entry-sdk/server` | Node server | Sign Manifests and Events; call Re-entry |\n| `@4xeoz/re-entry-sdk/client` | browser | Build the shared action, register its Site Tool, and open Re-entry consent |\n| `@4xeoz/re-entry-sdk/next` | Next.js server | Small Route Handler adapters |\n\n### Public methods at a glance\n\n| Method | Where to call it | What goes in | What comes out |\n| --- | --- | --- | --- |\n| `createReentry(config)` | Host server | Server configuration plus `subject`, `prompt`, and Host URL | Request handle, approved continuation, or visible consent status |\n| `sdk.registerHostKey({ hostId })` | Host setup/server | A stable Host ID | Receiver registration result |\n| `sdk.createManifest(fields)` | Host server | Current workflow, display copy, and Grant request | Signed Manifest; no network call |\n| `sdk.createConsentSession({ manifest, hostSubjectRef })` | Host server | Signed Manifest and authenticated Host subject | Consent URL and session ID |\n| `sdk.getConsentSession({ consentSessionId })` | Host server | Session ID | Pending, declined, expired, or approved status; approved status includes the binding |\n| `sdk.createEvent(fields)` | Host server | Binding and current workflow | Signed Event; no network call |\n| `sdk.sendEvent(fields)` | Host server | Binding and current workflow | Receiver acceptance; delivery is still pending |\n| `createReentryConsentAction(options)` | Browser | Host callbacks for create and confirm | One async function for a button and WebMCP |\n| `registerReentryWebMcpTool(options)` | Top-level browser page | Tool metadata and the shared action | Registered/unavailable result |\n\nThe SDK's synchronous methods only create signed protocol objects. Its asynchronous methods cross a\nnetwork boundary and can fail because the Receiver is unavailable, rejects the request, or returns a\nstale/expired result. The SDK does not silently retry these calls.\n\n`decideConsent` and `createConsentDecisionRoute` still exist for compatibility with the earlier\nHost-forwarded preview. A new account-first integration must let the Re-entry consent page make the\ndecision and use `getConsentSession` for server confirmation instead.\n\n## 1. Server routes\n\nKeep every value below in server environment configuration:\n\n```sh\nHOST_ORIGIN=https://your-app.example\nRECEIVER_ORIGIN=https://your-reentry.example\nREENTRY_KEY_ID=host_key_your_app\nREENTRY_PRIVATE_KEY='-----BEGIN PRIVATE KEY----- ...'\nREENTRY_ORGANIZATION_API_KEY=re_org_...\n```\n\n```js\nimport { createHostSdk } from \"@4xeoz/re-entry-sdk/server\";\n\nconst reentry = createHostSdk({\n  origin: process.env.HOST_ORIGIN,\n  receiverOrigin: process.env.RECEIVER_ORIGIN,\n  privateKey: process.env.REENTRY_PRIVATE_KEY,\n  keyId: process.env.REENTRY_KEY_ID,\n  organizationApiKey: process.env.REENTRY_ORGANIZATION_API_KEY,\n});\n\nawait reentry.registerHostKey({ hostId: \"host_your_app\" });\n```\n\n`registerHostKey` sends only the public key derived from the private key. Call it during controlled\nHost setup; do not put key registration in browser code.\n\n### Create and confirm consent\n\nThe consent route must derive the authenticated Host user and current workflow from server state:\n\n```js\nconst manifest = reentry.createManifest(loadCurrentHostManifest());\nconst session = await reentry.createConsentSession({\n  manifest,\n  hostSubjectRef: authenticatedHostUser.id,\n});\n\nreturn Response.json({\n  title: manifest.display.title,\n  reason: manifest.display.reason,\n  consent_url: session.consent_url,\n  consent_session_id: session.consent_session_id,\n});\n```\n\nThe status route must re-read Receiver state and retain the binding in the Host database. Never\nreturn the binding to browser JavaScript:\n\n```js\nconst status = await reentry.getConsentSession({ consentSessionId });\nif (status.status !== \"approved\") throw new Error(\"consent_not_approved\");\n\nconst continuation = await hostDatabase.continuations.create({\n  hostUserId: authenticatedHostUser.id,\n  workflowId: currentWorkflow.id,\n  binding: status.binding,\n});\n\nreturn Response.json({\n  status: \"approved\",\n  continuation_id: continuation.id,\n});\n```\n\n## 2. Use one JavaScript function for UI and WebMCP\n\nThis is the main integration seam. `requestReentry` is an ordinary async JavaScript function. The\nnormal button and the Site Tool receive that exact function:\n\n```js\n\"use client\";\n\nimport {\n  createReentryConsentAction,\n  registerReentryWebMcpTool,\n} from \"@4xeoz/re-entry-sdk/client\";\n\nconst requestReentry = createReentryConsentAction({\n  async createConsentSession(input) {\n    const response = await postJson(\"/api/reentry/consent\", input);\n    return {\n      title: response.title,\n      reason: response.reason,\n      consentUrl: response.consent_url,\n      consentSessionId: response.consent_session_id,\n    };\n  },\n  async confirmConsentSession({ consentSessionId }) {\n    const response = await postJson(\"/api/reentry/consent/status\", {\n      consent_session_id: consentSessionId,\n    });\n    return {\n      status: response.status,\n      continuationId: response.continuation_id,\n    };\n  },\n});\n\nbutton.addEventListener(\"click\", () => requestReentry({}));\n\nawait registerReentryWebMcpTool({\n  name: \"request_codex_reentry\",\n  description: \"Ask the signed-in user to approve one future Codex continuation. This creates consent; it does not trigger the later business event.\",\n  inputSchema: {\n    type: \"object\",\n    properties: {},\n    additionalProperties: false,\n  },\n  annotations: { readOnlyHint: false },\n  execute: requestReentry,\n});\n```\n\n`registerReentryWebMcpTool` uses `document.modelContext.registerTool(...)` on the top-level page. It\nreturns `{ registered: false, reason: \"webmcp_unavailable\" }` when WebMCP is unavailable; the normal\nbutton remains the visible path. Do not register from an iframe or use the declarative form API.\n\nThe SDK prompt opens the exact Re-entry URL from a human click, validates the popup window, Receiver\norigin, session identifier, and completion shape, then calls the Host status route. A popup message\nalone cannot produce `{ status: \"approved\" }` from the shared action.\n\n## 3. Send the later business Event from the Host server\n\nThe business event is separate from the WebMCP action and separate from consent:\n\n```js\nconst continuation = await hostDatabase.continuations.loadForUserAndWorkflow({\n  continuationId,\n  hostUserId: authenticatedHostUser.id,\n  workflowId: currentWorkflow.id,\n});\n\nawait reentry.sendEvent({\n  binding: continuation.binding,\n  workflow: {\n    id: currentWorkflow.id,\n    stateVersion: currentWorkflow.stateVersion,\n    canonicalUrl: currentWorkflow.canonicalUrl,\n  },\n});\n```\n\nThe Host backend decides when the real business event happened. Do not expose this endpoint as a\ngeneric Site Tool merely because the initial consent request is a Site Tool.\n\n## What the Host application supplies\n\nThe SDK cannot know the Host application's domain. Before integrating it, identify these values in\nthe Host codebase:\n\n| Host value | Example | Why it matters |\n| --- | --- | --- |\n| Authenticated Host subject | `user_123` | Associates approval with the correct Host user |\n| Stable workflow ID | `order_123` | Tells Codex which business workflow to reopen |\n| Workflow type | `order_review` | Describes the kind of workflow, not a tool command |\n| Monotonic state version | `42` | Prevents a later Event from using stale Host state |\n| Canonical URL | `https://shop.example/orders/order_123` | Gives Codex the exact page to open |\n| Display title/reason | `Review this order later?` | Explains the requested continuation to the person |\n| Grant expiry | ISO-8601 timestamp | Limits how long the approval remains usable |\n| Later business condition | `order.status === \"needs_review\"` | Decides when the Event is actually sent |\n\nThe browser can request an action, but it is not authoritative for any of these values. The server\nmust load the authenticated user, workflow, state version, and binding from Host-controlled state.\n\n## The current protocol objects\n\nThe SDK creates or transports four important objects. A new integration does not need to recreate\nthese schemas by hand; this list explains what the agent is expected to provide.\n\n### Manifest: the first offer\n\nThe Host creates a Manifest before consent. It describes what may happen later:\n\n```js\n{\n  offerExpiresAt: \"2026-09-01T12:00:00.000Z\",\n  workflow: {\n    id: \"order_123\",\n    type: \"order_review\",\n    stateVersion: 1,\n    canonicalUrl: \"https://shop.example/orders/order_123\"\n  },\n  display: {\n    title: \"Let Codex return later?\",\n    reason: \"Codex may review this order after the payment update.\"\n  },\n  grantRequest: {\n    eventType: \"order.needs_review\",\n    grantExpiresAt: \"2026-09-08T12:00:00.000Z\",\n    humanBoundary: \"explicit_receiver_consent\"\n  }\n}\n```\n\n`createManifest` signs this object with the Host private key. The Manifest is an offer, not yet a\nGrant. It does not give Codex permission to act.\n\n### Consent session: the browser handoff\n\n`createConsentSession` sends the signed Manifest from the Host backend to Re-entry. Re-entry returns\nan opaque session and a URL. The Host page opens that URL; it does not decide consent itself.\n\n```js\nconst session = await reentry.createConsentSession({\n  manifest,\n  hostSubjectRef: authenticatedUser.id,\n});\n\n// Safe to return to the browser:\n{\n  consent_url: session.consent_url,\n  consent_session_id: session.consent_session_id\n}\n```\n\nThe Re-entry page authenticates the person's Re-entry account, displays the scope, and lets the\nperson choose an eligible connected Mac. Only approval creates the Receiver-owned Grant and Host\nbinding.\n\n### Binding: the server-side continuation reference\n\nAfter approval, the Host calls `getConsentSession`. The approved response contains the binding that\nthe Host needs for the later Event. Store it in the Host database, associated with the Host user and\nworkflow. Treat it as an opaque server value:\n\n```text\nHost database\n  continuation_id\n  host_subject_id\n  workflow_id\n  binding\n  expires_at\n```\n\nReturn only `continuation_id` to browser code. Do not put the binding in a URL, cookie, local\nstorage, WebMCP result, prompt, log, or client response.\n\n### Event: the later business fact\n\nWhen the Host's real business rule fires, load the binding and current workflow on the server:\n\n```js\nconst continuation = await hostDatabase.continuations.loadForUserAndWorkflow({\n  continuationId,\n  hostUserId: authenticatedUser.id,\n  workflowId: currentWorkflow.id,\n});\n\nconst acceptance = await reentry.sendEvent({\n  binding: continuation.binding,\n  workflow: {\n    id: currentWorkflow.id,\n    stateVersion: currentWorkflow.stateVersion,\n    canonicalUrl: currentWorkflow.canonicalUrl,\n  },\n});\n```\n\n`sendEvent` signs the Event and sends it to Re-entry. An accepted Event creates pending delivery;\nit does not mean that Codex has finished the work or that a Host-side consequence has happened.\n\n## Recommended Next.js integration layout\n\nThe exact filenames may differ, but the responsibilities should remain separate:\n\n```text\napp/\n  _lib/reentry-server.js       server-only SDK instance and helpers\n  api/reentry/consent/route.js creates signed Manifest and consent session\n  api/reentry/consent/status/route.js confirms Receiver approval and stores binding\n  api/reentry/event/route.js   optional authenticated trigger for a later Event\n  components/ReentryAction.jsx Client Component for button + WebMCP Site Tool\n```\n\nKeep the organization key and private key imported only by the server module. A Client Component\nmay call your Host routes with `fetch`, but it must not import `@4xeoz/re-entry-sdk/server`.\n\n### Route 1: create consent\n\nThe browser sends only a trigger, or an empty JSON object. The route should:\n\n1. require the Host application's authenticated user;\n2. load the current workflow from the Host database;\n3. create the signed Manifest with `sdk.createManifest`;\n4. call `sdk.createConsentSession` with the authenticated Host subject; and\n5. return display fields, `consent_url`, and `consent_session_id`.\n\nDo not accept `hostSubjectRef`, `workflow.id`, `stateVersion`, `canonicalUrl`, or a binding from\nthe browser as authoritative input.\n\n### Route 2: confirm consent\n\nThe browser sends only the consent-session ID after the Re-entry popup reports completion. The route\nshould:\n\n1. require the Host application's authenticated user;\n2. load the pending Host-side record for that user and workflow;\n3. call `sdk.getConsentSession` with the session ID;\n4. require `status === \"approved\"`;\n5. store the returned binding in the Host database; and\n6. return a safe `continuation_id`.\n\nThe popup completion message is not proof of approval. The Receiver status response is the source\nof truth.\n\n### Route 3: send the later Event\n\nThis route or background job should be triggered by Host business logic. It should:\n\n1. authenticate and authorize the Host user or internal job;\n2. load the continuation by Host-owned ID;\n3. load the current workflow and current state version;\n4. verify that the continuation belongs to that workflow; and\n5. call `sdk.sendEvent` on the server.\n\nThe later Event route is not the same thing as the consent request. Do not expose it as a generic\nWebMCP Site Tool just because the first consent request is a Site Tool.\n\nThe `@4xeoz/re-entry-sdk/next` entrypoint provides small Route Handler adapters for these server\nboundaries. The callbacks are still responsible for loading Host authentication, workflow state,\nand bindings. `createConsentDecisionRoute` represents the older Host-forwarded decision path and is\nnot the normal account-first browser flow.\n\n## Using Re-entry with any business logic\n\nUse the same two-phase pattern for any domain:\n\n```text\nPHASE 1: PERMISSION\nHost workflow is visible\n  -> person approves one future continuation in Re-entry\n  -> Host stores continuation binding\n\nPHASE 2: BUSINESS EVENT\nHost business condition becomes true\n  -> Host sends signed Event\n  -> Receiver creates one delivery\n  -> Connector opens Codex on the canonical page\n```\n\nExamples:\n\n- **Commerce:** ask once when an order is created; send an Event when payment needs review.\n- **Support:** ask once for a ticket; send an Event when the customer replies.\n- **Travel:** ask once for an itinerary; send an Event when a price or schedule changes.\n- **Procurement:** ask once for a bid; send an Event when a clarification arrives.\n- **Documents:** ask once for a review; send an Event when a required attachment is uploaded.\n\nThe SDK does not inspect an order, ticket, itinerary, bid, or document. The Host application owns\nthat logic. Re-entry only requires a stable workflow identity, current state version, canonical\npage, approved binding, and a later signed Event.\n\n## Browser behavior and WebMCP rules\n\nThe browser integration has two layers:\n\n1. The Host SDK renders a small top-layer dialog branded for Codex/Re-entry. It explains the\n   handoff and gives the person a **Review in Re-entry** button.\n2. Re-entry renders the actual account consent page. It owns sign-in, approval/decline, and device\n   selection.\n\nUse one action for both entry points:\n\n```text\nnormal Host button ────────┐\n                           ├── createReentryConsentAction(...)\ntop-level WebMCP tool ────┘\n```\n\nRules for the Client Component:\n\n- call `createReentryConsentAction` once and reuse the returned function;\n- pass that exact function to the normal button and to `registerReentryWebMcpTool({ execute })`;\n- register the tool from the top-level page with `document.modelContext.registerTool`;\n- use a closed input schema when the action needs no parameters;\n- describe the tool as requesting future consent, not performing the later business action;\n- keep the normal button when WebMCP is unavailable; and\n- let the SDK reject overlapping requests and visible popup-blocker failures.\n\nThe SDK validates the exact Receiver origin, popup window, consent-session ID, and completion\nmessage. It then requires the Host status route to confirm approval. A WebMCP safety review and a\npopup message are not substitutes for the authenticated Re-entry Grant.\n\n## Persistence and ownership checklist\n\nThe Host application must persist enough information to answer these questions after a restart:\n\n1. Which Host user approved the continuation?\n2. Which workflow did they approve?\n3. Which Host-side continuation ID represents it?\n4. Which opaque binding belongs to that continuation?\n5. Is it still active and within the intended Host business lifetime?\n\nThe sample stores the binding in a process-local Map so it is easy to run. That Map is deliberately\nnon-production and is cleared when Next.js stops. A real Host must use its database and must scope\nevery lookup to the authenticated Host user and workflow.\n\nThe Host owns business records and continuation references. Re-entry owns account identity,\nconnected-device identity, Grant state, Event reservation, and delivery state. Do not duplicate\nReceiver authority in the Host database.\n\n## Errors and safe handling\n\nThe SDK uses bounded requests, strict response validation, exact origins, and no automatic retry.\nAn integrating agent should keep failures visible:\n\n- if the user declines or cancels, do not create a continuation;\n- if the popup is blocked, show the user how to allow it and let them retry;\n- if WebMCP is unavailable, keep the normal button available;\n- if Receiver status is not approved, do not store a binding or return success;\n- if a Manifest, binding, workflow, or state version is stale, show a typed failure;\n- if `sendEvent` times out, do not blindly create a second Event without deciding how the Host will\n  inspect or safely reconcile the unknown outcome; and\n- treat a `202` Event acceptance as “Receiver accepted the Event,” not “Codex completed the task.”\n\nThe Host should add its own authentication, authorization, CSRF protection, rate limiting, audit\nlogging, and production secret rotation. Those are Host/deployment responsibilities, not hidden SDK\nfallbacks.\n\n## Integration definition of done\n\nAn agent should consider the integration complete only when all of these are true:\n\n1. `@4xeoz/re-entry-sdk/server` is used only in server code.\n2. The organization key and Host private key are loaded from server secret configuration.\n3. The Host public key is registered with the Receiver.\n4. A real Host user and current workflow create the Manifest server-side.\n5. The normal button and WebMCP Site Tool call the same browser action.\n6. The Re-entry popup is opened from the SDK handoff and approval is confirmed server-to-server.\n7. The binding is stored only in the Host database and never returned to browser JavaScript.\n8. The later business rule sends the Event from the Host backend, not from the browser.\n9. The canonical page exposes the current Host state and the tools Codex is allowed to use.\n10. Decline, popup blocking, WebMCP absence, stale state, and Receiver failure remain visible.\n11. The Host tests the path with a connected Local Connector, while keeping Browser/WebMCP and final\n    Host-effect claims separate from SDK unit-test evidence.\n\n## Verification for an integrating agent\n\nFrom the SDK package directory:\n\n```sh\nnpm run verify\n```\n\nFor the included sample:\n\n```sh\ncd app\nnpm install\nnpm run build\nnpm run dev\n```\n\nThen test both paths:\n\n1. Open the sample in an ordinary browser and click the normal button.\n2. Complete the Re-entry account consent and connected-Mac selection.\n3. Confirm that the Host status route returns a continuation ID but not a binding.\n4. Use the sample's separate later-event control or a real Host business transition.\n5. Run the Connector's `claim-once` path and inspect that a fresh Codex process starts.\n6. In a compatible built-in Browser, verify Site Tool discovery separately from ordinary browser\n   button behavior.\n\nThe current sample's process-local binding store resets on restart. The current Local Connector\nstarts a fresh `codex exec` session; it does not prove an existing Codex Browser-session attachment,\ncross-machine reliability, production deployment, or final Host-effect verification.\n\n## Run the included Next.js sample (historical receiver integration)\n\n> This sample's Receiver configuration targets the deprecated local Cloud Receiver. Keep the\n> sample for SDK contract evidence only; do not use its setup steps for a new production\n> integration until a replacement Receiver is accepted.\n\nThe sample page uses the same function for its button and `request_codex_reentry`, confirms consent\nserver-side, retains the opaque binding in a process-local demo store, and exposes a separate button\nthat simulates the later business event.\n\n1. Start the historical local Cloud Receiver preview:\n\n   ```sh\n   cd runtime/cloud-receiver\n   npm install\n   npm start\n   ```\n\n   It listens on `http://127.0.0.1:43224` by default. This is a historical replay only. Create a Re-entry account, an organization,\n   an organization API key, and a connected Mac before testing the Host sample. The Receiver is a\n   local preview; it is not a production identity or deployment environment.\n\n2. Start the Local Connector on the Mac where Codex should open. From the repository root or the\n   intended Host project directory, use an absolute Codex workspace:\n\n   ```sh\n   npx @4xeoz/re-entry install \\\n     --receiver http://127.0.0.1:43224 \\\n     --codex-cd /absolute/path/to/your/project\n   ```\n\n   Complete the Re-entry account approval in the browser. The Connector stores its own local\n   credential and polls the Receiver; the Host API key never goes to the Connector.\n\n3. Put these server-only values in `runtime/host-sdk/app/.env.local`:\n\n```dotenv\nHOST_ORIGIN=http://127.0.0.1:43220\nRECEIVER_ORIGIN=http://127.0.0.1:43224\nREENTRY_HOST_ID=host_sdk_demo\nREENTRY_KEY_ID=host_key_sdk_demo\nREENTRY_ORGANIZATION_API_KEY=re_org_replace_me\nREENTRY_PRIVATE_KEY=\"-----BEGIN PRIVATE KEY-----\nreplace-with-an-ed25519-private-key\n-----END PRIVATE KEY-----\"\n```\n\n4. Run the app:\n\n```sh\ncd runtime/host-sdk/app\nnpm install\nnpm run dev\n```\n\nOpen `http://127.0.0.1:43220`. In a compatible Codex built-in Browser, inspect Site Tools for\n`request_codex_reentry`; in an ordinary browser, click **Approve a future return**. The sample's\nprocess-local continuation store is deliberately non-production and clears when Next.js stops.\n\n## Give this to a coding agent\n\n```text\nIntegrate the reusable Re-entry Host SDK into this Host application. First read\nruntime/host-sdk/README.md and the example under runtime/host-sdk/app. Do not target the deprecated\nruntime/cloud-receiver package or its former hosted alias; use only a separately accepted Receiver\norigin. Inspect the Host's existing\nauthentication, workflow model, canonical page, and database before editing. Install\n@4xeoz/re-entry-sdk in the Host application; import its server and Next entrypoints only from\nserver code, and its client entrypoint only from a top-level Client Component. Keep the organization\nAPI key and Ed25519 private key in server secret configuration and out of browser code, logs, prompts,\nand git. Add a server-only SDK module. Add one authenticated route that loads the current Host user\nand workflow, creates a signed Manifest, and creates a Re-entry consent session. Add a second route\nthat receives only a consent-session ID, re-reads Receiver status, requires approved status, stores\nthe binding against the authenticated Host user and workflow, and returns only a safe continuation\nID. In the top-level Client Component, create one function with createReentryConsentAction; call\nthat exact function from the normal UI and pass it as execute to registerReentryWebMcpTool. Keep the\nSite Tool schema closed and describe that it requests future consent; it must not trigger the later\nbusiness event. When the real business condition occurs, load the binding and current workflow on\nthe Host server and call reentry.sendEvent. Do not trust browser-supplied identity, workflow state,\nbinding, or consent. Do not use the legacy Host-forwarded consent-decision path for the new flow.\nPreserve the normal UI when WebMCP is unavailable. Run the SDK tests and Host build, then report\nBrowser/WebMCP, Connector, deployment, and final Host-effect evidence separately.\n```\n\nVerify with Node 24:\n\n```sh\nnpm run verify\ncd app && npm run build\n```\n\n<details>\n<summary>Legacy Host-forwarded consent notes</summary>\n\nThe material below describes the superseded Host-forwarded decision preview and is kept only for\ntraceability.\n\nThis is the smallest reusable Host integration package around `reentry-core`. It is compatible\nwith Next.js because it uses standard server `Request`/`Response` objects and does not depend on\nReact or Next at runtime.\n\nIt has three deliberate entrypoints:\n\n| Entry point | Runs in | Job |\n| --- | --- | --- |\n| `@4xeoz/re-entry-sdk/server` | Host server only | Register a Host key; sign Manifests and Events; create consent sessions; send decisions and Events to Reentry |\n| `@4xeoz/re-entry-sdk/client` | Browser only | Render a small top-layer consent-looking prompt |\n| `@4xeoz/re-entry-sdk/next` | Next.js server route | Turn the server methods into `GET`/`POST` handlers |\n\nThe browser entrypoint never sees the Host private key. The prompt is only UI: it returns\n`{ action: \"approve\" }` or `{ action: \"decline\" }`; it does not create a Grant or replace the\nReceiver's consent authority. The Host server must send that action through the decision route.\n\n## Publish the SDK\n\nThe package name is `@4xeoz/re-entry-sdk`, and the reusable core is bundled inside the tarball. From this\ndirectory, publish it with an npm account that owns the name:\n\n```sh\nnpm login\nnpm publish --access public\n```\n\nDevelopers can then install it with:\n\n```sh\nnpm install @4xeoz/re-entry-sdk\n```\n\n## Get started\n\nUse this flow to hook the host app into Reentry quickly.\n\n### 1) Install\n\nFor local development, use the checkout directly:\n\n```sh\ncd /path/to/OpenAI-Web-MCP-Challenge/runtime/host-sdk\nnpm install\n```\n\n### 2) Configure your server environment\n\nSet these values in your host server environment only (never in browser/client code):\n\n```sh\nHOST_ORIGIN=https://your-app.example\nRECEIVER_ORIGIN=https://your-reentry.example\nREENTRY_KEY_ID=host_key_your_app\nREENTRY_PRIVATE_KEY=your_host_private_key\nREENTRY_ORGANIZATION_API_KEY=re_org_...\n```\n\n### 3) Create the Host SDK instance\n\nCall this from a Node server module or Next.js route handler:\n\n```ts\nimport { createHostSdk } from \"@4xeoz/re-entry-sdk/server\";\n\nexport const reentry = createHostSdk({\n  origin: process.env.HOST_ORIGIN!,\n  receiverOrigin: process.env.RECEIVER_ORIGIN!,\n  privateKey: process.env.REENTRY_PRIVATE_KEY!,\n  keyId: process.env.REENTRY_KEY_ID!,\n  organizationApiKey: process.env.REENTRY_ORGANIZATION_API_KEY!,\n});\n```\n\n<details>\n<summary>Quick design check</summary>\n\n```sh\ncd runtime/host-sdk\nnpm run verify\n```\n</details>\n\n`privateKey` and the Receiver origin configuration belong in the Host server environment. Do not\nimport `server` from a Client Component.\n\nThe server object exposes these operations:\n\n```js\nawait reentry.registerHostKey({ hostId: \"host_001\" });\nconst manifest = reentry.createManifest(hostOwnedManifestFields);\nconst session = await reentry.createConsentSession({\n  manifest,\n  hostSubjectRef: authenticatedUser.id,\n});\nconst status = await reentry.getConsentSession({\n  consentSessionId: session.consent_session_id,\n});\nconst acceptance = await reentry.sendEvent(hostOwnedEventFields);\n```\n\n`registerHostKey` sends only the derived public key. `createManifest` produces the signed offer.\n`createConsentSession` sends the signed Manifest to Reentry and returns a public challenge plus a\nconsent URL containing one opaque token. The human decides on the Receiver-owned consent page; the\nactive v2 Host server only confirms the resulting session through `getConsentSession`. `decideConsent` is a\nretained compatibility method for the retired preview and is not an active-v2 browser or Host\ndecision route. `sendEvent` signs an Event and calls `POST /v0.1/events`. None of these calls retry\nautomatically.\n\nThe organization API key is accepted only by the server entrypoint. It is never part of the\nbrowser bundle or the Browser SDK prompt.\n\n## Next.js route handlers\n\n`app/api/reentry/manifest/route.js`:\n\n```js\nimport { createManifestRoute } from \"@4xeoz/re-entry-sdk/next\";\nimport { sdk } from \"../sdk\";\n\nexport const GET = createManifestRoute({\n  sdk,\n  async getManifestInput() {\n    return loadManifestFieldsFromHostDatabase();\n  },\n});\n```\n\n`app/api/reentry/event/route.js`:\n\n```js\nimport { createEventRoute } from \"@4xeoz/re-entry-sdk/next\";\nimport { sdk } from \"../sdk\";\n\nexport const POST = createEventRoute({\n  sdk,\n  async getEventInput({ body }) {\n    const workflow = await loadCurrentWorkflowFromHostDatabase();\n    const binding = await loadBindingFromHostDatabase(body.bindingId);\n    return {\n      binding,\n      workflow: {\n        id: workflow.id,\n        stateVersion: workflow.stateVersion,\n        canonicalUrl: workflow.canonicalUrl,\n      },\n    };\n  },\n});\n```\n\n`app/api/reentry/consent/route.js`:\n\n```js\nimport { createConsentSessionRoute } from \"@4xeoz/re-entry-sdk/next\";\nimport { sdk } from \"../sdk\";\n\nexport const POST = createConsentSessionRoute({\n  sdk,\n  async getConsentSessionInput() {\n    const user = await requireAuthenticatedUser();\n    const fields = await loadManifestFieldsFromHostDatabase();\n    return {\n      manifest: sdk.createManifest(fields),\n      hostSubjectRef: user.id,\n    };\n  },\n});\n```\n\n`app/api/reentry/consent/decision/route.js`:\n\n```js\nimport { createConsentDecisionRoute } from \"@4xeoz/re-entry-sdk/next\";\nimport { sdk } from \"../sdk\";\n\nexport const POST = createConsentDecisionRoute({\n  sdk,\n  async getConsentDecisionInput({ body }) {\n    const user = await requireAuthenticatedUser();\n    return {\n      challengeId: body.challenge_id,\n      consentToken: body.consent_token,\n      hostSubjectRef: user.id,\n      action: body.action,\n    };\n  },\n});\n```\n\nThe callbacks are the important connection points: they load the current Host user and current\nManifest/session from the Host server. The browser request is only a trigger and must not be the\nsource of identity, binding, current state, or consent token authority.\n\nThe callback is the important connection point: the Host server loads current authoritative state\nand the opaque binding, then the SDK signs exactly that data. Do not accept a binding or state\nversion from the browser as proof of authority.\n\n## Browser prompt\n\nUse this from a Client Component or browser event handler:\n\n```js\n\"use client\";\n\nimport { createContinuationPrompt } from \"@4xeoz/re-entry-sdk/client\";\n\nconst prompt = createContinuationPrompt();\nconst session = await (await fetch(\"/api/reentry/consent\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({ trigger: \"workflow-ready\" }),\n})).json();\nconst decision = await prompt.show({\n  title: session.challenge.display.title,\n  reason: session.challenge.display.reason,\n});\n\nawait fetch(\"/api/reentry/consent/decision\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    challenge_id: session.challenge.challenge_id,\n    consent_token: session.consent_token,\n    action: decision.action,\n  }),\n});\n```\n\nThe local Cloud Receiver preview now includes the consent-session routes. The prompt remains a\nHost-page UI and is not a trusted Reentry-origin UI: the browser sees only the public challenge and\nopaque token, while the Host server supplies the authenticated subject, owns the organization key,\nand forwards the decision to Reentry. Production still needs authenticated user sessions, CSRF\nprotection, rate limits, key rotation, and a selected deployment identity model.\n\n## What to test first\n\n1. Run `npm run verify` in this directory.\n2. Read `test/host-sdk.test.mjs` to see the exact signed request sent to the Receiver.\n3. Read `test/next.test.mjs` to see the Next route boundary.\n4. In a browser, call `createContinuationPrompt().show(...)` from a Client Component and click both\n   buttons.\n\nThis is a local-preview SDK. It intentionally does not implement account or organization\nadministration, production consent identity, billing, deployment, Host-effect verification, or\nAgent activation.\n\n</details>\n","readmeFilename":"README.md"}