@pushary/server 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -174,6 +174,19 @@ const { webhookSecret } = await pushary.decisions.getWebhookSecret()
174
174
  const ok = verifyWebhookSignature(rawBody, signatureHeader, webhookSecret)
175
175
  ```
176
176
 
177
+ ### List decisions
178
+
179
+ `decisions.list()` returns customer decisions newest first, within your plan's retention window. Bound keys only see their own customer. Sandbox tests and Pushary's internal preview/onboarding recipients are excluded. Status is `pending`, `answered`, `expired`, or `cancelled`.
180
+
181
+ ```ts
182
+ const page = await pushary.decisions.list({ externalId: 'user-123', limit: 50 })
183
+ const next = page.nextCursor
184
+ ? await pushary.decisions.list({ externalId: 'user-123', limit: 50, cursor: page.nextCursor })
185
+ : null
186
+ ```
187
+
188
+ Pass `nextCursor` unchanged with the same filters. It is opaque; malformed cursors return HTTP 400.
189
+
177
190
  ### Helpers
178
191
 
179
192
  Four exports that the flows above rely on. Signatures are given because guessing
package/dist/adapters.cjs CHANGED
@@ -279,6 +279,7 @@ var createDecisionsResource = (request) => {
279
279
  return Object.freeze({
280
280
  create: (data) => request("POST", "/decisions", data),
281
281
  get: (id, opts) => request("GET", `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : void 0),
282
+ list: (opts) => request("GET", "/decisions", opts ? { ...opts } : void 0),
282
283
  ask,
283
284
  answer: (id, answer) => request("POST", `/decisions/${id}`, { answer }),
284
285
  cancel: (id) => request("DELETE", `/decisions/${id}`),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters.ts","../src/error.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/util.ts","../src/resources/keys.ts","../src/authorize.ts","../src/permits.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["// The shared kernel behind every Pushary framework adapter. Published as\n// `@pushary/server/adapters` so a new adapter is a thin binding — map the\n// framework's context onto these shapes, map the result back — instead of another\n// copy of the same ~150 lines. Nothing here imports a framework.\n\nimport { randomUUID } from 'node:crypto'\nimport { createPusharyServer, type PusharyServer } from './client'\nimport { decisionFingerprint, isApproved } from './util'\nimport { parseDecisionCallback, verifyWebhookSignature } from './webhook'\nimport type {\n AskResult,\n AuthorizationBinding,\n AuthorizationEvaluation,\n AuthorizationVerdict,\n ConsumeAuthorizationResult,\n DecisionParameters,\n DecisionSubject,\n DecisionType,\n EnrollResult,\n ExecutionOutcome,\n} from './types'\n\n/** Config every adapter accepts, whatever framework it binds. */\nexport interface PusharyAdapterConfig {\n /** Pushary API key (pk_xxx.sk_xxx). Defaults to `process.env.PUSHARY_API_KEY`. */\n readonly apiKey?: string\n /** Shown on the approval so the human knows which agent is asking. */\n readonly agentName?: string\n /** How long a blocking ask waits before returning (default 55s, serverless-safe). */\n readonly timeoutMs?: number\n /** Override the API base URL (tests / self-host). */\n readonly baseUrl?: string\n}\n\n/** One ask, as an adapter hands it to the kernel. */\nexport interface AskHumanInput extends DecisionSubject {\n /** Stable business operation ID for retries; required when parking a durable workflow. */\n readonly idempotencyKey?: string\n readonly question: string\n /** Your own stable id for the end-user who decides. */\n readonly externalId: string\n /** Stable node/step/tool name, used to build a re-run-safe idempotency key. */\n readonly node?: string\n readonly type?: DecisionType\n readonly options?: readonly string[]\n readonly context?: string\n /** Present on the durable path; Pushary POSTs the answer here. */\n readonly callbackUrl?: string\n readonly expiresInSeconds?: number\n readonly requireReachable?: boolean\n readonly placeholder?: string\n}\n\n/** A decision opened durably, before the framework parks its run. */\nexport interface CreatedDecision {\n readonly decisionId: string\n readonly correlationId: string\n readonly status: string\n readonly reachable?: boolean\n readonly reachableChannels?: number\n readonly deviceCount?: number\n}\n\n/** A verified, parsed answer callback, keyed by `correlationId`. */\nexport interface PusharyCallback {\n readonly correlationId: string\n readonly answer: string\n readonly value: string\n readonly approved: boolean\n readonly context?: string\n readonly answeredAt: string\n}\n\n/**\n * What the human is shown when the caller names no question.\n *\n * The action and its target, not a rendering of the facts: a protected action is\n * written by hand, so its name is already business language (\"refund.create\n * order_4471\") in a way a tool's raw input never is.\n */\nexport const defaultProtectQuestion = (action: string, target?: string): string =>\n `Approve ${action}${target ? ` ${target}` : ''}?`\n\n/**\n * Only an explicit operation key may replay an approval. Independent asks get\n * fresh keys even when their question and recipient are identical.\n */\nexport const idempotencyKeyFor = (input: AskHumanInput): string =>\n input.idempotencyKey ?? randomUUID()\n\nconst decisionSubject = (input: AskHumanInput) => ({\n toolTarget: input.toolTarget,\n actor: input.actor,\n environment: input.environment,\n parameters: input.parameters,\n presentation: input.presentation,\n approvalUrl: input.approvalUrl,\n expiresInSeconds: input.expiresInSeconds,\n requireReachable: input.requireReachable,\n placeholder: input.placeholder,\n callbackUrl: input.callbackUrl,\n})\n\n/** Fail-closed yes/no check for a confirm answer. */\nexport const isAffirmative = (answer: string | null | undefined): boolean =>\n isApproved({ status: 'answered', type: 'confirm', value: answer ?? null })\n\n/** Turn a decision outcome into an unambiguous instruction for the model. */\nexport const describeAnswer = (type: DecisionType, result: AskResult): string => {\n if (!result.answered) {\n return `No answer (status: ${result.status}). Treat this as NOT approved and do not proceed.`\n }\n if (type === 'confirm') {\n return result.approved\n ? 'The human approved. You may proceed.'\n : 'The human declined. Do not proceed.'\n }\n return `The human answered: ${result.value ?? ''}`\n}\n\n/**\n * Verify a callback signature and parse it, or return null. The `answer` is what\n * you feed back into the framework's resume seam.\n */\nexport const resolvePusharyCallback = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): PusharyCallback | null => {\n if (!verifyWebhookSignature(rawBody, signature, secret)) return null\n const callback = parseDecisionCallback(rawBody)\n if (!callback) return null\n return {\n correlationId: callback.correlationId,\n answer: callback.answer,\n value: callback.value,\n approved: isAffirmative(callback.answer),\n context: callback.context,\n answeredAt: callback.answeredAt,\n }\n}\n\n/**\n * Connection-level options for a request-time approval gate. Everything that\n * varies per tool call lives on {@link ApprovalAsk} instead, so a gate holds one\n * client and no per-call state.\n */\nexport interface PusharyGateConfig {\n /** Pushary API key. Defaults to `process.env.PUSHARY_API_KEY`. */\n readonly apiKey?: string\n /** Shown on the approval so the human knows which agent is asking. */\n readonly agentName?: string\n /** How long the decision stays answerable. */\n readonly expiresInSeconds?: number\n /** How long to block waiting for an answer before failing closed. */\n readonly timeoutMs?: number\n /**\n * Refuse to open a decision nobody can receive, so an end-user with no connected\n * device is denied at request time instead of silently expiring.\n */\n readonly requireReachable?: boolean\n /**\n * Ask the site's policy before asking a person (default true). A rule that names\n * the action resolves it without paging anyone; everything else still asks.\n *\n * Set false to restore the always-ask gate: a site whose rules were written for\n * its own coding agents, and which happens to name a tool this gate also sits\n * on, would otherwise start resolving that tool automatically.\n */\n readonly policy?: boolean\n /** Override the API base URL (tests / self-host). */\n readonly baseUrl?: string\n}\n\n/**\n * One gated tool call, resolved down to what Pushary needs. The adapter owns the\n * mapping from its framework's context, so per-call resolvers stay typed against\n * the framework's own shapes rather than a lowest common denominator.\n */\nexport interface ApprovalAsk {\n /** The tool the model wants to run. */\n readonly toolName: string\n /** Unique per tool call. Keys the decision together with `sessionId`. */\n readonly callId: string\n /** Stable per run, session, or thread. */\n readonly sessionId: string\n /** What the human is shown. */\n readonly question: string\n /** The enrolled end-user who decides. */\n readonly externalId: string\n /** What the tool acts on, e.g. an order id. Narrows which rule governs. */\n readonly toolTarget?: string\n /** Whose authority the action claims, e.g. `user:u_44`. Not the approver. */\n readonly actor?: string\n /** Which deployment the action runs against, e.g. `production`. */\n readonly environment?: string\n /**\n * The tool's raw input. Bounded scalars are derived from it so a rule can decide\n * on the arguments and not only on the name; anything the bounds cannot carry\n * sends nothing, which escalates. Already rendered into the default question, so\n * passing it exposes nothing new.\n */\n readonly input?: unknown\n /** Explicit facts for policy, used instead of deriving them from `input`. */\n readonly parameters?: DecisionParameters\n readonly presentation?: DecisionSubject['presentation']\n readonly context?: string\n}\n\n/**\n * What a gate answers. Denials always carry a reason the model can read.\n *\n * An approval also carries the binding: which authorization settled it and the\n * exact subject it was recorded against. Carried rather than rebuilt, because\n * only the gate knows what it actually sent — the ask records the full subject\n * only when the evaluation succeeded — and a caller that rebuilds it would send\n * a subject the record does not hold and be refused for its own approval.\n * Absent only from a server too old to name the authorization.\n */\nexport type ApprovalDecision =\n | { readonly approved: true; readonly authorization?: AuthorizationBinding }\n | { readonly approved: false; readonly reason: string }\n\n/**\n * A request-time gate: ask the site's policy, ask a person when policy defers,\n * fail closed either way.\n */\nexport type ApprovalGate = (ask: ApprovalAsk) => Promise<ApprovalDecision>\n\nconst MAX_INPUT_CHARS = 300\n\nexport const DENIED_UNANSWERED =\n 'No answer from the approver, so this was denied. Do not retry the same action.'\nexport const DENIED_REFUSED = 'The approver denied this action.'\nexport const DENIED_BY_POLICY = 'Denied by policy. Do not retry the same action.'\n\nexport const UNBOUND_APPROVAL =\n 'This was approved, but the approval could not be bound to a single execution, so it was not run. Do not retry the same action.'\n\nexport const UNCONFIRMED_PERMIT =\n 'This was approved, but it could not be confirmed as still unspent, so it was not run. Ask again rather than retrying this one.'\n\nconst REFUSED_REPLAY =\n 'This authorization was already used. The action has run once and must not run again.'\n\n/**\n * A refused permit, as the model reads it.\n *\n * `already_consumed` is worded to stop the caller rather than to invite a retry,\n * because a retry is the failure: something already ran this action once, and\n * the agent reading this cannot see that from its own state.\n */\nexport const refusedPermit = (\n refused: Extract<ConsumeAuthorizationResult, { consumed: false }>,\n): string => (refused.refusal === 'already_consumed' ? REFUSED_REPLAY : `${refused.reason} Do not retry the same action.`)\n\n/**\n * A policy denial, as the model reads it. The server already words the reason\n * (\"Denied by policy rule refund.create.\"); the retry instruction is added here so\n * every denial this gate returns ends the same way, whoever settled it.\n */\nexport const deniedByPolicy = (evaluation: AuthorizationEvaluation): string =>\n evaluation.reason ? `${evaluation.reason} Do not retry the same action.` : DENIED_BY_POLICY\n\nconst PARAMETERS_MAX_KEYS = 32\nconst PARAMETER_KEY_MAX_LENGTH = 64\nconst PARAMETER_VALUE_MAX_LENGTH = 200\n\n/**\n * Bounded facts for policy, derived from a tool's own input.\n *\n * All or nothing, deliberately. The bounds mirror what the API accepts, so a\n * partial object is always representable — and that is exactly the danger: a rule\n * reading `amount >= 500` stops denying the moment `amount` goes missing, so\n * dropping one entry can only ever move a verdict toward allow. Sending nothing\n * instead leaves the rule's parameter absent, which escalates to a person.\n *\n * The same bounds also mean this never turns a working tool call into a rejected\n * one: an input this cannot carry is simply not sent.\n */\nexport const deriveParameters = (input: unknown): DecisionParameters | undefined => {\n if (input === null || typeof input !== 'object' || Array.isArray(input)) return undefined\n const entries = Object.entries(input as Record<string, unknown>)\n if (entries.length === 0 || entries.length > PARAMETERS_MAX_KEYS) return undefined\n\n const parameters: Record<string, string | number | boolean> = {}\n for (const [key, value] of entries) {\n if (key.length === 0 || key.length > PARAMETER_KEY_MAX_LENGTH) return undefined\n if (typeof value === 'boolean') {\n parameters[key] = value\n } else if (typeof value === 'number') {\n if (!Number.isFinite(value)) return undefined\n parameters[key] = value\n } else if (typeof value === 'string') {\n if (value.length > PARAMETER_VALUE_MAX_LENGTH) return undefined\n parameters[key] = value\n } else {\n return undefined\n }\n }\n return parameters\n}\n\n/**\n * The default question: the tool name plus a truncated view of its input, so the\n * human sees what they are approving without a wall of JSON on a lock screen.\n */\nexport const renderApprovalQuestion = (toolName: string, toolInput: unknown): string => {\n if (toolInput === undefined || toolInput === null) return `Approve ${toolName}?`\n const summary = summarise(toolInput)\n return summary === undefined ? `Approve ${toolName}?` : `Approve ${toolName}? ${summary}`\n}\n\nconst summarise = (toolInput: unknown): string | undefined => {\n let rendered: string | undefined\n if (typeof toolInput === 'string') {\n rendered = toolInput\n } else {\n try {\n // Returns undefined for a function or a symbol, and throws on a cycle. Neither\n // is a reason to fail the tool call: a gate that cannot render its input still\n // has to ask, so fall back to the bare tool name.\n rendered = JSON.stringify(toolInput)\n } catch {\n return undefined\n }\n }\n if (rendered === undefined) return undefined\n return rendered.length > MAX_INPUT_CHARS ? `${rendered.slice(0, MAX_INPUT_CHARS)}...` : rendered\n}\n\n\n/**\n * One protected action: what it is, who it belongs to, and what it does.\n *\n * The shape a developer writes once instead of threading authorize, escalate and\n * run through their business code by hand. Everything above `run` is the same\n * subject `authorize()` and the decision both read, so the rule that governs the\n * action and the sentence the human sees describe the one thing.\n */\nexport interface ProtectedAction<TResult> {\n /** What is being done, e.g. `refund.create`. A rule has to name this. */\n readonly action: string\n /** What it is done to, e.g. an order id. Narrows which rule governs. */\n readonly target?: string\n /** The end-user this action belongs to, and who decides if policy defers. */\n readonly externalId: string\n /** Whose authority the action claims, e.g. `user:u_44`. Not the approver. */\n readonly actor?: string\n /** Which deployment it runs against, e.g. `production`. */\n readonly environment?: string\n /**\n * The bounded facts a rule may decide on, e.g. `{ amount: 4800 }`. Scalars\n * only. Passed as given rather than derived, because a protected action is\n * written by hand and the author knows which of its arguments matter.\n */\n readonly facts?: DecisionParameters\n /** Unique per attempt. Keys the decision together with `runId`. */\n readonly callId: string\n /** Stable per run, session or thread. */\n readonly runId: string\n /** What the human is shown. Defaults to the action and its target. */\n readonly question?: string\n /**\n * The side effect. Called only after the action is authorized, and never on a\n * denial or an unanswered escalation.\n */\n readonly run: () => Promise<TResult>\n}\n\n/**\n * What a protected action did.\n *\n * A discriminated union rather than a thrown error, because \"policy said no\" is\n * an ordinary outcome an agent has to be able to read and report, not an\n * exception. An error thrown by `run` itself is NOT caught: that is the caller's\n * failure, about their side effect, and swallowing it here would turn a failed\n * refund into a silent success.\n */\nexport type ProtectResult<TResult> =\n | { readonly ok: true; readonly result: TResult }\n | { readonly ok: false; readonly reason: string }\n\n/** Protect one action. Build it once per configuration, call it per action. */\nexport type Protect = <TResult>(action: ProtectedAction<TResult>) => Promise<ProtectResult<TResult>>\n\n/**\n * The calls an adapter makes, bound to one framework's name so a missing key or a\n * missing end-user says which helpers to configure. Build one per adapter module.\n */\nexport interface AdapterKernel {\n /** The underlying client, for anything the kernel does not cover. */\n readonly client: (config: PusharyAdapterConfig) => PusharyServer\n /**\n * Blocking ask: create a decision and poll durably until the human answers or\n * the deadline passes. A fresh key is used unless the caller supplies an operation-specific idempotencyKey.\n */\n readonly askExternalUser: (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ) => Promise<AskResult>\n /**\n * Durable create: open a decision with a `callbackUrl` and return at once, for\n * a framework that parks its run and resumes on the webhook.\n */\n readonly createDurableDecision: (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ) => Promise<CreatedDecision>\n /** Connect one end-user's phone (keyless). Show them the returned link. */\n readonly connect: (config: PusharyAdapterConfig, externalId: string) => Promise<EnrollResult>\n /**\n * Build a request-time approval gate. The client is created here, so a missing\n * key throws where the gate is defined rather than on the first tool call.\n */\n readonly createGate: (config: PusharyGateConfig) => ApprovalGate\n /**\n * Wrap one action so authorization, escalation and execution are one call.\n *\n * The whole of it is `createGate` plus \"run it if the answer was yes\", which is\n * the point: there is no second policy engine, no durable runtime and no new\n * abstraction under this. An adapter binds it by mapping its framework's\n * context onto ProtectedAction, exactly as it already does for the gate.\n *\n * At most once. The decision is idempotent — a replay of the same\n * `runId` + `callId` + action resolves to the same approval instead of asking\n * twice — and the approval is then spent against a durable permit before the\n * side effect runs. A retry, a concurrent worker and a resumed run all reach\n * the same permit, and exactly one of them proceeds; the rest are refused with\n * a reason that tells the model to stop rather than try again.\n *\n * `run` is called only after the permit is spent, never before, so the failure\n * mode of a crash mid-action is a permit with no receipt — which is visible —\n * rather than an action that runs twice, which is not recoverable.\n */\n readonly protect: (config: PusharyGateConfig) => Protect\n /**\n * The end-user to ask, or a clear error naming these helpers. Adapters call this\n * after applying their own resolver and the framework's own default.\n */\n readonly requireExternalId: (externalId: string | undefined) => string\n}\n\n/**\n * Build the kernel for one adapter.\n *\n * `helpersLabel` names the adapter in the missing-key error, so the message points\n * at the helpers the caller is actually using (\"the LangGraph helpers\").\n *\n * ```ts\n * const kernel = createAdapterKernel('the Acme helpers')\n * export const askHuman = kernel.askExternalUser\n * ```\n */\nexport const createAdapterKernel = (helpersLabel: string): AdapterKernel => {\n const requireApiKey = (apiKey: string | undefined): string => {\n const resolved = apiKey ?? process.env.PUSHARY_API_KEY\n if (!resolved) {\n throw new Error(`Pushary: set PUSHARY_API_KEY or pass { apiKey } to ${helpersLabel}.`)\n }\n return resolved\n }\n\n const client = (config: PusharyAdapterConfig): PusharyServer =>\n createPusharyServer({\n apiKey: requireApiKey(config.apiKey),\n ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),\n })\n\n const requireExternalId = (externalId: string | undefined): string => {\n if (!externalId?.trim()) {\n throw new Error(\n `Pushary: no end-user to ask. Pass { externalId } to ${helpersLabel}, or run ` +\n 'user-scoped auth so the framework supplies one.',\n )\n }\n if (externalId.length > 256) throw new Error('Pushary: externalId must not exceed 256 characters.')\n return externalId\n }\n\n const createGate = (config: PusharyGateConfig): ApprovalGate => {\n const gateClient = createPusharyServer({\n apiKey: requireApiKey(config.apiKey),\n ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),\n })\n const usePolicy = config.policy ?? true\n\n /**\n * The verdict, or null if there is no usable one.\n *\n * Every failure is null rather than a throw. This route is entitled to the\n * Partner plan and is newer than the published adapters, so a caller who\n * cannot reach it — wrong plan, older deployment, a network blip — has to keep\n * working exactly as it did before policy existed, which is by asking a person.\n * Null is never allow, so the fail-safe direction is preserved.\n */\n const evaluate = async (\n ask: ApprovalAsk,\n facts: DecisionParameters | undefined,\n ): Promise<AuthorizationEvaluation | null> => {\n if (!usePolicy) return null\n try {\n return await gateClient.evaluateAuthorization({\n toolName: ask.toolName,\n toolTarget: ask.toolTarget,\n actor: ask.actor,\n environment: ask.environment,\n parameters: facts,\n externalId: ask.externalId,\n question: ask.question,\n ...(config.agentName ? { agentName: config.agentName } : {}),\n })\n } catch {\n return null\n }\n }\n\n return async (ask: ApprovalAsk): Promise<ApprovalDecision> => {\n if (!ask.callId?.trim()) throw new Error('Pushary: a stable tool call ID is required to resolve an approval.')\n requireExternalId(ask.externalId)\n // Derived once. The evaluation and the ask that may follow have to describe\n // the same action, and computing it twice is two chances for them not to.\n const facts = ask.parameters ?? deriveParameters(ask.input)\n const idempotencyKey = decisionFingerprint({ ...ask, parameters: facts })\n const evaluation = await evaluate(ask, facts)\n const includeSubject = evaluation !== null || !usePolicy\n if (evaluation?.verdict === 'allow') {\n return {\n approved: true,\n // Exactly the subject POST /authorize recorded, which is the whole of\n // what it was sent.\n ...(evaluation.authorizationId\n ? {\n authorization: {\n authorizationId: evaluation.authorizationId,\n toolName: ask.toolName,\n toolTarget: ask.toolTarget,\n actor: ask.actor,\n environment: ask.environment,\n externalId: ask.externalId,\n parameters: facts,\n },\n }\n : {}),\n }\n }\n if (evaluation?.verdict === 'deny') {\n return { approved: false, reason: deniedByPolicy(evaluation) }\n }\n\n const result = await gateClient.decisions.ask({\n question: ask.question,\n type: 'confirm',\n externalId: ask.externalId,\n context: ask.context,\n ...(config.agentName ? { agentName: config.agentName } : {}),\n ...(config.expiresInSeconds ? { expiresInSeconds: config.expiresInSeconds } : {}),\n ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),\n ...(config.requireReachable ? { requireReachable: config.requireReachable } : {}),\n // The gated tool, recorded on the decision. The gate already knows it\n // and keyed on it below; sending it is what lets the decision be grouped\n // and matched later instead of surviving only as prose in the question.\n toolName: ask.toolName,\n ...(includeSubject\n ? {\n presentation: ask.presentation,\n toolTarget: ask.toolTarget,\n actor: ask.actor,\n environment: ask.environment,\n parameters: facts,\n }\n : {}),\n // Stable across replays, so a re-run of this call resolves to the same\n // decision instead of asking twice.\n idempotencyKey,\n })\n\n if (result.approved) {\n return {\n approved: true,\n authorization: {\n authorizationId: result.decisionId,\n toolName: ask.toolName,\n externalId: ask.externalId,\n ...(includeSubject\n ? {\n toolTarget: ask.toolTarget,\n actor: ask.actor,\n environment: ask.environment,\n parameters: facts,\n }\n : {}),\n },\n }\n }\n return { approved: false, reason: result.answered ? DENIED_REFUSED : DENIED_UNANSWERED }\n }\n }\n\n const askExternalUser = (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ): Promise<AskResult> =>\n client(config).decisions.ask({\n ...decisionSubject(input),\n question: input.question,\n type: input.type,\n options: input.options,\n externalId: requireExternalId(input.externalId),\n context: input.context,\n agentName: config.agentName,\n timeoutMs: config.timeoutMs,\n // The node/step name doubles as the action label. The default is not\n // sent: grouping every unnamed ask across every framework under one\n // bucket would mine into a suggestion to auto-approve \"ask-human\", which\n // spans unrelated questions.\n toolName: input.toolName ?? input.node,\n idempotencyKey: idempotencyKeyFor(input),\n })\n\n const createDurableDecision = async (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ): Promise<CreatedDecision> => {\n if (!input.idempotencyKey?.trim()) {\n throw new Error('Pushary: durable decisions require an idempotencyKey tied to the run and step.')\n }\n const created = await client(config).decisions.create({\n ...decisionSubject(input),\n question: input.question,\n type: input.type,\n options: input.options,\n externalId: requireExternalId(input.externalId),\n context: input.context,\n callbackUrl: input.callbackUrl,\n agentName: config.agentName,\n toolName: input.toolName ?? input.node,\n idempotencyKey: idempotencyKeyFor(input),\n wait: false,\n })\n return {\n decisionId: created.decisionId,\n correlationId: created.decisionId,\n status: created.status,\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n }\n\n const connect = (config: PusharyAdapterConfig, externalId: string): Promise<EnrollResult> =>\n client(config).enroll(requireExternalId(externalId))\n\n const protect = (config: PusharyGateConfig): Protect => {\n // One gate per configuration, not per action: it holds the client, so\n // building it here means a missing key throws where protect() is defined\n // rather than on the first refund somebody tries to issue.\n const gate = createGate(config)\n const permitClient = client(config)\n\n // The receipt is evidence about an action that has already happened. Failing\n // the caller because the evidence could not be written would turn a\n // successful refund into an error and invite them to issue it again, which\n // is the exact outcome this whole path exists to prevent. A permit left\n // `running` is the honest record, and it is queryable.\n const report = async (permitId: string, outcome: ExecutionOutcome, summary?: string) => {\n try {\n await permitClient.recordExecution({ permitId, outcome, summary })\n } catch {\n // Deliberately swallowed. See above.\n }\n }\n\n return async <TResult>(action: ProtectedAction<TResult>): Promise<ProtectResult<TResult>> => {\n const decision = await gate({\n toolName: action.action,\n toolTarget: action.target,\n callId: action.callId,\n sessionId: action.runId,\n question: action.question ?? defaultProtectQuestion(action.action, action.target),\n externalId: action.externalId,\n actor: action.actor,\n environment: action.environment,\n parameters: action.facts,\n })\n\n if (!decision.approved) return { ok: false, reason: decision.reason }\n if (!decision.authorization) return { ok: false, reason: UNBOUND_APPROVAL }\n\n // Spent before the side effect, never after. A permit consumed after a\n // successful run would leave the window this exists to close: the crash\n // between the two would lose the record that it ran, and the retry would\n // run it again.\n let consumed\n try {\n consumed = await permitClient.consumeAuthorization(decision.authorization)\n } catch {\n // The action was approved and we could not confirm it is still ours to\n // run. Not running is the only safe answer; a duplicate refund cannot be\n // taken back and an unissued one can be asked for again.\n return { ok: false, reason: UNCONFIRMED_PERMIT }\n }\n if (!consumed.consumed) return { ok: false, reason: refusedPermit(consumed) }\n\n const permitId = consumed.permit.permitId\n let result: TResult\n try {\n result = await action.run()\n } catch (error) {\n // Recorded and rethrown, not swallowed. An error from the side effect is\n // the caller's, and reporting it as `ok: false` would be\n // indistinguishable from a refusal to an agent reading the result.\n await report(permitId, 'failed', error instanceof Error ? error.message : 'unknown error')\n throw error\n }\n await report(permitId, 'succeeded')\n return { ok: true, result }\n }\n }\n\n return Object.freeze({\n client,\n askExternalUser,\n createDurableDecision,\n connect,\n createGate,\n protect,\n requireExternalId,\n })\n}\n\nexport { SIGNATURE_HEADER, verifyWebhookSignature, parseDecisionCallback } from './webhook'\nexport { decisionFingerprint, deterministicKey, isApproved } from './util'\nexport type {\n AskResult,\n AuthorizationEvaluation,\n AuthorizationVerdict,\n DecisionParameters,\n DecisionType,\n EnrollResult,\n PusharyServer,\n}\n","/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown, options?: { readonly signal?: AbortSignal }): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n signal: options?.signal ?? AbortSignal.timeout(ctx.timeoutMs ?? 65_000),\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const timeoutMs = Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n // Zero retains the create-only API. Positive budgets include creation and polling.\n const deadline = Date.now() + timeoutMs\n const controller = new AbortController()\n const abort = () => controller.abort(data.signal?.reason)\n if (data.signal?.aborted) abort()\n else data.signal?.addEventListener('abort', abort, { once: true })\n const timer = setTimeout(() => controller.abort(new DOMException('Pushary ask timed out', 'TimeoutError')), timeoutMs || 65_000)\n try {\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n toolName: data.toolName,\n toolTarget: data.toolTarget,\n actor: data.actor,\n environment: data.environment,\n parameters: data.parameters,\n // Its changes name parameters rather than carrying values, so what the\n // person sees is read from the same numbers a rule compares.\n presentation: data.presentation,\n approvalUrl: data.approvalUrl,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n requireReachable: data.requireReachable,\n // Read only by a key issued on your workspace's sandbox site, where the\n // simulator answers instead of a person. Ignored in production.\n sandboxOutcome: data.sandboxOutcome,\n idempotencyKey,\n wait: false,\n }, { signal: controller.signal })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n try {\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n }, { signal: controller.signal })\n status = polled.status\n answered = polled.answered\n value = polled.value\n } catch (error) {\n if (controller.signal.aborted && !data.signal?.aborted) break\n throw error\n }\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n } finally {\n clearTimeout(timer)\n data.signal?.removeEventListener('abort', abort)\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nexport const decisionFingerprint = (value: unknown): string => {\n const ancestors = new Set<object>()\n const serialize = (item: unknown): string => {\n if (item === null || typeof item === 'string' || typeof item === 'boolean') return JSON.stringify(item)\n if (typeof item === 'number' && Number.isFinite(item)) return JSON.stringify(item)\n if (typeof item !== 'object' || ancestors.has(item)) {\n throw new TypeError('Pushary: decision identity must contain finite, acyclic JSON values.')\n }\n if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {\n throw new TypeError('Pushary: decision identity must contain plain JSON objects.')\n }\n ancestors.add(item)\n const entries = Object.entries(item)\n .filter(([, entry]) => entry !== undefined)\n .sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)\n const serialized = Array.isArray(item)\n ? `[${Array.from(item, serialize).join(',')}]`\n : `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serialize(entry)}`).join(',')}}`\n ancestors.delete(item)\n return serialized\n }\n return createHash('sha256').update(serialize(value)).digest('hex')\n}\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\n })\n","import type {\n AuthorizationEvaluation,\n AuthorizationQuery,\n AuthorizeRequest,\n AuthorizeResult,\n DecisionSubject,\n RequestFn,\n} from './types'\nimport type { DecisionsResource } from './resources/decisions'\n\nconst defaultQuestion = (toolName: string, toolTarget?: string): string =>\n `Approve ${toolName}${toolTarget ? ` ${toolTarget}` : ''}?`\n\n// The evaluation and the ask must describe the same action. Built once so a field\n// added to the subject cannot reach one call and not the other, which would let a\n// decision be recorded under a different description than the one policy saw.\nexport const authorizationSubject = (input: DecisionSubject): DecisionSubject => ({\n toolName: input.toolName,\n toolTarget: input.toolTarget,\n actor: input.actor,\n environment: input.environment,\n parameters: input.parameters,\n})\n\n/**\n * The raw boundary: what the site's policy says about one action, with nobody\n * paged and no decision opened.\n *\n * Exported separately from {@link createAuthorize} because a caller that owns its\n * own escalation — the adapter gate, or a custom harness with its own approval\n * surface — needs to tell \"policy had no opinion\" apart from \"the ask failed\".\n * Composing the two calls hides that difference, and the two failures want\n * opposite handling.\n */\nexport const createEvaluateAuthorization =\n (request: RequestFn) =>\n (input: AuthorizationQuery): Promise<AuthorizationEvaluation> =>\n request<AuthorizationEvaluation>('POST', '/authorize', {\n ...authorizationSubject(input),\n externalId: input.externalId,\n question: input.question,\n agentName: input.agentName,\n })\n\n/**\n * Ask policy first, and a person only if policy defers.\n *\n * Two calls rather than one server round trip on purpose. POST /authorize\n * evaluates and nothing else, so every limit, reachability check and delivery\n * rung stays in the one path that already owns them, and this composes the two.\n * The extra hop only happens when a human is needed, where it is lost in the\n * time that person takes to answer.\n *\n * Fail-closed throughout: a denial and an unanswered ask both return\n * approved: false, and the reason says which it was.\n */\nexport const createAuthorize = (request: RequestFn, decisions: DecisionsResource) => {\n const evaluate = createEvaluateAuthorization(request)\n\n return async (input: AuthorizeRequest): Promise<AuthorizeResult> => {\n const subject = authorizationSubject(input)\n const evaluation = await evaluate(input)\n\n if (evaluation.verdict !== 'requires_human') {\n return {\n approved: evaluation.verdict === 'allow',\n resolvedBy: 'policy',\n policy: evaluation.policy,\n reason: evaluation.reason,\n decisionId: null,\n authorizationId: evaluation.authorizationId,\n }\n }\n\n const answer = await decisions.ask({\n question: input.question ?? defaultQuestion(input.toolName, input.toolTarget),\n type: 'confirm',\n externalId: input.externalId,\n agentName: input.agentName,\n ...subject,\n timeoutMs: input.timeoutMs,\n expiresInSeconds: input.expiresInSeconds,\n idempotencyKey: input.idempotencyKey,\n requireReachable: input.requireReachable,\n })\n\n return {\n approved: answer.approved,\n resolvedBy: 'human',\n policy: evaluation.policy,\n reason: answer.approved\n ? 'A person approved it.'\n : answer.answered\n ? 'A person denied it.'\n : 'Nobody answered, so this was not approved.',\n decisionId: answer.decisionId,\n authorizationId: null,\n }\n }\n}\n","import { PusharyApiError } from './error'\nimport type {\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ConsumedAuthorization,\n ExecutionReceipt,\n PermitExecutionState,\n PermitRefusal,\n RequestFn,\n} from './types'\n\nconst REFUSALS: readonly PermitRefusal[] = [\n 'not_authorized',\n 'action_mismatch',\n 'expired',\n 'already_consumed',\n]\n\nconst EXECUTION_STATES: readonly PermitExecutionState[] = ['running', 'succeeded', 'failed']\n\n// A refusal is an answer, not a failure: the server looked, and said no. A\n// transport error is neither, and must not be dressed up as one — a caller that\n// reads \"already consumed\" stops, and a caller that reads a network error may\n// try again. Only a body the server actually sent gets read as a refusal.\nconst refusalOf = (error: unknown): ConsumeAuthorizationResult | null => {\n if (!(error instanceof PusharyApiError)) return null\n const body = error.body as { refusal?: unknown; executionState?: unknown } | undefined\n const refusal = body?.refusal\n if (typeof refusal !== 'string' || !REFUSALS.includes(refusal as PermitRefusal)) return null\n const state = body?.executionState\n return {\n consumed: false,\n refusal: refusal as PermitRefusal,\n reason: error.message,\n ...(typeof state === 'string' && EXECUTION_STATES.includes(state as PermitExecutionState)\n ? { executionState: state as PermitExecutionState }\n : {}),\n }\n}\n\n/**\n * Spend one settled authorization, at most once.\n *\n * Re-states the action about to run. That is the point: the server digests what\n * you claim and what it recorded, and refuses unless they are the same action.\n * An amount changed between the approval and the execution does not fail a check\n * that could be skipped — it simply has no permit.\n *\n * Refusals come back as `consumed: false` with a machine-readable reason.\n * Anything else throws, because \"the network was down\" is not \"you may not do\n * this\", and a caller that cannot tell them apart will eventually guess wrong.\n */\nexport const createConsumeAuthorization =\n (request: RequestFn) =>\n async (binding: AuthorizationBinding): Promise<ConsumeAuthorizationResult> => {\n try {\n const permit = await request<ConsumedAuthorization>('POST', '/authorizations/consume', {\n authorizationId: binding.authorizationId,\n toolName: binding.toolName,\n toolTarget: binding.toolTarget,\n actor: binding.actor,\n environment: binding.environment,\n externalId: binding.externalId,\n parameters: binding.parameters,\n })\n return { consumed: true, permit }\n } catch (error) {\n const refused = refusalOf(error)\n if (refused) return refused\n throw error\n }\n }\n\n/**\n * Say what the authorized action did.\n *\n * The evidence half. \"Was this allowed\" and \"did this happen\" are different\n * questions, and only the second one answers a customer asking where their\n * refund is. The first report wins; a late duplicate cannot rewrite it.\n */\nexport const createRecordExecution =\n (request: RequestFn) =>\n (receipt: ExecutionReceipt): Promise<{ readonly executionState: PermitExecutionState }> =>\n request('POST', `/authorizations/${encodeURIComponent(receipt.permitId)}/receipt`, {\n outcome: receipt.outcome,\n summary: receipt.summary,\n })\n","import type {\n PusharyConfig,\n RequestContext,\n EnrollResult,\n AuthorizationEvaluation,\n AuthorizationQuery,\n DecisionReach,\n AuthorizeRequest,\n AuthorizeResult,\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ExecutionReceipt,\n PermitExecutionState,\n} from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\nimport { createKeysResource, type KeysResource } from './resources/keys'\nimport { createAuthorize, createEvaluateAuthorization } from './authorize'\nimport { createConsumeAuthorization, createRecordExecution } from './permits'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\n // Ask policy whether one action may proceed, and a person only if policy\n // defers. allow and deny come back without anyone being paged. Requires the\n // Partner plan.\n readonly authorize: (input: AuthorizeRequest) => Promise<AuthorizeResult>\n // The verdict alone, with no decision opened and nobody paged. For a caller\n // that owns its own escalation and needs to tell \"policy deferred\" apart from\n // \"the ask failed\", which `authorize` folds together. Requires the Partner plan.\n readonly evaluateAuthorization: (input: AuthorizationQuery) => Promise<AuthorizationEvaluation>\n // Spend one settled authorization, at most once. Re-state the action you are\n // about to run: the server refuses unless it is the action that was approved.\n // Requires the Partner plan.\n readonly consumeAuthorization: (binding: AuthorizationBinding) => Promise<ConsumeAuthorizationResult>\n // Say what the authorized action did. The first report wins.\n readonly recordExecution: (receipt: ExecutionReceipt) => Promise<{ readonly executionState: PermitExecutionState }>\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n // Can this end-user be reached, and by whose hands? A read-only preflight, so\n // you can decide how to deliver BEFORE opening a decision and parking a run on\n // it. Opens nothing, mints nothing, costs no quota.\n readonly reachability: (externalId: string) => Promise<DecisionReach>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.requestTimeoutMs ?? 65_000,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n const decisions = createDecisionsResource(request)\n\n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions,\n keys: createKeysResource(request),\n authorize: createAuthorize(request, decisions),\n evaluateAuthorization: createEvaluateAuthorization(request),\n consumeAuthorization: createConsumeAuthorization(request),\n recordExecution: createRecordExecution(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n reachability: (externalId: string) =>\n request<DecisionReach>('GET', '/reachability', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto"]}
