@tangle-network/agent-app 0.43.76 → 0.44.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/DesignCanvas-Z362I3R3.js +10 -0
- package/dist/{DesignCanvasEditor-FJIVYOA2.js → DesignCanvasEditor-ZFE2W4OJ.js} +4 -4
- package/dist/assets/index.d.ts +4 -4
- package/dist/assets/index.js +223 -11
- package/dist/assets/index.js.map +1 -1
- package/dist/assistant/index.d.ts +7 -7
- package/dist/assistant/index.js +3 -3
- package/dist/assistant/index.js.map +1 -1
- package/dist/{attachment-validation-Dv1A_Puy.d.ts → attachment-validation-DRuLEjAE.d.ts} +1 -1
- package/dist/{auth-_FU8w01b.d.ts → auth-anc7mv2W.d.ts} +2 -2
- package/dist/chat-routes/index.d.ts +65 -14
- package/dist/chat-routes/index.js +61 -24
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +26 -7
- package/dist/chat-store/index.js +14 -5
- package/dist/chat-store/index.js.map +1 -1
- package/dist/{chunk-ROLFDOMJ.js → chunk-4TI7QWLW.js} +3 -3
- package/dist/{chunk-3JUTOVUH.js → chunk-6KMAM7BL.js} +1 -1
- package/dist/chunk-6KMAM7BL.js.map +1 -0
- package/dist/{chunk-WL7XHLDK.js → chunk-ICOHEZK6.js} +77 -5
- package/dist/chunk-ICOHEZK6.js.map +1 -0
- package/dist/{chunk-22GLESKE.js → chunk-O63WWYET.js} +2 -4
- package/dist/chunk-O63WWYET.js.map +1 -0
- package/dist/{chunk-E7N67FYI.js → chunk-Q3FONVAK.js} +5 -5
- package/dist/{chunk-E7N67FYI.js.map → chunk-Q3FONVAK.js.map} +1 -1
- package/dist/{chunk-RXOTWZ4G.js → chunk-TKRI463Z.js} +4 -4
- package/dist/chunk-TKRI463Z.js.map +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +123 -4
- package/dist/config/index.js.map +1 -1
- package/dist/{contract-B3h7peV3.d.ts → contract-CEewO6DI.d.ts} +1 -1
- package/dist/design-canvas/index.d.ts +4 -4
- package/dist/design-canvas-react/index.js +7 -7
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/eval/index.d.ts +1 -1
- package/dist/eval/index.js +22 -8
- package/dist/eval/index.js.map +1 -1
- package/dist/{flow-types-CqomVAUN.d.ts → flow-types-CJxEmaRy.d.ts} +1 -1
- package/dist/integrations/index.js +67 -5
- package/dist/integrations/index.js.map +1 -1
- package/dist/interactions/index.d.ts +6 -4
- package/dist/interactions/index.js +1 -2
- package/dist/knowledge/index.js +63 -4
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge-loop/index.d.ts +1 -1
- package/dist/knowledge-loop/index.js +94 -4
- package/dist/knowledge-loop/index.js.map +1 -1
- package/dist/{mcp-BjPqQ-3C.d.ts → mcp-D3qVzbE1.d.ts} +2 -2
- package/dist/{mcp-rpc-BfUOsalw.d.ts → mcp-rpc-CzU5LWWT.d.ts} +1 -1
- package/dist/missions/index.js +1143 -23
- package/dist/missions/index.js.map +1 -1
- package/dist/{model-DmdkIteM.d.ts → model-CdCDfBA9.d.ts} +1 -1
- package/dist/object-store/index.js +113 -8
- package/dist/object-store/index.js.map +1 -1
- package/dist/{parts-2ymE5cs-.d.ts → parts-Co_lFgLI.d.ts} +3 -3
- package/dist/platform/index.d.ts +1 -1
- package/dist/preflight/cli.d.ts +13 -0
- package/dist/preflight/cli.js +50 -0
- package/dist/preflight/cli.js.map +1 -0
- package/dist/preset-cloudflare/index.d.ts +1 -1
- package/dist/preset-cloudflare/index.js +284 -11
- package/dist/preset-cloudflare/index.js.map +1 -1
- package/dist/{queue-C24V13h9.d.ts → queue-CD5-_VTX.d.ts} +1 -1
- package/dist/redact/index.js +116 -8
- package/dist/redact/index.js.map +1 -1
- package/dist/runtime/index.d.ts +19 -111
- package/dist/runtime/index.js +234 -14
- package/dist/runtime/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +3 -3
- package/dist/sandbox/index.js +2 -3
- package/dist/sequences/index.d.ts +4 -4
- package/dist/stream/index.d.ts +4 -4
- package/dist/{stream-normalizer-QYUl4vnl.d.ts → stream-normalizer-DnuqkZvw.d.ts} +2 -2
- package/dist/tangle/index.js +42 -4
- package/dist/tangle/index.js.map +1 -1
- package/dist/teams/index.js +5 -5
- package/dist/teams/invitations-api.js +4 -4
- package/dist/teams-react/index.js +3 -3
- package/dist/theme/index.js +0 -1
- package/dist/tools/index.d.ts +6 -6
- package/dist/tools/index.js +5 -7
- package/dist/trace/index.d.ts +1 -1
- package/dist/trace/index.js +179 -10
- package/dist/trace/index.js.map +1 -1
- package/dist/{turn-buffer-BaSgxVmK.d.ts → turn-buffer-BcPfhr1_.d.ts} +1 -1
- package/dist/turn-stream/index.d.ts +1 -1
- package/dist/{types-CBRyqijY.d.ts → types-BCxK0wyS.d.ts} +1 -1
- package/dist/{types-CEchbvgz.d.ts → types-XIZqIdX1.d.ts} +1 -1
- package/dist/web-react/index.d.ts +10 -10
- package/dist/web-react/index.js +5 -5
- package/dist/work-product/index.d.ts +4 -4
- package/dist/work-product/index.js +735 -15
- package/dist/work-product/index.js.map +1 -1
- package/dist/work-product-react/index.d.ts +1 -1
- package/dist/work-product-react/index.js.map +1 -1
- package/package.json +4 -19
- package/dist/DesignCanvas-UNL34LS2.js +0 -12
- package/dist/chunk-22GLESKE.js.map +0 -1
- package/dist/chunk-3JUTOVUH.js.map +0 -1
- package/dist/chunk-5EQCITY3.js +0 -20
- package/dist/chunk-5EQCITY3.js.map +0 -1
- package/dist/chunk-5SD6HT2V.js +0 -236
- package/dist/chunk-5SD6HT2V.js.map +0 -1
- package/dist/chunk-5TESA4SN.js +0 -74
- package/dist/chunk-5TESA4SN.js.map +0 -1
- package/dist/chunk-6GQ5DKFK.js +0 -920
- package/dist/chunk-6GQ5DKFK.js.map +0 -1
- package/dist/chunk-7ESQUSAC.js +0 -343
- package/dist/chunk-7ESQUSAC.js.map +0 -1
- package/dist/chunk-7LNGJDNA.js +0 -1
- package/dist/chunk-7LNGJDNA.js.map +0 -1
- package/dist/chunk-AP2UXKUK.js +0 -69
- package/dist/chunk-AP2UXKUK.js.map +0 -1
- package/dist/chunk-FA4XR66Y.js +0 -32
- package/dist/chunk-FA4XR66Y.js.map +0 -1
- package/dist/chunk-FKCB5FTD.js +0 -126
- package/dist/chunk-FKCB5FTD.js.map +0 -1
- package/dist/chunk-GPBRL2XR.js +0 -102
- package/dist/chunk-GPBRL2XR.js.map +0 -1
- package/dist/chunk-HRH7ASAG.js +0 -759
- package/dist/chunk-HRH7ASAG.js.map +0 -1
- package/dist/chunk-HYTIHYNV.js +0 -297
- package/dist/chunk-HYTIHYNV.js.map +0 -1
- package/dist/chunk-LGBO24FL.js +0 -190
- package/dist/chunk-LGBO24FL.js.map +0 -1
- package/dist/chunk-NLSSFLLS.js +0 -1168
- package/dist/chunk-NLSSFLLS.js.map +0 -1
- package/dist/chunk-OSMNYPOG.js +0 -129
- package/dist/chunk-OSMNYPOG.js.map +0 -1
- package/dist/chunk-QYOD3K56.js +0 -124
- package/dist/chunk-QYOD3K56.js.map +0 -1
- package/dist/chunk-RXOTWZ4G.js.map +0 -1
- package/dist/chunk-UAKFCMWK.js +0 -1
- package/dist/chunk-UAKFCMWK.js.map +0 -1
- package/dist/chunk-UKTYKG57.js +0 -48
- package/dist/chunk-UKTYKG57.js.map +0 -1
- package/dist/chunk-UOAYS72M.js +0 -80
- package/dist/chunk-UOAYS72M.js.map +0 -1
- package/dist/chunk-WL7XHLDK.js.map +0 -1
- package/dist/composer/index.d.ts +0 -2
- package/dist/composer/index.js +0 -22
- package/dist/composer/index.js.map +0 -1
- package/dist/core-7qIM7svy.d.ts +0 -21
- package/dist/durable-chat/index.d.ts +0 -464
- package/dist/durable-chat/index.js +0 -59
- package/dist/durable-chat/index.js.map +0 -1
- package/dist/index.d.ts +0 -51
- package/dist/index.js +0 -827
- package/dist/index.js.map +0 -1
- /package/dist/{DesignCanvas-UNL34LS2.js.map → DesignCanvas-Z362I3R3.js.map} +0 -0
- /package/dist/{DesignCanvasEditor-FJIVYOA2.js.map → DesignCanvasEditor-ZFE2W4OJ.js.map} +0 -0
- /package/dist/{chunk-ROLFDOMJ.js.map → chunk-4TI7QWLW.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/openai-stream.ts","../src/runtime/loop.ts","../src/runtime/agent.ts","../src/runtime/certified-delivery.ts","../src/runtime/surface-profile.ts"],"sourcesContent":["/**\n * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.\n *\n * `streamAppToolLoop` takes a `streamTurn` seam that yields `LoopEvent`s. A\n * sandboxed agent produces those from its container; a browser/edge copilot\n * instead calls a model directly. The Tangle Router, the tcloud SDK, and most\n * providers all speak the OpenAI Chat Completions streaming shape — so the ONE\n * reusable piece is assembling that stream (content deltas + FRAGMENTED\n * tool-call deltas) into `LoopEvent`s. That assembly is the boilerplate every\n * copilot would re-write (and get wrong — OpenAI streams tool-call arguments in\n * pieces across chunks).\n *\n * This does NOT implement an HTTP client beyond a minimal `fetch` + SSE reader\n * (browser/edge/Node-safe, zero deps). For richer transport use the tcloud SDK\n * or the Vercel AI SDK and pipe their stream through {@link toLoopEvents}.\n */\nimport type { LoopEvent, LoopMessage, LoopToolCall } from './loop'\n\n/** Minimal OpenAI Chat Completions streaming chunk (structural — no `openai` dep). */\nexport interface OpenAIStreamChunk {\n choices?: Array<{\n delta?: {\n content?: string | null\n /** Reasoning deltas — DeepSeek/router use `reasoning_content`; some proxies use `thinking`. */\n reasoning_content?: string | null\n thinking?: string | null\n tool_calls?: Array<{\n index: number\n id?: string\n function?: { name?: string; arguments?: string }\n }>\n }\n finish_reason?: string | null\n }>\n /** Final-chunk token accounting (requires `stream_options.include_usage`). */\n usage?: {\n prompt_tokens?: number\n completion_tokens?: number\n } | null\n}\n\ninterface PartialToolCall {\n id?: string\n name: string\n args: string\n}\n\n/**\n * Map an OpenAI-compat streaming chunk iterator to `LoopEvent`s: each content\n * delta → a `text` event; tool-call deltas are accumulated by index across\n * chunks and emitted as one complete `tool_call` event when the stream finishes\n * (arguments JSON-parsed; an empty/garbled args string yields `{}` rather than\n * throwing). Works for the Tangle Router, tcloud, or any OpenAI-compat source.\n */\nexport async function* toLoopEvents(chunks: AsyncIterable<OpenAIStreamChunk>): AsyncIterable<LoopEvent> {\n const calls = new Map<number, PartialToolCall>()\n for await (const chunk of chunks) {\n // Usage rides the final chunk, which has an empty choices array — handle\n // it before the choice guard.\n if (chunk.usage?.prompt_tokens != null || chunk.usage?.completion_tokens != null) {\n yield {\n type: 'usage',\n usage: {\n promptTokens: chunk.usage.prompt_tokens ?? 0,\n completionTokens: chunk.usage.completion_tokens ?? 0,\n },\n }\n }\n const choice = chunk.choices?.[0]\n if (!choice) continue\n const content = choice.delta?.content\n if (content) yield { type: 'text', text: content }\n const reasoning = choice.delta?.reasoning_content ?? choice.delta?.thinking\n if (reasoning) yield { type: 'reasoning', text: reasoning }\n for (const tc of choice.delta?.tool_calls ?? []) {\n const cur = calls.get(tc.index) ?? { name: '', args: '' }\n if (tc.id) cur.id = tc.id\n if (tc.function?.name) cur.name += tc.function.name\n if (tc.function?.arguments) cur.args += tc.function.arguments\n calls.set(tc.index, cur)\n }\n }\n for (const [, c] of [...calls.entries()].sort((a, b) => a[0] - b[0])) {\n if (!c.name) continue\n yield { type: 'tool_call', call: { toolCallId: c.id, toolName: c.name, args: safeParse(c.args) } satisfies LoopToolCall }\n }\n}\n\nfunction safeParse(s: string): Record<string, unknown> {\n if (!s.trim()) return {}\n try {\n const v = JSON.parse(s)\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : {}\n } catch {\n return {}\n }\n}\n\n/** Define options for configuring an OpenAI-compatible streaming chat turn including API details and tools */\nexport interface OpenAICompatStreamTurnOptions {\n /** OpenAI-compat base URL (e.g. the Tangle Router `https://router.tangle.tools/v1`). */\n baseUrl: string\n apiKey: string\n model: string\n /** OpenAI tool definitions — pass `buildAppToolOpenAITools(taxonomy)` so the\n * model can call the app tools. Omit for a tool-free copilot. */\n tools?: unknown[]\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra body fields (e.g. `max_tokens`). */\n extraBody?: Record<string, unknown>\n}\n\n/**\n * Build a `streamTurn` that calls an OpenAI-compatible `/chat/completions`\n * endpoint (Tangle Router / tcloud / any compat provider) with `stream: true`\n * and yields `LoopEvent`s via {@link toLoopEvents}. Browser/edge/Node-safe —\n * just `fetch` + an SSE reader. Drop straight into `streamAppToolLoop`:\n *\n * const cfg = resolveTangleModelConfig() // or { baseUrl, apiKey, model }\n * streamAppToolLoop({ streamTurn: createOpenAICompatStreamTurn({ ...cfg, tools }), executeToolCall, ... })\n */\nexport function createOpenAICompatStreamTurn(\n opts: OpenAICompatStreamTurnOptions,\n): (messages: LoopMessage[]) => AsyncIterable<LoopEvent> {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const doFetch = opts.fetchImpl ?? fetch\n return (messages) =>\n toLoopEvents(\n streamChatCompletions(doFetch, `${base}/chat/completions`, opts.apiKey, {\n model: opts.model,\n messages,\n stream: true,\n stream_options: { include_usage: true },\n ...(opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {}),\n ...(opts.temperature != null ? { temperature: opts.temperature } : {}),\n ...opts.extraBody,\n }),\n )\n}\n\n/** Stream + parse an OpenAI-compat SSE response into chunks. Tolerates `data:`\n * framing, multi-line buffers, and the terminal `[DONE]`. */\nasync function* streamChatCompletions(\n doFetch: typeof fetch,\n url: string,\n apiKey: string,\n body: Record<string, unknown>,\n): AsyncIterable<OpenAIStreamChunk> {\n const res = await doFetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'text/event-stream' },\n body: JSON.stringify(body),\n })\n if (!res.ok || !res.body) {\n const text = res.body ? await res.text().catch(() => '') : ''\n throw new Error(`OpenAI-compat stream failed (HTTP ${res.status})${text ? `: ${text.slice(0, 200)}` : ''}`)\n }\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) continue\n const data = trimmed.slice(5).trim()\n if (data === '[DONE]') return\n try {\n yield JSON.parse(data) as OpenAIStreamChunk\n } catch {\n /* skip a partial/garbled SSE frame */\n }\n }\n }\n}\n","/**\n * The bounded agent tool-loop — owned by `@tangle-network/agent-runtime`.\n *\n * A model turn may emit tool calls (integration-hub actions, the app tools from\n * `../tools`, delegation). The loop streams a turn, collects the executable tool\n * calls, dispatches each, appends the results to history in OpenAI\n * function-calling shape, and re-runs so the model reads them — bounded by\n * `maxToolTurns`, a wall-clock `deadlineMs`, and a `maxCostUsd` budget.\n *\n * The history shape is the OpenAI function-calling contract: the assistant turn\n * that emitted tool calls is preserved as an `assistant` message carrying its\n * `tool_calls` array, and each result is its own `{ role: 'tool', tool_call_id,\n * content }` message keyed to the call. A strict model (Claude, and any\n * OpenAI-compatible provider that validates tool history) needs this to read its\n * own tool use back; folding results into a `user` message makes such models\n * re-issue the same call in a loop.\n *\n * The loop is substrate-owned (`runToolLoop` / `streamToolLoop`); the app\n * supplies `streamTurn` (wrapping its model endpoint) and `executeToolCall`\n * (routing to its integration + app-tool executors). The app-facing names below\n * are 1:1 aliases of the canonical symbols, kept so this package's consumers and\n * the in-package `createAgentRuntime` read against a single, stable vocabulary.\n *\n * This is the LEAF the runtime barrel and its children both import — keeping the\n * tool-loop vocabulary out of any import cycle. The barrel re-exports it.\n */\n\nexport {\n runToolLoop as runAppToolLoop,\n streamToolLoop as streamAppToolLoop,\n} from '@tangle-network/agent-runtime'\nexport type {\n ToolLoopCall as LoopToolCall,\n ToolLoopAssistantToolCall as LoopAssistantToolCall,\n ToolLoopMessage as LoopMessage,\n ToolLoopEvent,\n ToolLoopStopReason,\n ToolLoopResult,\n RunToolLoopOptions as AppToolLoopOptions,\n StreamToolLoopOptions as StreamAppToolLoopOptions,\n StreamToolLoopYield as StreamLoopYield,\n} from '@tangle-network/agent-runtime'\n\n/**\n * Events the app's OpenAI-compat stream adapter ({@link toLoopEvents}) yields.\n *\n * This is the app's own `Raw` event type for the streaming loop — the canonical\n * `streamToolLoop<Raw>` is generic over it. It widens the substrate's\n * tool-loop event with `reasoning` (DeepSeek/router `reasoning_content` /\n * `thinking` deltas, rendered as thinking sections) and `usage` (per-message\n * token accounting) — neither belongs in the substrate's loop contract, so they\n * stay here. The adapter maps each into the `streamTurn` seam; `text` and\n * `tool_call` drive the loop, `reasoning` / `usage` pass through to the UI.\n */\nexport type LoopEvent =\n | { type: 'text'; text: string }\n | { type: 'reasoning'; text: string }\n | { type: 'tool_call'; call: import('@tangle-network/agent-runtime').ToolLoopCall }\n | { type: 'usage'; usage: { promptTokens: number; completionTokens: number } }\n | { type: 'other'; event: unknown }\n","/**\n * `createAgentRuntime` — the in-process agent core, assembled.\n *\n * The bricks to run an agent turn WITHOUT a sandbox already exist in this\n * package, but a consumer must hand-wire five of them every time: resolve the\n * model config, build the OpenAI tool schemas from the taxonomy, build a\n * `streamTurn` over the model endpoint, build an `executeToolCall` over the\n * product's handlers, and drive `runAppToolLoop` / `streamAppToolLoop` with an\n * `isExecutableTool` predicate. That boilerplate is identical across every\n * sandbox-free surface (an edge/browser copilot, an eval harness, a Node CLI),\n * and getting it subtly wrong — e.g. NOT advertising the tools, so the model\n * never emits a `tool_call` and no side effect ever fires — is exactly the\n * failure that makes a tool-driven agent score zero off-sandbox.\n *\n * This factory bundles those five into one object configured for ONE agent:\n *\n * const runtime = createAgentRuntime({ model, taxonomy, handlers, systemPrompt })\n * const result = await runtime.run(userMessage, { ctx }) // awaitable\n * for await (const y of runtime.stream(userMessage, { ctx })) {…} // streaming\n *\n * The model is advertised the app tools (so it CAN call them); each call is\n * dispatched against the product's `handlers` (so the side effect is real); the\n * `onProduced` hook fires at the real side-effect site (so an eval/UI credits a\n * persisted proposal or artifact). Substrate-free: no `@tangle-network/sandbox`,\n * no Durable Object, no `@tangle-network/agent-runtime` import. The SAME core\n * the Cloudflare Worker runs, runnable anywhere a `fetch` to an OpenAI-compatible\n * endpoint works.\n *\n * Domain stays out: the proposal taxonomy, the handlers, and the system prompt\n * are all injected — the factory knows nothing about insurance, law, tax, etc.\n */\nimport {\n type AppToolHandlers,\n type AppToolContext,\n type AppToolOutcome,\n type AppToolProducedEvent,\n type AppToolTaxonomy,\n} from '../tools/types'\nimport { buildAppToolOpenAITools, isAppToolName } from '../tools/openai'\nimport { createAppToolRuntimeExecutor } from '../tools/runtime'\nimport {\n runAppToolLoop,\n streamAppToolLoop,\n type LoopEvent,\n type LoopMessage,\n type LoopToolCall,\n type StreamLoopYield,\n type ToolLoopEvent,\n type ToolLoopResult,\n} from './loop'\nimport { createOpenAICompatStreamTurn } from './openai-stream'\n\n/** OpenAI-compatible model endpoint (Tangle Router / tcloud / any compat\n * provider). Build from {@link resolveTangleModelConfig} or pass literals. */\nexport interface AgentRuntimeModelConfig {\n baseUrl: string\n apiKey: string\n model: string\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra request-body fields (e.g. `max_tokens`, a `reasoning` block). */\n extraBody?: Record<string, unknown>\n}\n\n/** The agent's resolved profile surfaces for one turn — the things a delivered\n * / certified `AgentProfile` can change. Profile-WIDE on purpose: certified\n * delivery folds prompt-surface + skills into `systemPrompt` AND can add\n * certified `tool` artifacts to `extraTools` (the model's advertised tools is\n * rebuilt when these change). MCP servers / memory / RAG that materialize as\n * files or servers deliver through the sandbox-provisioning seam, not here. */\nexport interface ResolvedAgentProfile {\n systemPrompt: string\n extraTools: unknown[]\n}\n\n/** Define options for creating an agent runtime including model config and optional profile transformation */\nexport interface CreateAgentRuntimeOptions {\n /** The model endpoint the turns stream from. */\n model: AgentRuntimeModelConfig\n /**\n * Optional transform applied to the resolved profile surfaces each turn —\n * the seam for certified-artifact delivery (`createCertifiedDelivery`). It is\n * profile-WIDE (not prompt-only): it returns the effective `systemPrompt` +\n * advertised `extraTools`. Kept generic + injected so this substrate-free core\n * never imports `@tangle-network/agent-runtime`. Fail-closed by contract: an\n * impl that can't reach the plane returns the base surfaces unchanged.\n */\n composeProfile?: (base: ResolvedAgentProfile) => ResolvedAgentProfile | Promise<ResolvedAgentProfile>\n /** The product's proposal taxonomy — advertises `submit_proposal`'s `type`\n * enum to the model and labels the regulated subset on the result. */\n taxonomy: AppToolTaxonomy\n /** Domain handlers persisting each tool to the product's store/vault. */\n handlers: AppToolHandlers\n /** Default agent identity / system prompt. A turn may override it. */\n systemPrompt: string\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.\n * For per-workflow limits use `deadlineMs` or `maxCostUsd` on the loop options. */\n maxToolTurns?: number\n /** Extra OpenAI tool definitions advertised ALONGSIDE the four app tools\n * (e.g. `integration_invoke`). Pair with {@link executeOtherTool}. */\n extraTools?: unknown[]\n /** Execute a tool that is NOT one of the four app tools (e.g. an integration\n * action). Only consulted for names {@link isOtherExecutableTool} accepts. */\n executeOtherTool?: (call: LoopToolCall, ctx: AppToolContext) => Promise<AppToolOutcome>\n /** Which non-app tool names are executable here. Required if {@link executeOtherTool} is set. */\n isOtherExecutableTool?: (toolName: string) => boolean\n}\n\n/** Define options for configuring a single agent turn including context, prior messages, prompts, and event handlers */\nexport interface AgentTurnOptions {\n /** The trusted per-turn context (who/where the turn runs as). */\n ctx: AppToolContext\n /** Prior conversation turns, in order. */\n priorMessages?: Array<{ role: string; content: string }>\n /** Override the factory's default system prompt for this turn. */\n systemPrompt?: string\n /** Fires at the real side-effect site for each produced proposal/artifact. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\n/** Resolve and stream tool execution loops with final results and intermediate events for agent runtime */\nexport interface AgentRuntime {\n /** Run the bounded tool loop to completion; resolve with final text + every\n * executed tool outcome. */\n run(userMessage: string, turn: AgentTurnOptions): Promise<ToolLoopResult>\n /** Stream the bounded tool loop: yields each raw model event and each executed\n * tool result as it happens (for SSE re-emission + telemetry). */\n stream(userMessage: string, turn: AgentTurnOptions): AsyncGenerator<StreamLoopYield<LoopEvent>, void, unknown>\n}\n\n/**\n * Create an in-process agent runtime for one agent. See the module doc for the\n * full rationale; the short version: it advertises the app tools to the model,\n * dispatches each emitted call against `handlers`, and drives the bounded loop —\n * the whole agent core, sandbox-free.\n */\nexport function createAgentRuntime(opts: CreateAgentRuntimeOptions): AgentRuntime {\n if (opts.executeOtherTool && !opts.isOtherExecutableTool) {\n throw new Error('createAgentRuntime: isOtherExecutableTool is required when executeOtherTool is set')\n }\n\n // Tool schemas + the streamTurn are stable across turns — build once. The\n // model MUST be advertised the tools or it never emits a tool_call (the exact\n // failure that scores a tool-driven agent zero off-sandbox).\n const m = opts.model\n const buildStreamTurn = (extraTools: unknown[]) =>\n createOpenAICompatStreamTurn({\n baseUrl: m.baseUrl,\n apiKey: m.apiKey,\n model: m.model,\n tools: [...buildAppToolOpenAITools(opts.taxonomy), ...extraTools],\n temperature: m.temperature,\n fetchImpl: m.fetchImpl,\n extraBody: m.extraBody,\n })\n\n // The advertised tool set is stable across turns UNLESS a delivered profile\n // changes `extraTools` (certified-tool delivery, on the cache-refresh cadence\n // — not per turn). Memoize the streamTurn by the active extraTools identity so\n // it rebuilds only when the certified tools actually change.\n const baseExtraTools = opts.extraTools ?? []\n let activeExtraTools = baseExtraTools\n let activeStreamTurn = buildStreamTurn(baseExtraTools)\n const streamTurnFor = (extraTools: unknown[]) => {\n if (extraTools !== activeExtraTools) {\n activeExtraTools = extraTools\n activeStreamTurn = buildStreamTurn(extraTools)\n }\n return activeStreamTurn\n }\n\n // Resolve the per-turn profile surfaces, applying the optional profile\n // transform (certified-artifact delivery). Profile-wide: system prompt +\n // advertised tools.\n const resolveProfile = async (turn: AgentTurnOptions): Promise<ResolvedAgentProfile> => {\n const base: ResolvedAgentProfile = {\n systemPrompt: turn.systemPrompt ?? opts.systemPrompt,\n extraTools: baseExtraTools,\n }\n return opts.composeProfile ? opts.composeProfile(base) : base\n }\n\n const isExecutableTool = (name: string): boolean =>\n isAppToolName(name) || (opts.isOtherExecutableTool?.(name) ?? false)\n\n const buildExecutor = (turn: AgentTurnOptions) => {\n const appExecutor = createAppToolRuntimeExecutor({\n handlers: opts.handlers,\n taxonomy: opts.taxonomy,\n ctx: turn.ctx,\n onProduced: turn.onProduced,\n })\n return async (call: LoopToolCall): Promise<AppToolOutcome> => {\n if (isAppToolName(call.toolName)) return appExecutor({ toolName: call.toolName, args: call.args })\n if (opts.executeOtherTool && opts.isOtherExecutableTool?.(call.toolName)) {\n return opts.executeOtherTool(call, turn.ctx)\n }\n return { ok: false, code: 'unknown_tool', message: `No executor for tool: ${call.toolName}` }\n }\n }\n\n return {\n async run(userMessage, turn) {\n const profile = await resolveProfile(turn)\n return runAppToolLoop({\n systemPrompt: profile.systemPrompt,\n userMessage,\n priorMessages: turn.priorMessages,\n // The awaitable loop consumes only text + tool_call; the app's UI-only\n // reasoning/usage events ride the substrate's `other` channel.\n streamTurn: narrowToToolLoopEvents(streamTurnFor(profile.extraTools)),\n executeToolCall: buildExecutor(turn),\n isExecutableTool,\n maxToolTurns: opts.maxToolTurns,\n })\n },\n async *stream(userMessage, turn) {\n const profile = await resolveProfile(turn)\n yield* streamAppToolLoop<LoopEvent>({\n systemPrompt: profile.systemPrompt,\n userMessage,\n priorMessages: turn.priorMessages,\n streamTurn: streamTurnFor(profile.extraTools),\n extractText: (ev) => (ev.type === 'text' ? ev.text : ''),\n extractToolCall: (ev) => (ev.type === 'tool_call' ? ev.call : null),\n isExecutableTool,\n executeToolCall: buildExecutor(turn),\n maxToolTurns: opts.maxToolTurns,\n })\n },\n }\n}\n\n/**\n * Adapt the app's rich {@link LoopEvent} stream to the substrate awaitable\n * loop's `ToolLoopEvent` contract. The loop reads only `text` (accumulated into\n * the answer) and `tool_call` (dispatched); the app's UI-only `reasoning` /\n * `usage` events have no awaitable meaning, so they collapse onto the\n * substrate's `other` channel and are ignored by the loop.\n */\nfunction narrowToToolLoopEvents(\n streamTurn: (messages: LoopMessage[]) => AsyncIterable<LoopEvent>,\n): (messages: LoopMessage[]) => AsyncIterable<ToolLoopEvent> {\n return (messages) =>\n (async function* () {\n for await (const ev of streamTurn(messages)) {\n if (ev.type === 'text') yield { type: 'text', text: ev.text }\n else if (ev.type === 'tool_call') yield { type: 'tool_call', call: ev.call }\n else yield { type: 'other', event: ev }\n }\n })()\n}\n","/**\n * `createCertifiedDelivery` — the delivery truck for Tangle Intelligence.\n *\n * Pulls a tenant's CERTIFIED `AgentProfile` from the deployed plane\n * (`GET /v1/profiles/:target/composed`) and applies it to the agent's resolved\n * surfaces each turn, so an approved improvement reaches the running agent with\n * NO redeploy. This is the `composeProfile` transform `createAgentRuntime`\n * accepts — opt-in per product, fail-closed, cached + refreshed.\n *\n * Profile-WIDE by design (not prompt-only): the composed profile carries every\n * promoted artifact type keyed by kind. What's folded where:\n * - `prompt-surface` + `skill` → the system prompt (via `composeCertifiedPrompt`).\n * - `tool` artifacts that carry an OpenAI tool definition → `extraTools`\n * (advertised to the model; the matching executor is supplied by the product\n * via `executeOtherTool`). Until a `tool` artifact carries a runnable def,\n * it is surfaced (see `current()`) but not advertised — advertising a tool\n * with no executor would make the model call into a dead end.\n * - `mcp` / `memory` / `rag` artifacts materialize as servers/files and deliver\n * through the SANDBOX-provisioning seam, not this in-process one. The full\n * certified profile is exposed via `current()` so that seam can consume it.\n *\n * Substrate boundary: THIS module imports `@tangle-network/agent-runtime`; the\n * `createAgentRuntime` core does not (it only consumes the generic transform).\n */\n\nimport {\n composeCertifiedPrompt,\n type CertifiedProfile,\n pullCertified,\n} from '@tangle-network/agent-runtime/intelligence'\nimport type { ResolvedAgentProfile } from './agent'\n\nconst defaultRefreshMs = 300_000\n\n/** Define configuration options for delivering certified artifacts to a specified tenant target */\nexport interface CertifiedDeliveryConfig {\n /** The tenant target whose certified artifacts to deliver (the agent id). */\n target: string\n /** Bearer for the plane. Reads `TANGLE_API_KEY` when omitted. */\n apiKey?: string\n /** Plane base URL. Reads `TANGLE_INTELLIGENCE_URL` then the public plane. */\n baseUrl?: string\n /** Min interval between certified-profile pulls. Default 5m. */\n refreshMs?: number\n /** fetch impl (tests / non-global-fetch runtimes). */\n fetchImpl?: typeof fetch\n}\n\n/** Resolve and manage certified profiles with refresh and composition capabilities */\nexport interface CertifiedDelivery {\n /** The `composeProfile` transform to pass to `createAgentRuntime`. Applies the\n * cached certified profile to the base surfaces; refreshes on the cadence. */\n composeProfile(base: ResolvedAgentProfile): Promise<ResolvedAgentProfile>\n /** Force a pull now (ignores the refresh window). Best-effort. */\n refresh(): Promise<void>\n /** The certified profile currently in effect (null = none promoted / pull\n * failed). Lets the sandbox-provisioning seam deliver the file/server\n * artifact types this in-process seam doesn't. */\n current(): CertifiedProfile | null\n}\n\n/**\n * Build a certified-delivery transform for one agent target. Fail-closed: a pull\n * error or 404 keeps the last-known certified profile (or null), and the agent\n * runs on its base surfaces — it never breaks because Intelligence is down.\n */\nexport function createCertifiedDelivery(config: CertifiedDeliveryConfig): CertifiedDelivery {\n const refreshMs = config.refreshMs ?? defaultRefreshMs\n let certified: CertifiedProfile | null = null\n let lastPullAt = 0\n let inflight: Promise<void> | null = null\n\n async function refresh(force = false): Promise<void> {\n if (!force && Date.now() - lastPullAt < refreshMs) return\n if (inflight) return inflight\n inflight = (async () => {\n const outcome = await pullCertified({\n target: config.target,\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n fetchImpl: config.fetchImpl,\n })\n lastPullAt = Date.now()\n // Only replace on a real pull; a 404/error keeps the last-known profile.\n if (outcome.succeeded) certified = outcome.value\n })()\n try {\n await inflight\n } finally {\n inflight = null\n }\n }\n\n return {\n async composeProfile(base) {\n await refresh()\n return {\n // prompt-surface + skill fold into the system prompt.\n systemPrompt: composeCertifiedPrompt(base.systemPrompt, certified),\n // Certified `tool` artifacts deliver here once they carry a runnable\n // OpenAI def + the product wires the executor; until then pass through.\n extraTools: base.extraTools,\n }\n },\n refresh: () => refresh(true),\n current: () => certified,\n }\n}\n","/**\n * Surface-scoped profile overlay — the seam letting any product page (a\n * sequence editor, a brief composer, a dataset view) add MCP servers, a prompt\n * addendum, and permission tightening to the workspace agent profile for turns\n * initiated FROM that surface, without the chat orchestrator knowing any\n * surface's specifics. The orchestrator resolves `(kind, ctx)` through a\n * registry the REQUEST HANDLER constructs per request (construction is a Map\n * build — cheap) and merges the result into the base profile it was about to\n * send to the sandbox. Per-request construction is the trust mechanism, not an\n * optimization target: each `build()` closes over server-trusted request state\n * (env bindings, secrets, the AUTHENTICATED user/workspace), which on Workers\n * exists only per request — a startup-built registry would force identity\n * through the untrusted client `ctx`.\n *\n * SECURITY INVARIANT: the surface `kind` and the ids inside `ctx` arrive on\n * the client request and are pure ROUTING data — never trusted content, never\n * identity. Identity comes from the closure (see above). The registered\n * `build()` runs server-side only: it validates the routing ids against the\n * product's access control, then mints its own URLs and capability tokens from\n * server configuration (`buildHttpMcpServer` + `createCapabilityToken` in\n * ../tools). A client can therefore never inject an arbitrary MCP url, header,\n * or token into the agent profile: the overlay's `mcp` values are typed as\n * {@link SurfaceMcpServer} (= the server-built `AppToolMcpServer` entry shape),\n * and only build() constructs them.\n */\n\nimport type { AppToolMcpServer } from '../tools/mcp'\n\n/** Sandbox permission posture values, ranked deny > ask > allow for merging. */\nexport type SurfacePermissionValue = 'allow' | 'ask' | 'deny'\n\n/** The only MCP entry shape an overlay may carry: the server-built bridge\n * entry from ../tools/mcp (transport, url, headers, and capability token all\n * assembled server-side). The alias exists so overlay authors reach for the\n * builders in ../tools rather than hand-rolling `{ url: ctx.url }` shapes\n * that would let request data become a dialable endpoint. */\nexport type SurfaceMcpServer = AppToolMcpServer\n\n/** What one surface contributes to the agent profile for a single turn. */\nexport interface SurfaceOverlay {\n /** MCP servers to mount for this turn, keyed by tool-routing name. Names\n * must not collide with the base profile's — see {@link mergeSurfaceOverlay}. */\n mcp?: Record<string, SurfaceMcpServer>\n /** Appended to the base system-prompt addendum with a blank-line separator. */\n promptAddendum?: string\n /** Per-key posture the surface wants for its turns. Merging is monotone\n * fail-closed: the stricter of base/overlay wins, so a surface can tighten\n * the workspace posture but never relax it. */\n permissions?: Record<string, SurfacePermissionValue>\n}\n\n/**\n * One registered surface kind. `TCtx` is the shape build() expects — a CLAIM\n * about the client payload, not a guarantee: the registry hands build() the\n * request's `ctx` unvalidated, so build() must treat every field as an\n * untrusted id (resolve it through access control that throws on a bad or\n * foreign id) before minting anything from it.\n */\nexport interface SurfaceKindDefinition<TCtx> {\n kind: string\n build: (ctx: TCtx) => SurfaceOverlay | Promise<SurfaceOverlay>\n}\n\n/** The variance-erased form a registry accepts (`build` is contravariant in\n * `TCtx`, so every concrete definition is assignable to this). */\nexport type AnySurfaceKind = SurfaceKindDefinition<never>\n\n/**\n * Declare one surface kind. The `kind` string is the client-visible routing\n * key (e.g. `'sequences'`); `build` is the server-side factory that turns a\n * validated ctx into the overlay for one turn.\n */\nexport function defineSurfaceKind<TCtx>(opts: {\n kind: string\n build: (ctx: TCtx) => SurfaceOverlay | Promise<SurfaceOverlay>\n}): SurfaceKindDefinition<TCtx> {\n if (typeof opts.kind !== 'string' || opts.kind.length === 0 || /\\s/.test(opts.kind)) {\n throw new Error(`surface kind must be a non-empty string without whitespace (got ${JSON.stringify(opts.kind)})`)\n }\n if (typeof opts.build !== 'function') {\n throw new Error(`surface kind '${opts.kind}' requires a build function`)\n }\n return { kind: opts.kind, build: opts.build }\n}\n\n/** Resolve and build the overlay for a given surface kind within a turn context */\nexport interface SurfaceRegistry {\n /** Build the overlay for one turn. Throws on an unknown kind — an unknown\n * surface is a routing bug (client and server registries drifted), and\n * silently returning an empty overlay would strip the surface's tools from\n * the turn with no signal anywhere. Build errors propagate unwrapped. */\n resolve(kind: string, ctx: unknown): Promise<SurfaceOverlay>\n}\n\n/**\n * Assemble the product's surface registry from its registered kinds. Duplicate\n * kinds throw at construction: two builders behind one routing key would make\n * the mounted toolset depend on registration order.\n */\nexport function createSurfaceRegistry(kinds: readonly AnySurfaceKind[]): SurfaceRegistry {\n const byKind = new Map<string, AnySurfaceKind>()\n for (const definition of kinds) {\n if (byKind.has(definition.kind)) {\n throw new Error(`duplicate surface kind '${definition.kind}' — each kind must be registered exactly once`)\n }\n byKind.set(definition.kind, definition)\n }\n\n return {\n async resolve(kind, ctx) {\n const definition = byKind.get(kind)\n if (!definition) {\n const known = [...byKind.keys()].join(', ') || '(none)'\n throw new Error(\n `unknown surface kind '${kind}' — registered kinds: ${known}. ` +\n 'An unknown surface is a routing bug: register the kind via defineSurfaceKind before clients can reference it.',\n )\n }\n // The trust boundary where static ctx typing ends: the request payload is\n // handed to build() as-is, and build() validates it (see SurfaceKindDefinition).\n const overlay = await definition.build(ctx as never)\n assertSurfaceOverlay(overlay, `surface kind '${kind}'`)\n return overlay\n },\n }\n}\n\n/** Base-profile slice the merge reads/writes. Real callers pass their full\n * profile object; every field outside this slice passes through untouched. */\nexport interface SurfaceMergeBase {\n mcp?: Record<string, unknown>\n systemPromptAddendum?: string\n permissions?: Record<string, SurfacePermissionValue>\n}\n\nconst PERMISSION_SEVERITY: Record<SurfacePermissionValue, number> = { allow: 0, ask: 1, deny: 2 }\n\n/**\n * Merge one surface overlay into a base profile, returning a new object\n * (the base is never mutated; untouched nested records are shared by\n * reference).\n *\n * - `mcp`: overlay servers are added under their own names. A name already\n * present on the base THROWS — a collision is two servers claiming one\n * routing name, and renaming either silently would corrupt tool routing for\n * whichever caller expected the original binding.\n * - `systemPromptAddendum`: the overlay's `promptAddendum` appends after a\n * blank-line separator (no separator when the base has no addendum).\n * - `permissions`: per key the STRICTER value wins (deny > ask > allow). A\n * surface can tighten the base posture for its turns; a base 'deny' survives\n * any overlay.\n */\nexport function mergeSurfaceOverlay<TBase extends SurfaceMergeBase>(\n base: TBase,\n overlay: SurfaceOverlay,\n): TBase & SurfaceMergeBase {\n assertSurfaceOverlay(overlay, 'surface overlay')\n const merged: SurfaceMergeBase = { ...base }\n\n if (overlay.mcp && Object.keys(overlay.mcp).length > 0) {\n const baseMcp = base.mcp ?? {}\n const collisions = Object.keys(overlay.mcp).filter((name) => name in baseMcp)\n if (collisions.length > 0) {\n throw new Error(\n `surface overlay MCP name collision: ${collisions.map((n) => `'${n}'`).join(', ')} already exist on the base profile. ` +\n 'Two servers cannot claim one name — give the surface server a distinct name.',\n )\n }\n merged.mcp = { ...baseMcp, ...overlay.mcp }\n }\n\n if (overlay.promptAddendum !== undefined) {\n merged.systemPromptAddendum = base.systemPromptAddendum\n ? `${base.systemPromptAddendum}\\n\\n${overlay.promptAddendum}`\n : overlay.promptAddendum\n }\n\n if (overlay.permissions && Object.keys(overlay.permissions).length > 0) {\n const permissions: Record<string, SurfacePermissionValue> = { ...(base.permissions ?? {}) }\n for (const [key, value] of Object.entries(overlay.permissions)) {\n const existing = permissions[key]\n permissions[key] =\n existing === undefined || PERMISSION_SEVERITY[value] > PERMISSION_SEVERITY[existing] ? value : existing\n }\n merged.permissions = permissions\n }\n\n // merged began as a shallow copy of base; only the three slice fields were\n // replaced, so the intersection type is the true shape.\n return merged as TBase & SurfaceMergeBase\n}\n\n/** Reject overlays a build() (or hand-rolled caller) malformed, with the exact\n * field named: a relative MCP url, a blank addendum, or an off-vocabulary\n * permission would otherwise surface only as an opaque sandbox failure. */\nfunction assertSurfaceOverlay(overlay: SurfaceOverlay, label: string): void {\n if (overlay.promptAddendum !== undefined) {\n if (typeof overlay.promptAddendum !== 'string' || overlay.promptAddendum.trim().length === 0) {\n throw new Error(`${label}: promptAddendum must be a non-blank string when provided`)\n }\n }\n if (overlay.mcp !== undefined) {\n for (const [name, server] of Object.entries(overlay.mcp)) {\n if (name.trim().length === 0) throw new Error(`${label}: MCP server names must be non-empty`)\n if (server.transport !== 'http') {\n throw new Error(`${label}: MCP server '${name}' must use transport 'http' (got ${JSON.stringify((server as { transport?: unknown }).transport)})`)\n }\n let parsed: URL\n try {\n parsed = new URL(server.url)\n } catch {\n throw new Error(`${label}: MCP server '${name}' url must be an absolute URL (got ${JSON.stringify(server.url)})`)\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`${label}: MCP server '${name}' url must be http(s) (got ${JSON.stringify(server.url)})`)\n }\n }\n }\n if (overlay.permissions !== undefined) {\n for (const [key, value] of Object.entries(overlay.permissions)) {\n if (!(value in PERMISSION_SEVERITY)) {\n throw new Error(`${label}: permission '${key}' must be 'allow' | 'ask' | 'deny' (got ${JSON.stringify(value)})`)\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;AAsDA,gBAAuB,aAAa,QAAoE;AACtG,QAAM,QAAQ,oBAAI,IAA6B;AAC/C,mBAAiB,SAAS,QAAQ;AAGhC,QAAI,MAAM,OAAO,iBAAiB,QAAQ,MAAM,OAAO,qBAAqB,MAAM;AAChF,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,OAAO;AAAA,UACL,cAAc,MAAM,MAAM,iBAAiB;AAAA,UAC3C,kBAAkB,MAAM,MAAM,qBAAqB;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,MAAM,UAAU,CAAC;AAChC,QAAI,CAAC,OAAQ;AACb,UAAM,UAAU,OAAO,OAAO;AAC9B,QAAI,QAAS,OAAM,EAAE,MAAM,QAAQ,MAAM,QAAQ;AACjD,UAAM,YAAY,OAAO,OAAO,qBAAqB,OAAO,OAAO;AACnE,QAAI,UAAW,OAAM,EAAE,MAAM,aAAa,MAAM,UAAU;AAC1D,eAAW,MAAM,OAAO,OAAO,cAAc,CAAC,GAAG;AAC/C,YAAM,MAAM,MAAM,IAAI,GAAG,KAAK,KAAK,EAAE,MAAM,IAAI,MAAM,GAAG;AACxD,UAAI,GAAG,GAAI,KAAI,KAAK,GAAG;AACvB,UAAI,GAAG,UAAU,KAAM,KAAI,QAAQ,GAAG,SAAS;AAC/C,UAAI,GAAG,UAAU,UAAW,KAAI,QAAQ,GAAG,SAAS;AACpD,YAAM,IAAI,GAAG,OAAO,GAAG;AAAA,IACzB;AAAA,EACF;AACA,aAAW,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,MAAM,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG;AACpE,QAAI,CAAC,EAAE,KAAM;AACb,UAAM,EAAE,MAAM,aAAa,MAAM,EAAE,YAAY,EAAE,IAAI,UAAU,EAAE,MAAM,MAAM,UAAU,EAAE,IAAI,EAAE,EAAyB;AAAA,EAC1H;AACF;AAEA,SAAS,UAAU,GAAoC;AACrD,MAAI,CAAC,EAAE,KAAK,EAAG,QAAO,CAAC;AACvB,MAAI;AACF,UAAM,IAAI,KAAK,MAAM,CAAC;AACtB,WAAO,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,IAAK,IAAgC,CAAC;AAAA,EAC7F,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AA0BO,SAAS,6BACd,MACuD;AACvD,QAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC5C,QAAM,UAAU,KAAK,aAAa;AAClC,SAAO,CAAC,aACN;AAAA,IACE,sBAAsB,SAAS,GAAG,IAAI,qBAAqB,KAAK,QAAQ;AAAA,MACtE,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,gBAAgB,EAAE,eAAe,KAAK;AAAA,MACtC,GAAI,KAAK,SAAS,KAAK,MAAM,SAAS,IAAI,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,MACnE,GAAI,KAAK,eAAe,OAAO,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,MACpE,GAAG,KAAK;AAAA,IACV,CAAC;AAAA,EACH;AACJ;AAIA,gBAAgB,sBACd,SACA,KACA,QACA,MACkC;AAClC,QAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,IAC7B,QAAQ;AAAA,IACR,SAAS,EAAE,eAAe,UAAU,MAAM,IAAI,gBAAgB,oBAAoB,QAAQ,oBAAoB;AAAA,IAC9G,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AACD,MAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM;AACxB,UAAM,OAAO,IAAI,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE,IAAI;AAC3D,UAAM,IAAI,MAAM,qCAAqC,IAAI,MAAM,IAAI,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,EAAE;AAAA,EAC5G;AACA,QAAM,SAAS,IAAI,KAAK,UAAU;AAClC,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,SAAS;AACb,aAAS;AACP,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,QAAI,KAAM;AACV,cAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAChD,UAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,aAAS,MAAM,IAAI,KAAK;AACxB,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,QAAQ,WAAW,OAAO,EAAG;AAClC,YAAM,OAAO,QAAQ,MAAM,CAAC,EAAE,KAAK;AACnC,UAAI,SAAS,SAAU;AACvB,UAAI;AACF,cAAM,KAAK,MAAM,IAAI;AAAA,MACvB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;ACxJA;AAAA,EACiB;AAAA,EACG;AAAA,OACb;;;AC0GA,SAAS,mBAAmB,MAA+C;AAChF,MAAI,KAAK,oBAAoB,CAAC,KAAK,uBAAuB;AACxD,UAAM,IAAI,MAAM,oFAAoF;AAAA,EACtG;AAKA,QAAM,IAAI,KAAK;AACf,QAAM,kBAAkB,CAAC,eACvB,6BAA6B;AAAA,IAC3B,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE;AAAA,IACT,OAAO,CAAC,GAAG,wBAAwB,KAAK,QAAQ,GAAG,GAAG,UAAU;AAAA,IAChE,aAAa,EAAE;AAAA,IACf,WAAW,EAAE;AAAA,IACb,WAAW,EAAE;AAAA,EACf,CAAC;AAMH,QAAM,iBAAiB,KAAK,cAAc,CAAC;AAC3C,MAAI,mBAAmB;AACvB,MAAI,mBAAmB,gBAAgB,cAAc;AACrD,QAAM,gBAAgB,CAAC,eAA0B;AAC/C,QAAI,eAAe,kBAAkB;AACnC,yBAAmB;AACnB,yBAAmB,gBAAgB,UAAU;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAKA,QAAM,iBAAiB,OAAO,SAA0D;AACtF,UAAM,OAA6B;AAAA,MACjC,cAAc,KAAK,gBAAgB,KAAK;AAAA,MACxC,YAAY;AAAA,IACd;AACA,WAAO,KAAK,iBAAiB,KAAK,eAAe,IAAI,IAAI;AAAA,EAC3D;AAEA,QAAM,mBAAmB,CAAC,SACxB,cAAc,IAAI,MAAM,KAAK,wBAAwB,IAAI,KAAK;AAEhE,QAAM,gBAAgB,CAAC,SAA2B;AAChD,UAAM,cAAc,6BAA6B;AAAA,MAC/C,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,KAAK,KAAK;AAAA,MACV,YAAY,KAAK;AAAA,IACnB,CAAC;AACD,WAAO,OAAO,SAAgD;AAC5D,UAAI,cAAc,KAAK,QAAQ,EAAG,QAAO,YAAY,EAAE,UAAU,KAAK,UAAU,MAAM,KAAK,KAAK,CAAC;AACjG,UAAI,KAAK,oBAAoB,KAAK,wBAAwB,KAAK,QAAQ,GAAG;AACxE,eAAO,KAAK,iBAAiB,MAAM,KAAK,GAAG;AAAA,MAC7C;AACA,aAAO,EAAE,IAAI,OAAO,MAAM,gBAAgB,SAAS,yBAAyB,KAAK,QAAQ,GAAG;AAAA,IAC9F;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,IAAI,aAAa,MAAM;AAC3B,YAAM,UAAU,MAAM,eAAe,IAAI;AACzC,aAAO,YAAe;AAAA,QACpB,cAAc,QAAQ;AAAA,QACtB;AAAA,QACA,eAAe,KAAK;AAAA;AAAA;AAAA,QAGpB,YAAY,uBAAuB,cAAc,QAAQ,UAAU,CAAC;AAAA,QACpE,iBAAiB,cAAc,IAAI;AAAA,QACnC;AAAA,QACA,cAAc,KAAK;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,IACA,OAAO,OAAO,aAAa,MAAM;AAC/B,YAAM,UAAU,MAAM,eAAe,IAAI;AACzC,aAAO,eAA6B;AAAA,QAClC,cAAc,QAAQ;AAAA,QACtB;AAAA,QACA,eAAe,KAAK;AAAA,QACpB,YAAY,cAAc,QAAQ,UAAU;AAAA,QAC5C,aAAa,CAAC,OAAQ,GAAG,SAAS,SAAS,GAAG,OAAO;AAAA,QACrD,iBAAiB,CAAC,OAAQ,GAAG,SAAS,cAAc,GAAG,OAAO;AAAA,QAC9D;AAAA,QACA,iBAAiB,cAAc,IAAI;AAAA,QACnC,cAAc,KAAK;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AACF;AASA,SAAS,uBACP,YAC2D;AAC3D,SAAO,CAAC,cACL,mBAAmB;AAClB,qBAAiB,MAAM,WAAW,QAAQ,GAAG;AAC3C,UAAI,GAAG,SAAS,OAAQ,OAAM,EAAE,MAAM,QAAQ,MAAM,GAAG,KAAK;AAAA,eACnD,GAAG,SAAS,YAAa,OAAM,EAAE,MAAM,aAAa,MAAM,GAAG,KAAK;AAAA,UACtE,OAAM,EAAE,MAAM,SAAS,OAAO,GAAG;AAAA,IACxC;AAAA,EACF,GAAG;AACP;;;AClOA;AAAA,EACE;AAAA,EAEA;AAAA,OACK;AAGP,IAAM,mBAAmB;AAkClB,SAAS,wBAAwB,QAAoD;AAC1F,QAAM,YAAY,OAAO,aAAa;AACtC,MAAI,YAAqC;AACzC,MAAI,aAAa;AACjB,MAAI,WAAiC;AAErC,iBAAe,QAAQ,QAAQ,OAAsB;AACnD,QAAI,CAAC,SAAS,KAAK,IAAI,IAAI,aAAa,UAAW;AACnD,QAAI,SAAU,QAAO;AACrB,gBAAY,YAAY;AACtB,YAAM,UAAU,MAAM,cAAc;AAAA,QAClC,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO;AAAA,QACf,SAAS,OAAO;AAAA,QAChB,WAAW,OAAO;AAAA,MACpB,CAAC;AACD,mBAAa,KAAK,IAAI;AAEtB,UAAI,QAAQ,UAAW,aAAY,QAAQ;AAAA,IAC7C,GAAG;AACH,QAAI;AACF,YAAM;AAAA,IACR,UAAE;AACA,iBAAW;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,eAAe,MAAM;AACzB,YAAM,QAAQ;AACd,aAAO;AAAA;AAAA,QAEL,cAAc,uBAAuB,KAAK,cAAc,SAAS;AAAA;AAAA;AAAA,QAGjE,YAAY,KAAK;AAAA,MACnB;AAAA,IACF;AAAA,IACA,SAAS,MAAM,QAAQ,IAAI;AAAA,IAC3B,SAAS,MAAM;AAAA,EACjB;AACF;;;ACnCO,SAAS,kBAAwB,MAGR;AAC9B,MAAI,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,WAAW,KAAK,KAAK,KAAK,KAAK,IAAI,GAAG;AACnF,UAAM,IAAI,MAAM,mEAAmE,KAAK,UAAU,KAAK,IAAI,CAAC,GAAG;AAAA,EACjH;AACA,MAAI,OAAO,KAAK,UAAU,YAAY;AACpC,UAAM,IAAI,MAAM,iBAAiB,KAAK,IAAI,6BAA6B;AAAA,EACzE;AACA,SAAO,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM;AAC9C;AAgBO,SAAS,sBAAsB,OAAmD;AACvF,QAAM,SAAS,oBAAI,IAA4B;AAC/C,aAAW,cAAc,OAAO;AAC9B,QAAI,OAAO,IAAI,WAAW,IAAI,GAAG;AAC/B,YAAM,IAAI,MAAM,2BAA2B,WAAW,IAAI,oDAA+C;AAAA,IAC3G;AACA,WAAO,IAAI,WAAW,MAAM,UAAU;AAAA,EACxC;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,MAAM,KAAK;AACvB,YAAM,aAAa,OAAO,IAAI,IAAI;AAClC,UAAI,CAAC,YAAY;AACf,cAAM,QAAQ,CAAC,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI,KAAK;AAC/C,cAAM,IAAI;AAAA,UACR,yBAAyB,IAAI,8BAAyB,KAAK;AAAA,QAE7D;AAAA,MACF;AAGA,YAAM,UAAU,MAAM,WAAW,MAAM,GAAY;AACnD,2BAAqB,SAAS,iBAAiB,IAAI,GAAG;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAUA,IAAM,sBAA8D,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,EAAE;AAiBzF,SAAS,oBACd,MACA,SAC0B;AAC1B,uBAAqB,SAAS,iBAAiB;AAC/C,QAAM,SAA2B,EAAE,GAAG,KAAK;AAE3C,MAAI,QAAQ,OAAO,OAAO,KAAK,QAAQ,GAAG,EAAE,SAAS,GAAG;AACtD,UAAM,UAAU,KAAK,OAAO,CAAC;AAC7B,UAAM,aAAa,OAAO,KAAK,QAAQ,GAAG,EAAE,OAAO,CAAC,SAAS,QAAQ,OAAO;AAC5E,QAAI,WAAW,SAAS,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,uCAAuC,WAAW,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,MAEnF;AAAA,IACF;AACA,WAAO,MAAM,EAAE,GAAG,SAAS,GAAG,QAAQ,IAAI;AAAA,EAC5C;AAEA,MAAI,QAAQ,mBAAmB,QAAW;AACxC,WAAO,uBAAuB,KAAK,uBAC/B,GAAG,KAAK,oBAAoB;AAAA;AAAA,EAAO,QAAQ,cAAc,KACzD,QAAQ;AAAA,EACd;AAEA,MAAI,QAAQ,eAAe,OAAO,KAAK,QAAQ,WAAW,EAAE,SAAS,GAAG;AACtE,UAAM,cAAsD,EAAE,GAAI,KAAK,eAAe,CAAC,EAAG;AAC1F,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,WAAW,GAAG;AAC9D,YAAM,WAAW,YAAY,GAAG;AAChC,kBAAY,GAAG,IACb,aAAa,UAAa,oBAAoB,KAAK,IAAI,oBAAoB,QAAQ,IAAI,QAAQ;AAAA,IACnG;AACA,WAAO,cAAc;AAAA,EACvB;AAIA,SAAO;AACT;AAKA,SAAS,qBAAqB,SAAyB,OAAqB;AAC1E,MAAI,QAAQ,mBAAmB,QAAW;AACxC,QAAI,OAAO,QAAQ,mBAAmB,YAAY,QAAQ,eAAe,KAAK,EAAE,WAAW,GAAG;AAC5F,YAAM,IAAI,MAAM,GAAG,KAAK,2DAA2D;AAAA,IACrF;AAAA,EACF;AACA,MAAI,QAAQ,QAAQ,QAAW;AAC7B,eAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,QAAQ,GAAG,GAAG;AACxD,UAAI,KAAK,KAAK,EAAE,WAAW,EAAG,OAAM,IAAI,MAAM,GAAG,KAAK,sCAAsC;AAC5F,UAAI,OAAO,cAAc,QAAQ;AAC/B,cAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,IAAI,oCAAoC,KAAK,UAAW,OAAmC,SAAS,CAAC,GAAG;AAAA,MACnJ;AACA,UAAI;AACJ,UAAI;AACF,iBAAS,IAAI,IAAI,OAAO,GAAG;AAAA,MAC7B,QAAQ;AACN,cAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,IAAI,sCAAsC,KAAK,UAAU,OAAO,GAAG,CAAC,GAAG;AAAA,MAClH;AACA,UAAI,OAAO,aAAa,WAAW,OAAO,aAAa,UAAU;AAC/D,cAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,IAAI,8BAA8B,KAAK,UAAU,OAAO,GAAG,CAAC,GAAG;AAAA,MAC1G;AAAA,IACF;AAAA,EACF;AACA,MAAI,QAAQ,gBAAgB,QAAW;AACrC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,WAAW,GAAG;AAC9D,UAAI,EAAE,SAAS,sBAAsB;AACnC,cAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,GAAG,2CAA2C,KAAK,UAAU,KAAK,CAAC,GAAG;AAAA,MACjH;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/chunk-7LNGJDNA.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=chunk-7LNGJDNA.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/chunk-AP2UXKUK.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// src/knowledge/index.ts
|
|
2
|
-
function clamp(value) {
|
|
3
|
-
if (!Number.isFinite(value)) return 0;
|
|
4
|
-
if (value < 0) return 0;
|
|
5
|
-
if (value > 1) return 1;
|
|
6
|
-
return value;
|
|
7
|
-
}
|
|
8
|
-
function buildKnowledgeRequirements(specs, signals = {}) {
|
|
9
|
-
return specs.map((spec) => {
|
|
10
|
-
const signal = signals[spec.id];
|
|
11
|
-
return {
|
|
12
|
-
id: spec.id,
|
|
13
|
-
description: spec.description,
|
|
14
|
-
requiredFor: spec.requiredFor ?? [],
|
|
15
|
-
category: spec.category,
|
|
16
|
-
acquisitionMode: spec.acquisitionMode,
|
|
17
|
-
importance: spec.importance ?? "blocking",
|
|
18
|
-
freshness: spec.freshness ?? "static",
|
|
19
|
-
sensitivity: spec.sensitivity ?? "private",
|
|
20
|
-
confidenceNeeded: spec.confidenceNeeded ?? 1,
|
|
21
|
-
currentConfidence: clamp(signal?.confidence ?? 0),
|
|
22
|
-
evidenceIds: signal?.evidence ? [signal.evidence] : [],
|
|
23
|
-
fallbackPolicy: spec.acquisitionMode === "ask_user" ? "ask" : "block"
|
|
24
|
-
};
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
async function deriveSignals(specs, ctx) {
|
|
28
|
-
const out = {};
|
|
29
|
-
for (const spec of specs) {
|
|
30
|
-
if (spec.derive) {
|
|
31
|
-
out[spec.id] = { confidence: clamp(await spec.derive(ctx)), evidence: spec.evidence };
|
|
32
|
-
} else if (spec.satisfiedBy) {
|
|
33
|
-
const ok = await evalRule(spec.satisfiedBy, ctx);
|
|
34
|
-
out[spec.id] = ok ? { confidence: 1, evidence: spec.evidence ?? describeRule(spec.satisfiedBy) } : { confidence: 0 };
|
|
35
|
-
} else {
|
|
36
|
-
out[spec.id] = { confidence: 0 };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return out;
|
|
40
|
-
}
|
|
41
|
-
async function evalRule(rule, ctx) {
|
|
42
|
-
if ("anyOf" in rule) {
|
|
43
|
-
for (const sub of rule.anyOf) if (await evalRule(sub, ctx)) return true;
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
if ("allOf" in rule) {
|
|
47
|
-
for (const sub of rule.allOf) if (!await evalRule(sub, ctx)) return false;
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
if ("config" in rule) {
|
|
51
|
-
const value = ctx.config(rule.config);
|
|
52
|
-
if (rule.nonEmpty) return Array.isArray(value) ? value.length > 0 : value != null && value !== "";
|
|
53
|
-
return value != null && value !== "" && value !== false;
|
|
54
|
-
}
|
|
55
|
-
const rows = await ctx.count({ table: rule.table, where: rule.where, statusIn: rule.statusIn });
|
|
56
|
-
return rows >= (rule.minRows ?? 1);
|
|
57
|
-
}
|
|
58
|
-
function describeRule(rule) {
|
|
59
|
-
if ("anyOf" in rule) return `anyOf(${rule.anyOf.map(describeRule).join(",")})`;
|
|
60
|
-
if ("allOf" in rule) return `allOf(${rule.allOf.map(describeRule).join(",")})`;
|
|
61
|
-
if ("config" in rule) return `config:${rule.config}`;
|
|
62
|
-
return `${rule.table}${rule.statusIn ? `[${rule.statusIn.join("|")}]` : ""}>=${rule.minRows ?? 1}`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export {
|
|
66
|
-
buildKnowledgeRequirements,
|
|
67
|
-
deriveSignals
|
|
68
|
-
};
|
|
69
|
-
//# sourceMappingURL=chunk-AP2UXKUK.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/knowledge/index.ts"],"sourcesContent":["/**\n * Declarative knowledge-requirement gate.\n *\n * Every agent product hand-rolls the same pair: a `buildXKnowledgeRequirements`\n * declaring the requirements that gate its control loop, and a\n * `deriveXRuntimeKnowledge` that scores each one from workspace state. Across\n * the fleet those derives are uniformly \"is a config field set\" / \"are there\n * >= N rows in table T (optionally with a status filter)\" / \"any/all of the\n * above\" — data, not logic. This module makes both DATA: a spec list with\n * declarative `satisfiedBy` rules, plus a per-spec `derive` escape hatch for\n * the rare rule a declarative form can't express (e.g. an aggregate over a\n * JSON column).\n *\n * Substrate-free: the only seam is `KnowledgeStateAccessor` (a config lookup +\n * a row count), which the consumer's backend — or `agent-app/preset-cloudflare`\n * — implements. Emits `@tangle-network/agent-eval`'s `KnowledgeRequirement[]`,\n * exactly what the agent-runtime control loop consumes.\n */\n\nimport type {\n KnowledgeAcquisitionMode,\n KnowledgeFreshness,\n KnowledgeImportance,\n KnowledgeRequirement,\n KnowledgeRequirementCategory,\n KnowledgeSensitivity,\n} from '@tangle-network/agent-eval'\n\n/** A declarative rule for satisfying a requirement from workspace state. */\nexport type SatisfiedByRule =\n /** A workspace-config field (dot-path) is set. `nonEmpty` requires a\n * non-empty array/string rather than mere presence. */\n | { config: string; nonEmpty?: boolean }\n /** At least `minRows` (default 1) rows exist in `table` for the workspace,\n * optionally filtered to `statusIn`. `where` names the workspace fk column\n * the accessor scopes on (default: the accessor's convention). */\n | { table: string; where?: string; statusIn?: string[]; minRows?: number }\n | { anyOf: SatisfiedByRule[] }\n | { allOf: SatisfiedByRule[] }\n\n/** Define the criteria and conditions required to satisfy a specific knowledge requirement */\nexport interface KnowledgeRequirementSpec {\n id: string\n description: string\n category: KnowledgeRequirementCategory\n acquisitionMode: KnowledgeAcquisitionMode\n importance?: KnowledgeImportance\n freshness?: KnowledgeFreshness\n sensitivity?: KnowledgeSensitivity\n confidenceNeeded?: number\n requiredFor?: string[]\n /** The data path — evaluated against the `KnowledgeStateAccessor`. */\n satisfiedBy?: SatisfiedByRule\n /** The escape hatch — a code derive for what a rule can't express. Wins\n * over `satisfiedBy` when both are present. Returns confidence in [0, 1]. */\n derive?: (ctx: KnowledgeStateAccessor) => number | Promise<number>\n /** Evidence id attached when satisfied (default: a description of the rule). */\n evidence?: string\n}\n\n/** The single seam a backend implements. `preset-cloudflare` provides a D1\n * implementation; a custom stack supplies its own. */\nexport interface KnowledgeStateAccessor {\n /** Resolve a workspace-config field value (dot-path), or undefined. */\n config: (path: string) => unknown\n /** Count rows in `table` for the active workspace, optionally status-filtered. */\n count: (query: { table: string; where?: string; statusIn?: string[] }) => number | Promise<number>\n}\n\n/** Define a signal representing knowledge with confidence level and optional supporting evidence */\nexport interface KnowledgeSignal {\n confidence: number\n evidence?: string\n}\n\nfunction clamp(value: number): number {\n if (!Number.isFinite(value)) return 0\n if (value < 0) return 0\n if (value > 1) return 1\n return value\n}\n\n/**\n * Map specs -> the runtime's `KnowledgeRequirement[]`, folding in per-spec\n * confidence from `signals` (default 0). Pure + sync: an eval harness can pass\n * hand-authored signals; production passes the output of {@link deriveSignals}.\n */\nexport function buildKnowledgeRequirements(\n specs: KnowledgeRequirementSpec[],\n signals: Record<string, KnowledgeSignal> = {},\n): KnowledgeRequirement[] {\n return specs.map((spec) => {\n const signal = signals[spec.id]\n return {\n id: spec.id,\n description: spec.description,\n requiredFor: spec.requiredFor ?? [],\n category: spec.category,\n acquisitionMode: spec.acquisitionMode,\n importance: spec.importance ?? 'blocking',\n freshness: spec.freshness ?? 'static',\n sensitivity: spec.sensitivity ?? 'private',\n confidenceNeeded: spec.confidenceNeeded ?? 1,\n currentConfidence: clamp(signal?.confidence ?? 0),\n evidenceIds: signal?.evidence ? [signal.evidence] : [],\n fallbackPolicy: spec.acquisitionMode === 'ask_user' ? 'ask' : 'block',\n }\n })\n}\n\n/**\n * Score every spec from workspace state. `derive` (code) wins; otherwise the\n * declarative `satisfiedBy` rule is evaluated through the accessor; a spec with\n * neither scores 0 (an acquisition gate, e.g. `search_web`).\n */\nexport async function deriveSignals(\n specs: KnowledgeRequirementSpec[],\n ctx: KnowledgeStateAccessor,\n): Promise<Record<string, KnowledgeSignal>> {\n const out: Record<string, KnowledgeSignal> = {}\n for (const spec of specs) {\n if (spec.derive) {\n out[spec.id] = { confidence: clamp(await spec.derive(ctx)), evidence: spec.evidence }\n } else if (spec.satisfiedBy) {\n const ok = await evalRule(spec.satisfiedBy, ctx)\n out[spec.id] = ok\n ? { confidence: 1, evidence: spec.evidence ?? describeRule(spec.satisfiedBy) }\n : { confidence: 0 }\n } else {\n out[spec.id] = { confidence: 0 }\n }\n }\n return out\n}\n\nasync function evalRule(rule: SatisfiedByRule, ctx: KnowledgeStateAccessor): Promise<boolean> {\n if ('anyOf' in rule) {\n for (const sub of rule.anyOf) if (await evalRule(sub, ctx)) return true\n return false\n }\n if ('allOf' in rule) {\n for (const sub of rule.allOf) if (!(await evalRule(sub, ctx))) return false\n return true\n }\n if ('config' in rule) {\n const value = ctx.config(rule.config)\n if (rule.nonEmpty) return Array.isArray(value) ? value.length > 0 : value != null && value !== ''\n return value != null && value !== '' && value !== false\n }\n const rows = await ctx.count({ table: rule.table, where: rule.where, statusIn: rule.statusIn })\n return rows >= (rule.minRows ?? 1)\n}\n\nfunction describeRule(rule: SatisfiedByRule): string {\n if ('anyOf' in rule) return `anyOf(${rule.anyOf.map(describeRule).join(',')})`\n if ('allOf' in rule) return `allOf(${rule.allOf.map(describeRule).join(',')})`\n if ('config' in rule) return `config:${rule.config}`\n return `${rule.table}${rule.statusIn ? `[${rule.statusIn.join('|')}]` : ''}>=${rule.minRows ?? 1}`\n}\n"],"mappings":";AA2EA,SAAS,MAAM,OAAuB;AACpC,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO;AACT;AAOO,SAAS,2BACd,OACA,UAA2C,CAAC,GACpB;AACxB,SAAO,MAAM,IAAI,CAAC,SAAS;AACzB,UAAM,SAAS,QAAQ,KAAK,EAAE;AAC9B,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK,eAAe,CAAC;AAAA,MAClC,UAAU,KAAK;AAAA,MACf,iBAAiB,KAAK;AAAA,MACtB,YAAY,KAAK,cAAc;AAAA,MAC/B,WAAW,KAAK,aAAa;AAAA,MAC7B,aAAa,KAAK,eAAe;AAAA,MACjC,kBAAkB,KAAK,oBAAoB;AAAA,MAC3C,mBAAmB,MAAM,QAAQ,cAAc,CAAC;AAAA,MAChD,aAAa,QAAQ,WAAW,CAAC,OAAO,QAAQ,IAAI,CAAC;AAAA,MACrD,gBAAgB,KAAK,oBAAoB,aAAa,QAAQ;AAAA,IAChE;AAAA,EACF,CAAC;AACH;AAOA,eAAsB,cACpB,OACA,KAC0C;AAC1C,QAAM,MAAuC,CAAC;AAC9C,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,QAAQ;AACf,UAAI,KAAK,EAAE,IAAI,EAAE,YAAY,MAAM,MAAM,KAAK,OAAO,GAAG,CAAC,GAAG,UAAU,KAAK,SAAS;AAAA,IACtF,WAAW,KAAK,aAAa;AAC3B,YAAM,KAAK,MAAM,SAAS,KAAK,aAAa,GAAG;AAC/C,UAAI,KAAK,EAAE,IAAI,KACX,EAAE,YAAY,GAAG,UAAU,KAAK,YAAY,aAAa,KAAK,WAAW,EAAE,IAC3E,EAAE,YAAY,EAAE;AAAA,IACtB,OAAO;AACL,UAAI,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,SAAS,MAAuB,KAA+C;AAC5F,MAAI,WAAW,MAAM;AACnB,eAAW,OAAO,KAAK,MAAO,KAAI,MAAM,SAAS,KAAK,GAAG,EAAG,QAAO;AACnE,WAAO;AAAA,EACT;AACA,MAAI,WAAW,MAAM;AACnB,eAAW,OAAO,KAAK,MAAO,KAAI,CAAE,MAAM,SAAS,KAAK,GAAG,EAAI,QAAO;AACtE,WAAO;AAAA,EACT;AACA,MAAI,YAAY,MAAM;AACpB,UAAM,QAAQ,IAAI,OAAO,KAAK,MAAM;AACpC,QAAI,KAAK,SAAU,QAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS,IAAI,SAAS,QAAQ,UAAU;AAC/F,WAAO,SAAS,QAAQ,UAAU,MAAM,UAAU;AAAA,EACpD;AACA,QAAM,OAAO,MAAM,IAAI,MAAM,EAAE,OAAO,KAAK,OAAO,OAAO,KAAK,OAAO,UAAU,KAAK,SAAS,CAAC;AAC9F,SAAO,SAAS,KAAK,WAAW;AAClC;AAEA,SAAS,aAAa,MAA+B;AACnD,MAAI,WAAW,KAAM,QAAO,SAAS,KAAK,MAAM,IAAI,YAAY,EAAE,KAAK,GAAG,CAAC;AAC3E,MAAI,WAAW,KAAM,QAAO,SAAS,KAAK,MAAM,IAAI,YAAY,EAAE,KAAK,GAAG,CAAC;AAC3E,MAAI,YAAY,KAAM,QAAO,UAAU,KAAK,MAAM;AAClD,SAAO,GAAG,KAAK,KAAK,GAAG,KAAK,WAAW,IAAI,KAAK,SAAS,KAAK,GAAG,CAAC,MAAM,EAAE,KAAK,KAAK,WAAW,CAAC;AAClG;","names":[]}
|
package/dist/chunk-FA4XR66Y.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// src/eval/index.ts
|
|
2
|
-
import { verifyCompletion, extractProducedState, weightedComposite, createLlmCorrectnessChecker } from "@tangle-network/agent-eval";
|
|
3
|
-
function producedFromToolEvents(events) {
|
|
4
|
-
return events.map(
|
|
5
|
-
(e) => e.type === "proposal_created" ? { type: "proposal_created", proposalId: e.proposalId, title: e.title, status: e.status, content: e.content } : { type: "artifact", artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: "text/markdown", content: e.content }
|
|
6
|
-
);
|
|
7
|
-
}
|
|
8
|
-
var STOPWORDS = /* @__PURE__ */ new Set(["the", "a", "an", "and", "or", "for", "to", "of", "in", "on", "with", "review", "update", "new", "proposed"]);
|
|
9
|
-
function createTokenRecallChecker(opts = {}) {
|
|
10
|
-
const minRecall = opts.minRecall ?? 0.5;
|
|
11
|
-
const minLen = opts.minContentLength ?? 120;
|
|
12
|
-
return async (requirement, content) => {
|
|
13
|
-
const body = content.trim();
|
|
14
|
-
if (body.length < minLen) return { correct: false, reason: `content too thin (${body.length} chars) to be the deliverable` };
|
|
15
|
-
const tokens = requirement.title.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t));
|
|
16
|
-
if (tokens.length === 0) return { correct: true, reason: "requirement title has no significant tokens \u2014 structural match accepted" };
|
|
17
|
-
const lower = body.toLowerCase();
|
|
18
|
-
const hits = tokens.filter((t) => lower.includes(t)).length;
|
|
19
|
-
const recall = hits / tokens.length;
|
|
20
|
-
return recall >= minRecall ? { correct: true, reason: `content recalls ${hits}/${tokens.length} requirement tokens` } : { correct: false, reason: `content recalls only ${hits}/${tokens.length} requirement tokens` };
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
producedFromToolEvents,
|
|
26
|
-
createTokenRecallChecker,
|
|
27
|
-
verifyCompletion,
|
|
28
|
-
extractProducedState,
|
|
29
|
-
weightedComposite,
|
|
30
|
-
createLlmCorrectnessChecker
|
|
31
|
-
};
|
|
32
|
-
//# sourceMappingURL=chunk-FA4XR66Y.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/eval/index.ts"],"sourcesContent":["/**\n * Eval — the app-shell BRIDGE to `@tangle-network/agent-eval`, not a reimpl.\n *\n * The completion/scoring ENGINE lives in agent-eval (a peer dependency):\n * `verifyCompletion`, `extractProducedState`, `weightedComposite`,\n * `createLlmCorrectnessChecker`, and the `CompletionRequirement` / `TaskGold` /\n * `ProducedState` types — all re-exported here so a consumer has one import\n * root. This module adds only what agent-eval doesn't have and what is\n * app-shell-specific:\n *\n * 1. {@link producedFromToolEvents} — the bridge: turn the structured app-tool\n * side channel's `AppToolProducedEvent`s (from a tool runtime executor's\n * `onProduced`) into the `RuntimeEventLike`s agent-eval's\n * `extractProducedState` consumes. This is the one piece that knows about\n * the app-tool channel, so it belongs here, not in the engine.\n * 2. {@link createTokenRecallChecker} — a deterministic, no-LLM\n * `CorrectnessChecker` (agent-eval ships only the LLM one). For apps/tests\n * that gate completion without a judge call.\n *\n * Full campaigns (persona simulation, traces, scorecards, held-out gates) are\n * agent-eval's `runEvalCampaign` / `AgentDriver` / `BenchmarkRunner` — use them\n * directly; this module composes with them.\n */\nimport type { RuntimeEventLike, CompletionRequirement } from '@tangle-network/agent-eval'\nimport type { AppToolProducedEvent } from '../tools/types'\n\n// Re-export the engine so consumers import completion + scoring from one place.\nexport { verifyCompletion, extractProducedState, weightedComposite, createLlmCorrectnessChecker } from '@tangle-network/agent-eval'\nexport type {\n CompletionRequirement,\n TaskGold,\n ProducedState,\n SatisfiedBy,\n CompletionVerdict,\n CorrectnessChecker,\n RuntimeEventLike,\n} from '@tangle-network/agent-eval'\n\n/**\n * Bridge the app-tool side channel's produced events into the runtime-event\n * shape agent-eval's `extractProducedState` reads. Pipe it:\n * `verifyCompletion(taskGold, extractProducedState(producedFromToolEvents(events)), checker)`\n */\nexport function producedFromToolEvents(events: readonly AppToolProducedEvent[]): RuntimeEventLike[] {\n return events.map((e) =>\n e.type === 'proposal_created'\n ? { type: 'proposal_created', proposalId: e.proposalId, title: e.title, status: e.status, content: e.content }\n : { type: 'artifact', artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: 'text/markdown', content: e.content },\n )\n}\n\nconst STOPWORDS = new Set(['the', 'a', 'an', 'and', 'or', 'for', 'to', 'of', 'in', 'on', 'with', 'review', 'update', 'new', 'proposed'])\n\n/**\n * A deterministic `CorrectnessChecker` (agent-eval exports only\n * `createLlmCorrectnessChecker`). A produced item fulfils a requirement when\n * its content is substantive and recalls ≥ `minRecall` of the requirement\n * title's significant tokens. No network — the default gate for apps/tests\n * without an LLM judge. Pass to `verifyCompletion` as the checker.\n */\nexport function createTokenRecallChecker(opts: { minRecall?: number; minContentLength?: number } = {}): (\n requirement: CompletionRequirement,\n content: string,\n) => Promise<{ correct: boolean; reason: string }> {\n const minRecall = opts.minRecall ?? 0.5\n const minLen = opts.minContentLength ?? 120\n return async (requirement, content) => {\n const body = content.trim()\n if (body.length < minLen) return { correct: false, reason: `content too thin (${body.length} chars) to be the deliverable` }\n const tokens = requirement.title.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t))\n if (tokens.length === 0) return { correct: true, reason: 'requirement title has no significant tokens — structural match accepted' }\n const lower = body.toLowerCase()\n const hits = tokens.filter((t) => lower.includes(t)).length\n const recall = hits / tokens.length\n return recall >= minRecall\n ? { correct: true, reason: `content recalls ${hits}/${tokens.length} requirement tokens` }\n : { correct: false, reason: `content recalls only ${hits}/${tokens.length} requirement tokens` }\n }\n}\n"],"mappings":";AA2BA,SAAS,kBAAkB,sBAAsB,mBAAmB,mCAAmC;AAgBhG,SAAS,uBAAuB,QAA6D;AAClG,SAAO,OAAO;AAAA,IAAI,CAAC,MACjB,EAAE,SAAS,qBACP,EAAE,MAAM,oBAAoB,YAAY,EAAE,YAAY,OAAO,EAAE,OAAO,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ,IAC3G,EAAE,MAAM,YAAY,YAAY,SAAS,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,KAAK,WAAW,EAAE,IAAI,IAAI,UAAU,iBAAiB,SAAS,EAAE,QAAQ;AAAA,EAC/I;AACF;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,OAAO,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,QAAQ,UAAU,UAAU,OAAO,UAAU,CAAC;AAShI,SAAS,yBAAyB,OAA0D,CAAC,GAGjD;AACjD,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAS,KAAK,oBAAoB;AACxC,SAAO,OAAO,aAAa,YAAY;AACrC,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,KAAK,SAAS,OAAQ,QAAO,EAAE,SAAS,OAAO,QAAQ,qBAAqB,KAAK,MAAM,gCAAgC;AAC3H,UAAM,SAAS,YAAY,MAAM,YAAY,EAAE,MAAM,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;AAClH,QAAI,OAAO,WAAW,EAAG,QAAO,EAAE,SAAS,MAAM,QAAQ,+EAA0E;AACnI,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,OAAO,OAAO,OAAO,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE;AACrD,UAAM,SAAS,OAAO,OAAO;AAC7B,WAAO,UAAU,YACb,EAAE,SAAS,MAAM,QAAQ,mBAAmB,IAAI,IAAI,OAAO,MAAM,sBAAsB,IACvF,EAAE,SAAS,OAAO,QAAQ,wBAAwB,IAAI,IAAI,OAAO,MAAM,sBAAsB;AAAA,EACnG;AACF;","names":[]}
|
package/dist/chunk-FKCB5FTD.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// src/redact/index.ts
|
|
2
|
-
var DEFAULT_REDACTION_PATTERNS = [
|
|
3
|
-
{ kind: "ssn", pattern: /\d{3}-\d{2}-\d{4}/ },
|
|
4
|
-
{ kind: "ein", pattern: /\d{2}-\d{7}/ }
|
|
5
|
-
];
|
|
6
|
-
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
7
|
-
"ssn",
|
|
8
|
-
"ein",
|
|
9
|
-
"password",
|
|
10
|
-
"apikey",
|
|
11
|
-
"token",
|
|
12
|
-
"secret",
|
|
13
|
-
"authorization",
|
|
14
|
-
"email",
|
|
15
|
-
"phone"
|
|
16
|
-
]);
|
|
17
|
-
function redactString(value, patterns) {
|
|
18
|
-
for (const { kind, pattern, validate } of patterns) {
|
|
19
|
-
if (!validate) {
|
|
20
|
-
if (pattern.test(value)) return `[REDACTED:${kind}]`;
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
const g = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
24
|
-
for (const m of value.matchAll(g)) {
|
|
25
|
-
if (m[0].length > 0 && validate(m[0])) return `[REDACTED:${kind}]`;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
function maskSpans(text, patterns = DEFAULT_REDACTION_PATTERNS) {
|
|
31
|
-
const spans = detectSpans(text, patterns);
|
|
32
|
-
if (spans.length === 0) return text;
|
|
33
|
-
let out = "";
|
|
34
|
-
let pos = 0;
|
|
35
|
-
for (const s of spans) {
|
|
36
|
-
if (s.start > pos) out += text.slice(pos, s.start);
|
|
37
|
-
out += `[REDACTED:${s.kind}]`;
|
|
38
|
-
pos = s.end;
|
|
39
|
-
}
|
|
40
|
-
if (pos < text.length) out += text.slice(pos);
|
|
41
|
-
return out;
|
|
42
|
-
}
|
|
43
|
-
function isPlainObject(value) {
|
|
44
|
-
if (value === null || typeof value !== "object") return false;
|
|
45
|
-
const proto = Object.getPrototypeOf(value);
|
|
46
|
-
return proto === Object.prototype || proto === null;
|
|
47
|
-
}
|
|
48
|
-
function redactForIngestion(value, options = {}) {
|
|
49
|
-
const patterns = options.extraPatterns ? [...DEFAULT_REDACTION_PATTERNS, ...options.extraPatterns] : DEFAULT_REDACTION_PATTERNS;
|
|
50
|
-
const sensitiveKeys = options.extraSensitiveKeys ? /* @__PURE__ */ new Set([...SENSITIVE_KEYS, ...options.extraSensitiveKeys.map((k) => k.toLowerCase())]) : SENSITIVE_KEYS;
|
|
51
|
-
const maskString = options.stringMode === "mask-spans" ? (s) => maskSpans(s, patterns) : (s) => redactString(s, patterns);
|
|
52
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
53
|
-
const walk = (v) => {
|
|
54
|
-
if (typeof v === "string") return maskString(v);
|
|
55
|
-
if (Array.isArray(v)) {
|
|
56
|
-
if (seen.has(v)) return v;
|
|
57
|
-
seen.add(v);
|
|
58
|
-
return v.map(walk);
|
|
59
|
-
}
|
|
60
|
-
if (isPlainObject(v)) {
|
|
61
|
-
if (seen.has(v)) return v;
|
|
62
|
-
seen.add(v);
|
|
63
|
-
const out = {};
|
|
64
|
-
for (const [k, val] of Object.entries(v)) {
|
|
65
|
-
out[k] = sensitiveKeys.has(k.toLowerCase()) ? "[REDACTED:field]" : walk(val);
|
|
66
|
-
}
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
return v;
|
|
70
|
-
};
|
|
71
|
-
return walk(value);
|
|
72
|
-
}
|
|
73
|
-
function detectSpans(text, patterns = DEFAULT_REDACTION_PATTERNS) {
|
|
74
|
-
const raw = [];
|
|
75
|
-
for (const { kind, pattern, validate } of patterns) {
|
|
76
|
-
const g = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
77
|
-
for (const m of text.matchAll(g)) {
|
|
78
|
-
if (m.index === void 0 || m[0].length === 0) continue;
|
|
79
|
-
if (validate && !validate(m[0])) continue;
|
|
80
|
-
raw.push({ kind, start: m.index, end: m.index + m[0].length, text: m[0] });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
raw.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
84
|
-
const spans = [];
|
|
85
|
-
let cursor = -1;
|
|
86
|
-
let i = 0;
|
|
87
|
-
for (const s of raw) {
|
|
88
|
-
if (s.start < cursor) continue;
|
|
89
|
-
spans.push({ id: `span-${i++}`, ...s });
|
|
90
|
-
cursor = s.end;
|
|
91
|
-
}
|
|
92
|
-
return spans;
|
|
93
|
-
}
|
|
94
|
-
async function buildRedactedDocument(text, options) {
|
|
95
|
-
const spans = detectSpans(text, options.patterns);
|
|
96
|
-
const segments = [];
|
|
97
|
-
let pos = 0;
|
|
98
|
-
for (const span of spans) {
|
|
99
|
-
if (span.start > pos) segments.push({ type: "text", text: text.slice(pos, span.start) });
|
|
100
|
-
segments.push({ type: "redacted", id: span.id, kind: span.kind, cipher: await options.encrypt(span.text) });
|
|
101
|
-
pos = span.end;
|
|
102
|
-
}
|
|
103
|
-
if (pos < text.length) segments.push({ type: "text", text: text.slice(pos) });
|
|
104
|
-
return { segments };
|
|
105
|
-
}
|
|
106
|
-
async function revealSpan(doc, spanId, options) {
|
|
107
|
-
const seg = doc.segments.find(
|
|
108
|
-
(s) => s.type === "redacted" && s.id === spanId
|
|
109
|
-
);
|
|
110
|
-
if (!seg) return { ok: false, reason: "not_found" };
|
|
111
|
-
const allowed = await options.canReveal({ id: seg.id, kind: seg.kind });
|
|
112
|
-
if (!allowed) return { ok: false, reason: "forbidden" };
|
|
113
|
-
const value = await options.decrypt(seg.cipher);
|
|
114
|
-
if (options.onReveal) await options.onReveal({ id: seg.id, kind: seg.kind });
|
|
115
|
-
return { ok: true, value };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export {
|
|
119
|
-
DEFAULT_REDACTION_PATTERNS,
|
|
120
|
-
maskSpans,
|
|
121
|
-
redactForIngestion,
|
|
122
|
-
detectSpans,
|
|
123
|
-
buildRedactedDocument,
|
|
124
|
-
revealSpan
|
|
125
|
-
};
|
|
126
|
-
//# sourceMappingURL=chunk-FKCB5FTD.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/redact/index.ts"],"sourcesContent":["/**\n * PII redaction — two complementary modes.\n *\n * 1. ONE-WAY scrub (`redactForIngestion`): for production trace payloads. Tool\n * args + results (and once, the LLM span's prompt) cross the wire into the\n * ingestion store, which also feeds the analyst-loop's LLM prompts, so\n * personal identifiers MUST be stripped before they leave the request path.\n * Destructive — the original is gone, replaced by a sentinel.\n *\n * 2. REVERSIBLE redaction (`buildRedactedDocument` / `revealSpan`): for the UI.\n * A document is split into text + redacted segments; each redacted original\n * is kept ENCRYPTED (via a caller-supplied `encrypt` seam → `agent-app/crypto`)\n * so a viewer can reveal a single span on demand, gated by an authorization\n * callback and an audit hook. The mask is presentation; the original is\n * recoverable by an authorized reveal, not lost.\n *\n * Discipline: cheap deterministic string patterns + well-known sensitive object\n * keys (value replaced, key kept, so the shape stays debuggable); recurse arrays\n * + plain objects only; NEVER throw on the one-way path.\n */\n\n/** A named PII pattern. `pattern` is matched case-insensitively at the string\n * level; keep it non-global (global instances are derived where needed). */\nexport interface RedactionPattern {\n kind: string\n pattern: RegExp\n /** Optional predicate over each match — the pattern fires only when it returns\n * true. For matches a regex alone can't decide (e.g. a Luhn check on a\n * card-number candidate). When set, the value is scanned globally and the\n * first match that passes wins; when absent, a plain `pattern.test` decides. */\n validate?: (match: string) => boolean\n}\n\n/** The default deterministic patterns. Extend via the `extraPatterns` /\n * `patterns` options rather than forking this module (the seam that lets a\n * product add e.g. a credit-card matcher without a local copy). */\nexport const DEFAULT_REDACTION_PATTERNS: readonly RedactionPattern[] = [\n { kind: 'ssn', pattern: /\\d{3}-\\d{2}-\\d{4}/ },\n { kind: 'ein', pattern: /\\d{2}-\\d{7}/ },\n]\n\nconst SENSITIVE_KEYS = new Set([\n 'ssn',\n 'ein',\n 'password',\n 'apikey',\n 'token',\n 'secret',\n 'authorization',\n 'email',\n 'phone',\n])\n\n/** Define options to customize sensitive data redaction patterns and key names for ingestion */\nexport interface RedactForIngestionOptions {\n /** Extra patterns appended to {@link DEFAULT_REDACTION_PATTERNS} for the\n * string-level scrub (e.g. credit-card). Additive — defaults still apply. */\n extraPatterns?: readonly RedactionPattern[]\n /** Extra sensitive object-key names (case-insensitive) added to the built-in\n * set, e.g. the snake_case `api_key` an intake form uses. Additive. */\n extraSensitiveKeys?: readonly string[]\n /**\n * How a matched string is rewritten:\n * - `'collapse'` (default) — the whole string becomes `[REDACTED:<kind>]` on\n * the first matching pattern. Safest for telemetry: nothing of the original\n * survives.\n * - `'mask-spans'` — only the matched substrings are replaced (each with\n * `[REDACTED:<kind>]`), preserving surrounding text. Use when a downstream\n * reader needs the non-PII context (e.g. an analyst loop reading prose).\n */\n stringMode?: 'collapse' | 'mask-spans'\n}\n\nfunction redactString(value: string, patterns: readonly RedactionPattern[]): string {\n for (const { kind, pattern, validate } of patterns) {\n if (!validate) {\n if (pattern.test(value)) return `[REDACTED:${kind}]`\n continue\n }\n const g = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)\n for (const m of value.matchAll(g)) {\n if (m[0].length > 0 && validate(m[0])) return `[REDACTED:${kind}]`\n }\n }\n return value\n}\n\n/**\n * Replace only the PII substrings in `text`, preserving everything around them\n * (the `mask-spans` string mode). Built on {@link detectSpans} so matching,\n * non-overlap, and `validate` predicates behave identically to the reversible\n * path. Each span becomes `[REDACTED:<kind>]`.\n */\nexport function maskSpans(\n text: string,\n patterns: readonly RedactionPattern[] = DEFAULT_REDACTION_PATTERNS,\n): string {\n const spans = detectSpans(text, patterns)\n if (spans.length === 0) return text\n let out = ''\n let pos = 0\n for (const s of spans) {\n if (s.start > pos) out += text.slice(pos, s.start)\n out += `[REDACTED:${s.kind}]`\n pos = s.end\n }\n if (pos < text.length) out += text.slice(pos)\n return out\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') return false\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n}\n\n/**\n * One-way PII scrub for telemetry/ingestion. Backward-compatible: called with no\n * options it behaves exactly as before (SSN/EIN strings + sensitive object keys\n * → sentinels). `extraPatterns` lets a product add matchers (e.g. credit-card)\n * without forking this module.\n */\nexport function redactForIngestion(value: unknown, options: RedactForIngestionOptions = {}): unknown {\n const patterns = options.extraPatterns\n ? [...DEFAULT_REDACTION_PATTERNS, ...options.extraPatterns]\n : DEFAULT_REDACTION_PATTERNS\n const sensitiveKeys = options.extraSensitiveKeys\n ? new Set([...SENSITIVE_KEYS, ...options.extraSensitiveKeys.map((k) => k.toLowerCase())])\n : SENSITIVE_KEYS\n const maskString =\n options.stringMode === 'mask-spans'\n ? (s: string) => maskSpans(s, patterns)\n : (s: string) => redactString(s, patterns)\n // Cycle guard: a payload with a circular reference would otherwise recurse\n // forever. On re-encountering an object/array, return it untouched to break\n // the cycle (the same value was already redacted on its first visit).\n const seen = new WeakSet<object>()\n const walk = (v: unknown): unknown => {\n if (typeof v === 'string') return maskString(v)\n if (Array.isArray(v)) {\n if (seen.has(v)) return v\n seen.add(v)\n return v.map(walk)\n }\n if (isPlainObject(v)) {\n if (seen.has(v)) return v\n seen.add(v)\n const out: Record<string, unknown> = {}\n for (const [k, val] of Object.entries(v)) {\n out[k] = sensitiveKeys.has(k.toLowerCase()) ? '[REDACTED:field]' : walk(val)\n }\n return out\n }\n return v\n }\n return walk(value)\n}\n\n// ── Reversible document redaction (the UI path) ─────────────────────────────\n\n/** A detected PII span in a source string. */\nexport interface RedactionSpan {\n /** Stable within a document (index-derived) — used for reveal + audit. */\n id: string\n kind: string\n start: number\n end: number\n text: string\n}\n\n/**\n * Find non-overlapping PII spans in `text`. Matches every pattern, sorts by\n * position, and drops overlaps (first match wins). Deterministic — no ids that\n * vary per call.\n */\nexport function detectSpans(\n text: string,\n patterns: readonly RedactionPattern[] = DEFAULT_REDACTION_PATTERNS,\n): RedactionSpan[] {\n const raw: Array<{ kind: string; start: number; end: number; text: string }> = []\n for (const { kind, pattern, validate } of patterns) {\n const g = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)\n for (const m of text.matchAll(g)) {\n if (m.index === undefined || m[0].length === 0) continue\n if (validate && !validate(m[0])) continue\n raw.push({ kind, start: m.index, end: m.index + m[0].length, text: m[0] })\n }\n }\n raw.sort((a, b) => a.start - b.start || b.end - a.end)\n const spans: RedactionSpan[] = []\n let cursor = -1\n let i = 0\n for (const s of raw) {\n if (s.start < cursor) continue // overlaps an earlier (higher-priority) span\n spans.push({ id: `span-${i++}`, ...s })\n cursor = s.end\n }\n return spans\n}\n\n/** A redacted document segment: literal text, or a masked span with the\n * original kept ENCRYPTED for an authorized reveal. */\nexport type RedactedDocSegment =\n | { type: 'text'; text: string }\n | { type: 'redacted'; id: string; kind: string; cipher: string }\n\n/** Define a document composed of multiple redacted content segments */\nexport interface RedactedDocument {\n segments: RedactedDocSegment[]\n}\n\n/** Define options to encrypt text and specify patterns for redacting sensitive document content */\nexport interface BuildRedactedDocumentOptions {\n /** Encrypt one original span value. Wire it to `agent-app/crypto`\n * (`encryptWithKey` / `createFieldCrypto`). The cipher is what's stored. */\n encrypt: (plaintext: string) => string | Promise<string>\n /** Patterns to detect (default: {@link DEFAULT_REDACTION_PATTERNS}). */\n patterns?: readonly RedactionPattern[]\n}\n\n/**\n * Split `text` into text + redacted segments, encrypting each redacted span's\n * original. The result carries NO plaintext PII — only the masked structure and\n * ciphertext — so it is safe to ship to a client; reveal happens server-side via\n * {@link revealSpan}.\n */\nexport async function buildRedactedDocument(\n text: string,\n options: BuildRedactedDocumentOptions,\n): Promise<RedactedDocument> {\n const spans = detectSpans(text, options.patterns)\n const segments: RedactedDocSegment[] = []\n let pos = 0\n for (const span of spans) {\n if (span.start > pos) segments.push({ type: 'text', text: text.slice(pos, span.start) })\n segments.push({ type: 'redacted', id: span.id, kind: span.kind, cipher: await options.encrypt(span.text) })\n pos = span.end\n }\n if (pos < text.length) segments.push({ type: 'text', text: text.slice(pos) })\n return { segments }\n}\n\n/** Define options to decrypt, authorize, and audit the reveal of a span segment */\nexport interface RevealSpanOptions {\n /** Decrypt a span cipher. Wire to `agent-app/crypto` (`decryptWithKey`). */\n decrypt: (cipher: string) => string | Promise<string>\n /** Authorization gate — return false to deny the reveal (fail-closed). */\n canReveal: (segment: { id: string; kind: string }) => boolean | Promise<boolean>\n /** Audit hook — invoked only on a granted reveal (the caller records who/when). */\n onReveal?: (segment: { id: string; kind: string }) => void | Promise<void>\n}\n\n/** Describe the outcome of a reveal operation including success status, value, and failure reason */\nexport interface RevealResult {\n ok: boolean\n value?: string\n /** `not_found` | `forbidden` when `ok` is false. */\n reason?: string\n}\n\n/**\n * Reveal one redacted span's original, gated + audited. Fail-closed: an unknown\n * id or a denied `canReveal` returns `{ ok: false }` and never decrypts; a\n * granted reveal decrypts, fires `onReveal` for the audit trail, and returns the\n * value.\n */\nexport async function revealSpan(\n doc: RedactedDocument,\n spanId: string,\n options: RevealSpanOptions,\n): Promise<RevealResult> {\n const seg = doc.segments.find((s): s is Extract<RedactedDocSegment, { type: 'redacted' }> =>\n s.type === 'redacted' && s.id === spanId,\n )\n if (!seg) return { ok: false, reason: 'not_found' }\n const allowed = await options.canReveal({ id: seg.id, kind: seg.kind })\n if (!allowed) return { ok: false, reason: 'forbidden' }\n const value = await options.decrypt(seg.cipher)\n if (options.onReveal) await options.onReveal({ id: seg.id, kind: seg.kind })\n return { ok: true, value }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-GPBRL2XR.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// src/knowledge-loop/index.ts
|
|
2
|
-
import {
|
|
3
|
-
runKnowledgeResearchLoop,
|
|
4
|
-
textSourceAdapter
|
|
5
|
-
} from "@tangle-network/agent-knowledge";
|
|
6
|
-
var DEFAULT_MIN_CONFIDENCE = 0.7;
|
|
7
|
-
var DEFAULT_GOAL = "Acquire and ground the knowledge this product requires.";
|
|
8
|
-
function reviewCandidate(candidate, minConfidence) {
|
|
9
|
-
if (!candidate.proposalText) {
|
|
10
|
-
return {
|
|
11
|
-
accepted: true,
|
|
12
|
-
reason: "no-proposal",
|
|
13
|
-
confidence: candidate.confidence ?? 1,
|
|
14
|
-
minConfidence
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
const confidence = candidate.confidence ?? 0;
|
|
18
|
-
if (confidence >= minConfidence) {
|
|
19
|
-
return {
|
|
20
|
-
accepted: true,
|
|
21
|
-
reason: `confidence ${confidence.toFixed(2)} >= minConfidence ${minConfidence.toFixed(2)}`,
|
|
22
|
-
confidence,
|
|
23
|
-
minConfidence
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
accepted: false,
|
|
28
|
-
reason: `confidence ${confidence.toFixed(2)} < minConfidence ${minConfidence.toFixed(2)}`,
|
|
29
|
-
confidence,
|
|
30
|
-
minConfidence
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
function createReviewerDecider(propose) {
|
|
34
|
-
return async (input) => {
|
|
35
|
-
const candidate = await propose(input);
|
|
36
|
-
const verdict = reviewCandidate(candidate, input.minConfidence);
|
|
37
|
-
return { candidate, verdict };
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function toResearchDecision(decision) {
|
|
41
|
-
const { candidate, verdict } = decision;
|
|
42
|
-
return {
|
|
43
|
-
notes: candidate.notes,
|
|
44
|
-
sourcePaths: candidate.sourcePaths,
|
|
45
|
-
sourceTexts: candidate.sourceTexts,
|
|
46
|
-
proposalText: verdict.accepted ? candidate.proposalText : void 0,
|
|
47
|
-
done: candidate.done,
|
|
48
|
-
metadata: {
|
|
49
|
-
...candidate.metadata ?? {},
|
|
50
|
-
gate: verdict
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
var noopDecider = (input) => ({
|
|
55
|
-
candidate: { done: true, notes: "no decider supplied; nothing proposed" },
|
|
56
|
-
verdict: {
|
|
57
|
-
accepted: true,
|
|
58
|
-
reason: "no-proposal",
|
|
59
|
-
confidence: 1,
|
|
60
|
-
minConfidence: input.minConfidence
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
function createKnowledgeLoop(knowledge, deps) {
|
|
64
|
-
const goal = knowledge.loop?.goal ?? deps.defaultGoal ?? DEFAULT_GOAL;
|
|
65
|
-
const minConfidence = knowledge.loop?.minConfidence ?? deps.defaultMinConfidence ?? DEFAULT_MIN_CONFIDENCE;
|
|
66
|
-
const freshness = knowledge.loop?.freshness;
|
|
67
|
-
const adapters = [...deps.adapters ?? [], textSourceAdapter];
|
|
68
|
-
const decide = deps.decide ?? noopDecider;
|
|
69
|
-
const run = () => runKnowledgeResearchLoop({
|
|
70
|
-
root: deps.root,
|
|
71
|
-
goal,
|
|
72
|
-
maxIterations: deps.maxIterations,
|
|
73
|
-
actor: deps.actor,
|
|
74
|
-
signal: deps.signal,
|
|
75
|
-
onStep: deps.onStep,
|
|
76
|
-
sourceOptions: { adapters },
|
|
77
|
-
async step(context) {
|
|
78
|
-
const decision = await decide({
|
|
79
|
-
context,
|
|
80
|
-
goal,
|
|
81
|
-
minConfidence,
|
|
82
|
-
freshness,
|
|
83
|
-
sources: knowledge.sources,
|
|
84
|
-
driver: deps.driver
|
|
85
|
-
});
|
|
86
|
-
return toResearchDecision(decision);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
return {
|
|
90
|
-
run,
|
|
91
|
-
goal,
|
|
92
|
-
minConfidence,
|
|
93
|
-
adapters
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export {
|
|
98
|
-
reviewCandidate,
|
|
99
|
-
createReviewerDecider,
|
|
100
|
-
createKnowledgeLoop
|
|
101
|
-
};
|
|
102
|
-
//# sourceMappingURL=chunk-GPBRL2XR.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/knowledge-loop/index.ts"],"sourcesContent":["/**\n * `@tangle-network/agent-app/knowledge-loop` — wire the declarative\n * `AgentKnowledgeConfig` to a running, source-grounded, eval-gated knowledge\n * acquisition loop.\n *\n * This module does NOT implement a loop. `@tangle-network/agent-knowledge`\n * already ships `runKnowledgeResearchLoop` — the source-grounded,\n * propose-don't-apply primitive (sources become immutable records first; only\n * accepted `---FILE: knowledge/...---` write blocks are applied; lint +\n * validation + optional readiness gate every iteration). It is pluggable on two\n * seams: a `SourceAdapter[]` (how raw bytes/text become curated source records —\n * text by default, audio/video/image adapters added by the consumer) and a\n * `step` decider (the per-iteration policy: an agentic judge, a sandbox run, or\n * a deterministic gate — the loop deliberately bakes none).\n *\n * `createKnowledgeLoop(config.knowledge, deps)` is the thin mapper between the\n * two:\n *\n * - `config.knowledge.sources` → adapter selection. The text adapter is the\n * default; `deps.adapters` is the multimodal seam (prepend an audio/video/\n * image `SourceAdapter` and the loop ingests that media). agent-app bakes no\n * media handler — it's a parameter.\n * - `config.knowledge.loop` → `runKnowledgeResearchLoop` options. `goal` maps\n * to the loop goal, `minConfidence` to the gate threshold, `freshness` is\n * threaded to the decider so a per-source-freshness policy can read it.\n * - `deps.decide` → the pluggable gate. DEFAULT is a reviewer policy\n * (agent-knowledge's propose-don't-apply posture): a candidate carrying\n * confidence below `minConfidence` is gated OUT (its proposal text is\n * dropped — sources are still recorded, because grounding is never the thing\n * we gate); at/above threshold it is accepted and the loop applies the write\n * blocks. Swap in an agentic judge or a sandbox run by passing your own\n * `decide`.\n * - `deps.driver` → the agent-runtime turn driver (this repo's `../runtime`\n * `runAppToolLoop` seam, or any compatible driver) the decider invokes for\n * the loop's agent turns. Optional; a deterministic / sandbox decider needs\n * no model turns and omits it.\n *\n * Layering: agent-knowledge and agent-runtime are PEER dependencies, never\n * bundled. This module imports only TYPES + the loop entry from agent-knowledge\n * and stays substrate-free behind the `decide` / `driver` / `adapters` seams.\n */\n\nimport type {\n AddSourceTextInput,\n KnowledgeResearchLoopContext,\n KnowledgeResearchLoopDecision,\n KnowledgeResearchLoopResult,\n RunKnowledgeResearchLoopOptions,\n SourceAdapter,\n} from '@tangle-network/agent-knowledge'\nimport {\n runKnowledgeResearchLoop,\n textSourceAdapter,\n} from '@tangle-network/agent-knowledge'\nimport type { AgentKnowledgeConfig, KnowledgeSourceSpec } from '../config/index'\n\n/**\n * A research candidate the decider evaluates before the loop applies it. This is\n * the propose-don't-apply unit: notes + the sources discovered this iteration +\n * the proposed write blocks, each carrying a confidence the gate scores against\n * the configured `minConfidence`.\n */\nexport interface KnowledgeCandidate {\n /** Human-readable research transcript for this iteration. */\n notes?: string\n /**\n * Textual source artifacts to register as immutable sources BEFORE any\n * proposal is applied. Recording these is grounding, not the gated step — a\n * rejected candidate still keeps its sources (so the next iteration is better\n * grounded). The decider may not strip these.\n */\n sourceTexts?: AddSourceTextInput[]\n /** Local files to register as immutable sources (same grounding posture). */\n sourcePaths?: string[]\n /**\n * Safe-write-protocol text (`---FILE: knowledge/...---` blocks). This IS the\n * gated step: the default decider drops it when `confidence < minConfidence`.\n */\n proposalText?: string\n /**\n * Aggregate confidence in this candidate's proposal, in [0, 1]. The default\n * reviewer gate compares this to `minConfidence`. A candidate with no\n * `proposalText` needs no confidence (nothing is gated).\n */\n confidence?: number\n /** The researcher's signal that the goal is met; ends the loop. */\n done?: boolean\n metadata?: Record<string, unknown>\n}\n\n/** The verdict a gate returns for one candidate. */\nexport interface KnowledgeGateVerdict {\n /** Whether the candidate's proposal is accepted (applied) or gated out. */\n accepted: boolean\n /** Why — surfaced in the decision metadata for audit. */\n reason: string\n /** The confidence the gate scored (echoed for telemetry). */\n confidence: number\n /** The threshold it was scored against. */\n minConfidence: number\n}\n\n/**\n * The pluggable acquisition policy. Given the loop context (current index, lint,\n * validation, freshness target, the driver), produce a candidate AND a gate\n * verdict on it. This is the seam an agentic judge or a sandbox run plugs into;\n * the default {@link createReviewerDecider} is a confidence gate.\n */\nexport interface KnowledgeDecider {\n (input: KnowledgeDeciderInput): Promise<KnowledgeDecision> | KnowledgeDecision\n}\n\n/** Define the input parameters required to decide knowledge proposals within an agent-knowledge loop */\nexport interface KnowledgeDeciderInput {\n /** The agent-knowledge loop context for this iteration. */\n context: KnowledgeResearchLoopContext\n /** The acquisition goal (from `config.loop.goal` or a deps fallback). */\n goal: string\n /** The confidence threshold a proposal must clear to be applied. */\n minConfidence: number\n /** The freshness target (`config.loop.freshness`), if set. */\n freshness?: string\n /** The configured sources, for a decider that fetches/selects among them. */\n sources: KnowledgeSourceSpec[]\n /** The agent-runtime turn driver, if one was supplied to the loop. */\n driver?: KnowledgeLoopDriver\n}\n\n/** Define a decision containing a candidate and the gate's verdict on that candidate */\nexport interface KnowledgeDecision {\n /** The candidate the policy produced (may be empty to end the loop). */\n candidate: KnowledgeCandidate\n /** The gate's verdict on the candidate's proposal. */\n verdict: KnowledgeGateVerdict\n}\n\n/**\n * The agent-runtime turn driver seam. This is exactly `../runtime`'s\n * `runAppToolLoop` shape (a bounded, awaitable tool-driving turn loop over a\n * model). Typed structurally so a decider can drive the loop's agent turns\n * without this module importing the runtime engine. A deterministic / sandbox\n * decider may omit it.\n */\nexport interface KnowledgeLoopDriver {\n (opts: {\n systemPrompt: string\n userMessage: string\n }): Promise<{ finalText: string }>\n}\n\n/** Define dependencies required to create and run a knowledge processing loop */\nexport interface CreateKnowledgeLoopDeps {\n /**\n * The knowledge-base root the loop reads/writes (an agent-knowledge layout).\n * Required — agent-knowledge owns disk; agent-app owns only the wiring.\n */\n root: string\n /**\n * The per-iteration policy. Defaults to {@link createReviewerDecider} keyed on\n * the config's `minConfidence`. Pass your own to use an agentic judge or a\n * sandbox run.\n */\n decide?: KnowledgeDecider\n /**\n * Extra source adapters (audio/video/image/PDF/...). The text adapter is\n * always present as the fallback; these are tried first so a multimodal\n * source is claimed by its adapter. This is the multimodal seam.\n */\n adapters?: SourceAdapter[]\n /** The agent-runtime turn driver for the loop's agent turns. */\n driver?: KnowledgeLoopDriver\n /**\n * Fallback goal when `config.loop.goal` is unset. agent-knowledge requires a\n * goal; this keeps the loop runnable for a config with only requirement specs.\n */\n defaultGoal?: string\n /** Default confidence threshold when `config.loop.minConfidence` is unset. */\n defaultMinConfidence?: number\n /** Max research iterations (forwarded to agent-knowledge). Default 3. */\n maxIterations?: number\n /** Actor stamped on the loop's knowledge events. */\n actor?: string\n /** Abort the loop. */\n signal?: AbortSignal\n /** Per-step hook (forwarded to agent-knowledge's `onStep`). */\n onStep?: RunKnowledgeResearchLoopOptions['onStep']\n}\n\n/** The handle `createKnowledgeLoop` returns. */\nexport interface KnowledgeLoop {\n /** Run the acquisition loop to completion and return the agent-knowledge result. */\n run(): Promise<KnowledgeResearchLoopResult>\n /** The resolved goal the loop pursues. */\n readonly goal: string\n /** The resolved confidence gate threshold. */\n readonly minConfidence: number\n /** The adapters the loop uses (consumer extras first, text last). */\n readonly adapters: SourceAdapter[]\n}\n\nconst DEFAULT_MIN_CONFIDENCE = 0.7\nconst DEFAULT_GOAL = 'Acquire and ground the knowledge this product requires.'\n\n/**\n * The default gate — agent-knowledge's propose-don't-apply reviewer posture as a\n * confidence threshold. A candidate's `proposalText` is applied only when its\n * `confidence` is at/above `minConfidence`; otherwise the proposal is gated out\n * (sources are still recorded). A candidate with no `proposalText` is trivially\n * accepted (nothing to gate). This is the floor policy; swap in an agentic judge\n * or a sandbox-run decider via `deps.decide` for richer review.\n */\nexport function reviewCandidate(\n candidate: KnowledgeCandidate,\n minConfidence: number,\n): KnowledgeGateVerdict {\n if (!candidate.proposalText) {\n return {\n accepted: true,\n reason: 'no-proposal',\n confidence: candidate.confidence ?? 1,\n minConfidence,\n }\n }\n const confidence = candidate.confidence ?? 0\n if (confidence >= minConfidence) {\n return {\n accepted: true,\n reason: `confidence ${confidence.toFixed(2)} >= minConfidence ${minConfidence.toFixed(2)}`,\n confidence,\n minConfidence,\n }\n }\n return {\n accepted: false,\n reason: `confidence ${confidence.toFixed(2)} < minConfidence ${minConfidence.toFixed(2)}`,\n confidence,\n minConfidence,\n }\n}\n\n/**\n * Wrap a candidate-producing policy in the default reviewer gate. The policy\n * decides WHAT to propose (notes, sources, proposalText, confidence); the gate\n * decides whether the proposal is APPLIED. Use this when you have a proposer but\n * want the standard confidence gate; pass a full {@link KnowledgeDecider} to\n * `deps.decide` to own the gate too.\n */\nexport function createReviewerDecider(\n propose: (\n input: KnowledgeDeciderInput,\n ) => Promise<KnowledgeCandidate> | KnowledgeCandidate,\n): KnowledgeDecider {\n return async (input) => {\n const candidate = await propose(input)\n const verdict = reviewCandidate(candidate, input.minConfidence)\n return { candidate, verdict }\n }\n}\n\n/**\n * Apply a gate verdict to a candidate, producing the agent-knowledge decision.\n * Grounding (sources) always passes through; the gated `proposalText` is dropped\n * when the verdict rejects it. The verdict is recorded in `metadata.gate` so the\n * loop's event stream carries the audit trail.\n */\nfunction toResearchDecision(decision: KnowledgeDecision): KnowledgeResearchLoopDecision {\n const { candidate, verdict } = decision\n return {\n notes: candidate.notes,\n sourcePaths: candidate.sourcePaths,\n sourceTexts: candidate.sourceTexts,\n proposalText: verdict.accepted ? candidate.proposalText : undefined,\n done: candidate.done,\n metadata: {\n ...(candidate.metadata ?? {}),\n gate: verdict,\n },\n }\n}\n\n/**\n * The do-nothing default policy: when no `decide` is supplied, the loop runs\n * with a proposer that proposes nothing and ends immediately. A real product\n * supplies a proposer (agentic judge, sandbox run, or deterministic) via\n * `deps.decide`; this keeps `createKnowledgeLoop` total for a config that only\n * declares sources/requirements without a wired researcher yet.\n */\nconst noopDecider: KnowledgeDecider = (input) => ({\n candidate: { done: true, notes: 'no decider supplied; nothing proposed' },\n verdict: {\n accepted: true,\n reason: 'no-proposal',\n confidence: 1,\n minConfidence: input.minConfidence,\n },\n})\n\n/**\n * Build a runnable knowledge-acquisition loop from the product's\n * `AgentKnowledgeConfig` and a small set of seams. Maps config → agent-knowledge\n * `runKnowledgeResearchLoop` options; never reimplements the loop.\n */\nexport function createKnowledgeLoop(\n knowledge: AgentKnowledgeConfig,\n deps: CreateKnowledgeLoopDeps,\n): KnowledgeLoop {\n const goal = knowledge.loop?.goal ?? deps.defaultGoal ?? DEFAULT_GOAL\n const minConfidence =\n knowledge.loop?.minConfidence ?? deps.defaultMinConfidence ?? DEFAULT_MIN_CONFIDENCE\n const freshness = knowledge.loop?.freshness\n\n // Multimodal seam: consumer adapters are tried first; the text adapter is the\n // always-present fallback (it claims text/.md/.txt/.json/.csv). A config\n // source whose media an extra adapter claims is ingested by that adapter.\n const adapters: SourceAdapter[] = [...(deps.adapters ?? []), textSourceAdapter]\n\n const decide = deps.decide ?? noopDecider\n\n const run = (): Promise<KnowledgeResearchLoopResult> =>\n runKnowledgeResearchLoop({\n root: deps.root,\n goal,\n maxIterations: deps.maxIterations,\n actor: deps.actor,\n signal: deps.signal,\n onStep: deps.onStep,\n sourceOptions: { adapters },\n async step(context) {\n const decision = await decide({\n context,\n goal,\n minConfidence,\n freshness,\n sources: knowledge.sources,\n driver: deps.driver,\n })\n return toResearchDecision(decision)\n },\n })\n\n return {\n run,\n goal,\n minConfidence,\n adapters,\n }\n}\n"],"mappings":";AAkDA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAmJP,IAAM,yBAAyB;AAC/B,IAAM,eAAe;AAUd,SAAS,gBACd,WACA,eACsB;AACtB,MAAI,CAAC,UAAU,cAAc;AAC3B,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,YAAY,UAAU,cAAc;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AACA,QAAM,aAAa,UAAU,cAAc;AAC3C,MAAI,cAAc,eAAe;AAC/B,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ,cAAc,WAAW,QAAQ,CAAC,CAAC,qBAAqB,cAAc,QAAQ,CAAC,CAAC;AAAA,MACxF;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,QAAQ,cAAc,WAAW,QAAQ,CAAC,CAAC,oBAAoB,cAAc,QAAQ,CAAC,CAAC;AAAA,IACvF;AAAA,IACA;AAAA,EACF;AACF;AASO,SAAS,sBACd,SAGkB;AAClB,SAAO,OAAO,UAAU;AACtB,UAAM,YAAY,MAAM,QAAQ,KAAK;AACrC,UAAM,UAAU,gBAAgB,WAAW,MAAM,aAAa;AAC9D,WAAO,EAAE,WAAW,QAAQ;AAAA,EAC9B;AACF;AAQA,SAAS,mBAAmB,UAA4D;AACtF,QAAM,EAAE,WAAW,QAAQ,IAAI;AAC/B,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,aAAa,UAAU;AAAA,IACvB,aAAa,UAAU;AAAA,IACvB,cAAc,QAAQ,WAAW,UAAU,eAAe;AAAA,IAC1D,MAAM,UAAU;AAAA,IAChB,UAAU;AAAA,MACR,GAAI,UAAU,YAAY,CAAC;AAAA,MAC3B,MAAM;AAAA,IACR;AAAA,EACF;AACF;AASA,IAAM,cAAgC,CAAC,WAAW;AAAA,EAChD,WAAW,EAAE,MAAM,MAAM,OAAO,wCAAwC;AAAA,EACxE,SAAS;AAAA,IACP,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,eAAe,MAAM;AAAA,EACvB;AACF;AAOO,SAAS,oBACd,WACA,MACe;AACf,QAAM,OAAO,UAAU,MAAM,QAAQ,KAAK,eAAe;AACzD,QAAM,gBACJ,UAAU,MAAM,iBAAiB,KAAK,wBAAwB;AAChE,QAAM,YAAY,UAAU,MAAM;AAKlC,QAAM,WAA4B,CAAC,GAAI,KAAK,YAAY,CAAC,GAAI,iBAAiB;AAE9E,QAAM,SAAS,KAAK,UAAU;AAE9B,QAAM,MAAM,MACV,yBAAyB;AAAA,IACvB,MAAM,KAAK;AAAA,IACX;AAAA,IACA,eAAe,KAAK;AAAA,IACpB,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,QAAQ,KAAK;AAAA,IACb,eAAe,EAAE,SAAS;AAAA,IAC1B,MAAM,KAAK,SAAS;AAClB,YAAM,WAAW,MAAM,OAAO;AAAA,QAC5B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,UAAU;AAAA,QACnB,QAAQ,KAAK;AAAA,MACf,CAAC;AACD,aAAO,mBAAmB,QAAQ;AAAA,IACpC;AAAA,EACF,CAAC;AAEH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|