@tangle-network/agent-app 0.49.9 → 0.49.11
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/dist/chat-routes/index.js +1 -1
- package/dist/{chunk-7S24DNVP.js → chunk-J42REVQJ.js} +1 -1
- package/dist/chunk-J42REVQJ.js.map +1 -0
- package/dist/hosted-agent/engine.d.ts +159 -0
- package/dist/hosted-agent/index.d.ts +1 -0
- package/dist/hosted-agent/index.js +233 -38
- package/dist/hosted-agent/index.js.map +1 -1
- package/dist/preset-cloudflare/index.js +10 -1
- package/dist/preset-cloudflare/index.js.map +1 -1
- package/dist/sandbox/index.js +1 -1
- package/package.json +11 -11
- package/dist/chunk-7S24DNVP.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hosted-agent/index.ts"],"sourcesContent":["import { authenticateHubEventRequest, HubClient, type HubProviderEvent } from '@tangle-network/hub-sdk'\nimport { buildMessagingReply, normalizeConversationEvent } from '@tangle-network/agent-integrations/conversation-events'\nimport type { AgentProfile, BackendConfig, SandboxInstance } from '@tangle-network/sandbox'\nimport { Sandbox, SandboxError } from '@tangle-network/sandbox/core'\n\n/**\n * A hosted agent: a person texts or calls a line, and the agent answers from\n * that person's own isolated sandbox. The developer's Tangle API key pays for\n * every box, model turn and reply.\n *\n * Each person gets one fresh isolated box built from the published profile,\n * never a copy of the developer's box. Later messages resume the same box and\n * the same conversation, so text and voice share one memory. A new profile\n * starts a new session, and a deleted box or a new API key starts a new box;\n * either way the person's recent conversation carries over from the store.\n *\n * Every step is idempotent by the message's turn id: a retried delivery\n * settles the same sandbox turn and replays the same Hub send. So the caller\n * may retry any failure, and needs no dedupe table.\n */\n\n/** Durable key-value storage. A Cloudflare KV namespace satisfies it. */\nexport interface HostedAgentStore {\n get(key: string): Promise<string | null>\n put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>\n}\n\nexport type HostedChannel = 'imessage' | 'voice'\n\nexport interface HostedMessage {\n /** The sender's address as the channel delivered it: E.164 for a phone. */\n userId: string\n channel: HostedChannel\n text: string\n /** Stable per logical message. Reusing it settles the same turn. */\n turnId: string\n}\n\n/** What the allowance hook decides for one new message. */\nexport type Allowance = 'answer' | 'ignore' | { reply: string }\n\nexport interface HostedAgentConfig {\n /** The developer's Tangle API key (Sandbox and Hub). */\n apiKey: string\n /**\n * The persona every person's box runs. A profile without `model.default`\n * runs {@link DEFAULT_HOSTED_MODEL}, and a profile without `tools` runs with\n * {@link CONVERSATION_TOOLS_OFF} turned off. Set `tools` to choose your own,\n * for example on a harness that cannot turn those tools off.\n */\n profile: AgentProfile\n /** Backend harness type, such as `opencode`; the runtime default when omitted. */\n harness?: string\n store: HostedAgentStore\n /** Answers per person per UTC day before `allow` must decide. Default 20. */\n freeTurnsPerDay?: number\n /**\n * Decide a new message after the free allowance is used, for example reply\n * with a checkout link. Without it, a person past the allowance is told to\n * come back tomorrow. Retries of an admitted message never reach this hook.\n */\n allow?: (message: HostedMessage, usedToday: number) => Allowance | Promise<Allowance>\n /**\n * The owner's own address (E.164 phone). Only this sender can text DEBUG ON\n * or DEBUG OFF; while on, each reply to them ends with one ⚙ line naming the\n * harness, model, box, time, tokens, cost and tools of that turn.\n */\n owner?: string\n /** Shared secret for the ph0ny call hook and `ask_workspace` webhook tool. */\n voiceSecret?: string\n /** How long one `ask_workspace` call may wait before it returns a ticket. Default 8 s. */\n voiceBudgetMs?: number\n box?: Partial<BoxPolicy>\n turnWallCapMs?: number\n sandboxUrl?: string\n hubUrl?: string\n}\n\nexport interface BoxPolicy {\n cpuCores: number\n memoryMB: number\n diskGB: number\n idleTimeoutSeconds: number\n maxLifetimeSeconds: number\n deleteAfterStoppedSeconds: number\n /** Egress allow-list. The default reaches the model router only. */\n allowDomains: string[]\n}\n\nexport const DEFAULT_BOX_POLICY: BoxPolicy = {\n cpuCores: 1, memoryMB: 2048, diskGB: 10,\n idleTimeoutSeconds: 600, maxLifetimeSeconds: 86_400, deleteAfterStoppedSeconds: 7 * 86_400,\n allowDomains: ['router.tangle.tools'],\n}\n\n/**\n * Harness tools a texting or calling assistant does not use. Their\n * descriptions present every turn as coding work: a shell, file search,\n * sub-agents, to-do lists, skills and web fetch. File read, write and edit\n * stay, so a persona can keep notes such as `memory.md`.\n */\nexport const CONVERSATION_TOOLS_OFF = ['bash', 'glob', 'grep', 'task', 'todowrite', 'webfetch', 'skill'] as const\n\n/**\n * The model for a profile without `model.default`. It gave the most useful\n * on-topic replies among four Router models on the same five texts and calls\n * (2026-09-23), within the latency of the others.\n */\nexport const DEFAULT_HOSTED_MODEL = 'openai/gpt-5.6-luna'\n\n/** The profile a person's box runs: the developer's profile over the conversation defaults. */\nfunction conversationProfile(profile: AgentProfile): AgentProfile {\n return {\n ...profile,\n model: { ...profile.model, default: profile.model?.default ?? DEFAULT_HOSTED_MODEL },\n // A profile that sets `tools` owns its tool set.\n ...(profile.tools ? {} : {\n tools: Object.fromEntries(CONVERSATION_TOOLS_OFF.map(tool => [tool, false])),\n // The sandbox's preview policy grants the shell unless its permission\n // is denied, so turning the tool off alone leaves the shell in place.\n permissions: { bash: 'deny' as const, ...profile.permissions },\n }),\n }\n}\n\n/** What one turn reports about itself, for the owner's debug line. A figure\n * the run did not report stays undefined and is left out of the line. */\nexport interface TurnTrace {\n /** Box before the turn: `warm`, `resumed` or `new`. */\n box?: string\n harness?: string\n model?: string\n /** Sandbox clock: the run's own start to finish. */\n runMs?: number\n inputTokens?: number\n outputTokens?: number\n reasoningTokens?: number\n costUsd?: number\n tools?: number\n}\n\nexport type AskResult =\n | { state: 'answered'; text: string }\n | { state: 'declined'; reply?: string }\n | { state: 'pending' }\n\n/** A Hub delivery that passed authentication, in a form a queue can carry. */\nexport interface HostedInbound {\n runId: string\n connectionId: string\n event: HubProviderEvent\n}\n\nexport const NOTICE = {\n debugOn: 'Debug on. Each reply now ends with a ⚙ line: agent, harness, model, box, time, tokens, cost, tools and turn. Text DEBUG OFF to stop.',\n debugOff: 'Debug off. Replies no longer carry the ⚙ line.',\n stopped: 'You are unsubscribed. Text START to talk again.',\n started: 'Welcome back. Text me anytime. Reply STOP to stop.',\n limit: 'You have used today\\'s free messages. Talk tomorrow!',\n unavailable: 'Sorry, I could not answer that just now. Please try again in a minute.',\n} as const\n\nconst IMESSAGE_EVENT = 'inkbox.imessage.received'\nconst IMESSAGE_REPLY = 'inkbox.imessage.reply'\nconst STOP = /^(stop|stopall|unsubscribe|cancel|end|quit)$/i\nconst START = /^(start|unstop|resume)$/i\nconst TWO_DAYS = 2 * 86_400\nconst RESUMABLE = new Set(['stopped', 'expired'])\nconst GONE = new Set(['failed', 'deleted'])\n/** How much of a person's recent conversation a new session inherits. */\nconst CARRY_CHARS = 6000\nconst CARRY_HEAD = '[Your conversation with this person so far. Your instructions may have changed since; follow the current ones.]'\nconst CARRY_NOW = '[Their new message:]'\n\n/** A person's recent turns, kept outside the box so a new session or box can continue them. */\ninterface Conversation {\n /** The box and session the turns last ran in. */\n at: string\n /** Oldest first: turn id, what they said, what the agent answered. */\n turns: Array<[string, string, string]>\n}\n\nconst encode = (text: string) => new TextEncoder().encode(text)\nconst hex = (bytes: ArrayBuffer) => Array.from(new Uint8Array(bytes), byte => byte.toString(16).padStart(2, '0')).join('')\nconst sha256 = async (value: string) => hex(await crypto.subtle.digest('SHA-256', encode(value)))\n/** Compares digests, so the time taken reveals nothing about the secret. */\nasync function equal(given: string | null | undefined, expected: string): Promise<boolean> {\n if (!given) return false\n const [a, b] = await Promise.all([sha256(given), sha256(expected)])\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\nconst CALL_TTL_MS = 2 * 3_600_000\n/** 43 url-safe characters from 32 random bytes. */\nconst randomToken = () => btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\nconst b64 = (text: string) => btoa(String.fromCharCode(...encode(text))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\nconst unb64 = (text: string) => new TextDecoder().decode(Uint8Array.from(atob(text.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)))\nconst sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))\nconst json = (body: unknown, status = 200) => Response.json(body, { status })\nconst describe = (error: unknown) => (error instanceof Error ? `${error.name}: ${error.message}` : String(error))\n .replace(/sk-tan-[\\w-]+|Bearer\\s+\\S+/g, '<redacted>').slice(0, 400)\n\n/** One address per person: E.164 for anything phone-shaped, else lowercase. */\nexport function normalizeAddress(value: string): string | null {\n const trimmed = value.trim()\n if (trimmed.includes('@')) return trimmed.toLowerCase()\n const digits = trimmed.replace(/[^\\d]/g, '')\n if (digits.length < 8 || digits.length > 15) return null\n return `+${digits.length === 10 && !trimmed.startsWith('+') ? `1${digits}` : digits}`\n}\n\n/** `true` for DEBUG ON, `false` for DEBUG OFF, `null` for any other text. */\nexport function debugCommand(text: string): boolean | null {\n const match = /^debug\\s+(on|off)$/i.exec(text.trim())\n return match ? match[1]!.toLowerCase() === 'on' : null\n}\n\nconst num = (value: unknown) => typeof value === 'number' && Number.isFinite(value) ? value : undefined\nconst seconds = (ms: number) => ms < 10_000 ? `${(Math.max(0, ms) / 1000).toFixed(1)}s` : `${Math.round(ms / 1000)}s`\nconst count = (n: number) => n < 1000 ? String(n) : n < 10_000 ? `${(n / 1000).toFixed(1)}k` : `${Math.round(n / 1000)}k`\n\n/** The owner's ⚙ line. `totalMs` runs from the provider's receipt of the text to now. */\nexport function debugFooter(agent: string, turnId: string, trace: TurnTrace, totalMs?: number): string {\n const parts = [agent]\n if (trace.harness) parts.push(trace.harness)\n if (trace.model) parts.push(trace.model.split('/').at(-1)!)\n if (trace.box) parts.push(trace.box)\n if (totalMs !== undefined) parts.push(`${seconds(totalMs)}${trace.runMs !== undefined ? ` (run ${seconds(trace.runMs)})` : ''}`)\n else if (trace.runMs !== undefined) parts.push(`run ${seconds(trace.runMs)}`)\n if (trace.inputTokens !== undefined && trace.outputTokens !== undefined) {\n parts.push(`${count(trace.inputTokens)}→${count(trace.outputTokens)} tok${trace.reasoningTokens ? ` (${count(trace.reasoningTokens)} thinking)` : ''}`)\n }\n // Zero on a turn that used tokens means the harness had no price for the model.\n if (trace.costUsd !== undefined && (trace.costUsd > 0 || !trace.outputTokens)) {\n parts.push(trace.costUsd >= 0.01 ? `$${trace.costUsd.toFixed(3)}` : `$${trace.costUsd.toPrecision(2)}`)\n }\n if (trace.tools !== undefined) parts.push(`${trace.tools} tool${trace.tools === 1 ? '' : 's'}`)\n parts.push(turnId.slice(0, 8))\n return `⚙ ${parts.join(' · ')}`\n}\n\n/** Model, tokens, cost, tools and run time from a finished turn, as far as it reports them. */\nfunction noteResult(trace: TurnTrace, result: Record<string, unknown>, usage?: { inputTokens?: number; outputTokens?: number }) {\n // The SDK's typed usage carries counts only; the harness's own record also carries cost.\n const reported = (result.tokenUsage ?? {}) as Record<string, unknown>\n const timing = (result.timing ?? {}) as Record<string, unknown>\n const started = num(timing.startedAt), completed = num(timing.completedAt)\n if (started !== undefined && completed !== undefined) trace.runMs = completed - started\n trace.inputTokens = num(usage?.inputTokens) ?? num(reported.inputTokens)\n trace.outputTokens = num(usage?.outputTokens) ?? num(reported.outputTokens)\n trace.reasoningTokens = num(reported.reasoningTokens)\n if (result.usdKnown !== false) trace.costUsd = num(reported.cost)\n if (Array.isArray(result.toolInvocations)) trace.tools = result.toolInvocations.length\n}\n\nexport class HostedAgentError extends Error {\n constructor(readonly code: string, message: string) { super(message); this.name = 'HostedAgentError' }\n}\n\nexport function createHostedAgent(config: HostedAgentConfig) {\n const store = config.store\n const policy = { ...DEFAULT_BOX_POLICY, ...config.box }\n const wallCapMs = config.turnWallCapMs ?? 120_000\n const sandbox = new Sandbox({ apiKey: config.apiKey, baseUrl: config.sandboxUrl ?? 'https://sandbox.tangle.tools', timeoutMs: 20_000 })\n const hub = new HubClient({ baseUrl: config.hubUrl ?? 'https://id.tangle.tools', apiKey: config.apiKey })\n const profile = conversationProfile(config.profile)\n const backend: BackendConfig = { ...(config.harness ? { type: config.harness as BackendConfig['type'] } : {}), profile }\n const profileTag = sha256(JSON.stringify(profile)).then(hash => hash.slice(0, 8))\n\n /** Resume a stopped box. False when an earlier API key made it: the platform\n * resumes a box only under the key lineage that created it. */\n async function resumed(box: SandboxInstance): Promise<boolean> {\n try {\n await box.resume()\n return true\n } catch (error) {\n if (error instanceof SandboxError && error.code === 'SANDBOX_ATTRIBUTION_MISMATCH') return false\n throw error\n }\n }\n\n /** The person's running box: created on the first message, resumed later,\n * replaced when the platform deleted it. Null when the deadline passed. */\n async function ensureBox(user: string, deadline: number, trace?: TurnTrace): Promise<SandboxInstance | null> {\n const key = `box:${user}`\n const known = await store.get(key)\n let box = known ? await sandbox.get(known) : null\n if (trace) trace.box = !box || GONE.has(box.status) ? 'new' : RESUMABLE.has(box.status) ? 'resumed' : 'warm'\n if (box && RESUMABLE.has(box.status) && !await resumed(box)) {\n box = null\n if (trace) trace.box = 'new'\n }\n if (!box || GONE.has(box.status)) {\n // The key names the box it replaces, so concurrent first messages and\n // retries converge on one allocation instead of each creating a box.\n box = await sandbox.createIsolated({\n name: `hosted-${user.slice(0, 24)}`, idempotencyKey: known ? `hosted-${user}-after-${known}` : `hosted-${user}`,\n resources: { cpuCores: policy.cpuCores, memoryMB: policy.memoryMB, diskGB: policy.diskGB },\n backend, secrets: [], sshEnabled: false,\n egressPolicy: { mode: 'strict', allowDomains: policy.allowDomains, includeImplicitDomains: false },\n idleTimeoutSeconds: policy.idleTimeoutSeconds, maxLifetimeSeconds: policy.maxLifetimeSeconds,\n deleteAfterStoppedSeconds: policy.deleteAfterStoppedSeconds, metadata: { hostedUser: user },\n })\n await store.put(key, box.id)\n }\n if (RESUMABLE.has(box.status) && !await resumed(box)) throw new HostedAgentError('box_foreign', 'A new box was made under another API key.')\n if (box.status === 'running') return box\n const timeoutMs = deadline - Date.now()\n if (timeoutMs < 1000) return null\n try {\n return await sandbox.waitForRunning(box.id, { timeoutMs })\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') return null\n throw error\n }\n }\n\n /** Admit a new message once: STOP/START, the free allowance, then `allow`. */\n async function admit(message: HostedMessage, user: string): Promise<Allowance> {\n const stopKey = `stop:${user}`\n if (message.channel !== 'voice' && STOP.test(message.text.trim())) { await store.put(stopKey, '1'); return { reply: NOTICE.stopped } }\n if (message.channel !== 'voice' && START.test(message.text.trim())) { await store.put(stopKey, ''); return { reply: NOTICE.started } }\n if (await store.get(stopKey)) return 'ignore'\n const dayKey = `turns:${user}:${new Date().toISOString().slice(0, 10)}`\n // KV has no atomic increment, so concurrent messages may overshoot the\n // allowance by a few; the sponsor key's budget is the hard ceiling.\n const used = Number(await store.get(dayKey) ?? 0)\n const decision = used < (config.freeTurnsPerDay ?? 20) ? 'answer'\n : config.allow ? await config.allow(message, used) : { reply: NOTICE.limit }\n if (decision === 'answer') await store.put(dayKey, String(used + 1), { expirationTtl: TWO_DAYS })\n return decision\n }\n\n /** Run one message in the person's box until it answers or `deadline`. */\n async function ask(message: HostedMessage, options: { deadline: number; trace?: TurnTrace }): Promise<AskResult> {\n const text = message.text.trim()\n if (!text || text.length > 8000 || !/^[\\w-]{1,120}$/.test(message.turnId)) return { state: 'declined' }\n const user = (await sha256(message.userId)).slice(0, 32)\n // Scoped to the person: a voice ticket is model-supplied, so a turn id\n // admitted for one person must not skip another person's allowance.\n const admittedKey = `turn:${user}:${message.turnId}`\n if (!await store.get(admittedKey)) {\n const decision = await admit(message, user)\n if (decision === 'ignore') return { state: 'declined' }\n if (decision !== 'answer') return { state: 'declined', reply: decision.reply }\n await store.put(admittedKey, '1', { expirationTtl: TWO_DAYS })\n }\n // A retried delivery finds the box it woke already warm, so the first\n // attempt's view of the box is kept for the turn.\n const trace = options.trace, traceKey = `trace:${user}:${message.turnId}`\n const box = await ensureBox(user, options.deadline, trace)\n if (trace) {\n const first = await store.get(traceKey)\n if (first) trace.box = first\n else if (trace.box) await store.put(traceKey, trace.box, { expirationTtl: TWO_DAYS })\n }\n if (!box) return { state: 'pending' }\n // A session binds its backend when created, and the runtime would give a\n // bare session its generic default assistant. A new profile starts a new\n // session, so the persona always matches the one in force.\n const sessionId = `hosted-${user}-${await profileTag}`\n const session = await box.session(sessionId).status()\n if (!session) await box.createSession({ sessionId, retention: 'workspace', backend })\n if (trace) {\n trace.harness = session?.backend ?? config.harness\n trace.model = session?.model\n }\n const asked = message.channel === 'voice' ? `[Phone call. Answer in one to three short spoken sentences.]\\n${text}` : text\n // The first turn in a new session or box inherits the person's recent\n // turns. A retry before that turn completes carries them again, so a\n // failed first attempt loses nothing.\n const conversationKey = `convo:${user}`, at = `${box.id}/${sessionId}`\n const stored = await store.get(conversationKey)\n const conversation: Conversation = stored ? JSON.parse(stored) as Conversation : { at, turns: [] }\n const carried = conversation.at === at ? '' : conversation.turns.map(([, them, you]) => `Them: ${them}\\nYou: ${you}`).join('\\n')\n const prompt = carried ? `${CARRY_HEAD}\\n${carried}\\n${CARRY_NOW}\\n${asked}` : asked\n for (;;) {\n const result = await box.driveConversationTurn(prompt, { sessionId, turnId: message.turnId, wallCapMs, timeoutMs: 8000 })\n if (result.state === 'completed') {\n const answer = result.text.trim()\n if (!answer) throw new HostedAgentError('empty_reply', 'The turn completed with no reply text.')\n if (trace) noteResult(trace, result.result, result.usage)\n // Concurrent turns may drop one another's entry; the box keeps the full record.\n const turns = conversation.turns.filter(([id]) => id !== message.turnId)\n turns.push([message.turnId, message.channel === 'voice' ? `(on a call) ${text}` : text, answer])\n while (turns.length > 1 && JSON.stringify(turns).length > CARRY_CHARS) turns.shift()\n await store.put(conversationKey, JSON.stringify({ at, turns } satisfies Conversation))\n return { state: 'answered', text: answer }\n }\n if (result.state === 'failed') throw new HostedAgentError('turn_failed', result.error)\n if (Date.now() + 2000 > options.deadline) return { state: 'pending' }\n await sleep(2000)\n }\n }\n\n return {\n ask,\n\n /**\n * One-time setup: route an Inkbox iMessage identity's messages, connected\n * to Hub under the developer's account, to `callbackUrl`, and allow the\n * one reply action on that connection. Safe to repeat.\n */\n async connect(input: { connectionId: string; identityId: string; callbackUrl: string; secret: string }) {\n const webhook = await hub.connections.inboundWebhook.get(input.connectionId)\n if (!webhook.configured) await hub.connections.inboundWebhook.create(input.connectionId, { identityId: input.identityId, imessage: true })\n await hub.permissions.set({ connectionId: input.connectionId, actionPath: IMESSAGE_REPLY, decision: 'allow' })\n const { subscription } = await hub.eventSubscriptions.create({\n clientReference: `hosted-agent:${input.connectionId}`, label: 'Hosted agent messages',\n source: { type: 'connection', connectionId: input.connectionId }, event: IMESSAGE_EVENT,\n callback: { url: input.callbackUrl, secret: input.secret },\n })\n return subscription\n },\n\n /** The Hub callback. Authenticates within Hub's 10 s budget and runs nothing. */\n async receive(request: Request, secret: string): Promise<{ response: Response; inbound?: HostedInbound }> {\n const authenticated = await authenticateHubEventRequest({ request, secret, maxBodyBytes: 64 * 1024 })\n if (!authenticated.ok) return { response: authenticated.response }\n const { runId, source, providerEvent } = authenticated.delivery\n const ack = new Response(null, { status: 204 })\n if (source.kind !== 'connection' || source.event !== IMESSAGE_EVENT || providerEvent.connectionId !== source.id) return { response: ack }\n return { response: ack, inbound: { runId, connectionId: source.id, event: providerEvent } }\n },\n\n /**\n * Answer one inbound message and send the reply through Hub. Returns\n * 'pending' while the turn still runs; call again with the same inbound.\n * On `lastAttempt`, a failure is answered with an apology instead of thrown.\n */\n async respond(inbound: HostedInbound, options: { lastAttempt?: boolean } = {}): Promise<'replied' | 'ignored' | 'pending'> {\n const normalized = normalizeConversationEvent(inbound.event)\n if (!normalized.ok) return 'ignored'\n const event = normalized.event\n const userId = normalizeAddress(event.sender.id ?? '')\n if (!userId || !event.text?.trim() || event.isGroup || event.historyOnly) return 'ignored'\n const turnId = `t-${(await sha256(inbound.runId)).slice(0, 40)}`\n // Only the configured owner toggles debug; anyone else's DEBUG ON is an ordinary message.\n const isOwner = Boolean(config.owner) && normalizeAddress(config.owner!) === userId\n const debugKey = `debug:${(await sha256(userId)).slice(0, 32)}`\n const toggle = isOwner ? debugCommand(event.text) : null\n const trace: TurnTrace | undefined = isOwner && toggle === null && await store.get(debugKey) ? {} : undefined\n let text: string\n try {\n if (toggle !== null) {\n await store.put(debugKey, toggle ? '1' : '')\n text = toggle ? NOTICE.debugOn : NOTICE.debugOff\n } else {\n const result = await ask({ userId, channel: 'imessage', text: event.text, turnId },\n { deadline: Date.now() + wallCapMs + 60_000, trace })\n if (result.state === 'pending') return 'pending'\n if (result.state === 'declined' && !result.reply) return 'ignored'\n text = result.state === 'answered' ? result.text : result.reply!\n if (trace && result.state === 'answered') {\n const totalMs = event.occurredAt !== null && event.occurredAt !== undefined ? Date.now() - event.occurredAt : undefined\n text = `${text.slice(0, 1300)}\\n\\n${debugFooter(config.profile.name ?? 'agent', turnId, trace, totalMs)}`\n }\n }\n } catch (error) {\n // A failed turn fails the same way on every retry; anything else may clear.\n if (!(error instanceof HostedAgentError) && !options.lastAttempt) throw error\n console.error(`[hosted-agent] turn=${turnId} ${describe(error)}`)\n text = NOTICE.unavailable\n }\n const plan = buildMessagingReply(inbound.event, text.slice(0, 1500), `reply-${turnId}`)\n if (!plan.ok) return 'ignored'\n // Hub replays the first result for a retry with this key and input.\n await hub.tools.invoke(plan.reply.action, plan.reply.input, { connectionId: inbound.connectionId, idempotencyKey: plan.reply.idempotencyKey })\n return 'replied'\n },\n\n /**\n * ph0ny's call hook. `admit` returns a call token bound to the caller's\n * number from the carrier's signed call record, never from the model.\n */\n async voiceHook(request: Request): Promise<Response> {\n const secret = config.voiceSecret\n if (!secret || !await equal(request.headers.get('authorization'), `Bearer ${secret}`)) return json({ error: 'unauthorized' }, 401)\n const body = await request.json().catch(() => null) as { event?: string; phone?: string } | null\n if (body?.event !== 'admit') return json({ ok: true })\n const phone = normalizeAddress(body.phone ?? '')\n if (!phone?.startsWith('+')) return json({ admit: false, say: 'Sorry, I can only take calls from a visible number.' })\n if (await store.get(`stop:${(await sha256(phone)).slice(0, 32)}`)) return json({ admit: false, say: 'You unsubscribed from this line.' })\n // ph0ny accepts only an opaque token matching ^[A-Za-z0-9_-]{16,128}$,\n // so the caller's number stays here and the token is its random key.\n const callToken = randomToken()\n await store.put(`vcall:${callToken}`, JSON.stringify({ phone, exp: Date.now() + CALL_TTL_MS }), { expirationTtl: CALL_TTL_MS / 1000 })\n return json({ admit: true, callToken })\n },\n\n /**\n * ph0ny's `ask_workspace` webhook tool, configured with `forwardCallToken`.\n * A slow turn returns a ticket; the voice agent asks again with it.\n */\n async voiceAsk(request: Request): Promise<Response> {\n const secret = config.voiceSecret\n if (!secret || !await equal(request.headers.get('authorization'), `Bearer ${secret}`)) return json({ error: 'unauthorized' }, 401)\n const token = request.headers.get('x-voice-call-token') ?? ''\n const admitted = /^[A-Za-z0-9_-]{16,128}$/.test(token) ? await store.get(`vcall:${token}`) : null\n if (!admitted) return json({ status: 'error', error: 'call_not_admitted' }, 403)\n const call = JSON.parse(admitted) as { phone: string; exp: number }\n if (call.exp < Date.now()) return json({ status: 'error', error: 'call_expired' }, 403)\n const body = await request.json().catch(() => null) as { utterance?: string; ticket?: string } | null\n // A ticket carries its own turn id and question, so a retry after a\n // slow first step still admits and settles exactly one turn.\n const [turnId, asked] = body?.ticket ? body.ticket.split('.') : [`v-${crypto.randomUUID()}`, b64(body?.utterance ?? '')]\n if (!turnId || !asked) return json({ status: 'error', error: 'missing_utterance' }, 400)\n try {\n // ph0ny drops the tool call at its own timeout, and resuming an idle\n // box can take longer than the budget. Answer 'pending' at the\n // budget regardless; every step is idempotent by turn id, so the\n // ticket call continues where this one stopped.\n const budgetMs = config.voiceBudgetMs ?? 8000\n const result = await Promise.race([\n ask({ userId: call.phone, channel: 'voice', text: unb64(asked), turnId }, { deadline: Date.now() + budgetMs }),\n sleep(budgetMs).then((): AskResult => ({ state: 'pending' })),\n ])\n if (result.state === 'pending') return json({ status: 'pending', ticket: `${turnId}.${asked}` })\n if (result.state === 'declined') return json({ status: 'complete', answer: result.reply ?? 'I cannot answer that right now.' })\n return json({ status: 'complete', answer: result.text })\n } catch (error) {\n console.error(`[hosted-agent] voice turn=${turnId} ${describe(error)}`)\n return json({ status: 'error', error: 'agent_unavailable' }, 502)\n }\n },\n }\n}\n\nexport type HostedAgent = ReturnType<typeof createHostedAgent>\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/hosted-agent/index.ts","../../src/hosted-agent/engine.ts"],"sourcesContent":["import { authenticateHubEventRequest, HubClient, type HubProviderEvent } from '@tangle-network/hub-sdk'\nimport { buildMessagingReply, normalizeConversationEvent } from '@tangle-network/agent-integrations/conversation-events'\nimport type { AgentProfile, BackendConfig, SandboxInstance } from '@tangle-network/sandbox'\nimport { Sandbox, SandboxError } from '@tangle-network/sandbox/core'\nimport { runHostedTurn, TurnPending } from './engine'\n\nexport * from './engine'\n\n/**\n * A hosted agent: a person texts or calls a line, and the agent answers from\n * that person's own isolated sandbox. The developer's Tangle API key pays for\n * every box, model turn and reply.\n *\n * Each person gets one fresh isolated box built from the published profile,\n * never a copy of the developer's box. Later messages resume the same box and\n * the same conversation, so text and voice share one memory. A new profile\n * starts a new session, and a deleted box or a new API key starts a new box;\n * either way the person's recent conversation carries over from the store.\n *\n * Every step is idempotent by the message's turn id: a retried delivery\n * settles the same sandbox turn and replays the same Hub send. So the caller\n * may retry any failure, and needs no dedupe table.\n */\n\n/** Durable key-value storage. A Cloudflare KV namespace satisfies it. */\nexport interface HostedAgentStore {\n get(key: string): Promise<string | null>\n put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>\n}\n\nexport type HostedChannel = 'imessage' | 'voice'\n\nexport interface HostedMessage {\n /** The sender's address as the channel delivered it: E.164 for a phone. */\n userId: string\n channel: HostedChannel\n text: string\n /** Stable per logical message. Reusing it settles the same turn. */\n turnId: string\n}\n\n/** What the allowance hook decides for one new message. */\nexport type Allowance = 'answer' | 'ignore' | { reply: string }\n\nexport interface HostedAgentConfig {\n /** The developer's Tangle API key (Sandbox and Hub). */\n apiKey: string\n /**\n * The persona every person's box runs. A profile without `model.default`\n * runs {@link DEFAULT_HOSTED_MODEL}, and a profile without `tools` runs with\n * {@link CONVERSATION_TOOLS_OFF} turned off. Set `tools` to choose your own,\n * for example on a harness that cannot turn those tools off.\n */\n profile: AgentProfile\n /** Backend harness type, such as `opencode`; the runtime default when omitted. */\n harness?: string\n store: HostedAgentStore\n /** Answers per person per UTC day before `allow` must decide. Default 20. */\n freeTurnsPerDay?: number\n /**\n * Decide a new message after the free allowance is used, for example reply\n * with a checkout link. Without it, a person past the allowance is told to\n * come back tomorrow. Retries of an admitted message never reach this hook.\n */\n allow?: (message: HostedMessage, usedToday: number) => Allowance | Promise<Allowance>\n /**\n * The owner's own address (E.164 phone). Only this sender can text DEBUG ON\n * or DEBUG OFF; while on, each reply to them ends with one ⚙ line naming the\n * harness, model, box, time, tokens, cost and tools of that turn.\n */\n owner?: string\n /** Shared secret for the ph0ny call hook and `ask_workspace` webhook tool. */\n voiceSecret?: string\n /** How long one `ask_workspace` call may wait before it returns a ticket. Default 8 s. */\n voiceBudgetMs?: number\n box?: Partial<BoxPolicy>\n turnWallCapMs?: number\n sandboxUrl?: string\n hubUrl?: string\n}\n\nexport interface BoxPolicy {\n cpuCores: number\n memoryMB: number\n diskGB: number\n idleTimeoutSeconds: number\n maxLifetimeSeconds: number\n deleteAfterStoppedSeconds: number\n /** Egress allow-list. The default reaches the model router only. */\n allowDomains: string[]\n}\n\nexport const DEFAULT_BOX_POLICY: BoxPolicy = {\n cpuCores: 1, memoryMB: 2048, diskGB: 10,\n idleTimeoutSeconds: 600, maxLifetimeSeconds: 86_400, deleteAfterStoppedSeconds: 7 * 86_400,\n allowDomains: ['router.tangle.tools'],\n}\n\n/**\n * Harness tools a texting or calling assistant does not use. Their\n * descriptions present every turn as coding work: a shell, file search,\n * sub-agents, to-do lists, skills and web fetch. File read, write and edit\n * stay, so a persona can keep notes such as `memory.md`.\n */\nexport const CONVERSATION_TOOLS_OFF = ['bash', 'glob', 'grep', 'task', 'todowrite', 'webfetch', 'skill'] as const\n\n/**\n * The model for a profile without `model.default`. It gave the most useful\n * on-topic replies among four Router models on the same five texts and calls\n * (2026-09-23), within the latency of the others.\n */\nexport const DEFAULT_HOSTED_MODEL = 'openai/gpt-5.6-luna'\n\n/** The profile a person's box runs: the developer's profile over the conversation defaults. */\nfunction conversationProfile(profile: AgentProfile): AgentProfile {\n return {\n ...profile,\n model: { ...profile.model, default: profile.model?.default ?? DEFAULT_HOSTED_MODEL },\n // A profile that sets `tools` owns its tool set.\n ...(profile.tools ? {} : {\n tools: Object.fromEntries(CONVERSATION_TOOLS_OFF.map(tool => [tool, false])),\n // The sandbox's preview policy grants the shell unless its permission\n // is denied, so turning the tool off alone leaves the shell in place.\n permissions: { bash: 'deny' as const, ...profile.permissions },\n }),\n }\n}\n\n/** What one turn reports about itself, for the owner's debug line. A figure\n * the run did not report stays undefined and is left out of the line. */\nexport interface TurnTrace {\n /** Box before the turn: `warm`, `resumed` or `new`. */\n box?: string\n harness?: string\n model?: string\n /** Sandbox clock: the run's own start to finish. */\n runMs?: number\n inputTokens?: number\n outputTokens?: number\n reasoningTokens?: number\n costUsd?: number\n tools?: number\n}\n\nexport type AskResult =\n | { state: 'answered'; text: string }\n | { state: 'declined'; reply?: string }\n | { state: 'pending' }\n\n/** A Hub delivery that passed authentication, in a form a queue can carry. */\nexport interface HostedInbound {\n runId: string\n connectionId: string\n event: HubProviderEvent\n}\n\nexport const NOTICE = {\n debugOn: 'Debug on. Each reply now ends with a ⚙ line: agent, harness, model, box, time, tokens, cost, tools and turn. Text DEBUG OFF to stop.',\n debugOff: 'Debug off. Replies no longer carry the ⚙ line.',\n stopped: 'You are unsubscribed. Text START to talk again.',\n started: 'Welcome back. Text me anytime. Reply STOP to stop.',\n limit: 'You have used today\\'s free messages. Talk tomorrow!',\n unavailable: 'Sorry, I could not answer that just now. Please try again in a minute.',\n} as const\n\nconst IMESSAGE_EVENT = 'inkbox.imessage.received'\nconst IMESSAGE_REPLY = 'inkbox.imessage.reply'\nconst STOP = /^(stop|stopall|unsubscribe|cancel|end|quit)$/i\nconst START = /^(start|unstop|resume)$/i\nconst TWO_DAYS = 2 * 86_400\nconst RESUMABLE = new Set(['stopped', 'expired'])\nconst GONE = new Set(['failed', 'deleted'])\n/** How much of a person's recent conversation a new session inherits. */\nconst CARRY_CHARS = 6000\nconst CARRY_HEAD = '[Your conversation with this person so far. Your instructions may have changed since; follow the current ones.]'\nconst CARRY_NOW = '[Their new message:]'\n\n/** A person's recent turns, kept outside the box so a new session or box can continue them. */\ninterface Conversation {\n /** The box and session the turns last ran in. */\n at: string\n /** Oldest first: turn id, what they said, what the agent answered. */\n turns: Array<[string, string, string]>\n}\n\nconst encode = (text: string) => new TextEncoder().encode(text)\nconst hex = (bytes: ArrayBuffer) => Array.from(new Uint8Array(bytes), byte => byte.toString(16).padStart(2, '0')).join('')\nconst sha256 = async (value: string) => hex(await crypto.subtle.digest('SHA-256', encode(value)))\n/** Compares digests, so the time taken reveals nothing about the secret. */\nasync function equal(given: string | null | undefined, expected: string): Promise<boolean> {\n if (!given) return false\n const [a, b] = await Promise.all([sha256(given), sha256(expected)])\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\nconst CALL_TTL_MS = 2 * 3_600_000\n/** 43 url-safe characters from 32 random bytes. */\nconst randomToken = () => btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\nconst b64 = (text: string) => btoa(String.fromCharCode(...encode(text))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\nconst unb64 = (text: string) => new TextDecoder().decode(Uint8Array.from(atob(text.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)))\nconst sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))\nconst json = (body: unknown, status = 200) => Response.json(body, { status })\nconst describe = (error: unknown) => (error instanceof Error ? `${error.name}: ${error.message}` : String(error))\n .replace(/sk-tan-[\\w-]+|Bearer\\s+\\S+/g, '<redacted>').slice(0, 400)\n\n/** One address per person: E.164 for anything phone-shaped, else lowercase. */\nexport function normalizeAddress(value: string): string | null {\n const trimmed = value.trim()\n if (trimmed.includes('@')) return trimmed.toLowerCase()\n const digits = trimmed.replace(/[^\\d]/g, '')\n if (digits.length < 8 || digits.length > 15) return null\n return `+${digits.length === 10 && !trimmed.startsWith('+') ? `1${digits}` : digits}`\n}\n\n/** `true` for DEBUG ON, `false` for DEBUG OFF, `null` for any other text. */\nexport function debugCommand(text: string): boolean | null {\n const match = /^debug\\s+(on|off)$/i.exec(text.trim())\n return match ? match[1]!.toLowerCase() === 'on' : null\n}\n\nconst num = (value: unknown) => typeof value === 'number' && Number.isFinite(value) ? value : undefined\nconst seconds = (ms: number) => ms < 10_000 ? `${(Math.max(0, ms) / 1000).toFixed(1)}s` : `${Math.round(ms / 1000)}s`\nconst count = (n: number) => n < 1000 ? String(n) : n < 10_000 ? `${(n / 1000).toFixed(1)}k` : `${Math.round(n / 1000)}k`\n\n/** The owner's ⚙ line. `totalMs` runs from the provider's receipt of the text to now. */\nexport function debugFooter(agent: string, turnId: string, trace: TurnTrace, totalMs?: number): string {\n const parts = [agent]\n if (trace.harness) parts.push(trace.harness)\n if (trace.model) parts.push(trace.model.split('/').at(-1)!)\n if (trace.box) parts.push(trace.box)\n if (totalMs !== undefined) parts.push(`${seconds(totalMs)}${trace.runMs !== undefined ? ` (run ${seconds(trace.runMs)})` : ''}`)\n else if (trace.runMs !== undefined) parts.push(`run ${seconds(trace.runMs)}`)\n if (trace.inputTokens !== undefined && trace.outputTokens !== undefined) {\n parts.push(`${count(trace.inputTokens)}→${count(trace.outputTokens)} tok${trace.reasoningTokens ? ` (${count(trace.reasoningTokens)} thinking)` : ''}`)\n }\n // Zero on a turn that used tokens means the harness had no price for the model.\n if (trace.costUsd !== undefined && (trace.costUsd > 0 || !trace.outputTokens)) {\n parts.push(trace.costUsd >= 0.01 ? `$${trace.costUsd.toFixed(3)}` : `$${trace.costUsd.toPrecision(2)}`)\n }\n if (trace.tools !== undefined) parts.push(`${trace.tools} tool${trace.tools === 1 ? '' : 's'}`)\n parts.push(turnId.slice(0, 8))\n return `⚙ ${parts.join(' · ')}`\n}\n\n/** Model, tokens, cost, tools and run time from a finished turn, as far as it reports them. */\nfunction noteResult(trace: TurnTrace, result: Record<string, unknown>, usage?: { inputTokens?: number; outputTokens?: number }) {\n // The SDK's typed usage carries counts only; the harness's own record also carries cost.\n const reported = (result.tokenUsage ?? {}) as Record<string, unknown>\n const timing = (result.timing ?? {}) as Record<string, unknown>\n const started = num(timing.startedAt), completed = num(timing.completedAt)\n if (started !== undefined && completed !== undefined) trace.runMs = completed - started\n trace.inputTokens = num(usage?.inputTokens) ?? num(reported.inputTokens)\n trace.outputTokens = num(usage?.outputTokens) ?? num(reported.outputTokens)\n trace.reasoningTokens = num(reported.reasoningTokens)\n if (result.usdKnown !== false) trace.costUsd = num(reported.cost)\n if (Array.isArray(result.toolInvocations)) trace.tools = result.toolInvocations.length\n}\n\nexport class HostedAgentError extends Error {\n constructor(readonly code: string, message: string) { super(message); this.name = 'HostedAgentError' }\n}\n\nexport function createHostedAgent(config: HostedAgentConfig) {\n const store = config.store\n const policy = { ...DEFAULT_BOX_POLICY, ...config.box }\n const wallCapMs = config.turnWallCapMs ?? 120_000\n const sandbox = new Sandbox({ apiKey: config.apiKey, baseUrl: config.sandboxUrl ?? 'https://sandbox.tangle.tools', timeoutMs: 20_000 })\n const hub = new HubClient({ baseUrl: config.hubUrl ?? 'https://id.tangle.tools', apiKey: config.apiKey })\n const profile = conversationProfile(config.profile)\n const backend: BackendConfig = { ...(config.harness ? { type: config.harness as BackendConfig['type'] } : {}), profile }\n const profileTag = sha256(JSON.stringify(profile)).then(hash => hash.slice(0, 8))\n\n /** Resume a stopped box. False when an earlier API key made it: the platform\n * resumes a box only under the key lineage that created it. */\n async function resumed(box: SandboxInstance): Promise<boolean> {\n try {\n await box.resume()\n return true\n } catch (error) {\n if (error instanceof SandboxError && error.code === 'SANDBOX_ATTRIBUTION_MISMATCH') return false\n throw error\n }\n }\n\n /** The person's running box: created on the first message, resumed later,\n * replaced when the platform deleted it. Null when the deadline passed. */\n async function ensureBox(user: string, deadline: number, trace?: TurnTrace): Promise<SandboxInstance | null> {\n const key = `box:${user}`\n const known = await store.get(key)\n let box = known ? await sandbox.get(known) : null\n if (trace) trace.box = !box || GONE.has(box.status) ? 'new' : RESUMABLE.has(box.status) ? 'resumed' : 'warm'\n if (box && RESUMABLE.has(box.status) && !await resumed(box)) {\n box = null\n if (trace) trace.box = 'new'\n }\n if (!box || GONE.has(box.status)) {\n // The key names the box it replaces, so concurrent first messages and\n // retries converge on one allocation instead of each creating a box.\n box = await sandbox.createIsolated({\n name: `hosted-${user.slice(0, 24)}`, idempotencyKey: known ? `hosted-${user}-after-${known}` : `hosted-${user}`,\n resources: { cpuCores: policy.cpuCores, memoryMB: policy.memoryMB, diskGB: policy.diskGB },\n backend, secrets: [], sshEnabled: false,\n egressPolicy: { mode: 'strict', allowDomains: policy.allowDomains, includeImplicitDomains: false },\n idleTimeoutSeconds: policy.idleTimeoutSeconds, maxLifetimeSeconds: policy.maxLifetimeSeconds,\n deleteAfterStoppedSeconds: policy.deleteAfterStoppedSeconds, metadata: { hostedUser: user },\n })\n await store.put(key, box.id)\n }\n if (RESUMABLE.has(box.status) && !await resumed(box)) throw new HostedAgentError('box_foreign', 'A new box was made under another API key.')\n if (box.status === 'running') return box\n const timeoutMs = deadline - Date.now()\n if (timeoutMs < 1000) return null\n try {\n return await sandbox.waitForRunning(box.id, { timeoutMs })\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') return null\n throw error\n }\n }\n\n /** Admit a new message once: STOP/START, the free allowance, then `allow`. */\n async function admit(message: HostedMessage, user: string): Promise<Allowance> {\n const stopKey = `stop:${user}`\n if (message.channel !== 'voice' && STOP.test(message.text.trim())) { await store.put(stopKey, '1'); return { reply: NOTICE.stopped } }\n if (message.channel !== 'voice' && START.test(message.text.trim())) { await store.put(stopKey, ''); return { reply: NOTICE.started } }\n if (await store.get(stopKey)) return 'ignore'\n const dayKey = `turns:${user}:${new Date().toISOString().slice(0, 10)}`\n // KV has no atomic increment, so concurrent messages may overshoot the\n // allowance by a few; the sponsor key's budget is the hard ceiling.\n const used = Number(await store.get(dayKey) ?? 0)\n const decision = used < (config.freeTurnsPerDay ?? 20) ? 'answer'\n : config.allow ? await config.allow(message, used) : { reply: NOTICE.limit }\n if (decision === 'answer') await store.put(dayKey, String(used + 1), { expirationTtl: TWO_DAYS })\n return decision\n }\n\n /** Run one message in the person's box until it answers or `deadline`. */\n async function ask(message: HostedMessage, options: { deadline: number; trace?: TurnTrace }): Promise<AskResult> {\n const text = message.text.trim()\n if (!text || text.length > 8000 || !/^[\\w-]{1,120}$/.test(message.turnId)) return { state: 'declined' }\n const user = (await sha256(message.userId)).slice(0, 32)\n // Scoped to the person: a voice ticket is model-supplied, so a turn id\n // admitted for one person must not skip another person's allowance.\n const admittedKey = `turn:${user}:${message.turnId}`\n // A session binds its backend when created, and the runtime would give a\n // bare session its generic default assistant. A new profile starts a new\n // session, so the persona always matches the one in force.\n const sessionId = `hosted-${user}-${await profileTag}`\n const trace = options.trace, traceKey = `trace:${user}:${message.turnId}`\n const conversationKey = `convo:${user}`\n let declined: AskResult | undefined\n let conversation: Conversation | undefined, at = ''\n const outcome = await runHostedTurn({ turnId: message.turnId, text }, {\n async admit() {\n if (await store.get(admittedKey)) return null\n const decision = await admit(message, user)\n if (decision === 'answer') {\n await store.put(admittedKey, '1', { expirationTtl: TWO_DAYS })\n return null\n }\n declined = decision === 'ignore' ? { state: 'declined' } : { state: 'declined', reply: decision.reply }\n return { ok: false, reason: 'refused', detail: 'declined' }\n },\n async box() {\n const box = await ensureBox(user, options.deadline, trace)\n // A retried delivery finds the box it woke already warm, so the first\n // attempt's view of the box is kept for the turn.\n if (trace) {\n const first = await store.get(traceKey)\n if (first) trace.box = first\n else if (trace.box) await store.put(traceKey, trace.box, { expirationTtl: TWO_DAYS })\n }\n if (!box) throw new TurnPending('box_starting', true)\n return box\n },\n sessionId,\n backend: async () => backend,\n async prompt(box) {\n const asked = message.channel === 'voice' ? `[Phone call. Answer in one to three short spoken sentences.]\\n${text}` : text\n // The first turn in a new session or box inherits the person's recent\n // turns. A retry before that turn completes carries them again, so a\n // failed first attempt loses nothing.\n at = `${box.id}/${sessionId}`\n const stored = await store.get(conversationKey)\n conversation = stored ? JSON.parse(stored) as Conversation : { at, turns: [] }\n const carried = conversation.at === at ? '' : conversation.turns.map(([, them, you]) => `Them: ${them}\\nYou: ${you}`).join('\\n')\n return carried ? `${CARRY_HEAD}\\n${carried}\\n${CARRY_NOW}\\n${asked}` : asked\n },\n async answered(answer) {\n // Concurrent turns may drop one another's entry; the box keeps the full record.\n const turns = (conversation?.turns ?? []).filter(([id]) => id !== message.turnId)\n turns.push([message.turnId, message.channel === 'voice' ? `(on a call) ${text}` : text, answer])\n while (turns.length > 1 && JSON.stringify(turns).length > CARRY_CHARS) turns.shift()\n await store.put(conversationKey, JSON.stringify({ at, turns } satisfies Conversation))\n },\n classify: error => error instanceof HostedAgentError ? { ok: false, reason: 'unavailable', detail: error.code } : undefined,\n observe: {\n readCreatedSession: Boolean(trace),\n session(info) {\n if (!trace) return\n trace.harness = info.harness ?? config.harness\n trace.model = info.model\n },\n drive(result) {\n if (trace && result.state === 'completed') noteResult(trace, result.result, result.usage)\n },\n failure(step, failure) {\n console.error(`[hosted-agent] turn=${message.turnId} step=${step} code=${failure.code} message=${JSON.stringify(failure.message)}`)\n },\n },\n }, { wallCapMs, timeoutMs: 8000, until: options.deadline })\n if (declined) return declined\n if (outcome.ok === true) return { state: 'answered', text: outcome.text }\n // A thrown failure is rethrown, so the caller's retry policy decides it.\n if (outcome.ok === 'pending') {\n if (outcome.cause !== undefined) throw outcome.cause\n return { state: 'pending' }\n }\n const code = outcome.detail ?? outcome.reason\n throw new HostedAgentError(code === 'agent_failed' ? 'turn_failed' : code, `The turn ended without an answer (${code}).`)\n }\n\n return {\n ask,\n\n /**\n * One-time setup: route an Inkbox iMessage identity's messages, connected\n * to Hub under the developer's account, to `callbackUrl`, and allow the\n * one reply action on that connection. Safe to repeat.\n */\n async connect(input: { connectionId: string; identityId: string; callbackUrl: string; secret: string }) {\n const webhook = await hub.connections.inboundWebhook.get(input.connectionId)\n if (!webhook.configured) await hub.connections.inboundWebhook.create(input.connectionId, { identityId: input.identityId, imessage: true })\n await hub.permissions.set({ connectionId: input.connectionId, actionPath: IMESSAGE_REPLY, decision: 'allow' })\n const { subscription } = await hub.eventSubscriptions.create({\n clientReference: `hosted-agent:${input.connectionId}`, label: 'Hosted agent messages',\n source: { type: 'connection', connectionId: input.connectionId }, event: IMESSAGE_EVENT,\n callback: { url: input.callbackUrl, secret: input.secret },\n })\n return subscription\n },\n\n /** The Hub callback. Authenticates within Hub's 10 s budget and runs nothing. */\n async receive(request: Request, secret: string): Promise<{ response: Response; inbound?: HostedInbound }> {\n const authenticated = await authenticateHubEventRequest({ request, secret, maxBodyBytes: 64 * 1024 })\n if (!authenticated.ok) return { response: authenticated.response }\n const { runId, source, providerEvent } = authenticated.delivery\n const ack = new Response(null, { status: 204 })\n if (source.kind !== 'connection' || source.event !== IMESSAGE_EVENT || providerEvent.connectionId !== source.id) return { response: ack }\n return { response: ack, inbound: { runId, connectionId: source.id, event: providerEvent } }\n },\n\n /**\n * Answer one inbound message and send the reply through Hub. Returns\n * 'pending' while the turn still runs; call again with the same inbound.\n * On `lastAttempt`, a failure is answered with an apology instead of thrown.\n */\n async respond(inbound: HostedInbound, options: { lastAttempt?: boolean } = {}): Promise<'replied' | 'ignored' | 'pending'> {\n const normalized = normalizeConversationEvent(inbound.event)\n if (!normalized.ok) return 'ignored'\n const event = normalized.event\n const userId = normalizeAddress(event.sender.id ?? '')\n if (!userId || !event.text?.trim() || event.isGroup || event.historyOnly) return 'ignored'\n const turnId = `t-${(await sha256(inbound.runId)).slice(0, 40)}`\n // Only the configured owner toggles debug; anyone else's DEBUG ON is an ordinary message.\n const isOwner = Boolean(config.owner) && normalizeAddress(config.owner!) === userId\n const debugKey = `debug:${(await sha256(userId)).slice(0, 32)}`\n const toggle = isOwner ? debugCommand(event.text) : null\n const trace: TurnTrace | undefined = isOwner && toggle === null && await store.get(debugKey) ? {} : undefined\n let text: string\n try {\n if (toggle !== null) {\n await store.put(debugKey, toggle ? '1' : '')\n text = toggle ? NOTICE.debugOn : NOTICE.debugOff\n } else {\n const result = await ask({ userId, channel: 'imessage', text: event.text, turnId },\n { deadline: Date.now() + wallCapMs + 60_000, trace })\n if (result.state === 'pending') return 'pending'\n if (result.state === 'declined' && !result.reply) return 'ignored'\n text = result.state === 'answered' ? result.text : result.reply!\n if (trace && result.state === 'answered') {\n const totalMs = event.occurredAt !== null && event.occurredAt !== undefined ? Date.now() - event.occurredAt : undefined\n text = `${text.slice(0, 1300)}\\n\\n${debugFooter(config.profile.name ?? 'agent', turnId, trace, totalMs)}`\n }\n }\n } catch (error) {\n // A failed turn fails the same way on every retry; anything else may clear.\n if (!(error instanceof HostedAgentError) && !options.lastAttempt) throw error\n console.error(`[hosted-agent] turn=${turnId} ${describe(error)}`)\n text = NOTICE.unavailable\n }\n const plan = buildMessagingReply(inbound.event, text.slice(0, 1500), `reply-${turnId}`)\n if (!plan.ok) return 'ignored'\n // Hub replays the first result for a retry with this key and input.\n await hub.tools.invoke(plan.reply.action, plan.reply.input, { connectionId: inbound.connectionId, idempotencyKey: plan.reply.idempotencyKey })\n return 'replied'\n },\n\n /**\n * ph0ny's call hook. `admit` returns a call token bound to the caller's\n * number from the carrier's signed call record, never from the model.\n */\n async voiceHook(request: Request): Promise<Response> {\n const secret = config.voiceSecret\n if (!secret || !await equal(request.headers.get('authorization'), `Bearer ${secret}`)) return json({ error: 'unauthorized' }, 401)\n const body = await request.json().catch(() => null) as { event?: string; phone?: string } | null\n if (body?.event !== 'admit') return json({ ok: true })\n const phone = normalizeAddress(body.phone ?? '')\n if (!phone?.startsWith('+')) return json({ admit: false, say: 'Sorry, I can only take calls from a visible number.' })\n if (await store.get(`stop:${(await sha256(phone)).slice(0, 32)}`)) return json({ admit: false, say: 'You unsubscribed from this line.' })\n // ph0ny accepts only an opaque token matching ^[A-Za-z0-9_-]{16,128}$,\n // so the caller's number stays here and the token is its random key.\n const callToken = randomToken()\n await store.put(`vcall:${callToken}`, JSON.stringify({ phone, exp: Date.now() + CALL_TTL_MS }), { expirationTtl: CALL_TTL_MS / 1000 })\n return json({ admit: true, callToken })\n },\n\n /**\n * ph0ny's `ask_workspace` webhook tool, configured with `forwardCallToken`.\n * A slow turn returns a ticket; the voice agent asks again with it.\n */\n async voiceAsk(request: Request): Promise<Response> {\n const secret = config.voiceSecret\n if (!secret || !await equal(request.headers.get('authorization'), `Bearer ${secret}`)) return json({ error: 'unauthorized' }, 401)\n const token = request.headers.get('x-voice-call-token') ?? ''\n const admitted = /^[A-Za-z0-9_-]{16,128}$/.test(token) ? await store.get(`vcall:${token}`) : null\n if (!admitted) return json({ status: 'error', error: 'call_not_admitted' }, 403)\n const call = JSON.parse(admitted) as { phone: string; exp: number }\n if (call.exp < Date.now()) return json({ status: 'error', error: 'call_expired' }, 403)\n const body = await request.json().catch(() => null) as { utterance?: string; ticket?: string } | null\n // A ticket carries its own turn id and question, so a retry after a\n // slow first step still admits and settles exactly one turn.\n const [turnId, asked] = body?.ticket ? body.ticket.split('.') : [`v-${crypto.randomUUID()}`, b64(body?.utterance ?? '')]\n if (!turnId || !asked) return json({ status: 'error', error: 'missing_utterance' }, 400)\n try {\n // ph0ny drops the tool call at its own timeout, and resuming an idle\n // box can take longer than the budget. Answer 'pending' at the\n // budget regardless; every step is idempotent by turn id, so the\n // ticket call continues where this one stopped.\n const budgetMs = config.voiceBudgetMs ?? 8000\n const result = await Promise.race([\n ask({ userId: call.phone, channel: 'voice', text: unb64(asked), turnId }, { deadline: Date.now() + budgetMs }),\n sleep(budgetMs).then((): AskResult => ({ state: 'pending' })),\n ])\n if (result.state === 'pending') return json({ status: 'pending', ticket: `${turnId}.${asked}` })\n if (result.state === 'declined') return json({ status: 'complete', answer: result.reply ?? 'I cannot answer that right now.' })\n return json({ status: 'complete', answer: result.text })\n } catch (error) {\n console.error(`[hosted-agent] voice turn=${turnId} ${describe(error)}`)\n return json({ status: 'error', error: 'agent_unavailable' }, 502)\n }\n },\n }\n}\n\nexport type HostedAgent = ReturnType<typeof createHostedAgent>\n","import type { BackendConfig, CreateSessionOptions } from '@tangle-network/sandbox'\nimport type { ConversationTurnOptions, ConversationTurnResult, PromptInputPart } from '@tangle-network/sandbox/core'\n\n/**\n * The one turn engine for a hosted agent, whatever hosts it. This kit's\n * `createHostedAgent` runs on it with a key-value store, and Agent Builder\n * runs on it with its D1 enrollments, per-user keys, spaces and product\n * ingest. A host supplies those as ports; the engine owns the order of the\n * steps and what each outcome and failure means.\n *\n * One call is one non-blocking pass for one message:\n *\n * validate -> admit once -> the person's box -> authorize -> session -> drive -> classify\n *\n * Every step is idempotent by the turn id: the sandbox's admission receipt\n * settles a repeated drive on the same turn, so a host may repeat a pass for\n * the same message any number of times and the agent runs it once.\n */\n\nexport type TurnRefusal = 'not_enrolled' | 'execution_not_ready' | 'refused' | 'unavailable'\nexport type TurnReply = { ok: true; text: string } | { ok: false; reason: TurnRefusal; detail?: string }\nexport type TurnStep = 'ensure' | 'drive' | 'turn'\n/**\n * Where an unfinished turn stands. `code` is the same on every repeat of the\n * same cause, so a host can tell a stuck turn from a slow one. A transient\n * cause (a lost response, a timeout, a busy platform, a box still starting)\n * is expected to clear by itself; any other repeats until it is fixed.\n */\nexport interface PendingDetail { step: TurnStep; code: string; transient: boolean }\n/** `cause` is what a port or the Sandbox threw, when a throw left the turn pending. */\nexport type TurnOutcome = TurnReply | { ok: 'pending'; detail: PendingDetail; cause?: unknown }\n\nexport const MAX_MESSAGE_CHARS = 8000\n/** Identical non-transient failures in a row after which a host fails a turn. */\nexport const MAX_REPEATED_FAILURES = 3\n/** Refusal detail when the host's `authorize` check fails. */\nexport const AUTHORITY_ENDED = 'authority_ended'\n\n/** The part of a Sandbox box one turn uses. `SandboxInstance` satisfies it. */\nexport interface TurnBox {\n readonly id: string\n driveConversationTurn(message: string | PromptInputPart[], options: ConversationTurnOptions): Promise<ConversationTurnResult>\n session(id: string): { status(): Promise<unknown> }\n createSession(options: CreateSessionOptions): Promise<unknown>\n}\n\n/** A port throws this to report a turn that is not ready yet, such as a box still starting. */\nexport class TurnPending extends Error {\n constructor(readonly code: string, readonly transient: boolean) {\n super(code)\n this.name = 'TurnPending'\n }\n}\n\n/** A thrown value as a stable code plus a secret-free message for a log line. */\nexport interface TurnFailure { name: string; code: string; status?: number; reason?: string; message: string }\n\n/** What a pass reports while it runs. Every hook is optional; none changes the outcome. */\nexport interface TurnObserver {\n /** Time one step of this pass took, whether it returned or threw. */\n span?(step: 'ensure' | 'session' | 'drive', ms: number): void\n /** The session the turn runs in, as the runtime reports it, or the backend it was created with. */\n session?(info: { harness?: string; model?: string; created: boolean }): void\n /**\n * Read a session this pass created once more, so `session` reports the\n * harness and model the runtime chose for it. It costs one request, so a\n * host asks for it only when it shows them (an owner's debug line, say).\n */\n readCreatedSession?: boolean\n /** Each drive result and the time it arrived. */\n drive?(result: ConversationTurnResult, at: number): void\n /** A failure that left the turn pending or refused. */\n failure?(step: TurnStep, failure: TurnFailure, boxId?: string): void | Promise<void>\n}\n\n/** Storage and policy a host supplies for one message. */\nexport interface HostedTurnPorts<Box extends TurnBox = TurnBox> {\n /**\n * Admit this turn once, before any compute. Return null to run it, or the\n * refusal. A turn admitted by an earlier pass must return null again, so a\n * retry never consumes allowance twice.\n */\n admit(): Promise<Extract<TurnReply, { ok: false }> | null>\n /** The person's running box: created, resumed or replaced as needed. Throw to report why not. */\n box(): Promise<Box>\n /** The host's own authority (a phone link, say), read after the box is ready and right before the drive. */\n authorize?(): Promise<boolean>\n /** The session this turn runs in. A session binds its backend when created, so a new persona needs a new id. */\n sessionId: string\n /** The backend a missing session is created with. Without it the runtime answers as its generic assistant. */\n backend(): Promise<BackendConfig>\n /** The message as the agent receives it: text, or text and images. */\n prompt(box: Box): Promise<string | PromptInputPart[]>\n /** Runs once the turn has a non-empty answer, before the pass returns it. */\n answered?(answer: string, box: Box): Promise<void>\n /** Map a thrown value to an outcome. Undefined leaves it to the engine's own classification. */\n classify?(error: unknown, step: 'ensure' | 'drive'): TurnOutcome | undefined\n observe?: TurnObserver\n}\n\nexport interface HostedTurnOptions {\n /** The run's wall-clock cap; the Sandbox cancels a turn that exceeds it. */\n wallCapMs: number\n /** How long one drive request may wait on a running turn. Default 8 s. */\n timeoutMs?: number\n /** False only reads a turn admitted earlier; it never admits or starts one. */\n allowDispatch?: boolean\n /** Keep driving a running turn until this time, instead of returning pending after one drive. */\n until?: number\n /** Wait between drives while `until` allows. Default 2 s. */\n pollMs?: number\n now?: () => number\n sleep?: (ms: number) => Promise<void>\n}\n\n/** Harness and model as the runtime reports them; the model it selected after normalization wins. */\nfunction sessionInfo(session: unknown): { harness?: string; model?: string } {\n const s = (typeof session === 'object' && session !== null ? session : {}) as\n { backend?: unknown; model?: unknown; effectiveBackend?: { model?: unknown } | null }\n const effective = s.effectiveBackend?.model\n const model = typeof effective === 'string' && effective ? effective : typeof s.model === 'string' && s.model ? s.model : undefined\n return { ...(typeof s.backend === 'string' && s.backend ? { harness: s.backend } : {}), ...(model ? { model } : {}) }\n}\n\nconst refusal = (reason: TurnRefusal, detail?: string): Extract<TurnReply, { ok: false }> => ({ ok: false, reason, ...(detail ? { detail } : {}) })\nconst RUNNING = new Set(['running', 'awaiting_input', 'not_admitted'])\nconst TRANSIENT_ERRORS = new Set(['NetworkError', 'TimeoutError', 'QuotaError', 'AbortError', 'EdgeNotReadyError'])\n/**\n * The 503 codes the Sandbox SDK itself classifies as refused before dispatch\n * and safe to retry. A 503 with any other code, such as\n * HUB_CREDENTIAL_REFRESH_FAILED, has not shown it will clear by itself.\n */\nconst TRANSIENT_503 = new Set(['MODEL_CREDENTIAL_SUPERSEDED', 'HUB_CREDENTIAL_REFRESH_UNAVAILABLE'])\n\n/** The code omits the message, which can carry ids or times that differ on every repeat of one cause. */\nexport function describeFailure(error: unknown): TurnFailure {\n const e = (typeof error === 'object' && error !== null ? error : {}) as Record<string, unknown>\n const name = typeof e.name === 'string' ? e.name : typeof error\n const status = typeof e.status === 'number' ? e.status : undefined\n const own = typeof e.code === 'string' ? e.code : typeof e.reason === 'string' ? e.reason : undefined\n const code = [name, own, status].filter(part => part !== undefined).join(':').replace(/[^\\w.:-]/g, '_').slice(0, 100)\n const message = String(typeof e.message === 'string' ? e.message : error)\n .replace(/sk-tan-[\\w-]+|eyJ[\\w-]+\\.[\\w-]+\\.[\\w-]*|Bearer\\s+\\S+/g, '<redacted>').slice(0, 400)\n return { name, code, ...(status !== undefined ? { status } : {}), ...(own !== undefined ? { reason: own } : {}), message }\n}\n\nexport function isTransientFailure(failure: Pick<TurnFailure, 'name' | 'status' | 'reason'>): boolean {\n return TRANSIENT_ERRORS.has(failure.name) || [408, 429, 502, 504].includes(failure.status ?? 0) ||\n (failure.status === 503 && (failure.reason === undefined || TRANSIENT_503.has(failure.reason)))\n}\n\n/**\n * The platform refuses a funded request once the paying key's budget is\n * spent. That refusal is decided, so retrying it would loop forever.\n */\nconst budgetExhausted = (error: unknown) => error instanceof Error && error.name === 'KeyBudgetExhaustedError'\n\n/** One non-blocking pass for one message. Safe to repeat with the same turn id. */\nexport async function runHostedTurn<Box extends TurnBox>(turn: { turnId: string; text: string }, ports: HostedTurnPorts<Box>,\n options: HostedTurnOptions): Promise<TurnOutcome> {\n const now = options.now ?? Date.now\n const sleep = options.sleep ?? ((ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms)))\n const observe = ports.observe ?? {}\n const allowDispatch = options.allowDispatch !== false\n const text = turn.text.trim()\n if (!text || text.length > MAX_MESSAGE_CHARS || !turn.turnId || turn.turnId.length > 200) return refusal('refused', 'invalid_message')\n const refused = await ports.admit()\n if (refused) return refused\n\n const timed = async <T>(step: 'ensure' | 'session' | 'drive', fn: () => Promise<T>): Promise<T> => {\n const start = now()\n try { return await fn() } finally { observe.span?.(step, now() - start) }\n }\n const failed = async (step: 'ensure' | 'drive', error: unknown, boxId?: string): Promise<TurnOutcome> => {\n const failure = describeFailure(error)\n await observe.failure?.(step, failure, boxId)\n const classified = ports.classify?.(error, step)\n if (classified) return classified\n if (error instanceof TurnPending) return { ok: 'pending', detail: { step, code: error.code, transient: error.transient } }\n if (budgetExhausted(error)) return refusal('refused', 'budget_exhausted')\n // An admission whose response was lost is retried with the same turn id;\n // the runtime's admission receipt prevents a second execution.\n return { ok: 'pending', detail: { step, code: failure.code, transient: isTransientFailure(failure) }, cause: error }\n }\n\n let box: Box\n try {\n box = await timed('ensure', () => ports.box())\n } catch (error) {\n return failed('ensure', error)\n }\n if (ports.authorize && !await ports.authorize()) return refusal('refused', AUTHORITY_ENDED)\n\n let result: ConversationTurnResult\n try {\n const prompt = await ports.prompt(box)\n const sessionId = ports.sessionId\n // The SDK would create a missing session with no profile, and the runtime\n // would then answer as its generic default assistant.\n if (allowDispatch) await timed('session', async () => {\n const session = await box.session(sessionId).status()\n if (session) {\n observe.session?.({ created: false, ...sessionInfo(session) })\n return\n }\n const backend = await ports.backend()\n await box.createSession({ sessionId, retention: 'workspace', backend })\n // A new session names its harness and model only once the runtime has created it.\n const created = observe.session && observe.readCreatedSession ? sessionInfo(await box.session(sessionId).status()) : {}\n observe.session?.({ created: true, ...(backend.type ? { harness: backend.type } : {}), ...created })\n })\n for (;;) {\n result = await timed('drive', () => box.driveConversationTurn(prompt, { sessionId, turnId: turn.turnId,\n wallCapMs: options.wallCapMs, timeoutMs: options.timeoutMs ?? 8000, allowDispatch }))\n observe.drive?.(result, now())\n const pollMs = options.pollMs ?? 2000\n if (!RUNNING.has(result.state) || options.until === undefined || now() + pollMs > options.until) break\n await sleep(pollMs)\n }\n } catch (error) {\n return failed('drive', error, box.id)\n }\n switch (result.state) {\n case 'completed': {\n const answer = result.text.trim()\n if (!answer) {\n await observe.failure?.('turn', describeFailure(new Error('The turn completed with no reply text')), box.id)\n return refusal('unavailable', 'empty_reply')\n }\n await ports.answered?.(answer, box)\n return { ok: true, text: answer }\n }\n case 'failed':\n await observe.failure?.('turn', describeFailure(Object.assign(new Error(result.error), { name: 'TurnFailed' })), box.id)\n return refusal('unavailable', 'agent_failed')\n // Interaction and plan approval are not exposed over messaging. The wall\n // cap cancels a turn that waits on one, which frees the session.\n // Sandbox 0.45 reported approval and question outcomes as `failed`.\n case 'awaiting_plan_decision': case 'blocked_on_approval': case 'awaiting_question': case 'awaiting_interaction':\n return refusal('refused', 'needs_decision')\n case 'running': case 'awaiting_input': case 'not_admitted':\n return { ok: 'pending', detail: { step: 'turn', code: result.state, transient: true } }\n }\n}\n\n/**\n * Repeat passes until the turn settles, for a caller that holds a connection\n * open (a voice question, say). A turn the runtime never finishes must not\n * hold the caller forever, so `deadline` bounds the wait; `signal` stops the\n * wait only, and the admitted turn keeps running.\n */\nexport async function settleHostedTurn(pass: () => Promise<TurnOutcome>, options: {\n deadline: number\n pollMs?: number\n signal?: AbortSignal\n now?: () => number\n sleep?: (ms: number, signal?: AbortSignal) => Promise<void>\n}): Promise<TurnReply> {\n const now = options.now ?? Date.now\n const sleep = options.sleep ?? ((ms: number, signal?: AbortSignal) => new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms)\n signal?.addEventListener('abort', () => { clearTimeout(timer); reject(signal.reason) }, { once: true })\n }))\n let last = '', repeats = 0\n for (;;) {\n if (options.signal?.aborted || now() > options.deadline) return refusal('unavailable', 'pending')\n const outcome = await pass()\n if (outcome.ok !== 'pending') return outcome\n repeats = outcome.detail.code === last ? repeats + 1 : 1\n last = outcome.detail.code\n if (!outcome.detail.transient && repeats >= MAX_REPEATED_FAILURES) return refusal('unavailable', `${outcome.detail.step}:${last}`)\n try { await sleep(options.pollMs ?? 1500, options.signal) } catch { return refusal('unavailable', 'pending') }\n }\n}\n\n/**\n * A space member who may chat only: no shell and no file writes in the shared\n * box. The profile's other settings are kept.\n */\nexport function chatOnlyBackend(backend: BackendConfig): BackendConfig {\n const profile = backend.profile ?? {}\n return { ...backend, profile: { ...profile,\n tools: { ...profile.tools, bash: false, edit: false, write: false },\n permissions: { ...profile.permissions, bash: 'deny', edit: 'deny' } } }\n}\n"],"mappings":"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","names":["sleep"]}
|
|
@@ -32,8 +32,17 @@ function assertIdentity(payload) {
|
|
|
32
32
|
throw new Error("detached turn Workflow payload requires a non-empty turnId");
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
var DRIVE_STATES = {
|
|
36
|
+
running: true,
|
|
37
|
+
completed: true,
|
|
38
|
+
failed: true,
|
|
39
|
+
awaiting_plan_decision: true,
|
|
40
|
+
blocked_on_approval: true,
|
|
41
|
+
awaiting_question: true,
|
|
42
|
+
awaiting_interaction: true
|
|
43
|
+
};
|
|
35
44
|
function isKnownDriveState(state) {
|
|
36
|
-
return
|
|
45
|
+
return typeof state === "string" && Object.hasOwn(DRIVE_STATES, state);
|
|
37
46
|
}
|
|
38
47
|
function checkedDriveResult(value) {
|
|
39
48
|
const state = value?.state;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/preset-cloudflare/workflow-instance.ts","../../src/preset-cloudflare/detached-turn-workflow.ts","../../src/preset-cloudflare/native-completion-workflow.ts","../../src/preset-cloudflare/headers.ts","../../src/preset-cloudflare/index.ts"],"sourcesContent":["/**\n * The structural part of a Cloudflare Workflow instance used to prove that a\n * failed create call actually left an addressable durable instance behind.\n */\nexport interface CloudflareWorkflowInstanceLike<TStatus = unknown> {\n status(): Promise<TStatus>\n}\n\n/** The structural binding implemented by Cloudflare's `Workflow<TParams>`. */\nexport interface CloudflareWorkflowBindingLike<\n TParams,\n TStatus = unknown,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus> =\n CloudflareWorkflowInstanceLike<TStatus>,\n> {\n create(input: { id: string; params: TParams }): Promise<TInstance>\n get(id: string): Promise<TInstance>\n}\n\nexport interface EnsureCloudflareWorkflowInstanceResult<\n TStatus,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus>,\n> {\n instance: TInstance\n /** False only when create failed but the same id was proven readable. */\n created: boolean\n /** Present on reuse because reading status is the proof the instance exists. */\n status?: TStatus\n}\n\n/**\n * Create one durable Workflow instance, or prove an instance with the same id\n * already exists before accepting a create error as an idempotent retry.\n *\n * Cloudflare does not expose a stable duplicate-error class. Matching error\n * text can therefore swallow unrelated failures. This helper uses the durable\n * resource itself as proof: `get(id).status()` must succeed, otherwise the\n * original create error is rethrown.\n */\nexport async function ensureCloudflareWorkflowInstance<\n TParams,\n TStatus,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus>,\n>(\n binding: CloudflareWorkflowBindingLike<TParams, TStatus, TInstance>,\n input: { id: string; params: TParams },\n): Promise<EnsureCloudflareWorkflowInstanceResult<TStatus, TInstance>> {\n try {\n return {\n instance: await binding.create(input),\n created: true,\n }\n } catch (createError) {\n try {\n const instance = await binding.get(input.id)\n const status = await instance.status()\n return { instance, created: false, status }\n } catch {\n throw createError\n }\n }\n}\n","import type { TurnDriveResult } from '@tangle-network/sandbox'\nimport type { Outcome } from '../sandbox/outcome'\n\n/** The stable identity a Workflow reuses on every retry of one turn. */\nexport interface DetachedTurnWorkflowIdentity {\n /** Sandbox session resume key. */\n sessionId: string\n /** Sandbox completed-turn idempotency key. */\n turnId: string\n}\n\n/** The part of Cloudflare's Workflow event needed by the tick. */\nexport interface CloudflareWorkflowEventLike<TPayload> {\n payload: TPayload\n}\n\n/** A duration accepted by Cloudflare Workflow `step.sleep`. */\nexport type CloudflareWorkflowSleepDuration =\n | `${number} ${\n | 'second'\n | 'seconds'\n | 'minute'\n | 'minutes'\n | 'hour'\n | 'hours'\n | 'day'\n | 'days'\n | 'week'\n | 'weeks'\n | 'month'\n | 'months'\n | 'year'\n | 'years'}`\n | number\n\n/** The durable Workflow operations used by the tick. */\nexport interface CloudflareWorkflowStepLike {\n do<T>(name: string, callback: (context: unknown) => Promise<T>): Promise<T>\n sleep(name: string, duration: CloudflareWorkflowSleepDuration): Promise<void>\n}\n\n/** The states returned by the Sandbox `driveTurn` primitive. */\nexport type DetachedTurnDriveState = TurnDriveResult['state']\n\n/** The small state contract a durable tick needs. Product observers may carry\n * a richer terminal receipt than Sandbox's `TurnDriveResult`. */\nexport interface DetachedTurnDriveResultLike {\n state: DetachedTurnDriveState\n}\n\n/** The terminal result passed to product settlement. */\nexport type DetachedTurnTerminalResult<\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> = Exclude<TResult, { state: 'running' }>\n\n/** A drive call's retryable transport boundary. */\nexport type DetachedTurnDriveOutcome<\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> = Outcome<TResult>\n\n/** Options for one durable detached-turn Workflow run. */\nexport interface DetachedTurnWorkflowTickOptions<\n TPayload extends DetachedTurnWorkflowIdentity,\n TSettled,\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> {\n event: CloudflareWorkflowEventLike<TPayload>\n step: CloudflareWorkflowStepLike\n /** One SDK drive pass. Rejected results must not enter the Workflow cache. */\n drive: (payload: TPayload) => Promise<DetachedTurnDriveOutcome<TResult>>\n /** Must be idempotent: the Worker can stop after the write but before commit. */\n settle: (payload: TPayload, result: DetachedTurnTerminalResult<TResult>) => Promise<TSettled>\n pollDelay?: CloudflareWorkflowSleepDuration\n stepName?: string\n}\n\nfunction assertIdentity(payload: DetachedTurnWorkflowIdentity): void {\n if (!payload || typeof payload.sessionId !== 'string' || !payload.sessionId.trim()) {\n throw new Error('detached turn Workflow payload requires a non-empty sessionId')\n }\n if (typeof payload.turnId !== 'string' || !payload.turnId.trim()) {\n throw new Error('detached turn Workflow payload requires a non-empty turnId')\n }\n}\n\nfunction isKnownDriveState(state: unknown): state is DetachedTurnDriveState {\n return state === 'running'\n || state === 'completed'\n || state === 'failed'\n || state === 'awaiting_plan_decision'\n}\n\nfunction checkedDriveResult<TResult extends DetachedTurnDriveResultLike>(value: TResult): TResult {\n const state = (value as { state?: unknown } | null)?.state\n if (!isKnownDriveState(state)) {\n throw new Error(`detached turn drive returned unknown state: ${String(state)}`)\n }\n return value\n}\n\n/**\n * Drive a detached SDK turn with durable steps, not an HTTP waitUntil lifetime.\n * Stable step names let an evicted Workflow resume without repeating committed\n * passes. Validation belongs inside step.do so a transient malformed response\n * is retried rather than committed permanently as a poisoned checkpoint.\n */\nexport async function runDetachedTurnWorkflowTick<\n TPayload extends DetachedTurnWorkflowIdentity,\n TSettled,\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n>(\n options: DetachedTurnWorkflowTickOptions<TPayload, TSettled, TResult>,\n): Promise<TSettled> {\n assertIdentity(options.event?.payload)\n // Never allow a callback to change the admission identity for later passes.\n const payload = Object.freeze({ ...options.event.payload }) as TPayload\n const name = options.stepName ?? 'detached-turn'\n const delay = options.pollDelay ?? '5 seconds'\n let attempt = 0\n let terminalResult: DetachedTurnTerminalResult<TResult>\n while (true) {\n const driveResult = checkedDriveResult(await options.step.do<TResult>(`${name}:drive:${attempt}`, async () => {\n const outcome = await options.drive(payload)\n if (!outcome.succeeded) throw outcome.error\n return checkedDriveResult(outcome.value)\n }))\n // Validate replayed values as well, including checkpoints from older code.\n if (driveResult.state !== 'running') {\n terminalResult = driveResult as DetachedTurnTerminalResult<TResult>\n break\n }\n await options.step.sleep(`${name}:wait:${attempt}`, delay)\n attempt += 1\n }\n return options.step.do(`${name}:settle`, () => options.settle(payload, terminalResult))\n}\n","/**\n * Durable terminal assembly for a native turn admitted by another request.\n * Product callbacks own domain effects and may use their own Workflow steps;\n * this helper never nests those callbacks inside `step.do`.\n */\n\nimport type {\n NativeCompletionObservation,\n NativeCompletionReceipt,\n} from '../chat-routes/native-completion'\nimport {\n runDetachedTurnWorkflowTick,\n type CloudflareWorkflowEventLike,\n type CloudflareWorkflowSleepDuration,\n type CloudflareWorkflowStepLike,\n type DetachedTurnWorkflowIdentity,\n} from './detached-turn-workflow'\n\n/** Stable identity required to resume a registered native completion. */\nexport interface NativeCompletionWorkflowPayload extends DetachedTurnWorkflowIdentity {\n registeredAt: number\n}\n\ntype NativeCompletionDriveResult = NativeCompletionObservation\n\nexport interface NativeCompletionWorkflowOptions<\n TPayload extends NativeCompletionWorkflowPayload,\n TMessageId,\n> {\n event: CloudflareWorkflowEventLike<TPayload>\n step: CloudflareWorkflowStepLike\n /** Read-only exact observation. It must never dispatch, drive, or cancel. */\n observe(payload: TPayload): Promise<NativeCompletionObservation>\n /**\n * Product-owned idempotent preparation, for example artifact promotion and\n * output classification. It runs outside an enclosing Workflow step so a\n * product may use its own named steps without nesting `step.do`.\n */\n prepare?(payload: TPayload, receipt: NativeCompletionReceipt): Promise<NativeCompletionReceipt>\n /** Durable transcript write. A supplied message id updates the existing row. */\n persistTranscript(\n payload: TPayload,\n receipt: NativeCompletionReceipt,\n existingMessageId?: TMessageId,\n ): Promise<TMessageId>\n /**\n * Product-owned idempotent settlement. A rejection leaves the lock held and\n * skips finalization, making the failed dependency observable and retryable.\n */\n settle(\n payload: TPayload,\n receipt: NativeCompletionReceipt,\n messageId: TMessageId,\n ): Promise<NativeCompletionReceipt | void>\n /** Optional product buffer completion after successful settlement. */\n finalizeBuffer?(payload: TPayload, receipt: NativeCompletionReceipt, messageId: TMessageId): Promise<void>\n /** Release the product lock only after every terminal effect completed. */\n releaseLock(payload: TPayload): Promise<void>\n pollDelay?: CloudflareWorkflowSleepDuration\n stepName?: string\n}\n\nfunction validObservation(value: NativeCompletionObservation): NativeCompletionDriveResult {\n if (!value || (value.state !== 'running' && value.state !== 'completed' && value.state !== 'failed')) {\n throw new Error(`native completion observer returned unknown state: ${String((value as { state?: unknown } | null)?.state)}`)\n }\n if (value.state !== 'running' && !value.receipt) {\n throw new Error('native completion observer returned a terminal state without a receipt')\n }\n return value\n}\n\n/**\n * Drive exact observation through durable retry steps, then run the terminal\n * sequence once. `prepare` and `settle` are deliberately outside `step.do` so\n * product callbacks can compose their own named Cloudflare Workflow steps.\n */\nexport async function runNativeCompletionWorkflow<\n TPayload extends NativeCompletionWorkflowPayload,\n TMessageId,\n>(options: NativeCompletionWorkflowOptions<TPayload, TMessageId>): Promise<NativeCompletionReceipt> {\n const name = options.stepName ?? 'native-completion'\n let receipt = await runDetachedTurnWorkflowTick<\n TPayload,\n NativeCompletionReceipt,\n NativeCompletionDriveResult\n >({\n event: options.event,\n step: options.step,\n pollDelay: options.pollDelay,\n stepName: `${name}:observe`,\n drive: async (payload) => ({ succeeded: true, value: validObservation(await options.observe(payload)) }),\n settle: async (_payload, result) => result.receipt,\n })\n\n if (options.prepare) receipt = await options.prepare(options.event.payload, receipt)\n const messageId = await options.step.do(`${name}:persist-transcript`, () => (\n options.persistTranscript(options.event.payload, receipt)\n ))\n const settledReceipt = await options.settle(options.event.payload, receipt, messageId)\n if (settledReceipt) {\n receipt = settledReceipt\n await options.step.do(`${name}:persist-final-transcript`, () => (\n options.persistTranscript(options.event.payload, receipt, messageId)\n ))\n }\n if (options.finalizeBuffer) {\n await options.step.do(`${name}:finalize-buffer`, () => (\n options.finalizeBuffer!(options.event.payload, receipt, messageId)\n ))\n }\n await options.step.do(`${name}:release-lock`, () => options.releaseLock(options.event.payload))\n return receipt\n}\n","const HEADER_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\n\n/** One path rule in Cloudflare's static-asset `_headers` file. */\nexport interface CloudflareHeadersRule {\n pattern: string\n headers: Readonly<Record<string, string>>\n}\n\nfunction assertSingleLine(value: string, label: string): void {\n if (value.length === 0 || /[\\r\\n]/.test(value)) {\n throw new Error(`${label} must be a non-empty single line`)\n }\n}\n\n/**\n * Render native Cloudflare static-asset header rules.\n *\n * Values are validated before serialization so a product setting cannot add a\n * second path rule or response header through a newline.\n */\nexport function renderCloudflareHeadersFile(\n rules: readonly CloudflareHeadersRule[],\n): string {\n if (rules.length === 0) {\n throw new Error('At least one Cloudflare header rule is required')\n }\n\n const renderedRules = rules.map(({ pattern, headers }) => {\n assertSingleLine(pattern, 'Cloudflare header pattern')\n if (!pattern.startsWith('/') || /\\s/.test(pattern)) {\n throw new Error('Cloudflare header patterns must start with / and contain no whitespace')\n }\n\n const entries = Object.entries(headers)\n if (entries.length === 0) {\n throw new Error(`Cloudflare header rule ${pattern} must contain at least one header`)\n }\n\n const renderedHeaders = entries.map(([name, value]) => {\n if (!HEADER_NAME.test(name)) {\n throw new Error(`Invalid HTTP header name: ${name}`)\n }\n assertSingleLine(value, `Cloudflare header ${name}`)\n return ` ${name}: ${value}`\n })\n\n return [pattern, ...renderedHeaders].join('\\n')\n })\n\n return `${renderedRules.join('\\n\\n')}\\n`\n}\n","/**\n * `@tangle-network/agent-app/preset-cloudflare` — the batteries-included default\n * stack.\n *\n * Every fleet agent runs the SAME backend: Cloudflare D1 (SQLite) through\n * Drizzle for state, a KV namespace as the artifact vault, AES-GCM field crypto\n * for PII, and per-workspace budget-capped model keys. The other agent-app\n * modules are pure SEAMS — `./tools` needs an `AppToolHandlers`, `./knowledge`\n * needs a `KnowledgeStateAccessor`, `./billing` needs a `WorkspaceKeyStore` +\n * `KeyCrypto`. This module is the ONE implementation of those seams against the\n * house stack, so a consumer that runs D1 + KV stands the whole shell up with\n * config + bindings and ZERO handler code.\n *\n * Layering:\n * - Drizzle is a PEER (the consumer installs `drizzle-orm`, never bundled). The\n * schema is therefore expressed two ways that need no import here: the plain\n * DDL ({@link PRESET_MIGRATION_SQL}) a consumer runs to create the tables, and\n * a {@link createPresetDrizzleSchema} factory that takes the consumer's\n * `drizzle-orm/sqlite-core` builder module and returns the typed tables. The\n * column names in {@link PRESET_TABLES} are the contract the handlers,\n * accessor, and DDL all agree on.\n * - D1 + KV are STRUCTURAL: {@link D1Like} (Cloudflare `D1Database` satisfies it)\n * and `KvLike` from `../web` (Cloudflare `KVNamespace` satisfies it). No\n * `@cloudflare/workers-types` dependency.\n * - Crypto/billing reuse `../crypto` + `../billing` exactly — this only wires\n * them to the D1 key table.\n */\n\nimport { createFieldCrypto } from '../crypto/index'\nimport {\n createWorkspaceKeyManager,\n type KeyCrypto,\n type KeyProvisioner,\n type WorkspaceKeyManager,\n type WorkspaceKeyRecord,\n type WorkspaceKeyStore,\n} from '../billing/index'\nimport type { KnowledgeStateAccessor } from '../knowledge/index'\nimport type {\n AddCitationArgs,\n AddCitationResult,\n AppToolContext,\n AppToolHandlers,\n RenderUiArgs,\n RenderUiResult,\n ScheduleFollowupArgs,\n ScheduleFollowupResult,\n SubmitProposalArgs,\n SubmitProposalResult,\n} from '../tools/index'\nimport type { KvLike } from '../web/index'\n\nexport {\n ensureCloudflareWorkflowInstance,\n type CloudflareWorkflowBindingLike,\n type CloudflareWorkflowInstanceLike,\n type EnsureCloudflareWorkflowInstanceResult,\n} from './workflow-instance'\n\nexport {\n runDetachedTurnWorkflowTick,\n type CloudflareWorkflowEventLike,\n type CloudflareWorkflowSleepDuration,\n type CloudflareWorkflowStepLike,\n type DetachedTurnDriveOutcome,\n type DetachedTurnDriveState,\n type DetachedTurnTerminalResult,\n type DetachedTurnWorkflowIdentity,\n type DetachedTurnWorkflowTickOptions,\n} from './detached-turn-workflow'\n\nexport {\n runNativeCompletionWorkflow,\n type NativeCompletionWorkflowOptions,\n type NativeCompletionWorkflowPayload,\n} from './native-completion-workflow'\n\nexport {\n renderCloudflareHeadersFile,\n type CloudflareHeadersRule,\n} from './headers'\n\n// ---------------------------------------------------------------------------\n// D1 structural seam\n//\n// The minimal surface of Cloudflare's `D1Database` the handlers + accessor use.\n// `D1Database` satisfies it structurally, so the consumer passes `env.DB`\n// directly and tests pass an in-memory fake. We keep it to the\n// prepare/bind/first/run/all shape D1 already exposes — no Drizzle here, so the\n// default handlers run on a fresh Worker with only the D1 binding.\n// ---------------------------------------------------------------------------\n\n/** A prepared, bound D1 statement. */\nexport interface D1PreparedLike {\n bind(...values: unknown[]): D1PreparedLike\n first<T = Record<string, unknown>>(colName?: string): Promise<T | null>\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n}\n\n/** The D1 surface the preset needs. Cloudflare `D1Database` satisfies it. */\nexport interface D1Like {\n prepare(query: string): D1PreparedLike\n}\n\n// ---------------------------------------------------------------------------\n// Default schema\n//\n// The four tables the default handlers + accessor read/write. Column names are\n// the single source of truth shared by the DDL, the Drizzle factory, the\n// handlers, and the accessor. Every table is workspace-scoped on `workspace_id`\n// (the accessor's default `where` column) so the knowledge `count` rule and the\n// tool writes agree without per-consumer wiring.\n//\n// Deliberately NOT here: chat thread/message tables. `/chat-store` is the\n// single thread-schema owner (`createChatTables` + `createChatStore`); the\n// preset once declared an unconsumed `threads` DDL, removed so two schemas\n// can't drift.\n// ---------------------------------------------------------------------------\n\n/** The preset table + column names — the contract the DDL, Drizzle schema,\n * handlers, and accessor share. Exposed so a consumer can reference a column\n * without a string literal. */\nexport const PRESET_TABLES = {\n proposals: {\n name: 'proposals',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n type: 'type',\n title: 'title',\n description: 'description',\n status: 'status',\n createdBy: 'created_by',\n createdAt: 'created_at',\n },\n },\n knowledge: {\n name: 'knowledge',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n path: 'path',\n kind: 'kind',\n label: 'label',\n content: 'content',\n createdAt: 'created_at',\n },\n },\n deadlines: {\n name: 'deadlines',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n title: 'title',\n dueDate: 'due_date',\n priority: 'priority',\n status: 'status',\n createdAt: 'created_at',\n },\n },\n workspaceKeys: {\n name: 'workspace_keys',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n keyId: 'key_id',\n keyEncrypted: 'key_encrypted',\n budgetUsd: 'budget_usd',\n expiresAt: 'expires_at',\n revokedAt: 'revoked_at',\n createdAt: 'created_at',\n },\n },\n} as const\n\n/**\n * Plain DDL for the preset schema — run by a consumer to create the tables with\n * ZERO drizzle (`for (const sql of PRESET_MIGRATION_SQL) await db.prepare(sql).run()`,\n * or paste into a `.sql` migration). One statement per table so D1's\n * single-statement `prepare` accepts each. Matches {@link PRESET_TABLES} exactly.\n */\nexport const PRESET_MIGRATION_SQL: readonly string[] = [\n `CREATE TABLE IF NOT EXISTS proposals (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n created_by TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS knowledge (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n path TEXT NOT NULL,\n kind TEXT NOT NULL,\n label TEXT,\n content TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS deadlines (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n title TEXT NOT NULL,\n due_date TEXT NOT NULL,\n priority TEXT,\n status TEXT NOT NULL DEFAULT 'scheduled',\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS workspace_keys (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n key_id TEXT NOT NULL,\n key_encrypted TEXT NOT NULL,\n budget_usd REAL NOT NULL,\n expires_at INTEGER NOT NULL,\n revoked_at INTEGER,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_proposals_ws ON proposals (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_deadlines_ws ON deadlines (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_knowledge_ws ON knowledge (workspace_id)`,\n `CREATE INDEX IF NOT EXISTS idx_workspace_keys_ws ON workspace_keys (workspace_id, revoked_at)`,\n]\n\n/** A chainable column builder — every modifier returns the builder so calls\n * like `.notNull().default('pending')` typecheck. The concrete drizzle builders\n * satisfy this structurally. */\nexport interface DrizzleColumnLike {\n primaryKey: () => DrizzleColumnLike\n notNull: () => DrizzleColumnLike\n default: (v: unknown) => DrizzleColumnLike\n}\n\n/** The shape of a `drizzle-orm/sqlite-core` module — the few builders the\n * preset schema uses. The consumer passes the real module; agent-app never\n * imports it (it stays a peer). */\nexport interface DrizzleSqliteCoreLike {\n sqliteTable: (name: string, columns: Record<string, DrizzleColumnLike>) => unknown\n text: (name?: string) => DrizzleColumnLike\n integer: (name?: string, config?: unknown) => DrizzleColumnLike\n real: (name?: string) => DrizzleColumnLike\n}\n\n/**\n * Build the typed Drizzle schema for the preset, given the consumer's\n * `drizzle-orm/sqlite-core` module. Returns one table object per\n * {@link PRESET_TABLES} entry — pass to `drizzle(db, { schema })` for typed\n * queries, or to drizzle-kit for migration generation. agent-app never imports\n * drizzle; the builder module is the seam.\n *\n * ```ts\n * import * as d from 'drizzle-orm/sqlite-core'\n * const schema = createPresetDrizzleSchema(d)\n * ```\n */\nexport function createPresetDrizzleSchema(d: DrizzleSqliteCoreLike) {\n const { sqliteTable, text, integer, real } = d\n const C = PRESET_TABLES\n return {\n proposals: sqliteTable(C.proposals.name, {\n id: text(C.proposals.columns.id).primaryKey(),\n workspaceId: text(C.proposals.columns.workspaceId).notNull(),\n threadId: text(C.proposals.columns.threadId),\n type: text(C.proposals.columns.type).notNull(),\n title: text(C.proposals.columns.title).notNull(),\n description: text(C.proposals.columns.description),\n status: text(C.proposals.columns.status).notNull().default('pending'),\n createdBy: text(C.proposals.columns.createdBy),\n createdAt: integer(C.proposals.columns.createdAt).notNull(),\n }),\n knowledge: sqliteTable(C.knowledge.name, {\n id: text(C.knowledge.columns.id).primaryKey(),\n workspaceId: text(C.knowledge.columns.workspaceId).notNull(),\n path: text(C.knowledge.columns.path).notNull(),\n kind: text(C.knowledge.columns.kind).notNull(),\n label: text(C.knowledge.columns.label),\n content: text(C.knowledge.columns.content),\n createdAt: integer(C.knowledge.columns.createdAt).notNull(),\n }),\n deadlines: sqliteTable(C.deadlines.name, {\n id: text(C.deadlines.columns.id).primaryKey(),\n workspaceId: text(C.deadlines.columns.workspaceId).notNull(),\n threadId: text(C.deadlines.columns.threadId),\n title: text(C.deadlines.columns.title).notNull(),\n dueDate: text(C.deadlines.columns.dueDate).notNull(),\n priority: text(C.deadlines.columns.priority),\n status: text(C.deadlines.columns.status).notNull().default('scheduled'),\n createdAt: integer(C.deadlines.columns.createdAt).notNull(),\n }),\n workspaceKeys: sqliteTable(C.workspaceKeys.name, {\n id: text(C.workspaceKeys.columns.id).primaryKey(),\n workspaceId: text(C.workspaceKeys.columns.workspaceId).notNull(),\n keyId: text(C.workspaceKeys.columns.keyId).notNull(),\n keyEncrypted: text(C.workspaceKeys.columns.keyEncrypted).notNull(),\n budgetUsd: real(C.workspaceKeys.columns.budgetUsd).notNull(),\n expiresAt: integer(C.workspaceKeys.columns.expiresAt).notNull(),\n revokedAt: integer(C.workspaceKeys.columns.revokedAt),\n createdAt: integer(C.workspaceKeys.columns.createdAt).notNull(),\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Default AppToolHandlers over D1 + KV\n// ---------------------------------------------------------------------------\n\n/** The KV-backed vault. `KvLike` (from `../web`) is the structural KV contract;\n * Cloudflare `KVNamespace` satisfies it. Artifacts are stored under their path. */\nexport type VaultKv = KvLike\n\n/** Define configuration options for handling preset tools including database, vault, and optional utilities */\nexport interface PresetToolHandlerOptions {\n /** The D1 database (Cloudflare `D1Database` satisfies {@link D1Like}). */\n db: D1Like\n /** The KV namespace used as the artifact vault. */\n vault: VaultKv\n /** Id generator. Default `crypto.randomUUID`. Injectable for deterministic tests. */\n newId?: () => string\n /** Clock (epoch ms). Default `Date.now`. Injectable for deterministic tests. */\n now?: () => number\n /** Vault path prefix for `render_ui` artifacts. Default `'ui'`. */\n uiPathPrefix?: string\n /** Vault path prefix for `add_citation` artifacts. Default `'citations'`. */\n citationPathPrefix?: string\n}\n\nfunction slug(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64) || 'item'\n}\n\n/**\n * The default {@link AppToolHandlers} for the house stack:\n * - `submit_proposal` → insert a `proposals` row (`status='pending'`), deduped\n * on (workspace, title) so a retried turn doesn't double-queue.\n * - `schedule_followup` → insert a `deadlines` row, deduped on (workspace, title, due_date).\n * - `render_ui` → write the schema JSON as a `ui/<thread>/<slug>.json`\n * vault artifact AND a `knowledge` row pointing at it.\n * - `add_citation` → write the quote as a `citations/<slug>.json` artifact AND\n * a `knowledge` row.\n *\n * Returns the EXACT persisted content from `render_ui` (per the seam contract) so\n * a completion oracle sees real bytes. Pure seam wiring: a consumer that runs\n * D1 + KV gets all four tools with no handler code.\n */\nexport function createPresetToolHandlers(opts: PresetToolHandlerOptions): AppToolHandlers {\n const { db, vault } = opts\n const newId = opts.newId ?? (() => crypto.randomUUID())\n const now = opts.now ?? (() => Date.now())\n const uiPrefix = opts.uiPathPrefix ?? 'ui'\n const citationPrefix = opts.citationPathPrefix ?? 'citations'\n const P = PRESET_TABLES.proposals\n const D = PRESET_TABLES.deadlines\n const K = PRESET_TABLES.knowledge\n\n async function persistArtifact(path: string, body: string): Promise<void> {\n await vault.put(path, body)\n }\n\n async function insertKnowledge(workspaceId: string, path: string, kind: string, label: string | null, content: string): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${K.name} (${K.columns.id}, ${K.columns.workspaceId}, ${K.columns.path}, ${K.columns.kind}, ${K.columns.label}, ${K.columns.content}, ${K.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(newId(), workspaceId, path, kind, label, content, now())\n .run()\n }\n\n return {\n async submitProposal(args: SubmitProposalArgs, ctx: AppToolContext): Promise<SubmitProposalResult> {\n const existing = await db\n .prepare(`SELECT ${P.columns.id} AS id FROM ${P.name} WHERE ${P.columns.workspaceId} = ? AND ${P.columns.title} = ? LIMIT 1`)\n .bind(ctx.workspaceId, args.title)\n .first<{ id: string }>()\n if (existing) return { proposalId: existing.id, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${P.name} (${P.columns.id}, ${P.columns.workspaceId}, ${P.columns.threadId}, ${P.columns.type}, ${P.columns.title}, ${P.columns.description}, ${P.columns.status}, ${P.columns.createdBy}, ${P.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.type, args.title, args.description ?? null, ctx.userId, now())\n .run()\n return { proposalId: id, deduped: false }\n },\n\n async scheduleFollowup(args: ScheduleFollowupArgs, ctx: AppToolContext): Promise<ScheduleFollowupResult> {\n const existing = await db\n .prepare(\n `SELECT ${D.columns.id} AS id, ${D.columns.dueDate} AS dueDate FROM ${D.name} WHERE ${D.columns.workspaceId} = ? AND ${D.columns.title} = ? AND ${D.columns.dueDate} = ? LIMIT 1`,\n )\n .bind(ctx.workspaceId, args.title, args.dueDate)\n .first<{ id: string; dueDate: string }>()\n if (existing) return { id: existing.id, dueDate: existing.dueDate, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${D.name} (${D.columns.id}, ${D.columns.workspaceId}, ${D.columns.threadId}, ${D.columns.title}, ${D.columns.dueDate}, ${D.columns.priority}, ${D.columns.status}, ${D.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'scheduled', ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.title, args.dueDate, args.priority ?? null, now())\n .run()\n return { id, dueDate: args.dueDate, deduped: false }\n },\n\n async renderUi(args: RenderUiArgs, ctx: AppToolContext): Promise<RenderUiResult> {\n const content = JSON.stringify(args.schema)\n const path = `${uiPrefix}/${ctx.threadId ?? 'global'}/${slug(args.title)}.json`\n await persistArtifact(path, content)\n await insertKnowledge(ctx.workspaceId, path, 'ui', args.title, content)\n return { path, content }\n },\n\n async addCitation(args: AddCitationArgs, ctx: AppToolContext): Promise<AddCitationResult> {\n const citationId = newId()\n const path = `${citationPrefix}/${slug(args.label ?? args.path)}-${citationId.slice(0, 8)}.json`\n const body = JSON.stringify({ sourcePath: args.path, quote: args.quote, label: args.label ?? null })\n await persistArtifact(path, body)\n await insertKnowledge(ctx.workspaceId, path, 'citation', args.label ?? null, body)\n return { citationId, path }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// D1-backed KnowledgeStateAccessor\n// ---------------------------------------------------------------------------\n\n/** Define options for accessing preset knowledge scoped to a specific workspace and configuration */\nexport interface PresetKnowledgeAccessorOptions {\n db: D1Like\n /** The active workspace — every `count` is scoped to it. */\n workspaceId: string\n /** Workspace config the `satisfiedBy: { config }` rules read. A resolved\n * object (dot-path lookup), or a function the accessor calls per path. */\n config: Record<string, unknown> | ((path: string) => unknown)\n /** The default workspace fk column a `count` rule scopes on when its rule\n * omits `where`. Default `'workspace_id'` (the preset schema convention). */\n defaultWhereColumn?: string\n}\n\nfunction readDotPath(obj: Record<string, unknown>, path: string): unknown {\n let cur: unknown = obj\n for (const part of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\n/**\n * The {@link KnowledgeStateAccessor} over the preset D1 schema — the seam that\n * lets the declarative `satisfiedBy` rules resolve with ZERO consumer code:\n * - `config(path)` reads the supplied workspace config by dot-path.\n * - `count({ table, where, statusIn })` runs `SELECT count(*)` scoped to the\n * active workspace (the rule's `where` column, default `workspace_id`),\n * optionally filtered to `statusIn` via a parameterized `IN (...)`.\n *\n * Identifiers (table/column) are validated against a safe pattern before\n * interpolation — they originate from the product's own config, never model\n * input, but we fail loud rather than build a malformed/injectable query.\n */\nexport function createD1KnowledgeStateAccessor(opts: PresetKnowledgeAccessorOptions): KnowledgeStateAccessor {\n const { db, workspaceId } = opts\n const defaultWhere = opts.defaultWhereColumn ?? 'workspace_id'\n const configFn = typeof opts.config === 'function' ? opts.config : (path: string) => readDotPath(opts.config as Record<string, unknown>, path)\n\n const isIdentifier = (s: string) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s)\n\n return {\n config: configFn,\n async count(query) {\n if (!isIdentifier(query.table)) throw new Error(`unsafe table identifier: ${query.table}`)\n const whereCol = query.where ?? defaultWhere\n if (!isIdentifier(whereCol)) throw new Error(`unsafe where identifier: ${whereCol}`)\n\n let sql = `SELECT count(*) AS n FROM ${query.table} WHERE ${whereCol} = ?`\n const binds: unknown[] = [workspaceId]\n if (query.statusIn && query.statusIn.length > 0) {\n sql += ` AND status IN (${query.statusIn.map(() => '?').join(', ')})`\n binds.push(...query.statusIn)\n }\n const row = await db.prepare(sql).bind(...binds).first<{ n: number }>()\n return row?.n ?? 0\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Crypto + per-workspace key wiring\n// ---------------------------------------------------------------------------\n\n/** Build the {@link KeyCrypto} the billing key store uses — AES-256-GCM field\n * crypto bound to the product's 64-char-hex `ENCRYPTION_KEY` (or a resolver).\n * This is the concrete impl behind the `../billing` `KeyCrypto` seam. */\nexport function createPresetFieldCrypto(key: string | (() => string)): KeyCrypto {\n return createFieldCrypto(key)\n}\n\n/**\n * The {@link WorkspaceKeyStore} over the preset `workspace_keys` table — the\n * persistence seam the per-workspace key manager needs. \"Active\" = a row with a\n * null `revoked_at`. Pure D1 wiring; no key minting (that's the provisioner).\n */\nexport function createPresetWorkspaceKeyStore(db: D1Like): WorkspaceKeyStore {\n const W = PRESET_TABLES.workspaceKeys\n return {\n async getActive(workspaceId: string): Promise<WorkspaceKeyRecord | null> {\n const row = await db\n .prepare(\n `SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId, ${W.columns.keyEncrypted} AS keyEncrypted, ${W.columns.budgetUsd} AS budgetUsd, ${W.columns.expiresAt} AS expiresAt FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL ORDER BY ${W.columns.createdAt} DESC LIMIT 1`,\n )\n .bind(workspaceId)\n .first<{ id: string; keyId: string; keyEncrypted: string; budgetUsd: number; expiresAt: number | null }>()\n if (!row) return null\n return {\n id: row.id,\n keyId: row.keyId,\n keyEncrypted: row.keyEncrypted,\n budgetUsd: row.budgetUsd,\n expiresAt: row.expiresAt == null ? null : new Date(row.expiresAt),\n }\n },\n async listActive(workspaceId: string): Promise<Array<{ id: string; keyId: string }>> {\n const res = await db\n .prepare(`SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL`)\n .bind(workspaceId)\n .all<{ id: string; keyId: string }>()\n return res.results\n },\n async insert(record): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${W.name} (${W.columns.id}, ${W.columns.workspaceId}, ${W.columns.keyId}, ${W.columns.keyEncrypted}, ${W.columns.budgetUsd}, ${W.columns.expiresAt}, ${W.columns.revokedAt}, ${W.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, NULL, ?)`,\n )\n .bind(crypto.randomUUID(), record.workspaceId, record.keyId, record.keyEncrypted, record.budgetUsd, record.expiresAt.getTime(), Date.now())\n .run()\n },\n async markRevoked(id: string, now: Date): Promise<void> {\n await db\n .prepare(`UPDATE ${W.name} SET ${W.columns.revokedAt} = ? WHERE ${W.columns.id} = ?`)\n .bind(now.getTime(), id)\n .run()\n },\n }\n}\n\n/** Define preset billing options including database, provisioner, encryption key, budget, and optional settings */\nexport interface PresetBillingOptions {\n db: D1Like\n /** The key provisioner (`@tangle-network/tcloud`'s client satisfies it structurally). */\n provisioner: KeyProvisioner\n /** Field-crypto key (64-char hex) or resolver — encrypts the minted key at rest. */\n encryptionKey: string | (() => string)\n /** Default monthly USD allowance when a call doesn't specify one. */\n defaultBudgetUsd: number\n /** Injectable clock. */\n now?: () => Date\n /** tcloud product the key is scoped to. Default `'router'`. */\n product?: string\n}\n\n/**\n * Stand up the per-workspace budget-capped {@link WorkspaceKeyManager} on the\n * house stack: the preset `workspace_keys` D1 store + AES-GCM field crypto +\n * the consumer's tcloud provisioner. The mint/rotate/rollover/usage LOGIC lives\n * in `../billing`; this only binds it to the preset table + crypto.\n */\nexport function createPresetWorkspaceKeyManager(opts: PresetBillingOptions): WorkspaceKeyManager {\n return createWorkspaceKeyManager({\n provisioner: opts.provisioner,\n store: createPresetWorkspaceKeyStore(opts.db),\n crypto: createPresetFieldCrypto(opts.encryptionKey),\n defaultBudgetUsd: opts.defaultBudgetUsd,\n now: opts.now,\n product: opts.product,\n })\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/preset-cloudflare/workflow-instance.ts","../../src/preset-cloudflare/detached-turn-workflow.ts","../../src/preset-cloudflare/native-completion-workflow.ts","../../src/preset-cloudflare/headers.ts","../../src/preset-cloudflare/index.ts"],"sourcesContent":["/**\n * The structural part of a Cloudflare Workflow instance used to prove that a\n * failed create call actually left an addressable durable instance behind.\n */\nexport interface CloudflareWorkflowInstanceLike<TStatus = unknown> {\n status(): Promise<TStatus>\n}\n\n/** The structural binding implemented by Cloudflare's `Workflow<TParams>`. */\nexport interface CloudflareWorkflowBindingLike<\n TParams,\n TStatus = unknown,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus> =\n CloudflareWorkflowInstanceLike<TStatus>,\n> {\n create(input: { id: string; params: TParams }): Promise<TInstance>\n get(id: string): Promise<TInstance>\n}\n\nexport interface EnsureCloudflareWorkflowInstanceResult<\n TStatus,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus>,\n> {\n instance: TInstance\n /** False only when create failed but the same id was proven readable. */\n created: boolean\n /** Present on reuse because reading status is the proof the instance exists. */\n status?: TStatus\n}\n\n/**\n * Create one durable Workflow instance, or prove an instance with the same id\n * already exists before accepting a create error as an idempotent retry.\n *\n * Cloudflare does not expose a stable duplicate-error class. Matching error\n * text can therefore swallow unrelated failures. This helper uses the durable\n * resource itself as proof: `get(id).status()` must succeed, otherwise the\n * original create error is rethrown.\n */\nexport async function ensureCloudflareWorkflowInstance<\n TParams,\n TStatus,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus>,\n>(\n binding: CloudflareWorkflowBindingLike<TParams, TStatus, TInstance>,\n input: { id: string; params: TParams },\n): Promise<EnsureCloudflareWorkflowInstanceResult<TStatus, TInstance>> {\n try {\n return {\n instance: await binding.create(input),\n created: true,\n }\n } catch (createError) {\n try {\n const instance = await binding.get(input.id)\n const status = await instance.status()\n return { instance, created: false, status }\n } catch {\n throw createError\n }\n }\n}\n","import type { TurnDriveResult } from '@tangle-network/sandbox'\nimport type { Outcome } from '../sandbox/outcome'\n\n/** The stable identity a Workflow reuses on every retry of one turn. */\nexport interface DetachedTurnWorkflowIdentity {\n /** Sandbox session resume key. */\n sessionId: string\n /** Sandbox completed-turn idempotency key. */\n turnId: string\n}\n\n/** The part of Cloudflare's Workflow event needed by the tick. */\nexport interface CloudflareWorkflowEventLike<TPayload> {\n payload: TPayload\n}\n\n/** A duration accepted by Cloudflare Workflow `step.sleep`. */\nexport type CloudflareWorkflowSleepDuration =\n | `${number} ${\n | 'second'\n | 'seconds'\n | 'minute'\n | 'minutes'\n | 'hour'\n | 'hours'\n | 'day'\n | 'days'\n | 'week'\n | 'weeks'\n | 'month'\n | 'months'\n | 'year'\n | 'years'}`\n | number\n\n/** The durable Workflow operations used by the tick. */\nexport interface CloudflareWorkflowStepLike {\n do<T>(name: string, callback: (context: unknown) => Promise<T>): Promise<T>\n sleep(name: string, duration: CloudflareWorkflowSleepDuration): Promise<void>\n}\n\n/** The states returned by the Sandbox `driveTurn` primitive. */\nexport type DetachedTurnDriveState = TurnDriveResult['state']\n\n/** The small state contract a durable tick needs. Product observers may carry\n * a richer terminal receipt than Sandbox's `TurnDriveResult`. */\nexport interface DetachedTurnDriveResultLike {\n state: DetachedTurnDriveState\n}\n\n/** The terminal result passed to product settlement. */\nexport type DetachedTurnTerminalResult<\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> = Exclude<TResult, { state: 'running' }>\n\n/** A drive call's retryable transport boundary. */\nexport type DetachedTurnDriveOutcome<\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> = Outcome<TResult>\n\n/** Options for one durable detached-turn Workflow run. */\nexport interface DetachedTurnWorkflowTickOptions<\n TPayload extends DetachedTurnWorkflowIdentity,\n TSettled,\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> {\n event: CloudflareWorkflowEventLike<TPayload>\n step: CloudflareWorkflowStepLike\n /** One SDK drive pass. Rejected results must not enter the Workflow cache. */\n drive: (payload: TPayload) => Promise<DetachedTurnDriveOutcome<TResult>>\n /** Must be idempotent: the Worker can stop after the write but before commit. */\n settle: (payload: TPayload, result: DetachedTurnTerminalResult<TResult>) => Promise<TSettled>\n pollDelay?: CloudflareWorkflowSleepDuration\n stepName?: string\n}\n\nfunction assertIdentity(payload: DetachedTurnWorkflowIdentity): void {\n if (!payload || typeof payload.sessionId !== 'string' || !payload.sessionId.trim()) {\n throw new Error('detached turn Workflow payload requires a non-empty sessionId')\n }\n if (typeof payload.turnId !== 'string' || !payload.turnId.trim()) {\n throw new Error('detached turn Workflow payload requires a non-empty turnId')\n }\n}\n\n// Keyed by the SDK's own union, so a state a new Sandbox adds fails this\n// package's build instead of retrying a settled turn as \"unknown\" forever.\nconst DRIVE_STATES: Record<DetachedTurnDriveState, true> = {\n running: true,\n completed: true,\n failed: true,\n awaiting_plan_decision: true,\n blocked_on_approval: true,\n awaiting_question: true,\n awaiting_interaction: true,\n}\n\nfunction isKnownDriveState(state: unknown): state is DetachedTurnDriveState {\n return typeof state === 'string' && Object.hasOwn(DRIVE_STATES, state)\n}\n\nfunction checkedDriveResult<TResult extends DetachedTurnDriveResultLike>(value: TResult): TResult {\n const state = (value as { state?: unknown } | null)?.state\n if (!isKnownDriveState(state)) {\n throw new Error(`detached turn drive returned unknown state: ${String(state)}`)\n }\n return value\n}\n\n/**\n * Drive a detached SDK turn with durable steps, not an HTTP waitUntil lifetime.\n * Stable step names let an evicted Workflow resume without repeating committed\n * passes. Validation belongs inside step.do so a transient malformed response\n * is retried rather than committed permanently as a poisoned checkpoint.\n */\nexport async function runDetachedTurnWorkflowTick<\n TPayload extends DetachedTurnWorkflowIdentity,\n TSettled,\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n>(\n options: DetachedTurnWorkflowTickOptions<TPayload, TSettled, TResult>,\n): Promise<TSettled> {\n assertIdentity(options.event?.payload)\n // Never allow a callback to change the admission identity for later passes.\n const payload = Object.freeze({ ...options.event.payload }) as TPayload\n const name = options.stepName ?? 'detached-turn'\n const delay = options.pollDelay ?? '5 seconds'\n let attempt = 0\n let terminalResult: DetachedTurnTerminalResult<TResult>\n while (true) {\n const driveResult = checkedDriveResult(await options.step.do<TResult>(`${name}:drive:${attempt}`, async () => {\n const outcome = await options.drive(payload)\n if (!outcome.succeeded) throw outcome.error\n return checkedDriveResult(outcome.value)\n }))\n // Validate replayed values as well, including checkpoints from older code.\n if (driveResult.state !== 'running') {\n terminalResult = driveResult as DetachedTurnTerminalResult<TResult>\n break\n }\n await options.step.sleep(`${name}:wait:${attempt}`, delay)\n attempt += 1\n }\n return options.step.do(`${name}:settle`, () => options.settle(payload, terminalResult))\n}\n","/**\n * Durable terminal assembly for a native turn admitted by another request.\n * Product callbacks own domain effects and may use their own Workflow steps;\n * this helper never nests those callbacks inside `step.do`.\n */\n\nimport type {\n NativeCompletionObservation,\n NativeCompletionReceipt,\n} from '../chat-routes/native-completion'\nimport {\n runDetachedTurnWorkflowTick,\n type CloudflareWorkflowEventLike,\n type CloudflareWorkflowSleepDuration,\n type CloudflareWorkflowStepLike,\n type DetachedTurnWorkflowIdentity,\n} from './detached-turn-workflow'\n\n/** Stable identity required to resume a registered native completion. */\nexport interface NativeCompletionWorkflowPayload extends DetachedTurnWorkflowIdentity {\n registeredAt: number\n}\n\ntype NativeCompletionDriveResult = NativeCompletionObservation\n\nexport interface NativeCompletionWorkflowOptions<\n TPayload extends NativeCompletionWorkflowPayload,\n TMessageId,\n> {\n event: CloudflareWorkflowEventLike<TPayload>\n step: CloudflareWorkflowStepLike\n /** Read-only exact observation. It must never dispatch, drive, or cancel. */\n observe(payload: TPayload): Promise<NativeCompletionObservation>\n /**\n * Product-owned idempotent preparation, for example artifact promotion and\n * output classification. It runs outside an enclosing Workflow step so a\n * product may use its own named steps without nesting `step.do`.\n */\n prepare?(payload: TPayload, receipt: NativeCompletionReceipt): Promise<NativeCompletionReceipt>\n /** Durable transcript write. A supplied message id updates the existing row. */\n persistTranscript(\n payload: TPayload,\n receipt: NativeCompletionReceipt,\n existingMessageId?: TMessageId,\n ): Promise<TMessageId>\n /**\n * Product-owned idempotent settlement. A rejection leaves the lock held and\n * skips finalization, making the failed dependency observable and retryable.\n */\n settle(\n payload: TPayload,\n receipt: NativeCompletionReceipt,\n messageId: TMessageId,\n ): Promise<NativeCompletionReceipt | void>\n /** Optional product buffer completion after successful settlement. */\n finalizeBuffer?(payload: TPayload, receipt: NativeCompletionReceipt, messageId: TMessageId): Promise<void>\n /** Release the product lock only after every terminal effect completed. */\n releaseLock(payload: TPayload): Promise<void>\n pollDelay?: CloudflareWorkflowSleepDuration\n stepName?: string\n}\n\nfunction validObservation(value: NativeCompletionObservation): NativeCompletionDriveResult {\n if (!value || (value.state !== 'running' && value.state !== 'completed' && value.state !== 'failed')) {\n throw new Error(`native completion observer returned unknown state: ${String((value as { state?: unknown } | null)?.state)}`)\n }\n if (value.state !== 'running' && !value.receipt) {\n throw new Error('native completion observer returned a terminal state without a receipt')\n }\n return value\n}\n\n/**\n * Drive exact observation through durable retry steps, then run the terminal\n * sequence once. `prepare` and `settle` are deliberately outside `step.do` so\n * product callbacks can compose their own named Cloudflare Workflow steps.\n */\nexport async function runNativeCompletionWorkflow<\n TPayload extends NativeCompletionWorkflowPayload,\n TMessageId,\n>(options: NativeCompletionWorkflowOptions<TPayload, TMessageId>): Promise<NativeCompletionReceipt> {\n const name = options.stepName ?? 'native-completion'\n let receipt = await runDetachedTurnWorkflowTick<\n TPayload,\n NativeCompletionReceipt,\n NativeCompletionDriveResult\n >({\n event: options.event,\n step: options.step,\n pollDelay: options.pollDelay,\n stepName: `${name}:observe`,\n drive: async (payload) => ({ succeeded: true, value: validObservation(await options.observe(payload)) }),\n settle: async (_payload, result) => result.receipt,\n })\n\n if (options.prepare) receipt = await options.prepare(options.event.payload, receipt)\n const messageId = await options.step.do(`${name}:persist-transcript`, () => (\n options.persistTranscript(options.event.payload, receipt)\n ))\n const settledReceipt = await options.settle(options.event.payload, receipt, messageId)\n if (settledReceipt) {\n receipt = settledReceipt\n await options.step.do(`${name}:persist-final-transcript`, () => (\n options.persistTranscript(options.event.payload, receipt, messageId)\n ))\n }\n if (options.finalizeBuffer) {\n await options.step.do(`${name}:finalize-buffer`, () => (\n options.finalizeBuffer!(options.event.payload, receipt, messageId)\n ))\n }\n await options.step.do(`${name}:release-lock`, () => options.releaseLock(options.event.payload))\n return receipt\n}\n","const HEADER_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\n\n/** One path rule in Cloudflare's static-asset `_headers` file. */\nexport interface CloudflareHeadersRule {\n pattern: string\n headers: Readonly<Record<string, string>>\n}\n\nfunction assertSingleLine(value: string, label: string): void {\n if (value.length === 0 || /[\\r\\n]/.test(value)) {\n throw new Error(`${label} must be a non-empty single line`)\n }\n}\n\n/**\n * Render native Cloudflare static-asset header rules.\n *\n * Values are validated before serialization so a product setting cannot add a\n * second path rule or response header through a newline.\n */\nexport function renderCloudflareHeadersFile(\n rules: readonly CloudflareHeadersRule[],\n): string {\n if (rules.length === 0) {\n throw new Error('At least one Cloudflare header rule is required')\n }\n\n const renderedRules = rules.map(({ pattern, headers }) => {\n assertSingleLine(pattern, 'Cloudflare header pattern')\n if (!pattern.startsWith('/') || /\\s/.test(pattern)) {\n throw new Error('Cloudflare header patterns must start with / and contain no whitespace')\n }\n\n const entries = Object.entries(headers)\n if (entries.length === 0) {\n throw new Error(`Cloudflare header rule ${pattern} must contain at least one header`)\n }\n\n const renderedHeaders = entries.map(([name, value]) => {\n if (!HEADER_NAME.test(name)) {\n throw new Error(`Invalid HTTP header name: ${name}`)\n }\n assertSingleLine(value, `Cloudflare header ${name}`)\n return ` ${name}: ${value}`\n })\n\n return [pattern, ...renderedHeaders].join('\\n')\n })\n\n return `${renderedRules.join('\\n\\n')}\\n`\n}\n","/**\n * `@tangle-network/agent-app/preset-cloudflare` — the batteries-included default\n * stack.\n *\n * Every fleet agent runs the SAME backend: Cloudflare D1 (SQLite) through\n * Drizzle for state, a KV namespace as the artifact vault, AES-GCM field crypto\n * for PII, and per-workspace budget-capped model keys. The other agent-app\n * modules are pure SEAMS — `./tools` needs an `AppToolHandlers`, `./knowledge`\n * needs a `KnowledgeStateAccessor`, `./billing` needs a `WorkspaceKeyStore` +\n * `KeyCrypto`. This module is the ONE implementation of those seams against the\n * house stack, so a consumer that runs D1 + KV stands the whole shell up with\n * config + bindings and ZERO handler code.\n *\n * Layering:\n * - Drizzle is a PEER (the consumer installs `drizzle-orm`, never bundled). The\n * schema is therefore expressed two ways that need no import here: the plain\n * DDL ({@link PRESET_MIGRATION_SQL}) a consumer runs to create the tables, and\n * a {@link createPresetDrizzleSchema} factory that takes the consumer's\n * `drizzle-orm/sqlite-core` builder module and returns the typed tables. The\n * column names in {@link PRESET_TABLES} are the contract the handlers,\n * accessor, and DDL all agree on.\n * - D1 + KV are STRUCTURAL: {@link D1Like} (Cloudflare `D1Database` satisfies it)\n * and `KvLike` from `../web` (Cloudflare `KVNamespace` satisfies it). No\n * `@cloudflare/workers-types` dependency.\n * - Crypto/billing reuse `../crypto` + `../billing` exactly — this only wires\n * them to the D1 key table.\n */\n\nimport { createFieldCrypto } from '../crypto/index'\nimport {\n createWorkspaceKeyManager,\n type KeyCrypto,\n type KeyProvisioner,\n type WorkspaceKeyManager,\n type WorkspaceKeyRecord,\n type WorkspaceKeyStore,\n} from '../billing/index'\nimport type { KnowledgeStateAccessor } from '../knowledge/index'\nimport type {\n AddCitationArgs,\n AddCitationResult,\n AppToolContext,\n AppToolHandlers,\n RenderUiArgs,\n RenderUiResult,\n ScheduleFollowupArgs,\n ScheduleFollowupResult,\n SubmitProposalArgs,\n SubmitProposalResult,\n} from '../tools/index'\nimport type { KvLike } from '../web/index'\n\nexport {\n ensureCloudflareWorkflowInstance,\n type CloudflareWorkflowBindingLike,\n type CloudflareWorkflowInstanceLike,\n type EnsureCloudflareWorkflowInstanceResult,\n} from './workflow-instance'\n\nexport {\n runDetachedTurnWorkflowTick,\n type CloudflareWorkflowEventLike,\n type CloudflareWorkflowSleepDuration,\n type CloudflareWorkflowStepLike,\n type DetachedTurnDriveOutcome,\n type DetachedTurnDriveState,\n type DetachedTurnTerminalResult,\n type DetachedTurnWorkflowIdentity,\n type DetachedTurnWorkflowTickOptions,\n} from './detached-turn-workflow'\n\nexport {\n runNativeCompletionWorkflow,\n type NativeCompletionWorkflowOptions,\n type NativeCompletionWorkflowPayload,\n} from './native-completion-workflow'\n\nexport {\n renderCloudflareHeadersFile,\n type CloudflareHeadersRule,\n} from './headers'\n\n// ---------------------------------------------------------------------------\n// D1 structural seam\n//\n// The minimal surface of Cloudflare's `D1Database` the handlers + accessor use.\n// `D1Database` satisfies it structurally, so the consumer passes `env.DB`\n// directly and tests pass an in-memory fake. We keep it to the\n// prepare/bind/first/run/all shape D1 already exposes — no Drizzle here, so the\n// default handlers run on a fresh Worker with only the D1 binding.\n// ---------------------------------------------------------------------------\n\n/** A prepared, bound D1 statement. */\nexport interface D1PreparedLike {\n bind(...values: unknown[]): D1PreparedLike\n first<T = Record<string, unknown>>(colName?: string): Promise<T | null>\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n}\n\n/** The D1 surface the preset needs. Cloudflare `D1Database` satisfies it. */\nexport interface D1Like {\n prepare(query: string): D1PreparedLike\n}\n\n// ---------------------------------------------------------------------------\n// Default schema\n//\n// The four tables the default handlers + accessor read/write. Column names are\n// the single source of truth shared by the DDL, the Drizzle factory, the\n// handlers, and the accessor. Every table is workspace-scoped on `workspace_id`\n// (the accessor's default `where` column) so the knowledge `count` rule and the\n// tool writes agree without per-consumer wiring.\n//\n// Deliberately NOT here: chat thread/message tables. `/chat-store` is the\n// single thread-schema owner (`createChatTables` + `createChatStore`); the\n// preset once declared an unconsumed `threads` DDL, removed so two schemas\n// can't drift.\n// ---------------------------------------------------------------------------\n\n/** The preset table + column names — the contract the DDL, Drizzle schema,\n * handlers, and accessor share. Exposed so a consumer can reference a column\n * without a string literal. */\nexport const PRESET_TABLES = {\n proposals: {\n name: 'proposals',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n type: 'type',\n title: 'title',\n description: 'description',\n status: 'status',\n createdBy: 'created_by',\n createdAt: 'created_at',\n },\n },\n knowledge: {\n name: 'knowledge',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n path: 'path',\n kind: 'kind',\n label: 'label',\n content: 'content',\n createdAt: 'created_at',\n },\n },\n deadlines: {\n name: 'deadlines',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n title: 'title',\n dueDate: 'due_date',\n priority: 'priority',\n status: 'status',\n createdAt: 'created_at',\n },\n },\n workspaceKeys: {\n name: 'workspace_keys',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n keyId: 'key_id',\n keyEncrypted: 'key_encrypted',\n budgetUsd: 'budget_usd',\n expiresAt: 'expires_at',\n revokedAt: 'revoked_at',\n createdAt: 'created_at',\n },\n },\n} as const\n\n/**\n * Plain DDL for the preset schema — run by a consumer to create the tables with\n * ZERO drizzle (`for (const sql of PRESET_MIGRATION_SQL) await db.prepare(sql).run()`,\n * or paste into a `.sql` migration). One statement per table so D1's\n * single-statement `prepare` accepts each. Matches {@link PRESET_TABLES} exactly.\n */\nexport const PRESET_MIGRATION_SQL: readonly string[] = [\n `CREATE TABLE IF NOT EXISTS proposals (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n created_by TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS knowledge (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n path TEXT NOT NULL,\n kind TEXT NOT NULL,\n label TEXT,\n content TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS deadlines (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n title TEXT NOT NULL,\n due_date TEXT NOT NULL,\n priority TEXT,\n status TEXT NOT NULL DEFAULT 'scheduled',\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS workspace_keys (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n key_id TEXT NOT NULL,\n key_encrypted TEXT NOT NULL,\n budget_usd REAL NOT NULL,\n expires_at INTEGER NOT NULL,\n revoked_at INTEGER,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_proposals_ws ON proposals (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_deadlines_ws ON deadlines (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_knowledge_ws ON knowledge (workspace_id)`,\n `CREATE INDEX IF NOT EXISTS idx_workspace_keys_ws ON workspace_keys (workspace_id, revoked_at)`,\n]\n\n/** A chainable column builder — every modifier returns the builder so calls\n * like `.notNull().default('pending')` typecheck. The concrete drizzle builders\n * satisfy this structurally. */\nexport interface DrizzleColumnLike {\n primaryKey: () => DrizzleColumnLike\n notNull: () => DrizzleColumnLike\n default: (v: unknown) => DrizzleColumnLike\n}\n\n/** The shape of a `drizzle-orm/sqlite-core` module — the few builders the\n * preset schema uses. The consumer passes the real module; agent-app never\n * imports it (it stays a peer). */\nexport interface DrizzleSqliteCoreLike {\n sqliteTable: (name: string, columns: Record<string, DrizzleColumnLike>) => unknown\n text: (name?: string) => DrizzleColumnLike\n integer: (name?: string, config?: unknown) => DrizzleColumnLike\n real: (name?: string) => DrizzleColumnLike\n}\n\n/**\n * Build the typed Drizzle schema for the preset, given the consumer's\n * `drizzle-orm/sqlite-core` module. Returns one table object per\n * {@link PRESET_TABLES} entry — pass to `drizzle(db, { schema })` for typed\n * queries, or to drizzle-kit for migration generation. agent-app never imports\n * drizzle; the builder module is the seam.\n *\n * ```ts\n * import * as d from 'drizzle-orm/sqlite-core'\n * const schema = createPresetDrizzleSchema(d)\n * ```\n */\nexport function createPresetDrizzleSchema(d: DrizzleSqliteCoreLike) {\n const { sqliteTable, text, integer, real } = d\n const C = PRESET_TABLES\n return {\n proposals: sqliteTable(C.proposals.name, {\n id: text(C.proposals.columns.id).primaryKey(),\n workspaceId: text(C.proposals.columns.workspaceId).notNull(),\n threadId: text(C.proposals.columns.threadId),\n type: text(C.proposals.columns.type).notNull(),\n title: text(C.proposals.columns.title).notNull(),\n description: text(C.proposals.columns.description),\n status: text(C.proposals.columns.status).notNull().default('pending'),\n createdBy: text(C.proposals.columns.createdBy),\n createdAt: integer(C.proposals.columns.createdAt).notNull(),\n }),\n knowledge: sqliteTable(C.knowledge.name, {\n id: text(C.knowledge.columns.id).primaryKey(),\n workspaceId: text(C.knowledge.columns.workspaceId).notNull(),\n path: text(C.knowledge.columns.path).notNull(),\n kind: text(C.knowledge.columns.kind).notNull(),\n label: text(C.knowledge.columns.label),\n content: text(C.knowledge.columns.content),\n createdAt: integer(C.knowledge.columns.createdAt).notNull(),\n }),\n deadlines: sqliteTable(C.deadlines.name, {\n id: text(C.deadlines.columns.id).primaryKey(),\n workspaceId: text(C.deadlines.columns.workspaceId).notNull(),\n threadId: text(C.deadlines.columns.threadId),\n title: text(C.deadlines.columns.title).notNull(),\n dueDate: text(C.deadlines.columns.dueDate).notNull(),\n priority: text(C.deadlines.columns.priority),\n status: text(C.deadlines.columns.status).notNull().default('scheduled'),\n createdAt: integer(C.deadlines.columns.createdAt).notNull(),\n }),\n workspaceKeys: sqliteTable(C.workspaceKeys.name, {\n id: text(C.workspaceKeys.columns.id).primaryKey(),\n workspaceId: text(C.workspaceKeys.columns.workspaceId).notNull(),\n keyId: text(C.workspaceKeys.columns.keyId).notNull(),\n keyEncrypted: text(C.workspaceKeys.columns.keyEncrypted).notNull(),\n budgetUsd: real(C.workspaceKeys.columns.budgetUsd).notNull(),\n expiresAt: integer(C.workspaceKeys.columns.expiresAt).notNull(),\n revokedAt: integer(C.workspaceKeys.columns.revokedAt),\n createdAt: integer(C.workspaceKeys.columns.createdAt).notNull(),\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Default AppToolHandlers over D1 + KV\n// ---------------------------------------------------------------------------\n\n/** The KV-backed vault. `KvLike` (from `../web`) is the structural KV contract;\n * Cloudflare `KVNamespace` satisfies it. Artifacts are stored under their path. */\nexport type VaultKv = KvLike\n\n/** Define configuration options for handling preset tools including database, vault, and optional utilities */\nexport interface PresetToolHandlerOptions {\n /** The D1 database (Cloudflare `D1Database` satisfies {@link D1Like}). */\n db: D1Like\n /** The KV namespace used as the artifact vault. */\n vault: VaultKv\n /** Id generator. Default `crypto.randomUUID`. Injectable for deterministic tests. */\n newId?: () => string\n /** Clock (epoch ms). Default `Date.now`. Injectable for deterministic tests. */\n now?: () => number\n /** Vault path prefix for `render_ui` artifacts. Default `'ui'`. */\n uiPathPrefix?: string\n /** Vault path prefix for `add_citation` artifacts. Default `'citations'`. */\n citationPathPrefix?: string\n}\n\nfunction slug(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64) || 'item'\n}\n\n/**\n * The default {@link AppToolHandlers} for the house stack:\n * - `submit_proposal` → insert a `proposals` row (`status='pending'`), deduped\n * on (workspace, title) so a retried turn doesn't double-queue.\n * - `schedule_followup` → insert a `deadlines` row, deduped on (workspace, title, due_date).\n * - `render_ui` → write the schema JSON as a `ui/<thread>/<slug>.json`\n * vault artifact AND a `knowledge` row pointing at it.\n * - `add_citation` → write the quote as a `citations/<slug>.json` artifact AND\n * a `knowledge` row.\n *\n * Returns the EXACT persisted content from `render_ui` (per the seam contract) so\n * a completion oracle sees real bytes. Pure seam wiring: a consumer that runs\n * D1 + KV gets all four tools with no handler code.\n */\nexport function createPresetToolHandlers(opts: PresetToolHandlerOptions): AppToolHandlers {\n const { db, vault } = opts\n const newId = opts.newId ?? (() => crypto.randomUUID())\n const now = opts.now ?? (() => Date.now())\n const uiPrefix = opts.uiPathPrefix ?? 'ui'\n const citationPrefix = opts.citationPathPrefix ?? 'citations'\n const P = PRESET_TABLES.proposals\n const D = PRESET_TABLES.deadlines\n const K = PRESET_TABLES.knowledge\n\n async function persistArtifact(path: string, body: string): Promise<void> {\n await vault.put(path, body)\n }\n\n async function insertKnowledge(workspaceId: string, path: string, kind: string, label: string | null, content: string): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${K.name} (${K.columns.id}, ${K.columns.workspaceId}, ${K.columns.path}, ${K.columns.kind}, ${K.columns.label}, ${K.columns.content}, ${K.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(newId(), workspaceId, path, kind, label, content, now())\n .run()\n }\n\n return {\n async submitProposal(args: SubmitProposalArgs, ctx: AppToolContext): Promise<SubmitProposalResult> {\n const existing = await db\n .prepare(`SELECT ${P.columns.id} AS id FROM ${P.name} WHERE ${P.columns.workspaceId} = ? AND ${P.columns.title} = ? LIMIT 1`)\n .bind(ctx.workspaceId, args.title)\n .first<{ id: string }>()\n if (existing) return { proposalId: existing.id, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${P.name} (${P.columns.id}, ${P.columns.workspaceId}, ${P.columns.threadId}, ${P.columns.type}, ${P.columns.title}, ${P.columns.description}, ${P.columns.status}, ${P.columns.createdBy}, ${P.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.type, args.title, args.description ?? null, ctx.userId, now())\n .run()\n return { proposalId: id, deduped: false }\n },\n\n async scheduleFollowup(args: ScheduleFollowupArgs, ctx: AppToolContext): Promise<ScheduleFollowupResult> {\n const existing = await db\n .prepare(\n `SELECT ${D.columns.id} AS id, ${D.columns.dueDate} AS dueDate FROM ${D.name} WHERE ${D.columns.workspaceId} = ? AND ${D.columns.title} = ? AND ${D.columns.dueDate} = ? LIMIT 1`,\n )\n .bind(ctx.workspaceId, args.title, args.dueDate)\n .first<{ id: string; dueDate: string }>()\n if (existing) return { id: existing.id, dueDate: existing.dueDate, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${D.name} (${D.columns.id}, ${D.columns.workspaceId}, ${D.columns.threadId}, ${D.columns.title}, ${D.columns.dueDate}, ${D.columns.priority}, ${D.columns.status}, ${D.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'scheduled', ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.title, args.dueDate, args.priority ?? null, now())\n .run()\n return { id, dueDate: args.dueDate, deduped: false }\n },\n\n async renderUi(args: RenderUiArgs, ctx: AppToolContext): Promise<RenderUiResult> {\n const content = JSON.stringify(args.schema)\n const path = `${uiPrefix}/${ctx.threadId ?? 'global'}/${slug(args.title)}.json`\n await persistArtifact(path, content)\n await insertKnowledge(ctx.workspaceId, path, 'ui', args.title, content)\n return { path, content }\n },\n\n async addCitation(args: AddCitationArgs, ctx: AppToolContext): Promise<AddCitationResult> {\n const citationId = newId()\n const path = `${citationPrefix}/${slug(args.label ?? args.path)}-${citationId.slice(0, 8)}.json`\n const body = JSON.stringify({ sourcePath: args.path, quote: args.quote, label: args.label ?? null })\n await persistArtifact(path, body)\n await insertKnowledge(ctx.workspaceId, path, 'citation', args.label ?? null, body)\n return { citationId, path }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// D1-backed KnowledgeStateAccessor\n// ---------------------------------------------------------------------------\n\n/** Define options for accessing preset knowledge scoped to a specific workspace and configuration */\nexport interface PresetKnowledgeAccessorOptions {\n db: D1Like\n /** The active workspace — every `count` is scoped to it. */\n workspaceId: string\n /** Workspace config the `satisfiedBy: { config }` rules read. A resolved\n * object (dot-path lookup), or a function the accessor calls per path. */\n config: Record<string, unknown> | ((path: string) => unknown)\n /** The default workspace fk column a `count` rule scopes on when its rule\n * omits `where`. Default `'workspace_id'` (the preset schema convention). */\n defaultWhereColumn?: string\n}\n\nfunction readDotPath(obj: Record<string, unknown>, path: string): unknown {\n let cur: unknown = obj\n for (const part of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\n/**\n * The {@link KnowledgeStateAccessor} over the preset D1 schema — the seam that\n * lets the declarative `satisfiedBy` rules resolve with ZERO consumer code:\n * - `config(path)` reads the supplied workspace config by dot-path.\n * - `count({ table, where, statusIn })` runs `SELECT count(*)` scoped to the\n * active workspace (the rule's `where` column, default `workspace_id`),\n * optionally filtered to `statusIn` via a parameterized `IN (...)`.\n *\n * Identifiers (table/column) are validated against a safe pattern before\n * interpolation — they originate from the product's own config, never model\n * input, but we fail loud rather than build a malformed/injectable query.\n */\nexport function createD1KnowledgeStateAccessor(opts: PresetKnowledgeAccessorOptions): KnowledgeStateAccessor {\n const { db, workspaceId } = opts\n const defaultWhere = opts.defaultWhereColumn ?? 'workspace_id'\n const configFn = typeof opts.config === 'function' ? opts.config : (path: string) => readDotPath(opts.config as Record<string, unknown>, path)\n\n const isIdentifier = (s: string) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s)\n\n return {\n config: configFn,\n async count(query) {\n if (!isIdentifier(query.table)) throw new Error(`unsafe table identifier: ${query.table}`)\n const whereCol = query.where ?? defaultWhere\n if (!isIdentifier(whereCol)) throw new Error(`unsafe where identifier: ${whereCol}`)\n\n let sql = `SELECT count(*) AS n FROM ${query.table} WHERE ${whereCol} = ?`\n const binds: unknown[] = [workspaceId]\n if (query.statusIn && query.statusIn.length > 0) {\n sql += ` AND status IN (${query.statusIn.map(() => '?').join(', ')})`\n binds.push(...query.statusIn)\n }\n const row = await db.prepare(sql).bind(...binds).first<{ n: number }>()\n return row?.n ?? 0\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Crypto + per-workspace key wiring\n// ---------------------------------------------------------------------------\n\n/** Build the {@link KeyCrypto} the billing key store uses — AES-256-GCM field\n * crypto bound to the product's 64-char-hex `ENCRYPTION_KEY` (or a resolver).\n * This is the concrete impl behind the `../billing` `KeyCrypto` seam. */\nexport function createPresetFieldCrypto(key: string | (() => string)): KeyCrypto {\n return createFieldCrypto(key)\n}\n\n/**\n * The {@link WorkspaceKeyStore} over the preset `workspace_keys` table — the\n * persistence seam the per-workspace key manager needs. \"Active\" = a row with a\n * null `revoked_at`. Pure D1 wiring; no key minting (that's the provisioner).\n */\nexport function createPresetWorkspaceKeyStore(db: D1Like): WorkspaceKeyStore {\n const W = PRESET_TABLES.workspaceKeys\n return {\n async getActive(workspaceId: string): Promise<WorkspaceKeyRecord | null> {\n const row = await db\n .prepare(\n `SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId, ${W.columns.keyEncrypted} AS keyEncrypted, ${W.columns.budgetUsd} AS budgetUsd, ${W.columns.expiresAt} AS expiresAt FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL ORDER BY ${W.columns.createdAt} DESC LIMIT 1`,\n )\n .bind(workspaceId)\n .first<{ id: string; keyId: string; keyEncrypted: string; budgetUsd: number; expiresAt: number | null }>()\n if (!row) return null\n return {\n id: row.id,\n keyId: row.keyId,\n keyEncrypted: row.keyEncrypted,\n budgetUsd: row.budgetUsd,\n expiresAt: row.expiresAt == null ? null : new Date(row.expiresAt),\n }\n },\n async listActive(workspaceId: string): Promise<Array<{ id: string; keyId: string }>> {\n const res = await db\n .prepare(`SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL`)\n .bind(workspaceId)\n .all<{ id: string; keyId: string }>()\n return res.results\n },\n async insert(record): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${W.name} (${W.columns.id}, ${W.columns.workspaceId}, ${W.columns.keyId}, ${W.columns.keyEncrypted}, ${W.columns.budgetUsd}, ${W.columns.expiresAt}, ${W.columns.revokedAt}, ${W.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, NULL, ?)`,\n )\n .bind(crypto.randomUUID(), record.workspaceId, record.keyId, record.keyEncrypted, record.budgetUsd, record.expiresAt.getTime(), Date.now())\n .run()\n },\n async markRevoked(id: string, now: Date): Promise<void> {\n await db\n .prepare(`UPDATE ${W.name} SET ${W.columns.revokedAt} = ? WHERE ${W.columns.id} = ?`)\n .bind(now.getTime(), id)\n .run()\n },\n }\n}\n\n/** Define preset billing options including database, provisioner, encryption key, budget, and optional settings */\nexport interface PresetBillingOptions {\n db: D1Like\n /** The key provisioner (`@tangle-network/tcloud`'s client satisfies it structurally). */\n provisioner: KeyProvisioner\n /** Field-crypto key (64-char hex) or resolver — encrypts the minted key at rest. */\n encryptionKey: string | (() => string)\n /** Default monthly USD allowance when a call doesn't specify one. */\n defaultBudgetUsd: number\n /** Injectable clock. */\n now?: () => Date\n /** tcloud product the key is scoped to. Default `'router'`. */\n product?: string\n}\n\n/**\n * Stand up the per-workspace budget-capped {@link WorkspaceKeyManager} on the\n * house stack: the preset `workspace_keys` D1 store + AES-GCM field crypto +\n * the consumer's tcloud provisioner. The mint/rotate/rollover/usage LOGIC lives\n * in `../billing`; this only binds it to the preset table + crypto.\n */\nexport function createPresetWorkspaceKeyManager(opts: PresetBillingOptions): WorkspaceKeyManager {\n return createWorkspaceKeyManager({\n provisioner: opts.provisioner,\n store: createPresetWorkspaceKeyStore(opts.db),\n crypto: createPresetFieldCrypto(opts.encryptionKey),\n defaultBudgetUsd: opts.defaultBudgetUsd,\n now: opts.now,\n product: opts.product,\n })\n}\n"],"mappings":"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","names":[]}
|
package/dist/sandbox/index.js
CHANGED
|
@@ -99,7 +99,7 @@ import {
|
|
|
99
99
|
workspaceSandboxRecoveryMessage,
|
|
100
100
|
workspaceSandboxRecoveryRecommendedActions,
|
|
101
101
|
writeProfileFilesToBox
|
|
102
|
-
} from "../chunk-
|
|
102
|
+
} from "../chunk-J42REVQJ.js";
|
|
103
103
|
import "../chunk-MCYJON3F.js";
|
|
104
104
|
import "../chunk-LWSJK546.js";
|
|
105
105
|
import "../chunk-FZOGDD2E.js";
|