@tangle-network/agent-app 0.1.4 → 0.1.6

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.
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-EAJSWUU5.js";
4
4
  import {
5
5
  createFieldCrypto
6
- } from "./chunk-ZJGY7OMZ.js";
6
+ } from "./chunk-TA5Q4I2K.js";
7
7
 
8
8
  // src/preset-cloudflare/index.ts
9
9
  var PRESET_TABLES = {
@@ -315,4 +315,4 @@ export {
315
315
  createPresetWorkspaceKeyStore,
316
316
  createPresetWorkspaceKeyManager
317
317
  };
318
- //# sourceMappingURL=chunk-EZXN67KE.js.map
318
+ //# sourceMappingURL=chunk-MTJXFHYD.js.map
@@ -134,4 +134,4 @@ export {
134
134
  defineAgentApp,
135
135
  agentAppConfigJsonSchema
136
136
  };
137
- //# sourceMappingURL=chunk-5RV6CAHZ.js.map
137
+ //# sourceMappingURL=chunk-OC3RAUZ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/index.ts"],"sourcesContent":["/**\n * `@tangle-network/agent-app/config` — the typed `AgentAppConfig` contract.\n *\n * This is the single declarative DATA surface a coding agent fills to stand up a\n * new agent product. Everything an agent product otherwise hand-rolls as\n * imperative wiring — who the agent is, which proposal types exist and which are\n * approval-gated, what knowledge the control loop requires, which integrations\n * are connectable, how the model resolves — is expressed here as plain data the\n * rest of agent-app's modules consume through their typed seams:\n *\n * - `identity` → the system-prompt + disclaimer surface the chat pipeline reads.\n * - `taxonomy` → which `submit_proposal` types the `./tools` side channel\n * accepts, and which the approval queue treats as regulated\n * (certified-human-gated).\n * - `knowledge` → the `./knowledge` requirement specs that gate the loop, plus\n * the acquisition-loop goal/threshold the researcher pursues.\n * - `integrations` → which `@tangle-network/agent-integrations` catalog kinds the\n * product enables.\n * - `ui` → whether the agent may emit generated UI (`render_ui`).\n * - `model` → the `./runtime` `TangleModelConfig` (or null to resolve from env).\n *\n * Layering: this module is the CONTRACT only — pure types + an identity helper.\n * It introduces no behavior and no engine dependency; it REUSES the existing\n * `KnowledgeRequirementSpec`/`SatisfiedByRule` (from `../knowledge`) and\n * `TangleModelConfig` (from `../runtime`) rather than redefining them, so the\n * config a product authors is the exact shape those modules already consume.\n * Steps that build the composer/loader read this file as the schema floor.\n */\n\nimport type {\n KnowledgeRequirementSpec,\n SatisfiedByRule,\n} from '../knowledge/index'\nimport type { TangleModelConfig } from '../runtime/index'\n\n// Re-export the borrowed types so a config author has a single import surface\n// (`@tangle-network/agent-app/config`) and never has to reach into `../knowledge`\n// or `../runtime` to spell out a field's type. These are the SAME types the\n// knowledge gate and runtime model resolver consume — not parallel copies.\nexport type { KnowledgeRequirementSpec, SatisfiedByRule, TangleModelConfig }\n\n/**\n * Who the agent is, as data. Composed into the chat pipeline's system prompt;\n * never baked into agent-app (a domain value, per the layering contract).\n */\nexport interface AgentIdentityConfig {\n /** Product/agent name, e.g. `\"Acme Support Copilot\"`. Shown to the user and\n * available to the system-prompt builder. */\n name: string\n /** One-paragraph persona statement — the agent's role, voice, and remit. The\n * spine of the system prompt. */\n persona: string\n /** Additional system-prompt fragments appended verbatim after the persona\n * (standing workflows, hard rules, tone). Order is preserved. Optional. */\n systemPromptFragments?: string[]\n /** Named disclaimers the product surfaces (e.g. a regulatory human-in-the-loop\n * notice). Keyed by a stable id (`compliance`, `not-advice`, …) so the chat\n * pipeline / UI can select one by name. Values are the literal text. Optional. */\n disclaimers?: Record<string, string>\n}\n\n/**\n * The proposal taxonomy, as data. `proposalTypes` is the closed set of\n * `submit_proposal` types the `./tools` side channel accepts; `regulatedTypes`\n * is the subset that is approval-gated — the executor refuses to run one without\n * a certified human approver. `regulatedTypes` MUST be a subset of\n * `proposalTypes` (validated by the loader step; not enforced at the type level).\n */\nexport interface AgentTaxonomyConfig {\n /** Every proposal type this product can emit, e.g.\n * `['recommend', 'contact', 'escalate']`. The closed allow-list\n * the tool layer validates a `submit_proposal` call against. */\n proposalTypes: string[]\n /** The subset of `proposalTypes` that is regulated → cannot execute without a\n * certified-human approver. The approval executor reads this to decide which\n * proposals are fail-loud certified-gated. */\n regulatedTypes: string[]\n}\n\n/** A knowledge source the acquisition loop / researcher may draw on. */\nexport interface KnowledgeSourceSpec {\n /** Where the source lives — a URL, a `vault://` path, an integration ref, etc.\n * Opaque to agent-app; the consumer's loader resolves it. */\n uri: string\n /** Optional source classifier the researcher uses to pick a fetch strategy,\n * e.g. `'web'`, `'vault'`, `'regulation'`, `'integration'`. Free-form. */\n kind?: string\n}\n\n/**\n * The knowledge-acquisition loop config — the goal the researcher pursues and\n * the gate it must clear before the loop is considered satisfied. All optional;\n * a product with only static requirement specs omits it.\n */\nexport interface KnowledgeLoopConfig {\n /** The acquisition goal in natural language, e.g.\n * `\"ground every quoted premium against a real policy record\"`. */\n goal?: string\n /** The minimum aggregate confidence [0, 1] the loop must reach to pass its\n * gate. The runtime control loop blocks below this. Default decided by the\n * consumer's loop wiring when omitted. */\n minConfidence?: number\n /** How fresh acquired knowledge must be, e.g. `'static'`, `'7d'`, `'session'`.\n * Free-form; the consumer's loop interprets it. */\n freshness?: string\n}\n\n/**\n * The knowledge surface, as data. `sources` are what the researcher may read;\n * `requirements` are the declarative `./knowledge` specs that gate the control\n * loop (reused verbatim — the same `KnowledgeRequirementSpec` the gate scores);\n * `loop` configures the acquisition pass.\n */\nexport interface AgentKnowledgeConfig {\n /** Sources the acquisition loop / researcher may draw on. */\n sources: KnowledgeSourceSpec[]\n /** The declarative requirement specs that gate the loop. Reuses\n * `KnowledgeRequirementSpec` from `../knowledge` — `buildKnowledgeRequirements`\n * + `deriveSignals` consume these directly. */\n requirements: KnowledgeRequirementSpec[]\n /** Optional acquisition-loop config (goal + confidence gate + freshness). */\n loop?: KnowledgeLoopConfig\n}\n\n/**\n * Which integrations the product enables, as data. `enabled` lists\n * `@tangle-network/agent-integrations` catalog kinds the product connects\n * (e.g. `['salesforce', 'gmail', 'whatsapp']`); the integration hub resolves\n * each to a connector. Strings, not connector objects — agent-app bakes no\n * catalog value.\n */\nexport interface AgentIntegrationsConfig {\n /** Catalog kinds this product enables. */\n enabled: string[]\n}\n\n/** UI capability flags, as data. */\nexport interface AgentUiConfig {\n /** Whether the agent may emit generated UI (the `render_ui` / OpenUI side\n * channel). When false/omitted, the tool layer can omit/refuse the tool.\n * Default behavior decided by the consumer; omit to leave unset. */\n generatedUi?: boolean\n}\n\n/** Background-agent / delegated-loop capability, as data. */\nexport interface AgentDelegationConfig {\n /** When true, the app's own agent may spawn `delegate_research` /\n * `delegate_code` loops that run to completion in their OWN sandbox and\n * return the artifact (the `../delegation` driven-loop MCP). Sandbox-path\n * ONLY — the browser/edge path ignores it. Wired via the existing\n * `buildDelegationMcpServer`; never reimplemented. */\n enabled?: boolean\n}\n\n/**\n * The declarative domain surface of a Tangle agent product.\n *\n * A coding agent fills THIS object and nothing else to define a product's\n * identity, proposal taxonomy, knowledge gate, integrations, UI capability, and\n * model. Every field is DATA consumed by an agent-app module through its typed\n * seam — no field is behavior. Author it through {@link defineAgentApp} for\n * autocomplete and a single import.\n */\nexport interface AgentAppConfig {\n /** Who the agent is — name, persona, system-prompt fragments, disclaimers. */\n identity: AgentIdentityConfig\n /** Proposal types + which are regulated/approval-gated. */\n taxonomy: AgentTaxonomyConfig\n /** Knowledge sources, requirement specs (the loop gate), and loop config. */\n knowledge: AgentKnowledgeConfig\n /** Enabled integration catalog kinds. */\n integrations: AgentIntegrationsConfig\n /** UI capability flags. Optional. */\n ui?: AgentUiConfig\n /** Background-agent / delegated-loop capability (sandbox path). Optional. */\n delegation?: AgentDelegationConfig\n /** The resolved model config (`../runtime`'s `TangleModelConfig`). Omit to\n * resolve from env at boot via `resolveTangleModelConfig`. */\n model?: TangleModelConfig\n}\n\n/**\n * Identity helper: returns its argument unchanged, but anchors inference so a\n * coding agent authoring a config gets full autocomplete + type-checking from a\n * single import. The canonical way to declare a product config:\n *\n * ```ts\n * import { defineAgentApp } from '@tangle-network/agent-app/config'\n *\n * export const config = defineAgentApp({\n * identity: { name: 'Acme Ops', persona: '…' },\n * taxonomy: { proposalTypes: ['recommend', 'contact'], regulatedTypes: ['recommend'] },\n * knowledge: { sources: [], requirements: [] },\n * integrations: { enabled: ['salesforce'] },\n * })\n * ```\n */\nexport function defineAgentApp<const T extends AgentAppConfig>(config: T): T {\n return config\n}\n\n/**\n * Machine-readable JSON Schema (draft 2020-12) for {@link AgentAppConfig}.\n *\n * The schema FLOOR a non-TypeScript coding agent (or a config validator/UI) reads\n * to know the shape without parsing the `.d.ts`. Kept in lockstep with the\n * interfaces above by the config test, which asserts the documented fields are\n * present. `KnowledgeRequirementSpec`'s full sub-shape is intentionally left\n * `additionalProperties: true` here — its authoritative definition lives in\n * `../knowledge`; this floor pins only the fields the config contract owns.\n */\nexport const agentAppConfigJsonSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n $id: 'https://tangle.tools/schemas/agent-app-config.json',\n title: 'AgentAppConfig',\n description: 'The declarative domain surface of a Tangle agent product.',\n type: 'object',\n additionalProperties: false,\n required: ['identity', 'taxonomy', 'knowledge', 'integrations'],\n properties: {\n identity: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'persona'],\n properties: {\n name: { type: 'string', description: 'Product/agent name.' },\n persona: { type: 'string', description: 'One-paragraph persona — the system-prompt spine.' },\n systemPromptFragments: {\n type: 'array',\n items: { type: 'string' },\n description: 'Verbatim system-prompt fragments appended after the persona, in order.',\n },\n disclaimers: {\n type: 'object',\n additionalProperties: { type: 'string' },\n description: 'Named disclaimers keyed by stable id; values are literal text.',\n },\n },\n },\n taxonomy: {\n type: 'object',\n additionalProperties: false,\n required: ['proposalTypes', 'regulatedTypes'],\n properties: {\n proposalTypes: {\n type: 'array',\n items: { type: 'string' },\n description: 'Closed allow-list of proposal types the product can emit.',\n },\n regulatedTypes: {\n type: 'array',\n items: { type: 'string' },\n description: 'Subset of proposalTypes that is approval-gated (certified-human required).',\n },\n },\n },\n knowledge: {\n type: 'object',\n additionalProperties: false,\n required: ['sources', 'requirements'],\n properties: {\n sources: {\n type: 'array',\n description: 'Sources the acquisition loop may draw on.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['uri'],\n properties: {\n uri: { type: 'string', description: 'Where the source lives (opaque to agent-app).' },\n kind: { type: 'string', description: 'Optional source classifier (web/vault/regulation/…).' },\n },\n },\n },\n requirements: {\n type: 'array',\n description: \"Declarative KnowledgeRequirementSpec[] (../knowledge) that gate the loop.\",\n items: { type: 'object', additionalProperties: true },\n },\n loop: {\n type: 'object',\n additionalProperties: false,\n description: 'Acquisition-loop config.',\n properties: {\n goal: { type: 'string', description: 'Acquisition goal in natural language.' },\n minConfidence: {\n type: 'number',\n minimum: 0,\n maximum: 1,\n description: 'Minimum aggregate confidence the loop must reach.',\n },\n freshness: { type: 'string', description: 'How fresh acquired knowledge must be.' },\n },\n },\n },\n },\n integrations: {\n type: 'object',\n additionalProperties: false,\n required: ['enabled'],\n properties: {\n enabled: {\n type: 'array',\n items: { type: 'string' },\n description: 'Enabled agent-integrations catalog kinds.',\n },\n },\n },\n ui: {\n type: 'object',\n additionalProperties: false,\n description: 'UI capability flags.',\n properties: {\n generatedUi: { type: 'boolean', description: 'Whether the agent may emit generated UI.' },\n },\n },\n delegation: {\n type: 'object',\n additionalProperties: false,\n description: 'Background-agent / delegated-loop capability (sandbox path).',\n properties: {\n enabled: { type: 'boolean', description: 'Whether the agent may spawn delegate_research/delegate_code loops.' },\n },\n },\n model: {\n type: 'object',\n additionalProperties: false,\n description: \"Resolved TangleModelConfig (../runtime). Omit to resolve from env.\",\n required: ['provider', 'model', 'apiKey', 'baseUrl'],\n properties: {\n provider: { type: 'string', enum: ['openai-compat', 'anthropic'] },\n model: { type: 'string' },\n apiKey: { type: 'string' },\n baseUrl: { type: 'string' },\n },\n },\n },\n} as const\n"],"mappings":"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","names":[]}
@@ -0,0 +1,43 @@
1
+ // src/harness/index.ts
2
+ var KNOWN_HARNESSES = [
3
+ "opencode",
4
+ "claude-code",
5
+ "kimi-code",
6
+ "codex",
7
+ "amp",
8
+ "factory-droids",
9
+ "pi",
10
+ "hermes",
11
+ "forge",
12
+ "openclaw",
13
+ "acp",
14
+ "cursor",
15
+ "cli-base"
16
+ ];
17
+ var DEFAULT_HARNESS = "opencode";
18
+ var HARNESS_SET = new Set(KNOWN_HARNESSES);
19
+ function isHarness(value) {
20
+ return typeof value === "string" && HARNESS_SET.has(value);
21
+ }
22
+ function coerceHarness(value, fallback = DEFAULT_HARNESS) {
23
+ return isHarness(value) ? value : fallback;
24
+ }
25
+ function resolveSessionHarness(input = {}) {
26
+ const fallback = input.fallback ?? DEFAULT_HARNESS;
27
+ if (isHarness(input.sessionHarness)) {
28
+ const locked = input.sessionHarness;
29
+ const swapAttempted = isHarness(input.requested) && input.requested !== locked;
30
+ return { harness: locked, locked: true, swapAttempted };
31
+ }
32
+ const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback));
33
+ return { harness, locked: false, swapAttempted: false };
34
+ }
35
+
36
+ export {
37
+ KNOWN_HARNESSES,
38
+ DEFAULT_HARNESS,
39
+ isHarness,
40
+ coerceHarness,
41
+ resolveSessionHarness
42
+ };
43
+ //# sourceMappingURL=chunk-SD2H4FWY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/harness/index.ts"],"sourcesContent":["/**\n * Coding-agent harness selection — taxonomy, coercion, and the session-lock invariant.\n *\n * A \"harness\" is the coding-agent CLI a sandbox drives (opencode / codex /\n * claude-code / …). The shell governs WHICH harness a chat session uses and\n * enforces that a session is LOCKED to the harness it started with — the model\n * may change mid-session, the harness may not (swapping it mid-session would\n * orphan the session's running agent state). Every product otherwise hand-rolls\n * this and hard-codes a single harness; this is the one place the rule lives.\n *\n * Substrate-free: the harness list mirrors the sandbox SDK's `BackendType` as a\n * plain string union (no sandbox dependency). The consumer owns storage — which\n * harness a workspace defaults to, which one a session locked — and maps the\n * resolved value onto the SDK's `backend.type`.\n */\n\n/** The known coding-agent backends. Mirrors `@tangle-network/sandbox`'s\n * `BackendType`; kept structural so this module needs no sandbox dependency. */\nexport const KNOWN_HARNESSES = [\n 'opencode',\n 'claude-code',\n 'kimi-code',\n 'codex',\n 'amp',\n 'factory-droids',\n 'pi',\n 'hermes',\n 'forge',\n 'openclaw',\n 'acp',\n 'cursor',\n 'cli-base',\n] as const\n\nexport type Harness = (typeof KNOWN_HARNESSES)[number]\n\nexport const DEFAULT_HARNESS: Harness = 'opencode'\n\nconst HARNESS_SET: ReadonlySet<string> = new Set(KNOWN_HARNESSES)\n\nexport function isHarness(value: unknown): value is Harness {\n return typeof value === 'string' && HARNESS_SET.has(value)\n}\n\n/** Coerce an arbitrary value to a known harness, falling back (default `opencode`). */\nexport function coerceHarness(value: unknown, fallback: Harness = DEFAULT_HARNESS): Harness {\n return isHarness(value) ? value : fallback\n}\n\nexport interface ResolveSessionHarnessInput {\n /** The harness already locked to this session (recorded at its first turn). */\n sessionHarness?: unknown\n /** The harness requested now — a new session's choice, or a turn's attempt to switch. */\n requested?: unknown\n /** The workspace's default harness, used only when starting a fresh session. */\n workspaceDefault?: unknown\n /** Final fallback when nothing else resolves (default `opencode`). */\n fallback?: Harness\n}\n\nexport interface ResolvedSessionHarness {\n /** The harness to actually run — the locked one when the session already has it. */\n harness: Harness\n /** True when the session already had a locked harness (this turn did not pick it). */\n locked: boolean\n /** True when `requested` differs from the locked harness — a forbidden mid-session\n * swap the caller should reject or warn on. The lock always wins regardless. */\n swapAttempted: boolean\n}\n\n/**\n * Resolve the harness for a turn, enforcing the session lock.\n *\n * - **Session already started** (`sessionHarness` is a known harness): that harness\n * wins (`locked: true`); a differing `requested` sets `swapAttempted` so the caller\n * can reject the swap. The model is a separate per-turn concern and is unaffected.\n * - **Fresh session**: pick `requested → workspaceDefault → fallback`. The caller\n * persists the result as the session's lock for every subsequent turn.\n */\nexport function resolveSessionHarness(input: ResolveSessionHarnessInput = {}): ResolvedSessionHarness {\n const fallback = input.fallback ?? DEFAULT_HARNESS\n if (isHarness(input.sessionHarness)) {\n const locked = input.sessionHarness\n const swapAttempted = isHarness(input.requested) && input.requested !== locked\n return { harness: locked, locked: true, swapAttempted }\n }\n const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback))\n return { harness, locked: false, swapAttempted: false }\n}\n"],"mappings":";AAkBO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,IAAM,kBAA2B;AAExC,IAAM,cAAmC,IAAI,IAAI,eAAe;AAEzD,SAAS,UAAU,OAAkC;AAC1D,SAAO,OAAO,UAAU,YAAY,YAAY,IAAI,KAAK;AAC3D;AAGO,SAAS,cAAc,OAAgB,WAAoB,iBAA0B;AAC1F,SAAO,UAAU,KAAK,IAAI,QAAQ;AACpC;AAgCO,SAAS,sBAAsB,QAAoC,CAAC,GAA2B;AACpG,QAAM,WAAW,MAAM,YAAY;AACnC,MAAI,UAAU,MAAM,cAAc,GAAG;AACnC,UAAM,SAAS,MAAM;AACrB,UAAM,gBAAgB,UAAU,MAAM,SAAS,KAAK,MAAM,cAAc;AACxE,WAAO,EAAE,SAAS,QAAQ,QAAQ,MAAM,cAAc;AAAA,EACxD;AACA,QAAM,UAAU,cAAc,MAAM,WAAW,cAAc,MAAM,kBAAkB,QAAQ,CAAC;AAC9F,SAAO,EAAE,SAAS,QAAQ,OAAO,eAAe,MAAM;AACxD;","names":[]}
@@ -99,4 +99,4 @@ export {
99
99
  encryptBytes,
100
100
  decryptBytes
101
101
  };
