@tangle-network/agent-app 0.60.191 → 0.60.192

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/operator/contract.ts","../../src/operator/server.ts","../../src/operator/client.ts","../../src/operator/chat-adapter.ts","../../src/operator/platform-agent-keys.ts"],"sourcesContent":["/**\n * The standard operator API contract every agent app mounts at\n * `/api/operator/v1`. An outside agent (a Claude Code or Codex session, or\n * another app's agent) holds one scoped key per app and drives workspaces\n * through these routes; the app supplies only storage and execution.\n */\n\nexport const OPERATOR_API_VERSION = 'v1'\nexport const OPERATOR_API_BASE_PATH = '/api/operator/v1'\n\n/** Key permissions, with the labels an app's API access page shows. */\nexport const OPERATOR_ACCESS = [\n {\n scope: 'operator:read',\n label: 'Read workspaces',\n description: 'Read workspaces, conversations, files, approvals, scorecards, and run status.',\n },\n {\n scope: 'operator:write',\n label: 'Create and edit',\n description: 'Create workspaces and conversations, and write files.',\n },\n {\n scope: 'operator:run',\n requires: ['operator:read'],\n label: 'Run agents',\n description: 'Requires read access. Start, continue, and interrupt agent turns, and answer their questions and tool-permission requests. Agents can modify workspace files. Uses your plan and budget.',\n },\n] as const\n\nexport type OperatorScope = typeof OPERATOR_ACCESS[number]['scope']\n\nexport const OPERATOR_API_SCOPES: readonly OperatorScope[] = OPERATOR_ACCESS.map(({ scope }) => scope)\n\nexport const OPERATOR_SCOPE_DEPENDENCIES: Readonly<Record<string, readonly string[]>> = Object.fromEntries(\n OPERATOR_ACCESS.flatMap((option) => 'requires' in option ? [[option.scope, option.requires]] : []),\n)\n\n/**\n * A key carrying one or more of these scopes reaches only the named\n * workspaces and cannot create workspaces. Keys without one keep the owner's\n * ordinary workspace access.\n */\nexport const OPERATOR_WORKSPACE_SCOPE_PREFIX = 'operator:workspace:'\n\nconst WORKSPACE_ID = /^[A-Za-z0-9_-]{1,128}$/\n\nexport function operatorWorkspaceScope(workspaceId: string): string {\n if (!WORKSPACE_ID.test(workspaceId)) throw new TypeError('Workspace ids contain only letters, digits, - and _')\n return `${OPERATOR_WORKSPACE_SCOPE_PREFIX}${workspaceId}`\n}\n\n/** The workspaces a key is restricted to, or null when it is unrestricted. A malformed restriction restricts to nothing. */\nexport function operatorKeyWorkspaces(scopes: readonly string[]): readonly string[] | null {\n const restricted = scopes.filter((scope) => scope.startsWith(OPERATOR_WORKSPACE_SCOPE_PREFIX))\n if (restricted.length === 0) return null\n return restricted\n .map((scope) => scope.slice(OPERATOR_WORKSPACE_SCOPE_PREFIX.length))\n .filter((id) => WORKSPACE_ID.test(id))\n}\n\nexport type OperatorRole = 'owner' | 'admin' | 'editor' | 'viewer'\n\nexport interface OperatorAppInfo {\n /** Stable app identifier, such as `gtm` or `tax`. */\n app: string\n name: string\n apiVersion: typeof OPERATOR_API_VERSION\n /** Optional operations this app implements; absent ones answer 501. */\n capabilities: OperatorCapability[]\n}\n\nexport type OperatorCapability =\n | 'workspaces.create'\n | 'threads.read'\n | 'files.read'\n | 'journal.read'\n | 'assets.read'\n | 'scorecard.read'\n\nexport interface OperatorPrincipalInfo {\n keyId: string\n scopes: string[]\n /** Workspaces the key is restricted to, or null for the owner's ordinary access. */\n workspaces: string[] | null\n}\n\nexport interface OperatorWorkspace {\n id: string\n name: string\n role?: OperatorRole\n /** Browser link for a person to open the workspace. */\n url?: string\n createdAt?: string\n}\n\nexport interface OperatorThread {\n id: string\n title: string\n updatedAt?: string\n url?: string\n}\n\n/**\n * `succeeded` and `failed` are settled. `input-required` waits on a person or\n * an operator decision listed in `approvals`. `unknown` means the app cannot\n * establish the state; it is never evidence of success.\n */\nexport type OperatorTurnState = 'queued' | 'working' | 'input-required' | 'succeeded' | 'failed' | 'unknown'\n\nexport const OPERATOR_SETTLED_STATES: readonly OperatorTurnState[] = ['succeeded', 'failed']\n\nexport interface OperatorAssetRef {\n id: string\n /** Root-relative operator route that returns the bytes. */\n path: string\n mediaType?: string\n name?: string\n}\n\nexport interface OperatorFileChange {\n path: string\n action: 'created' | 'updated' | 'deleted' | 'changed'\n}\n\nexport type OperatorApprovalKind = 'question' | 'permission' | 'plan' | 'hub' | 'asset' | 'review'\n\nexport interface OperatorApproval {\n id: string\n kind: OperatorApprovalKind\n title: string\n detail?: string\n threadId?: string\n /** Browser link where an authorized person decides it. */\n url?: string\n createdAt?: string\n expiresAt?: string\n /** Who can decide: an operator key with run access, an editor, or the workspace owner. */\n decidedBy: 'operator' | 'editor' | 'owner'\n}\n\nexport interface OperatorTurn {\n workspaceId: string\n threadId: string\n turnId: string\n state: OperatorTurnState\n url?: string\n executionId?: string\n model?: string\n costUsd?: number\n completedAt?: string\n /** The settled answer; null until the turn settles. */\n reply: { content: string; mediaType: string } | null\n failure: { code?: string; message: string } | null\n assets: OperatorAssetRef[]\n files: OperatorFileChange[]\n /** Decisions open on this turn's conversation. */\n approvals: OperatorApproval[]\n}\n\nexport interface OperatorJournalEntry {\n path: string\n /** `YYYY-MM-DD` the entry belongs to. */\n date: string\n title?: string\n updatedAt?: string\n}\n\nexport interface OperatorFileEntry {\n path: string\n updatedAt?: string\n bytes?: number\n}\n\nexport interface OperatorFile {\n path: string\n content: string\n mediaType: string\n revision?: string\n}\n\nexport interface OperatorMetric {\n key: string\n label: string\n /** Null when the app has no measurement; never a guessed zero. */\n value: number | null\n unit: 'count' | 'usd' | 'ratio' | 'ms'\n note?: string\n}\n\nexport interface OperatorScorecard {\n workspaceId: string\n generatedAt: string\n window: { start: string; end: string; days: number }\n /** Work the agent did in the window. */\n metrics: OperatorMetric[]\n /** Business results recorded in the window, such as clicks, signups, or replies. */\n outcomes: OperatorMetric[]\n /** How the numbers are measured and what they exclude. */\n measurement: string\n}\n\nexport interface OperatorErrorBody {\n error: string\n code: string\n retryable?: boolean\n}\n\nexport interface StartTurnInput {\n /** Client-generated UUID. Reuse it to retry the same turn; it never starts twice. */\n turnId: string\n content: string\n /** Continue this conversation; omitted starts a new one. */\n threadId?: string\n /** Title for a new conversation. */\n title?: string\n model?: string\n}\n\n/** Route table: method, path pattern under the base path, and required scopes. */\nexport const OPERATOR_ROUTES = [\n { id: 'describe', method: 'GET', path: '', scopes: ['operator:read'] },\n { id: 'workspaces.list', method: 'GET', path: '/workspaces', scopes: ['operator:read'] },\n { id: 'workspaces.create', method: 'POST', path: '/workspaces', scopes: ['operator:write'] },\n { id: 'workspaces.get', method: 'GET', path: '/workspaces/:workspaceId', scopes: ['operator:read'] },\n { id: 'turns.start', method: 'POST', path: '/workspaces/:workspaceId/turns', scopes: ['operator:read', 'operator:run'] },\n { id: 'threads.list', method: 'GET', path: '/workspaces/:workspaceId/threads', scopes: ['operator:read'] },\n { id: 'threads.get', method: 'GET', path: '/workspaces/:workspaceId/threads/:threadId', scopes: ['operator:read'] },\n { id: 'turns.get', method: 'GET', path: '/workspaces/:workspaceId/threads/:threadId/turns/:turnId', scopes: ['operator:read'] },\n { id: 'approvals.list', method: 'GET', path: '/workspaces/:workspaceId/approvals', scopes: ['operator:read'] },\n { id: 'journal.list', method: 'GET', path: '/workspaces/:workspaceId/journal', scopes: ['operator:read'] },\n { id: 'files.list', method: 'GET', path: '/workspaces/:workspaceId/files', scopes: ['operator:read'] },\n { id: 'files.read', method: 'GET', path: '/workspaces/:workspaceId/file', scopes: ['operator:read'] },\n { id: 'assets.read', method: 'GET', path: '/workspaces/:workspaceId/assets/:assetId', scopes: ['operator:read'] },\n { id: 'scorecard.get', method: 'GET', path: '/workspaces/:workspaceId/scorecard', scopes: ['operator:read'] },\n] as const satisfies ReadonlyArray<{ id: string; method: 'GET' | 'POST'; path: string; scopes: readonly OperatorScope[] }>\n\nexport type OperatorRouteId = typeof OPERATOR_ROUTES[number]['id']\n\nconst SEGMENT = /^[A-Za-z0-9_:.%-]{1,256}$/\n\n/** Match a pathname under the base path to its route and decoded parameters. */\nexport function matchOperatorRoute(\n method: string,\n pathname: string,\n basePath: string = OPERATOR_API_BASE_PATH,\n): { route: typeof OPERATOR_ROUTES[number]; params: Record<string, string> } | null {\n if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return null\n const rest = pathname.slice(basePath.length).replace(/\\/$/, '')\n const actual = rest ? rest.split('/').slice(1) : []\n for (const route of OPERATOR_ROUTES) {\n if (route.method !== method) continue\n const pattern = route.path ? route.path.split('/').slice(1) : []\n if (pattern.length !== actual.length) continue\n const params: Record<string, string> = {}\n let matched = true\n for (let index = 0; index < pattern.length; index++) {\n const expected = pattern[index]!\n const segment = actual[index]!\n if (expected.startsWith(':')) {\n if (!SEGMENT.test(segment)) { matched = false; break }\n let decoded: string\n try { decoded = decodeURIComponent(segment) } catch { matched = false; break }\n if (!decoded || decoded.includes('/')) { matched = false; break }\n params[expected.slice(1)] = decoded\n } else if (expected !== segment) {\n matched = false\n break\n }\n }\n if (matched) return { route, params }\n }\n return null\n}\n","import { createApiKeyRequestAuth, type RequestApiKey } from '../platform/api-key-auth.js'\nimport {\n OPERATOR_API_BASE_PATH,\n OPERATOR_API_VERSION,\n matchOperatorRoute,\n operatorKeyWorkspaces,\n type OperatorApproval,\n type OperatorCapability,\n type OperatorFile,\n type OperatorFileEntry,\n type OperatorJournalEntry,\n type OperatorRouteId,\n type OperatorScorecard,\n type OperatorThread,\n type OperatorTurn,\n type OperatorWorkspace,\n type StartTurnInput,\n} from './contract.js'\n\n/** The verified caller of one operator request. */\nexport interface OperatorContext<Identity> {\n identity: Identity\n key: { keyId: string; scopes: readonly string[] }\n request: Request\n}\n\n/**\n * The app's side of the operator API: storage and execution only. Scope,\n * workspace restriction, input validation, and response shapes belong to the\n * shared handler, so every app answers the same way.\n */\nexport interface OperatorAdapter<Identity> {\n /**\n * Apply the app's workspace roles: `read` needs viewer access, `run` needs\n * the role that may start agent work. Null answers 404, so a key cannot\n * probe workspaces it cannot reach.\n */\n authorizeWorkspace(ctx: OperatorContext<Identity>, workspaceId: string, access: 'read' | 'run'): Promise<OperatorWorkspace | null>\n listWorkspaces(ctx: OperatorContext<Identity>): Promise<OperatorWorkspace[]>\n createWorkspace?(ctx: OperatorContext<Identity>, input: { name: string }): Promise<OperatorWorkspace>\n /** Admit the turn durably and return without waiting for it; the same turnId never starts twice. */\n startTurn(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, input: StartTurnInput): Promise<OperatorTurn>\n getTurn(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, target: { threadId: string; turnId: string }): Promise<OperatorTurn | null>\n listThreads?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, query: { cursor?: string; limit: number }): Promise<{ threads: OperatorThread[]; nextCursor: string | null }>\n getThread?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, threadId: string): Promise<{ thread: OperatorThread; latestTurn: OperatorTurn | null } | null>\n listApprovals(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace): Promise<OperatorApproval[]>\n listJournal?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, query: { days: number }): Promise<OperatorJournalEntry[]>\n listFiles?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, query: { prefix: string }): Promise<OperatorFileEntry[]>\n readFile?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, path: string): Promise<OperatorFile | null>\n /** The stored bytes, or null when the asset is not in this workspace. */\n readAsset?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, assetId: string): Promise<Response | null>\n getScorecard?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, query: { days: number }): Promise<OperatorScorecard>\n}\n\nexport interface OperatorKeyStore<Key extends RequestApiKey, Identity> {\n /** Verify against the app's existing key store, including revocation. */\n verify(authorization: string): Promise<Key | null>\n /** Load the key owner; throw a Response to refuse a key class, such as spending-capped keys. */\n resolveIdentity(key: Key): Promise<Identity | null>\n /** Atomically count the request against the key's limits. */\n claimRequest(key: Key, requestId: string): Promise<{ allowed: boolean; retryAfterSeconds?: number }>\n}\n\nexport interface OperatorApiOptions<Key extends RequestApiKey, Identity> {\n app: { id: string; name: string }\n keys: OperatorKeyStore<Key, Identity>\n adapter: OperatorAdapter<Identity>\n basePath?: string\n /** Longest hold for a turn read with `?wait=`, in seconds. */\n maxWaitSeconds?: number\n /** Interval between state reads during a held turn read. */\n pollIntervalMs?: number\n /** Unexpected adapter failures; responses never carry their detail. */\n onError?(error: unknown, route: OperatorRouteId): void\n}\n\nexport interface OperatorApi {\n /** True when the request addresses this API's base path. */\n matches(request: Request): boolean\n handle(request: Request): Promise<Response>\n}\n\n/** A refusal with a stable code; adapters throw it for expected failures. */\nexport class OperatorError extends Error {\n constructor(\n readonly code: string,\n readonly status: number,\n message?: string,\n readonly retryable = false,\n ) {\n super(message ?? code)\n this.name = 'OperatorError'\n }\n}\n\nconst TURN_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\nconst THREAD_ID = /^[A-Za-z0-9_-]{1,256}$/\nconst MAX_BODY_BYTES = 128 * 1024\nconst MAX_CONTENT_BYTES = 64 * 1024\nconst START_FIELDS = new Set(['turnId', 'content', 'threadId', 'title', 'model'])\n\nconst NO_STORE = { 'cache-control': 'private, no-store' }\n\nfunction json(body: unknown, status = 200): Response {\n return Response.json(body, { status, headers: NO_STORE })\n}\n\nfunction refusal(status: number, code: string, error: string, retryable = false): Response {\n return json({ error, code, ...(retryable ? { retryable } : {}) }, status)\n}\n\nfunction boundedInteger(value: string | null, fallback: number, min: number, max: number, name: string): number {\n if (value === null || value === '') return fallback\n if (!/^\\d{1,6}$/.test(value)) throw new OperatorError('operator.invalid_input', 400, `${name} must be a whole number`)\n const parsed = Number(value)\n if (parsed < min || parsed > max) throw new OperatorError('operator.invalid_input', 400, `${name} must be between ${min} and ${max}`)\n return parsed\n}\n\n/** Workspace-relative paths only: no absolute, parent, empty, or backslash segments. */\nfunction relativePath(value: string | null, name: string, { allowEmpty }: { allowEmpty: boolean }): string {\n const path = value ?? ''\n if (path === '' && allowEmpty) return ''\n if (!path || path.length > 1024 || path.startsWith('/') || path.includes('\\\\') || /[\\u0000-\\u001f\\u007f]/.test(path)) {\n throw new OperatorError('operator.invalid_path', 400, `${name} must be a workspace-relative path`)\n }\n const segments = path.replace(/\\/$/, '').split('/')\n if (segments.some((segment) => !segment || segment === '.' || segment === '..')) {\n throw new OperatorError('operator.invalid_path', 400, `${name} must be a workspace-relative path`)\n }\n return path\n}\n\nasync function readJsonBody(request: Request): Promise<Record<string, unknown>> {\n const declared = Number(request.headers.get('content-length') ?? '0')\n if (declared > MAX_BODY_BYTES) throw new OperatorError('operator.body_too_large', 413, 'Request body is too large')\n const text = await request.text()\n if (new TextEncoder().encode(text).byteLength > MAX_BODY_BYTES) {\n throw new OperatorError('operator.body_too_large', 413, 'Request body is too large')\n }\n let parsed: unknown\n try { parsed = JSON.parse(text) } catch { throw new OperatorError('operator.invalid_json', 400, 'Body must be a JSON object') }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new OperatorError('operator.invalid_json', 400, 'Body must be a JSON object')\n }\n return parsed as Record<string, unknown>\n}\n\nfunction optionalString(body: Record<string, unknown>, name: string, max: number): string | undefined {\n const value = body[name]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string' || !value.trim() || value.length > max) {\n throw new OperatorError('operator.invalid_input', 400, `${name} must be a nonempty string of at most ${max} characters`)\n }\n return value.trim()\n}\n\n/** Identity and destination come from the key and the path; the body can only describe the work. */\nfunction parseStartTurn(body: Record<string, unknown>): StartTurnInput {\n const unknown = Object.keys(body).filter((field) => !START_FIELDS.has(field))\n if (unknown.length) throw new OperatorError('operator.invalid_input', 400, `Unknown fields: ${unknown.join(', ')}`)\n if (typeof body.turnId !== 'string' || !TURN_ID.test(body.turnId)) {\n throw new OperatorError('operator.invalid_input', 400, 'turnId must be a client-generated UUID')\n }\n if (typeof body.content !== 'string' || !body.content.trim()) {\n throw new OperatorError('operator.invalid_input', 400, 'content must be a nonempty string')\n }\n if (new TextEncoder().encode(body.content).byteLength > MAX_CONTENT_BYTES) {\n throw new OperatorError('operator.invalid_input', 400, `content must be at most ${MAX_CONTENT_BYTES} bytes`)\n }\n const threadId = optionalString(body, 'threadId', 256)\n if (threadId !== undefined && !THREAD_ID.test(threadId)) {\n throw new OperatorError('operator.invalid_input', 400, 'threadId contains only letters, digits, - and _')\n }\n const title = optionalString(body, 'title', 200)\n const model = optionalString(body, 'model', 200)\n return {\n turnId: body.turnId.toLowerCase(),\n content: body.content,\n ...(threadId === undefined ? {} : { threadId }),\n ...(title === undefined ? {} : { title }),\n ...(model === undefined ? {} : { model }),\n }\n}\n\nfunction capabilities<Identity>(adapter: OperatorAdapter<Identity>): OperatorCapability[] {\n const present: Array<[OperatorCapability, unknown]> = [\n ['workspaces.create', adapter.createWorkspace],\n ['threads.read', adapter.getThread ?? adapter.listThreads],\n ['files.read', adapter.readFile ?? adapter.listFiles],\n ['journal.read', adapter.listJournal],\n ['assets.read', adapter.readAsset],\n ['scorecard.read', adapter.getScorecard],\n ]\n return present.filter(([, implemented]) => typeof implemented === 'function').map(([name]) => name)\n}\n\nfunction unsupported(): never {\n throw new OperatorError('operator.unsupported', 501, 'This app does not implement this operation')\n}\n\n/** A held turn read returns as soon as the turn settles or waits on a decision. */\nfunction needsAttention(turn: OperatorTurn): boolean {\n return turn.state === 'succeeded' || turn.state === 'failed' || turn.state === 'input-required' || turn.approvals.length > 0\n}\n\nconst sleep = (ms: number, signal: AbortSignal) => new Promise<void>((resolve) => {\n if (signal.aborted) return resolve()\n const timer = setTimeout(resolve, ms)\n signal.addEventListener('abort', () => { clearTimeout(timer); resolve() }, { once: true })\n})\n\n/**\n * Mount the standard operator API. Requests must carry a Bearer key: there is\n * no browser-cookie fallback, so a page session never reaches these routes.\n */\nexport function createOperatorApi<Key extends RequestApiKey, Identity>(\n options: OperatorApiOptions<Key, Identity>,\n): OperatorApi {\n const basePath = (options.basePath ?? OPERATOR_API_BASE_PATH).replace(/\\/$/, '')\n const maxWaitSeconds = Math.max(0, Math.min(options.maxWaitSeconds ?? 25, 55))\n const pollIntervalMs = Math.max(250, options.pollIntervalMs ?? 2_000)\n const { adapter } = options\n\n function matches(request: Request): boolean {\n const { pathname } = new URL(request.url)\n return pathname === basePath || pathname.startsWith(`${basePath}/`)\n }\n\n async function handle(request: Request): Promise<Response> {\n const url = new URL(request.url)\n const match = matchOperatorRoute(request.method, url.pathname, basePath)\n if (!match) return refusal(404, 'operator.route_not_found', 'No operator route matches this method and path')\n const { route, params } = match\n if (request.headers.get('Authorization') === null) {\n return refusal(401, 'operator.unauthenticated', 'An operator API key is required')\n }\n\n let verified: { identity: Identity; key: Key }\n try {\n const authenticate = createApiKeyRequestAuth<Key, { identity: Identity; key: Key }>({\n verify: (authorization) => options.keys.verify(authorization),\n requiredScope: () => route.scopes,\n resolveIdentity: async (key) => {\n const identity = await options.keys.resolveIdentity(key)\n return identity === null || identity === undefined ? null : { identity, key }\n },\n claimRequest: (key, requestId) => options.keys.claimRequest(key, requestId),\n })\n const result = await authenticate(request)\n if (!result) return refusal(401, 'operator.unauthenticated', 'An operator API key is required')\n verified = result\n } catch (error) {\n if (error instanceof Response) return error\n options.onError?.(error, route.id)\n return refusal(503, 'operator.auth_unavailable', 'Key verification is unavailable', true)\n }\n\n const ctx: OperatorContext<Identity> = {\n identity: verified.identity,\n key: { keyId: verified.key.keyId, scopes: [...verified.key.scopes] },\n request,\n }\n const restriction = operatorKeyWorkspaces(verified.key.scopes)\n\n try {\n if (route.id === 'describe') {\n return json({\n app: {\n app: options.app.id, name: options.app.name, apiVersion: OPERATOR_API_VERSION,\n capabilities: capabilities(adapter),\n },\n principal: { keyId: ctx.key.keyId, scopes: [...ctx.key.scopes], workspaces: restriction ? [...restriction] : null },\n })\n }\n if (route.id === 'workspaces.list') {\n const listed = await adapter.listWorkspaces(ctx)\n return json({ workspaces: restriction ? listed.filter((workspace) => restriction.includes(workspace.id)) : listed })\n }\n if (route.id === 'workspaces.create') {\n if (restriction) {\n throw new OperatorError('operator.workspace_restricted', 403, 'A workspace-restricted key cannot create workspaces')\n }\n if (!adapter.createWorkspace) unsupported()\n const body = await readJsonBody(request)\n const unknown = Object.keys(body).filter((field) => field !== 'name')\n if (unknown.length) throw new OperatorError('operator.invalid_input', 400, `Unknown fields: ${unknown.join(', ')}`)\n const name = optionalString(body, 'name', 120)\n if (!name) throw new OperatorError('operator.invalid_input', 400, 'name is required')\n return json({ workspace: await adapter.createWorkspace(ctx, { name }) }, 201)\n }\n\n const workspaceId = params.workspaceId!\n if (restriction && !restriction.includes(workspaceId)) {\n throw new OperatorError('operator.workspace_not_found', 404, 'Workspace not found')\n }\n const workspace = await adapter.authorizeWorkspace(ctx, workspaceId, route.id === 'turns.start' ? 'run' : 'read')\n if (!workspace) throw new OperatorError('operator.workspace_not_found', 404, 'Workspace not found')\n\n switch (route.id) {\n case 'workspaces.get':\n return json({ workspace })\n case 'turns.start': {\n const input = parseStartTurn(await readJsonBody(request))\n return json({ turn: await adapter.startTurn(ctx, workspace, input) }, 202)\n }\n case 'threads.list': {\n if (!adapter.listThreads) unsupported()\n const limit = boundedInteger(url.searchParams.get('limit'), 25, 1, 100, 'limit')\n const cursor = url.searchParams.get('cursor') ?? undefined\n if (cursor !== undefined && (cursor.length > 512 || !cursor)) {\n throw new OperatorError('operator.invalid_input', 400, 'cursor is invalid')\n }\n return json(await adapter.listThreads(ctx, workspace, { limit, ...(cursor ? { cursor } : {}) }))\n }\n case 'threads.get': {\n if (!adapter.getThread) unsupported()\n const found = await adapter.getThread(ctx, workspace, params.threadId!)\n if (!found) throw new OperatorError('operator.thread_not_found', 404, 'Thread not found')\n return json(found)\n }\n case 'turns.get': {\n const target = { threadId: params.threadId!, turnId: params.turnId! }\n const wait = boundedInteger(url.searchParams.get('wait'), 0, 0, maxWaitSeconds, 'wait')\n const deadline = Date.now() + wait * 1000\n let turn = await adapter.getTurn(ctx, workspace, target)\n while (turn && !needsAttention(turn) && Date.now() + pollIntervalMs <= deadline && !request.signal.aborted) {\n await sleep(pollIntervalMs, request.signal)\n turn = await adapter.getTurn(ctx, workspace, target)\n }\n if (!turn) throw new OperatorError('operator.turn_not_found', 404, 'Turn not found')\n return json({ turn })\n }\n case 'approvals.list':\n return json({ approvals: await adapter.listApprovals(ctx, workspace) })\n case 'journal.list': {\n if (!adapter.listJournal) unsupported()\n const days = boundedInteger(url.searchParams.get('days'), 7, 1, 90, 'days')\n return json({ entries: await adapter.listJournal(ctx, workspace, { days }) })\n }\n case 'files.list': {\n if (!adapter.listFiles) unsupported()\n const prefix = relativePath(url.searchParams.get('prefix'), 'prefix', { allowEmpty: true })\n return json({ files: await adapter.listFiles(ctx, workspace, { prefix }) })\n }\n case 'files.read': {\n if (!adapter.readFile) unsupported()\n const path = relativePath(url.searchParams.get('path'), 'path', { allowEmpty: false })\n const file = await adapter.readFile(ctx, workspace, path)\n if (!file) throw new OperatorError('operator.file_not_found', 404, 'File not found')\n return json({ file })\n }\n case 'assets.read': {\n if (!adapter.readAsset) unsupported()\n const response = await adapter.readAsset(ctx, workspace, params.assetId!)\n if (!response) throw new OperatorError('operator.asset_not_found', 404, 'Asset not found')\n const headers = new Headers(response.headers)\n headers.set('cache-control', 'private, no-store')\n headers.delete('set-cookie')\n return new Response(response.body, { status: response.status, headers })\n }\n case 'scorecard.get': {\n if (!adapter.getScorecard) unsupported()\n const days = boundedInteger(url.searchParams.get('days'), 7, 1, 90, 'days')\n return json({ scorecard: await adapter.getScorecard(ctx, workspace, { days }) })\n }\n }\n return refusal(404, 'operator.route_not_found', 'No operator route matches this method and path')\n } catch (error) {\n if (error instanceof OperatorError) return refusal(error.status, error.code, error.message, error.retryable)\n if (error instanceof Response) return error\n options.onError?.(error, route.id)\n return refusal(500, 'operator.internal', 'The operation failed', true)\n }\n }\n\n return { matches, handle }\n}\n","import { createApiKeyFetch } from '../web/api-key-fetch.js'\nimport {\n OPERATOR_API_BASE_PATH,\n type OperatorApproval,\n type OperatorAppInfo,\n type OperatorErrorBody,\n type OperatorFile,\n type OperatorFileEntry,\n type OperatorJournalEntry,\n type OperatorPrincipalInfo,\n type OperatorScorecard,\n type OperatorThread,\n type OperatorTurn,\n type OperatorWorkspace,\n type StartTurnInput,\n} from './contract.js'\n\nexport type OperatorResult<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; status: number; code: string; error: string; retryable: boolean }\n\nexport interface OperatorClientOptions {\n /** The app's origin, such as `https://gtm.tangle.tools`. */\n origin: string\n /** Resolve the key from trusted secret storage on each request; never from model output. */\n getApiKey(): string | Promise<string>\n fetchImpl?: typeof fetch\n allowHttpLoopback?: boolean\n basePath?: string\n}\n\nexport interface WaitForTurnOptions {\n /** Total time to follow the turn. */\n timeoutMs: number\n /** Server hold per request, in seconds; the server caps it. */\n waitSeconds?: number\n signal?: AbortSignal\n}\n\nexport interface OperatorClient {\n describe(): Promise<OperatorResult<{ app: OperatorAppInfo; principal: OperatorPrincipalInfo }>>\n listWorkspaces(): Promise<OperatorResult<OperatorWorkspace[]>>\n createWorkspace(input: { name: string }): Promise<OperatorResult<OperatorWorkspace>>\n getWorkspace(workspaceId: string): Promise<OperatorResult<OperatorWorkspace>>\n /** Omitting turnId generates one; retry with the returned turn's id. */\n startTurn(workspaceId: string, input: Omit<StartTurnInput, 'turnId'> & { turnId?: string }): Promise<OperatorResult<OperatorTurn>>\n getTurn(workspaceId: string, threadId: string, turnId: string, options?: { waitSeconds?: number; signal?: AbortSignal }): Promise<OperatorResult<OperatorTurn>>\n /** Follow until the turn settles, waits on a decision, or the timeout passes; the last read is returned. */\n waitForTurn(workspaceId: string, threadId: string, turnId: string, options: WaitForTurnOptions): Promise<OperatorResult<OperatorTurn>>\n listThreads(workspaceId: string, query?: { cursor?: string; limit?: number }): Promise<OperatorResult<{ threads: OperatorThread[]; nextCursor: string | null }>>\n getThread(workspaceId: string, threadId: string): Promise<OperatorResult<{ thread: OperatorThread; latestTurn: OperatorTurn | null }>>\n listApprovals(workspaceId: string): Promise<OperatorResult<OperatorApproval[]>>\n listJournal(workspaceId: string, query?: { days?: number }): Promise<OperatorResult<OperatorJournalEntry[]>>\n listFiles(workspaceId: string, query?: { prefix?: string }): Promise<OperatorResult<OperatorFileEntry[]>>\n readFile(workspaceId: string, path: string): Promise<OperatorResult<OperatorFile>>\n /** The raw asset response; the caller reads and bounds its body. */\n readAsset(workspaceId: string, assetId: string, init?: { signal?: AbortSignal }): Promise<OperatorResult<Response>>\n getScorecard(workspaceId: string, query?: { days?: number }): Promise<OperatorResult<OperatorScorecard>>\n}\n\nfunction segment(value: string): string {\n return encodeURIComponent(value)\n}\n\nasync function failure(response: Response): Promise<OperatorResult<never>> {\n let body: Partial<OperatorErrorBody> = {}\n try { body = await response.json() as Partial<OperatorErrorBody> } catch { /* A non-JSON refusal keeps its status. */ }\n return {\n succeeded: false,\n status: response.status,\n code: typeof body.code === 'string' ? body.code : `http_${response.status}`,\n error: typeof body.error === 'string' ? body.error : `HTTP ${response.status}`,\n retryable: body.retryable === true || response.status === 429 || response.status >= 500,\n }\n}\n\nfunction transportFailure(error: unknown): OperatorResult<never> {\n const aborted = error instanceof Error && error.name === 'AbortError'\n return {\n succeeded: false,\n status: 0,\n code: aborted ? 'operator.aborted' : 'operator.transport_failed',\n // createApiKeyFetch never forwards transport diagnostics, which could carry the key.\n error: error instanceof Error ? error.message : 'Operator transport failed',\n retryable: !aborted,\n }\n}\n\nconst SETTLED = new Set(['succeeded', 'failed', 'input-required'])\n\n/** A typed client for one app's operator API. */\nexport function createOperatorClient(options: OperatorClientOptions): OperatorClient {\n const basePath = (options.basePath ?? OPERATOR_API_BASE_PATH).replace(/\\/$/, '')\n const request = createApiKeyFetch({\n origin: options.origin,\n getApiKey: options.getApiKey,\n ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),\n ...(options.allowHttpLoopback ? { allowHttpLoopback: true } : {}),\n })\n\n async function call<T>(\n path: string,\n pick: (body: Record<string, unknown>) => T,\n init: RequestInit = {},\n ): Promise<OperatorResult<T>> {\n let response: Response\n try {\n response = await request(`${basePath}${path}`, {\n ...init,\n headers: { accept: 'application/json', ...(init.body ? { 'content-type': 'application/json' } : {}) },\n })\n } catch (error) {\n return transportFailure(error)\n }\n if (!response.ok) return failure(response)\n try {\n return { succeeded: true, value: pick(await response.json() as Record<string, unknown>) }\n } catch {\n return { succeeded: false, status: response.status, code: 'operator.invalid_response', error: 'The response was not the expected JSON', retryable: true }\n }\n }\n\n const query = (params: Record<string, string | number | undefined>) => {\n const search = new URLSearchParams()\n for (const [name, value] of Object.entries(params)) if (value !== undefined && value !== '') search.set(name, String(value))\n const text = search.toString()\n return text ? `?${text}` : ''\n }\n\n const client: OperatorClient = {\n describe: () => call('', (body) => body as unknown as { app: OperatorAppInfo; principal: OperatorPrincipalInfo }),\n listWorkspaces: () => call('/workspaces', (body) => body.workspaces as OperatorWorkspace[]),\n createWorkspace: (input) => call('/workspaces', (body) => body.workspace as OperatorWorkspace, {\n method: 'POST', body: JSON.stringify({ name: input.name }),\n }),\n getWorkspace: (workspaceId) => call(`/workspaces/${segment(workspaceId)}`, (body) => body.workspace as OperatorWorkspace),\n startTurn: (workspaceId, input) => call(`/workspaces/${segment(workspaceId)}/turns`, (body) => body.turn as OperatorTurn, {\n method: 'POST', body: JSON.stringify({ ...input, turnId: input.turnId ?? crypto.randomUUID() }),\n }),\n getTurn: (workspaceId, threadId, turnId, opts = {}) => call(\n `/workspaces/${segment(workspaceId)}/threads/${segment(threadId)}/turns/${segment(turnId)}${query({ wait: opts.waitSeconds })}`,\n (body) => body.turn as OperatorTurn,\n opts.signal ? { signal: opts.signal } : {},\n ),\n async waitForTurn(workspaceId, threadId, turnId, opts) {\n const deadline = Date.now() + opts.timeoutMs\n const waitSeconds = opts.waitSeconds ?? 25\n let last: OperatorResult<OperatorTurn> | null = null\n while (true) {\n const remaining = Math.floor((deadline - Date.now()) / 1000)\n const started = Date.now()\n last = await client.getTurn(workspaceId, threadId, turnId, {\n waitSeconds: Math.max(0, Math.min(waitSeconds, remaining)),\n ...(opts.signal ? { signal: opts.signal } : {}),\n })\n if (!last.succeeded) {\n if (!last.retryable || Date.now() >= deadline || opts.signal?.aborted) return last\n await new Promise((resolve) => setTimeout(resolve, Math.min(5_000, Math.max(0, deadline - Date.now()))))\n continue\n }\n if (SETTLED.has(last.value.state) || last.value.approvals.length > 0) return last\n if (Date.now() >= deadline || opts.signal?.aborted) return last\n // A server that does not hold the read still gets a bounded request rate.\n if (Date.now() - started < 1_000) {\n await new Promise((resolve) => setTimeout(resolve, Math.min(2_000, Math.max(0, deadline - Date.now()))))\n }\n }\n },\n listThreads: (workspaceId, q = {}) => call(\n `/workspaces/${segment(workspaceId)}/threads${query({ cursor: q.cursor, limit: q.limit })}`,\n (body) => ({ threads: body.threads as OperatorThread[], nextCursor: (body.nextCursor as string | null) ?? null }),\n ),\n getThread: (workspaceId, threadId) => call(\n `/workspaces/${segment(workspaceId)}/threads/${segment(threadId)}`,\n (body) => ({ thread: body.thread as OperatorThread, latestTurn: (body.latestTurn as OperatorTurn | null) ?? null }),\n ),\n listApprovals: (workspaceId) => call(`/workspaces/${segment(workspaceId)}/approvals`, (body) => body.approvals as OperatorApproval[]),\n listJournal: (workspaceId, q = {}) => call(\n `/workspaces/${segment(workspaceId)}/journal${query({ days: q.days })}`,\n (body) => body.entries as OperatorJournalEntry[],\n ),\n listFiles: (workspaceId, q = {}) => call(\n `/workspaces/${segment(workspaceId)}/files${query({ prefix: q.prefix })}`,\n (body) => body.files as OperatorFileEntry[],\n ),\n readFile: (workspaceId, path) => call(\n `/workspaces/${segment(workspaceId)}/file${query({ path })}`,\n (body) => body.file as OperatorFile,\n ),\n async readAsset(workspaceId, assetId, init = {}) {\n let response: Response\n try {\n response = await request(`${basePath}/workspaces/${segment(workspaceId)}/assets/${segment(assetId)}`, init.signal ? { signal: init.signal } : {})\n } catch (error) {\n return transportFailure(error)\n }\n if (!response.ok) return failure(response)\n return { succeeded: true, value: response }\n },\n getScorecard: (workspaceId, q = {}) => call(\n `/workspaces/${segment(workspaceId)}/scorecard${query({ days: q.days })}`,\n (body) => body.scorecard as OperatorScorecard,\n ),\n }\n return client\n}\n","import { messageHasTurnId } from '../stream/turn-identity.js'\nimport type {\n OperatorApproval,\n OperatorAssetRef,\n OperatorFile,\n OperatorFileChange,\n OperatorFileEntry,\n OperatorJournalEntry,\n OperatorRole,\n OperatorScorecard,\n OperatorThread,\n OperatorTurn,\n OperatorTurnState,\n OperatorWorkspace,\n StartTurnInput,\n} from './contract.js'\nimport { OperatorError, type OperatorAdapter, type OperatorContext } from './server.js'\n\n/** The message fields the adapter reads; agent-app's chat-store rows satisfy it. */\nexport interface ChatOperatorMessage {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n /** Typed chat parts, such as chat-store's, are read field by field. */\n parts: ReadonlyArray<object> | null\n createdAt?: Date | string | number | null\n model?: string | null\n servedModel?: string | null\n costUsd?: number | null\n /** The row's product metadata, for a `failureOf` that reads a stamped turn status. */\n metadata?: Record<string, unknown> | null\n}\n\nexport interface ChatOperatorThread {\n id: string\n workspaceId: string\n title: string\n updatedAt?: Date | string | number | null\n}\n\nexport interface ChatOperatorWorkspace {\n id: string\n name: string\n role?: OperatorRole\n createdAt?: Date | string | number | null\n}\n\n/** A durable execution claim, separate from the chat transcript that owns results. */\nexport interface ChatOperatorAdmission {\n threadId: string\n /** Only an explicit refusal before a usable stream opened is recorded here. */\n refusal?: { code?: string; message: string }\n}\n\n/** Product-owned atomic storage for duplicate suppression across processes.\n * A claim with no transcript stays unknown after a crash; it never licenses a\n * second execution. This is not a completion ledger or an automatic retry queue.\n */\nexport interface ChatOperatorAdmissionStore<Identity> {\n get(ctx: OperatorContext<Identity>, workspaceId: string, turnId: string): Promise<ChatOperatorAdmission | null>\n claim(ctx: OperatorContext<Identity>, input: { workspaceId: string; threadId: string; turnId: string }): Promise<boolean>\n recordRefusal(ctx: OperatorContext<Identity>, input: { workspaceId: string; threadId: string; turnId: string }, refusal: { code?: string; message: string }): Promise<void>\n}\n\nexport interface ChatOperatorAdapterOptions<Identity> {\n /** Optional durable claims; without one, transcript retry detection is not an atomic admission lock. */\n admissions?: ChatOperatorAdmissionStore<Identity>\n /** The app's public origin, the base of every browser link. */\n origin: string\n /** Browser path of a conversation; defaults to `/app/<workspace>/chat/<thread>`. */\n threadPath?(workspaceId: string, threadId: string): string\n /** Browser path of a workspace; defaults to `/app/<workspace>`. */\n workspacePath?(workspaceId: string): string\n workspaces: {\n /** Apply the app's roles: `read` needs viewer access, `run` the role that may start agent work. */\n authorize(ctx: OperatorContext<Identity>, workspaceId: string, access: 'read' | 'run'): Promise<ChatOperatorWorkspace | null>\n list(ctx: OperatorContext<Identity>): Promise<ChatOperatorWorkspace[]>\n create?(ctx: OperatorContext<Identity>, input: { name: string }): Promise<ChatOperatorWorkspace>\n }\n threads: {\n /**\n * A thread this caller may read, or null. An app whose conversations\n * belong to one user (not the whole workspace) applies that here.\n */\n get(threadId: string, ctx: OperatorContext<Identity>): Promise<ChatOperatorThread | null>\n /** Insert a thread with this id for this caller; agent-app's chat store accepts a caller-assigned id. */\n create(ctx: OperatorContext<Identity>, input: { id: string; workspaceId: string; title: string }): Promise<ChatOperatorThread>\n /** Oldest first. */\n listMessages(threadId: string, ctx: OperatorContext<Identity>): Promise<ChatOperatorMessage[]>\n list?(ctx: OperatorContext<Identity>, workspaceId: string, query: { cursor?: string; limit: number }): Promise<{ threads: ChatOperatorThread[]; nextCursor: string | null }>\n }\n /**\n * Run one turn through the app's own chat route as this caller. The adapter\n * reads the response to its end, so the turn is driven exactly as a browser\n * holding its stream drives it, and the start request returns once it settles.\n */\n runTurn(ctx: OperatorContext<Identity>, input: { workspaceId: string; threadId: string; turnId: string; content: string; model?: string }): Promise<Response>\n /** Optional authoritative status of this user-turn anchor. When supplied,\n * missing/running status without a live lease never promotes a partial reply to success. */\n turnStatus?(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string, user: ChatOperatorMessage): Promise<'running' | 'complete' | 'error' | null>\n /** Turn ids still running on a thread, or null when the app cannot tell. */\n runningTurns?(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string): Promise<string[] | null>\n /** Decisions open in the workspace, or on one conversation when `threadId` is set. */\n approvals?(ctx: OperatorContext<Identity>, workspaceId: string, threadId?: string): Promise<OperatorApproval[]>\n files?: {\n list(ctx: OperatorContext<Identity>, workspaceId: string, prefix: string): Promise<OperatorFileEntry[]>\n read(ctx: OperatorContext<Identity>, workspaceId: string, path: string): Promise<OperatorFile | null>\n }\n journal?(ctx: OperatorContext<Identity>, workspaceId: string, days: number): Promise<OperatorJournalEntry[]>\n assets?: {\n /** Assets a reply references; defaults to none. */\n refs?(message: ChatOperatorMessage, workspaceId: string): OperatorAssetRef[]\n read?(ctx: OperatorContext<Identity>, workspaceId: string, assetId: string): Promise<Response | null>\n }\n scorecard?(ctx: OperatorContext<Identity>, workspaceId: string, days: number): Promise<OperatorScorecard>\n /**\n * A reply's failure. Defaults to an `error` notice (the sandbox producer's\n * `errorNotice` mode), a `turn-failure` notice or an `error` part.\n */\n failureOf?(message: ChatOperatorMessage): { code?: string; message: string } | null\n}\n\nconst iso = (value: Date | string | number | null | undefined): string | undefined => {\n if (value === null || value === undefined) return undefined\n const date = value instanceof Date ? value : new Date(typeof value === 'number' && value < 1e12 ? value * 1000 : value)\n return Number.isNaN(date.getTime()) ? undefined : date.toISOString()\n}\n\nconst text = (value: unknown): string | null => (typeof value === 'string' && value.trim() ? value : null)\n\nfunction partsOf(message: ChatOperatorMessage): ReadonlyArray<Record<string, unknown>> {\n return (message.parts ?? []) as ReadonlyArray<Record<string, unknown>>\n}\n\nfunction defaultFailure(message: ChatOperatorMessage): { code?: string; message: string } | null {\n for (const part of partsOf(message)) {\n const errorNotice = part.type === 'notice' && part.noticeKind === 'error'\n const notice = errorNotice || (part.type === 'notice' && (part.noticeKind === 'turn-failure' || part.kind === 'turn-failure'))\n if (!notice && part.type !== 'error') continue\n const detail = text(part.message) ?? text(part.text) ?? text(part.error) ?? 'The turn failed'\n // An error notice always names a reason, so a caller can tell failures apart.\n const code = typeof part.code === 'string' ? part.code : errorNotice ? 'turn.error' : undefined\n return { ...(code ? { code } : {}), message: detail }\n }\n return null\n}\n\nfunction fileChanges(message: ChatOperatorMessage): OperatorFileChange[] {\n const files: OperatorFileChange[] = []\n for (const part of partsOf(message)) {\n if (part.type !== 'session-artifact' || typeof part.path !== 'string') continue\n const action = part.action === 'created' || part.action === 'updated' || part.action === 'deleted' ? part.action : 'changed'\n if (!files.some((file) => file.path === part.path)) files.push({ path: part.path, action })\n }\n return files\n}\n\nfunction turnIdOf(message: ChatOperatorMessage): string | null {\n for (const part of partsOf(message)) {\n if (typeof part.turnId === 'string' && part.turnId) return part.turnId\n }\n return null\n}\n\nasync function sha256Hex(value: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nasync function drain(response: Response, signal: AbortSignal): Promise<void> {\n if (!response.body) return\n const reader = response.body.getReader()\n const stop = () => { void reader.cancel().catch(() => {}) }\n signal.addEventListener('abort', stop, { once: true })\n try {\n while (!(await reader.read()).done) { /* The turn is driven by reading its stream. */ }\n } finally {\n signal.removeEventListener('abort', stop)\n reader.releaseLock()\n }\n}\n\n/**\n * The operator adapter for an app built on agent-app's chat stack: threads and\n * messages from its chat store, turns through its own chat route. The app\n * supplies its workspace roles and, optionally, files, journal, approvals,\n * assets, and a scorecard.\n */\nexport function createChatOperatorAdapter<Identity>(options: ChatOperatorAdapterOptions<Identity>): OperatorAdapter<Identity> {\n const origin = options.origin.replace(/\\/$/, '')\n const threadUrl = (workspaceId: string, threadId: string) =>\n origin + (options.threadPath?.(workspaceId, threadId) ?? `/app/${workspaceId}/chat/${threadId}`)\n const workspaceView = (workspace: ChatOperatorWorkspace): OperatorWorkspace => ({\n id: workspace.id,\n name: workspace.name,\n ...(workspace.role ? { role: workspace.role } : {}),\n url: origin + (options.workspacePath?.(workspace.id) ?? `/app/${workspace.id}`),\n ...(iso(workspace.createdAt) ? { createdAt: iso(workspace.createdAt)! } : {}),\n })\n const threadView = (thread: ChatOperatorThread): OperatorThread => ({\n id: thread.id, title: thread.title, url: threadUrl(thread.workspaceId, thread.id),\n ...(iso(thread.updatedAt) ? { updatedAt: iso(thread.updatedAt)! } : {}),\n })\n\n async function threadIn(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string): Promise<ChatOperatorThread | null> {\n const thread = await options.threads.get(threadId, ctx)\n return thread && thread.workspaceId === workspaceId ? thread : null\n }\n\n async function turnOf(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string, turnId: string): Promise<OperatorTurn | null> {\n const messages = await options.threads.listMessages(threadId, ctx)\n const userIndex = messages.findIndex((message) => message.role === 'user' && messageHasTurnId(message, turnId))\n if (userIndex < 0) return null\n const later = messages.slice(userIndex + 1)\n const nextUser = later.findIndex((message) => message.role === 'user')\n const window = nextUser < 0 ? later : later.slice(0, nextUser)\n const assistant = [...window].reverse().find((message) => message.role === 'assistant') ?? null\n const latest = nextUser < 0\n const running = latest && options.runningTurns ? await options.runningTurns(ctx, workspaceId, threadId) : []\n const status = options.turnStatus ? await options.turnStatus(ctx, workspaceId, threadId, messages[userIndex]!) : undefined\n const terminal = status === undefined || status === 'complete' || status === 'error'\n const turn: OperatorTurn = {\n workspaceId, threadId, turnId, state: 'unknown', url: threadUrl(workspaceId, threadId),\n reply: null, failure: null, assets: [], files: [], approvals: [],\n }\n let state: OperatorTurnState = running === null ? 'unknown' : running.length > 0 ? 'working' : 'unknown'\n if (assistant) {\n turn.files = fileChanges(assistant)\n turn.assets = options.assets?.refs?.(assistant, workspaceId) ?? []\n const model = assistant.servedModel ?? assistant.model\n if (model) turn.model = model\n if (typeof assistant.costUsd === 'number') turn.costUsd = assistant.costUsd\n if (state !== 'working' && terminal) {\n const failure = (options.failureOf ?? defaultFailure)(assistant) ?? (status === 'error' ? { code: 'turn.error', message: 'The turn failed; retained text may be partial' } : null)\n if (failure) {\n state = 'failed'\n turn.failure = failure\n } else if (assistant.content.trim()) {\n state = 'succeeded'\n turn.reply = { content: assistant.content, mediaType: 'text/markdown' }\n }\n const completedAt = iso(assistant.createdAt)\n if (completedAt && (state === 'succeeded' || state === 'failed')) turn.completedAt = completedAt\n }\n } else if (state !== 'working' && terminal && (!latest || status === 'error' || status === 'complete')) {\n state = 'failed'\n turn.failure = { code: status === 'error' ? 'turn.error' : 'turn.no_reply', message: 'The turn ended without a reply' }\n }\n turn.state = state\n if (state !== 'succeeded' && state !== 'failed' && options.approvals) {\n turn.approvals = await options.approvals(ctx, workspaceId, threadId)\n }\n return turn\n }\n\n async function admittedTurn(ctx: OperatorContext<Identity>, workspaceId: string, turnId: string, admission: ChatOperatorAdmission): Promise<OperatorTurn> {\n if (!(await threadIn(ctx, workspaceId, admission.threadId))) throw new OperatorError('operator.turn_identity_conflict', 409)\n const persisted = await turnOf(ctx, workspaceId, admission.threadId, turnId)\n if (persisted) return persisted\n return {\n workspaceId, threadId: admission.threadId, turnId, state: admission.refusal ? 'failed' : 'unknown',\n url: threadUrl(workspaceId, admission.threadId), reply: null, failure: admission.refusal ?? null,\n assets: [], files: [], approvals: [],\n }\n }\n\n async function ensureThread(ctx: OperatorContext<Identity>, workspaceId: string, input: StartTurnInput): Promise<string> {\n const id = (await sha256Hex(`operator-thread\\0${ctx.key.keyId}\\0${workspaceId}\\0${input.turnId}`)).slice(0, 32)\n const existing = await options.threads.get(id, ctx)\n if (existing) {\n if (existing.workspaceId !== workspaceId) throw new OperatorError('operator.turn_identity_conflict', 409, 'This turn id belongs to another conversation')\n return id\n }\n const title = (input.title ?? input.content.split('\\n').find((line) => line.trim()) ?? 'New conversation').trim().slice(0, 80)\n try {\n await options.threads.create(ctx, { id, workspaceId, title })\n } catch (error) {\n // A concurrent retry may have inserted the same id first.\n if (!(await threadIn(ctx, workspaceId, id))) throw error\n }\n return id\n }\n\n const adapter: OperatorAdapter<Identity> = {\n async authorizeWorkspace(ctx, workspaceId, access) {\n const workspace = await options.workspaces.authorize(ctx, workspaceId, access)\n return workspace ? workspaceView(workspace) : null\n },\n async listWorkspaces(ctx) {\n return (await options.workspaces.list(ctx)).map(workspaceView)\n },\n async startTurn(ctx, workspace, input) {\n const priorAdmission = await options.admissions?.get(ctx, workspace.id, input.turnId)\n if (priorAdmission) {\n if (input.threadId && input.threadId !== priorAdmission.threadId) throw new OperatorError('operator.turn_identity_conflict', 409)\n return admittedTurn(ctx, workspace.id, input.turnId, priorAdmission)\n }\n let threadId = input.threadId\n if (threadId === undefined) {\n threadId = await ensureThread(ctx, workspace.id, input)\n } else if (!(await threadIn(ctx, workspace.id, threadId))) {\n throw new OperatorError('operator.thread_not_found', 404, 'Thread not found')\n }\n // A retried start whose turn is already recorded reads it instead of running again.\n const existing = await turnOf(ctx, workspace.id, threadId, input.turnId)\n if (existing) return existing\n const target = { workspaceId: workspace.id, threadId, turnId: input.turnId }\n if (options.admissions && !(await options.admissions.claim(ctx, target))) {\n const raced = await options.admissions.get(ctx, workspace.id, input.turnId)\n if (!raced) throw new OperatorError('operator.admission_unavailable', 503, 'Admission state is unavailable', true)\n if (raced.threadId !== threadId) throw new OperatorError('operator.turn_identity_conflict', 409)\n return admittedTurn(ctx, workspace.id, input.turnId, raced)\n }\n const response = await options.runTurn(ctx, {\n workspaceId: workspace.id, threadId, turnId: input.turnId, content: input.content,\n ...(input.model === undefined ? {} : { model: input.model }),\n })\n if (!response.ok) {\n const body = await response.json().catch(() => ({})) as { error?: unknown; code?: unknown }\n // Apps answer `{ error, code }` or `{ error: { code, message } }`.\n const nested = body.error && typeof body.error === 'object' ? body.error as { code?: unknown; message?: unknown } : {}\n const failure = {\n code: typeof body.code === 'string' ? body.code : typeof nested.code === 'string' ? nested.code : `operator.chat_${response.status}`,\n message: typeof body.error === 'string' ? body.error : typeof nested.message === 'string' ? nested.message : 'The app refused the turn',\n }\n if (options.admissions) {\n await options.admissions.recordRefusal(ctx, target, failure)\n return admittedTurn(ctx, workspace.id, input.turnId, { threadId, refusal: failure })\n }\n throw new OperatorError(failure.code, response.status, failure.message, response.status === 429 || response.status >= 500)\n }\n await drain(response, ctx.request.signal)\n return await turnOf(ctx, workspace.id, threadId, input.turnId) ?? {\n workspaceId: workspace.id, threadId, turnId: input.turnId, state: 'unknown', url: threadUrl(workspace.id, threadId),\n reply: null, failure: null, assets: [], files: [], approvals: [],\n }\n },\n async getTurn(ctx, workspace, target) {\n if (!(await threadIn(ctx, workspace.id, target.threadId))) return null\n const persisted = await turnOf(ctx, workspace.id, target.threadId, target.turnId)\n if (persisted) return persisted\n const admission = await options.admissions?.get(ctx, workspace.id, target.turnId)\n return admission?.threadId === target.threadId ? admittedTurn(ctx, workspace.id, target.turnId, admission) : null\n },\n async getThread(ctx, workspace, threadId) {\n const thread = await threadIn(ctx, workspace.id, threadId)\n if (!thread) return null\n const messages = await options.threads.listMessages(threadId, ctx)\n const lastUser = [...messages].reverse().find((message) => message.role === 'user')\n const turnId = lastUser ? turnIdOf(lastUser) : null\n return { thread: threadView(thread), latestTurn: turnId ? await turnOf(ctx, workspace.id, threadId, turnId) : null }\n },\n listApprovals: (ctx, workspace) => options.approvals ? options.approvals(ctx, workspace.id) : Promise.resolve([]),\n }\n if (options.workspaces.create) {\n const create = options.workspaces.create\n adapter.createWorkspace = async (ctx, input) => workspaceView(await create(ctx, input))\n }\n if (options.threads.list) {\n const list = options.threads.list\n adapter.listThreads = async (ctx, workspace, query) => {\n const page = await list(ctx, workspace.id, query)\n return { threads: page.threads.map(threadView), nextCursor: page.nextCursor }\n }\n }\n if (options.files) {\n const files = options.files\n adapter.listFiles = (ctx, workspace, query) => files.list(ctx, workspace.id, query.prefix)\n adapter.readFile = (ctx, workspace, path) => files.read(ctx, workspace.id, path)\n }\n if (options.journal) {\n const journal = options.journal\n adapter.listJournal = (ctx, workspace, query) => journal(ctx, workspace.id, query.days)\n }\n if (options.assets?.read) {\n const read = options.assets.read\n adapter.readAsset = (ctx, workspace, assetId) => read(ctx, workspace.id, assetId)\n }\n if (options.scorecard) {\n const scorecard = options.scorecard\n adapter.getScorecard = (ctx, workspace, query) => scorecard(ctx, workspace.id, query.days)\n }\n return adapter\n}\n","/**\n * Let the operator API accept a Platform agent key next to the app's own keys.\n *\n * `withPlatformAgentKeys` wraps the app's existing `OperatorKeyStore`: a\n * Bearer key starting `sk-tan-` is verified at Platform for this app's\n * product (see `platform/agent-keys.ts`); every other key goes to the app's\n * own store unchanged, so existing `gak_` keys keep working.\n *\n * Authority for a Platform agent key:\n * - Which actions: only the key's `<product>:operator:read|write|run` scopes\n * (or `*`), mapped to `operator:read|write|run`. A key without any is\n * refused, so a key approved for other products cannot reach this API.\n * - Which user: the key owner's Tangle identity (stable Platform user id and\n * verified email), resolved through the app's own `TangleSsoAccountStore`,\n * the same account policy browser SSO applies. A first call creates the app\n * user exactly as a first browser sign-in would, and links it when the store\n * keeps links per user; an ambiguous or conflicting identity is refused.\n * Nothing in the request names the user.\n * - Which credential: `loadIdentity` receives the agent key for the request,\n * so the app acts with it and the work spends the key's one cap. It is held\n * only for the request, never on the key object.\n * - Which workspaces: the app's ordinary roles, through the operator\n * adapter's `authorizeWorkspace`. The key confers no workspace access.\n */\nimport {\n agentOperatorScopes,\n PLATFORM_KEY_PREFIX,\n type PlatformAgentKeyVerifier,\n} from '../platform/agent-keys.js'\nimport type { RequestApiKey } from '../platform/api-key-auth.js'\nimport type { TangleSsoAccountStore } from '../platform/sso.js'\nimport type { OperatorKeyStore } from './server.js'\n\nconst OPERATOR_ACTIONS = ['read', 'write', 'run'] as const\n\nexport interface PlatformAgentKeyAccess<Identity> {\n verifier: PlatformAgentKeyVerifier\n /** This app's Platform product id, e.g. `gtm-agent`. */\n product: string\n /**\n * The app's browser-SSO account store. Give `saveTangleLink` only when the\n * store keeps the Platform link per user (a link table): a first call then\n * links the user with the agent key, as a first sign-in would. A store that\n * keeps the key on the session row has no session to bind here; omit it and\n * act with `context.platformApiKey` instead.\n */\n accounts: Pick<TangleSsoAccountStore, 'resolveAccount' | 'upsertUserByEmail'>\n & Partial<Pick<TangleSsoAccountStore, 'saveTangleLink'>>\n /**\n * Load the operator identity for an app user id. `context.platformApiKey` is\n * the agent key this request came with: put it on the identity and use it as\n * the request's Platform credential. Never store or log it.\n */\n loadIdentity(userId: string, context: PlatformAgentKeyContext): Promise<Identity | null>\n /** Optional per-key request quota; by default every verified request is admitted. */\n claimRequest?(keyId: string, requestId: string): Promise<{ allowed: boolean; retryAfterSeconds?: number }>\n}\n\n/** What `loadIdentity` receives for a request made with a Platform agent key. */\nexport interface PlatformAgentKeyContext {\n /** The agent key itself, for this request only. */\n platformApiKey: string\n}\n\n/** An operator key that came from Platform agent signup. */\nexport interface PlatformAgentOperatorKey extends RequestApiKey {\n source: 'platform-agent'\n}\n\nfunction refused(status: number, code: string, error: string): Response {\n return Response.json({ error, code }, { status, headers: { 'cache-control': 'private, no-store' } })\n}\n\nfunction isPlatformAgentKey(key: RequestApiKey): key is PlatformAgentOperatorKey {\n return (key as Partial<PlatformAgentOperatorKey>).source === 'platform-agent'\n}\n\nexport function withPlatformAgentKeys<Key extends RequestApiKey, Identity>(\n appKeys: OperatorKeyStore<Key, Identity>,\n access: PlatformAgentKeyAccess<Identity>,\n): OperatorKeyStore<Key | PlatformAgentOperatorKey, Identity> {\n // The raw key for each verified key object, for this request's identity.\n const rawKeys = new WeakMap<PlatformAgentOperatorKey, string>()\n\n async function provisionOwner(verified: { platformUserId: string; email: string; name: string | null }, rawKey: string): Promise<string> {\n const resolution = await access.accounts.resolveAccount({ email: verified.email, platformUserId: verified.platformUserId })\n if (resolution.kind === 'reject') {\n throw refused(403, 'agent_key.account_conflict', `The key owner's Tangle identity conflicts with an account in this app (${resolution.reason}).`)\n }\n // A user already linked to this Platform identity is used as it is: its\n // profile and link belong to the owner's own sign-ins.\n if (resolution.kind === 'existing' && resolution.matchedBy === 'platform-id') return resolution.userId\n // A new or email-matched user is created now, the way a first browser\n // sign-in would. A link-table store also links it, with the agent key as\n // the credential the app acts with later; spend then counts against the\n // key's one cap.\n const { userId } = await access.accounts.upsertUserByEmail({\n email: verified.email,\n name: verified.name,\n tangleUserId: verified.platformUserId,\n resolution,\n })\n await access.accounts.saveTangleLink?.({\n userId,\n sessionToken: '',\n tangleUserId: verified.platformUserId,\n email: verified.email,\n name: verified.name,\n apiKey: rawKey,\n planTier: null,\n })\n return userId\n }\n\n return {\n async verify(authorization) {\n const raw = /^Bearer +(\\S+)$/i.exec(authorization)?.[1]\n if (!raw?.startsWith(PLATFORM_KEY_PREFIX)) return appKeys.verify(authorization)\n const verified = await access.verifier.verify(raw)\n if (!verified.ok) throw refused(verified.status, verified.code, verified.message)\n const scopes = agentOperatorScopes(verified.scopes, access.product, OPERATOR_ACTIONS)\n if (scopes.length === 0) {\n throw refused(403, 'agent_key.product_not_granted', \"The key's owner did not approve it for this app.\")\n }\n const ownerId = await provisionOwner(verified, raw)\n const key: PlatformAgentOperatorKey = {\n keyId: verified.keyId,\n ownerId,\n scopes,\n // The operator API requires a finite expiry. This one bounds the\n // request being authorized; the next request asks Platform again once\n // the verifier's short cache lapses.\n expiresAt: Math.max(verified.validUntil, Date.now() + 30_000),\n source: 'platform-agent',\n }\n rawKeys.set(key, raw)\n return key\n },\n async resolveIdentity(key) {\n if (!isPlatformAgentKey(key)) return appKeys.resolveIdentity(key as Key)\n const platformApiKey = rawKeys.get(key)\n return platformApiKey ? access.loadIdentity(key.ownerId, { platformApiKey }) : null\n },\n async claimRequest(key, requestId) {\n if (!isPlatformAgentKey(key)) return appKeys.claimRequest(key as Key, requestId)\n return access.claimRequest ? access.claimRequest(key.keyId, requestId) : { allowed: true }\n },\n }\n}\n"],"mappings":"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","names":["segment","segment","text","workspace","text","failure"]}
1
+ {"version":3,"sources":["../../src/operator/contract.ts","../../src/operator/server.ts","../../src/operator/client.ts","../../src/operator/chat-adapter.ts","../../src/operator/platform-agent-keys.ts"],"sourcesContent":["/**\n * The standard operator API contract every agent app mounts at\n * `/api/operator/v1`. An outside agent (a Claude Code or Codex session, or\n * another app's agent) holds one scoped key per app and drives workspaces\n * through these routes; the app supplies only storage and execution.\n */\n\nimport type { ChatAttachmentInput } from '../chat-routes/wire.js'\n\nexport const OPERATOR_API_VERSION = 'v1'\nexport const OPERATOR_API_BASE_PATH = '/api/operator/v1'\n\n/** Key permissions, with the labels an app's API access page shows. */\nexport const OPERATOR_ACCESS = [\n {\n scope: 'operator:read',\n label: 'Read workspaces',\n description: 'Read workspaces, conversations, files, approvals, scorecards, and run status.',\n },\n {\n scope: 'operator:write',\n label: 'Create and edit',\n description: 'Create workspaces and conversations, and write files.',\n },\n {\n scope: 'operator:run',\n requires: ['operator:read'],\n label: 'Run agents',\n description: 'Requires read access. Start, continue, and interrupt agent turns, and answer their questions and tool-permission requests. Agents can modify workspace files. Uses your plan and budget.',\n },\n] as const\n\nexport type OperatorScope = typeof OPERATOR_ACCESS[number]['scope']\n\nexport const OPERATOR_API_SCOPES: readonly OperatorScope[] = OPERATOR_ACCESS.map(({ scope }) => scope)\n\nexport const OPERATOR_SCOPE_DEPENDENCIES: Readonly<Record<string, readonly string[]>> = Object.fromEntries(\n OPERATOR_ACCESS.flatMap((option) => 'requires' in option ? [[option.scope, option.requires]] : []),\n)\n\n/**\n * A key carrying one or more of these scopes reaches only the named\n * workspaces and cannot create workspaces. Keys without one keep the owner's\n * ordinary workspace access.\n */\nexport const OPERATOR_WORKSPACE_SCOPE_PREFIX = 'operator:workspace:'\n\nconst WORKSPACE_ID = /^[A-Za-z0-9_-]{1,128}$/\n\nexport function operatorWorkspaceScope(workspaceId: string): string {\n if (!WORKSPACE_ID.test(workspaceId)) throw new TypeError('Workspace ids contain only letters, digits, - and _')\n return `${OPERATOR_WORKSPACE_SCOPE_PREFIX}${workspaceId}`\n}\n\n/** The workspaces a key is restricted to, or null when it is unrestricted. A malformed restriction restricts to nothing. */\nexport function operatorKeyWorkspaces(scopes: readonly string[]): readonly string[] | null {\n const restricted = scopes.filter((scope) => scope.startsWith(OPERATOR_WORKSPACE_SCOPE_PREFIX))\n if (restricted.length === 0) return null\n return restricted\n .map((scope) => scope.slice(OPERATOR_WORKSPACE_SCOPE_PREFIX.length))\n .filter((id) => WORKSPACE_ID.test(id))\n}\n\nexport type OperatorRole = 'owner' | 'admin' | 'editor' | 'viewer'\n\nexport interface OperatorAppInfo {\n /** Stable app identifier, such as `gtm` or `tax`. */\n app: string\n name: string\n apiVersion: typeof OPERATOR_API_VERSION\n /** Optional operations this app implements; absent ones answer 501. */\n capabilities: OperatorCapability[]\n}\n\nexport type OperatorCapability =\n | 'turns.attachments'\n | 'workspaces.create'\n | 'threads.read'\n | 'files.read'\n | 'journal.read'\n | 'assets.read'\n | 'scorecard.read'\n\nexport interface OperatorPrincipalInfo {\n keyId: string\n scopes: string[]\n /** Workspaces the key is restricted to, or null for the owner's ordinary access. */\n workspaces: string[] | null\n}\n\nexport interface OperatorWorkspace {\n id: string\n name: string\n role?: OperatorRole\n /** Browser link for a person to open the workspace. */\n url?: string\n createdAt?: string\n}\n\nexport interface OperatorThread {\n id: string\n title: string\n updatedAt?: string\n url?: string\n}\n\n/**\n * `succeeded` and `failed` are settled. `input-required` waits on a person or\n * an operator decision listed in `approvals`. `unknown` means the app cannot\n * establish the state; it is never evidence of success.\n */\nexport type OperatorTurnState = 'queued' | 'working' | 'input-required' | 'succeeded' | 'failed' | 'unknown'\n\nexport const OPERATOR_SETTLED_STATES: readonly OperatorTurnState[] = ['succeeded', 'failed']\n\nexport interface OperatorAssetRef {\n id: string\n /** Root-relative operator route that returns the bytes. */\n path: string\n mediaType?: string\n name?: string\n}\n\nexport interface OperatorFileChange {\n path: string\n action: 'created' | 'updated' | 'deleted' | 'changed'\n}\n\nexport type OperatorApprovalKind = 'question' | 'permission' | 'plan' | 'hub' | 'asset' | 'review'\n\nexport interface OperatorApproval {\n id: string\n kind: OperatorApprovalKind\n title: string\n detail?: string\n threadId?: string\n /** Browser link where an authorized person decides it. */\n url?: string\n createdAt?: string\n expiresAt?: string\n /** Who can decide: an operator key with run access, an editor, or the workspace owner. */\n decidedBy: 'operator' | 'editor' | 'owner'\n}\n\nexport interface OperatorTurn {\n workspaceId: string\n threadId: string\n turnId: string\n state: OperatorTurnState\n url?: string\n executionId?: string\n model?: string\n costUsd?: number\n completedAt?: string\n /** The settled answer; null until the turn settles. */\n reply: { content: string; mediaType: string } | null\n failure: { code?: string; message: string } | null\n assets: OperatorAssetRef[]\n files: OperatorFileChange[]\n /** Decisions open on this turn's conversation. */\n approvals: OperatorApproval[]\n}\n\nexport interface OperatorJournalEntry {\n path: string\n /** `YYYY-MM-DD` the entry belongs to. */\n date: string\n title?: string\n updatedAt?: string\n}\n\nexport interface OperatorFileEntry {\n path: string\n updatedAt?: string\n bytes?: number\n}\n\nexport interface OperatorFile {\n path: string\n content: string\n mediaType: string\n revision?: string\n}\n\nexport interface OperatorMetric {\n key: string\n label: string\n /** Null when the app has no measurement; never a guessed zero. */\n value: number | null\n unit: 'count' | 'usd' | 'ratio' | 'ms'\n note?: string\n}\n\nexport interface OperatorScorecard {\n workspaceId: string\n generatedAt: string\n window: { start: string; end: string; days: number }\n /** Work the agent did in the window. */\n metrics: OperatorMetric[]\n /** Business results recorded in the window, such as clicks, signups, or replies. */\n outcomes: OperatorMetric[]\n /** How the numbers are measured and what they exclude. */\n measurement: string\n}\n\nexport interface OperatorErrorBody {\n error: string\n code: string\n retryable?: boolean\n}\n\nexport interface StartTurnInput {\n /** Client-generated UUID. Reuse it to retry the same turn; it never starts twice. */\n turnId: string\n content: string\n /** Continue this conversation; omitted starts a new one. */\n threadId?: string\n /** Title for a new conversation. */\n title?: string\n model?: string\n /**\n * Files uploaded to this workspace ahead of the turn, as the app's\n * attachment upload route returned them in `{ files }`. They reach the\n * agent exactly as browser-composer attachments do; the app re-checks each\n * path against the workspace's own store. At most `ATTACHMENT_MAX_COUNT`\n * (`/chat-routes`) entries. Apps without the `turns.attachments`\n * capability refuse them.\n */\n attachments?: ChatAttachmentInput[]\n}\n\n/** Route table: method, path pattern under the base path, and required scopes. */\nexport const OPERATOR_ROUTES = [\n { id: 'describe', method: 'GET', path: '', scopes: ['operator:read'] },\n { id: 'workspaces.list', method: 'GET', path: '/workspaces', scopes: ['operator:read'] },\n { id: 'workspaces.create', method: 'POST', path: '/workspaces', scopes: ['operator:write'] },\n { id: 'workspaces.get', method: 'GET', path: '/workspaces/:workspaceId', scopes: ['operator:read'] },\n { id: 'turns.start', method: 'POST', path: '/workspaces/:workspaceId/turns', scopes: ['operator:read', 'operator:run'] },\n { id: 'threads.list', method: 'GET', path: '/workspaces/:workspaceId/threads', scopes: ['operator:read'] },\n { id: 'threads.get', method: 'GET', path: '/workspaces/:workspaceId/threads/:threadId', scopes: ['operator:read'] },\n { id: 'turns.get', method: 'GET', path: '/workspaces/:workspaceId/threads/:threadId/turns/:turnId', scopes: ['operator:read'] },\n { id: 'approvals.list', method: 'GET', path: '/workspaces/:workspaceId/approvals', scopes: ['operator:read'] },\n { id: 'journal.list', method: 'GET', path: '/workspaces/:workspaceId/journal', scopes: ['operator:read'] },\n { id: 'files.list', method: 'GET', path: '/workspaces/:workspaceId/files', scopes: ['operator:read'] },\n { id: 'files.read', method: 'GET', path: '/workspaces/:workspaceId/file', scopes: ['operator:read'] },\n { id: 'assets.read', method: 'GET', path: '/workspaces/:workspaceId/assets/:assetId', scopes: ['operator:read'] },\n { id: 'scorecard.get', method: 'GET', path: '/workspaces/:workspaceId/scorecard', scopes: ['operator:read'] },\n] as const satisfies ReadonlyArray<{ id: string; method: 'GET' | 'POST'; path: string; scopes: readonly OperatorScope[] }>\n\nexport type OperatorRouteId = typeof OPERATOR_ROUTES[number]['id']\n\nconst SEGMENT = /^[A-Za-z0-9_:.%-]{1,256}$/\n\n/** Match a pathname under the base path to its route and decoded parameters. */\nexport function matchOperatorRoute(\n method: string,\n pathname: string,\n basePath: string = OPERATOR_API_BASE_PATH,\n): { route: typeof OPERATOR_ROUTES[number]; params: Record<string, string> } | null {\n if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return null\n const rest = pathname.slice(basePath.length).replace(/\\/$/, '')\n const actual = rest ? rest.split('/').slice(1) : []\n for (const route of OPERATOR_ROUTES) {\n if (route.method !== method) continue\n const pattern = route.path ? route.path.split('/').slice(1) : []\n if (pattern.length !== actual.length) continue\n const params: Record<string, string> = {}\n let matched = true\n for (let index = 0; index < pattern.length; index++) {\n const expected = pattern[index]!\n const segment = actual[index]!\n if (expected.startsWith(':')) {\n if (!SEGMENT.test(segment)) { matched = false; break }\n let decoded: string\n try { decoded = decodeURIComponent(segment) } catch { matched = false; break }\n if (!decoded || decoded.includes('/')) { matched = false; break }\n params[expected.slice(1)] = decoded\n } else if (expected !== segment) {\n matched = false\n break\n }\n }\n if (matched) return { route, params }\n }\n return null\n}\n","import { parseChatAttachmentInputs } from '../chat-routes/attachment-validation.js'\nimport { createApiKeyRequestAuth, type RequestApiKey } from '../platform/api-key-auth.js'\nimport {\n OPERATOR_API_BASE_PATH,\n OPERATOR_API_VERSION,\n matchOperatorRoute,\n operatorKeyWorkspaces,\n type OperatorApproval,\n type OperatorCapability,\n type OperatorFile,\n type OperatorFileEntry,\n type OperatorJournalEntry,\n type OperatorRouteId,\n type OperatorScorecard,\n type OperatorThread,\n type OperatorTurn,\n type OperatorWorkspace,\n type StartTurnInput,\n} from './contract.js'\n\n/** The verified caller of one operator request. */\nexport interface OperatorContext<Identity> {\n identity: Identity\n key: { keyId: string; scopes: readonly string[] }\n request: Request\n}\n\n/**\n * The app's side of the operator API: storage and execution only. Scope,\n * workspace restriction, input validation, and response shapes belong to the\n * shared handler, so every app answers the same way.\n */\nexport interface OperatorAdapter<Identity> {\n /**\n * Apply the app's workspace roles: `read` needs viewer access, `run` needs\n * the role that may start agent work. Null answers 404, so a key cannot\n * probe workspaces it cannot reach.\n */\n authorizeWorkspace(ctx: OperatorContext<Identity>, workspaceId: string, access: 'read' | 'run'): Promise<OperatorWorkspace | null>\n listWorkspaces(ctx: OperatorContext<Identity>): Promise<OperatorWorkspace[]>\n createWorkspace?(ctx: OperatorContext<Identity>, input: { name: string }): Promise<OperatorWorkspace>\n /**\n * True when `startTurn` delivers `input.attachments` to the agent, through\n * a chat route whose attachment resolver reads them from the workspace's\n * store. Otherwise the handler refuses turns that carry attachments rather\n * than letting them be dropped.\n */\n acceptsAttachments?: boolean\n /** Admit the turn durably and return without waiting for it; the same turnId never starts twice. */\n startTurn(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, input: StartTurnInput): Promise<OperatorTurn>\n getTurn(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, target: { threadId: string; turnId: string }): Promise<OperatorTurn | null>\n listThreads?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, query: { cursor?: string; limit: number }): Promise<{ threads: OperatorThread[]; nextCursor: string | null }>\n getThread?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, threadId: string): Promise<{ thread: OperatorThread; latestTurn: OperatorTurn | null } | null>\n listApprovals(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace): Promise<OperatorApproval[]>\n listJournal?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, query: { days: number }): Promise<OperatorJournalEntry[]>\n listFiles?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, query: { prefix: string }): Promise<OperatorFileEntry[]>\n readFile?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, path: string): Promise<OperatorFile | null>\n /** The stored bytes, or null when the asset is not in this workspace. */\n readAsset?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, assetId: string): Promise<Response | null>\n getScorecard?(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, query: { days: number }): Promise<OperatorScorecard>\n}\n\nexport interface OperatorKeyStore<Key extends RequestApiKey, Identity> {\n /** Verify against the app's existing key store, including revocation. */\n verify(authorization: string): Promise<Key | null>\n /** Load the key owner; throw a Response to refuse a key class, such as spending-capped keys. */\n resolveIdentity(key: Key): Promise<Identity | null>\n /** Atomically count the request against the key's limits. */\n claimRequest(key: Key, requestId: string): Promise<{ allowed: boolean; retryAfterSeconds?: number }>\n}\n\nexport interface OperatorApiOptions<Key extends RequestApiKey, Identity> {\n app: { id: string; name: string }\n keys: OperatorKeyStore<Key, Identity>\n adapter: OperatorAdapter<Identity>\n basePath?: string\n /** Longest hold for a turn read with `?wait=`, in seconds. */\n maxWaitSeconds?: number\n /** Interval between state reads during a held turn read. */\n pollIntervalMs?: number\n /** Unexpected adapter failures; responses never carry their detail. */\n onError?(error: unknown, route: OperatorRouteId): void\n}\n\nexport interface OperatorApi {\n /** True when the request addresses this API's base path. */\n matches(request: Request): boolean\n handle(request: Request): Promise<Response>\n}\n\n/** A refusal with a stable code; adapters throw it for expected failures. */\nexport class OperatorError extends Error {\n constructor(\n readonly code: string,\n readonly status: number,\n message?: string,\n readonly retryable = false,\n ) {\n super(message ?? code)\n this.name = 'OperatorError'\n }\n}\n\nconst TURN_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\nconst THREAD_ID = /^[A-Za-z0-9_-]{1,256}$/\nconst MAX_BODY_BYTES = 128 * 1024\nconst MAX_CONTENT_BYTES = 64 * 1024\nconst START_FIELDS = new Set(['turnId', 'content', 'threadId', 'title', 'model', 'attachments'])\n\nconst NO_STORE = { 'cache-control': 'private, no-store' }\n\nfunction json(body: unknown, status = 200): Response {\n return Response.json(body, { status, headers: NO_STORE })\n}\n\nfunction refusal(status: number, code: string, error: string, retryable = false): Response {\n return json({ error, code, ...(retryable ? { retryable } : {}) }, status)\n}\n\nfunction boundedInteger(value: string | null, fallback: number, min: number, max: number, name: string): number {\n if (value === null || value === '') return fallback\n if (!/^\\d{1,6}$/.test(value)) throw new OperatorError('operator.invalid_input', 400, `${name} must be a whole number`)\n const parsed = Number(value)\n if (parsed < min || parsed > max) throw new OperatorError('operator.invalid_input', 400, `${name} must be between ${min} and ${max}`)\n return parsed\n}\n\n/** Workspace-relative paths only: no absolute, parent, empty, or backslash segments. */\nfunction relativePath(value: string | null, name: string, { allowEmpty }: { allowEmpty: boolean }): string {\n const path = value ?? ''\n if (path === '' && allowEmpty) return ''\n if (!path || path.length > 1024 || path.startsWith('/') || path.includes('\\\\') || /[\\u0000-\\u001f\\u007f]/.test(path)) {\n throw new OperatorError('operator.invalid_path', 400, `${name} must be a workspace-relative path`)\n }\n const segments = path.replace(/\\/$/, '').split('/')\n if (segments.some((segment) => !segment || segment === '.' || segment === '..')) {\n throw new OperatorError('operator.invalid_path', 400, `${name} must be a workspace-relative path`)\n }\n return path\n}\n\nasync function readJsonBody(request: Request): Promise<Record<string, unknown>> {\n const declared = Number(request.headers.get('content-length') ?? '0')\n if (declared > MAX_BODY_BYTES) throw new OperatorError('operator.body_too_large', 413, 'Request body is too large')\n const text = await request.text()\n if (new TextEncoder().encode(text).byteLength > MAX_BODY_BYTES) {\n throw new OperatorError('operator.body_too_large', 413, 'Request body is too large')\n }\n let parsed: unknown\n try { parsed = JSON.parse(text) } catch { throw new OperatorError('operator.invalid_json', 400, 'Body must be a JSON object') }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new OperatorError('operator.invalid_json', 400, 'Body must be a JSON object')\n }\n return parsed as Record<string, unknown>\n}\n\nfunction optionalString(body: Record<string, unknown>, name: string, max: number): string | undefined {\n const value = body[name]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string' || !value.trim() || value.length > max) {\n throw new OperatorError('operator.invalid_input', 400, `${name} must be a nonempty string of at most ${max} characters`)\n }\n return value.trim()\n}\n\n/** Identity and destination come from the key and the path; the body can only describe the work. */\nfunction parseStartTurn(body: Record<string, unknown>): StartTurnInput {\n const unknown = Object.keys(body).filter((field) => !START_FIELDS.has(field))\n if (unknown.length) throw new OperatorError('operator.invalid_input', 400, `Unknown fields: ${unknown.join(', ')}`)\n if (typeof body.turnId !== 'string' || !TURN_ID.test(body.turnId)) {\n throw new OperatorError('operator.invalid_input', 400, 'turnId must be a client-generated UUID')\n }\n if (typeof body.content !== 'string' || !body.content.trim()) {\n throw new OperatorError('operator.invalid_input', 400, 'content must be a nonempty string')\n }\n if (new TextEncoder().encode(body.content).byteLength > MAX_CONTENT_BYTES) {\n throw new OperatorError('operator.invalid_input', 400, `content must be at most ${MAX_CONTENT_BYTES} bytes`)\n }\n const threadId = optionalString(body, 'threadId', 256)\n if (threadId !== undefined && !THREAD_ID.test(threadId)) {\n throw new OperatorError('operator.invalid_input', 400, 'threadId contains only letters, digits, - and _')\n }\n const title = optionalString(body, 'title', 200)\n const model = optionalString(body, 'model', 200)\n // The chat route's own structural rules; the app's store read re-checks\n // every path against this workspace and re-derives every size.\n const attachments = parseChatAttachmentInputs(body.attachments)\n if (!attachments.succeeded) throw new OperatorError('operator.invalid_input', 400, attachments.error)\n return {\n turnId: body.turnId.toLowerCase(),\n content: body.content,\n ...(threadId === undefined ? {} : { threadId }),\n ...(title === undefined ? {} : { title }),\n ...(model === undefined ? {} : { model }),\n ...(attachments.value.length === 0 ? {} : { attachments: attachments.value }),\n }\n}\n\nfunction capabilities<Identity>(adapter: OperatorAdapter<Identity>): OperatorCapability[] {\n const present: Array<[OperatorCapability, unknown]> = [\n ['workspaces.create', adapter.createWorkspace],\n ['threads.read', adapter.getThread ?? adapter.listThreads],\n ['files.read', adapter.readFile ?? adapter.listFiles],\n ['journal.read', adapter.listJournal],\n ['assets.read', adapter.readAsset],\n ['scorecard.read', adapter.getScorecard],\n ]\n const implemented = present.filter(([, method]) => typeof method === 'function').map(([name]) => name)\n return adapter.acceptsAttachments === true ? ['turns.attachments', ...implemented] : implemented\n}\n\nfunction unsupported(): never {\n throw new OperatorError('operator.unsupported', 501, 'This app does not implement this operation')\n}\n\n/** A held turn read returns as soon as the turn settles or waits on a decision. */\nfunction needsAttention(turn: OperatorTurn): boolean {\n return turn.state === 'succeeded' || turn.state === 'failed' || turn.state === 'input-required' || turn.approvals.length > 0\n}\n\nconst sleep = (ms: number, signal: AbortSignal) => new Promise<void>((resolve) => {\n if (signal.aborted) return resolve()\n const timer = setTimeout(resolve, ms)\n signal.addEventListener('abort', () => { clearTimeout(timer); resolve() }, { once: true })\n})\n\n/**\n * Mount the standard operator API. Requests must carry a Bearer key: there is\n * no browser-cookie fallback, so a page session never reaches these routes.\n */\nexport function createOperatorApi<Key extends RequestApiKey, Identity>(\n options: OperatorApiOptions<Key, Identity>,\n): OperatorApi {\n const basePath = (options.basePath ?? OPERATOR_API_BASE_PATH).replace(/\\/$/, '')\n const maxWaitSeconds = Math.max(0, Math.min(options.maxWaitSeconds ?? 25, 55))\n const pollIntervalMs = Math.max(250, options.pollIntervalMs ?? 2_000)\n const { adapter } = options\n\n function matches(request: Request): boolean {\n const { pathname } = new URL(request.url)\n return pathname === basePath || pathname.startsWith(`${basePath}/`)\n }\n\n async function handle(request: Request): Promise<Response> {\n const url = new URL(request.url)\n const match = matchOperatorRoute(request.method, url.pathname, basePath)\n if (!match) return refusal(404, 'operator.route_not_found', 'No operator route matches this method and path')\n const { route, params } = match\n if (request.headers.get('Authorization') === null) {\n return refusal(401, 'operator.unauthenticated', 'An operator API key is required')\n }\n\n let verified: { identity: Identity; key: Key }\n try {\n const authenticate = createApiKeyRequestAuth<Key, { identity: Identity; key: Key }>({\n verify: (authorization) => options.keys.verify(authorization),\n requiredScope: () => route.scopes,\n resolveIdentity: async (key) => {\n const identity = await options.keys.resolveIdentity(key)\n return identity === null || identity === undefined ? null : { identity, key }\n },\n claimRequest: (key, requestId) => options.keys.claimRequest(key, requestId),\n })\n const result = await authenticate(request)\n if (!result) return refusal(401, 'operator.unauthenticated', 'An operator API key is required')\n verified = result\n } catch (error) {\n if (error instanceof Response) return error\n options.onError?.(error, route.id)\n return refusal(503, 'operator.auth_unavailable', 'Key verification is unavailable', true)\n }\n\n const ctx: OperatorContext<Identity> = {\n identity: verified.identity,\n key: { keyId: verified.key.keyId, scopes: [...verified.key.scopes] },\n request,\n }\n const restriction = operatorKeyWorkspaces(verified.key.scopes)\n\n try {\n if (route.id === 'describe') {\n return json({\n app: {\n app: options.app.id, name: options.app.name, apiVersion: OPERATOR_API_VERSION,\n capabilities: capabilities(adapter),\n },\n principal: { keyId: ctx.key.keyId, scopes: [...ctx.key.scopes], workspaces: restriction ? [...restriction] : null },\n })\n }\n if (route.id === 'workspaces.list') {\n const listed = await adapter.listWorkspaces(ctx)\n return json({ workspaces: restriction ? listed.filter((workspace) => restriction.includes(workspace.id)) : listed })\n }\n if (route.id === 'workspaces.create') {\n if (restriction) {\n throw new OperatorError('operator.workspace_restricted', 403, 'A workspace-restricted key cannot create workspaces')\n }\n if (!adapter.createWorkspace) unsupported()\n const body = await readJsonBody(request)\n const unknown = Object.keys(body).filter((field) => field !== 'name')\n if (unknown.length) throw new OperatorError('operator.invalid_input', 400, `Unknown fields: ${unknown.join(', ')}`)\n const name = optionalString(body, 'name', 120)\n if (!name) throw new OperatorError('operator.invalid_input', 400, 'name is required')\n return json({ workspace: await adapter.createWorkspace(ctx, { name }) }, 201)\n }\n\n const workspaceId = params.workspaceId!\n if (restriction && !restriction.includes(workspaceId)) {\n throw new OperatorError('operator.workspace_not_found', 404, 'Workspace not found')\n }\n const workspace = await adapter.authorizeWorkspace(ctx, workspaceId, route.id === 'turns.start' ? 'run' : 'read')\n if (!workspace) throw new OperatorError('operator.workspace_not_found', 404, 'Workspace not found')\n\n switch (route.id) {\n case 'workspaces.get':\n return json({ workspace })\n case 'turns.start': {\n const input = parseStartTurn(await readJsonBody(request))\n if (input.attachments && adapter.acceptsAttachments !== true) {\n throw new OperatorError('operator.unsupported', 501, 'This app does not accept attachments on operator turns')\n }\n return json({ turn: await adapter.startTurn(ctx, workspace, input) }, 202)\n }\n case 'threads.list': {\n if (!adapter.listThreads) unsupported()\n const limit = boundedInteger(url.searchParams.get('limit'), 25, 1, 100, 'limit')\n const cursor = url.searchParams.get('cursor') ?? undefined\n if (cursor !== undefined && (cursor.length > 512 || !cursor)) {\n throw new OperatorError('operator.invalid_input', 400, 'cursor is invalid')\n }\n return json(await adapter.listThreads(ctx, workspace, { limit, ...(cursor ? { cursor } : {}) }))\n }\n case 'threads.get': {\n if (!adapter.getThread) unsupported()\n const found = await adapter.getThread(ctx, workspace, params.threadId!)\n if (!found) throw new OperatorError('operator.thread_not_found', 404, 'Thread not found')\n return json(found)\n }\n case 'turns.get': {\n const target = { threadId: params.threadId!, turnId: params.turnId! }\n const wait = boundedInteger(url.searchParams.get('wait'), 0, 0, maxWaitSeconds, 'wait')\n const deadline = Date.now() + wait * 1000\n let turn = await adapter.getTurn(ctx, workspace, target)\n while (turn && !needsAttention(turn) && Date.now() + pollIntervalMs <= deadline && !request.signal.aborted) {\n await sleep(pollIntervalMs, request.signal)\n turn = await adapter.getTurn(ctx, workspace, target)\n }\n if (!turn) throw new OperatorError('operator.turn_not_found', 404, 'Turn not found')\n return json({ turn })\n }\n case 'approvals.list':\n return json({ approvals: await adapter.listApprovals(ctx, workspace) })\n case 'journal.list': {\n if (!adapter.listJournal) unsupported()\n const days = boundedInteger(url.searchParams.get('days'), 7, 1, 90, 'days')\n return json({ entries: await adapter.listJournal(ctx, workspace, { days }) })\n }\n case 'files.list': {\n if (!adapter.listFiles) unsupported()\n const prefix = relativePath(url.searchParams.get('prefix'), 'prefix', { allowEmpty: true })\n return json({ files: await adapter.listFiles(ctx, workspace, { prefix }) })\n }\n case 'files.read': {\n if (!adapter.readFile) unsupported()\n const path = relativePath(url.searchParams.get('path'), 'path', { allowEmpty: false })\n const file = await adapter.readFile(ctx, workspace, path)\n if (!file) throw new OperatorError('operator.file_not_found', 404, 'File not found')\n return json({ file })\n }\n case 'assets.read': {\n if (!adapter.readAsset) unsupported()\n const response = await adapter.readAsset(ctx, workspace, params.assetId!)\n if (!response) throw new OperatorError('operator.asset_not_found', 404, 'Asset not found')\n const headers = new Headers(response.headers)\n headers.set('cache-control', 'private, no-store')\n headers.delete('set-cookie')\n return new Response(response.body, { status: response.status, headers })\n }\n case 'scorecard.get': {\n if (!adapter.getScorecard) unsupported()\n const days = boundedInteger(url.searchParams.get('days'), 7, 1, 90, 'days')\n return json({ scorecard: await adapter.getScorecard(ctx, workspace, { days }) })\n }\n }\n return refusal(404, 'operator.route_not_found', 'No operator route matches this method and path')\n } catch (error) {\n if (error instanceof OperatorError) return refusal(error.status, error.code, error.message, error.retryable)\n if (error instanceof Response) return error\n options.onError?.(error, route.id)\n return refusal(500, 'operator.internal', 'The operation failed', true)\n }\n }\n\n return { matches, handle }\n}\n","import { createApiKeyFetch } from '../web/api-key-fetch.js'\nimport {\n OPERATOR_API_BASE_PATH,\n type OperatorApproval,\n type OperatorAppInfo,\n type OperatorErrorBody,\n type OperatorFile,\n type OperatorFileEntry,\n type OperatorJournalEntry,\n type OperatorPrincipalInfo,\n type OperatorScorecard,\n type OperatorThread,\n type OperatorTurn,\n type OperatorWorkspace,\n type StartTurnInput,\n} from './contract.js'\n\nexport type OperatorResult<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; status: number; code: string; error: string; retryable: boolean }\n\nexport interface OperatorClientOptions {\n /** The app's origin, such as `https://gtm.tangle.tools`. */\n origin: string\n /** Resolve the key from trusted secret storage on each request; never from model output. */\n getApiKey(): string | Promise<string>\n fetchImpl?: typeof fetch\n allowHttpLoopback?: boolean\n basePath?: string\n}\n\nexport interface WaitForTurnOptions {\n /** Total time to follow the turn. */\n timeoutMs: number\n /** Server hold per request, in seconds; the server caps it. */\n waitSeconds?: number\n signal?: AbortSignal\n}\n\nexport interface OperatorClient {\n describe(): Promise<OperatorResult<{ app: OperatorAppInfo; principal: OperatorPrincipalInfo }>>\n listWorkspaces(): Promise<OperatorResult<OperatorWorkspace[]>>\n createWorkspace(input: { name: string }): Promise<OperatorResult<OperatorWorkspace>>\n getWorkspace(workspaceId: string): Promise<OperatorResult<OperatorWorkspace>>\n /** Omitting turnId generates one; retry with the returned turn's id. */\n startTurn(workspaceId: string, input: Omit<StartTurnInput, 'turnId'> & { turnId?: string }): Promise<OperatorResult<OperatorTurn>>\n getTurn(workspaceId: string, threadId: string, turnId: string, options?: { waitSeconds?: number; signal?: AbortSignal }): Promise<OperatorResult<OperatorTurn>>\n /** Follow until the turn settles, waits on a decision, or the timeout passes; the last read is returned. */\n waitForTurn(workspaceId: string, threadId: string, turnId: string, options: WaitForTurnOptions): Promise<OperatorResult<OperatorTurn>>\n listThreads(workspaceId: string, query?: { cursor?: string; limit?: number }): Promise<OperatorResult<{ threads: OperatorThread[]; nextCursor: string | null }>>\n getThread(workspaceId: string, threadId: string): Promise<OperatorResult<{ thread: OperatorThread; latestTurn: OperatorTurn | null }>>\n listApprovals(workspaceId: string): Promise<OperatorResult<OperatorApproval[]>>\n listJournal(workspaceId: string, query?: { days?: number }): Promise<OperatorResult<OperatorJournalEntry[]>>\n listFiles(workspaceId: string, query?: { prefix?: string }): Promise<OperatorResult<OperatorFileEntry[]>>\n readFile(workspaceId: string, path: string): Promise<OperatorResult<OperatorFile>>\n /** The raw asset response; the caller reads and bounds its body. */\n readAsset(workspaceId: string, assetId: string, init?: { signal?: AbortSignal }): Promise<OperatorResult<Response>>\n getScorecard(workspaceId: string, query?: { days?: number }): Promise<OperatorResult<OperatorScorecard>>\n}\n\nfunction segment(value: string): string {\n return encodeURIComponent(value)\n}\n\nasync function failure(response: Response): Promise<OperatorResult<never>> {\n let body: Partial<OperatorErrorBody> = {}\n try { body = await response.json() as Partial<OperatorErrorBody> } catch { /* A non-JSON refusal keeps its status. */ }\n return {\n succeeded: false,\n status: response.status,\n code: typeof body.code === 'string' ? body.code : `http_${response.status}`,\n error: typeof body.error === 'string' ? body.error : `HTTP ${response.status}`,\n retryable: body.retryable === true || response.status === 429 || response.status >= 500,\n }\n}\n\nfunction transportFailure(error: unknown): OperatorResult<never> {\n const aborted = error instanceof Error && error.name === 'AbortError'\n return {\n succeeded: false,\n status: 0,\n code: aborted ? 'operator.aborted' : 'operator.transport_failed',\n // createApiKeyFetch never forwards transport diagnostics, which could carry the key.\n error: error instanceof Error ? error.message : 'Operator transport failed',\n retryable: !aborted,\n }\n}\n\nconst SETTLED = new Set(['succeeded', 'failed', 'input-required'])\n\n/** A typed client for one app's operator API. */\nexport function createOperatorClient(options: OperatorClientOptions): OperatorClient {\n const basePath = (options.basePath ?? OPERATOR_API_BASE_PATH).replace(/\\/$/, '')\n const request = createApiKeyFetch({\n origin: options.origin,\n getApiKey: options.getApiKey,\n ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),\n ...(options.allowHttpLoopback ? { allowHttpLoopback: true } : {}),\n })\n\n async function call<T>(\n path: string,\n pick: (body: Record<string, unknown>) => T,\n init: RequestInit = {},\n ): Promise<OperatorResult<T>> {\n let response: Response\n try {\n response = await request(`${basePath}${path}`, {\n ...init,\n headers: { accept: 'application/json', ...(init.body ? { 'content-type': 'application/json' } : {}) },\n })\n } catch (error) {\n return transportFailure(error)\n }\n if (!response.ok) return failure(response)\n try {\n return { succeeded: true, value: pick(await response.json() as Record<string, unknown>) }\n } catch {\n return { succeeded: false, status: response.status, code: 'operator.invalid_response', error: 'The response was not the expected JSON', retryable: true }\n }\n }\n\n const query = (params: Record<string, string | number | undefined>) => {\n const search = new URLSearchParams()\n for (const [name, value] of Object.entries(params)) if (value !== undefined && value !== '') search.set(name, String(value))\n const text = search.toString()\n return text ? `?${text}` : ''\n }\n\n const client: OperatorClient = {\n describe: () => call('', (body) => body as unknown as { app: OperatorAppInfo; principal: OperatorPrincipalInfo }),\n listWorkspaces: () => call('/workspaces', (body) => body.workspaces as OperatorWorkspace[]),\n createWorkspace: (input) => call('/workspaces', (body) => body.workspace as OperatorWorkspace, {\n method: 'POST', body: JSON.stringify({ name: input.name }),\n }),\n getWorkspace: (workspaceId) => call(`/workspaces/${segment(workspaceId)}`, (body) => body.workspace as OperatorWorkspace),\n startTurn: (workspaceId, input) => call(`/workspaces/${segment(workspaceId)}/turns`, (body) => body.turn as OperatorTurn, {\n method: 'POST', body: JSON.stringify({ ...input, turnId: input.turnId ?? crypto.randomUUID() }),\n }),\n getTurn: (workspaceId, threadId, turnId, opts = {}) => call(\n `/workspaces/${segment(workspaceId)}/threads/${segment(threadId)}/turns/${segment(turnId)}${query({ wait: opts.waitSeconds })}`,\n (body) => body.turn as OperatorTurn,\n opts.signal ? { signal: opts.signal } : {},\n ),\n async waitForTurn(workspaceId, threadId, turnId, opts) {\n const deadline = Date.now() + opts.timeoutMs\n const waitSeconds = opts.waitSeconds ?? 25\n let last: OperatorResult<OperatorTurn> | null = null\n while (true) {\n const remaining = Math.floor((deadline - Date.now()) / 1000)\n const started = Date.now()\n last = await client.getTurn(workspaceId, threadId, turnId, {\n waitSeconds: Math.max(0, Math.min(waitSeconds, remaining)),\n ...(opts.signal ? { signal: opts.signal } : {}),\n })\n if (!last.succeeded) {\n if (!last.retryable || Date.now() >= deadline || opts.signal?.aborted) return last\n await new Promise((resolve) => setTimeout(resolve, Math.min(5_000, Math.max(0, deadline - Date.now()))))\n continue\n }\n if (SETTLED.has(last.value.state) || last.value.approvals.length > 0) return last\n if (Date.now() >= deadline || opts.signal?.aborted) return last\n // A server that does not hold the read still gets a bounded request rate.\n if (Date.now() - started < 1_000) {\n await new Promise((resolve) => setTimeout(resolve, Math.min(2_000, Math.max(0, deadline - Date.now()))))\n }\n }\n },\n listThreads: (workspaceId, q = {}) => call(\n `/workspaces/${segment(workspaceId)}/threads${query({ cursor: q.cursor, limit: q.limit })}`,\n (body) => ({ threads: body.threads as OperatorThread[], nextCursor: (body.nextCursor as string | null) ?? null }),\n ),\n getThread: (workspaceId, threadId) => call(\n `/workspaces/${segment(workspaceId)}/threads/${segment(threadId)}`,\n (body) => ({ thread: body.thread as OperatorThread, latestTurn: (body.latestTurn as OperatorTurn | null) ?? null }),\n ),\n listApprovals: (workspaceId) => call(`/workspaces/${segment(workspaceId)}/approvals`, (body) => body.approvals as OperatorApproval[]),\n listJournal: (workspaceId, q = {}) => call(\n `/workspaces/${segment(workspaceId)}/journal${query({ days: q.days })}`,\n (body) => body.entries as OperatorJournalEntry[],\n ),\n listFiles: (workspaceId, q = {}) => call(\n `/workspaces/${segment(workspaceId)}/files${query({ prefix: q.prefix })}`,\n (body) => body.files as OperatorFileEntry[],\n ),\n readFile: (workspaceId, path) => call(\n `/workspaces/${segment(workspaceId)}/file${query({ path })}`,\n (body) => body.file as OperatorFile,\n ),\n async readAsset(workspaceId, assetId, init = {}) {\n let response: Response\n try {\n response = await request(`${basePath}/workspaces/${segment(workspaceId)}/assets/${segment(assetId)}`, init.signal ? { signal: init.signal } : {})\n } catch (error) {\n return transportFailure(error)\n }\n if (!response.ok) return failure(response)\n return { succeeded: true, value: response }\n },\n getScorecard: (workspaceId, q = {}) => call(\n `/workspaces/${segment(workspaceId)}/scorecard${query({ days: q.days })}`,\n (body) => body.scorecard as OperatorScorecard,\n ),\n }\n return client\n}\n","import type { ChatAttachmentInput } from '../chat-routes/wire.js'\nimport { messageHasTurnId } from '../stream/turn-identity.js'\nimport type {\n OperatorApproval,\n OperatorAssetRef,\n OperatorFile,\n OperatorFileChange,\n OperatorFileEntry,\n OperatorJournalEntry,\n OperatorRole,\n OperatorScorecard,\n OperatorThread,\n OperatorTurn,\n OperatorTurnState,\n OperatorWorkspace,\n StartTurnInput,\n} from './contract.js'\nimport { OperatorError, type OperatorAdapter, type OperatorContext } from './server.js'\n\n/** The message fields the adapter reads; agent-app's chat-store rows satisfy it. */\nexport interface ChatOperatorMessage {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n /** Typed chat parts, such as chat-store's, are read field by field. */\n parts: ReadonlyArray<object> | null\n createdAt?: Date | string | number | null\n model?: string | null\n servedModel?: string | null\n costUsd?: number | null\n /** The row's product metadata, for a `failureOf` that reads a stamped turn status. */\n metadata?: Record<string, unknown> | null\n}\n\nexport interface ChatOperatorThread {\n id: string\n workspaceId: string\n title: string\n updatedAt?: Date | string | number | null\n}\n\nexport interface ChatOperatorWorkspace {\n id: string\n name: string\n role?: OperatorRole\n createdAt?: Date | string | number | null\n}\n\n/** A durable execution claim, separate from the chat transcript that owns results. */\nexport interface ChatOperatorAdmission {\n threadId: string\n /** Only an explicit refusal before a usable stream opened is recorded here. */\n refusal?: { code?: string; message: string }\n}\n\n/** Product-owned atomic storage for duplicate suppression across processes.\n * A claim with no transcript stays unknown after a crash; it never licenses a\n * second execution. This is not a completion ledger or an automatic retry queue.\n */\nexport interface ChatOperatorAdmissionStore<Identity> {\n get(ctx: OperatorContext<Identity>, workspaceId: string, turnId: string): Promise<ChatOperatorAdmission | null>\n claim(ctx: OperatorContext<Identity>, input: { workspaceId: string; threadId: string; turnId: string }): Promise<boolean>\n recordRefusal(ctx: OperatorContext<Identity>, input: { workspaceId: string; threadId: string; turnId: string }, refusal: { code?: string; message: string }): Promise<void>\n}\n\nexport interface ChatOperatorAdapterOptions<Identity> {\n /** Optional durable claims; without one, transcript retry detection is not an atomic admission lock. */\n admissions?: ChatOperatorAdmissionStore<Identity>\n /**\n * Set when `runTurn` forwards `attachments` in the chat request body and the\n * chat route resolves them from the workspace's store, as with\n * `resolveChatAttachments`. Without it, turns that carry attachments are refused.\n */\n acceptsAttachments?: boolean\n /** The app's public origin, the base of every browser link. */\n origin: string\n /** Browser path of a conversation; defaults to `/app/<workspace>/chat/<thread>`. */\n threadPath?(workspaceId: string, threadId: string): string\n /** Browser path of a workspace; defaults to `/app/<workspace>`. */\n workspacePath?(workspaceId: string): string\n workspaces: {\n /** Apply the app's roles: `read` needs viewer access, `run` the role that may start agent work. */\n authorize(ctx: OperatorContext<Identity>, workspaceId: string, access: 'read' | 'run'): Promise<ChatOperatorWorkspace | null>\n list(ctx: OperatorContext<Identity>): Promise<ChatOperatorWorkspace[]>\n create?(ctx: OperatorContext<Identity>, input: { name: string }): Promise<ChatOperatorWorkspace>\n }\n threads: {\n /**\n * A thread this caller may read, or null. An app whose conversations\n * belong to one user (not the whole workspace) applies that here.\n */\n get(threadId: string, ctx: OperatorContext<Identity>): Promise<ChatOperatorThread | null>\n /** Insert a thread with this id for this caller; agent-app's chat store accepts a caller-assigned id. */\n create(ctx: OperatorContext<Identity>, input: { id: string; workspaceId: string; title: string }): Promise<ChatOperatorThread>\n /** Oldest first. */\n listMessages(threadId: string, ctx: OperatorContext<Identity>): Promise<ChatOperatorMessage[]>\n list?(ctx: OperatorContext<Identity>, workspaceId: string, query: { cursor?: string; limit: number }): Promise<{ threads: ChatOperatorThread[]; nextCursor: string | null }>\n }\n /**\n * Run one turn through the app's own chat route as this caller. The adapter\n * reads the response to its end, so the turn is driven exactly as a browser\n * holding its stream drives it, and the start request returns once it settles.\n * `attachments` arrives only when `acceptsAttachments` is set.\n */\n runTurn(ctx: OperatorContext<Identity>, input: {\n workspaceId: string; threadId: string; turnId: string; content: string; model?: string\n attachments?: ChatAttachmentInput[]\n }): Promise<Response>\n /** Optional authoritative status of this user-turn anchor. When supplied,\n * missing/running status without a live lease never promotes a partial reply to success. */\n turnStatus?(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string, user: ChatOperatorMessage): Promise<'running' | 'complete' | 'error' | null>\n /** Turn ids still running on a thread, or null when the app cannot tell. */\n runningTurns?(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string): Promise<string[] | null>\n /** Decisions open in the workspace, or on one conversation when `threadId` is set. */\n approvals?(ctx: OperatorContext<Identity>, workspaceId: string, threadId?: string): Promise<OperatorApproval[]>\n files?: {\n list(ctx: OperatorContext<Identity>, workspaceId: string, prefix: string): Promise<OperatorFileEntry[]>\n read(ctx: OperatorContext<Identity>, workspaceId: string, path: string): Promise<OperatorFile | null>\n }\n journal?(ctx: OperatorContext<Identity>, workspaceId: string, days: number): Promise<OperatorJournalEntry[]>\n assets?: {\n /** Assets a reply references; defaults to none. */\n refs?(message: ChatOperatorMessage, workspaceId: string): OperatorAssetRef[]\n read?(ctx: OperatorContext<Identity>, workspaceId: string, assetId: string): Promise<Response | null>\n }\n scorecard?(ctx: OperatorContext<Identity>, workspaceId: string, days: number): Promise<OperatorScorecard>\n /**\n * A reply's failure. Defaults to an `error` notice (the sandbox producer's\n * `errorNotice` mode), a `turn-failure` notice or an `error` part.\n */\n failureOf?(message: ChatOperatorMessage): { code?: string; message: string } | null\n}\n\nconst iso = (value: Date | string | number | null | undefined): string | undefined => {\n if (value === null || value === undefined) return undefined\n const date = value instanceof Date ? value : new Date(typeof value === 'number' && value < 1e12 ? value * 1000 : value)\n return Number.isNaN(date.getTime()) ? undefined : date.toISOString()\n}\n\nconst text = (value: unknown): string | null => (typeof value === 'string' && value.trim() ? value : null)\n\nfunction partsOf(message: ChatOperatorMessage): ReadonlyArray<Record<string, unknown>> {\n return (message.parts ?? []) as ReadonlyArray<Record<string, unknown>>\n}\n\nfunction defaultFailure(message: ChatOperatorMessage): { code?: string; message: string } | null {\n for (const part of partsOf(message)) {\n const errorNotice = part.type === 'notice' && part.noticeKind === 'error'\n const notice = errorNotice || (part.type === 'notice' && (part.noticeKind === 'turn-failure' || part.kind === 'turn-failure'))\n if (!notice && part.type !== 'error') continue\n const detail = text(part.message) ?? text(part.text) ?? text(part.error) ?? 'The turn failed'\n // An error notice always names a reason, so a caller can tell failures apart.\n const code = typeof part.code === 'string' ? part.code : errorNotice ? 'turn.error' : undefined\n return { ...(code ? { code } : {}), message: detail }\n }\n return null\n}\n\nfunction fileChanges(message: ChatOperatorMessage): OperatorFileChange[] {\n const files: OperatorFileChange[] = []\n for (const part of partsOf(message)) {\n if (part.type !== 'session-artifact' || typeof part.path !== 'string') continue\n const action = part.action === 'created' || part.action === 'updated' || part.action === 'deleted' ? part.action : 'changed'\n if (!files.some((file) => file.path === part.path)) files.push({ path: part.path, action })\n }\n return files\n}\n\nfunction turnIdOf(message: ChatOperatorMessage): string | null {\n for (const part of partsOf(message)) {\n if (typeof part.turnId === 'string' && part.turnId) return part.turnId\n }\n return null\n}\n\nasync function sha256Hex(value: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nasync function drain(response: Response, signal: AbortSignal): Promise<void> {\n if (!response.body) return\n const reader = response.body.getReader()\n const stop = () => { void reader.cancel().catch(() => {}) }\n signal.addEventListener('abort', stop, { once: true })\n try {\n while (!(await reader.read()).done) { /* The turn is driven by reading its stream. */ }\n } finally {\n signal.removeEventListener('abort', stop)\n reader.releaseLock()\n }\n}\n\n/**\n * The operator adapter for an app built on agent-app's chat stack: threads and\n * messages from its chat store, turns through its own chat route. The app\n * supplies its workspace roles and, optionally, files, journal, approvals,\n * assets, and a scorecard.\n */\nexport function createChatOperatorAdapter<Identity>(options: ChatOperatorAdapterOptions<Identity>): OperatorAdapter<Identity> {\n const origin = options.origin.replace(/\\/$/, '')\n const threadUrl = (workspaceId: string, threadId: string) =>\n origin + (options.threadPath?.(workspaceId, threadId) ?? `/app/${workspaceId}/chat/${threadId}`)\n const workspaceView = (workspace: ChatOperatorWorkspace): OperatorWorkspace => ({\n id: workspace.id,\n name: workspace.name,\n ...(workspace.role ? { role: workspace.role } : {}),\n url: origin + (options.workspacePath?.(workspace.id) ?? `/app/${workspace.id}`),\n ...(iso(workspace.createdAt) ? { createdAt: iso(workspace.createdAt)! } : {}),\n })\n const threadView = (thread: ChatOperatorThread): OperatorThread => ({\n id: thread.id, title: thread.title, url: threadUrl(thread.workspaceId, thread.id),\n ...(iso(thread.updatedAt) ? { updatedAt: iso(thread.updatedAt)! } : {}),\n })\n\n async function threadIn(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string): Promise<ChatOperatorThread | null> {\n const thread = await options.threads.get(threadId, ctx)\n return thread && thread.workspaceId === workspaceId ? thread : null\n }\n\n async function turnOf(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string, turnId: string): Promise<OperatorTurn | null> {\n const messages = await options.threads.listMessages(threadId, ctx)\n const userIndex = messages.findIndex((message) => message.role === 'user' && messageHasTurnId(message, turnId))\n if (userIndex < 0) return null\n const later = messages.slice(userIndex + 1)\n const nextUser = later.findIndex((message) => message.role === 'user')\n const window = nextUser < 0 ? later : later.slice(0, nextUser)\n const assistant = [...window].reverse().find((message) => message.role === 'assistant') ?? null\n const latest = nextUser < 0\n const running = latest && options.runningTurns ? await options.runningTurns(ctx, workspaceId, threadId) : []\n const status = options.turnStatus ? await options.turnStatus(ctx, workspaceId, threadId, messages[userIndex]!) : undefined\n const terminal = status === undefined || status === 'complete' || status === 'error'\n const turn: OperatorTurn = {\n workspaceId, threadId, turnId, state: 'unknown', url: threadUrl(workspaceId, threadId),\n reply: null, failure: null, assets: [], files: [], approvals: [],\n }\n let state: OperatorTurnState = running === null ? 'unknown' : running.length > 0 ? 'working' : 'unknown'\n if (assistant) {\n turn.files = fileChanges(assistant)\n turn.assets = options.assets?.refs?.(assistant, workspaceId) ?? []\n const model = assistant.servedModel ?? assistant.model\n if (model) turn.model = model\n if (typeof assistant.costUsd === 'number') turn.costUsd = assistant.costUsd\n if (state !== 'working' && terminal) {\n const failure = (options.failureOf ?? defaultFailure)(assistant) ?? (status === 'error' ? { code: 'turn.error', message: 'The turn failed; retained text may be partial' } : null)\n if (failure) {\n state = 'failed'\n turn.failure = failure\n } else if (assistant.content.trim()) {\n state = 'succeeded'\n turn.reply = { content: assistant.content, mediaType: 'text/markdown' }\n }\n const completedAt = iso(assistant.createdAt)\n if (completedAt && (state === 'succeeded' || state === 'failed')) turn.completedAt = completedAt\n }\n } else if (state !== 'working' && terminal && (!latest || status === 'error' || status === 'complete')) {\n state = 'failed'\n turn.failure = { code: status === 'error' ? 'turn.error' : 'turn.no_reply', message: 'The turn ended without a reply' }\n }\n turn.state = state\n if (state !== 'succeeded' && state !== 'failed' && options.approvals) {\n turn.approvals = await options.approvals(ctx, workspaceId, threadId)\n }\n return turn\n }\n\n async function admittedTurn(ctx: OperatorContext<Identity>, workspaceId: string, turnId: string, admission: ChatOperatorAdmission): Promise<OperatorTurn> {\n if (!(await threadIn(ctx, workspaceId, admission.threadId))) throw new OperatorError('operator.turn_identity_conflict', 409)\n const persisted = await turnOf(ctx, workspaceId, admission.threadId, turnId)\n if (persisted) return persisted\n return {\n workspaceId, threadId: admission.threadId, turnId, state: admission.refusal ? 'failed' : 'unknown',\n url: threadUrl(workspaceId, admission.threadId), reply: null, failure: admission.refusal ?? null,\n assets: [], files: [], approvals: [],\n }\n }\n\n async function ensureThread(ctx: OperatorContext<Identity>, workspaceId: string, input: StartTurnInput): Promise<string> {\n const id = (await sha256Hex(`operator-thread\\0${ctx.key.keyId}\\0${workspaceId}\\0${input.turnId}`)).slice(0, 32)\n const existing = await options.threads.get(id, ctx)\n if (existing) {\n if (existing.workspaceId !== workspaceId) throw new OperatorError('operator.turn_identity_conflict', 409, 'This turn id belongs to another conversation')\n return id\n }\n const title = (input.title ?? input.content.split('\\n').find((line) => line.trim()) ?? 'New conversation').trim().slice(0, 80)\n try {\n await options.threads.create(ctx, { id, workspaceId, title })\n } catch (error) {\n // A concurrent retry may have inserted the same id first.\n if (!(await threadIn(ctx, workspaceId, id))) throw error\n }\n return id\n }\n\n const adapter: OperatorAdapter<Identity> = {\n ...(options.acceptsAttachments === true ? { acceptsAttachments: true } : {}),\n async authorizeWorkspace(ctx, workspaceId, access) {\n const workspace = await options.workspaces.authorize(ctx, workspaceId, access)\n return workspace ? workspaceView(workspace) : null\n },\n async listWorkspaces(ctx) {\n return (await options.workspaces.list(ctx)).map(workspaceView)\n },\n async startTurn(ctx, workspace, input) {\n const priorAdmission = await options.admissions?.get(ctx, workspace.id, input.turnId)\n if (priorAdmission) {\n if (input.threadId && input.threadId !== priorAdmission.threadId) throw new OperatorError('operator.turn_identity_conflict', 409)\n return admittedTurn(ctx, workspace.id, input.turnId, priorAdmission)\n }\n let threadId = input.threadId\n if (threadId === undefined) {\n threadId = await ensureThread(ctx, workspace.id, input)\n } else if (!(await threadIn(ctx, workspace.id, threadId))) {\n throw new OperatorError('operator.thread_not_found', 404, 'Thread not found')\n }\n // A retried start whose turn is already recorded reads it instead of running again.\n const existing = await turnOf(ctx, workspace.id, threadId, input.turnId)\n if (existing) return existing\n const target = { workspaceId: workspace.id, threadId, turnId: input.turnId }\n if (options.admissions && !(await options.admissions.claim(ctx, target))) {\n const raced = await options.admissions.get(ctx, workspace.id, input.turnId)\n if (!raced) throw new OperatorError('operator.admission_unavailable', 503, 'Admission state is unavailable', true)\n if (raced.threadId !== threadId) throw new OperatorError('operator.turn_identity_conflict', 409)\n return admittedTurn(ctx, workspace.id, input.turnId, raced)\n }\n const response = await options.runTurn(ctx, {\n workspaceId: workspace.id, threadId, turnId: input.turnId, content: input.content,\n ...(input.model === undefined ? {} : { model: input.model }),\n ...(input.attachments === undefined ? {} : { attachments: input.attachments }),\n })\n if (!response.ok) {\n const body = await response.json().catch(() => ({})) as { error?: unknown; code?: unknown }\n // Apps answer `{ error, code }` or `{ error: { code, message } }`.\n const nested = body.error && typeof body.error === 'object' ? body.error as { code?: unknown; message?: unknown } : {}\n const failure = {\n code: typeof body.code === 'string' ? body.code : typeof nested.code === 'string' ? nested.code : `operator.chat_${response.status}`,\n message: typeof body.error === 'string' ? body.error : typeof nested.message === 'string' ? nested.message : 'The app refused the turn',\n }\n if (options.admissions) {\n await options.admissions.recordRefusal(ctx, target, failure)\n return admittedTurn(ctx, workspace.id, input.turnId, { threadId, refusal: failure })\n }\n throw new OperatorError(failure.code, response.status, failure.message, response.status === 429 || response.status >= 500)\n }\n await drain(response, ctx.request.signal)\n return await turnOf(ctx, workspace.id, threadId, input.turnId) ?? {\n workspaceId: workspace.id, threadId, turnId: input.turnId, state: 'unknown', url: threadUrl(workspace.id, threadId),\n reply: null, failure: null, assets: [], files: [], approvals: [],\n }\n },\n async getTurn(ctx, workspace, target) {\n if (!(await threadIn(ctx, workspace.id, target.threadId))) return null\n const persisted = await turnOf(ctx, workspace.id, target.threadId, target.turnId)\n if (persisted) return persisted\n const admission = await options.admissions?.get(ctx, workspace.id, target.turnId)\n return admission?.threadId === target.threadId ? admittedTurn(ctx, workspace.id, target.turnId, admission) : null\n },\n async getThread(ctx, workspace, threadId) {\n const thread = await threadIn(ctx, workspace.id, threadId)\n if (!thread) return null\n const messages = await options.threads.listMessages(threadId, ctx)\n const lastUser = [...messages].reverse().find((message) => message.role === 'user')\n const turnId = lastUser ? turnIdOf(lastUser) : null\n return { thread: threadView(thread), latestTurn: turnId ? await turnOf(ctx, workspace.id, threadId, turnId) : null }\n },\n listApprovals: (ctx, workspace) => options.approvals ? options.approvals(ctx, workspace.id) : Promise.resolve([]),\n }\n if (options.workspaces.create) {\n const create = options.workspaces.create\n adapter.createWorkspace = async (ctx, input) => workspaceView(await create(ctx, input))\n }\n if (options.threads.list) {\n const list = options.threads.list\n adapter.listThreads = async (ctx, workspace, query) => {\n const page = await list(ctx, workspace.id, query)\n return { threads: page.threads.map(threadView), nextCursor: page.nextCursor }\n }\n }\n if (options.files) {\n const files = options.files\n adapter.listFiles = (ctx, workspace, query) => files.list(ctx, workspace.id, query.prefix)\n adapter.readFile = (ctx, workspace, path) => files.read(ctx, workspace.id, path)\n }\n if (options.journal) {\n const journal = options.journal\n adapter.listJournal = (ctx, workspace, query) => journal(ctx, workspace.id, query.days)\n }\n if (options.assets?.read) {\n const read = options.assets.read\n adapter.readAsset = (ctx, workspace, assetId) => read(ctx, workspace.id, assetId)\n }\n if (options.scorecard) {\n const scorecard = options.scorecard\n adapter.getScorecard = (ctx, workspace, query) => scorecard(ctx, workspace.id, query.days)\n }\n return adapter\n}\n","/**\n * Let the operator API accept a Platform agent key next to the app's own keys.\n *\n * `withPlatformAgentKeys` wraps the app's existing `OperatorKeyStore`: a\n * Bearer key starting `sk-tan-` is verified at Platform for this app's\n * product (see `platform/agent-keys.ts`); every other key goes to the app's\n * own store unchanged, so existing `gak_` keys keep working.\n *\n * Authority for a Platform agent key:\n * - Which actions: only the key's `<product>:operator:read|write|run` scopes\n * (or `*`), mapped to `operator:read|write|run`. A key without any is\n * refused, so a key approved for other products cannot reach this API.\n * - Which user: the key owner's Tangle identity (stable Platform user id and\n * verified email), resolved through the app's own `TangleSsoAccountStore`,\n * the same account policy browser SSO applies. A first call creates the app\n * user exactly as a first browser sign-in would, and links it when the store\n * keeps links per user; an ambiguous or conflicting identity is refused.\n * Nothing in the request names the user.\n * - Which credential: `loadIdentity` receives the agent key for the request,\n * so the app acts with it and the work spends the key's one cap. It is held\n * only for the request, never on the key object.\n * - Which workspaces: the app's ordinary roles, through the operator\n * adapter's `authorizeWorkspace`. The key confers no workspace access.\n */\nimport {\n agentOperatorScopes,\n PLATFORM_KEY_PREFIX,\n type PlatformAgentKeyVerifier,\n} from '../platform/agent-keys.js'\nimport type { RequestApiKey } from '../platform/api-key-auth.js'\nimport type { TangleSsoAccountStore } from '../platform/sso.js'\nimport type { OperatorKeyStore } from './server.js'\n\nconst OPERATOR_ACTIONS = ['read', 'write', 'run'] as const\n\nexport interface PlatformAgentKeyAccess<Identity> {\n verifier: PlatformAgentKeyVerifier\n /** This app's Platform product id, e.g. `gtm-agent`. */\n product: string\n /**\n * The app's browser-SSO account store. Give `saveTangleLink` only when the\n * store keeps the Platform link per user (a link table): a first call then\n * links the user with the agent key, as a first sign-in would. A store that\n * keeps the key on the session row has no session to bind here; omit it and\n * act with `context.platformApiKey` instead.\n */\n accounts: Pick<TangleSsoAccountStore, 'resolveAccount' | 'upsertUserByEmail'>\n & Partial<Pick<TangleSsoAccountStore, 'saveTangleLink'>>\n /**\n * Load the operator identity for an app user id. `context.platformApiKey` is\n * the agent key this request came with: put it on the identity and use it as\n * the request's Platform credential. Never store or log it.\n */\n loadIdentity(userId: string, context: PlatformAgentKeyContext): Promise<Identity | null>\n /** Optional per-key request quota; by default every verified request is admitted. */\n claimRequest?(keyId: string, requestId: string): Promise<{ allowed: boolean; retryAfterSeconds?: number }>\n}\n\n/** What `loadIdentity` receives for a request made with a Platform agent key. */\nexport interface PlatformAgentKeyContext {\n /** The agent key itself, for this request only. */\n platformApiKey: string\n}\n\n/** An operator key that came from Platform agent signup. */\nexport interface PlatformAgentOperatorKey extends RequestApiKey {\n source: 'platform-agent'\n}\n\nfunction refused(status: number, code: string, error: string): Response {\n return Response.json({ error, code }, { status, headers: { 'cache-control': 'private, no-store' } })\n}\n\nfunction isPlatformAgentKey(key: RequestApiKey): key is PlatformAgentOperatorKey {\n return (key as Partial<PlatformAgentOperatorKey>).source === 'platform-agent'\n}\n\nexport function withPlatformAgentKeys<Key extends RequestApiKey, Identity>(\n appKeys: OperatorKeyStore<Key, Identity>,\n access: PlatformAgentKeyAccess<Identity>,\n): OperatorKeyStore<Key | PlatformAgentOperatorKey, Identity> {\n // The raw key for each verified key object, for this request's identity.\n const rawKeys = new WeakMap<PlatformAgentOperatorKey, string>()\n\n async function provisionOwner(verified: { platformUserId: string; email: string; name: string | null }, rawKey: string): Promise<string> {\n const resolution = await access.accounts.resolveAccount({ email: verified.email, platformUserId: verified.platformUserId })\n if (resolution.kind === 'reject') {\n throw refused(403, 'agent_key.account_conflict', `The key owner's Tangle identity conflicts with an account in this app (${resolution.reason}).`)\n }\n // A user already linked to this Platform identity is used as it is: its\n // profile and link belong to the owner's own sign-ins.\n if (resolution.kind === 'existing' && resolution.matchedBy === 'platform-id') return resolution.userId\n // A new or email-matched user is created now, the way a first browser\n // sign-in would. A link-table store also links it, with the agent key as\n // the credential the app acts with later; spend then counts against the\n // key's one cap.\n const { userId } = await access.accounts.upsertUserByEmail({\n email: verified.email,\n name: verified.name,\n tangleUserId: verified.platformUserId,\n resolution,\n })\n await access.accounts.saveTangleLink?.({\n userId,\n sessionToken: '',\n tangleUserId: verified.platformUserId,\n email: verified.email,\n name: verified.name,\n apiKey: rawKey,\n planTier: null,\n })\n return userId\n }\n\n return {\n async verify(authorization) {\n const raw = /^Bearer +(\\S+)$/i.exec(authorization)?.[1]\n if (!raw?.startsWith(PLATFORM_KEY_PREFIX)) return appKeys.verify(authorization)\n const verified = await access.verifier.verify(raw)\n if (!verified.ok) throw refused(verified.status, verified.code, verified.message)\n const scopes = agentOperatorScopes(verified.scopes, access.product, OPERATOR_ACTIONS)\n if (scopes.length === 0) {\n throw refused(403, 'agent_key.product_not_granted', \"The key's owner did not approve it for this app.\")\n }\n const ownerId = await provisionOwner(verified, raw)\n const key: PlatformAgentOperatorKey = {\n keyId: verified.keyId,\n ownerId,\n scopes,\n // The operator API requires a finite expiry. This one bounds the\n // request being authorized; the next request asks Platform again once\n // the verifier's short cache lapses.\n expiresAt: Math.max(verified.validUntil, Date.now() + 30_000),\n source: 'platform-agent',\n }\n rawKeys.set(key, raw)\n return key\n },\n async resolveIdentity(key) {\n if (!isPlatformAgentKey(key)) return appKeys.resolveIdentity(key as Key)\n const platformApiKey = rawKeys.get(key)\n return platformApiKey ? access.loadIdentity(key.ownerId, { platformApiKey }) : null\n },\n async claimRequest(key, requestId) {\n if (!isPlatformAgentKey(key)) return appKeys.claimRequest(key as Key, requestId)\n return access.claimRequest ? access.claimRequest(key.keyId, requestId) : { allowed: true }\n },\n }\n}\n"],"mappings":"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","names":["segment","segment","text","workspace","text","failure"]}
@@ -25,6 +25,13 @@ export interface OperatorAdapter<Identity> {
25
25
  createWorkspace?(ctx: OperatorContext<Identity>, input: {
26
26
  name: string;
27
27
  }): Promise<OperatorWorkspace>;
28
+ /**
29
+ * True when `startTurn` delivers `input.attachments` to the agent, through
30
+ * a chat route whose attachment resolver reads them from the workspace's
31
+ * store. Otherwise the handler refuses turns that carry attachments rather
32
+ * than letting them be dropped.
33
+ */
34
+ acceptsAttachments?: boolean;
28
35
  /** Admit the turn durably and return without waiting for it; the same turnId never starts twice. */
29
36
  startTurn(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, input: StartTurnInput): Promise<OperatorTurn>;
30
37
  getTurn(ctx: OperatorContext<Identity>, workspace: OperatorWorkspace, target: {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createChatTurnRoutes,
3
3
  streamChatRouteAsSandboxEvents
4
- } from "../chunk-J26IXWFK.js";
4
+ } from "../chunk-Q2H5FKVG.js";
5
5
  import "../chunk-Q7WEEN7P.js";
6
6
  import "../chunk-UTAIRVOK.js";
7
7
  import "../chunk-RN4WPTBH.js";
@@ -9,9 +9,9 @@ import "../chunk-3NCPXNBZ.js";
9
9
  import "../chunk-Z4RD5UF7.js";
10
10
  import "../chunk-YG66IHDU.js";
11
11
  import "../chunk-ISP6ZH3G.js";
12
- import "../chunk-TNWDYAFX.js";
13
- import "../chunk-YHRWZZJN.js";
12
+ import "../chunk-E6UTFXKQ.js";
14
13
  import "../chunk-ZVEEWGDK.js";
14
+ import "../chunk-YHRWZZJN.js";
15
15
  import "../chunk-TXD5HXLE.js";
16
16
  import "../chunk-ZGUXI5SI.js";
17
17
  import "../chunk-6VQCFJWP.js";
@@ -143,7 +143,7 @@ import {
143
143
  withoutRecordGridCreated,
144
144
  withoutRecordGridRemoved,
145
145
  withoutRecordGridUpdate
146
- } from "../chunk-Y2SOQOJE.js";
146
+ } from "../chunk-KNTRS7OZ.js";
147
147
  import {
148
148
  MENTION_PILL_CLASS
149
149
  } from "../chunk-3HUFJ5LO.js";
@@ -180,7 +180,7 @@ import {
180
180
  preloadWhenIdle,
181
181
  useComposerAttachments,
182
182
  useDictation
183
- } from "../chunk-MSAGTFN4.js";
183
+ } from "../chunk-6RFNLZCK.js";
184
184
  import "../chunk-L7MB2LLM.js";
185
185
  import {
186
186
  WorkspaceInitial,
@@ -240,7 +240,7 @@ import "../chunk-AVP5SNIG.js";
240
240
  import "../chunk-FQYAJYPW.js";
241
241
  import {
242
242
  ATTACHMENT_ACCEPT
243
- } from "../chunk-WTC6AOPL.js";
243
+ } from "../chunk-PBQYVAB6.js";
244
244
  import "../chunk-VBRUG4N5.js";
245
245
  import "../chunk-B7CL7ENS.js";
246
246
  import "../chunk-PV33JZKV.js";
@@ -252,7 +252,8 @@ import {
252
252
  isChatAttachmentPart,
253
253
  mentionInputToPart,
254
254
  mentionPartsFromMessageParts
255
- } from "../chunk-TNWDYAFX.js";
255
+ } from "../chunk-E6UTFXKQ.js";
256
+ import "../chunk-ZVEEWGDK.js";
256
257
  import {
257
258
  DISPATCH_MAX_MEDIA_PARTS,
258
259
  DISPATCH_MAX_PARTS,
@@ -276,7 +277,6 @@ import {
276
277
  mentionKindForPath,
277
278
  settleTurnResponse
278
279
  } from "../chunk-YHRWZZJN.js";
279
- import "../chunk-ZVEEWGDK.js";
280
280
  import "../chunk-TXD5HXLE.js";
281
281
  import {
282
282
  attachmentPartKey
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.60.191",
3
+ "version": "0.60.192",
4
4
  "packageManager": "pnpm@11.24.0",
5
5
  "description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
6
6
  "keywords": [