1
+ {"version":3,"sources":["../src/adapters.ts","../src/error.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/util.ts","../src/resources/keys.ts","../src/authorize.ts","../src/permits.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["// The shared kernel behind every Pushary framework adapter. Published as\n// `@pushary/server/adapters` so a new adapter is a thin binding — map the\n// framework's context onto these shapes, map the result back — instead of another\n// copy of the same ~150 lines. Nothing here imports a framework.\n\nimport { randomUUID } from 'node:crypto'\nimport { createPusharyServer, type PusharyServer } from './client'\nimport { decisionFingerprint, isApproved } from './util'\nimport { parseDecisionCallback, verifyWebhookSignature } from './webhook'\nimport type {\n AskResult,\n AuthorizationBinding,\n AuthorizationEvaluation,\n AuthorizationVerdict,\n ConsumeAuthorizationResult,\n DecisionParameters,\n DecisionSubject,\n DecisionType,\n EnrollResult,\n ExecutionOutcome,\n} from './types'\n\n/** Config every adapter accepts, whatever framework it binds. */\nexport interface PusharyAdapterConfig {\n /** Pushary API key (pk_xxx.sk_xxx). Defaults to `process.env.PUSHARY_API_KEY`. */\n readonly apiKey?: string\n /** Shown on the approval so the human knows which agent is asking. */\n readonly agentName?: string\n /** How long a blocking ask waits before returning (default 55s, serverless-safe). */\n readonly timeoutMs?: number\n /** Override the API base URL (tests / self-host). */\n readonly baseUrl?: string\n}\n\n/** One ask, as an adapter hands it to the kernel. */\nexport interface AskHumanInput extends DecisionSubject {\n /** Stable business operation ID for retries; required when parking a durable workflow. */\n readonly idempotencyKey?: string\n readonly question: string\n /** Your own stable id for the end-user who decides. */\n readonly externalId: string\n /** Stable node/step/tool name, used to build a re-run-safe idempotency key. */\n readonly node?: string\n readonly type?: DecisionType\n readonly options?: readonly string[]\n readonly context?: string\n /** Present on the durable path; Pushary POSTs the answer here. */\n readonly callbackUrl?: string\n readonly expiresInSeconds?: number\n readonly requireReachable?: boolean\n readonly placeholder?: string\n}\n\n/** A decision opened durably, before the framework parks its run. */\nexport interface CreatedDecision {\n readonly decisionId: string\n readonly correlationId: string\n readonly status: string\n readonly reachable?: boolean\n readonly reachableChannels?: number\n readonly deviceCount?: number\n}\n\n/** A verified, parsed answer callback, keyed by `correlationId`. */\nexport interface PusharyCallback {\n readonly correlationId: string\n readonly answer: string\n readonly value: string\n readonly approved: boolean\n readonly context?: string\n readonly answeredAt: string\n}\n\n/**\n * What the human is shown when the caller names no question.\n *\n * The action and its target, not a rendering of the facts: a protected action is\n * written by hand, so its name is already business language (\"refund.create\n * order_4471\") in a way a tool's raw input never is.\n */\nexport const defaultProtectQuestion = (action: string, target?: string): string =>\n `Approve ${action}${target ? ` ${target}` : ''}?`\n\n/**\n * Only an explicit operation key may replay an approval. Independent asks get\n * fresh keys even when their question and recipient are identical.\n */\nexport const idempotencyKeyFor = (input: AskHumanInput): string =>\n input.idempotencyKey ?? randomUUID()\n\nconst decisionSubject = (input: AskHumanInput) => ({\n toolTarget: input.toolTarget,\n actor: input.actor,\n environment: input.environment,\n parameters: input.parameters,\n presentation: input.presentation,\n approvalUrl: input.approvalUrl,\n expiresInSeconds: input.expiresInSeconds,\n requireReachable: input.requireReachable,\n placeholder: input.placeholder,\n callbackUrl: input.callbackUrl,\n})\n\n/** Fail-closed yes/no check for a confirm answer. */\nexport const isAffirmative = (answer: string | null | undefined): boolean =>\n isApproved({ status: 'answered', type: 'confirm', value: answer ?? null })\n\n/** Turn a decision outcome into an unambiguous instruction for the model. */\nexport const describeAnswer = (type: DecisionType, result: AskResult): string => {\n if (!result.answered) {\n return `No answer (status: ${result.status}). Treat this as NOT approved and do not proceed.`\n }\n if (type === 'confirm') {\n return result.approved\n ? 'The human approved. You may proceed.'\n : 'The human declined. Do not proceed.'\n }\n return `The human answered: ${result.value ?? ''}`\n}\n\n/**\n * Verify a callback signature and parse it, or return null. The `answer` is what\n * you feed back into the framework's resume seam.\n */\nexport const resolvePusharyCallback = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): PusharyCallback | null => {\n if (!verifyWebhookSignature(rawBody, signature, secret)) return null\n const callback = parseDecisionCallback(rawBody)\n if (!callback) return null\n return {\n correlationId: callback.correlationId,\n answer: callback.answer,\n value: callback.value,\n approved: isAffirmative(callback.answer),\n context: callback.context,\n answeredAt: callback.answeredAt,\n }\n}\n\n/**\n * Connection-level options for a request-time approval gate. Everything that\n * varies per tool call lives on {@link ApprovalAsk} instead, so a gate holds one\n * client and no per-call state.\n */\nexport interface PusharyGateConfig {\n /** Pushary API key. Defaults to `process.env.PUSHARY_API_KEY`. */\n readonly apiKey?: string\n /** Shown on the approval so the human knows which agent is asking. */\n readonly agentName?: string\n /** How long the decision stays answerable. */\n readonly expiresInSeconds?: number\n /** How long to block waiting for an answer before failing closed. */\n readonly timeoutMs?: number\n /**\n * Refuse to open a decision nobody can receive, so an end-user with no connected\n * device is denied at request time instead of silently expiring.\n */\n readonly requireReachable?: boolean\n /**\n * Ask the site's policy before asking a person (default true). A rule that names\n * the action resolves it without paging anyone; everything else still asks.\n *\n * Set false to restore the always-ask gate: a site whose rules were written for\n * its own coding agents, and which happens to name a tool this gate also sits\n * on, would otherwise start resolving that tool automatically.\n */\n readonly policy?: boolean\n /** Override the API base URL (tests / self-host). */\n readonly baseUrl?: string\n}\n\n/**\n * One gated tool call, resolved down to what Pushary needs. The adapter owns the\n * mapping from its framework's context, so per-call resolvers stay typed against\n * the framework's own shapes rather than a lowest common denominator.\n */\nexport interface ApprovalAsk {\n /** The tool the model wants to run. */\n readonly toolName: string\n /** Unique per tool call. Keys the decision together with `sessionId`. */\n readonly callId: string\n /** Stable per run, session, or thread. */\n readonly sessionId: string\n /** What the human is shown. */\n readonly question: string\n /** The enrolled end-user who decides. */\n readonly externalId: string\n /** What the tool acts on, e.g. an order id. Narrows which rule governs. */\n readonly toolTarget?: string\n /** Whose authority the action claims, e.g. `user:u_44`. Not the approver. */\n readonly actor?: string\n /** Which deployment the action runs against, e.g. `production`. */\n readonly environment?: string\n /**\n * The tool's raw input. Bounded scalars are derived from it so a rule can decide\n * on the arguments and not only on the name; anything the bounds cannot carry\n * sends nothing, which escalates. Already rendered into the default question, so\n * passing it exposes nothing new.\n */\n readonly input?: unknown\n /** Explicit facts for policy, used instead of deriving them from `input`. */\n readonly parameters?: DecisionParameters\n readonly presentation?: DecisionSubject['presentation']\n readonly context?: string\n}\n\n/**\n * What a gate answers. Denials always carry a reason the model can read.\n *\n * An approval also carries the binding: which authorization settled it and the\n * exact subject it was recorded against. Carried rather than rebuilt, because\n * only the gate knows what it actually sent — the ask records the full subject\n * only when the evaluation succeeded — and a caller that rebuilds it would send\n * a subject the record does not hold and be refused for its own approval.\n * Absent only from a server too old to name the authorization.\n */\nexport type ApprovalDecision =\n | { readonly approved: true; readonly authorization?: AuthorizationBinding }\n | { readonly approved: false; readonly reason: string }\n\n/**\n * A request-time gate: ask the site's policy, ask a person when policy defers,\n * fail closed either way.\n */\nexport type ApprovalGate = (ask: ApprovalAsk) => Promise<ApprovalDecision>\n\nconst MAX_INPUT_CHARS = 300\n\nexport const DENIED_UNANSWERED =\n 'No answer from the approver, so this was denied. Do not retry the same action.'\nexport const DENIED_REFUSED = 'The approver denied this action.'\nexport const DENIED_BY_POLICY = 'Denied by policy. Do not retry the same action.'\n\nexport const UNBOUND_APPROVAL =\n 'This was approved, but the approval could not be bound to a single execution, so it was not run. Do not retry the same action.'\n\nexport const UNCONFIRMED_PERMIT =\n 'This was approved, but it could not be confirmed as still unspent, so it was not run. Ask again rather than retrying this one.'\n\nconst REFUSED_REPLAY =\n 'This authorization was already used. The action has run once and must not run again.'\n\n/**\n * A refused permit, as the model reads it.\n *\n * `already_consumed` is worded to stop the caller rather than to invite a retry,\n * because a retry is the failure: something already ran this action once, and\n * the agent reading this cannot see that from its own state.\n */\nexport const refusedPermit = (\n refused: Extract<ConsumeAuthorizationResult, { consumed: false }>,\n): string => (refused.refusal === 'already_consumed' ? REFUSED_REPLAY : `${refused.reason} Do not retry the same action.`)\n\n/**\n * A policy denial, as the model reads it. The server already words the reason\n * (\"Denied by policy rule refund.create.\"); the retry instruction is added here so\n * every denial this gate returns ends the same way, whoever settled it.\n */\nexport const deniedByPolicy = (evaluation: AuthorizationEvaluation): string =>\n evaluation.reason ? `${evaluation.reason} Do not retry the same action.` : DENIED_BY_POLICY\n\nconst PARAMETERS_MAX_KEYS = 32\nconst PARAMETER_KEY_MAX_LENGTH = 64\nconst PARAMETER_VALUE_MAX_LENGTH = 200\n\n/**\n * Bounded facts for policy, derived from a tool's own input.\n *\n * All or nothing, deliberately. The bounds mirror what the API accepts, so a\n * partial object is always representable — and that is exactly the danger: a rule\n * reading `amount >= 500` stops denying the moment `amount` goes missing, so\n * dropping one entry can only ever move a verdict toward allow. Sending nothing\n * instead leaves the rule's parameter absent, which escalates to a person.\n *\n * The same bounds also mean this never turns a working tool call into a rejected\n * one: an input this cannot carry is simply not sent.\n */\nexport const deriveParameters = (input: unknown): DecisionParameters | undefined => {\n if (input === null || typeof input !== 'object' || Array.isArray(input)) return undefined\n const entries = Object.entries(input as Record<string, unknown>)\n if (entries.length === 0 || entries.length > PARAMETERS_MAX_KEYS) return undefined\n\n const parameters: Record<string, string | number | boolean> = {}\n for (const [key, value] of entries) {\n if (key.length === 0 || key.length > PARAMETER_KEY_MAX_LENGTH) return undefined\n if (typeof value === 'boolean') {\n parameters[key] = value\n } else if (typeof value === 'number') {\n if (!Number.isFinite(value)) return undefined\n parameters[key] = value\n } else if (typeof value === 'string') {\n if (value.length > PARAMETER_VALUE_MAX_LENGTH) return undefined\n parameters[key] = value\n } else {\n return undefined\n }\n }\n return parameters\n}\n\n/**\n * The default question: the tool name plus a truncated view of its input, so the\n * human sees what they are approving without a wall of JSON on a lock screen.\n */\nexport const renderApprovalQuestion = (toolName: string, toolInput: unknown): string => {\n if (toolInput === undefined || toolInput === null) return `Approve ${toolName}?`\n const summary = summarise(toolInput)\n return summary === undefined ? `Approve ${toolName}?` : `Approve ${toolName}? ${summary}`\n}\n\nconst summarise = (toolInput: unknown): string | undefined => {\n let rendered: string | undefined\n if (typeof toolInput === 'string') {\n rendered = toolInput\n } else {\n try {\n // Returns undefined for a function or a symbol, and throws on a cycle. Neither\n // is a reason to fail the tool call: a gate that cannot render its input still\n // has to ask, so fall back to the bare tool name.\n rendered = JSON.stringify(toolInput)\n } catch {\n return undefined\n }\n }\n if (rendered === undefined) return undefined\n return rendered.length > MAX_INPUT_CHARS ? `${rendered.slice(0, MAX_INPUT_CHARS)}...` : rendered\n}\n\n\n/**\n * One protected action: what it is, who it belongs to, and what it does.\n *\n * The shape a developer writes once instead of threading authorize, escalate and\n * run through their business code by hand. Everything above `run` is the same\n * subject `authorize()` and the decision both read, so the rule that governs the\n * action and the sentence the human sees describe the one thing.\n */\nexport interface ProtectedAction<TResult> {\n /** What is being done, e.g. `refund.create`. A rule has to name this. */\n readonly action: string\n /** What it is done to, e.g. an order id. Narrows which rule governs. */\n readonly target?: string\n /** The end-user this action belongs to, and who decides if policy defers. */\n readonly externalId: string\n /** Whose authority the action claims, e.g. `user:u_44`. Not the approver. */\n readonly actor?: string\n /** Which deployment it runs against, e.g. `production`. */\n readonly environment?: string\n /**\n * The bounded facts a rule may decide on, e.g. `{ amount: 4800 }`. Scalars\n * only. Passed as given rather than derived, because a protected action is\n * written by hand and the author knows which of its arguments matter.\n */\n readonly facts?: DecisionParameters\n /** Unique per attempt. Keys the decision together with `runId`. */\n readonly callId: string\n /** Stable per run, session or thread. */\n readonly runId: string\n /** What the human is shown. Defaults to the action and its target. */\n readonly question?: string\n /**\n * The side effect. Called only after the action is authorized, and never on a\n * denial or an unanswered escalation.\n */\n readonly run: () => Promise<TResult>\n}\n\n/**\n * What a protected action did.\n *\n * A discriminated union rather than a thrown error, because \"policy said no\" is\n * an ordinary outcome an agent has to be able to read and report, not an\n * exception. An error thrown by `run` itself is NOT caught: that is the caller's\n * failure, about their side effect, and swallowing it here would turn a failed\n * refund into a silent success.\n */\nexport type ProtectResult<TResult> =\n | { readonly ok: true; readonly result: TResult }\n | { readonly ok: false; readonly reason: string }\n\n/** Protect one action. Build it once per configuration, call it per action. */\nexport type Protect = <TResult>(action: ProtectedAction<TResult>) => Promise<ProtectResult<TResult>>\n\n/**\n * The calls an adapter makes, bound to one framework's name so a missing key or a\n * missing end-user says which helpers to configure. Build one per adapter module.\n */\nexport interface AdapterKernel {\n /** The underlying client, for anything the kernel does not cover. */\n readonly client: (config: PusharyAdapterConfig) => PusharyServer\n /**\n * Blocking ask: create a decision and poll durably until the human answers or\n * the deadline passes. A fresh key is used unless the caller supplies an operation-specific idempotencyKey.\n */\n readonly askExternalUser: (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ) => Promise<AskResult>\n /**\n * Durable create: open a decision with a `callbackUrl` and return at once, for\n * a framework that parks its run and resumes on the webhook.\n */\n readonly createDurableDecision: (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ) => Promise<CreatedDecision>\n /** Connect one end-user's phone (keyless). Show them the returned link. */\n readonly connect: (config: PusharyAdapterConfig, externalId: string) => Promise<EnrollResult>\n /**\n * Build a request-time approval gate. The client is created here, so a missing\n * key throws where the gate is defined rather than on the first tool call.\n */\n readonly createGate: (config: PusharyGateConfig) => ApprovalGate\n /**\n * Wrap one action so authorization, escalation and execution are one call.\n *\n * The whole of it is `createGate` plus \"run it if the answer was yes\", which is\n * the point: there is no second policy engine, no durable runtime and no new\n * abstraction under this. An adapter binds it by mapping its framework's\n * context onto ProtectedAction, exactly as it already does for the gate.\n *\n * At most once. The decision is idempotent — a replay of the same\n * `runId` + `callId` + action resolves to the same approval instead of asking\n * twice — and the approval is then spent against a durable permit before the\n * side effect runs. A retry, a concurrent worker and a resumed run all reach\n * the same permit, and exactly one of them proceeds; the rest are refused with\n * a reason that tells the model to stop rather than try again.\n *\n * `run` is called only after the permit is spent, never before, so the failure\n * mode of a crash mid-action is a permit with no receipt — which is visible —\n * rather than an action that runs twice, which is not recoverable.\n */\n readonly protect: (config: PusharyGateConfig) => Protect\n /**\n * The end-user to ask, or a clear error naming these helpers. Adapters call this\n * after applying their own resolver and the framework's own default.\n */\n readonly requireExternalId: (externalId: string | undefined) => string\n}\n\n/**\n * Build the kernel for one adapter.\n *\n * `helpersLabel` names the adapter in the missing-key error, so the message points\n * at the helpers the caller is actually using (\"the LangGraph helpers\").\n *\n * ```ts\n * const kernel = createAdapterKernel('the Acme helpers')\n * export const askHuman = kernel.askExternalUser\n * ```\n */\nexport const createAdapterKernel = (helpersLabel: string): AdapterKernel => {\n const requireApiKey = (apiKey: string | undefined): string => {\n const resolved = apiKey ?? process.env.PUSHARY_API_KEY\n if (!resolved) {\n throw new Error(`Pushary: set PUSHARY_API_KEY or pass { apiKey } to ${helpersLabel}.`)\n }\n return resolved\n }\n\n const client = (config: PusharyAdapterConfig): PusharyServer =>\n createPusharyServer({\n apiKey: requireApiKey(config.apiKey),\n ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),\n })\n\n const requireExternalId = (externalId: string | undefined): string => {\n if (!externalId?.trim()) {\n throw new Error(\n `Pushary: no end-user to ask. Pass { externalId } to ${helpersLabel}, or run ` +\n 'user-scoped auth so the framework supplies one.',\n )\n }\n if (externalId.length > 256) throw new Error('Pushary: externalId must not exceed 256 characters.')\n return externalId\n }\n\n const createGate = (config: PusharyGateConfig): ApprovalGate => {\n const gateClient = createPusharyServer({\n apiKey: requireApiKey(config.apiKey),\n ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),\n })\n const usePolicy = config.policy ?? true\n\n /**\n * The verdict, or null if there is no usable one.\n *\n * Every failure is null rather than a throw. This route is entitled to the\n * Partner plan and is newer than the published adapters, so a caller who\n * cannot reach it — wrong plan, older deployment, a network blip — has to keep\n * working exactly as it did before policy existed, which is by asking a person.\n * Null is never allow, so the fail-safe direction is preserved.\n */\n const evaluate = async (\n ask: ApprovalAsk,\n facts: DecisionParameters | undefined,\n ): Promise<AuthorizationEvaluation | null> => {\n if (!usePolicy) return null\n try {\n return await gateClient.evaluateAuthorization({\n toolName: ask.toolName,\n toolTarget: ask.toolTarget,\n actor: ask.actor,\n environment: ask.environment,\n parameters: facts,\n externalId: ask.externalId,\n question: ask.question,\n ...(config.agentName ? { agentName: config.agentName } : {}),\n })\n } catch {\n return null\n }\n }\n\n return async (ask: ApprovalAsk): Promise<ApprovalDecision> => {\n if (!ask.callId?.trim()) throw new Error('Pushary: a stable tool call ID is required to resolve an approval.')\n requireExternalId(ask.externalId)\n // Derived once. The evaluation and the ask that may follow have to describe\n // the same action, and computing it twice is two chances for them not to.\n const facts = ask.parameters ?? deriveParameters(ask.input)\n const idempotencyKey = decisionFingerprint({ ...ask, parameters: facts })\n const evaluation = await evaluate(ask, facts)\n const includeSubject = evaluation !== null || !usePolicy\n if (evaluation?.verdict === 'allow') {\n return {\n approved: true,\n // Exactly the subject POST /authorize recorded, which is the whole of\n // what it was sent.\n ...(evaluation.authorizationId\n ? {\n authorization: {\n authorizationId: evaluation.authorizationId,\n toolName: ask.toolName,\n toolTarget: ask.toolTarget,\n actor: ask.actor,\n environment: ask.environment,\n externalId: ask.externalId,\n parameters: facts,\n },\n }\n : {}),\n }\n }\n if (evaluation?.verdict === 'deny') {\n return { approved: false, reason: deniedByPolicy(evaluation) }\n }\n\n const result = await gateClient.decisions.ask({\n question: ask.question,\n type: 'confirm',\n externalId: ask.externalId,\n context: ask.context,\n ...(config.agentName ? { agentName: config.agentName } : {}),\n ...(config.expiresInSeconds ? { expiresInSeconds: config.expiresInSeconds } : {}),\n ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),\n ...(config.requireReachable ? { requireReachable: config.requireReachable } : {}),\n // The gated tool, recorded on the decision. The gate already knows it\n // and keyed on it below; sending it is what lets the decision be grouped\n // and matched later instead of surviving only as prose in the question.\n toolName: ask.toolName,\n ...(includeSubject\n ? {\n presentation: ask.presentation,\n toolTarget: ask.toolTarget,\n actor: ask.actor,\n environment: ask.environment,\n parameters: facts,\n }\n : {}),\n // Stable across replays, so a re-run of this call resolves to the same\n // decision instead of asking twice.\n idempotencyKey,\n })\n\n if (result.approved) {\n return {\n approved: true,\n authorization: {\n authorizationId: result.decisionId,\n toolName: ask.toolName,\n externalId: ask.externalId,\n ...(includeSubject\n ? {\n toolTarget: ask.toolTarget,\n actor: ask.actor,\n environment: ask.environment,\n parameters: facts,\n }\n : {}),\n },\n }\n }\n return { approved: false, reason: result.answered ? DENIED_REFUSED : DENIED_UNANSWERED }\n }\n }\n\n const askExternalUser = (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ): Promise<AskResult> =>\n client(config).decisions.ask({\n ...decisionSubject(input),\n question: input.question,\n type: input.type,\n options: input.options,\n externalId: requireExternalId(input.externalId),\n context: input.context,\n agentName: config.agentName,\n timeoutMs: config.timeoutMs,\n // The node/step name doubles as the action label. The default is not\n // sent: grouping every unnamed ask across every framework under one\n // bucket would mine into a suggestion to auto-approve \"ask-human\", which\n // spans unrelated questions.\n toolName: input.toolName ?? input.node,\n idempotencyKey: idempotencyKeyFor(input),\n })\n\n const createDurableDecision = async (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ): Promise<CreatedDecision> => {\n if (!input.idempotencyKey?.trim()) {\n throw new Error('Pushary: durable decisions require an idempotencyKey tied to the run and step.')\n }\n const created = await client(config).decisions.create({\n ...decisionSubject(input),\n question: input.question,\n type: input.type,\n options: input.options,\n externalId: requireExternalId(input.externalId),\n context: input.context,\n callbackUrl: input.callbackUrl,\n agentName: config.agentName,\n toolName: input.toolName ?? input.node,\n idempotencyKey: idempotencyKeyFor(input),\n wait: false,\n })\n return {\n decisionId: created.decisionId,\n correlationId: created.decisionId,\n status: created.status,\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n }\n\n const connect = (config: PusharyAdapterConfig, externalId: string): Promise<EnrollResult> =>\n client(config).enroll(requireExternalId(externalId))\n\n const protect = (config: PusharyGateConfig): Protect => {\n // One gate per configuration, not per action: it holds the client, so\n // building it here means a missing key throws where protect() is defined\n // rather than on the first refund somebody tries to issue.\n const gate = createGate(config)\n const permitClient = client(config)\n\n // The receipt is evidence about an action that has already happened. Failing\n // the caller because the evidence could not be written would turn a\n // successful refund into an error and invite them to issue it again, which\n // is the exact outcome this whole path exists to prevent. A permit left\n // `running` is the honest record, and it is queryable.\n const report = async (permitId: string, outcome: ExecutionOutcome, summary?: string) => {\n try {\n await permitClient.recordExecution({ permitId, outcome, summary })\n } catch {\n // Deliberately swallowed. See above.\n }\n }\n\n return async <TResult>(action: ProtectedAction<TResult>): Promise<ProtectResult<TResult>> => {\n const decision = await gate({\n toolName: action.action,\n toolTarget: action.target,\n callId: action.callId,\n sessionId: action.runId,\n question: action.question ?? defaultProtectQuestion(action.action, action.target),\n externalId: action.externalId,\n actor: action.actor,\n environment: action.environment,\n parameters: action.facts,\n })\n\n if (!decision.approved) return { ok: false, reason: decision.reason }\n if (!decision.authorization) return { ok: false, reason: UNBOUND_APPROVAL }\n\n // Spent before the side effect, never after. A permit consumed after a\n // successful run would leave the window this exists to close: the crash\n // between the two would lose the record that it ran, and the retry would\n // run it again.\n let consumed\n try {\n consumed = await permitClient.consumeAuthorization(decision.authorization)\n } catch {\n // The action was approved and we could not confirm it is still ours to\n // run. Not running is the only safe answer; a duplicate refund cannot be\n // taken back and an unissued one can be asked for again.\n return { ok: false, reason: UNCONFIRMED_PERMIT }\n }\n if (!consumed.consumed) return { ok: false, reason: refusedPermit(consumed) }\n\n const permitId = consumed.permit.permitId\n let result: TResult\n try {\n result = await action.run()\n } catch (error) {\n // Recorded and rethrown, not swallowed. An error from the side effect is\n // the caller's, and reporting it as `ok: false` would be\n // indistinguishable from a refusal to an agent reading the result.\n await report(permitId, 'failed', error instanceof Error ? error.message : 'unknown error')\n throw error\n }\n await report(permitId, 'succeeded')\n return { ok: true, result }\n }\n }\n\n return Object.freeze({\n client,\n askExternalUser,\n createDurableDecision,\n connect,\n createGate,\n protect,\n requireExternalId,\n })\n}\n\nexport { SIGNATURE_HEADER, verifyWebhookSignature, parseDecisionCallback } from './webhook'\nexport { decisionFingerprint, deterministicKey, isApproved } from './util'\nexport type {\n AskResult,\n AuthorizationEvaluation,\n AuthorizationVerdict,\n DecisionParameters,\n DecisionType,\n EnrollResult,\n PusharyServer,\n}\n","/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown, options?: { readonly signal?: AbortSignal }): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n signal: options?.signal ?? AbortSignal.timeout(ctx.timeoutMs ?? 65_000),\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionList,\n DecisionListOptions,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n readonly list: (opts?: DecisionListOptions) => Promise<DecisionList>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const timeoutMs = Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n // Zero retains the create-only API. Positive budgets include creation and polling.\n const deadline = Date.now() + timeoutMs\n const controller = new AbortController()\n const abort = () => controller.abort(data.signal?.reason)\n if (data.signal?.aborted) abort()\n else data.signal?.addEventListener('abort', abort, { once: true })\n const timer = setTimeout(() => controller.abort(new DOMException('Pushary ask timed out', 'TimeoutError')), timeoutMs || 65_000)\n try {\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n toolName: data.toolName,\n toolTarget: data.toolTarget,\n actor: data.actor,\n environment: data.environment,\n parameters: data.parameters,\n // Its changes name parameters rather than carrying values, so what the\n // person sees is read from the same numbers a rule compares.\n presentation: data.presentation,\n approvalUrl: data.approvalUrl,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n requireReachable: data.requireReachable,\n // Read only by a key issued on your workspace's sandbox site, where the\n // simulator answers instead of a person. Ignored in production.\n sandboxOutcome: data.sandboxOutcome,\n idempotencyKey,\n wait: false,\n }, { signal: controller.signal })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n try {\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n }, { signal: controller.signal })\n status = polled.status\n answered = polled.answered\n value = polled.value\n } catch (error) {\n if (controller.signal.aborted && !data.signal?.aborted) break\n throw error\n }\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n } finally {\n clearTimeout(timer)\n data.signal?.removeEventListener('abort', abort)\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n list: (opts?: DecisionListOptions) =>\n request<DecisionList>('GET', '/decisions', opts ? { ...opts } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nexport const decisionFingerprint = (value: unknown): string => {\n const ancestors = new Set<object>()\n const serialize = (item: unknown): string => {\n if (item === null || typeof item === 'string' || typeof item === 'boolean') return JSON.stringify(item)\n if (typeof item === 'number' && Number.isFinite(item)) return JSON.stringify(item)\n if (typeof item !== 'object' || ancestors.has(item)) {\n throw new TypeError('Pushary: decision identity must contain finite, acyclic JSON values.')\n }\n if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {\n throw new TypeError('Pushary: decision identity must contain plain JSON objects.')\n }\n ancestors.add(item)\n const entries = Object.entries(item)\n .filter(([, entry]) => entry !== undefined)\n .sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)\n const serialized = Array.isArray(item)\n ? `[${Array.from(item, serialize).join(',')}]`\n : `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serialize(entry)}`).join(',')}}`\n ancestors.delete(item)\n return serialized\n }\n return createHash('sha256').update(serialize(value)).digest('hex')\n}\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\n })\n","import type {\n AuthorizationEvaluation,\n AuthorizationQuery,\n AuthorizeRequest,\n AuthorizeResult,\n DecisionSubject,\n RequestFn,\n} from './types'\nimport type { DecisionsResource } from './resources/decisions'\n\nconst defaultQuestion = (toolName: string, toolTarget?: string): string =>\n `Approve ${toolName}${toolTarget ? ` ${toolTarget}` : ''}?`\n\n// The evaluation and the ask must describe the same action. Built once so a field\n// added to the subject cannot reach one call and not the other, which would let a\n// decision be recorded under a different description than the one policy saw.\nexport const authorizationSubject = (input: DecisionSubject): DecisionSubject => ({\n toolName: input.toolName,\n toolTarget: input.toolTarget,\n actor: input.actor,\n environment: input.environment,\n parameters: input.parameters,\n})\n\n/**\n * The raw boundary: what the site's policy says about one action, with nobody\n * paged and no decision opened.\n *\n * Exported separately from {@link createAuthorize} because a caller that owns its\n * own escalation — the adapter gate, or a custom harness with its own approval\n * surface — needs to tell \"policy had no opinion\" apart from \"the ask failed\".\n * Composing the two calls hides that difference, and the two failures want\n * opposite handling.\n */\nexport const createEvaluateAuthorization =\n (request: RequestFn) =>\n (input: AuthorizationQuery): Promise<AuthorizationEvaluation> =>\n request<AuthorizationEvaluation>('POST', '/authorize', {\n ...authorizationSubject(input),\n externalId: input.externalId,\n question: input.question,\n agentName: input.agentName,\n })\n\n/**\n * Ask policy first, and a person only if policy defers.\n *\n * Two calls rather than one server round trip on purpose. POST /authorize\n * evaluates and nothing else, so every limit, reachability check and delivery\n * rung stays in the one path that already owns them, and this composes the two.\n * The extra hop only happens when a human is needed, where it is lost in the\n * time that person takes to answer.\n *\n * Fail-closed throughout: a denial and an unanswered ask both return\n * approved: false, and the reason says which it was.\n */\nexport const createAuthorize = (request: RequestFn, decisions: DecisionsResource) => {\n const evaluate = createEvaluateAuthorization(request)\n\n return async (input: AuthorizeRequest): Promise<AuthorizeResult> => {\n const subject = authorizationSubject(input)\n const evaluation = await evaluate(input)\n\n if (evaluation.verdict !== 'requires_human') {\n return {\n approved: evaluation.verdict === 'allow',\n resolvedBy: 'policy',\n policy: evaluation.policy,\n reason: evaluation.reason,\n decisionId: null,\n authorizationId: evaluation.authorizationId,\n }\n }\n\n const answer = await decisions.ask({\n question: input.question ?? defaultQuestion(input.toolName, input.toolTarget),\n type: 'confirm',\n externalId: input.externalId,\n agentName: input.agentName,\n ...subject,\n timeoutMs: input.timeoutMs,\n expiresInSeconds: input.expiresInSeconds,\n idempotencyKey: input.idempotencyKey,\n requireReachable: input.requireReachable,\n })\n\n return {\n approved: answer.approved,\n resolvedBy: 'human',\n policy: evaluation.policy,\n reason: answer.approved\n ? 'A person approved it.'\n : answer.answered\n ? 'A person denied it.'\n : 'Nobody answered, so this was not approved.',\n decisionId: answer.decisionId,\n authorizationId: null,\n }\n }\n}\n","import { PusharyApiError } from './error'\nimport type {\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ConsumedAuthorization,\n ExecutionReceipt,\n PermitExecutionState,\n PermitRefusal,\n RequestFn,\n} from './types'\n\nconst REFUSALS: readonly PermitRefusal[] = [\n 'not_authorized',\n 'action_mismatch',\n 'expired',\n 'already_consumed',\n]\n\nconst EXECUTION_STATES: readonly PermitExecutionState[] = ['running', 'succeeded', 'failed']\n\n// A refusal is an answer, not a failure: the server looked, and said no. A\n// transport error is neither, and must not be dressed up as one — a caller that\n// reads \"already consumed\" stops, and a caller that reads a network error may\n// try again. Only a body the server actually sent gets read as a refusal.\nconst refusalOf = (error: unknown): ConsumeAuthorizationResult | null => {\n if (!(error instanceof PusharyApiError)) return null\n const body = error.body as { refusal?: unknown; executionState?: unknown } | undefined\n const refusal = body?.refusal\n if (typeof refusal !== 'string' || !REFUSALS.includes(refusal as PermitRefusal)) return null\n const state = body?.executionState\n return {\n consumed: false,\n refusal: refusal as PermitRefusal,\n reason: error.message,\n ...(typeof state === 'string' && EXECUTION_STATES.includes(state as PermitExecutionState)\n ? { executionState: state as PermitExecutionState }\n : {}),\n }\n}\n\n/**\n * Spend one settled authorization, at most once.\n *\n * Re-states the action about to run. That is the point: the server digests what\n * you claim and what it recorded, and refuses unless they are the same action.\n * An amount changed between the approval and the execution does not fail a check\n * that could be skipped — it simply has no permit.\n *\n * Refusals come back as `consumed: false` with a machine-readable reason.\n * Anything else throws, because \"the network was down\" is not \"you may not do\n * this\", and a caller that cannot tell them apart will eventually guess wrong.\n */\nexport const createConsumeAuthorization =\n (request: RequestFn) =>\n async (binding: AuthorizationBinding): Promise<ConsumeAuthorizationResult> => {\n try {\n const permit = await request<ConsumedAuthorization>('POST', '/authorizations/consume', {\n authorizationId: binding.authorizationId,\n toolName: binding.toolName,\n toolTarget: binding.toolTarget,\n actor: binding.actor,\n environment: binding.environment,\n externalId: binding.externalId,\n parameters: binding.parameters,\n })\n return { consumed: true, permit }\n } catch (error) {\n const refused = refusalOf(error)\n if (refused) return refused\n throw error\n }\n }\n\n/**\n * Say what the authorized action did.\n *\n * The evidence half. \"Was this allowed\" and \"did this happen\" are different\n * questions, and only the second one answers a customer asking where their\n * refund is. The first report wins; a late duplicate cannot rewrite it.\n */\nexport const createRecordExecution =\n (request: RequestFn) =>\n (receipt: ExecutionReceipt): Promise<{ readonly executionState: PermitExecutionState }> =>\n request('POST', `/authorizations/${encodeURIComponent(receipt.permitId)}/receipt`, {\n outcome: receipt.outcome,\n summary: receipt.summary,\n })\n","import type {\n PusharyConfig,\n RequestContext,\n EnrollResult,\n AuthorizationEvaluation,\n AuthorizationQuery,\n DecisionReach,\n AuthorizeRequest,\n AuthorizeResult,\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ExecutionReceipt,\n PermitExecutionState,\n} from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\nimport { createKeysResource, type KeysResource } from './resources/keys'\nimport { createAuthorize, createEvaluateAuthorization } from './authorize'\nimport { createConsumeAuthorization, createRecordExecution } from './permits'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\n // Ask policy whether one action may proceed, and a person only if policy\n // defers. allow and deny come back without anyone being paged. Requires the\n // Partner plan.\n readonly authorize: (input: AuthorizeRequest) => Promise<AuthorizeResult>\n // The verdict alone, with no decision opened and nobody paged. For a caller\n // that owns its own escalation and needs to tell \"policy deferred\" apart from\n // \"the ask failed\", which `authorize` folds together. Requires the Partner plan.\n readonly evaluateAuthorization: (input: AuthorizationQuery) => Promise<AuthorizationEvaluation>\n // Spend one settled authorization, at most once. Re-state the action you are\n // about to run: the server refuses unless it is the action that was approved.\n // Requires the Partner plan.\n readonly consumeAuthorization: (binding: AuthorizationBinding) => Promise<ConsumeAuthorizationResult>\n // Say what the authorized action did. The first report wins.\n readonly recordExecution: (receipt: ExecutionReceipt) => Promise<{ readonly executionState: PermitExecutionState }>\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n // Can this end-user be reached, and by whose hands? A read-only preflight, so\n // you can decide how to deliver BEFORE opening a decision and parking a run on\n // it. Opens nothing, mints nothing, costs no quota.\n readonly reachability: (externalId: string) => Promise<DecisionReach>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.requestTimeoutMs ?? 65_000,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n const decisions = createDecisionsResource(request)\n\n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions,\n keys: createKeysResource(request),\n authorize: createAuthorize(request, decisions),\n evaluateAuthorization: createEvaluateAuthorization(request),\n consumeAuthorization: createConsumeAuthorization(request),\n recordExecution: createRecordExecution(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n reachability: (externalId: string) =>\n request<DecisionReach>('GET', '/reachability', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,yBAA2B;;;ACKpB,IAAM,kBAAN,MAAM,yBAAwB,MAAM;AAAA,EAUzC,YACE,SACA,MACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS,KAAK;AACnB,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,OAAO,KAAK;AACjB,SAAK,OAAO,KAAK;AAGjB,WAAO,eAAe,MAAM,iBAAgB,SAAS;AAAA,EACvD;AAAA;AAAA,EAGA,IAAI,cAAuB;AACzB,WAAO,KAAK,WAAW,OAAO,KAAK,WAAW;AAAA,EAChD;AAAA;AAAA,EAGA,IAAI,gBAAyB;AAC3B,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA,EAGA,IAAI,gBAAyB;AAC3B,WAAO,KAAK,UAAU;AAAA,EACxB;AACF;;;AC9CA,IAAM,WAAW,CAAC,SAAiB,MAAc,WAA6C;AAI5F,QAAM,MAAM,IAAI,IAAI,GAAG,OAAO,GAAG,IAAI,EAAE;AAEvC,MAAI,QAAQ;AACV,WAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/C,UAAI,UAAU,UAAa,UAAU,MAAM;AACzC,YAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,IAAI,SAAS;AACtB;AAEA,IAAM,iBAAiB,OAAU,aAAmC;AAClE,MAAI,CAAC,SAAS,IAAI;AAIhB,UAAM,OAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AASpD,UAAM,IAAI,gBAAgB,KAAK,WAAW,KAAK,SAAS,QAAQ,SAAS,MAAM,IAAI;AAAA,MACjF,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS;AAAA,MACrB,MAAM,KAAK;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO,SAAS,KAAK;AACvB;AAEO,IAAM,gBAAgB,CAAC,QAC5B,OAAU,QAAgB,MAAc,MAAgB,YAA4D;AAClH,QAAM,QAAQ,WAAW;AACzB,QAAM,MAAM,SAAS,OACjB,SAAS,IAAI,SAAS,MAAM,IAA+B,IAC3D,GAAG,IAAI,OAAO,GAAG,IAAI;AAEzB,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC;AAAA,IACA,QAAQ,SAAS,UAAU,YAAY,QAAQ,IAAI,aAAa,IAAM;AAAA,IACtE,SAAS,IAAI;AAAA,IACb,MAAM,CAAC,SAAS,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,EAChD,CAAC;AAED,SAAO,eAAkB,QAAQ;AACnC;;;AC1CK,IAAM,4BAA4B,CAAC,YACxC,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,WACL,QAAuC,OAAO,gBAAgB,MAAM;AAAA,EAEtE,KAAK,CAAC,OACJ,QAAoB,OAAO,gBAAgB,EAAE,EAAE;AAAA,EAEjD,QAAQ,CAAC,IAAY,SACnB,QAAoB,SAAS,gBAAgB,EAAE,IAAI,IAAI;AAAA,EAEzD,QAAQ,CAAC,OACP,QAAc,UAAU,gBAAgB,EAAE,EAAE;AAAA,EAE9C,OAAO,MACL,QAAyB,OAAO,oBAAoB;AACxD,CAAC;;;ACXI,IAAM,0BAA0B,CAAC,YACtC,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,WACL,QAAqC,OAAO,cAAc,MAAM;AAAA,EAElE,QAAQ,CAAC,SACP,QAAkB,QAAQ,cAAc,IAAI;AAAA,EAE9C,KAAK,CAAC,OACJ,QAAkB,OAAO,cAAc,EAAE,EAAE;AAAA,EAE7C,QAAQ,CAAC,IAAY,SACnB,QAAkB,SAAS,cAAc,EAAE,IAAI,IAAI;AAAA,EAErD,QAAQ,CAAC,OACP,QAAc,UAAU,cAAc,EAAE,EAAE;AAAA,EAE5C,MAAM,CAAC,OACL,QAAkB,QAAQ,cAAc,EAAE,OAAO;AAAA,EAEnD,OAAO,CAAC,OACN,QAAkB,QAAQ,cAAc,EAAE,QAAQ;AAAA,EAEpD,QAAQ,CAAC,OACP,QAAkB,QAAQ,cAAc,EAAE,SAAS;AAAA,EAErD,OAAO,CAAC,OACN,QAAuB,OAAO,cAAc,EAAE,QAAQ;AAC1D,CAAC;;;ACjCI,IAAM,0BAA0B,CAAC,YACtC,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,WACL,QAAqC,OAAO,cAAc,MAAM;AAAA,EAElE,QAAQ,CAAC,SACP,QAAkB,QAAQ,cAAc,IAAI;AAAA,EAE9C,KAAK,CAAC,OACJ,QAAkB,OAAO,cAAc,EAAE,EAAE;AAAA,EAE7C,QAAQ,CAAC,IAAY,SACnB,QAAkB,SAAS,cAAc,EAAE,IAAI,IAAI;AAAA,EAErD,QAAQ,CAAC,OACP,QAAc,UAAU,cAAc,EAAE,EAAE;AAC9C,CAAC;;;AC3BI,IAAM,8BAA8B,CAAC,YAC1C,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,SACL,QAAoB,QAAQ,SAAS,IAAI;AAC7C,CAAC;;;ACSI,IAAM,sBAAsB,CAAC,YAClC,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,WACL,QAAiC,OAAO,UAAU,MAAM;AAAA,EAE1D,QAAQ,CAAC,SACP,QAAc,QAAQ,UAAU,IAAI;AAAA,EAEtC,KAAK,CAAC,OACJ,QAAc,OAAO,UAAU,EAAE,EAAE;AAAA,EAErC,QAAQ,CAAC,IAAY,SACnB,QAAc,SAAS,UAAU,EAAE,IAAI,IAAI;AAAA,EAE7C,QAAQ,CAAC,OACP,QAAc,UAAU,UAAU,EAAE,EAAE;AAAA,EAExC,UAAU,CAAC,OACT,QAAc,QAAQ,UAAU,EAAE,WAAW;AAAA,EAE/C,OAAO,CAAC,OACN,QAAc,QAAQ,UAAU,EAAE,QAAQ;AAC9C,CAAC;;;AC1BH,IAAAA,iBAA2B;;;ACf3B,oBAA2B;AAQpB,IAAM,mBAAmB,CAAC,cAC/B,0BAAW,QAAQ,EAAE,OAAO,MAAM,KAAK,IAAM,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAEpE,IAAM,sBAAsB,CAAC,UAA2B;AAC7D,QAAM,YAAY,oBAAI,IAAY;AAClC,QAAM,YAAY,CAAC,SAA0B;AAC3C,QAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,OAAO,SAAS,UAAW,QAAO,KAAK,UAAU,IAAI;AACtG,QAAI,OAAO,SAAS,YAAY,OAAO,SAAS,IAAI,EAAG,QAAO,KAAK,UAAU,IAAI;AACjF,QAAI,OAAO,SAAS,YAAY,UAAU,IAAI,IAAI,GAAG;AACnD,YAAM,IAAI,UAAU,sEAAsE;AAAA,IAC5F;AACA,QAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,OAAO,eAAe,IAAI,MAAM,OAAO,aAAa,OAAO,eAAe,IAAI,MAAM,MAAM;AACpH,YAAM,IAAI,UAAU,6DAA6D;AAAA,IACnF;AACA,cAAU,IAAI,IAAI;AAClB,UAAM,UAAU,OAAO,QAAQ,IAAI,EAChC,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,UAAU,MAAS,EACzC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC;AAChD,UAAM,aAAa,MAAM,QAAQ,IAAI,IACjC,IAAI,MAAM,KAAK,MAAM,SAAS,EAAE,KAAK,GAAG,CAAC,MACzC,IAAI,QAAQ,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,UAAU,KAAK,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC;AAC7F,cAAU,OAAO,IAAI;AACrB,WAAO;AAAA,EACT;AACA,aAAO,0BAAW,QAAQ,EAAE,OAAO,UAAU,KAAK,CAAC,EAAE,OAAO,KAAK;AACnE;AAEA,IAAM,cAAmC,oBAAI,IAAI;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKM,IAAM,aAAa,CAAC,aAIZ;AACb,MAAI,SAAS,WAAW,WAAY,QAAO;AAC3C,MAAI,SAAS,QAAQ,SAAS,SAAS,UAAW,QAAO;AACzD,SAAO,SAAS,SAAS,QAAQ,YAAY,IAAI,SAAS,MAAM,KAAK,EAAE,YAAY,CAAC;AACtF;;;ADtCA,IAAM,sBAAsB;AAC5B,IAAM,yBAAyB;AAgCxB,IAAM,0BAA0B,CAAC,YAA0C;AAChF,QAAM,MAAM,OAAO,SAA0C;AAO3D,UAAM,iBAAiB,KAAK,sBAAkB,2BAAW;AAEzD,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,aAAa,sBAAsB;AAEtE,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,MAAM,WAAW,MAAM,KAAK,QAAQ,MAAM;AACxD,QAAI,KAAK,QAAQ,QAAS,OAAM;AAAA,QAC3B,MAAK,QAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AACjE,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,IAAI,aAAa,yBAAyB,cAAc,CAAC,GAAG,aAAa,IAAM;AAC/H,QAAI;AACF,YAAM,UAAU,MAAM,QAAwB,QAAQ,cAAc;AAAA,QAClE,UAAU,KAAK;AAAA,QACf,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,YAAY,KAAK;AAAA,QACjB,OAAO,KAAK;AAAA,QACZ,WAAW,KAAK;AAAA,QAChB,SAAS,KAAK;AAAA,QACd,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK;AAAA,QACf,YAAY,KAAK;AAAA,QACjB,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,YAAY,KAAK;AAAA;AAAA;AAAA,QAGjB,cAAc,KAAK;AAAA,QACnB,aAAa,KAAK;AAAA,QAClB,kBAAkB,KAAK;AAAA,QACvB,WAAW,KAAK;AAAA,QAChB,aAAa,KAAK;AAAA,QAClB,kBAAkB,KAAK;AAAA;AAAA;AAAA,QAGvB,gBAAgB,KAAK;AAAA,QACrB;AAAA,QACA,MAAM;AAAA,MACR,GAAG,EAAE,QAAQ,WAAW,OAAO,CAAC;AAEhC,YAAM,OAAqB,QAAQ,QAAQ,KAAK,QAAQ;AACxD,UAAI,SAAyB,QAAQ;AACrC,UAAI,WAAW,QAAQ;AACvB,UAAI,QAAuB,QAAQ,SAAS;AAE5C,aAAO,WAAW,aAAa,KAAK,IAAI,IAAI,UAAU;AACpD,cAAM,cAAc,KAAK;AAAA,UACvB;AAAA,UACA,KAAK,IAAI,qBAAqB,KAAK,MAAM,WAAW,KAAK,IAAI,KAAK,GAAI,CAAC;AAAA,QACzE;AACA,YAAI;AACF,gBAAM,SAAS,MAAM,QAAkB,OAAO,cAAc,QAAQ,UAAU,IAAI;AAAA,YAChF,MAAM;AAAA,UACR,GAAG,EAAE,QAAQ,WAAW,OAAO,CAAC;AAChC,mBAAS,OAAO;AAChB,qBAAW,OAAO;AAClB,kBAAQ,OAAO;AAAA,QACjB,SAAS,OAAO;AACd,cAAI,WAAW,OAAO,WAAW,CAAC,KAAK,QAAQ,QAAS;AACxD,gBAAM;AAAA,QACR;AAAA,MACF;AAEA,aAAO;AAAA,QACL,YAAY,QAAQ;AAAA,QACpB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,WAAW,EAAE,QAAQ,MAAM,MAAM,CAAC;AAAA;AAAA;AAAA,QAG5C,WAAW,QAAQ;AAAA,QACnB,mBAAmB,QAAQ;AAAA,QAC3B,aAAa,QAAQ;AAAA,MACvB;AAAA,IACF,UAAE;AACA,mBAAa,KAAK;AAClB,WAAK,QAAQ,oBAAoB,SAAS,KAAK;AAAA,IACjD;AAAA,EACF;AAEA,SAAO,OAAO,OAAO;AAAA,IACnB,QAAQ,CAAC,SAAyB,QAAwB,QAAQ,cAAc,IAAI;AAAA,IACpF,KAAK,CAAC,IAAY,SAChB,QAAkB,OAAO,cAAc,EAAE,IAAI,MAAM,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,MAAS;AAAA,IAC3F,MAAM,CAAC,SACL,QAAsB,OAAO,cAAc,OAAO,EAAE,GAAG,KAAK,IAAI,MAAS;AAAA,IAC3E;AAAA,IACA,QAAQ,CAAC,IAAY,WACnB,QAA8B,QAAQ,cAAc,EAAE,IAAI,EAAE,OAAO,CAAC;AAAA,IACtE,QAAQ,CAAC,OAAe,QAA8B,UAAU,cAAc,EAAE,EAAE;AAAA,IAClF,kBAAkB,MAAM,QAAuB,OAAO,iBAAiB;AAAA,IACvE,qBAAqB,MAAM,QAAuB,QAAQ,iBAAiB;AAAA,EAC7E,CAAC;AACH;;;AEnIO,IAAM,qBAAqB,CAAC,YACjC,OAAO,OAAO;AAAA,EACZ,OAAO,CAAC,SAAwB,QAAwB,QAAQ,SAAS,IAAI;AAAA,EAC7E,MAAM,MACJ,QAA8C,OAAO,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI;AAAA,EAClF,QAAQ,CAAC,cACP,QAAyB,UAAU,SAAS,mBAAmB,SAAS,CAAC,EAAE;AAC/E,CAAC;;;ACtBH,IAAM,kBAAkB,CAAC,UAAkB,eACzC,WAAW,QAAQ,GAAG,aAAa,IAAI,UAAU,KAAK,EAAE;AAKnD,IAAM,uBAAuB,CAAC,WAA6C;AAAA,EAChF,UAAU,MAAM;AAAA,EAChB,YAAY,MAAM;AAAA,EAClB,OAAO,MAAM;AAAA,EACb,aAAa,MAAM;AAAA,EACnB,YAAY,MAAM;AACpB;AAYO,IAAM,8BACX,CAAC,YACD,CAAC,UACC,QAAiC,QAAQ,cAAc;AAAA,EACrD,GAAG,qBAAqB,KAAK;AAAA,EAC7B,YAAY,MAAM;AAAA,EAClB,UAAU,MAAM;AAAA,EAChB,WAAW,MAAM;AACnB,CAAC;AAcE,IAAM,kBAAkB,CAAC,SAAoB,cAAiC;AACnF,QAAM,WAAW,4BAA4B,OAAO;AAEpD,SAAO,OAAO,UAAsD;AAClE,UAAM,UAAU,qBAAqB,KAAK;AAC1C,UAAM,aAAa,MAAM,SAAS,KAAK;AAEvC,QAAI,WAAW,YAAY,kBAAkB;AAC3C,aAAO;AAAA,QACL,UAAU,WAAW,YAAY;AAAA,QACjC,YAAY;AAAA,QACZ,QAAQ,WAAW;AAAA,QACnB,QAAQ,WAAW;AAAA,QACnB,YAAY;AAAA,QACZ,iBAAiB,WAAW;AAAA,MAC9B;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,UAAU,IAAI;AAAA,MACjC,UAAU,MAAM,YAAY,gBAAgB,MAAM,UAAU,MAAM,UAAU;AAAA,MAC5E,MAAM;AAAA,MACN,YAAY,MAAM;AAAA,MAClB,WAAW,MAAM;AAAA,MACjB,GAAG;AAAA,MACH,WAAW,MAAM;AAAA,MACjB,kBAAkB,MAAM;AAAA,MACxB,gBAAgB,MAAM;AAAA,MACtB,kBAAkB,MAAM;AAAA,IAC1B,CAAC;AAED,WAAO;AAAA,MACL,UAAU,OAAO;AAAA,MACjB,YAAY;AAAA,MACZ,QAAQ,WAAW;AAAA,MACnB,QAAQ,OAAO,WACX,0BACA,OAAO,WACL,wBACA;AAAA,MACN,YAAY,OAAO;AAAA,MACnB,iBAAiB;AAAA,IACnB;AAAA,EACF;AACF;;;ACxFA,IAAM,WAAqC;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,mBAAoD,CAAC,WAAW,aAAa,QAAQ;AAM3F,IAAM,YAAY,CAAC,UAAsD;AACvE,MAAI,EAAE,iBAAiB,iBAAkB,QAAO;AAChD,QAAM,OAAO,MAAM;AACnB,QAAM,UAAU,MAAM;AACtB,MAAI,OAAO,YAAY,YAAY,CAAC,SAAS,SAAS,OAAwB,EAAG,QAAO;AACxF,QAAM,QAAQ,MAAM;AACpB,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,QAAQ,MAAM;AAAA,IACd,GAAI,OAAO,UAAU,YAAY,iBAAiB,SAAS,KAA6B,IACpF,EAAE,gBAAgB,MAA8B,IAChD,CAAC;AAAA,EACP;AACF;AAcO,IAAM,6BACX,CAAC,YACD,OAAO,YAAuE;AAC5E,MAAI;AACF,UAAM,SAAS,MAAM,QAA+B,QAAQ,2BAA2B;AAAA,MACrF,iBAAiB,QAAQ;AAAA,MACzB,UAAU,QAAQ;AAAA,MAClB,YAAY,QAAQ;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf,aAAa,QAAQ;AAAA,MACrB,YAAY,QAAQ;AAAA,MACpB,YAAY,QAAQ;AAAA,IACtB,CAAC;AACD,WAAO,EAAE,UAAU,MAAM,OAAO;AAAA,EAClC,SAAS,OAAO;AACd,UAAM,UAAU,UAAU,KAAK;AAC/B,QAAI,QAAS,QAAO;AACpB,UAAM;AAAA,EACR;AACF;AASK,IAAM,wBACX,CAAC,YACD,CAAC,YACC,QAAQ,QAAQ,mBAAmB,mBAAmB,QAAQ,QAAQ,CAAC,YAAY;AAAA,EACjF,SAAS,QAAQ;AAAA,EACjB,SAAS,QAAQ;AACnB,CAAC;;;AC7DL,IAAM,mBAAmB;AAmCzB,IAAM,iBAAiB,CAAC,WAAyB;AAC/C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,mFAAmF;AAAA,EACrG;AAEA,MAAI,CAAC,OAAO,SAAS,GAAG,GAAG;AACzB,UAAM,IAAI,MAAM,8DAA8D;AAAA,EAChF;AACF;AAEA,IAAM,gBAAgB,CAAC,WAA0C;AAC/D,iBAAe,OAAO,MAAM;AAE5B,SAAO,OAAO,OAAO;AAAA,IACnB,SAAS,OAAO,WAAW;AAAA,IAC3B,WAAW,OAAO,oBAAoB;AAAA,IACtC,SAAS,OAAO,OAAO;AAAA,MACrB,gBAAgB;AAAA,MAChB,iBAAiB,UAAU,OAAO,MAAM;AAAA,IAC1C,CAAC;AAAA,EACH,CAAC;AACH;AAEO,IAAM,sBAAsB,CAAC,WAAyC;AAC3E,QAAM,MAAM,cAAc,MAAM;AAChC,QAAM,UAAU,cAAc,GAAG;AAEjC,QAAM,YAAY,wBAAwB,OAAO;AAEjD,SAAO,OAAO,OAAO;AAAA,IACnB,aAAa,0BAA0B,OAAO;AAAA,IAC9C,WAAW,wBAAwB,OAAO;AAAA,IAC1C,WAAW,wBAAwB,OAAO;AAAA,IAC1C,eAAe,4BAA4B,OAAO;AAAA,IAClD,OAAO,oBAAoB,OAAO;AAAA,IAClC;AAAA,IACA,MAAM,mBAAmB,OAAO;AAAA,IAChC,WAAW,gBAAgB,SAAS,SAAS;AAAA,IAC7C,uBAAuB,4BAA4B,OAAO;AAAA,IAC1D,sBAAsB,2BAA2B,OAAO;AAAA,IACxD,iBAAiB,sBAAsB,OAAO;AAAA,IAC9C,QAAQ,CAAC,eAAuB,QAAsB,QAAQ,WAAW,EAAE,WAAW,CAAC;AAAA,IACvF,cAAc,CAAC,eACb,QAAuB,OAAO,iBAAiB,EAAE,WAAW,CAAC;AAAA,EACjE,CAAC;AACH;;;ACzGA,IAAAC,iBAA4C;AAIrC,IAAM,mBAAmB;AAKzB,IAAM,yBAAyB,CACpC,SACA,WACA,WACY;AACZ,MAAI,CAAC,aAAa,CAAC,OAAQ,QAAO;AAClC,QAAM,eAAW,2BAAW,UAAU,MAAM,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC1E,QAAM,IAAI,OAAO,KAAK,QAAQ;AAC9B,QAAM,IAAI,OAAO,KAAK,SAAS;AAC/B,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,aAAO,gCAAgB,GAAG,CAAC;AAC7B;AAMO,IAAM,wBAAwB,CAAC,YAA6C;AACjF,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,OAAO;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,kBAAkB,YAAY,OAAO,EAAE,WAAW,SAAU,QAAO;AAChF,SAAO;AAAA,IACL,eAAe,EAAE;AAAA,IACjB,QAAQ,EAAE;AAAA,IACV,OAAO,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ,EAAE;AAAA,IACjD,YAAY,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AAAA,IAC9D,GAAI,OAAO,EAAE,YAAY,WAAW,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,EAChE;AACF;;;AdqCO,IAAM,yBAAyB,CAAC,QAAgB,WACrD,WAAW,MAAM,GAAG,SAAS,IAAI,MAAM,KAAK,EAAE;AAMzC,IAAM,oBAAoB,CAAC,UAChC,MAAM,sBAAkB,+BAAW;AAErC,IAAM,kBAAkB,CAAC,WAA0B;AAAA,EACjD,YAAY,MAAM;AAAA,EAClB,OAAO,MAAM;AAAA,EACb,aAAa,MAAM;AAAA,EACnB,YAAY,MAAM;AAAA,EAClB,cAAc,MAAM;AAAA,EACpB,aAAa,MAAM;AAAA,EACnB,kBAAkB,MAAM;AAAA,EACxB,kBAAkB,MAAM;AAAA,EACxB,aAAa,MAAM;AAAA,EACnB,aAAa,MAAM;AACrB;AAGO,IAAM,gBAAgB,CAAC,WAC5B,WAAW,EAAE,QAAQ,YAAY,MAAM,WAAW,OAAO,UAAU,KAAK,CAAC;AAGpE,IAAM,iBAAiB,CAAC,MAAoB,WAA8B;AAC/E,MAAI,CAAC,OAAO,UAAU;AACpB,WAAO,sBAAsB,OAAO,MAAM;AAAA,EAC5C;AACA,MAAI,SAAS,WAAW;AACtB,WAAO,OAAO,WACV,yCACA;AAAA,EACN;AACA,SAAO,uBAAuB,OAAO,SAAS,EAAE;AAClD;AAMO,IAAM,yBAAyB,CACpC,SACA,WACA,WAC2B;AAC3B,MAAI,CAAC,uBAAuB,SAAS,WAAW,MAAM,EAAG,QAAO;AAChE,QAAM,WAAW,sBAAsB,OAAO;AAC9C,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO;AAAA,IACL,eAAe,SAAS;AAAA,IACxB,QAAQ,SAAS;AAAA,IACjB,OAAO,SAAS;AAAA,IAChB,UAAU,cAAc,SAAS,MAAM;AAAA,IACvC,SAAS,SAAS;AAAA,IAClB,YAAY,SAAS;AAAA,EACvB;AACF;AAyFA,IAAM,kBAAkB;AAEjB,IAAM,oBACX;AACK,IAAM,iBAAiB;AACvB,IAAM,mBAAmB;AAEzB,IAAM,mBACX;AAEK,IAAM,qBACX;AAEF,IAAM,iBACJ;AASK,IAAM,gBAAgB,CAC3B,YACY,QAAQ,YAAY,qBAAqB,iBAAiB,GAAG,QAAQ,MAAM;AAOlF,IAAM,iBAAiB,CAAC,eAC7B,WAAW,SAAS,GAAG,WAAW,MAAM,mCAAmC;AAE7E,IAAM,sBAAsB;AAC5B,IAAM,2BAA2B;AACjC,IAAM,6BAA6B;AAc5B,IAAM,mBAAmB,CAAC,UAAmD;AAClF,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,QAAO;AAChF,QAAM,UAAU,OAAO,QAAQ,KAAgC;AAC/D,MAAI,QAAQ,WAAW,KAAK,QAAQ,SAAS,oBAAqB,QAAO;AAEzE,QAAM,aAAwD,CAAC;AAC/D,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,QAAI,IAAI,WAAW,KAAK,IAAI,SAAS,yBAA0B,QAAO;AACtE,QAAI,OAAO,UAAU,WAAW;AAC9B,iBAAW,GAAG,IAAI;AAAA,IACpB,WAAW,OAAO,UAAU,UAAU;AACpC,UAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,iBAAW,GAAG,IAAI;AAAA,IACpB,WAAW,OAAO,UAAU,UAAU;AACpC,UAAI,MAAM,SAAS,2BAA4B,QAAO;AACtD,iBAAW,GAAG,IAAI;AAAA,IACpB,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAMO,IAAM,yBAAyB,CAAC,UAAkB,cAA+B;AACtF,MAAI,cAAc,UAAa,cAAc,KAAM,QAAO,WAAW,QAAQ;AAC7E,QAAM,UAAU,UAAU,SAAS;AACnC,SAAO,YAAY,SAAY,WAAW,QAAQ,MAAM,WAAW,QAAQ,KAAK,OAAO;AACzF;AAEA,IAAM,YAAY,CAAC,cAA2C;AAC5D,MAAI;AACJ,MAAI,OAAO,cAAc,UAAU;AACjC,eAAW;AAAA,EACb,OAAO;AACL,QAAI;AAIF,iBAAW,KAAK,UAAU,SAAS;AAAA,IACrC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,aAAa,OAAW,QAAO;AACnC,SAAO,SAAS,SAAS,kBAAkB,GAAG,SAAS,MAAM,GAAG,eAAe,CAAC,QAAQ;AAC1F;AA6HO,IAAM,sBAAsB,CAAC,iBAAwC;AAC1E,QAAM,gBAAgB,CAAC,WAAuC;AAC5D,UAAM,WAAW,UAAU,QAAQ,IAAI;AACvC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,sDAAsD,YAAY,GAAG;AAAA,IACvF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,CAAC,WACd,oBAAoB;AAAA,IAClB,QAAQ,cAAc,OAAO,MAAM;AAAA,IACnC,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;AAAA,EACtD,CAAC;AAEH,QAAM,oBAAoB,CAAC,eAA2C;AACpE,QAAI,CAAC,YAAY,KAAK,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uDAAuD,YAAY;AAAA,MAErE;AAAA,IACF;AACA,QAAI,WAAW,SAAS,IAAK,OAAM,IAAI,MAAM,qDAAqD;AAClG,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,CAAC,WAA4C;AAC9D,UAAM,aAAa,oBAAoB;AAAA,MACrC,QAAQ,cAAc,OAAO,MAAM;AAAA,MACnC,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;AAAA,IACtD,CAAC;AACD,UAAM,YAAY,OAAO,UAAU;AAWnC,UAAM,WAAW,OACf,KACA,UAC4C;AAC5C,UAAI,CAAC,UAAW,QAAO;AACvB,UAAI;AACF,eAAO,MAAM,WAAW,sBAAsB;AAAA,UAC5C,UAAU,IAAI;AAAA,UACd,YAAY,IAAI;AAAA,UAChB,OAAO,IAAI;AAAA,UACX,aAAa,IAAI;AAAA,UACjB,YAAY;AAAA,UACZ,YAAY,IAAI;AAAA,UAChB,UAAU,IAAI;AAAA,UACd,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;AAAA,QAC5D,CAAC;AAAA,MACH,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO,OAAO,QAAgD;AAC5D,UAAI,CAAC,IAAI,QAAQ,KAAK,EAAG,OAAM,IAAI,MAAM,oEAAoE;AAC7G,wBAAkB,IAAI,UAAU;AAGhC,YAAM,QAAQ,IAAI,cAAc,iBAAiB,IAAI,KAAK;AAC1D,YAAM,iBAAiB,oBAAoB,EAAE,GAAG,KAAK,YAAY,MAAM,CAAC;AACxE,YAAM,aAAa,MAAM,SAAS,KAAK,KAAK;AAC5C,YAAM,iBAAiB,eAAe,QAAQ,CAAC;AAC/C,UAAI,YAAY,YAAY,SAAS;AACnC,eAAO;AAAA,UACL,UAAU;AAAA;AAAA;AAAA,UAGV,GAAI,WAAW,kBACX;AAAA,YACE,eAAe;AAAA,cACb,iBAAiB,WAAW;AAAA,cAC5B,UAAU,IAAI;AAAA,cACd,YAAY,IAAI;AAAA,cAChB,OAAO,IAAI;AAAA,cACX,aAAa,IAAI;AAAA,cACjB,YAAY,IAAI;AAAA,cAChB,YAAY;AAAA,YACd;AAAA,UACF,IACA,CAAC;AAAA,QACP;AAAA,MACF;AACA,UAAI,YAAY,YAAY,QAAQ;AAClC,eAAO,EAAE,UAAU,OAAO,QAAQ,eAAe,UAAU,EAAE;AAAA,MAC/D;AAEA,YAAM,SAAS,MAAM,WAAW,UAAU,IAAI;AAAA,QAC5C,UAAU,IAAI;AAAA,QACd,MAAM;AAAA,QACN,YAAY,IAAI;AAAA,QAChB,SAAS,IAAI;AAAA,QACb,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;AAAA,QAC1D,GAAI,OAAO,mBAAmB,EAAE,kBAAkB,OAAO,iBAAiB,IAAI,CAAC;AAAA,QAC/E,GAAI,OAAO,cAAc,SAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;AAAA,QACxE,GAAI,OAAO,mBAAmB,EAAE,kBAAkB,OAAO,iBAAiB,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,QAI/E,UAAU,IAAI;AAAA,QACd,GAAI,iBACA;AAAA,UACE,cAAc,IAAI;AAAA,UAClB,YAAY,IAAI;AAAA,UAChB,OAAO,IAAI;AAAA,UACX,aAAa,IAAI;AAAA,UACjB,YAAY;AAAA,QACd,IACA,CAAC;AAAA;AAAA;AAAA,QAGL;AAAA,MACF,CAAC;AAED,UAAI,OAAO,UAAU;AACnB,eAAO;AAAA,UACL,UAAU;AAAA,UACV,eAAe;AAAA,YACb,iBAAiB,OAAO;AAAA,YACxB,UAAU,IAAI;AAAA,YACd,YAAY,IAAI;AAAA,YAChB,GAAI,iBACA;AAAA,cACE,YAAY,IAAI;AAAA,cAChB,OAAO,IAAI;AAAA,cACX,aAAa,IAAI;AAAA,cACjB,YAAY;AAAA,YACd,IACA,CAAC;AAAA,UACP;AAAA,QACF;AAAA,MACF;AACA,aAAO,EAAE,UAAU,OAAO,QAAQ,OAAO,WAAW,iBAAiB,kBAAkB;AAAA,IACzF;AAAA,EACF;AAEA,QAAM,kBAAkB,CACtB,QACA,UAEA,OAAO,MAAM,EAAE,UAAU,IAAI;AAAA,IAC3B,GAAG,gBAAgB,KAAK;AAAA,IACxB,UAAU,MAAM;AAAA,IAChB,MAAM,MAAM;AAAA,IACZ,SAAS,MAAM;AAAA,IACf,YAAY,kBAAkB,MAAM,UAAU;AAAA,IAC9C,SAAS,MAAM;AAAA,IACf,WAAW,OAAO;AAAA,IAClB,WAAW,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlB,UAAU,MAAM,YAAY,MAAM;AAAA,IAClC,gBAAgB,kBAAkB,KAAK;AAAA,EACzC,CAAC;AAEH,QAAM,wBAAwB,OAC5B,QACA,UAC6B;AAC7B,QAAI,CAAC,MAAM,gBAAgB,KAAK,GAAG;AACjC,YAAM,IAAI,MAAM,gFAAgF;AAAA,IAClG;AACA,UAAM,UAAU,MAAM,OAAO,MAAM,EAAE,UAAU,OAAO;AAAA,MACpD,GAAG,gBAAgB,KAAK;AAAA,MACxB,UAAU,MAAM;AAAA,MAChB,MAAM,MAAM;AAAA,MACZ,SAAS,MAAM;AAAA,MACf,YAAY,kBAAkB,MAAM,UAAU;AAAA,MAC9C,SAAS,MAAM;AAAA,MACf,aAAa,MAAM;AAAA,MACnB,WAAW,OAAO;AAAA,MAClB,UAAU,MAAM,YAAY,MAAM;AAAA,MAClC,gBAAgB,kBAAkB,KAAK;AAAA,MACvC,MAAM;AAAA,IACR,CAAC;AACD,WAAO;AAAA,MACL,YAAY,QAAQ;AAAA,MACpB,eAAe,QAAQ;AAAA,MACvB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,mBAAmB,QAAQ;AAAA,MAC3B,aAAa,QAAQ;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,UAAU,CAAC,QAA8B,eAC7C,OAAO,MAAM,EAAE,OAAO,kBAAkB,UAAU,CAAC;AAErD,QAAM,UAAU,CAAC,WAAuC;AAItD,UAAM,OAAO,WAAW,MAAM;AAC9B,UAAM,eAAe,OAAO,MAAM;AAOlC,UAAM,SAAS,OAAO,UAAkB,SAA2B,YAAqB;AACtF,UAAI;AACF,cAAM,aAAa,gBAAgB,EAAE,UAAU,SAAS,QAAQ,CAAC;AAAA,MACnE,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,WAAO,OAAgB,WAAsE;AAC3F,YAAM,WAAW,MAAM,KAAK;AAAA,QAC1B,UAAU,OAAO;AAAA,QACjB,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,WAAW,OAAO;AAAA,QAClB,UAAU,OAAO,YAAY,uBAAuB,OAAO,QAAQ,OAAO,MAAM;AAAA,QAChF,YAAY,OAAO;AAAA,QACnB,OAAO,OAAO;AAAA,QACd,aAAa,OAAO;AAAA,QACpB,YAAY,OAAO;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,SAAS,SAAU,QAAO,EAAE,IAAI,OAAO,QAAQ,SAAS,OAAO;AACpE,UAAI,CAAC,SAAS,cAAe,QAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAM1E,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,aAAa,qBAAqB,SAAS,aAAa;AAAA,MAC3E,QAAQ;AAIN,eAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB;AAAA,MACjD;AACA,UAAI,CAAC,SAAS,SAAU,QAAO,EAAE,IAAI,OAAO,QAAQ,cAAc,QAAQ,EAAE;AAE5E,YAAM,WAAW,SAAS,OAAO;AACjC,UAAI;AACJ,UAAI;AACF,iBAAS,MAAM,OAAO,IAAI;AAAA,MAC5B,SAAS,OAAO;AAId,cAAM,OAAO,UAAU,UAAU,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AACzF,cAAM;AAAA,MACR;AACA,YAAM,OAAO,UAAU,WAAW;AAClC,aAAO,EAAE,IAAI,MAAM,OAAO;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO,OAAO,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;","names":["import_crypto","import_crypto"]}
@@ -1,5 +1,5 @@
1
- import { w as DecisionSubject, D as DecisionType, a7 as AskResult, z as DecisionParameters, N as AuthorizationBinding, R as ConsumeAuthorizationResult, M as AuthorizationEvaluation, P as PusharyServer, a5 as EnrollResult } from './util-CX6-cXFb.cjs';
2
- export { H as AuthorizationVerdict, al as SIGNATURE_HEADER, ao as decisionFingerprint, am as deterministicKey, an as isApproved, ak as parseDecisionCallback, aj as verifyWebhookSignature } from './util-CX6-cXFb.cjs';
1
+ import { w as DecisionSubject, D as DecisionType, aa as AskResult, z as DecisionParameters, N as AuthorizationBinding, R as ConsumeAuthorizationResult, M as AuthorizationEvaluation, P as PusharyServer, a8 as EnrollResult } from './util-M0kCZWd-.cjs';
2
+ export { H as AuthorizationVerdict, ao as SIGNATURE_HEADER, ar as decisionFingerprint, ap as deterministicKey, aq as isApproved, an as parseDecisionCallback, am as verifyWebhookSignature } from './util-M0kCZWd-.cjs';
3
3
 
4
4
  /** Config every adapter accepts, whatever framework it binds. */
5
5
  interface PusharyAdapterConfig {
@@ -1,5 +1,5 @@
1
- import { w as DecisionSubject, D as DecisionType, a7 as AskResult, z as DecisionParameters, N as AuthorizationBinding, R as ConsumeAuthorizationResult, M as AuthorizationEvaluation, P as PusharyServer, a5 as EnrollResult } from './util-CX6-cXFb.js';
2
- export { H as AuthorizationVerdict, al as SIGNATURE_HEADER, ao as decisionFingerprint, am as deterministicKey, an as isApproved, ak as parseDecisionCallback, aj as verifyWebhookSignature } from './util-CX6-cXFb.js';
1
+ import { w as DecisionSubject, D as DecisionType, aa as AskResult, z as DecisionParameters, N as AuthorizationBinding, R as ConsumeAuthorizationResult, M as AuthorizationEvaluation, P as PusharyServer, a8 as EnrollResult } from './util-M0kCZWd-.js';
2
+ export { H as AuthorizationVerdict, ao as SIGNATURE_HEADER, ar as decisionFingerprint, ap as deterministicKey, aq as isApproved, an as parseDecisionCallback, am as verifyWebhookSignature } from './util-M0kCZWd-.js';
3
3
 
4
4
  /** Config every adapter accepts, whatever framework it binds. */
5
5
  interface PusharyAdapterConfig {
package/dist/adapters.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  isApproved,
7
7
  parseDecisionCallback,
8
8
  verifyWebhookSignature
9
- } from "./chunk-AG6DI4RF.js";
9
+ } from "./chunk-DLJ3L77X.js";
10
10
 
11
11
  // src/adapters.ts
12
12
  import { randomUUID } from "crypto";
@@ -230,6 +230,7 @@ var createDecisionsResource = (request) => {
230
230
  return Object.freeze({
231
231
  create: (data) => request("POST", "/decisions", data),
232
232
  get: (id, opts) => request("GET", `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : void 0),
233
+ list: (opts) => request("GET", "/decisions", opts ? { ...opts } : void 0),
233
234
  ask,
234
235
  answer: (id, answer) => request("POST", `/decisions/${id}`, { answer }),
235
236
  cancel: (id) => request("DELETE", `/decisions/${id}`),
@@ -423,4 +424,4 @@ export {
423
424
  verifyWebhookSignature,
424
425
  parseDecisionCallback
425
426
  };
426
- //# sourceMappingURL=chunk-AG6DI4RF.js.map
427
+ //# sourceMappingURL=chunk-DLJ3L77X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error.ts","../src/util.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/resources/keys.ts","../src/authorize.ts","../src/permits.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nexport const decisionFingerprint = (value: unknown): string => {\n const ancestors = new Set<object>()\n const serialize = (item: unknown): string => {\n if (item === null || typeof item === 'string' || typeof item === 'boolean') return JSON.stringify(item)\n if (typeof item === 'number' && Number.isFinite(item)) return JSON.stringify(item)\n if (typeof item !== 'object' || ancestors.has(item)) {\n throw new TypeError('Pushary: decision identity must contain finite, acyclic JSON values.')\n }\n if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {\n throw new TypeError('Pushary: decision identity must contain plain JSON objects.')\n }\n ancestors.add(item)\n const entries = Object.entries(item)\n .filter(([, entry]) => entry !== undefined)\n .sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)\n const serialized = Array.isArray(item)\n ? `[${Array.from(item, serialize).join(',')}]`\n : `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serialize(entry)}`).join(',')}}`\n ancestors.delete(item)\n return serialized\n }\n return createHash('sha256').update(serialize(value)).digest('hex')\n}\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown, options?: { readonly signal?: AbortSignal }): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n signal: options?.signal ?? AbortSignal.timeout(ctx.timeoutMs ?? 65_000),\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionList,\n DecisionListOptions,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n readonly list: (opts?: DecisionListOptions) => Promise<DecisionList>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const timeoutMs = Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n // Zero retains the create-only API. Positive budgets include creation and polling.\n const deadline = Date.now() + timeoutMs\n const controller = new AbortController()\n const abort = () => controller.abort(data.signal?.reason)\n if (data.signal?.aborted) abort()\n else data.signal?.addEventListener('abort', abort, { once: true })\n const timer = setTimeout(() => controller.abort(new DOMException('Pushary ask timed out', 'TimeoutError')), timeoutMs || 65_000)\n try {\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n toolName: data.toolName,\n toolTarget: data.toolTarget,\n actor: data.actor,\n environment: data.environment,\n parameters: data.parameters,\n // Its changes name parameters rather than carrying values, so what the\n // person sees is read from the same numbers a rule compares.\n presentation: data.presentation,\n approvalUrl: data.approvalUrl,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n requireReachable: data.requireReachable,\n // Read only by a key issued on your workspace's sandbox site, where the\n // simulator answers instead of a person. Ignored in production.\n sandboxOutcome: data.sandboxOutcome,\n idempotencyKey,\n wait: false,\n }, { signal: controller.signal })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n try {\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n }, { signal: controller.signal })\n status = polled.status\n answered = polled.answered\n value = polled.value\n } catch (error) {\n if (controller.signal.aborted && !data.signal?.aborted) break\n throw error\n }\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n } finally {\n clearTimeout(timer)\n data.signal?.removeEventListener('abort', abort)\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n list: (opts?: DecisionListOptions) =>\n request<DecisionList>('GET', '/decisions', opts ? { ...opts } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import type {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\n })\n","import type {\n AuthorizationEvaluation,\n AuthorizationQuery,\n AuthorizeRequest,\n AuthorizeResult,\n DecisionSubject,\n RequestFn,\n} from './types'\nimport type { DecisionsResource } from './resources/decisions'\n\nconst defaultQuestion = (toolName: string, toolTarget?: string): string =>\n `Approve ${toolName}${toolTarget ? ` ${toolTarget}` : ''}?`\n\n// The evaluation and the ask must describe the same action. Built once so a field\n// added to the subject cannot reach one call and not the other, which would let a\n// decision be recorded under a different description than the one policy saw.\nexport const authorizationSubject = (input: DecisionSubject): DecisionSubject => ({\n toolName: input.toolName,\n toolTarget: input.toolTarget,\n actor: input.actor,\n environment: input.environment,\n parameters: input.parameters,\n})\n\n/**\n * The raw boundary: what the site's policy says about one action, with nobody\n * paged and no decision opened.\n *\n * Exported separately from {@link createAuthorize} because a caller that owns its\n * own escalation — the adapter gate, or a custom harness with its own approval\n * surface — needs to tell \"policy had no opinion\" apart from \"the ask failed\".\n * Composing the two calls hides that difference, and the two failures want\n * opposite handling.\n */\nexport const createEvaluateAuthorization =\n (request: RequestFn) =>\n (input: AuthorizationQuery): Promise<AuthorizationEvaluation> =>\n request<AuthorizationEvaluation>('POST', '/authorize', {\n ...authorizationSubject(input),\n externalId: input.externalId,\n question: input.question,\n agentName: input.agentName,\n })\n\n/**\n * Ask policy first, and a person only if policy defers.\n *\n * Two calls rather than one server round trip on purpose. POST /authorize\n * evaluates and nothing else, so every limit, reachability check and delivery\n * rung stays in the one path that already owns them, and this composes the two.\n * The extra hop only happens when a human is needed, where it is lost in the\n * time that person takes to answer.\n *\n * Fail-closed throughout: a denial and an unanswered ask both return\n * approved: false, and the reason says which it was.\n */\nexport const createAuthorize = (request: RequestFn, decisions: DecisionsResource) => {\n const evaluate = createEvaluateAuthorization(request)\n\n return async (input: AuthorizeRequest): Promise<AuthorizeResult> => {\n const subject = authorizationSubject(input)\n const evaluation = await evaluate(input)\n\n if (evaluation.verdict !== 'requires_human') {\n return {\n approved: evaluation.verdict === 'allow',\n resolvedBy: 'policy',\n policy: evaluation.policy,\n reason: evaluation.reason,\n decisionId: null,\n authorizationId: evaluation.authorizationId,\n }\n }\n\n const answer = await decisions.ask({\n question: input.question ?? defaultQuestion(input.toolName, input.toolTarget),\n type: 'confirm',\n externalId: input.externalId,\n agentName: input.agentName,\n ...subject,\n timeoutMs: input.timeoutMs,\n expiresInSeconds: input.expiresInSeconds,\n idempotencyKey: input.idempotencyKey,\n requireReachable: input.requireReachable,\n })\n\n return {\n approved: answer.approved,\n resolvedBy: 'human',\n policy: evaluation.policy,\n reason: answer.approved\n ? 'A person approved it.'\n : answer.answered\n ? 'A person denied it.'\n : 'Nobody answered, so this was not approved.',\n decisionId: answer.decisionId,\n authorizationId: null,\n }\n }\n}\n","import { PusharyApiError } from './error'\nimport type {\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ConsumedAuthorization,\n ExecutionReceipt,\n PermitExecutionState,\n PermitRefusal,\n RequestFn,\n} from './types'\n\nconst REFUSALS: readonly PermitRefusal[] = [\n 'not_authorized',\n 'action_mismatch',\n 'expired',\n 'already_consumed',\n]\n\nconst EXECUTION_STATES: readonly PermitExecutionState[] = ['running', 'succeeded', 'failed']\n\n// A refusal is an answer, not a failure: the server looked, and said no. A\n// transport error is neither, and must not be dressed up as one — a caller that\n// reads \"already consumed\" stops, and a caller that reads a network error may\n// try again. Only a body the server actually sent gets read as a refusal.\nconst refusalOf = (error: unknown): ConsumeAuthorizationResult | null => {\n if (!(error instanceof PusharyApiError)) return null\n const body = error.body as { refusal?: unknown; executionState?: unknown } | undefined\n const refusal = body?.refusal\n if (typeof refusal !== 'string' || !REFUSALS.includes(refusal as PermitRefusal)) return null\n const state = body?.executionState\n return {\n consumed: false,\n refusal: refusal as PermitRefusal,\n reason: error.message,\n ...(typeof state === 'string' && EXECUTION_STATES.includes(state as PermitExecutionState)\n ? { executionState: state as PermitExecutionState }\n : {}),\n }\n}\n\n/**\n * Spend one settled authorization, at most once.\n *\n * Re-states the action about to run. That is the point: the server digests what\n * you claim and what it recorded, and refuses unless they are the same action.\n * An amount changed between the approval and the execution does not fail a check\n * that could be skipped — it simply has no permit.\n *\n * Refusals come back as `consumed: false` with a machine-readable reason.\n * Anything else throws, because \"the network was down\" is not \"you may not do\n * this\", and a caller that cannot tell them apart will eventually guess wrong.\n */\nexport const createConsumeAuthorization =\n (request: RequestFn) =>\n async (binding: AuthorizationBinding): Promise<ConsumeAuthorizationResult> => {\n try {\n const permit = await request<ConsumedAuthorization>('POST', '/authorizations/consume', {\n authorizationId: binding.authorizationId,\n toolName: binding.toolName,\n toolTarget: binding.toolTarget,\n actor: binding.actor,\n environment: binding.environment,\n externalId: binding.externalId,\n parameters: binding.parameters,\n })\n return { consumed: true, permit }\n } catch (error) {\n const refused = refusalOf(error)\n if (refused) return refused\n throw error\n }\n }\n\n/**\n * Say what the authorized action did.\n *\n * The evidence half. \"Was this allowed\" and \"did this happen\" are different\n * questions, and only the second one answers a customer asking where their\n * refund is. The first report wins; a late duplicate cannot rewrite it.\n */\nexport const createRecordExecution =\n (request: RequestFn) =>\n (receipt: ExecutionReceipt): Promise<{ readonly executionState: PermitExecutionState }> =>\n request('POST', `/authorizations/${encodeURIComponent(receipt.permitId)}/receipt`, {\n outcome: receipt.outcome,\n summary: receipt.summary,\n })\n","import type {\n PusharyConfig,\n RequestContext,\n EnrollResult,\n AuthorizationEvaluation,\n AuthorizationQuery,\n DecisionReach,\n AuthorizeRequest,\n AuthorizeResult,\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ExecutionReceipt,\n PermitExecutionState,\n} from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\nimport { createKeysResource, type KeysResource } from './resources/keys'\nimport { createAuthorize, createEvaluateAuthorization } from './authorize'\nimport { createConsumeAuthorization, createRecordExecution } from './permits'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\n // Ask policy whether one action may proceed, and a person only if policy\n // defers. allow and deny come back without anyone being paged. Requires the\n // Partner plan.\n readonly authorize: (input: AuthorizeRequest) => Promise<AuthorizeResult>\n // The verdict alone, with no decision opened and nobody paged. For a caller\n // that owns its own escalation and needs to tell \"policy deferred\" apart from\n // \"the ask failed\", which `authorize` folds together. Requires the Partner plan.\n readonly evaluateAuthorization: (input: AuthorizationQuery) => Promise<AuthorizationEvaluation>\n // Spend one settled authorization, at most once. Re-state the action you are\n // about to run: the server refuses unless it is the action that was approved.\n // Requires the Partner plan.\n readonly consumeAuthorization: (binding: AuthorizationBinding) => Promise<ConsumeAuthorizationResult>\n // Say what the authorized action did. The first report wins.\n readonly recordExecution: (receipt: ExecutionReceipt) => Promise<{ readonly executionState: PermitExecutionState }>\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n // Can this end-user be reached, and by whose hands? A read-only preflight, so\n // you can decide how to deliver BEFORE opening a decision and parking a run on\n // it. Opens nothing, mints nothing, costs no quota.\n readonly reachability: (externalId: string) => Promise<DecisionReach>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.requestTimeoutMs ?? 65_000,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n const decisions = createDecisionsResource(request)\n\n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions,\n keys: createKeysResource(request),\n authorize: createAuthorize(request, decisions),\n evaluateAuthorization: createEvaluateAuthorization(request),\n consumeAuthorization: createConsumeAuthorization(request),\n recordExecution: createRecordExecution(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n reachability: (externalId: string) =>\n request<DecisionReach>('GET', '/reachability', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -245,6 +245,7 @@ var createDecisionsResource = (request) => {
245
245
  return Object.freeze({
246
246
  create: (data) => request("POST", "/decisions", data),
247
247
  get: (id, opts) => request("GET", `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : void 0),
248
+ list: (opts) => request("GET", "/decisions", opts ? { ...opts } : void 0),
248
249
  ask,
249
250
  answer: (id, answer) => request("POST", `/decisions/${id}`, { answer }),
250
251
  cancel: (id) => request("DELETE", `/decisions/${id}`),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/error.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/util.ts","../src/resources/keys.ts","../src/authorize.ts","../src/permits.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["export { createPusharyServer, type PusharyServer } from './client'\n\nexport { PusharyApiError } from './error'\n\nexport type {\n PusharyConfig,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n SubscriberCount,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n CampaignStats,\n Template,\n CreateTemplate,\n UpdateTemplate,\n SendNotification,\n SendResult,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n FlowTriggerType,\n FlowStepType,\n FlowStatus,\n FlowStep,\n FlowStepConfig,\n FlowStepInput,\n ListParams,\n PaginatedResponse,\n DecisionType,\n DecisionStatus,\n DecisionSubject,\n DecisionDeliverability,\n DecisionReach,\n DecisionParameters,\n ActionPresentation,\n PresentedChange,\n PresentedFormat,\n SandboxOutcome,\n AuthorizationVerdict,\n AuthorizationQuery,\n AuthorizeRequest,\n AuthorizeResult,\n AuthorizationEvaluation,\n AuthorizationBinding,\n AuthorizationAuthority,\n ConsumedAuthorization,\n ConsumeAuthorizationResult,\n ExecutionOutcome,\n ExecutionReceipt,\n PermitRefusal,\n PermitExecutionState,\n CreateDecision,\n DecisionReachability,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n DecisionCallback,\n WebhookSecret,\n EnrollResult,\n AskDecision,\n AskResult,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from './types'\n\nexport type { SubscribersResource } from './resources/subscribers'\nexport type { CampaignsResource } from './resources/campaigns'\nexport type { TemplatesResource } from './resources/templates'\nexport type { NotificationsResource } from './resources/notifications'\nexport type { FlowsResource } from './resources/flows'\nexport type { DecisionsResource } from './resources/decisions'\nexport type { KeysResource } from './resources/keys'\n\nexport { verifyWebhookSignature, parseDecisionCallback, SIGNATURE_HEADER } from './webhook'\n\nexport { deterministicKey, isApproved } from './util'\n\n","/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown, options?: { readonly signal?: AbortSignal }): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n signal: options?.signal ?? AbortSignal.timeout(ctx.timeoutMs ?? 65_000),\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const timeoutMs = Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n // Zero retains the create-only API. Positive budgets include creation and polling.\n const deadline = Date.now() + timeoutMs\n const controller = new AbortController()\n const abort = () => controller.abort(data.signal?.reason)\n if (data.signal?.aborted) abort()\n else data.signal?.addEventListener('abort', abort, { once: true })\n const timer = setTimeout(() => controller.abort(new DOMException('Pushary ask timed out', 'TimeoutError')), timeoutMs || 65_000)\n try {\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n toolName: data.toolName,\n toolTarget: data.toolTarget,\n actor: data.actor,\n environment: data.environment,\n parameters: data.parameters,\n // Its changes name parameters rather than carrying values, so what the\n // person sees is read from the same numbers a rule compares.\n presentation: data.presentation,\n approvalUrl: data.approvalUrl,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n requireReachable: data.requireReachable,\n // Read only by a key issued on your workspace's sandbox site, where the\n // simulator answers instead of a person. Ignored in production.\n sandboxOutcome: data.sandboxOutcome,\n idempotencyKey,\n wait: false,\n }, { signal: controller.signal })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n try {\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n }, { signal: controller.signal })\n status = polled.status\n answered = polled.answered\n value = polled.value\n } catch (error) {\n if (controller.signal.aborted && !data.signal?.aborted) break\n throw error\n }\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n } finally {\n clearTimeout(timer)\n data.signal?.removeEventListener('abort', abort)\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nexport const decisionFingerprint = (value: unknown): string => {\n const ancestors = new Set<object>()\n const serialize = (item: unknown): string => {\n if (item === null || typeof item === 'string' || typeof item === 'boolean') return JSON.stringify(item)\n if (typeof item === 'number' && Number.isFinite(item)) return JSON.stringify(item)\n if (typeof item !== 'object' || ancestors.has(item)) {\n throw new TypeError('Pushary: decision identity must contain finite, acyclic JSON values.')\n }\n if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {\n throw new TypeError('Pushary: decision identity must contain plain JSON objects.')\n }\n ancestors.add(item)\n const entries = Object.entries(item)\n .filter(([, entry]) => entry !== undefined)\n .sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)\n const serialized = Array.isArray(item)\n ? `[${Array.from(item, serialize).join(',')}]`\n : `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serialize(entry)}`).join(',')}}`\n ancestors.delete(item)\n return serialized\n }\n return createHash('sha256').update(serialize(value)).digest('hex')\n}\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\n })\n","import type {\n AuthorizationEvaluation,\n AuthorizationQuery,\n AuthorizeRequest,\n AuthorizeResult,\n DecisionSubject,\n RequestFn,\n} from './types'\nimport type { DecisionsResource } from './resources/decisions'\n\nconst defaultQuestion = (toolName: string, toolTarget?: string): string =>\n `Approve ${toolName}${toolTarget ? ` ${toolTarget}` : ''}?`\n\n// The evaluation and the ask must describe the same action. Built once so a field\n// added to the subject cannot reach one call and not the other, which would let a\n// decision be recorded under a different description than the one policy saw.\nexport const authorizationSubject = (input: DecisionSubject): DecisionSubject => ({\n toolName: input.toolName,\n toolTarget: input.toolTarget,\n actor: input.actor,\n environment: input.environment,\n parameters: input.parameters,\n})\n\n/**\n * The raw boundary: what the site's policy says about one action, with nobody\n * paged and no decision opened.\n *\n * Exported separately from {@link createAuthorize} because a caller that owns its\n * own escalation — the adapter gate, or a custom harness with its own approval\n * surface — needs to tell \"policy had no opinion\" apart from \"the ask failed\".\n * Composing the two calls hides that difference, and the two failures want\n * opposite handling.\n */\nexport const createEvaluateAuthorization =\n (request: RequestFn) =>\n (input: AuthorizationQuery): Promise<AuthorizationEvaluation> =>\n request<AuthorizationEvaluation>('POST', '/authorize', {\n ...authorizationSubject(input),\n externalId: input.externalId,\n question: input.question,\n agentName: input.agentName,\n })\n\n/**\n * Ask policy first, and a person only if policy defers.\n *\n * Two calls rather than one server round trip on purpose. POST /authorize\n * evaluates and nothing else, so every limit, reachability check and delivery\n * rung stays in the one path that already owns them, and this composes the two.\n * The extra hop only happens when a human is needed, where it is lost in the\n * time that person takes to answer.\n *\n * Fail-closed throughout: a denial and an unanswered ask both return\n * approved: false, and the reason says which it was.\n */\nexport const createAuthorize = (request: RequestFn, decisions: DecisionsResource) => {\n const evaluate = createEvaluateAuthorization(request)\n\n return async (input: AuthorizeRequest): Promise<AuthorizeResult> => {\n const subject = authorizationSubject(input)\n const evaluation = await evaluate(input)\n\n if (evaluation.verdict !== 'requires_human') {\n return {\n approved: evaluation.verdict === 'allow',\n resolvedBy: 'policy',\n policy: evaluation.policy,\n reason: evaluation.reason,\n decisionId: null,\n authorizationId: evaluation.authorizationId,\n }\n }\n\n const answer = await decisions.ask({\n question: input.question ?? defaultQuestion(input.toolName, input.toolTarget),\n type: 'confirm',\n externalId: input.externalId,\n agentName: input.agentName,\n ...subject,\n timeoutMs: input.timeoutMs,\n expiresInSeconds: input.expiresInSeconds,\n idempotencyKey: input.idempotencyKey,\n requireReachable: input.requireReachable,\n })\n\n return {\n approved: answer.approved,\n resolvedBy: 'human',\n policy: evaluation.policy,\n reason: answer.approved\n ? 'A person approved it.'\n : answer.answered\n ? 'A person denied it.'\n : 'Nobody answered, so this was not approved.',\n decisionId: answer.decisionId,\n authorizationId: null,\n }\n }\n}\n","import { PusharyApiError } from './error'\nimport type {\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ConsumedAuthorization,\n ExecutionReceipt,\n PermitExecutionState,\n PermitRefusal,\n RequestFn,\n} from './types'\n\nconst REFUSALS: readonly PermitRefusal[] = [\n 'not_authorized',\n 'action_mismatch',\n 'expired',\n 'already_consumed',\n]\n\nconst EXECUTION_STATES: readonly PermitExecutionState[] = ['running', 'succeeded', 'failed']\n\n// A refusal is an answer, not a failure: the server looked, and said no. A\n// transport error is neither, and must not be dressed up as one — a caller that\n// reads \"already consumed\" stops, and a caller that reads a network error may\n// try again. Only a body the server actually sent gets read as a refusal.\nconst refusalOf = (error: unknown): ConsumeAuthorizationResult | null => {\n if (!(error instanceof PusharyApiError)) return null\n const body = error.body as { refusal?: unknown; executionState?: unknown } | undefined\n const refusal = body?.refusal\n if (typeof refusal !== 'string' || !REFUSALS.includes(refusal as PermitRefusal)) return null\n const state = body?.executionState\n return {\n consumed: false,\n refusal: refusal as PermitRefusal,\n reason: error.message,\n ...(typeof state === 'string' && EXECUTION_STATES.includes(state as PermitExecutionState)\n ? { executionState: state as PermitExecutionState }\n : {}),\n }\n}\n\n/**\n * Spend one settled authorization, at most once.\n *\n * Re-states the action about to run. That is the point: the server digests what\n * you claim and what it recorded, and refuses unless they are the same action.\n * An amount changed between the approval and the execution does not fail a check\n * that could be skipped — it simply has no permit.\n *\n * Refusals come back as `consumed: false` with a machine-readable reason.\n * Anything else throws, because \"the network was down\" is not \"you may not do\n * this\", and a caller that cannot tell them apart will eventually guess wrong.\n */\nexport const createConsumeAuthorization =\n (request: RequestFn) =>\n async (binding: AuthorizationBinding): Promise<ConsumeAuthorizationResult> => {\n try {\n const permit = await request<ConsumedAuthorization>('POST', '/authorizations/consume', {\n authorizationId: binding.authorizationId,\n toolName: binding.toolName,\n toolTarget: binding.toolTarget,\n actor: binding.actor,\n environment: binding.environment,\n externalId: binding.externalId,\n parameters: binding.parameters,\n })\n return { consumed: true, permit }\n } catch (error) {\n const refused = refusalOf(error)\n if (refused) return refused\n throw error\n }\n }\n\n/**\n * Say what the authorized action did.\n *\n * The evidence half. \"Was this allowed\" and \"did this happen\" are different\n * questions, and only the second one answers a customer asking where their\n * refund is. The first report wins; a late duplicate cannot rewrite it.\n */\nexport const createRecordExecution =\n (request: RequestFn) =>\n (receipt: ExecutionReceipt): Promise<{ readonly executionState: PermitExecutionState }> =>\n request('POST', `/authorizations/${encodeURIComponent(receipt.permitId)}/receipt`, {\n outcome: receipt.outcome,\n summary: receipt.summary,\n })\n","import type {\n PusharyConfig,\n RequestContext,\n EnrollResult,\n AuthorizationEvaluation,\n AuthorizationQuery,\n DecisionReach,\n AuthorizeRequest,\n AuthorizeResult,\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ExecutionReceipt,\n PermitExecutionState,\n} from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\nimport { createKeysResource, type KeysResource } from './resources/keys'\nimport { createAuthorize, createEvaluateAuthorization } from './authorize'\nimport { createConsumeAuthorization, createRecordExecution } from './permits'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\n // Ask policy whether one action may proceed, and a person only if policy\n // defers. allow and deny come back without anyone being paged. Requires the\n // Partner plan.\n readonly authorize: (input: AuthorizeRequest) => Promise<AuthorizeResult>\n // The verdict alone, with no decision opened and nobody paged. For a caller\n // that owns its own escalation and needs to tell \"policy deferred\" apart from\n // \"the ask failed\", which `authorize` folds together. Requires the Partner plan.\n readonly evaluateAuthorization: (input: AuthorizationQuery) => Promise<AuthorizationEvaluation>\n // Spend one settled authorization, at most once. Re-state the action you are\n // about to run: the server refuses unless it is the action that was approved.\n // Requires the Partner plan.\n readonly consumeAuthorization: (binding: AuthorizationBinding) => Promise<ConsumeAuthorizationResult>\n // Say what the authorized action did. The first report wins.\n readonly recordExecution: (receipt: ExecutionReceipt) => Promise<{ readonly executionState: PermitExecutionState }>\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n // Can this end-user be reached, and by whose hands? A read-only preflight, so\n // you can decide how to deliver BEFORE opening a decision and parking a run on\n // it. Opens nothing, mints nothing, costs no quota.\n readonly reachability: (externalId: string) => Promise<DecisionReach>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.requestTimeoutMs ?? 65_000,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n const decisions = createDecisionsResource(request)\n\n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions,\n keys: createKeysResource(request),\n authorize: createAuthorize(request, decisions),\n evaluateAuthorization: createEvaluateAuthorization(request),\n consumeAuthorization: createConsumeAuthorization(request),\n recordExecution: createRecordExecution(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n reachability: (externalId: string) =>\n request<DecisionReach>('GET', '/reachability', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/error.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/util.ts","../src/resources/keys.ts","../src/authorize.ts","../src/permits.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["export { createPusharyServer, type PusharyServer } from './client'\n\nexport { PusharyApiError } from './error'\n\nexport type {\n PusharyConfig,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n SubscriberCount,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n CampaignStats,\n Template,\n CreateTemplate,\n UpdateTemplate,\n SendNotification,\n SendResult,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n FlowTriggerType,\n FlowStepType,\n FlowStatus,\n FlowStep,\n FlowStepConfig,\n FlowStepInput,\n ListParams,\n PaginatedResponse,\n DecisionType,\n DecisionStatus,\n DecisionSubject,\n DecisionDeliverability,\n DecisionReach,\n DecisionParameters,\n ActionPresentation,\n PresentedChange,\n PresentedFormat,\n SandboxOutcome,\n AuthorizationVerdict,\n AuthorizationQuery,\n AuthorizeRequest,\n AuthorizeResult,\n AuthorizationEvaluation,\n AuthorizationBinding,\n AuthorizationAuthority,\n ConsumedAuthorization,\n ConsumeAuthorizationResult,\n ExecutionOutcome,\n ExecutionReceipt,\n PermitRefusal,\n PermitExecutionState,\n CreateDecision,\n DecisionReachability,\n DecisionResult,\n Decision,\n ListedDecision,\n DecisionList,\n DecisionListOptions,\n DecisionAnswerResult,\n CancelDecisionResult,\n DecisionCallback,\n WebhookSecret,\n EnrollResult,\n AskDecision,\n AskResult,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from './types'\n\nexport type { SubscribersResource } from './resources/subscribers'\nexport type { CampaignsResource } from './resources/campaigns'\nexport type { TemplatesResource } from './resources/templates'\nexport type { NotificationsResource } from './resources/notifications'\nexport type { FlowsResource } from './resources/flows'\nexport type { DecisionsResource } from './resources/decisions'\nexport type { KeysResource } from './resources/keys'\n\nexport { verifyWebhookSignature, parseDecisionCallback, SIGNATURE_HEADER } from './webhook'\n\nexport { deterministicKey, isApproved } from './util'\n\n","/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown, options?: { readonly signal?: AbortSignal }): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n signal: options?.signal ?? AbortSignal.timeout(ctx.timeoutMs ?? 65_000),\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionList,\n DecisionListOptions,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n readonly list: (opts?: DecisionListOptions) => Promise<DecisionList>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const timeoutMs = Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n // Zero retains the create-only API. Positive budgets include creation and polling.\n const deadline = Date.now() + timeoutMs\n const controller = new AbortController()\n const abort = () => controller.abort(data.signal?.reason)\n if (data.signal?.aborted) abort()\n else data.signal?.addEventListener('abort', abort, { once: true })\n const timer = setTimeout(() => controller.abort(new DOMException('Pushary ask timed out', 'TimeoutError')), timeoutMs || 65_000)\n try {\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n toolName: data.toolName,\n toolTarget: data.toolTarget,\n actor: data.actor,\n environment: data.environment,\n parameters: data.parameters,\n // Its changes name parameters rather than carrying values, so what the\n // person sees is read from the same numbers a rule compares.\n presentation: data.presentation,\n approvalUrl: data.approvalUrl,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n requireReachable: data.requireReachable,\n // Read only by a key issued on your workspace's sandbox site, where the\n // simulator answers instead of a person. Ignored in production.\n sandboxOutcome: data.sandboxOutcome,\n idempotencyKey,\n wait: false,\n }, { signal: controller.signal })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n try {\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n }, { signal: controller.signal })\n status = polled.status\n answered = polled.answered\n value = polled.value\n } catch (error) {\n if (controller.signal.aborted && !data.signal?.aborted) break\n throw error\n }\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n } finally {\n clearTimeout(timer)\n data.signal?.removeEventListener('abort', abort)\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n list: (opts?: DecisionListOptions) =>\n request<DecisionList>('GET', '/decisions', opts ? { ...opts } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nexport const decisionFingerprint = (value: unknown): string => {\n const ancestors = new Set<object>()\n const serialize = (item: unknown): string => {\n if (item === null || typeof item === 'string' || typeof item === 'boolean') return JSON.stringify(item)\n if (typeof item === 'number' && Number.isFinite(item)) return JSON.stringify(item)\n if (typeof item !== 'object' || ancestors.has(item)) {\n throw new TypeError('Pushary: decision identity must contain finite, acyclic JSON values.')\n }\n if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {\n throw new TypeError('Pushary: decision identity must contain plain JSON objects.')\n }\n ancestors.add(item)\n const entries = Object.entries(item)\n .filter(([, entry]) => entry !== undefined)\n .sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)\n const serialized = Array.isArray(item)\n ? `[${Array.from(item, serialize).join(',')}]`\n : `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serialize(entry)}`).join(',')}}`\n ancestors.delete(item)\n return serialized\n }\n return createHash('sha256').update(serialize(value)).digest('hex')\n}\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\n })\n","import type {\n AuthorizationEvaluation,\n AuthorizationQuery,\n AuthorizeRequest,\n AuthorizeResult,\n DecisionSubject,\n RequestFn,\n} from './types'\nimport type { DecisionsResource } from './resources/decisions'\n\nconst defaultQuestion = (toolName: string, toolTarget?: string): string =>\n `Approve ${toolName}${toolTarget ? ` ${toolTarget}` : ''}?`\n\n// The evaluation and the ask must describe the same action. Built once so a field\n// added to the subject cannot reach one call and not the other, which would let a\n// decision be recorded under a different description than the one policy saw.\nexport const authorizationSubject = (input: DecisionSubject): DecisionSubject => ({\n toolName: input.toolName,\n toolTarget: input.toolTarget,\n actor: input.actor,\n environment: input.environment,\n parameters: input.parameters,\n})\n\n/**\n * The raw boundary: what the site's policy says about one action, with nobody\n * paged and no decision opened.\n *\n * Exported separately from {@link createAuthorize} because a caller that owns its\n * own escalation — the adapter gate, or a custom harness with its own approval\n * surface — needs to tell \"policy had no opinion\" apart from \"the ask failed\".\n * Composing the two calls hides that difference, and the two failures want\n * opposite handling.\n */\nexport const createEvaluateAuthorization =\n (request: RequestFn) =>\n (input: AuthorizationQuery): Promise<AuthorizationEvaluation> =>\n request<AuthorizationEvaluation>('POST', '/authorize', {\n ...authorizationSubject(input),\n externalId: input.externalId,\n question: input.question,\n agentName: input.agentName,\n })\n\n/**\n * Ask policy first, and a person only if policy defers.\n *\n * Two calls rather than one server round trip on purpose. POST /authorize\n * evaluates and nothing else, so every limit, reachability check and delivery\n * rung stays in the one path that already owns them, and this composes the two.\n * The extra hop only happens when a human is needed, where it is lost in the\n * time that person takes to answer.\n *\n * Fail-closed throughout: a denial and an unanswered ask both return\n * approved: false, and the reason says which it was.\n */\nexport const createAuthorize = (request: RequestFn, decisions: DecisionsResource) => {\n const evaluate = createEvaluateAuthorization(request)\n\n return async (input: AuthorizeRequest): Promise<AuthorizeResult> => {\n const subject = authorizationSubject(input)\n const evaluation = await evaluate(input)\n\n if (evaluation.verdict !== 'requires_human') {\n return {\n approved: evaluation.verdict === 'allow',\n resolvedBy: 'policy',\n policy: evaluation.policy,\n reason: evaluation.reason,\n decisionId: null,\n authorizationId: evaluation.authorizationId,\n }\n }\n\n const answer = await decisions.ask({\n question: input.question ?? defaultQuestion(input.toolName, input.toolTarget),\n type: 'confirm',\n externalId: input.externalId,\n agentName: input.agentName,\n ...subject,\n timeoutMs: input.timeoutMs,\n expiresInSeconds: input.expiresInSeconds,\n idempotencyKey: input.idempotencyKey,\n requireReachable: input.requireReachable,\n })\n\n return {\n approved: answer.approved,\n resolvedBy: 'human',\n policy: evaluation.policy,\n reason: answer.approved\n ? 'A person approved it.'\n : answer.answered\n ? 'A person denied it.'\n : 'Nobody answered, so this was not approved.',\n decisionId: answer.decisionId,\n authorizationId: null,\n }\n }\n}\n","import { PusharyApiError } from './error'\nimport type {\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ConsumedAuthorization,\n ExecutionReceipt,\n PermitExecutionState,\n PermitRefusal,\n RequestFn,\n} from './types'\n\nconst REFUSALS: readonly PermitRefusal[] = [\n 'not_authorized',\n 'action_mismatch',\n 'expired',\n 'already_consumed',\n]\n\nconst EXECUTION_STATES: readonly PermitExecutionState[] = ['running', 'succeeded', 'failed']\n\n// A refusal is an answer, not a failure: the server looked, and said no. A\n// transport error is neither, and must not be dressed up as one — a caller that\n// reads \"already consumed\" stops, and a caller that reads a network error may\n// try again. Only a body the server actually sent gets read as a refusal.\nconst refusalOf = (error: unknown): ConsumeAuthorizationResult | null => {\n if (!(error instanceof PusharyApiError)) return null\n const body = error.body as { refusal?: unknown; executionState?: unknown } | undefined\n const refusal = body?.refusal\n if (typeof refusal !== 'string' || !REFUSALS.includes(refusal as PermitRefusal)) return null\n const state = body?.executionState\n return {\n consumed: false,\n refusal: refusal as PermitRefusal,\n reason: error.message,\n ...(typeof state === 'string' && EXECUTION_STATES.includes(state as PermitExecutionState)\n ? { executionState: state as PermitExecutionState }\n : {}),\n }\n}\n\n/**\n * Spend one settled authorization, at most once.\n *\n * Re-states the action about to run. That is the point: the server digests what\n * you claim and what it recorded, and refuses unless they are the same action.\n * An amount changed between the approval and the execution does not fail a check\n * that could be skipped — it simply has no permit.\n *\n * Refusals come back as `consumed: false` with a machine-readable reason.\n * Anything else throws, because \"the network was down\" is not \"you may not do\n * this\", and a caller that cannot tell them apart will eventually guess wrong.\n */\nexport const createConsumeAuthorization =\n (request: RequestFn) =>\n async (binding: AuthorizationBinding): Promise<ConsumeAuthorizationResult> => {\n try {\n const permit = await request<ConsumedAuthorization>('POST', '/authorizations/consume', {\n authorizationId: binding.authorizationId,\n toolName: binding.toolName,\n toolTarget: binding.toolTarget,\n actor: binding.actor,\n environment: binding.environment,\n externalId: binding.externalId,\n parameters: binding.parameters,\n })\n return { consumed: true, permit }\n } catch (error) {\n const refused = refusalOf(error)\n if (refused) return refused\n throw error\n }\n }\n\n/**\n * Say what the authorized action did.\n *\n * The evidence half. \"Was this allowed\" and \"did this happen\" are different\n * questions, and only the second one answers a customer asking where their\n * refund is. The first report wins; a late duplicate cannot rewrite it.\n */\nexport const createRecordExecution =\n (request: RequestFn) =>\n (receipt: ExecutionReceipt): Promise<{ readonly executionState: PermitExecutionState }> =>\n request('POST', `/authorizations/${encodeURIComponent(receipt.permitId)}/receipt`, {\n outcome: receipt.outcome,\n summary: receipt.summary,\n })\n","import type {\n PusharyConfig,\n RequestContext,\n EnrollResult,\n AuthorizationEvaluation,\n AuthorizationQuery,\n DecisionReach,\n AuthorizeRequest,\n AuthorizeResult,\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ExecutionReceipt,\n PermitExecutionState,\n} from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\nimport { createKeysResource, type KeysResource } from './resources/keys'\nimport { createAuthorize, createEvaluateAuthorization } from './authorize'\nimport { createConsumeAuthorization, createRecordExecution } from './permits'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\n // Ask policy whether one action may proceed, and a person only if policy\n // defers. allow and deny come back without anyone being paged. Requires the\n // Partner plan.\n readonly authorize: (input: AuthorizeRequest) => Promise<AuthorizeResult>\n // The verdict alone, with no decision opened and nobody paged. For a caller\n // that owns its own escalation and needs to tell \"policy deferred\" apart from\n // \"the ask failed\", which `authorize` folds together. Requires the Partner plan.\n readonly evaluateAuthorization: (input: AuthorizationQuery) => Promise<AuthorizationEvaluation>\n // Spend one settled authorization, at most once. Re-state the action you are\n // about to run: the server refuses unless it is the action that was approved.\n // Requires the Partner plan.\n readonly consumeAuthorization: (binding: AuthorizationBinding) => Promise<ConsumeAuthorizationResult>\n // Say what the authorized action did. The first report wins.\n readonly recordExecution: (receipt: ExecutionReceipt) => Promise<{ readonly executionState: PermitExecutionState }>\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n // Can this end-user be reached, and by whose hands? A read-only preflight, so\n // you can decide how to deliver BEFORE opening a decision and parking a run on\n // it. Opens nothing, mints nothing, costs no quota.\n readonly reachability: (externalId: string) => Promise<DecisionReach>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.requestTimeoutMs ?? 65_000,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n const decisions = createDecisionsResource(request)\n\n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions,\n keys: createKeysResource(request),\n authorize: createAuthorize(request, decisions),\n evaluateAuthorization: createEvaluateAuthorization(request),\n consumeAuthorization: createConsumeAuthorization(request),\n recordExecution: createRecordExecution(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n reachability: (externalId: string) =>\n request<DecisionReach>('GET', '/reachability', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ActionPresentation, a6 as AskDecision, a7 as AskResult, O as AuthorizationAuthority, N as AuthorizationBinding, M as AuthorizationEvaluation, I as AuthorizationQuery, H as AuthorizationVerdict, J as AuthorizeRequest, K as AuthorizeResult, a9 as BoundKeyResult, aa as BoundKeySummary, C as Campaign, g as CampaignStats, ad as CampaignsResource, a2 as CancelDecisionResult, R as ConsumeAuthorizationResult, Q as ConsumedAuthorization, e as CreateCampaign, Z as CreateDecision, l as CreateFlow, h as CreateTemplate, a0 as Decision, a1 as DecisionAnswerResult, a3 as DecisionCallback, x as DecisionDeliverability, z as DecisionParameters, y as DecisionReach, _ as DecisionReachability, $ as DecisionResult, v as DecisionStatus, w as DecisionSubject, D as DecisionType, ah as DecisionsResource, a5 as EnrollResult, V as ExecutionOutcome, W as ExecutionReceipt, F as Flow, n as FlowListParams, q as FlowStatus, r as FlowStep, s as FlowStepConfig, t as FlowStepInput, p as FlowStepType, o as FlowTriggerType, ag as FlowsResource, a8 as IssueBoundKey, ai as KeysResource, L as ListParams, af as NotificationsResource, u as PaginatedResponse, Y as PermitExecutionState, X as PermitRefusal, B as PresentedChange, E as PresentedFormat, a as PusharyConfig, P as PusharyServer, ab as RevokeKeyResult, al as SIGNATURE_HEADER, G as SandboxOutcome, j as SendNotification, k as SendResult, S as Subscriber, d as SubscriberCount, b as SubscriberListParams, ac as SubscribersResource, T as Template, ae as TemplatesResource, f as UpdateCampaign, m as UpdateFlow, U as UpdateSubscriber, i as UpdateTemplate, a4 as WebhookSecret, c as createPusharyServer, am as deterministicKey, an as isApproved, ak as parseDecisionCallback, aj as verifyWebhookSignature } from './util-CX6-cXFb.cjs';