102
- //# sourceMappingURL=chunk-ZJGY7OMZ.js.map
102
+ //# sourceMappingURL=chunk-TA5Q4I2K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/crypto/index.ts"],"sourcesContent":["/**\n * AES-256-GCM field encryption (for PII at rest — SSN/EIN/ID numbers, secrets).\n * WebCrypto only — runs on Cloudflare Workers, Node, and the browser with no\n * Node `crypto` dependency. The 32-byte key is a PARAMETER (64-char hex); the\n * framework never reads env — the product binds its own `ENCRYPTION_KEY` (this\n * is the concrete impl behind the `KeyCrypto` seam in `../billing`).\n *\n * Wire format: base64(iv ‖ ciphertext ‖ tag) — the 12-byte IV is prepended; the\n * GCM auth tag is appended by WebCrypto inside the ciphertext.\n */\n\nconst IV_LENGTH = 12\nconst TAG_LENGTH = 16\nconst ALGORITHM = 'AES-GCM'\n\n/** Validate + decode a 64-char hex key to 32 bytes. Throws on the wrong shape so\n * a misconfigured key fails loud, never silently weakens encryption. */\nexport function decodeHexKey(keyHex: string): Uint8Array {\n if (keyHex.length !== 64) throw new Error('encryption key must be a 64-char hex string (32 bytes)')\n const bytes = new Uint8Array(32)\n for (let i = 0; i < 64; i += 2) bytes[i / 2] = parseInt(keyHex.substring(i, i + 2), 16)\n return bytes\n}\n\nasync function importKey(keyHex: string): Promise<CryptoKey> {\n const raw = decodeHexKey(keyHex)\n return crypto.subtle.importKey('raw', raw.buffer as ArrayBuffer, { name: ALGORITHM } as Algorithm, false, ['encrypt', 'decrypt'])\n}\n\nfunction toBase64(data: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < data.length; i++) binary += String.fromCharCode(data[i]!)\n return btoa(binary)\n}\n\nfunction fromBase64(b64: string): Uint8Array {\n const binary = atob(b64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return bytes\n}\n\n/** Encrypt `plaintext` with AES-256-GCM under `keyHex`. Returns\n * base64(iv ‖ ciphertext ‖ tag). A fresh random IV per call. */\nexport async function encryptAesGcm(plaintext: string, keyHex: string): Promise<string> {\n const key = await importKey(keyHex)\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv, tagLength: TAG_LENGTH * 8 }, key, new TextEncoder().encode(plaintext))\n const result = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n result.set(iv, 0)\n result.set(new Uint8Array(ciphertext), IV_LENGTH)\n return toBase64(result)\n}\n\n/** Decrypt a base64(iv ‖ ciphertext ‖ tag) string under `keyHex`. Throws if the\n * tag fails (tamper/wrong key). */\nexport async function decryptAesGcm(encrypted: string, keyHex: string): Promise<string> {\n const key = await importKey(keyHex)\n const data = fromBase64(encrypted)\n const iv = data.slice(0, IV_LENGTH)\n const ciphertext = data.slice(IV_LENGTH)\n const plain = await crypto.subtle.decrypt({ name: ALGORITHM, iv, tagLength: TAG_LENGTH * 8 }, key, ciphertext)\n return new TextDecoder().decode(plain)\n}\n\n/** Build a {@link import('../billing').KeyCrypto}-compatible pair bound to a key\n * (or a key-resolver, for env-backed keys resolved per call). */\nexport function createFieldCrypto(key: string | (() => string)): { encrypt(s: string): Promise<string>; decrypt(s: string): Promise<string> } {\n const resolve = typeof key === 'function' ? key : () => key\n return {\n encrypt: (s) => encryptAesGcm(s, resolve()),\n decrypt: (s) => decryptAesGcm(s, resolve()),\n }\n}\n\n/**\n * --- Passphrase-derived key path (PBKDF2 → AES-256-GCM CryptoKey) ---\n *\n * The `encryptAesGcm`/`decryptAesGcm` path takes a raw 64-char-hex key. Some\n * products instead bind a SECRET STRING (not a hex key) and derive the AES key\n * with PBKDF2 — and need a BINARY path (encrypting document bytes, not just\n * strings). Both are exposed here so a product never hand-rolls WebCrypto.\n *\n * The derivation parameters (salt, iterations) are PARAMETERS — a product pins\n * its own so the derived key bytes stay stable for data already at rest. The\n * defaults are a sensible baseline; any product supplies its own via\n * {@link DeriveKeyOptions}.\n */\n\nexport interface DeriveKeyOptions {\n /** PBKDF2 salt. A product MUST pin this — changing it changes the derived key\n * bytes and orphans every value already encrypted at rest. */\n salt: Uint8Array | string\n /** PBKDF2 iteration count. Pin it for the same reason as `salt`. */\n iterations: number\n /** PBKDF2 hash. Default `'SHA-256'`. */\n hash?: 'SHA-256' | 'SHA-384' | 'SHA-512'\n}\n\n/** Derive an AES-256-GCM `CryptoKey` from a secret string via PBKDF2. The key is\n * non-extractable and usable only for encrypt/decrypt. */\nexport async function deriveKey(secret: string, opts: DeriveKeyOptions): Promise<CryptoKey> {\n const salt = typeof opts.salt === 'string' ? new TextEncoder().encode(opts.salt) : opts.salt\n const keyMaterial = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), 'PBKDF2', false, ['deriveKey'])\n return crypto.subtle.deriveKey(\n { name: 'PBKDF2', salt: salt as BufferSource, iterations: opts.iterations, hash: opts.hash ?? 'SHA-256' },\n keyMaterial,\n { name: ALGORITHM, length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n/** Encrypt `plaintext` under a derived `CryptoKey`. Returns base64(iv ‖ ct ‖ tag). */\nexport async function encryptWithKey(plaintext: string, key: CryptoKey): Promise<string> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, new TextEncoder().encode(plaintext))\n const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n out.set(iv, 0)\n out.set(new Uint8Array(ciphertext), IV_LENGTH)\n return toBase64(out)\n}\n\n/** Decrypt a base64(iv ‖ ct ‖ tag) string under a derived `CryptoKey`. */\nexport async function decryptWithKey(encoded: string, key: CryptoKey): Promise<string> {\n const raw = fromBase64(encoded)\n const iv = raw.slice(0, IV_LENGTH)\n const ciphertext = raw.slice(IV_LENGTH)\n const plain = await crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext)\n return new TextDecoder().decode(plain)\n}\n\n/** Encrypt binary data under a derived `CryptoKey`. Returns an ArrayBuffer:\n * 12-byte IV ‖ ciphertext ‖ 16-byte GCM tag (same wire layout as the string\n * path, raw bytes instead of base64). */\nexport async function encryptBytes(data: ArrayBuffer, key: CryptoKey): Promise<ArrayBuffer> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, data)\n const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n out.set(iv, 0)\n out.set(new Uint8Array(ciphertext), IV_LENGTH)\n return out.buffer\n}\n\n/** Decrypt binary data (IV ‖ ciphertext ‖ tag) under a derived `CryptoKey`. */\nexport async function decryptBytes(data: ArrayBuffer, key: CryptoKey): Promise<ArrayBuffer> {\n const raw = new Uint8Array(data)\n const iv = raw.slice(0, IV_LENGTH)\n const ciphertext = raw.slice(IV_LENGTH)\n return crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext)\n}\n"],"mappings":"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","names":[]}
@@ -271,4 +271,4 @@ export {
271
271
  buildHttpMcpServer,
272
272
  buildAppToolMcpServer
273
273
  };