1
+ export { A as ActionPresentation, a9 as AskDecision, aa as AskResult, O as AuthorizationAuthority, N as AuthorizationBinding, M as AuthorizationEvaluation, I as AuthorizationQuery, H as AuthorizationVerdict, J as AuthorizeRequest, K as AuthorizeResult, ac as BoundKeyResult, ad as BoundKeySummary, C as Campaign, g as CampaignStats, ag as CampaignsResource, a5 as CancelDecisionResult, R as ConsumeAuthorizationResult, Q as ConsumedAuthorization, e as CreateCampaign, Z as CreateDecision, l as CreateFlow, h as CreateTemplate, a0 as Decision, a4 as DecisionAnswerResult, a6 as DecisionCallback, x as DecisionDeliverability, a2 as DecisionList, a3 as DecisionListOptions, z as DecisionParameters, y as DecisionReach, _ as DecisionReachability, $ as DecisionResult, v as DecisionStatus, w as DecisionSubject, D as DecisionType, ak as DecisionsResource, a8 as EnrollResult, V as ExecutionOutcome, W as ExecutionReceipt, F as Flow, n as FlowListParams, q as FlowStatus, r as FlowStep, s as FlowStepConfig, t as FlowStepInput, p as FlowStepType, o as FlowTriggerType, aj as FlowsResource, ab as IssueBoundKey, al as KeysResource, L as ListParams, a1 as ListedDecision, ai as NotificationsResource, u as PaginatedResponse, Y as PermitExecutionState, X as PermitRefusal, B as PresentedChange, E as PresentedFormat, a as PusharyConfig, P as PusharyServer, ae as RevokeKeyResult, ao as SIGNATURE_HEADER, G as SandboxOutcome, j as SendNotification, k as SendResult, S as Subscriber, d as SubscriberCount, b as SubscriberListParams, af as SubscribersResource, T as Template, ah as TemplatesResource, f as UpdateCampaign, m as UpdateFlow, U as UpdateSubscriber, i as UpdateTemplate, a7 as WebhookSecret, c as createPusharyServer, ap as deterministicKey, aq as isApproved, an as parseDecisionCallback, am as verifyWebhookSignature } from './util-M0kCZWd-.cjs';
2
2
 