274
- //# sourceMappingURL=chunk-EDIQ6F55.js.map
274
+ //# sourceMappingURL=chunk-WKY2ZVDY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tools/errors.ts","../src/tools/capability.ts","../src/tools/auth.ts","../src/tools/openai.ts","../src/tools/dispatch.ts","../src/tools/runtime.ts","../src/tools/http.ts","../src/tools/mcp.ts"],"sourcesContent":["/** A correctable bad-input error a tool handler throws; the HTTP layer maps it\n * to a 4xx with the code, the runtime layer to a failed tool_result. So the\n * agent learns the call failed and can correct, instead of a silent success. */\nexport class ToolInputError extends Error {\n constructor(\n public code: string,\n message: string,\n public status = 400,\n ) {\n super(message)\n this.name = 'ToolInputError'\n }\n}\n","/**\n * Per-user capability token — the sandbox→app auth primitive behind the\n * `verifyToken` seam in {@link authenticateToolRequest}.\n *\n * An app-agent runs inside the sandbox and reaches the host app back over HTTP\n * (the app tools, the integration-invoke bridge). The route must act AS the\n * connecting user without trusting any model-supplied identity, so the turn\n * mints a short HMAC token bound to the user id and bakes it into the per-turn\n * MCP server header; the route verifies it to recover the user.\n *\n * `HMAC-SHA256(secret, \"user:<userId>\")`, base64url, with an app-chosen prefix.\n * The token encodes no scopes — the hub's policy engine authorizes per action.\n * Fail-closed: with no secret, no token is minted (the caller MUST omit the MCP\n * server rather than fake an authorized call). WebCrypto only — runs on\n * Workers, Node, and the browser with no Node `crypto` dependency.\n */\n\nexport interface CapabilityTokenOptions {\n /** Shared HMAC secret. When absent, mint returns undefined / verify returns false. */\n secret?: string\n /** Token prefix (namespaces the credential; lets verify reject foreign tokens\n * cheaply). Default `cap_`. */\n prefix?: string\n}\n\n/** Mint a capability token for `userId`, or `undefined` when no secret is\n * configured (fail-closed — the caller omits the MCP server rather than fake it). */\nexport async function createCapabilityToken(userId: string, opts: CapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n const prefix = opts.prefix ?? 'cap_'\n return `${prefix}${await sign(userId, secret)}`\n}\n\n/** Verify a capability token against `userId`. Returns false (never throws) for\n * an unconfigured secret, a wrong prefix, a malformed token, or a mismatch. */\nexport async function verifyCapabilityToken(userId: string, token: string, opts: CapabilityTokenOptions): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const expected = `${prefix}${await sign(userId, secret)}`\n return timingSafeEqual(token, expected)\n}\n\nasync function sign(userId: string, secret: string): Promise<string> {\n const enc = new TextEncoder()\n const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n const sig = await crypto.subtle.sign('HMAC', key, enc.encode(`user:${userId}`))\n return base64url(new Uint8Array(sig))\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let s = ''\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/** Length-independent-leak-free compare for two same-charset strings. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","import type { AppToolContext } from './types'\n\n/**\n * Header names carrying the server-set per-turn context + the capability token.\n * Defaults are product-neutral (`X-Agent-App-*`); a product that already ships\n * a header convention (e.g. `X-Acme-User-Id`) passes its own.\n */\nexport interface ToolHeaderNames {\n userId: string\n workspaceId: string\n threadId: string\n}\n\nexport const DEFAULT_HEADER_NAMES: ToolHeaderNames = {\n userId: 'X-Agent-App-User-Id',\n workspaceId: 'X-Agent-App-Workspace-Id',\n threadId: 'X-Agent-App-Thread-Id',\n}\n\nexport interface AuthenticateOptions {\n /** Verify the bearer capability token belongs to `userId`. The product's\n * HMAC/JWT impl — the seam that keeps token crypto out of this package. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n}\n\nexport type ToolAuthResult =\n | { ok: true; ctx: AppToolContext }\n | { ok: false; response: Response }\n\n/**\n * Recover + verify the trusted context for a tool request. The user comes from\n * a server-set header and the bearer token MUST verify against THAT user; the\n * workspace comes from a header too — never from tool args — so the model can\n * neither forge identity nor target another workspace. Fail-closed: any missing\n * credential or a token minted for another user yields a 401/400 Response.\n */\nexport async function authenticateToolRequest(request: Request, opts: AuthenticateOptions): Promise<ToolAuthResult> {\n const h = opts.headerNames ?? DEFAULT_HEADER_NAMES\n const userId = request.headers.get(h.userId)?.trim()\n const workspaceId = request.headers.get(h.workspaceId)?.trim()\n const threadId = request.headers.get(h.threadId)?.trim() || null\n const bearer = request.headers.get('authorization')?.match(/^Bearer\\s+(.+)$/i)?.[1]\n\n if (!userId || !bearer) {\n return { ok: false, response: Response.json({ error: 'Missing capability credentials' }, { status: 401 }) }\n }\n if (!(await opts.verifyToken(userId, bearer))) {\n return { ok: false, response: Response.json({ error: 'Invalid capability token' }, { status: 401 }) }\n }\n if (!workspaceId) {\n return { ok: false, response: Response.json({ error: 'Missing workspace context' }, { status: 400 }) }\n }\n return { ok: true, ctx: { userId, workspaceId, threadId } }\n}\n\n/** Read a tool's argument object from the request body, tolerant of MCP host\n * aliases (`args` / `arguments`) or a bare body. Returns null on non-JSON. */\nexport async function readToolArgs<T>(request: Request): Promise<T | null> {\n let body: { args?: T; arguments?: T }\n try {\n body = (await request.json()) as typeof body\n } catch {\n return null\n }\n return (body.args ?? body.arguments ?? (body as T)) as T\n}\n","import type { AppToolTaxonomy } from './types'\n\n/** The four canonical app-tool names. Stable identifiers the model calls in\n * both the sandbox (MCP server name) and runtime (function-tool name) paths. */\nexport const APP_TOOL_NAMES = ['submit_proposal', 'schedule_followup', 'render_ui', 'add_citation'] as const\nexport type AppToolName = (typeof APP_TOOL_NAMES)[number]\n\nconst NAME_SET = new Set<string>(APP_TOOL_NAMES)\nexport function isAppToolName(name: string): name is AppToolName {\n return NAME_SET.has(name)\n}\n\n/** A minimal OpenAI Chat Completions function-tool shape — structurally\n * compatible with `@tangle-network/agent-runtime`'s `OpenAIChatTool` without\n * importing it (keeps this package runtime-free). */\nexport interface OpenAIFunctionTool {\n type: 'function'\n function: {\n name: string\n description: string\n parameters: Record<string, unknown>\n }\n}\n\n/**\n * Build the four app tools in OpenAI function-tool shape. `submit_proposal`'s\n * `type` enum is the product's {@link AppToolTaxonomy.proposalTypes}; the other\n * three are fixed. Pass the result to the agent-runtime backend's `tools`.\n */\nexport function buildAppToolOpenAITools(taxonomy: AppToolTaxonomy): OpenAIFunctionTool[] {\n return [\n {\n type: 'function',\n function: {\n name: 'submit_proposal',\n description:\n 'Route a regulated or state-changing action to a human for approval (a recommendation, contacting/soliciting a contact, outreach, a record/account change, scheduling). Queues it for a named certified human to approve before it executes.',\n parameters: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...taxonomy.proposalTypes] },\n title: { type: 'string', description: 'Short label for the approval queue.' },\n description: { type: 'string', description: 'The full drafted message/recommendation, with sources.' },\n },\n required: ['type', 'title'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'schedule_followup',\n description: 'Register a dated cadence step (a reminder, chase, or check-in) on the follow-up calendar. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n dueDate: { type: 'string', description: 'ISO date YYYY-MM-DD.' },\n priority: { type: 'string', enum: ['low', 'medium', 'high'] },\n },\n required: ['title', 'dueDate'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'render_ui',\n description: 'Show a generated view live in the workspace. Validates the OpenUI JSON and persists the artifact. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n schema: { type: 'object', description: 'The OpenUI JSON object.' },\n },\n required: ['title', 'schema'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'add_citation',\n description: 'Anchor a grounding reference: the exact quote from a file backing a figure or claim. Verifies the quote appears in the file. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'The vault file path.' },\n quote: { type: 'string', description: 'The exact text from it.' },\n },\n required: ['path', 'quote'],\n },\n },\n },\n ]\n}\n","import { ToolInputError } from './errors'\nimport { isAppToolName } from './openai'\nimport type {\n AppToolContext,\n AppToolHandlers,\n AppToolOutcome,\n AppToolProducedEvent,\n AppToolTaxonomy,\n} from './types'\n\nexport interface DispatchOptions {\n handlers: AppToolHandlers\n taxonomy: AppToolTaxonomy\n /** Called at the real side-effect site for proposals (proposal_created) and\n * generated views (artifact) so a consumer's completion oracle credits\n * persisted state. Omit when produced state isn't tracked. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\n/**\n * The ONE place an app-tool call is validated, dispatched to the product's\n * handler, and turned into an {@link AppToolOutcome} + produced events. Shared\n * by the HTTP route layer and the agent-runtime executor so both paths apply\n * identical validation and identical side effects. A {@link ToolInputError}\n * (bad input the agent can correct) and any other throw both become\n * `{ ok: false }` — a tool call never silently \"succeeds\" without its effect.\n */\nexport async function dispatchAppTool(\n toolName: string,\n rawArgs: Record<string, unknown>,\n ctx: AppToolContext,\n opts: DispatchOptions,\n): Promise<AppToolOutcome> {\n try {\n if (!isAppToolName(toolName)) {\n return { ok: false, code: 'unknown_tool', message: `${toolName} is not an app tool.` }\n }\n\n if (toolName === 'submit_proposal') {\n const type = String(rawArgs.type ?? '').trim()\n const title = String(rawArgs.title ?? '').trim()\n if (!type || !opts.taxonomy.proposalTypes.includes(type)) {\n return { ok: false, code: 'invalid_type', message: `type must be one of: ${opts.taxonomy.proposalTypes.join(', ')}.` }\n }\n if (!title) return { ok: false, code: 'missing_title', message: 'title is required.' }\n const description = rawArgs.description == null ? null : String(rawArgs.description)\n const r = await opts.handlers.submitProposal({ type, title, description }, ctx)\n const regulated = opts.taxonomy.regulatedTypes.includes(type)\n opts.onProduced?.({ type: 'proposal_created', proposalId: r.proposalId, title, status: 'pending' })\n return { ok: true, result: { status: 'queued_for_approval', proposalId: r.proposalId, deduped: r.deduped, regulated } }\n }\n\n if (toolName === 'schedule_followup') {\n const r = await opts.handlers.scheduleFollowup(\n { title: String(rawArgs.title ?? ''), dueDate: String(rawArgs.dueDate ?? ''), priority: rawArgs.priority as string | undefined },\n ctx,\n )\n return { ok: true, result: { followupId: r.id, dueDate: r.dueDate, deduped: r.deduped } }\n }\n\n if (toolName === 'render_ui') {\n const r = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ''), schema: rawArgs.schema }, ctx)\n opts.onProduced?.({ type: 'artifact', path: r.path, content: r.content })\n return { ok: true, result: { path: r.path } }\n }\n\n // add_citation\n const r = await opts.handlers.addCitation(\n { path: String(rawArgs.path ?? ''), quote: String(rawArgs.quote ?? ''), label: rawArgs.label as string | undefined },\n ctx,\n )\n return { ok: true, result: { citationId: r.citationId, path: r.path } }\n } catch (err) {\n if (err instanceof ToolInputError) return { ok: false, code: err.code, message: err.message, status: err.status }\n return { ok: false, code: 'app_tool_error', message: err instanceof Error ? err.message : String(err), status: 500 }\n }\n}\n\n/** HTTP status for a failed outcome — the handler's `ToolInputError.status`\n * when present, else 400 for a validation reject. */\nexport function outcomeStatus(outcome: Extract<AppToolOutcome, { ok: false }>): number {\n return outcome.status ?? 400\n}\n","import { dispatchAppTool, type DispatchOptions } from './dispatch'\nimport type { AppToolContext, AppToolOutcome } from './types'\n\n/** Executes an app-tool call the model emits on the agent-runtime chat path.\n * Plug into `runChatThroughRuntime({ appToolExecutor })` (or any loop that\n * dispatches function tool_calls). */\nexport type AppToolRuntimeExecutor = (call: {\n toolName: string\n args: Record<string, unknown>\n}) => Promise<AppToolOutcome>\n\nexport interface RuntimeExecutorOptions extends DispatchOptions {\n /** The trusted per-turn context — supplied directly (not from headers), since\n * the runtime path has no HTTP request. */\n ctx: AppToolContext\n}\n\n/**\n * Build the runtime executor for one turn. The agent-runtime backend must also\n * advertise the tools (`buildAppToolOpenAITools(taxonomy)` on the backend's\n * `tools`) for the model to call them; this executor fulfils each call against\n * the product's handlers and emits produced events via `opts.onProduced`.\n */\nexport function createAppToolRuntimeExecutor(opts: RuntimeExecutorOptions): AppToolRuntimeExecutor {\n return ({ toolName, args }) => dispatchAppTool(toolName, args, opts.ctx, opts)\n}\n","import { authenticateToolRequest, type ToolHeaderNames } from './auth'\nimport { dispatchAppTool, outcomeStatus, type DispatchOptions } from './dispatch'\nimport type { AppToolName } from './openai'\n\nexport interface HandleToolRequestOptions extends DispatchOptions {\n /** Which app tool this route serves. */\n tool: AppToolName\n /** Verify the bearer capability token belongs to the header user. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n /** Optional success-message builder for a friendlier tool result. */\n message?: (result: unknown) => string\n}\n\n/**\n * Handle one app-tool HTTP request end to end — the sandbox MCP path. The\n * agent's per-turn HTTP MCP server POSTs here; this authenticates (header user\n * + capability token), reads the args (MCP-alias tolerant), dispatches to the\n * product handler, and returns a JSON Response. A product's route file becomes\n * a one-liner: `export const action = ({ request }) => handleAppToolRequest(request, cfg)`.\n */\nexport async function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response> {\n if (request.method !== 'POST') return Response.json({ error: 'Method not allowed' }, { status: 405 })\n\n const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames })\n if (!auth.ok) return auth.response\n\n let body: { args?: Record<string, unknown>; arguments?: Record<string, unknown> } & Record<string, unknown>\n try {\n body = (await request.json()) as typeof body\n } catch {\n return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n const args = (body.args ?? body.arguments ?? body) as Record<string, unknown>\n\n const outcome = await dispatchAppTool(opts.tool, args, auth.ctx, opts)\n if (!outcome.ok) {\n return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) })\n }\n const payload = outcome.result as Record<string, unknown>\n return Response.json({ ok: true, ...payload, ...(opts.message ? { message: opts.message(outcome.result) } : {}) })\n}\n","import type { AppToolContext } from './types'\nimport type { AppToolName } from './openai'\nimport type { ToolHeaderNames } from './auth'\nimport { DEFAULT_HEADER_NAMES } from './auth'\n\n/** Default route path each app tool is served at. A product mounts its routes\n * at these paths (or supplies its own via {@link BuildMcpServerOptions.paths}). */\nexport const DEFAULT_APP_TOOL_PATHS: Record<AppToolName, string> = {\n submit_proposal: '/api/tools/propose',\n schedule_followup: '/api/tools/followup',\n render_ui: '/api/tools/render-ui',\n add_citation: '/api/tools/citation',\n}\n\n/** The portable MCP server entry the sandbox SDK accepts (transport + url +\n * headers). Matches `AgentProfileMcpServer` structurally without importing the\n * sandbox SDK — products spread it into their profile's `mcp` map. */\nexport interface AppToolMcpServer {\n transport: 'http'\n url: string\n headers: Record<string, string>\n enabled: true\n metadata: { description: string }\n}\n\nexport interface BuildHttpMcpServerOptions {\n /** Route path on the app the sandbox POSTs to (e.g. `/api/tools/propose`). */\n path: string\n /** App base URL the sandbox reaches back to (no trailing slash required). */\n baseUrl: string\n /** Per-user capability token, baked into the Authorization header. */\n token: string\n ctx: AppToolContext\n /** Tool description the model sees. */\n description: string\n headerNames?: ToolHeaderNames\n}\n\n/**\n * Build ONE HTTP MCP server entry — the generic agent→app bridge. The\n * capability token + the user/workspace/thread ids ride in server-set headers\n * (never tool args), so the model can't forge identity or target another\n * workspace. Workspace/thread headers are omitted when their `ctx` value is\n * empty/null (e.g. an integration-invoke bridge that's user-scoped only). Used\n * directly for non-app-tool bridges (integration_invoke) and via\n * {@link buildAppToolMcpServer} for the four app tools.\n */\nexport function buildHttpMcpServer(opts: BuildHttpMcpServerOptions): AppToolMcpServer {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const h = opts.headerNames ?? DEFAULT_HEADER_NAMES\n return {\n transport: 'http',\n url: `${base}${opts.path}`,\n headers: {\n Authorization: `Bearer ${opts.token}`,\n [h.userId]: opts.ctx.userId,\n ...(opts.ctx.workspaceId ? { [h.workspaceId]: opts.ctx.workspaceId } : {}),\n ...(opts.ctx.threadId ? { [h.threadId]: opts.ctx.threadId } : {}),\n 'Content-Type': 'application/json',\n },\n enabled: true,\n metadata: { description: opts.description },\n }\n}\n\nexport interface BuildMcpServerOptions {\n tool: AppToolName\n baseUrl: string\n token: string\n ctx: AppToolContext\n description: string\n headerNames?: ToolHeaderNames\n paths?: Partial<Record<AppToolName, string>>\n}\n\n/** Build one of the four app-tool MCP servers — a thin wrapper over\n * {@link buildHttpMcpServer} that maps the tool name to its route path. */\nexport function buildAppToolMcpServer(opts: BuildMcpServerOptions): AppToolMcpServer {\n return buildHttpMcpServer({\n path: opts.paths?.[opts.tool] ?? DEFAULT_APP_TOOL_PATHS[opts.tool],\n baseUrl: opts.baseUrl,\n token: opts.token,\n ctx: opts.ctx,\n description: opts.description,\n headerNames: opts.headerNames,\n })\n}\n"],"mappings":"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","names":["r"]}
@@ -37,7 +37,7 @@ import '@tangle-network/agent-eval';
37
37
  * never baked into agent-app (a domain value, per the layering contract).