3
3
  /**
4
4
  * Thrown for any non-2xx response from the API.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ActionPresentation, a6 as AskDecision, a7 as AskResult, O as AuthorizationAuthority, N as AuthorizationBinding, M as AuthorizationEvaluation, I as AuthorizationQuery, H as AuthorizationVerdict, J as AuthorizeRequest, K as AuthorizeResult, a9 as BoundKeyResult, aa as BoundKeySummary, C as Campaign, g as CampaignStats, ad as CampaignsResource, a2 as CancelDecisionResult, R as ConsumeAuthorizationResult, Q as ConsumedAuthorization, e as CreateCampaign, Z as CreateDecision, l as CreateFlow, h as CreateTemplate, a0 as Decision, a1 as DecisionAnswerResult, a3 as DecisionCallback, x as DecisionDeliverability, z as DecisionParameters, y as DecisionReach, _ as DecisionReachability, $ as DecisionResult, v as DecisionStatus, w as DecisionSubject, D as DecisionType, ah as DecisionsResource, a5 as EnrollResult, V as ExecutionOutcome, W as ExecutionReceipt, F as Flow, n as FlowListParams, q as FlowStatus, r as FlowStep, s as FlowStepConfig, t as FlowStepInput, p as FlowStepType, o as FlowTriggerType, ag as FlowsResource, a8 as IssueBoundKey, ai as KeysResource, L as ListParams, af as NotificationsResource, u as PaginatedResponse, Y as PermitExecutionState, X as PermitRefusal, B as PresentedChange, E as PresentedFormat, a as PusharyConfig, P as PusharyServer, ab as RevokeKeyResult, al as SIGNATURE_HEADER, G as SandboxOutcome, j as SendNotification, k as SendResult, S as Subscriber, d as SubscriberCount, b as SubscriberListParams, ac as SubscribersResource, T as Template, ae as TemplatesResource, f as UpdateCampaign, m as UpdateFlow, U as UpdateSubscriber, i as UpdateTemplate, a4 as WebhookSecret, c as createPusharyServer, am as deterministicKey, an as isApproved, ak as parseDecisionCallback, aj as verifyWebhookSignature } from './util-CX6-cXFb.js';
1
+ export { A as ActionPresentation, a9 as AskDecision, aa as AskResult, O as AuthorizationAuthority, N as AuthorizationBinding, M as AuthorizationEvaluation, I as AuthorizationQuery, H as AuthorizationVerdict, J as AuthorizeRequest, K as AuthorizeResult, ac as BoundKeyResult, ad as BoundKeySummary, C as Campaign, g as CampaignStats, ag as CampaignsResource, a5 as CancelDecisionResult, R as ConsumeAuthorizationResult, Q as ConsumedAuthorization, e as CreateCampaign, Z as CreateDecision, l as CreateFlow, h as CreateTemplate, a0 as Decision, a4 as DecisionAnswerResult, a6 as DecisionCallback, x as DecisionDeliverability, a2 as DecisionList, a3 as DecisionListOptions, z as DecisionParameters, y as DecisionReach, _ as DecisionReachability, $ as DecisionResult, v as DecisionStatus, w as DecisionSubject, D as DecisionType, ak as DecisionsResource, a8 as EnrollResult, V as ExecutionOutcome, W as ExecutionReceipt, F as Flow, n as FlowListParams, q as FlowStatus, r as FlowStep, s as FlowStepConfig, t as FlowStepInput, p as FlowStepType, o as FlowTriggerType, aj as FlowsResource, ab as IssueBoundKey, al as KeysResource, L as ListParams, a1 as ListedDecision, ai as NotificationsResource, u as PaginatedResponse, Y as PermitExecutionState, X as PermitRefusal, B as PresentedChange, E as PresentedFormat, a as PusharyConfig, P as PusharyServer, ae as RevokeKeyResult, ao as SIGNATURE_HEADER, G as SandboxOutcome, j as SendNotification, k as SendResult, S as Subscriber, d as SubscriberCount, b as SubscriberListParams, af as SubscribersResource, T as Template, ah as TemplatesResource, f as UpdateCampaign, m as UpdateFlow, U as UpdateSubscriber, i as UpdateTemplate, a7 as WebhookSecret, c as createPusharyServer, ap as deterministicKey, aq as isApproved, an as parseDecisionCallback, am as verifyWebhookSignature } from './util-M0kCZWd-.js';
2
2
 
3
3
  /**
4
4
  * Thrown for any non-2xx response from the API.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  isApproved,
7
7
  parseDecisionCallback,
8
8
  verifyWebhookSignature
9
- } from "./chunk-AG6DI4RF.js";
9
+ } from "./chunk-DLJ3L77X.js";
10
10
  export {
11
11
  PusharyApiError,
12
12
  SIGNATURE_HEADER,
@@ -480,6 +480,33 @@ interface Decision {
480
480
  readonly answeredAt: string | null;
481
481
  readonly expiresAt: string | null;
482
482
  }
483
+ interface ListedDecision {
484
+ readonly decisionId: string | null;
485
+ readonly status: DecisionStatus;
486
+ readonly answered: boolean;
487
+ readonly value: string | null;
488
+ readonly type: string | null;
489
+ readonly question: string;
490
+ readonly externalId: string | null;
491
+ readonly context: string | null;
492
+ readonly createdAt: string;
493
+ readonly answeredAt: string | null;
494
+ readonly toolName: string | null;
495
+ readonly toolTarget: string | null;
496
+ readonly autoResolved: boolean;
497
+ readonly decisionOrigin: string | null;
498
+ }
499
+ interface DecisionListOptions {
500
+ readonly limit?: number;
501
+ readonly cursor?: string;
502
+ readonly from?: string;
503
+ readonly to?: string;
504
+ readonly externalId?: string;
505
+ }
506
+ interface DecisionList {
507
+ readonly decisions: readonly ListedDecision[];
508
+ readonly nextCursor: string | null;
509
+ }
483
510
  interface DecisionAnswerResult {
484
511
  readonly decisionId: string;
485
512
  readonly status: DecisionStatus;
@@ -610,6 +637,7 @@ interface DecisionsResource {
610
637
  readonly get: (id: string, opts?: {
611
638
  readonly wait?: number;
612
639
  }) => Promise<Decision>;
640
+ readonly list: (opts?: DecisionListOptions) => Promise<DecisionList>;
613
641
  readonly ask: (data: AskDecision) => Promise<AskResult>;
614
642
  readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>;
615
643
  readonly cancel: (id: string) => Promise<CancelDecisionResult>;
@@ -654,4 +682,4 @@ declare const isApproved: (decision: {
654
682
  readonly value: string | null;
655
683
  }) => boolean;
656
684
 
657
- export { type DecisionResult as $, type ActionPresentation as A, type PresentedChange as B, type Campaign as C, type DecisionType as D, type PresentedFormat as E, type Flow as F, type SandboxOutcome as G, type AuthorizationVerdict as H, type AuthorizationQuery as I, type AuthorizeRequest as J, type AuthorizeResult as K, type ListParams as L, type AuthorizationEvaluation as M, type AuthorizationBinding as N, type AuthorizationAuthority as O, type PusharyServer as P, type ConsumedAuthorization as Q, type ConsumeAuthorizationResult as R, type Subscriber as S, type Template as T, type UpdateSubscriber as U, type ExecutionOutcome as V, type ExecutionReceipt as W, type PermitRefusal as X, type PermitExecutionState as Y, type CreateDecision as Z, type DecisionReachability as _, type PusharyConfig as a, type Decision as a0, type DecisionAnswerResult as a1, type CancelDecisionResult as a2, type DecisionCallback as a3, type WebhookSecret as a4, type EnrollResult as a5, type AskDecision as a6, type AskResult as a7, type IssueBoundKey as a8, type BoundKeyResult as a9, type BoundKeySummary as aa, type RevokeKeyResult as ab, type SubscribersResource as ac, type CampaignsResource as ad, type TemplatesResource as ae, type NotificationsResource as af, type FlowsResource as ag, type DecisionsResource as ah, type KeysResource as ai, verifyWebhookSignature as aj, parseDecisionCallback as ak, SIGNATURE_HEADER as al, deterministicKey as am, isApproved as an, decisionFingerprint as ao, type SubscriberListParams as b, createPusharyServer as c, type SubscriberCount as d, type CreateCampaign as e, type UpdateCampaign as f, type CampaignStats as g, type CreateTemplate as h, type UpdateTemplate as i, type SendNotification as j, type SendResult as k, type CreateFlow as l, type UpdateFlow as m, type FlowListParams as n, type FlowTriggerType as o, type FlowStepType as p, type FlowStatus as q, type FlowStep as r, type FlowStepConfig as s, type FlowStepInput as t, type PaginatedResponse as u, type DecisionStatus as v, type DecisionSubject as w, type DecisionDeliverability as x, type DecisionReach as y, type DecisionParameters as z };
685
+ export { type DecisionResult as $, type ActionPresentation as A, type PresentedChange as B, type Campaign as C, type DecisionType as D, type PresentedFormat as E, type Flow as F, type SandboxOutcome as G, type AuthorizationVerdict as H, type AuthorizationQuery as I, type AuthorizeRequest as J, type AuthorizeResult as K, type ListParams as L, type AuthorizationEvaluation as M, type AuthorizationBinding as N, type AuthorizationAuthority as O, type PusharyServer as P, type ConsumedAuthorization as Q, type ConsumeAuthorizationResult as R, type Subscriber as S, type Template as T, type UpdateSubscriber as U, type ExecutionOutcome as V, type ExecutionReceipt as W, type PermitRefusal as X, type PermitExecutionState as Y, type CreateDecision as Z, type DecisionReachability as _, type PusharyConfig as a, type Decision as a0, type ListedDecision as a1, type DecisionList as a2, type DecisionListOptions as a3, type DecisionAnswerResult as a4, type CancelDecisionResult as a5, type DecisionCallback as a6, type WebhookSecret as a7, type EnrollResult as a8, type AskDecision as a9, type AskResult as aa, type IssueBoundKey as ab, type BoundKeyResult as ac, type BoundKeySummary as ad, type RevokeKeyResult as ae, type SubscribersResource as af, type CampaignsResource as ag, type TemplatesResource as ah, type NotificationsResource as ai, type FlowsResource as aj, type DecisionsResource as ak, type KeysResource as al, verifyWebhookSignature as am, parseDecisionCallback as an, SIGNATURE_HEADER as ao, deterministicKey as ap, isApproved as aq, decisionFingerprint as ar, type SubscriberListParams as b, createPusharyServer as c, type SubscriberCount as d, type CreateCampaign as e, type UpdateCampaign as f, type CampaignStats as g, type CreateTemplate as h, type UpdateTemplate as i, type SendNotification as j, type SendResult as k, type CreateFlow as l, type UpdateFlow as m, type FlowListParams as n, type FlowTriggerType as o, type FlowStepType as p, type FlowStatus as q, type FlowStep as r, type FlowStepConfig as s, type FlowStepInput as t, type PaginatedResponse as u, type DecisionStatus as v, type DecisionSubject as w, type DecisionDeliverability as x, type DecisionReach as y, type DecisionParameters as z };
@@ -480,6 +480,33 @@ interface Decision {
480
480
  readonly answeredAt: string | null;
481
481
  readonly expiresAt: string | null;
482
482
  }
483
+ interface ListedDecision {
484
+ readonly decisionId: string | null;
485
+ readonly status: DecisionStatus;
486
+ readonly answered: boolean;
487
+ readonly value: string | null;
488
+ readonly type: string | null;
489
+ readonly question: string;
490
+ readonly externalId: string | null;
491
+ readonly context: string | null;
492
+ readonly createdAt: string;
493
+ readonly answeredAt: string | null;
494
+ readonly toolName: string | null;
495
+ readonly toolTarget: string | null;
496
+ readonly autoResolved: boolean;
497
+ readonly decisionOrigin: string | null;
498
+ }
499
+ interface DecisionListOptions {
500
+ readonly limit?: number;
501
+ readonly cursor?: string;
502
+ readonly from?: string;
503
+ readonly to?: string;
504
+ readonly externalId?: string;
505
+ }
506
+ interface DecisionList {
507
+ readonly decisions: readonly ListedDecision[];
508
+ readonly nextCursor: string | null;
509
+ }
483
510
  interface DecisionAnswerResult {
484
511
  readonly decisionId: string;
485
512
  readonly status: DecisionStatus;
@@ -610,6 +637,7 @@ interface DecisionsResource {
610
637
  readonly get: (id: string, opts?: {
611
638
  readonly wait?: number;
612
639
  }) => Promise<Decision>;
640
+ readonly list: (opts?: DecisionListOptions) => Promise<DecisionList>;
613
641
  readonly ask: (data: AskDecision) => Promise<AskResult>;
614
642
  readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>;
615
643
  readonly cancel: (id: string) => Promise<CancelDecisionResult>;
@@ -654,4 +682,4 @@ declare const isApproved: (decision: {
654
682
  readonly value: string | null;
655
683
  }) => boolean;
656
684
 
657
- export { type DecisionResult as $, type ActionPresentation as A, type PresentedChange as B, type Campaign as C, type DecisionType as D, type PresentedFormat as E, type Flow as F, type SandboxOutcome as G, type AuthorizationVerdict as H, type AuthorizationQuery as I, type AuthorizeRequest as J, type AuthorizeResult as K, type ListParams as L, type AuthorizationEvaluation as M, type AuthorizationBinding as N, type AuthorizationAuthority as O, type PusharyServer as P, type ConsumedAuthorization as Q, type ConsumeAuthorizationResult as R, type Subscriber as S, type Template as T, type UpdateSubscriber as U, type ExecutionOutcome as V, type ExecutionReceipt as W, type PermitRefusal as X, type PermitExecutionState as Y, type CreateDecision as Z, type DecisionReachability as _, type PusharyConfig as a, type Decision as a0, type DecisionAnswerResult as a1, type CancelDecisionResult as a2, type DecisionCallback as a3, type WebhookSecret as a4, type EnrollResult as a5, type AskDecision as a6, type AskResult as a7, type IssueBoundKey as a8, type BoundKeyResult as a9, type BoundKeySummary as aa, type RevokeKeyResult as ab, type SubscribersResource as ac, type CampaignsResource as ad, type TemplatesResource as ae, type NotificationsResource as af, type FlowsResource as ag, type DecisionsResource as ah, type KeysResource as ai, verifyWebhookSignature as aj, parseDecisionCallback as ak, SIGNATURE_HEADER as al, deterministicKey as am, isApproved as an, decisionFingerprint as ao, type SubscriberListParams as b, createPusharyServer as c, type SubscriberCount as d, type CreateCampaign as e, type UpdateCampaign as f, type CampaignStats as g, type CreateTemplate as h, type UpdateTemplate as i, type SendNotification as j, type SendResult as k, type CreateFlow as l, type UpdateFlow as m, type FlowListParams as n, type FlowTriggerType as o, type FlowStepType as p, type FlowStatus as q, type FlowStep as r, type FlowStepConfig as s, type FlowStepInput as t, type PaginatedResponse as u, type DecisionStatus as v, type DecisionSubject as w, type DecisionDeliverability as x, type DecisionReach as y, type DecisionParameters as z };
685
+ export { type DecisionResult as $, type ActionPresentation as A, type PresentedChange as B, type Campaign as C, type DecisionType as D, type PresentedFormat as E, type Flow as F, type SandboxOutcome as G, type AuthorizationVerdict as H, type AuthorizationQuery as I, type AuthorizeRequest as J, type AuthorizeResult as K, type ListParams as L, type AuthorizationEvaluation as M, type AuthorizationBinding as N, type AuthorizationAuthority as O, type PusharyServer as P, type ConsumedAuthorization as Q, type ConsumeAuthorizationResult as R, type Subscriber as S, type Template as T, type UpdateSubscriber as U, type ExecutionOutcome as V, type ExecutionReceipt as W, type PermitRefusal as X, type PermitExecutionState as Y, type CreateDecision as Z, type DecisionReachability as _, type PusharyConfig as a, type Decision as a0, type ListedDecision as a1, type DecisionList as a2, type DecisionListOptions as a3, type DecisionAnswerResult as a4, type CancelDecisionResult as a5, type DecisionCallback as a6, type WebhookSecret as a7, type EnrollResult as a8, type AskDecision as a9, type AskResult as aa, type IssueBoundKey as ab, type BoundKeyResult as ac, type BoundKeySummary as ad, type RevokeKeyResult as ae, type SubscribersResource as af, type CampaignsResource as ag, type TemplatesResource as ah, type NotificationsResource as ai, type FlowsResource as aj, type DecisionsResource as ak, type KeysResource as al, verifyWebhookSignature as am, parseDecisionCallback as an, SIGNATURE_HEADER as ao, deterministicKey as ap, isApproved as aq, decisionFingerprint as ar, type SubscriberListParams as b, createPusharyServer as c, type SubscriberCount as d, type CreateCampaign as e, type UpdateCampaign as f, type CampaignStats as g, type CreateTemplate as h, type UpdateTemplate as i, type SendNotification as j, type SendResult as k, type CreateFlow as l, type UpdateFlow as m, type FlowListParams as n, type FlowTriggerType as o, type FlowStepType as p, type FlowStatus as q, type FlowStep as r, type FlowStepConfig as s, type FlowStepInput as t, type PaginatedResponse as u, type DecisionStatus as v, type DecisionSubject as w, type DecisionDeliverability as x, type DecisionReach as y, type DecisionParameters as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/server",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Pushary Server SDK: send push notifications and add human-in-the-loop approvals to your AI agent (pause on a decision until a specific human answers).",
5
5
  "author": "Pushary <business@pushary.com>",
6
6
  "homepage": "https://pushary.com",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/error.ts","../src/util.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/resources/keys.ts","../src/authorize.ts","../src/permits.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nexport const decisionFingerprint = (value: unknown): string => {\n const ancestors = new Set<object>()\n const serialize = (item: unknown): string => {\n if (item === null || typeof item === 'string' || typeof item === 'boolean') return JSON.stringify(item)\n if (typeof item === 'number' && Number.isFinite(item)) return JSON.stringify(item)\n if (typeof item !== 'object' || ancestors.has(item)) {\n throw new TypeError('Pushary: decision identity must contain finite, acyclic JSON values.')\n }\n if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {\n throw new TypeError('Pushary: decision identity must contain plain JSON objects.')\n }\n ancestors.add(item)\n const entries = Object.entries(item)\n .filter(([, entry]) => entry !== undefined)\n .sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)\n const serialized = Array.isArray(item)\n ? `[${Array.from(item, serialize).join(',')}]`\n : `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serialize(entry)}`).join(',')}}`\n ancestors.delete(item)\n return serialized\n }\n return createHash('sha256').update(serialize(value)).digest('hex')\n}\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown, options?: { readonly signal?: AbortSignal }): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n signal: options?.signal ?? AbortSignal.timeout(ctx.timeoutMs ?? 65_000),\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const timeoutMs = Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n // Zero retains the create-only API. Positive budgets include creation and polling.\n const deadline = Date.now() + timeoutMs\n const controller = new AbortController()\n const abort = () => controller.abort(data.signal?.reason)\n if (data.signal?.aborted) abort()\n else data.signal?.addEventListener('abort', abort, { once: true })\n const timer = setTimeout(() => controller.abort(new DOMException('Pushary ask timed out', 'TimeoutError')), timeoutMs || 65_000)\n try {\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n toolName: data.toolName,\n toolTarget: data.toolTarget,\n actor: data.actor,\n environment: data.environment,\n parameters: data.parameters,\n // Its changes name parameters rather than carrying values, so what the\n // person sees is read from the same numbers a rule compares.\n presentation: data.presentation,\n approvalUrl: data.approvalUrl,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n requireReachable: data.requireReachable,\n // Read only by a key issued on your workspace's sandbox site, where the\n // simulator answers instead of a person. Ignored in production.\n sandboxOutcome: data.sandboxOutcome,\n idempotencyKey,\n wait: false,\n }, { signal: controller.signal })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n try {\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n }, { signal: controller.signal })\n status = polled.status\n answered = polled.answered\n value = polled.value\n } catch (error) {\n if (controller.signal.aborted && !data.signal?.aborted) break\n throw error\n }\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n } finally {\n clearTimeout(timer)\n data.signal?.removeEventListener('abort', abort)\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import type {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\n })\n","import type {\n AuthorizationEvaluation,\n AuthorizationQuery,\n AuthorizeRequest,\n AuthorizeResult,\n DecisionSubject,\n RequestFn,\n} from './types'\nimport type { DecisionsResource } from './resources/decisions'\n\nconst defaultQuestion = (toolName: string, toolTarget?: string): string =>\n `Approve ${toolName}${toolTarget ? ` ${toolTarget}` : ''}?`\n\n// The evaluation and the ask must describe the same action. Built once so a field\n// added to the subject cannot reach one call and not the other, which would let a\n// decision be recorded under a different description than the one policy saw.\nexport const authorizationSubject = (input: DecisionSubject): DecisionSubject => ({\n toolName: input.toolName,\n toolTarget: input.toolTarget,\n actor: input.actor,\n environment: input.environment,\n parameters: input.parameters,\n})\n\n/**\n * The raw boundary: what the site's policy says about one action, with nobody\n * paged and no decision opened.\n *\n * Exported separately from {@link createAuthorize} because a caller that owns its\n * own escalation — the adapter gate, or a custom harness with its own approval\n * surface — needs to tell \"policy had no opinion\" apart from \"the ask failed\".\n * Composing the two calls hides that difference, and the two failures want\n * opposite handling.\n */\nexport const createEvaluateAuthorization =\n (request: RequestFn) =>\n (input: AuthorizationQuery): Promise<AuthorizationEvaluation> =>\n request<AuthorizationEvaluation>('POST', '/authorize', {\n ...authorizationSubject(input),\n externalId: input.externalId,\n question: input.question,\n agentName: input.agentName,\n })\n\n/**\n * Ask policy first, and a person only if policy defers.\n *\n * Two calls rather than one server round trip on purpose. POST /authorize\n * evaluates and nothing else, so every limit, reachability check and delivery\n * rung stays in the one path that already owns them, and this composes the two.\n * The extra hop only happens when a human is needed, where it is lost in the\n * time that person takes to answer.\n *\n * Fail-closed throughout: a denial and an unanswered ask both return\n * approved: false, and the reason says which it was.\n */\nexport const createAuthorize = (request: RequestFn, decisions: DecisionsResource) => {\n const evaluate = createEvaluateAuthorization(request)\n\n return async (input: AuthorizeRequest): Promise<AuthorizeResult> => {\n const subject = authorizationSubject(input)\n const evaluation = await evaluate(input)\n\n if (evaluation.verdict !== 'requires_human') {\n return {\n approved: evaluation.verdict === 'allow',\n resolvedBy: 'policy',\n policy: evaluation.policy,\n reason: evaluation.reason,\n decisionId: null,\n authorizationId: evaluation.authorizationId,\n }\n }\n\n const answer = await decisions.ask({\n question: input.question ?? defaultQuestion(input.toolName, input.toolTarget),\n type: 'confirm',\n externalId: input.externalId,\n agentName: input.agentName,\n ...subject,\n timeoutMs: input.timeoutMs,\n expiresInSeconds: input.expiresInSeconds,\n idempotencyKey: input.idempotencyKey,\n requireReachable: input.requireReachable,\n })\n\n return {\n approved: answer.approved,\n resolvedBy: 'human',\n policy: evaluation.policy,\n reason: answer.approved\n ? 'A person approved it.'\n : answer.answered\n ? 'A person denied it.'\n : 'Nobody answered, so this was not approved.',\n decisionId: answer.decisionId,\n authorizationId: null,\n }\n }\n}\n","import { PusharyApiError } from './error'\nimport type {\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ConsumedAuthorization,\n ExecutionReceipt,\n PermitExecutionState,\n PermitRefusal,\n RequestFn,\n} from './types'\n\nconst REFUSALS: readonly PermitRefusal[] = [\n 'not_authorized',\n 'action_mismatch',\n 'expired',\n 'already_consumed',\n]\n\nconst EXECUTION_STATES: readonly PermitExecutionState[] = ['running', 'succeeded', 'failed']\n\n// A refusal is an answer, not a failure: the server looked, and said no. A\n// transport error is neither, and must not be dressed up as one — a caller that\n// reads \"already consumed\" stops, and a caller that reads a network error may\n// try again. Only a body the server actually sent gets read as a refusal.\nconst refusalOf = (error: unknown): ConsumeAuthorizationResult | null => {\n if (!(error instanceof PusharyApiError)) return null\n const body = error.body as { refusal?: unknown; executionState?: unknown } | undefined\n const refusal = body?.refusal\n if (typeof refusal !== 'string' || !REFUSALS.includes(refusal as PermitRefusal)) return null\n const state = body?.executionState\n return {\n consumed: false,\n refusal: refusal as PermitRefusal,\n reason: error.message,\n ...(typeof state === 'string' && EXECUTION_STATES.includes(state as PermitExecutionState)\n ? { executionState: state as PermitExecutionState }\n : {}),\n }\n}\n\n/**\n * Spend one settled authorization, at most once.\n *\n * Re-states the action about to run. That is the point: the server digests what\n * you claim and what it recorded, and refuses unless they are the same action.\n * An amount changed between the approval and the execution does not fail a check\n * that could be skipped — it simply has no permit.\n *\n * Refusals come back as `consumed: false` with a machine-readable reason.\n * Anything else throws, because \"the network was down\" is not \"you may not do\n * this\", and a caller that cannot tell them apart will eventually guess wrong.\n */\nexport const createConsumeAuthorization =\n (request: RequestFn) =>\n async (binding: AuthorizationBinding): Promise<ConsumeAuthorizationResult> => {\n try {\n const permit = await request<ConsumedAuthorization>('POST', '/authorizations/consume', {\n authorizationId: binding.authorizationId,\n toolName: binding.toolName,\n toolTarget: binding.toolTarget,\n actor: binding.actor,\n environment: binding.environment,\n externalId: binding.externalId,\n parameters: binding.parameters,\n })\n return { consumed: true, permit }\n } catch (error) {\n const refused = refusalOf(error)\n if (refused) return refused\n throw error\n }\n }\n\n/**\n * Say what the authorized action did.\n *\n * The evidence half. \"Was this allowed\" and \"did this happen\" are different\n * questions, and only the second one answers a customer asking where their\n * refund is. The first report wins; a late duplicate cannot rewrite it.\n */\nexport const createRecordExecution =\n (request: RequestFn) =>\n (receipt: ExecutionReceipt): Promise<{ readonly executionState: PermitExecutionState }> =>\n request('POST', `/authorizations/${encodeURIComponent(receipt.permitId)}/receipt`, {\n outcome: receipt.outcome,\n summary: receipt.summary,\n })\n","import type {\n PusharyConfig,\n RequestContext,\n EnrollResult,\n AuthorizationEvaluation,\n AuthorizationQuery,\n DecisionReach,\n AuthorizeRequest,\n AuthorizeResult,\n AuthorizationBinding,\n ConsumeAuthorizationResult,\n ExecutionReceipt,\n PermitExecutionState,\n} from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\nimport { createKeysResource, type KeysResource } from './resources/keys'\nimport { createAuthorize, createEvaluateAuthorization } from './authorize'\nimport { createConsumeAuthorization, createRecordExecution } from './permits'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\n // Ask policy whether one action may proceed, and a person only if policy\n // defers. allow and deny come back without anyone being paged. Requires the\n // Partner plan.\n readonly authorize: (input: AuthorizeRequest) => Promise<AuthorizeResult>\n // The verdict alone, with no decision opened and nobody paged. For a caller\n // that owns its own escalation and needs to tell \"policy deferred\" apart from\n // \"the ask failed\", which `authorize` folds together. Requires the Partner plan.\n readonly evaluateAuthorization: (input: AuthorizationQuery) => Promise<AuthorizationEvaluation>\n // Spend one settled authorization, at most once. Re-state the action you are\n // about to run: the server refuses unless it is the action that was approved.\n // Requires the Partner plan.\n readonly consumeAuthorization: (binding: AuthorizationBinding) => Promise<ConsumeAuthorizationResult>\n // Say what the authorized action did. The first report wins.\n readonly recordExecution: (receipt: ExecutionReceipt) => Promise<{ readonly executionState: PermitExecutionState }>\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n // Can this end-user be reached, and by whose hands? A read-only preflight, so\n // you can decide how to deliver BEFORE opening a decision and parking a run on\n // it. Opens nothing, mints nothing, costs no quota.\n readonly reachability: (externalId: string) => Promise<DecisionReach>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.requestTimeoutMs ?? 65_000,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n const decisions = createDecisionsResource(request)\n\n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions,\n keys: createKeysResource(request),\n authorize: createAuthorize(request, decisions),\n evaluateAuthorization: createEvaluateAuthorization(request),\n consumeAuthorization: createConsumeAuthorization(request),\n recordExecution: createRecordExecution(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n reachability: (externalId: string) =>\n request<DecisionReach>('GET', '/reachability', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":"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","names":[]}