38
38
  */
39
39
  interface AgentIdentityConfig {
40
- /** Product/agent name, e.g. `"WinFinance Ops Partner"`. Shown to the user and
40
+ /** Product/agent name, e.g. `"Acme Support Copilot"`. Shown to the user and
41
41
  * available to the system-prompt builder. */
42
42
  name: string;
43
43
  /** One-paragraph persona statement — the agent's role, voice, and remit. The
@@ -60,7 +60,7 @@ interface AgentIdentityConfig {
60
60
  */
61
61
  interface AgentTaxonomyConfig {
62
62
  /** Every proposal type this product can emit, e.g.
63
- * `['propose_swap', 'contact_lead', 'policy_change']`. The closed allow-list
63
+ * `['recommend', 'contact', 'escalate']`. The closed allow-list
64
64
  * the tool layer validates a `submit_proposal` call against. */
65
65
  proposalTypes: string[];
66
66
  /** The subset of `proposalTypes` that is regulated → cannot execute without a
@@ -113,7 +113,7 @@ interface AgentKnowledgeConfig {
113
113
  /**
114
114
  * Which integrations the product enables, as data. `enabled` lists
115
115
  * `@tangle-network/agent-integrations` catalog kinds the product connects
116
- * (e.g. `['shurens', 'lead-crm', 'whatsapp']`); the integration hub resolves
116
+ * (e.g. `['salesforce', 'gmail', 'whatsapp']`); the integration hub resolves
117
117
  * each to a connector. Strings, not connector objects — agent-app bakes no
118
118
  * catalog value.
119
119
  */
@@ -172,10 +172,10 @@ interface AgentAppConfig {
172
172
  * import { defineAgentApp } from '@tangle-network/agent-app/config'
173
173
  *
174
174
  * export const config = defineAgentApp({
175
- * identity: { name: 'WinFinance', persona: '…' },
176
- * taxonomy: { proposalTypes: ['propose_swap'], regulatedTypes: ['propose_swap'] },
175
+ * identity: { name: 'Acme Ops', persona: '…' },
176
+ * taxonomy: { proposalTypes: ['recommend', 'contact'], regulatedTypes: ['recommend'] },
177
177
  * knowledge: { sources: [], requirements: [] },
178
- * integrations: { enabled: ['shurens'] },
178
+ * integrations: { enabled: ['salesforce'] },
179
179
  * })
180
180
  * ```
181
181
  */
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  agentAppConfigJsonSchema,
3
3
  defineAgentApp
4
- } from "../chunk-5RV6CAHZ.js";
4
+ } from "../chunk-OC3RAUZ2.js";
5
5
  export {
6
6
  agentAppConfigJsonSchema,
7
7
  defineAgentApp
@@ -33,8 +33,8 @@ declare function createFieldCrypto(key: string | (() => string)): {
33
33
  *
34
34
  * The derivation parameters (salt, iterations) are PARAMETERS — a product pins
35
35
  * its own so the derived key bytes stay stable for data already at rest. The
36
- * default salt/iterations match the historical tax-agent contract, but any
37
- * product supplies its own via {@link DeriveKeyOptions}.
36
+ * defaults are a sensible baseline; any product supplies its own via
37
+ * {@link DeriveKeyOptions}.
38
38
  */
39
39
  interface DeriveKeyOptions {
40
40
  /** PBKDF2 salt. A product MUST pin this — changing it changes the derived key
@@ -8,7 +8,7 @@ import {
8
8
  encryptAesGcm,
9
9
  encryptBytes,
10
10
  encryptWithKey
11
- } from "../chunk-ZJGY7OMZ.js";
11
+ } from "../chunk-TA5Q4I2K.js";
12
12
  export {
13
13
  createFieldCrypto,
14
14
  decodeHexKey,
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Coding-agent harness selection — taxonomy, coercion, and the session-lock invariant.
3
+ *
4
+ * A "harness" is the coding-agent CLI a sandbox drives (opencode / codex /
5
+ * claude-code / …). The shell governs WHICH harness a chat session uses and
6
+ * enforces that a session is LOCKED to the harness it started with — the model
7
+ * may change mid-session, the harness may not (swapping it mid-session would
8
+ * orphan the session's running agent state). Every product otherwise hand-rolls
9
+ * this and hard-codes a single harness; this is the one place the rule lives.
10
+ *
11
+ * Substrate-free: the harness list mirrors the sandbox SDK's `BackendType` as a
12
+ * plain string union (no sandbox dependency). The consumer owns storage — which
13
+ * harness a workspace defaults to, which one a session locked — and maps the
14
+ * resolved value onto the SDK's `backend.type`.
15
+ */
16
+ /** The known coding-agent backends. Mirrors `@tangle-network/sandbox`'s
17
+ * `BackendType`; kept structural so this module needs no sandbox dependency. */
18
+ declare const KNOWN_HARNESSES: readonly ["opencode", "claude-code", "kimi-code", "codex", "amp", "factory-droids", "pi", "hermes", "forge", "openclaw", "acp", "cursor", "cli-base"];
19
+ type Harness = (typeof KNOWN_HARNESSES)[number];
20
+ declare const DEFAULT_HARNESS: Harness;
21
+ declare function isHarness(value: unknown): value is Harness;
22
+ /** Coerce an arbitrary value to a known harness, falling back (default `opencode`). */
23
+ declare function coerceHarness(value: unknown, fallback?: Harness): Harness;
24
+ interface ResolveSessionHarnessInput {
25
+ /** The harness already locked to this session (recorded at its first turn). */
26
+ sessionHarness?: unknown;
27
+ /** The harness requested now — a new session's choice, or a turn's attempt to switch. */
28
+ requested?: unknown;
29
+ /** The workspace's default harness, used only when starting a fresh session. */
30
+ workspaceDefault?: unknown;
31
+ /** Final fallback when nothing else resolves (default `opencode`). */
32
+ fallback?: Harness;
33
+ }
34
+ interface ResolvedSessionHarness {
35
+ /** The harness to actually run — the locked one when the session already has it. */
36
+ harness: Harness;
37
+ /** True when the session already had a locked harness (this turn did not pick it). */
38
+ locked: boolean;
39
+ /** True when `requested` differs from the locked harness — a forbidden mid-session
40
+ * swap the caller should reject or warn on. The lock always wins regardless. */
41
+ swapAttempted: boolean;
42
+ }
43
+ /**
44
+ * Resolve the harness for a turn, enforcing the session lock.
45
+ *
46
+ * - **Session already started** (`sessionHarness` is a known harness): that harness
47
+ * wins (`locked: true`); a differing `requested` sets `swapAttempted` so the caller
48
+ * can reject the swap. The model is a separate per-turn concern and is unaffected.
49
+ * - **Fresh session**: pick `requested → workspaceDefault → fallback`. The caller
50
+ * persists the result as the session's lock for every subsequent turn.
51
+ */
52
+ declare function resolveSessionHarness(input?: ResolveSessionHarnessInput): ResolvedSessionHarness;
53
+
54
+ export { DEFAULT_HARNESS, type Harness, KNOWN_HARNESSES, type ResolveSessionHarnessInput, type ResolvedSessionHarness, coerceHarness, isHarness, resolveSessionHarness };
@@ -0,0 +1,15 @@
1
+ import {
2
+ DEFAULT_HARNESS,
3
+ KNOWN_HARNESSES,
4
+ coerceHarness,
5
+ isHarness,
6
+ resolveSessionHarness
7
+ } from "../chunk-SD2H4FWY.js";
8
+ export {
9
+ DEFAULT_HARNESS,
10
+ KNOWN_HARNESSES,
11
+ coerceHarness,
12
+ isHarness,
13
+ resolveSessionHarness
14
+ };
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export { AppToolLoopOptions, LoopEvent, LoopToolCall, OpenAICompatStreamTurnOpti
6
6
  export { createTokenRecallChecker, producedFromToolEvents } from './eval/index.js';
7
7
  export { KnowledgeRequirementSpec, KnowledgeSignal, KnowledgeStateAccessor, SatisfiedByRule, buildKnowledgeRequirements, deriveSignals } from './knowledge/index.js';
8
8
  export { CreateKnowledgeLoopDeps, KnowledgeCandidate, KnowledgeDecider, KnowledgeDeciderInput, KnowledgeDecision, KnowledgeGateVerdict, KnowledgeLoop, KnowledgeLoopDriver, createKnowledgeLoop, createReviewerDecider, reviewCandidate } from './knowledge-loop/index.js';
9
+ export { DEFAULT_HARNESS, Harness, KNOWN_HARNESSES, ResolveSessionHarnessInput, ResolvedSessionHarness, coerceHarness, isHarness, resolveSessionHarness } from './harness/index.js';
9
10
  export { AgentAppConfig, AgentDelegationConfig, AgentIdentityConfig, AgentIntegrationsConfig, AgentKnowledgeConfig, AgentTaxonomyConfig, AgentUiConfig, KnowledgeLoopConfig, KnowledgeSourceSpec, agentAppConfigJsonSchema, defineAgentApp } from './config/index.js';
10
11
  export { D1Like, D1PreparedLike, DrizzleColumnLike, DrizzleSqliteCoreLike, PRESET_MIGRATION_SQL, PRESET_TABLES, PresetBillingOptions, PresetKnowledgeAccessorOptions, PresetToolHandlerOptions, VaultKv, createD1KnowledgeStateAccessor, createPresetDrizzleSchema, createPresetFieldCrypto, createPresetToolHandlers, createPresetWorkspaceKeyManager, createPresetWorkspaceKeyStore } from './preset-cloudflare/index.js';
11
12
  export { KeyCrypto, KeyProvisioner, PlanLimit, PlatformBalanceInfo, PlatformBalanceManager, PlatformBalanceManagerOptions, PlatformBillingClient, PlatformIdentity, PlatformProductUsage, SharedBillingState, WorkspaceKeyManager, WorkspaceKeyManagerOptions, WorkspaceKeyRecord, WorkspaceKeyStore, WorkspaceModelKeyUsage, createPlatformBalanceManager, createWorkspaceKeyManager } from './billing/index.js';
package/dist/index.js CHANGED
@@ -1,7 +1,17 @@
1
+ import {
2
+ redactForIngestion
3
+ } from "./chunk-C5CREGT2.js";
4
+ import {
5
+ DEFAULT_HARNESS,
6
+ KNOWN_HARNESSES,
7
+ coerceHarness,
8
+ isHarness,
9
+ resolveSessionHarness
10
+ } from "./chunk-SD2H4FWY.js";
1
11
  import {
2
12
  agentAppConfigJsonSchema,
3
13
  defineAgentApp
4
- } from "./chunk-5RV6CAHZ.js";
14
+ } from "./chunk-OC3RAUZ2.js";
5
15
  import {
6
16
  PRESET_MIGRATION_SQL,
7
17
  PRESET_TABLES,
@@ -11,7 +21,7 @@ import {
11
21
  createPresetToolHandlers,
12
22
  createPresetWorkspaceKeyManager,
13
23
  createPresetWorkspaceKeyStore
14
- } from "./chunk-EZXN67KE.js";
24
+ } from "./chunk-MTJXFHYD.js";
15
25
  import {
16
26
  createPlatformBalanceManager,
17
27
  createWorkspaceKeyManager
@@ -26,7 +36,7 @@ import {
26
36
  encryptAesGcm,
27
37
  encryptBytes,
28
38
  encryptWithKey
29
- } from "./chunk-ZJGY7OMZ.js";
39
+ } from "./chunk-TA5Q4I2K.js";
30
40
  import {
31
41
  asRecord,
32
42
  asString,
@@ -56,9 +66,6 @@ import {
56
66
  parseJsonObjectBody,
57
67
  requireString
58
68
  } from "./chunk-CN75FIPT.js";
59
- import {
60
- redactForIngestion
61
- } from "./chunk-C5CREGT2.js";
62
69
  import {
63
70
  APP_TOOL_NAMES,
64
71
  DEFAULT_APP_TOOL_PATHS,
@@ -76,7 +83,7 @@ import {
76
83
  outcomeStatus,
77
84
  readToolArgs,
78
85
  verifyCapabilityToken
79
- } from "./chunk-EDIQ6F55.js";
86
+ } from "./chunk-WKY2ZVDY.js";
80
87
  import {
81
88
  DELEGATION_MCP_SERVER_KEY,
82
89
  DELEGATION_TOOLS,
@@ -115,11 +122,13 @@ import {
115
122
  export {
116
123
  APP_TOOL_NAMES,
117
124
  DEFAULT_APP_TOOL_PATHS,
125
+ DEFAULT_HARNESS,
118
126
  DEFAULT_HEADER_NAMES,
119
127
  DEFAULT_TANGLE_ROUTER_BASE_URL,
120
128
  DELEGATION_MCP_SERVER_KEY,
121
129
  DELEGATION_TOOLS,
122
130
  HubExecClient,
131
+ KNOWN_HARNESSES,
123
132
  PRESET_MIGRATION_SQL,
124
133
  PRESET_TABLES,
125
134
  ToolInputError,
@@ -136,6 +145,7 @@ export {
136
145
  buildKnowledgeRequirements,
137
146
  buildUserTextParts,
138
147
  checkRateLimit,
148
+ coerceHarness,
139
149
  createAppToolRuntimeExecutor,
140
150
  createBrokerTokenProvider,
141
151
  createCapabilityToken,
@@ -173,6 +183,7 @@ export {
173
183
  handleAppToolRequest,
174
184
  invokeIntegrationHub,
175
185
  isAppToolName,
186
+ isHarness,
176
187
  mergePersistedPart,
177
188
  messageHasTurnId,
178
189
  normalizeClientTurnId,
@@ -187,6 +198,7 @@ export {
187
198
  requireString,
188
199
  resolveChatTurn,
189
200
  resolveIntegrationAction,
201
+ resolveSessionHarness,
190
202
  resolveTangleModelConfig,
191
203
  resolveToolId,
192
204
  resolveToolName,
@@ -7,9 +7,9 @@ import {
7
7
  createPresetToolHandlers,
8
8
  createPresetWorkspaceKeyManager,
9
9
  createPresetWorkspaceKeyStore
10
- } from "../chunk-EZXN67KE.js";
10
+ } from "../chunk-MTJXFHYD.js";
11
11
  import "../chunk-EAJSWUU5.js";
12
- import "../chunk-ZJGY7OMZ.js";
12
+ import "../chunk-TA5Q4I2K.js";
13
13
  export {
14
14
  PRESET_MIGRATION_SQL,
15
15
  PRESET_TABLES,
@@ -43,7 +43,7 @@ declare function verifyCapabilityToken(userId: string, token: string, opts: Capa
43
43
  /**
44
44
  * Header names carrying the server-set per-turn context + the capability token.
45
45
  * Defaults are product-neutral (`X-Agent-App-*`); a product that already ships
46
- * a header convention (e.g. `X-Insurance-User-Id`) passes its own.
46
+ * a header convention (e.g. `X-Acme-User-Id`) passes its own.
47
47
  */
48
48
  interface ToolHeaderNames {
49
49
  userId: string;
@@ -15,7 +15,7 @@ import {
15
15
  outcomeStatus,
16
16
  readToolArgs,
17
17
  verifyCapabilityToken
18
- } from "../chunk-EDIQ6F55.js";
18
+ } from "../chunk-WKY2ZVDY.js";
19
19
  export {
20
20
  APP_TOOL_NAMES,
21
21
  DEFAULT_APP_TOOL_PATHS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -76,6 +76,11 @@
76
76
  "import": "./dist/knowledge-loop/index.js",
77
77
  "default": "./dist/knowledge-loop/index.js"
78
78
  },
79
+ "./harness": {
80
+ "types": "./dist/harness/index.d.ts",
81
+ "import": "./dist/harness/index.js",
82
+ "default": "./dist/harness/index.js"
83
+ },
79
84
  "./preset-cloudflare": {
80
85
  "types": "./dist/preset-cloudflare/index.d.ts",
81
86
  "import": "./dist/preset-cloudflare/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config/index.ts"],"sourcesContent":["/**\n * `@tangle-network/agent-app/config` — the typed `AgentAppConfig` contract.\n *\n * This is the single declarative DATA surface a coding agent fills to stand up a\n * new agent product. Everything an agent product otherwise hand-rolls as\n * imperative wiring — who the agent is, which proposal types exist and which are\n * approval-gated, what knowledge the control loop requires, which integrations\n * are connectable, how the model resolves — is expressed here as plain data the\n * rest of agent-app's modules consume through their typed seams:\n *\n * - `identity` → the system-prompt + disclaimer surface the chat pipeline reads.\n * - `taxonomy` → which `submit_proposal` types the `./tools` side channel\n * accepts, and which the approval queue treats as regulated\n * (certified-human-gated).\n * - `knowledge` → the `./knowledge` requirement specs that gate the loop, plus\n * the acquisition-loop goal/threshold the researcher pursues.\n * - `integrations` → which `@tangle-network/agent-integrations` catalog kinds the\n * product enables.\n * - `ui` → whether the agent may emit generated UI (`render_ui`).\n * - `model` → the `./runtime` `TangleModelConfig` (or null to resolve from env).\n *\n * Layering: this module is the CONTRACT only — pure types + an identity helper.\n * It introduces no behavior and no engine dependency; it REUSES the existing\n * `KnowledgeRequirementSpec`/`SatisfiedByRule` (from `../knowledge`) and\n * `TangleModelConfig` (from `../runtime`) rather than redefining them, so the\n * config a product authors is the exact shape those modules already consume.\n * Steps that build the composer/loader read this file as the schema floor.\n */\n\nimport type {\n KnowledgeRequirementSpec,\n SatisfiedByRule,\n} from '../knowledge/index'\nimport type { TangleModelConfig } from '../runtime/index'\n\n// Re-export the borrowed types so a config author has a single import surface\n// (`@tangle-network/agent-app/config`) and never has to reach into `../knowledge`\n// or `../runtime` to spell out a field's type. These are the SAME types the\n// knowledge gate and runtime model resolver consume — not parallel copies.\nexport type { KnowledgeRequirementSpec, SatisfiedByRule, TangleModelConfig }\n\n/**\n * Who the agent is, as data. Composed into the chat pipeline's system prompt;\n * never baked into agent-app (a domain value, per the layering contract).\n */\nexport interface AgentIdentityConfig {\n /** Product/agent name, e.g. `\"WinFinance Ops Partner\"`. Shown to the user and\n * available to the system-prompt builder. */\n name: string\n /** One-paragraph persona statement — the agent's role, voice, and remit. The\n * spine of the system prompt. */\n persona: string\n /** Additional system-prompt fragments appended verbatim after the persona\n * (standing workflows, hard rules, tone). Order is preserved. Optional. */\n systemPromptFragments?: string[]\n /** Named disclaimers the product surfaces (e.g. a regulatory human-in-the-loop\n * notice). Keyed by a stable id (`compliance`, `not-advice`, …) so the chat\n * pipeline / UI can select one by name. Values are the literal text. Optional. */\n disclaimers?: Record<string, string>\n}\n\n/**\n * The proposal taxonomy, as data. `proposalTypes` is the closed set of\n * `submit_proposal` types the `./tools` side channel accepts; `regulatedTypes`\n * is the subset that is approval-gated — the executor refuses to run one without\n * a certified human approver. `regulatedTypes` MUST be a subset of\n * `proposalTypes` (validated by the loader step; not enforced at the type level).\n */\nexport interface AgentTaxonomyConfig {\n /** Every proposal type this product can emit, e.g.\n * `['propose_swap', 'contact_lead', 'policy_change']`. The closed allow-list\n * the tool layer validates a `submit_proposal` call against. */\n proposalTypes: string[]\n /** The subset of `proposalTypes` that is regulated → cannot execute without a\n * certified-human approver. The approval executor reads this to decide which\n * proposals are fail-loud certified-gated. */\n regulatedTypes: string[]\n}\n\n/** A knowledge source the acquisition loop / researcher may draw on. */\nexport interface KnowledgeSourceSpec {\n /** Where the source lives — a URL, a `vault://` path, an integration ref, etc.\n * Opaque to agent-app; the consumer's loader resolves it. */\n uri: string\n /** Optional source classifier the researcher uses to pick a fetch strategy,\n * e.g. `'web'`, `'vault'`, `'regulation'`, `'integration'`. Free-form. */\n kind?: string\n}\n\n/**\n * The knowledge-acquisition loop config — the goal the researcher pursues and\n * the gate it must clear before the loop is considered satisfied. All optional;\n * a product with only static requirement specs omits it.\n */\nexport interface KnowledgeLoopConfig {\n /** The acquisition goal in natural language, e.g.\n * `\"ground every quoted premium against a real policy record\"`. */\n goal?: string\n /** The minimum aggregate confidence [0, 1] the loop must reach to pass its\n * gate. The runtime control loop blocks below this. Default decided by the\n * consumer's loop wiring when omitted. */\n minConfidence?: number\n /** How fresh acquired knowledge must be, e.g. `'static'`, `'7d'`, `'session'`.\n * Free-form; the consumer's loop interprets it. */\n freshness?: string\n}\n\n/**\n * The knowledge surface, as data. `sources` are what the researcher may read;\n * `requirements` are the declarative `./knowledge` specs that gate the control\n * loop (reused verbatim — the same `KnowledgeRequirementSpec` the gate scores);\n * `loop` configures the acquisition pass.\n */\nexport interface AgentKnowledgeConfig {\n /** Sources the acquisition loop / researcher may draw on. */\n sources: KnowledgeSourceSpec[]\n /** The declarative requirement specs that gate the loop. Reuses\n * `KnowledgeRequirementSpec` from `../knowledge` — `buildKnowledgeRequirements`\n * + `deriveSignals` consume these directly. */\n requirements: KnowledgeRequirementSpec[]\n /** Optional acquisition-loop config (goal + confidence gate + freshness). */\n loop?: KnowledgeLoopConfig\n}\n\n/**\n * Which integrations the product enables, as data. `enabled` lists\n * `@tangle-network/agent-integrations` catalog kinds the product connects\n * (e.g. `['shurens', 'lead-crm', 'whatsapp']`); the integration hub resolves\n * each to a connector. Strings, not connector objects — agent-app bakes no\n * catalog value.\n */\nexport interface AgentIntegrationsConfig {\n /** Catalog kinds this product enables. */\n enabled: string[]\n}\n\n/** UI capability flags, as data. */\nexport interface AgentUiConfig {\n /** Whether the agent may emit generated UI (the `render_ui` / OpenUI side\n * channel). When false/omitted, the tool layer can omit/refuse the tool.\n * Default behavior decided by the consumer; omit to leave unset. */\n generatedUi?: boolean\n}\n\n/** Background-agent / delegated-loop capability, as data. */\nexport interface AgentDelegationConfig {\n /** When true, the app's own agent may spawn `delegate_research` /\n * `delegate_code` loops that run to completion in their OWN sandbox and\n * return the artifact (the `../delegation` driven-loop MCP). Sandbox-path\n * ONLY — the browser/edge path ignores it. Wired via the existing\n * `buildDelegationMcpServer`; never reimplemented. */\n enabled?: boolean\n}\n\n/**\n * The declarative domain surface of a Tangle agent product.\n *\n * A coding agent fills THIS object and nothing else to define a product's\n * identity, proposal taxonomy, knowledge gate, integrations, UI capability, and\n * model. Every field is DATA consumed by an agent-app module through its typed\n * seam — no field is behavior. Author it through {@link defineAgentApp} for\n * autocomplete and a single import.\n */\nexport interface AgentAppConfig {\n /** Who the agent is — name, persona, system-prompt fragments, disclaimers. */\n identity: AgentIdentityConfig\n /** Proposal types + which are regulated/approval-gated. */\n taxonomy: AgentTaxonomyConfig\n /** Knowledge sources, requirement specs (the loop gate), and loop config. */\n knowledge: AgentKnowledgeConfig\n /** Enabled integration catalog kinds. */\n integrations: AgentIntegrationsConfig\n /** UI capability flags. Optional. */\n ui?: AgentUiConfig\n /** Background-agent / delegated-loop capability (sandbox path). Optional. */\n delegation?: AgentDelegationConfig\n /** The resolved model config (`../runtime`'s `TangleModelConfig`). Omit to\n * resolve from env at boot via `resolveTangleModelConfig`. */\n model?: TangleModelConfig\n}\n\n/**\n * Identity helper: returns its argument unchanged, but anchors inference so a\n * coding agent authoring a config gets full autocomplete + type-checking from a\n * single import. The canonical way to declare a product config:\n *\n * ```ts\n * import { defineAgentApp } from '@tangle-network/agent-app/config'\n *\n * export const config = defineAgentApp({\n * identity: { name: 'WinFinance', persona: '…' },\n * taxonomy: { proposalTypes: ['propose_swap'], regulatedTypes: ['propose_swap'] },\n * knowledge: { sources: [], requirements: [] },\n * integrations: { enabled: ['shurens'] },\n * })\n * ```\n */\nexport function defineAgentApp<const T extends AgentAppConfig>(config: T): T {\n return config\n}\n\n/**\n * Machine-readable JSON Schema (draft 2020-12) for {@link AgentAppConfig}.\n *\n * The schema FLOOR a non-TypeScript coding agent (or a config validator/UI) reads\n * to know the shape without parsing the `.d.ts`. Kept in lockstep with the\n * interfaces above by the config test, which asserts the documented fields are\n * present. `KnowledgeRequirementSpec`'s full sub-shape is intentionally left\n * `additionalProperties: true` here — its authoritative definition lives in\n * `../knowledge`; this floor pins only the fields the config contract owns.\n */\nexport const agentAppConfigJsonSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n $id: 'https://tangle.tools/schemas/agent-app-config.json',\n title: 'AgentAppConfig',\n description: 'The declarative domain surface of a Tangle agent product.',\n type: 'object',\n additionalProperties: false,\n required: ['identity', 'taxonomy', 'knowledge', 'integrations'],\n properties: {\n identity: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'persona'],\n properties: {\n name: { type: 'string', description: 'Product/agent name.' },\n persona: { type: 'string', description: 'One-paragraph persona — the system-prompt spine.' },\n systemPromptFragments: {\n type: 'array',\n items: { type: 'string' },\n description: 'Verbatim system-prompt fragments appended after the persona, in order.',\n },\n disclaimers: {\n type: 'object',\n additionalProperties: { type: 'string' },\n description: 'Named disclaimers keyed by stable id; values are literal text.',\n },\n },\n },\n taxonomy: {\n type: 'object',\n additionalProperties: false,\n required: ['proposalTypes', 'regulatedTypes'],\n properties: {\n proposalTypes: {\n type: 'array',\n items: { type: 'string' },\n description: 'Closed allow-list of proposal types the product can emit.',\n },\n regulatedTypes: {\n type: 'array',\n items: { type: 'string' },\n description: 'Subset of proposalTypes that is approval-gated (certified-human required).',\n },\n },\n },\n knowledge: {\n type: 'object',\n additionalProperties: false,\n required: ['sources', 'requirements'],\n properties: {\n sources: {\n type: 'array',\n description: 'Sources the acquisition loop may draw on.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['uri'],\n properties: {\n uri: { type: 'string', description: 'Where the source lives (opaque to agent-app).' },\n kind: { type: 'string', description: 'Optional source classifier (web/vault/regulation/…).' },\n },\n },\n },\n requirements: {\n type: 'array',\n description: \"Declarative KnowledgeRequirementSpec[] (../knowledge) that gate the loop.\",\n items: { type: 'object', additionalProperties: true },\n },\n loop: {\n type: 'object',\n additionalProperties: false,\n description: 'Acquisition-loop config.',\n properties: {\n goal: { type: 'string', description: 'Acquisition goal in natural language.' },\n minConfidence: {\n type: 'number',\n minimum: 0,\n maximum: 1,\n description: 'Minimum aggregate confidence the loop must reach.',\n },\n freshness: { type: 'string', description: 'How fresh acquired knowledge must be.' },\n },\n },\n },\n },\n integrations: {\n type: 'object',\n additionalProperties: false,\n required: ['enabled'],\n properties: {\n enabled: {\n type: 'array',\n items: { type: 'string' },\n description: 'Enabled agent-integrations catalog kinds.',\n },\n },\n },\n ui: {\n type: 'object',\n additionalProperties: false,\n description: 'UI capability flags.',\n properties: {\n generatedUi: { type: 'boolean', description: 'Whether the agent may emit generated UI.' },\n },\n },\n delegation: {\n type: 'object',\n additionalProperties: false,\n description: 'Background-agent / delegated-loop capability (sandbox path).',\n properties: {\n enabled: { type: 'boolean', description: 'Whether the agent may spawn delegate_research/delegate_code loops.' },\n },\n },\n model: {\n type: 'object',\n additionalProperties: false,\n description: \"Resolved TangleModelConfig (../runtime). Omit to resolve from env.\",\n required: ['provider', 'model', 'apiKey', 'baseUrl'],\n properties: {\n provider: { type: 'string', enum: ['openai-compat', 'anthropic'] },\n model: { type: 'string' },\n apiKey: { type: 'string' },\n baseUrl: { type: 'string' },\n },\n },\n },\n} as const\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/errors.ts","../src/tools/capability.ts","../src/tools/auth.ts","../src/tools/openai.ts","../src/tools/dispatch.ts","../src/tools/runtime.ts","../src/tools/http.ts","../src/tools/mcp.ts"],"sourcesContent":["/** A correctable bad-input error a tool handler throws; the HTTP layer maps it\n * to a 4xx with the code, the runtime layer to a failed tool_result. So the\n * agent learns the call failed and can correct, instead of a silent success. */\nexport class ToolInputError extends Error {\n constructor(\n public code: string,\n message: string,\n public status = 400,\n ) {\n super(message)\n this.name = 'ToolInputError'\n }\n}\n","/**\n * Per-user capability token — the sandbox→app auth primitive behind the\n * `verifyToken` seam in {@link authenticateToolRequest}.\n *\n * An app-agent runs inside the sandbox and reaches the host app back over HTTP\n * (the app tools, the integration-invoke bridge). The route must act AS the\n * connecting user without trusting any model-supplied identity, so the turn\n * mints a short HMAC token bound to the user id and bakes it into the per-turn\n * MCP server header; the route verifies it to recover the user.\n *\n * `HMAC-SHA256(secret, \"user:<userId>\")`, base64url, with an app-chosen prefix.\n * The token encodes no scopes — the hub's policy engine authorizes per action.\n * Fail-closed: with no secret, no token is minted (the caller MUST omit the MCP\n * server rather than fake an authorized call). WebCrypto only — runs on\n * Workers, Node, and the browser with no Node `crypto` dependency.\n */\n\nexport interface CapabilityTokenOptions {\n /** Shared HMAC secret. When absent, mint returns undefined / verify returns false. */\n secret?: string\n /** Token prefix (namespaces the credential; lets verify reject foreign tokens\n * cheaply). Default `cap_`. */\n prefix?: string\n}\n\n/** Mint a capability token for `userId`, or `undefined` when no secret is\n * configured (fail-closed — the caller omits the MCP server rather than fake it). */\nexport async function createCapabilityToken(userId: string, opts: CapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n const prefix = opts.prefix ?? 'cap_'\n return `${prefix}${await sign(userId, secret)}`\n}\n\n/** Verify a capability token against `userId`. Returns false (never throws) for\n * an unconfigured secret, a wrong prefix, a malformed token, or a mismatch. */\nexport async function verifyCapabilityToken(userId: string, token: string, opts: CapabilityTokenOptions): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const expected = `${prefix}${await sign(userId, secret)}`\n return timingSafeEqual(token, expected)\n}\n\nasync function sign(userId: string, secret: string): Promise<string> {\n const enc = new TextEncoder()\n const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n const sig = await crypto.subtle.sign('HMAC', key, enc.encode(`user:${userId}`))\n return base64url(new Uint8Array(sig))\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let s = ''\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/** Length-independent-leak-free compare for two same-charset strings. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","import type { AppToolContext } from './types'\n\n/**\n * Header names carrying the server-set per-turn context + the capability token.\n * Defaults are product-neutral (`X-Agent-App-*`); a product that already ships\n * a header convention (e.g. `X-Insurance-User-Id`) passes its own.\n */\nexport interface ToolHeaderNames {\n userId: string\n workspaceId: string\n threadId: string\n}\n\nexport const DEFAULT_HEADER_NAMES: ToolHeaderNames = {\n userId: 'X-Agent-App-User-Id',\n workspaceId: 'X-Agent-App-Workspace-Id',\n threadId: 'X-Agent-App-Thread-Id',\n}\n\nexport interface AuthenticateOptions {\n /** Verify the bearer capability token belongs to `userId`. The product's\n * HMAC/JWT impl — the seam that keeps token crypto out of this package. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n}\n\nexport type ToolAuthResult =\n | { ok: true; ctx: AppToolContext }\n | { ok: false; response: Response }\n\n/**\n * Recover + verify the trusted context for a tool request. The user comes from\n * a server-set header and the bearer token MUST verify against THAT user; the\n * workspace comes from a header too — never from tool args — so the model can\n * neither forge identity nor target another workspace. Fail-closed: any missing\n * credential or a token minted for another user yields a 401/400 Response.\n */\nexport async function authenticateToolRequest(request: Request, opts: AuthenticateOptions): Promise<ToolAuthResult> {\n const h = opts.headerNames ?? DEFAULT_HEADER_NAMES\n const userId = request.headers.get(h.userId)?.trim()\n const workspaceId = request.headers.get(h.workspaceId)?.trim()\n const threadId = request.headers.get(h.threadId)?.trim() || null\n const bearer = request.headers.get('authorization')?.match(/^Bearer\\s+(.+)$/i)?.[1]\n\n if (!userId || !bearer) {\n return { ok: false, response: Response.json({ error: 'Missing capability credentials' }, { status: 401 }) }\n }\n if (!(await opts.verifyToken(userId, bearer))) {\n return { ok: false, response: Response.json({ error: 'Invalid capability token' }, { status: 401 }) }\n }\n if (!workspaceId) {\n return { ok: false, response: Response.json({ error: 'Missing workspace context' }, { status: 400 }) }\n }\n return { ok: true, ctx: { userId, workspaceId, threadId } }\n}\n\n/** Read a tool's argument object from the request body, tolerant of MCP host\n * aliases (`args` / `arguments`) or a bare body. Returns null on non-JSON. */\nexport async function readToolArgs<T>(request: Request): Promise<T | null> {\n let body: { args?: T; arguments?: T }\n try {\n body = (await request.json()) as typeof body\n } catch {\n return null\n }\n return (body.args ?? body.arguments ?? (body as T)) as T\n}\n","import type { AppToolTaxonomy } from './types'\n\n/** The four canonical app-tool names. Stable identifiers the model calls in\n * both the sandbox (MCP server name) and runtime (function-tool name) paths. */\nexport const APP_TOOL_NAMES = ['submit_proposal', 'schedule_followup', 'render_ui', 'add_citation'] as const\nexport type AppToolName = (typeof APP_TOOL_NAMES)[number]\n\nconst NAME_SET = new Set<string>(APP_TOOL_NAMES)\nexport function isAppToolName(name: string): name is AppToolName {\n return NAME_SET.has(name)\n}\n\n/** A minimal OpenAI Chat Completions function-tool shape — structurally\n * compatible with `@tangle-network/agent-runtime`'s `OpenAIChatTool` without\n * importing it (keeps this package runtime-free). */\nexport interface OpenAIFunctionTool {\n type: 'function'\n function: {\n name: string\n description: string\n parameters: Record<string, unknown>\n }\n}\n\n/**\n * Build the four app tools in OpenAI function-tool shape. `submit_proposal`'s\n * `type` enum is the product's {@link AppToolTaxonomy.proposalTypes}; the other\n * three are fixed. Pass the result to the agent-runtime backend's `tools`.\n */\nexport function buildAppToolOpenAITools(taxonomy: AppToolTaxonomy): OpenAIFunctionTool[] {\n return [\n {\n type: 'function',\n function: {\n name: 'submit_proposal',\n description:\n 'Route a regulated or state-changing action to a human for approval (a recommendation, contacting/soliciting a contact, outreach, a record/account change, scheduling). Queues it for a named certified human to approve before it executes.',\n parameters: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...taxonomy.proposalTypes] },\n title: { type: 'string', description: 'Short label for the approval queue.' },\n description: { type: 'string', description: 'The full drafted message/recommendation, with sources.' },\n },\n required: ['type', 'title'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'schedule_followup',\n description: 'Register a dated cadence step (a reminder, chase, or check-in) on the follow-up calendar. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n dueDate: { type: 'string', description: 'ISO date YYYY-MM-DD.' },\n priority: { type: 'string', enum: ['low', 'medium', 'high'] },\n },\n required: ['title', 'dueDate'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'render_ui',\n description: 'Show a generated view live in the workspace. Validates the OpenUI JSON and persists the artifact. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n schema: { type: 'object', description: 'The OpenUI JSON object.' },\n },\n required: ['title', 'schema'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'add_citation',\n description: 'Anchor a grounding reference: the exact quote from a file backing a figure or claim. Verifies the quote appears in the file. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'The vault file path.' },\n quote: { type: 'string', description: 'The exact text from it.' },\n },\n required: ['path', 'quote'],\n },\n },\n },\n ]\n}\n","import { ToolInputError } from './errors'\nimport { isAppToolName } from './openai'\nimport type {\n AppToolContext,\n AppToolHandlers,\n AppToolOutcome,\n AppToolProducedEvent,\n AppToolTaxonomy,\n} from './types'\n\nexport interface DispatchOptions {\n handlers: AppToolHandlers\n taxonomy: AppToolTaxonomy\n /** Called at the real side-effect site for proposals (proposal_created) and\n * generated views (artifact) so a consumer's completion oracle credits\n * persisted state. Omit when produced state isn't tracked. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\n/**\n * The ONE place an app-tool call is validated, dispatched to the product's\n * handler, and turned into an {@link AppToolOutcome} + produced events. Shared\n * by the HTTP route layer and the agent-runtime executor so both paths apply\n * identical validation and identical side effects. A {@link ToolInputError}\n * (bad input the agent can correct) and any other throw both become\n * `{ ok: false }` — a tool call never silently \"succeeds\" without its effect.\n */\nexport async function dispatchAppTool(\n toolName: string,\n rawArgs: Record<string, unknown>,\n ctx: AppToolContext,\n opts: DispatchOptions,\n): Promise<AppToolOutcome> {\n try {\n if (!isAppToolName(toolName)) {\n return { ok: false, code: 'unknown_tool', message: `${toolName} is not an app tool.` }\n }\n\n if (toolName === 'submit_proposal') {\n const type = String(rawArgs.type ?? '').trim()\n const title = String(rawArgs.title ?? '').trim()\n if (!type || !opts.taxonomy.proposalTypes.includes(type)) {\n return { ok: false, code: 'invalid_type', message: `type must be one of: ${opts.taxonomy.proposalTypes.join(', ')}.` }\n }\n if (!title) return { ok: false, code: 'missing_title', message: 'title is required.' }\n const description = rawArgs.description == null ? null : String(rawArgs.description)\n const r = await opts.handlers.submitProposal({ type, title, description }, ctx)\n const regulated = opts.taxonomy.regulatedTypes.includes(type)\n opts.onProduced?.({ type: 'proposal_created', proposalId: r.proposalId, title, status: 'pending' })\n return { ok: true, result: { status: 'queued_for_approval', proposalId: r.proposalId, deduped: r.deduped, regulated } }\n }\n\n if (toolName === 'schedule_followup') {\n const r = await opts.handlers.scheduleFollowup(\n { title: String(rawArgs.title ?? ''), dueDate: String(rawArgs.dueDate ?? ''), priority: rawArgs.priority as string | undefined },\n ctx,\n )\n return { ok: true, result: { followupId: r.id, dueDate: r.dueDate, deduped: r.deduped } }\n }\n\n if (toolName === 'render_ui') {\n const r = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ''), schema: rawArgs.schema }, ctx)\n opts.onProduced?.({ type: 'artifact', path: r.path, content: r.content })\n return { ok: true, result: { path: r.path } }\n }\n\n // add_citation\n const r = await opts.handlers.addCitation(\n { path: String(rawArgs.path ?? ''), quote: String(rawArgs.quote ?? ''), label: rawArgs.label as string | undefined },\n ctx,\n )\n return { ok: true, result: { citationId: r.citationId, path: r.path } }\n } catch (err) {\n if (err instanceof ToolInputError) return { ok: false, code: err.code, message: err.message, status: err.status }\n return { ok: false, code: 'app_tool_error', message: err instanceof Error ? err.message : String(err), status: 500 }\n }\n}\n\n/** HTTP status for a failed outcome — the handler's `ToolInputError.status`\n * when present, else 400 for a validation reject. */\nexport function outcomeStatus(outcome: Extract<AppToolOutcome, { ok: false }>): number {\n return outcome.status ?? 400\n}\n","import { dispatchAppTool, type DispatchOptions } from './dispatch'\nimport type { AppToolContext, AppToolOutcome } from './types'\n\n/** Executes an app-tool call the model emits on the agent-runtime chat path.\n * Plug into `runChatThroughRuntime({ appToolExecutor })` (or any loop that\n * dispatches function tool_calls). */\nexport type AppToolRuntimeExecutor = (call: {\n toolName: string\n args: Record<string, unknown>\n}) => Promise<AppToolOutcome>\n\nexport interface RuntimeExecutorOptions extends DispatchOptions {\n /** The trusted per-turn context — supplied directly (not from headers), since\n * the runtime path has no HTTP request. */\n ctx: AppToolContext\n}\n\n/**\n * Build the runtime executor for one turn. The agent-runtime backend must also\n * advertise the tools (`buildAppToolOpenAITools(taxonomy)` on the backend's\n * `tools`) for the model to call them; this executor fulfils each call against\n * the product's handlers and emits produced events via `opts.onProduced`.\n */\nexport function createAppToolRuntimeExecutor(opts: RuntimeExecutorOptions): AppToolRuntimeExecutor {\n return ({ toolName, args }) => dispatchAppTool(toolName, args, opts.ctx, opts)\n}\n","import { authenticateToolRequest, type ToolHeaderNames } from './auth'\nimport { dispatchAppTool, outcomeStatus, type DispatchOptions } from './dispatch'\nimport type { AppToolName } from './openai'\n\nexport interface HandleToolRequestOptions extends DispatchOptions {\n /** Which app tool this route serves. */\n tool: AppToolName\n /** Verify the bearer capability token belongs to the header user. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n /** Optional success-message builder for a friendlier tool result. */\n message?: (result: unknown) => string\n}\n\n/**\n * Handle one app-tool HTTP request end to end — the sandbox MCP path. The\n * agent's per-turn HTTP MCP server POSTs here; this authenticates (header user\n * + capability token), reads the args (MCP-alias tolerant), dispatches to the\n * product handler, and returns a JSON Response. A product's route file becomes\n * a one-liner: `export const action = ({ request }) => handleAppToolRequest(request, cfg)`.\n */\nexport async function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response> {\n if (request.method !== 'POST') return Response.json({ error: 'Method not allowed' }, { status: 405 })\n\n const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames })\n if (!auth.ok) return auth.response\n\n let body: { args?: Record<string, unknown>; arguments?: Record<string, unknown> } & Record<string, unknown>\n try {\n body = (await request.json()) as typeof body\n } catch {\n return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n const args = (body.args ?? body.arguments ?? body) as Record<string, unknown>\n\n const outcome = await dispatchAppTool(opts.tool, args, auth.ctx, opts)\n if (!outcome.ok) {\n return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) })\n }\n const payload = outcome.result as Record<string, unknown>\n return Response.json({ ok: true, ...payload, ...(opts.message ? { message: opts.message(outcome.result) } : {}) })\n}\n","import type { AppToolContext } from './types'\nimport type { AppToolName } from './openai'\nimport type { ToolHeaderNames } from './auth'\nimport { DEFAULT_HEADER_NAMES } from './auth'\n\n/** Default route path each app tool is served at. A product mounts its routes\n * at these paths (or supplies its own via {@link BuildMcpServerOptions.paths}). */\nexport const DEFAULT_APP_TOOL_PATHS: Record<AppToolName, string> = {\n submit_proposal: '/api/tools/propose',\n schedule_followup: '/api/tools/followup',\n render_ui: '/api/tools/render-ui',\n add_citation: '/api/tools/citation',\n}\n\n/** The portable MCP server entry the sandbox SDK accepts (transport + url +\n * headers). Matches `AgentProfileMcpServer` structurally without importing the\n * sandbox SDK — products spread it into their profile's `mcp` map. */\nexport interface AppToolMcpServer {\n transport: 'http'\n url: string\n headers: Record<string, string>\n enabled: true\n metadata: { description: string }\n}\n\nexport interface BuildHttpMcpServerOptions {\n /** Route path on the app the sandbox POSTs to (e.g. `/api/tools/propose`). */\n path: string\n /** App base URL the sandbox reaches back to (no trailing slash required). */\n baseUrl: string\n /** Per-user capability token, baked into the Authorization header. */\n token: string\n ctx: AppToolContext\n /** Tool description the model sees. */\n description: string\n headerNames?: ToolHeaderNames\n}\n\n/**\n * Build ONE HTTP MCP server entry — the generic agent→app bridge. The\n * capability token + the user/workspace/thread ids ride in server-set headers\n * (never tool args), so the model can't forge identity or target another\n * workspace. Workspace/thread headers are omitted when their `ctx` value is\n * empty/null (e.g. an integration-invoke bridge that's user-scoped only). Used\n * directly for non-app-tool bridges (integration_invoke) and via\n * {@link buildAppToolMcpServer} for the four app tools.\n */\nexport function buildHttpMcpServer(opts: BuildHttpMcpServerOptions): AppToolMcpServer {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const h = opts.headerNames ?? DEFAULT_HEADER_NAMES\n return {\n transport: 'http',\n url: `${base}${opts.path}`,\n headers: {\n Authorization: `Bearer ${opts.token}`,\n [h.userId]: opts.ctx.userId,\n ...(opts.ctx.workspaceId ? { [h.workspaceId]: opts.ctx.workspaceId } : {}),\n ...(opts.ctx.threadId ? { [h.threadId]: opts.ctx.threadId } : {}),\n 'Content-Type': 'application/json',\n },\n enabled: true,\n metadata: { description: opts.description },\n }\n}\n\nexport interface BuildMcpServerOptions {\n tool: AppToolName\n baseUrl: string\n token: string\n ctx: AppToolContext\n description: string\n headerNames?: ToolHeaderNames\n paths?: Partial<Record<AppToolName, string>>\n}\n\n/** Build one of the four app-tool MCP servers — a thin wrapper over\n * {@link buildHttpMcpServer} that maps the tool name to its route path. */\nexport function buildAppToolMcpServer(opts: BuildMcpServerOptions): AppToolMcpServer {\n return buildHttpMcpServer({\n path: opts.paths?.[opts.tool] ?? DEFAULT_APP_TOOL_PATHS[opts.tool],\n baseUrl: opts.baseUrl,\n token: opts.token,\n ctx: opts.ctx,\n description: opts.description,\n headerNames: opts.headerNames,\n })\n}\n"],"mappings":"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","names":["r"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/crypto/index.ts"],"sourcesContent":["/**\n * AES-256-GCM field encryption (for PII at rest — SSN/EIN/ID numbers, secrets).\n * WebCrypto only — runs on Cloudflare Workers, Node, and the browser with no\n * Node `crypto` dependency. The 32-byte key is a PARAMETER (64-char hex); the\n * framework never reads env — the product binds its own `ENCRYPTION_KEY` (this\n * is the concrete impl behind the `KeyCrypto` seam in `../billing`).\n *\n * Wire format: base64(iv ‖ ciphertext ‖ tag) — the 12-byte IV is prepended; the\n * GCM auth tag is appended by WebCrypto inside the ciphertext.\n */\n\nconst IV_LENGTH = 12\nconst TAG_LENGTH = 16\nconst ALGORITHM = 'AES-GCM'\n\n/** Validate + decode a 64-char hex key to 32 bytes. Throws on the wrong shape so\n * a misconfigured key fails loud, never silently weakens encryption. */\nexport function decodeHexKey(keyHex: string): Uint8Array {\n if (keyHex.length !== 64) throw new Error('encryption key must be a 64-char hex string (32 bytes)')\n const bytes = new Uint8Array(32)\n for (let i = 0; i < 64; i += 2) bytes[i / 2] = parseInt(keyHex.substring(i, i + 2), 16)\n return bytes\n}\n\nasync function importKey(keyHex: string): Promise<CryptoKey> {\n const raw = decodeHexKey(keyHex)\n return crypto.subtle.importKey('raw', raw.buffer as ArrayBuffer, { name: ALGORITHM } as Algorithm, false, ['encrypt', 'decrypt'])\n}\n\nfunction toBase64(data: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < data.length; i++) binary += String.fromCharCode(data[i]!)\n return btoa(binary)\n}\n\nfunction fromBase64(b64: string): Uint8Array {\n const binary = atob(b64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return bytes\n}\n\n/** Encrypt `plaintext` with AES-256-GCM under `keyHex`. Returns\n * base64(iv ‖ ciphertext ‖ tag). A fresh random IV per call. */\nexport async function encryptAesGcm(plaintext: string, keyHex: string): Promise<string> {\n const key = await importKey(keyHex)\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv, tagLength: TAG_LENGTH * 8 }, key, new TextEncoder().encode(plaintext))\n const result = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n result.set(iv, 0)\n result.set(new Uint8Array(ciphertext), IV_LENGTH)\n return toBase64(result)\n}\n\n/** Decrypt a base64(iv ‖ ciphertext ‖ tag) string under `keyHex`. Throws if the\n * tag fails (tamper/wrong key). */\nexport async function decryptAesGcm(encrypted: string, keyHex: string): Promise<string> {\n const key = await importKey(keyHex)\n const data = fromBase64(encrypted)\n const iv = data.slice(0, IV_LENGTH)\n const ciphertext = data.slice(IV_LENGTH)\n const plain = await crypto.subtle.decrypt({ name: ALGORITHM, iv, tagLength: TAG_LENGTH * 8 }, key, ciphertext)\n return new TextDecoder().decode(plain)\n}\n\n/** Build a {@link import('../billing').KeyCrypto}-compatible pair bound to a key\n * (or a key-resolver, for env-backed keys resolved per call). */\nexport function createFieldCrypto(key: string | (() => string)): { encrypt(s: string): Promise<string>; decrypt(s: string): Promise<string> } {\n const resolve = typeof key === 'function' ? key : () => key\n return {\n encrypt: (s) => encryptAesGcm(s, resolve()),\n decrypt: (s) => decryptAesGcm(s, resolve()),\n }\n}\n\n/**\n * --- Passphrase-derived key path (PBKDF2 → AES-256-GCM CryptoKey) ---\n *\n * The `encryptAesGcm`/`decryptAesGcm` path takes a raw 64-char-hex key. Some\n * products instead bind a SECRET STRING (not a hex key) and derive the AES key\n * with PBKDF2 — and need a BINARY path (encrypting document bytes, not just\n * strings). Both are exposed here so a product never hand-rolls WebCrypto.\n *\n * The derivation parameters (salt, iterations) are PARAMETERS — a product pins\n * its own so the derived key bytes stay stable for data already at rest. The\n * default salt/iterations match the historical tax-agent contract, but any\n * product supplies its own via {@link DeriveKeyOptions}.\n */\n\nexport interface DeriveKeyOptions {\n /** PBKDF2 salt. A product MUST pin this — changing it changes the derived key\n * bytes and orphans every value already encrypted at rest. */\n salt: Uint8Array | string\n /** PBKDF2 iteration count. Pin it for the same reason as `salt`. */\n iterations: number\n /** PBKDF2 hash. Default `'SHA-256'`. */\n hash?: 'SHA-256' | 'SHA-384' | 'SHA-512'\n}\n\n/** Derive an AES-256-GCM `CryptoKey` from a secret string via PBKDF2. The key is\n * non-extractable and usable only for encrypt/decrypt. */\nexport async function deriveKey(secret: string, opts: DeriveKeyOptions): Promise<CryptoKey> {\n const salt = typeof opts.salt === 'string' ? new TextEncoder().encode(opts.salt) : opts.salt\n const keyMaterial = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), 'PBKDF2', false, ['deriveKey'])\n return crypto.subtle.deriveKey(\n { name: 'PBKDF2', salt: salt as BufferSource, iterations: opts.iterations, hash: opts.hash ?? 'SHA-256' },\n keyMaterial,\n { name: ALGORITHM, length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n/** Encrypt `plaintext` under a derived `CryptoKey`. Returns base64(iv ‖ ct ‖ tag). */\nexport async function encryptWithKey(plaintext: string, key: CryptoKey): Promise<string> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, new TextEncoder().encode(plaintext))\n const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n out.set(iv, 0)\n out.set(new Uint8Array(ciphertext), IV_LENGTH)\n return toBase64(out)\n}\n\n/** Decrypt a base64(iv ‖ ct ‖ tag) string under a derived `CryptoKey`. */\nexport async function decryptWithKey(encoded: string, key: CryptoKey): Promise<string> {\n const raw = fromBase64(encoded)\n const iv = raw.slice(0, IV_LENGTH)\n const ciphertext = raw.slice(IV_LENGTH)\n const plain = await crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext)\n return new TextDecoder().decode(plain)\n}\n\n/** Encrypt binary data under a derived `CryptoKey`. Returns an ArrayBuffer:\n * 12-byte IV ‖ ciphertext ‖ 16-byte GCM tag (same wire layout as the string\n * path, raw bytes instead of base64). */\nexport async function encryptBytes(data: ArrayBuffer, key: CryptoKey): Promise<ArrayBuffer> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, data)\n const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n out.set(iv, 0)\n out.set(new Uint8Array(ciphertext), IV_LENGTH)\n return out.buffer\n}\n\n/** Decrypt binary data (IV ‖ ciphertext ‖ tag) under a derived `CryptoKey`. */\nexport async function decryptBytes(data: ArrayBuffer, key: CryptoKey): Promise<ArrayBuffer> {\n const raw = new Uint8Array(data)\n const iv = raw.slice(0, IV_LENGTH)\n const ciphertext = raw.slice(IV_LENGTH)\n return crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext)\n}\n"],"mappings":"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","names":[]}