@tangle-network/agent-app 0.43.24 → 0.43.26

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.
Files changed (65) hide show
  1. package/dist/app-auth/index.d.ts +163 -0
  2. package/dist/app-auth/index.js +166 -0
  3. package/dist/app-auth/index.js.map +1 -0
  4. package/dist/assets/index.d.ts +2 -2
  5. package/dist/assistant/index.d.ts +2 -0
  6. package/dist/assistant/index.js +3 -1
  7. package/dist/assistant/index.js.map +1 -1
  8. package/dist/chat-routes/index.d.ts +276 -0
  9. package/dist/chat-routes/index.js +564 -0
  10. package/dist/chat-routes/index.js.map +1 -0
  11. package/dist/chat-store/index.d.ts +194 -0
  12. package/dist/chat-store/index.js +198 -0
  13. package/dist/chat-store/index.js.map +1 -0
  14. package/dist/chunk-4TXDD6P2.js +163 -0
  15. package/dist/chunk-4TXDD6P2.js.map +1 -0
  16. package/dist/chunk-5EQCITY3.js +20 -0
  17. package/dist/chunk-5EQCITY3.js.map +1 -0
  18. package/dist/chunk-5SV5PSU7.js +80 -0
  19. package/dist/chunk-5SV5PSU7.js.map +1 -0
  20. package/dist/chunk-7LNGJDNA.js +1 -0
  21. package/dist/chunk-7LNGJDNA.js.map +1 -0
  22. package/dist/{chunk-77RLNLFP.js → chunk-ATRJULKZ.js} +41 -5
  23. package/dist/chunk-ATRJULKZ.js.map +1 -0
  24. package/dist/{chunk-VMY4TKMN.js → chunk-FCQP75JT.js} +936 -436
  25. package/dist/chunk-FCQP75JT.js.map +1 -0
  26. package/dist/chunk-I2R2XT4M.js +62 -0
  27. package/dist/chunk-I2R2XT4M.js.map +1 -0
  28. package/dist/{chunk-3PK3T4KD.js → chunk-JJGZ54EB.js} +44 -1
  29. package/dist/chunk-JJGZ54EB.js.map +1 -0
  30. package/dist/chunk-TKVJE63N.js +304 -0
  31. package/dist/chunk-TKVJE63N.js.map +1 -0
  32. package/dist/{chunk-SAOAAA3S.js → chunk-UHXQ3KNX.js} +1 -22
  33. package/dist/chunk-UHXQ3KNX.js.map +1 -0
  34. package/dist/chunk-Y4QHNQ75.js +203 -0
  35. package/dist/chunk-Y4QHNQ75.js.map +1 -0
  36. package/dist/contract-DYbTzEDf.d.ts +122 -0
  37. package/dist/core-7qIM7svy.d.ts +21 -0
  38. package/dist/index.d.ts +6 -2
  39. package/dist/index.js +179 -92
  40. package/dist/interactions/index.d.ts +141 -0
  41. package/dist/interactions/index.js +60 -0
  42. package/dist/interactions/index.js.map +1 -0
  43. package/dist/parts-BcbitSNp.d.ts +176 -0
  44. package/dist/platform/index.d.ts +2 -270
  45. package/dist/platform/index.js +14 -278
  46. package/dist/platform/index.js.map +1 -1
  47. package/dist/preset-cloudflare/index.d.ts +0 -10
  48. package/dist/preset-cloudflare/index.js +1 -1
  49. package/dist/profile/index.d.ts +33 -2
  50. package/dist/profile/index.js +37 -1
  51. package/dist/profile/index.js.map +1 -1
  52. package/dist/sandbox/index.d.ts +47 -1
  53. package/dist/sandbox/index.js +11 -1
  54. package/dist/sso-CNOsARMJ.d.ts +285 -0
  55. package/dist/stream/index.js +1 -1
  56. package/dist/teams/index.js +9 -9
  57. package/dist/teams/invitations-api.js +3 -3
  58. package/dist/web-react/index.d.ts +229 -99
  59. package/dist/web-react/index.js +72 -22
  60. package/dist/wire-BaUF66AS.d.ts +61 -0
  61. package/package.json +25 -1
  62. package/dist/chunk-3PK3T4KD.js.map +0 -1
  63. package/dist/chunk-77RLNLFP.js.map +0 -1
  64. package/dist/chunk-SAOAAA3S.js.map +0 -1
  65. package/dist/chunk-VMY4TKMN.js.map +0 -1
@@ -9,6 +9,37 @@ import {
9
9
  // src/profile/index.ts
10
10
  import { mergeAgentProfiles } from "@tangle-network/sandbox";
11
11
  import { profile } from "@tangle-network/agent-eval";
12
+ var DEFAULT_MAX_SYSTEM_PROMPT_BYTES = 4e4;
13
+ function largestPromptSections(prompt, top = 3) {
14
+ const encoder = new TextEncoder();
15
+ const sections = [];
16
+ let title = "(preamble)";
17
+ let start = 0;
18
+ const flush = (end) => {
19
+ const body = prompt.slice(start, end);
20
+ if (body.trim()) sections.push({ title, bytes: encoder.encode(body).byteLength });
21
+ };
22
+ const headingRe = /^#{1,6}\s+(.+)$/gm;
23
+ for (const match of prompt.matchAll(headingRe)) {
24
+ flush(match.index);
25
+ title = (match[1] ?? "").trim() || "(untitled section)";
26
+ start = match.index;
27
+ }
28
+ flush(prompt.length);
29
+ return sections.sort((a, b) => b.bytes - a.bytes).slice(0, top);
30
+ }
31
+ function assertSystemPromptWithinBudget(systemPrompt, budget = {}) {
32
+ const max = budget.maxSystemPromptBytes ?? DEFAULT_MAX_SYSTEM_PROMPT_BYTES;
33
+ const bytes = new TextEncoder().encode(systemPrompt).byteLength;
34
+ if (bytes <= max) return;
35
+ const sections = largestPromptSections(systemPrompt).map((s) => `"${s.title}" (${s.bytes}B)`).join(", ");
36
+ const message = `composed systemPrompt is ${bytes} bytes \u2014 over the ${max}-byte budget (oversized prompts degrade to empty answers). ` + (sections ? `Largest sections: ${sections}. ` : "") + `Trim the prompt, move content to skills/knowledge mounts, or raise maxSystemPromptBytes deliberately.`;
37
+ if (budget.warnOnly) {
38
+ console.warn(`[profile] ${message}`);
39
+ return;
40
+ }
41
+ throw new Error(message);
42
+ }
12
43
  function userSkillMounts(userSkills) {
13
44
  return userSkills.map(
14
45
  (s) => ({
@@ -17,7 +48,7 @@ function userSkillMounts(userSkills) {
17
48
  })
18
49
  ).sort((a, b) => a.path.localeCompare(b.path));
19
50
  }
20
- function composeAgentProfile(base, channels = {}, overlay = {}) {
51
+ function composeAgentProfile(base, channels = {}, overlay = {}, budget = {}) {
21
52
  const shellInput = {
22
53
  skills: channels.skills,
23
54
  knowledge: channels.knowledge,
@@ -41,6 +72,8 @@ function composeAgentProfile(base, channels = {}, overlay = {}) {
41
72
  const merged = mergeAgentProfiles(base, overlayProfile);
42
73
  if (!merged)
43
74
  throw new Error("composeAgentProfile: mergeAgentProfiles returned undefined for a defined base");
75
+ const systemPrompt = merged.prompt?.systemPrompt;
76
+ if (typeof systemPrompt === "string") assertSystemPromptWithinBudget(systemPrompt, budget);
44
77
  return pruneEmptyResourceChannels(merged);
45
78
  }
46
79
  function pruneEmptyResourceChannels(profile2) {
@@ -61,9 +94,12 @@ function makeEvolvableSection(input) {
61
94
  return { id: input.id, title: input.title, body, evolvable: true };
62
95
  }
63
96
  export {
97
+ DEFAULT_MAX_SYSTEM_PROMPT_BYTES,
98
+ assertSystemPromptWithinBudget,
64
99
  composeAgentProfile,
65
100
  composeShellResources,
66
101
  corpusSkills,
102
+ largestPromptSections,
67
103
  loadMarkdownCorpus,
68
104
  makeEvolvableSection,
69
105
  profile,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/profile/index.ts"],"sourcesContent":["/**\n * Profile composer + evolvable-section seam for agent products.\n *\n * The standard \"load a deployable AgentProfile, including skills, plus the\n * skills the end user added to their own instance\" entry point. A product holds\n * a canonical base `AgentProfile` (role/environment/tool-conventions rendered\n * into `prompt.systemPrompt`, baseline skills, baseline MCP). At deploy/turn\n * time it layers four file-mount channels onto `resources.files` —\n *\n * 1. skills — the always-mounted product skill corpus\n * 2. knowledge — a second always-mounted corpus (domain knowledge pack)\n * 3. registry — the tier-gated installable registry (free -> boot-mounted)\n * 4. userSkills — per-user / per-workspace skills the END USER adds to their\n * own instance, mounted at `~/.claude/skills/<id>/SKILL.md`\n * exactly like the registry's free tier\n *\n * plus an optional MCP overlay (delegation + per-turn app-tool side channel), a\n * per-turn `systemPrompt` override, and a `name` override. The merge is the SDK\n * `mergeAgentProfiles`: `mcp` is last-wins per key (base -> overlay), `resources`\n * arrays are concatenated (base ++ overlay), `prompt` is shallow-merged so an\n * overlay carrying only `systemPrompt` overrides it while keeping base\n * instructions. The compose algebra is DATA — the product injects the base\n * profile, the channel mounts (built with the `skills` subpath primitives), the\n * delegation/app-tool MCP map, and the override strings; nothing here reaches\n * for env, a glob, or a specific product's profile.\n *\n * The evolvable-section seam is the loader closure. A product's single\n * self-improvable domain section (the one `applyDomainPatch` targets) loads its\n * body from a deployed markdown override, falling back to an in-tree baseline.\n * The `import.meta.glob('<lit>', ...)` literal must stay at the CONSUMER call\n * site (Vite static-analyzes it), so `makeEvolvableSection` takes the loader as\n * a closure and a REQUIRED `baseline` — it never constructs a glob and never\n * defaults the baseline, so a product can't render an empty learned-guidance\n * section. `stripComments` is the shared \"is this addendum really empty?\" test.\n */\n\nimport type {\n AgentProfile,\n AgentProfileFileMount,\n AgentProfileMcpServer,\n} from '@tangle-network/sandbox'\nimport { mergeAgentProfiles } from '@tangle-network/sandbox'\nimport { profile } from '@tangle-network/agent-eval'\nimport {\n composeShellResources,\n registrySkills,\n skillMountPath,\n type ComposeShellResourcesInput,\n type SkillEntry,\n} from '../skills/index'\n\n/** Re-expose the agent-eval section/render substrate so a product wires the\n * evolvable surface through ONE subpath: `makeEvolvableSection` builds the\n * section, `profile.renderProfile` renders it, `profile.applyDomainPatch` lets\n * the loop patch it by id. The rendering/patching engine stays in agent-eval;\n * reach it through this namespace (re-exporting the bare fns would leak\n * agent-eval's un-nameable AgentProfile type into our generated d.ts). */\nexport { profile }\n\n/** The file-mount channels layered onto `resources.files`. The first three\n * mirror {@link ComposeShellResourcesInput}; `userSkills` is the per-user /\n * per-workspace channel — skills the END USER added to their own instance,\n * mounted at the harness skill-discovery path like the registry's free tier. */\nexport interface ProfileChannels {\n /** Always-mounted skill corpus (pass `corpusSkills(...)`). */\n skills?: AgentProfileFileMount[]\n /** Always-mounted knowledge corpus (pass `corpusSkills(...)` for the pack). */\n knowledge?: AgentProfileFileMount[]\n /** Single-file evolvable / learned-guidance corpora, if mounted as files. */\n evolvable?: AgentProfileFileMount[]\n /** Tier-gated installable registry (pass the registry array; free tier is\n * mounted, paid is install-on-demand). Gated through {@link registrySkills}. */\n registry?: SkillEntry[]\n /** Per-user / per-workspace skills the end user adds to their own instance.\n * Mounted at `~/.claude/skills/<id>/SKILL.md`, the same harness path the\n * registry uses, so a user skill and a registry skill with the same id\n * collide deterministically (the user skill, appended last, wins). */\n userSkills?: UserSkill[]\n /** Final skip filter applied to the composed mount list by mount `path`. */\n filesPredicate?: (mount: AgentProfileFileMount) => boolean\n}\n\n/** A per-user / per-workspace skill: an id and an inline `SKILL.md` body. The\n * user-facing analogue of a registry {@link SkillEntry} with no tier gate —\n * every user skill is mounted (the user opted in by adding it). */\nexport interface UserSkill {\n id: string\n /** Inline `SKILL.md` body mounted at {@link skillMountPath}. */\n skillMd: string\n}\n\n/** Overlay overrides applied on top of the channel mounts. */\nexport interface ProfileOverlay {\n /** Extra MCP servers merged into the profile `mcp` map (last-wins per key over\n * the base servers). The product builds this from its delegation MCP entry\n * and any per-turn app-tool side-channel servers. An absent/`undefined` entry\n * is dropped — pass only the servers that resolved (fail-closed at the seam,\n * not here). */\n mcp?: Record<string, AgentProfileMcpServer>\n /** Per-turn system-prompt override. When set, replaces the base\n * `prompt.systemPrompt` while keeping base `prompt.instructions`. When unset,\n * the base prompt passes through unchanged. */\n systemPrompt?: string\n /** Extra instruction lines merged onto the active prompt (e.g. a per-turn\n * domain/integration directive). Appended to base `prompt.instructions` by\n * the SDK merge. */\n instructions?: string[]\n /** Profile `name` override. When unset, the base name is kept. */\n name?: string\n}\n\n/** Project per-user skills onto SDK file mounts at the harness skill-discovery\n * path. No tier gate — a user skill is mounted because the user added it.\n * Sorted by path for determinism (matches {@link registrySkills}). */\nexport function userSkillMounts(userSkills: UserSkill[]): AgentProfileFileMount[] {\n return userSkills\n .map(\n (s) =>\n ({\n path: skillMountPath(s.id),\n resource: { kind: 'inline', name: s.id, content: s.skillMd },\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/**\n * Compose a deployable `AgentProfile` from a canonical base plus the four\n * file-mount channels and the overlay overrides.\n *\n * Files: base `resources.files` come first; the four channels follow in\n * `skills -> knowledge -> evolvable -> registry -> userSkills` order (so a\n * userSkill that mounts at the same path as a registry skill is the last write\n * and wins). MCP: base servers first, the overlay `mcp` last (last-wins per\n * key). Prompt: the overlay `systemPrompt`, when set, replaces the base one;\n * base instructions are preserved. Name: the overlay `name`, when set, wins.\n *\n * The merge delegates to the SDK `mergeAgentProfiles` (overlay-wins on records,\n * arrays concatenated) — the deterministic algebra is the overlay we hand it,\n * not a hand-rolled spread. `mergeAgentProfiles(base, overlay)` returns\n * `undefined` only when BOTH are `undefined`; `base` is always defined here, so\n * the result is non-`undefined` by construction and we assert that to the caller.\n */\nexport function composeAgentProfile(\n base: AgentProfile,\n channels: ProfileChannels = {},\n overlay: ProfileOverlay = {},\n): AgentProfile {\n const shellInput: ComposeShellResourcesInput = {\n skills: channels.skills,\n knowledge: channels.knowledge,\n evolvable: channels.evolvable,\n registry: channels.registry ? registrySkills(channels.registry) : undefined,\n predicate: channels.filesPredicate,\n }\n const channelFiles = composeShellResources(shellInput)\n const userFiles = channels.userSkills ? userSkillMounts(channels.userSkills) : []\n const overlayFiles = channels.filesPredicate\n ? userFiles.filter(channels.filesPredicate)\n : userFiles\n const files = [...channelFiles, ...overlayFiles]\n\n const promptOverlay: { systemPrompt?: string; instructions?: string[] } = {}\n if (overlay.systemPrompt) promptOverlay.systemPrompt = overlay.systemPrompt\n if (overlay.instructions && overlay.instructions.length > 0) promptOverlay.instructions = overlay.instructions\n\n const overlayProfile: AgentProfile = {\n ...(overlay.name ? { name: overlay.name } : {}),\n ...(Object.keys(promptOverlay).length > 0 ? { prompt: promptOverlay } : {}),\n ...(overlay.mcp ? { mcp: overlay.mcp } : {}),\n resources: { files },\n }\n\n const merged = mergeAgentProfiles(base, overlayProfile)\n if (!merged)\n throw new Error('composeAgentProfile: mergeAgentProfiles returned undefined for a defined base')\n return pruneEmptyResourceChannels(merged)\n}\n\n/** Drop empty resource channels the SDK merge normalizes in (`tools`/`skills`/\n * `agents`/`commands`: `[]`), so the composed profile's wire payload carries\n * only the channels that actually have content — one canonical shape every app\n * emits, instead of a sidecar payload full of empty arrays. */\nfunction pruneEmptyResourceChannels(profile: AgentProfile): AgentProfile {\n if (!profile.resources) return profile\n const kept = Object.fromEntries(\n Object.entries(profile.resources).filter(([, value]) => !(Array.isArray(value) && value.length === 0)),\n ) as AgentProfile['resources']\n const out: AgentProfile = { ...profile, resources: kept }\n if (kept && Object.keys(kept).length === 0) delete out.resources\n return out\n}\n\n/** True body of an addendum file with HTML comments stripped — an all-comment\n * placeholder counts as empty, so the loader falls back to the baseline. */\nexport function stripComments(raw: string): string {\n return raw.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n}\n\n/** Inputs to {@link makeEvolvableSection}. */\nexport interface EvolvableSectionInput {\n /** Section id the self-improvement loop targets with `applyDomainPatch`. */\n id: string\n /** Section title rendered as `### <title>`. */\n title: string\n /**\n * Load the deployed section body. The CONSUMER supplies this closure and runs\n * its own `import.meta.glob('<lit>', { eager: true, query: '?raw', import:\n * 'default' })` inside it — the literal must stay at the call site so Vite can\n * static-analyze it; a glob constructed here would not resolve the product's\n * files. Return the raw markdown (comments and all); `makeEvolvableSection`\n * applies {@link stripComments} to decide whether it is really populated.\n */\n load: () => string\n /**\n * The in-tree fallback body, used when `load()` returns an\n * all-comments/empty placeholder. REQUIRED — no internal default — so a\n * product can never accidentally render an empty evolvable section.\n */\n baseline: string\n}\n\n/**\n * Build the one evolvable (`evolvable: true`) domain section whose body comes\n * from the product's loader, falling back to the required baseline when the\n * loaded body is empty after stripping comments. Returns the agent-eval\n * `AgentProfileSection` shape — drop it straight into `prodProfile`'s shipped\n * sections. The loader is the only seam; the empty-vs-populated rule and the\n * baseline fallback are the lifted algebra.\n */\nexport function makeEvolvableSection(input: EvolvableSectionInput): profile.AgentProfileSection {\n const loaded = input.load()\n const body = stripComments(loaded) ? loaded.trim() : input.baseline\n return { id: input.id, title: input.title, body, evolvable: true }\n}\n\nexport {\n composeShellResources,\n corpusSkills,\n loadMarkdownCorpus,\n registrySkills,\n skillMountPath,\n} from '../skills/index'\nexport type {\n ComposeShellResourcesInput,\n CorpusEntry,\n CorpusLoadResult,\n GlobModules,\n LoadCorpusOptions,\n SkillEntry,\n} from '../skills/index'\n"],"mappings":";;;;;;;;;AAyCA,SAAS,0BAA0B;AACnC,SAAS,eAAe;AAwEjB,SAAS,gBAAgB,YAAkD;AAChF,SAAO,WACJ;AAAA,IACC,CAAC,OACE;AAAA,MACC,MAAM,eAAe,EAAE,EAAE;AAAA,MACzB,UAAU,EAAE,MAAM,UAAU,MAAM,EAAE,IAAI,SAAS,EAAE,QAAQ;AAAA,IAC7D;AAAA,EACJ,EACC,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChD;AAmBO,SAAS,oBACd,MACA,WAA4B,CAAC,GAC7B,UAA0B,CAAC,GACb;AACd,QAAM,aAAyC;AAAA,IAC7C,QAAQ,SAAS;AAAA,IACjB,WAAW,SAAS;AAAA,IACpB,WAAW,SAAS;AAAA,IACpB,UAAU,SAAS,WAAW,eAAe,SAAS,QAAQ,IAAI;AAAA,IAClE,WAAW,SAAS;AAAA,EACtB;AACA,QAAM,eAAe,sBAAsB,UAAU;AACrD,QAAM,YAAY,SAAS,aAAa,gBAAgB,SAAS,UAAU,IAAI,CAAC;AAChF,QAAM,eAAe,SAAS,iBAC1B,UAAU,OAAO,SAAS,cAAc,IACxC;AACJ,QAAM,QAAQ,CAAC,GAAG,cAAc,GAAG,YAAY;AAE/C,QAAM,gBAAoE,CAAC;AAC3E,MAAI,QAAQ,aAAc,eAAc,eAAe,QAAQ;AAC/D,MAAI,QAAQ,gBAAgB,QAAQ,aAAa,SAAS,EAAG,eAAc,eAAe,QAAQ;AAElG,QAAM,iBAA+B;AAAA,IACnC,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC7C,GAAI,OAAO,KAAK,aAAa,EAAE,SAAS,IAAI,EAAE,QAAQ,cAAc,IAAI,CAAC;AAAA,IACzE,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,IAC1C,WAAW,EAAE,MAAM;AAAA,EACrB;AAEA,QAAM,SAAS,mBAAmB,MAAM,cAAc;AACtD,MAAI,CAAC;AACH,UAAM,IAAI,MAAM,+EAA+E;AACjG,SAAO,2BAA2B,MAAM;AAC1C;AAMA,SAAS,2BAA2BA,UAAqC;AACvE,MAAI,CAACA,SAAQ,UAAW,QAAOA;AAC/B,QAAM,OAAO,OAAO;AAAA,IAClB,OAAO,QAAQA,SAAQ,SAAS,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,EAAE;AAAA,EACvG;AACA,QAAM,MAAoB,EAAE,GAAGA,UAAS,WAAW,KAAK;AACxD,MAAI,QAAQ,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG,QAAO,IAAI;AACvD,SAAO;AACT;AAIO,SAAS,cAAc,KAAqB;AACjD,SAAO,IAAI,QAAQ,oBAAoB,EAAE,EAAE,KAAK;AAClD;AAiCO,SAAS,qBAAqB,OAA2D;AAC9F,QAAM,SAAS,MAAM,KAAK;AAC1B,QAAM,OAAO,cAAc,MAAM,IAAI,OAAO,KAAK,IAAI,MAAM;AAC3D,SAAO,EAAE,IAAI,MAAM,IAAI,OAAO,MAAM,OAAO,MAAM,WAAW,KAAK;AACnE;","names":["profile"]}
1
+ {"version":3,"sources":["../../src/profile/index.ts"],"sourcesContent":["/**\n * Profile composer + evolvable-section seam for agent products.\n *\n * The standard \"load a deployable AgentProfile, including skills, plus the\n * skills the end user added to their own instance\" entry point. A product holds\n * a canonical base `AgentProfile` (role/environment/tool-conventions rendered\n * into `prompt.systemPrompt`, baseline skills, baseline MCP). At deploy/turn\n * time it layers four file-mount channels onto `resources.files` —\n *\n * 1. skills — the always-mounted product skill corpus\n * 2. knowledge — a second always-mounted corpus (domain knowledge pack)\n * 3. registry — the tier-gated installable registry (free -> boot-mounted)\n * 4. userSkills — per-user / per-workspace skills the END USER adds to their\n * own instance, mounted at `~/.claude/skills/<id>/SKILL.md`\n * exactly like the registry's free tier\n *\n * plus an optional MCP overlay (delegation + per-turn app-tool side channel), a\n * per-turn `systemPrompt` override, and a `name` override. The merge is the SDK\n * `mergeAgentProfiles`: `mcp` is last-wins per key (base -> overlay), `resources`\n * arrays are concatenated (base ++ overlay), `prompt` is shallow-merged so an\n * overlay carrying only `systemPrompt` overrides it while keeping base\n * instructions. The compose algebra is DATA — the product injects the base\n * profile, the channel mounts (built with the `skills` subpath primitives), the\n * delegation/app-tool MCP map, and the override strings; nothing here reaches\n * for env, a glob, or a specific product's profile.\n *\n * The evolvable-section seam is the loader closure. A product's single\n * self-improvable domain section (the one `applyDomainPatch` targets) loads its\n * body from a deployed markdown override, falling back to an in-tree baseline.\n * The `import.meta.glob('<lit>', ...)` literal must stay at the CONSUMER call\n * site (Vite static-analyzes it), so `makeEvolvableSection` takes the loader as\n * a closure and a REQUIRED `baseline` — it never constructs a glob and never\n * defaults the baseline, so a product can't render an empty learned-guidance\n * section. `stripComments` is the shared \"is this addendum really empty?\" test.\n */\n\nimport type {\n AgentProfile,\n AgentProfileFileMount,\n AgentProfileMcpServer,\n} from '@tangle-network/sandbox'\nimport { mergeAgentProfiles } from '@tangle-network/sandbox'\nimport { profile } from '@tangle-network/agent-eval'\nimport {\n composeShellResources,\n registrySkills,\n skillMountPath,\n type ComposeShellResourcesInput,\n type SkillEntry,\n} from '../skills/index'\n\n/** Re-expose the agent-eval section/render substrate so a product wires the\n * evolvable surface through ONE subpath: `makeEvolvableSection` builds the\n * section, `profile.renderProfile` renders it, `profile.applyDomainPatch` lets\n * the loop patch it by id. The rendering/patching engine stays in agent-eval;\n * reach it through this namespace (re-exporting the bare fns would leak\n * agent-eval's un-nameable AgentProfile type into our generated d.ts). */\nexport { profile }\n\n/** The file-mount channels layered onto `resources.files`. The first three\n * mirror {@link ComposeShellResourcesInput}; `userSkills` is the per-user /\n * per-workspace channel — skills the END USER added to their own instance,\n * mounted at the harness skill-discovery path like the registry's free tier. */\nexport interface ProfileChannels {\n /** Always-mounted skill corpus (pass `corpusSkills(...)`). */\n skills?: AgentProfileFileMount[]\n /** Always-mounted knowledge corpus (pass `corpusSkills(...)` for the pack). */\n knowledge?: AgentProfileFileMount[]\n /** Single-file evolvable / learned-guidance corpora, if mounted as files. */\n evolvable?: AgentProfileFileMount[]\n /** Tier-gated installable registry (pass the registry array; free tier is\n * mounted, paid is install-on-demand). Gated through {@link registrySkills}. */\n registry?: SkillEntry[]\n /** Per-user / per-workspace skills the end user adds to their own instance.\n * Mounted at `~/.claude/skills/<id>/SKILL.md`, the same harness path the\n * registry uses, so a user skill and a registry skill with the same id\n * collide deterministically (the user skill, appended last, wins). */\n userSkills?: UserSkill[]\n /** Final skip filter applied to the composed mount list by mount `path`. */\n filesPredicate?: (mount: AgentProfileFileMount) => boolean\n}\n\n/** A per-user / per-workspace skill: an id and an inline `SKILL.md` body. The\n * user-facing analogue of a registry {@link SkillEntry} with no tier gate —\n * every user skill is mounted (the user opted in by adding it). */\nexport interface UserSkill {\n id: string\n /** Inline `SKILL.md` body mounted at {@link skillMountPath}. */\n skillMd: string\n}\n\n/** Overlay overrides applied on top of the channel mounts. */\nexport interface ProfileOverlay {\n /** Extra MCP servers merged into the profile `mcp` map (last-wins per key over\n * the base servers). The product builds this from its delegation MCP entry\n * and any per-turn app-tool side-channel servers. An absent/`undefined` entry\n * is dropped — pass only the servers that resolved (fail-closed at the seam,\n * not here). */\n mcp?: Record<string, AgentProfileMcpServer>\n /** Per-turn system-prompt override. When set, replaces the base\n * `prompt.systemPrompt` while keeping base `prompt.instructions`. When unset,\n * the base prompt passes through unchanged. */\n systemPrompt?: string\n /** Extra instruction lines merged onto the active prompt (e.g. a per-turn\n * domain/integration directive). Appended to base `prompt.instructions` by\n * the SDK merge. */\n instructions?: string[]\n /** Profile `name` override. When unset, the base name is kept. */\n name?: string\n}\n\n/** Byte budget on the FINAL composed `prompt.systemPrompt`. Past this the\n * model degrades sharply (a 122,659-byte prompt shipped once and the model\n * returned empty answers), so the default gate throws well before that. */\nexport const DEFAULT_MAX_SYSTEM_PROMPT_BYTES = 40_000\n\n/** Budget config for the composed system prompt. */\nexport interface ComposeProfileBudget {\n /** Byte cap on the composed `prompt.systemPrompt`.\n * Default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}. */\n maxSystemPromptBytes?: number\n /** Downgrade the over-budget throw to a `console.warn` — the escape hatch\n * for a product with a known-big prompt that must still ship (it yells on\n * every compose instead of blocking). */\n warnOnly?: boolean\n}\n\n/** Largest markdown-heading-delimited sections of a prompt, by UTF-8 bytes.\n * Cheap heuristic: split on `#`-heading lines; the preamble before the first\n * heading reports as \"(preamble)\". */\nexport function largestPromptSections(\n prompt: string,\n top = 3,\n): Array<{ title: string; bytes: number }> {\n const encoder = new TextEncoder()\n const sections: Array<{ title: string; bytes: number }> = []\n let title = '(preamble)'\n let start = 0\n const flush = (end: number) => {\n const body = prompt.slice(start, end)\n if (body.trim()) sections.push({ title, bytes: encoder.encode(body).byteLength })\n }\n const headingRe = /^#{1,6}\\s+(.+)$/gm\n for (const match of prompt.matchAll(headingRe)) {\n flush(match.index)\n title = (match[1] ?? '').trim() || '(untitled section)'\n start = match.index\n }\n flush(prompt.length)\n return sections.sort((a, b) => b.bytes - a.bytes).slice(0, top)\n}\n\n/** Enforce {@link ComposeProfileBudget} on a composed system prompt: over\n * budget throws (or warns with `warnOnly`) with the actual size and the\n * top-3 largest sections. Exported so a product assembling its prompt\n * outside {@link composeAgentProfile} (e.g. via the `/prompt` assembler) can\n * run the same gate at its own final-composition point. */\nexport function assertSystemPromptWithinBudget(\n systemPrompt: string,\n budget: ComposeProfileBudget = {},\n): void {\n const max = budget.maxSystemPromptBytes ?? DEFAULT_MAX_SYSTEM_PROMPT_BYTES\n const bytes = new TextEncoder().encode(systemPrompt).byteLength\n if (bytes <= max) return\n const sections = largestPromptSections(systemPrompt)\n .map((s) => `\"${s.title}\" (${s.bytes}B)`)\n .join(', ')\n const message =\n `composed systemPrompt is ${bytes} bytes — over the ${max}-byte budget ` +\n `(oversized prompts degrade to empty answers). ` +\n (sections ? `Largest sections: ${sections}. ` : '') +\n `Trim the prompt, move content to skills/knowledge mounts, or raise maxSystemPromptBytes deliberately.`\n if (budget.warnOnly) {\n console.warn(`[profile] ${message}`)\n return\n }\n throw new Error(message)\n}\n\n/** Project per-user skills onto SDK file mounts at the harness skill-discovery\n * path. No tier gate — a user skill is mounted because the user added it.\n * Sorted by path for determinism (matches {@link registrySkills}). */\nexport function userSkillMounts(userSkills: UserSkill[]): AgentProfileFileMount[] {\n return userSkills\n .map(\n (s) =>\n ({\n path: skillMountPath(s.id),\n resource: { kind: 'inline', name: s.id, content: s.skillMd },\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/**\n * Compose a deployable `AgentProfile` from a canonical base plus the four\n * file-mount channels and the overlay overrides.\n *\n * Files: base `resources.files` come first; the four channels follow in\n * `skills -> knowledge -> evolvable -> registry -> userSkills` order (so a\n * userSkill that mounts at the same path as a registry skill is the last write\n * and wins). MCP: base servers first, the overlay `mcp` last (last-wins per\n * key). Prompt: the overlay `systemPrompt`, when set, replaces the base one;\n * base instructions are preserved. Name: the overlay `name`, when set, wins.\n *\n * The merge delegates to the SDK `mergeAgentProfiles` (overlay-wins on records,\n * arrays concatenated) — the deterministic algebra is the overlay we hand it,\n * not a hand-rolled spread. `mergeAgentProfiles(base, overlay)` returns\n * `undefined` only when BOTH are `undefined`; `base` is always defined here, so\n * the result is non-`undefined` by construction and we assert that to the caller.\n *\n * The composed `prompt.systemPrompt` is byte-budgeted here — the single point\n * where the FINAL prompt exists ({@link assertSystemPromptWithinBudget};\n * default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}, `warnOnly` escape hatch).\n */\nexport function composeAgentProfile(\n base: AgentProfile,\n channels: ProfileChannels = {},\n overlay: ProfileOverlay = {},\n budget: ComposeProfileBudget = {},\n): AgentProfile {\n const shellInput: ComposeShellResourcesInput = {\n skills: channels.skills,\n knowledge: channels.knowledge,\n evolvable: channels.evolvable,\n registry: channels.registry ? registrySkills(channels.registry) : undefined,\n predicate: channels.filesPredicate,\n }\n const channelFiles = composeShellResources(shellInput)\n const userFiles = channels.userSkills ? userSkillMounts(channels.userSkills) : []\n const overlayFiles = channels.filesPredicate\n ? userFiles.filter(channels.filesPredicate)\n : userFiles\n const files = [...channelFiles, ...overlayFiles]\n\n const promptOverlay: { systemPrompt?: string; instructions?: string[] } = {}\n if (overlay.systemPrompt) promptOverlay.systemPrompt = overlay.systemPrompt\n if (overlay.instructions && overlay.instructions.length > 0) promptOverlay.instructions = overlay.instructions\n\n const overlayProfile: AgentProfile = {\n ...(overlay.name ? { name: overlay.name } : {}),\n ...(Object.keys(promptOverlay).length > 0 ? { prompt: promptOverlay } : {}),\n ...(overlay.mcp ? { mcp: overlay.mcp } : {}),\n resources: { files },\n }\n\n const merged = mergeAgentProfiles(base, overlayProfile)\n if (!merged)\n throw new Error('composeAgentProfile: mergeAgentProfiles returned undefined for a defined base')\n // Byte-budget gate on the FINAL composed systemPrompt — this is the single\n // point where every channel and overlay has been merged in.\n const systemPrompt = merged.prompt?.systemPrompt\n if (typeof systemPrompt === 'string') assertSystemPromptWithinBudget(systemPrompt, budget)\n return pruneEmptyResourceChannels(merged)\n}\n\n/** Drop empty resource channels the SDK merge normalizes in (`tools`/`skills`/\n * `agents`/`commands`: `[]`), so the composed profile's wire payload carries\n * only the channels that actually have content — one canonical shape every app\n * emits, instead of a sidecar payload full of empty arrays. */\nfunction pruneEmptyResourceChannels(profile: AgentProfile): AgentProfile {\n if (!profile.resources) return profile\n const kept = Object.fromEntries(\n Object.entries(profile.resources).filter(([, value]) => !(Array.isArray(value) && value.length === 0)),\n ) as AgentProfile['resources']\n const out: AgentProfile = { ...profile, resources: kept }\n if (kept && Object.keys(kept).length === 0) delete out.resources\n return out\n}\n\n/** True body of an addendum file with HTML comments stripped — an all-comment\n * placeholder counts as empty, so the loader falls back to the baseline. */\nexport function stripComments(raw: string): string {\n return raw.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n}\n\n/** Inputs to {@link makeEvolvableSection}. */\nexport interface EvolvableSectionInput {\n /** Section id the self-improvement loop targets with `applyDomainPatch`. */\n id: string\n /** Section title rendered as `### <title>`. */\n title: string\n /**\n * Load the deployed section body. The CONSUMER supplies this closure and runs\n * its own `import.meta.glob('<lit>', { eager: true, query: '?raw', import:\n * 'default' })` inside it — the literal must stay at the call site so Vite can\n * static-analyze it; a glob constructed here would not resolve the product's\n * files. Return the raw markdown (comments and all); `makeEvolvableSection`\n * applies {@link stripComments} to decide whether it is really populated.\n */\n load: () => string\n /**\n * The in-tree fallback body, used when `load()` returns an\n * all-comments/empty placeholder. REQUIRED — no internal default — so a\n * product can never accidentally render an empty evolvable section.\n */\n baseline: string\n}\n\n/**\n * Build the one evolvable (`evolvable: true`) domain section whose body comes\n * from the product's loader, falling back to the required baseline when the\n * loaded body is empty after stripping comments. Returns the agent-eval\n * `AgentProfileSection` shape — drop it straight into `prodProfile`'s shipped\n * sections. The loader is the only seam; the empty-vs-populated rule and the\n * baseline fallback are the lifted algebra.\n */\nexport function makeEvolvableSection(input: EvolvableSectionInput): profile.AgentProfileSection {\n const loaded = input.load()\n const body = stripComments(loaded) ? loaded.trim() : input.baseline\n return { id: input.id, title: input.title, body, evolvable: true }\n}\n\nexport {\n composeShellResources,\n corpusSkills,\n loadMarkdownCorpus,\n registrySkills,\n skillMountPath,\n} from '../skills/index'\nexport type {\n ComposeShellResourcesInput,\n CorpusEntry,\n CorpusLoadResult,\n GlobModules,\n LoadCorpusOptions,\n SkillEntry,\n} from '../skills/index'\n"],"mappings":"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","names":["profile"]}
@@ -384,6 +384,52 @@ interface WriteProfileFilesOptions {
384
384
  maxRetries?: number;
385
385
  }
386
386
  declare function writeProfileFilesToBox(box: SandboxInstance, files: AgentProfileFileMount[], options?: WriteProfileFilesOptions): Promise<Outcome<void>>;
387
+ /** Gate on the provision body: the platform orchestrator caps the create
388
+ * payload at 256 KiB; 240 KB leaves headroom for transport framing. An
389
+ * over-cap payload fails provisioning 100% of the time (a 282 KB payload
390
+ * shipped once and no sandbox could ever be created). */
391
+ declare const PROVISION_PAYLOAD_MAX_BYTES = 240000;
392
+ /** Per-variable env gate: the kernel rejects any single `NAME=value` env entry
393
+ * over MAX_ARG_STRLEN (131072 bytes) with E2BIG, killing every exec inside
394
+ * the box. 120 KB leaves headroom for the name and framing. */
395
+ declare const ENV_VALUE_MAX_BYTES = 120000;
396
+ /** Total env gate: the whole environment block shares the payload budget with
397
+ * the profile; past 200 KB the provision body cannot stay under the cap. */
398
+ declare const ENV_TOTAL_MAX_BYTES = 200000;
399
+ /** Structural slice of the profile the payload gate reads: it only measures
400
+ * the profile's serialized size and names `resources.files` in the breakdown,
401
+ * so callers composing a payload outside the SDK (products, tests) can pass a
402
+ * plain object without casting through `AgentProfile`. */
403
+ interface ProvisionProfileSection {
404
+ resources?: {
405
+ files?: readonly unknown[];
406
+ };
407
+ }
408
+ /** The provision-payload sections the size gates need to see. Structural so
409
+ * the gate is testable without the SDK's (unexported) create-payload type. */
410
+ interface ProvisionPayloadSections {
411
+ env?: Record<string, string>;
412
+ secrets?: readonly string[];
413
+ /** `profile` may also be a named-profile string ref (the SDK's
414
+ * `BackendConfig` union) — a string ref is tiny and has no files channel. */
415
+ backend?: {
416
+ profile?: string | ProvisionProfileSection;
417
+ };
418
+ }
419
+ /**
420
+ * Throw when the serialized provision payload exceeds
421
+ * {@link PROVISION_PAYLOAD_MAX_BYTES}. The error carries a per-section byte
422
+ * breakdown (profile/files/env/secrets) so the offending channel is named, not
423
+ * guessed.
424
+ */
425
+ declare function assertProvisionPayloadWithinCap(payload: ProvisionPayloadSections): void;
426
+ /**
427
+ * Throw when any single env value exceeds {@link ENV_VALUE_MAX_BYTES} or the
428
+ * whole env block exceeds {@link ENV_TOTAL_MAX_BYTES}, naming the offending
429
+ * variable. This is the E2BIG incident class: the box may even provision, but
430
+ * every exec inside it dies on the oversized entry.
431
+ */
432
+ declare function assertEnvWithinLimits(env: Record<string, string>): void;
387
433
  declare function ensureWorkspaceSandbox(shell: SandboxRuntimeConfig, options: EnsureWorkspaceSandboxOptions): Promise<SandboxInstance>;
388
434
  interface ResolvedModel {
389
435
  model: string;
@@ -485,4 +531,4 @@ declare function classifySeveredStream(event: unknown): SandboxStepTransition |
485
531
  declare function isTerminalPromptEvent(event: unknown): boolean;
486
532
  declare function detectInteractiveQuestion(event: unknown): string | null;
487
533
 
488
- export { type AppToolDescriptor, type AuthenticatedSandboxUser, type BuildAppToolMcpServersOptions, type BuildSandboxToolFileMountsOptions, DEFAULT_SANDBOX_RESOURCES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type Outcome, type ProfileComposeOptions, type PromptInputPart, type ProviderResolutionConfig, type ResolveSandboxClientCredentialsOptions, type ResolvedModel, type SandboxApiCredentials, type SandboxBuildContext, type SandboxClientCredentials, type SandboxCredentialEnvironment, type SandboxPermissionLevel, type SandboxResourceConfig, type SandboxRestoreSpec, SandboxRuntimeAuthRefreshError, type SandboxRuntimeConfig, type SandboxRuntimeConnection, type SandboxScope, type SandboxStepTransition, type SandboxTerminalTokenOptions, type SandboxTerminalTokenResult, type SandboxTerminalTokenSubject, type SandboxTerminalWsMatch, type SandboxToolPathOptions, type SandboxToolSpec, type ScopedTokenResult, type SecretStore, type StoppedSandboxResumeFailure, type StoppedSandboxResumeRecovery, type StreamSandboxPromptOptions, type TerminalProxyIdentity, type WorkspaceSandboxConnectionArgs, type WorkspaceSandboxConnectionHandlerOptions, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRuntimeProxyArgs, type WorkspaceSandboxRuntimeProxyHandlerOptions, type WorkspaceSandboxTerminalUpgradeHandlerOptions, type WriteProfileFilesOptions, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, buildSandboxToolFileMounts, buildSandboxToolPathSetupScript, classifySeveredStream, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mergeHistoryIntoParts, mintSandboxScopedToken, mintTerminalProxyToken, readSecret, resetClientCache, resolveModel, resolveSandboxClientCredentials, runSandboxPrompt, runSandboxToolPathSetup, sandboxToolBinDir, sandboxToolPath, sandboxToolRootDir, secretStoreFromClient, splitDeferredProfileFiles, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken, writeProfileFilesToBox };
534
+ export { type AppToolDescriptor, type AuthenticatedSandboxUser, type BuildAppToolMcpServersOptions, type BuildSandboxToolFileMountsOptions, DEFAULT_SANDBOX_RESOURCES, ENV_TOTAL_MAX_BYTES, ENV_VALUE_MAX_BYTES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type Outcome, PROVISION_PAYLOAD_MAX_BYTES, type ProfileComposeOptions, type PromptInputPart, type ProviderResolutionConfig, type ProvisionPayloadSections, type ProvisionProfileSection, type ResolveSandboxClientCredentialsOptions, type ResolvedModel, type SandboxApiCredentials, type SandboxBuildContext, type SandboxClientCredentials, type SandboxCredentialEnvironment, type SandboxPermissionLevel, type SandboxResourceConfig, type SandboxRestoreSpec, SandboxRuntimeAuthRefreshError, type SandboxRuntimeConfig, type SandboxRuntimeConnection, type SandboxScope, type SandboxStepTransition, type SandboxTerminalTokenOptions, type SandboxTerminalTokenResult, type SandboxTerminalTokenSubject, type SandboxTerminalWsMatch, type SandboxToolPathOptions, type SandboxToolSpec, type ScopedTokenResult, type SecretStore, type StoppedSandboxResumeFailure, type StoppedSandboxResumeRecovery, type StreamSandboxPromptOptions, type TerminalProxyIdentity, type WorkspaceSandboxConnectionArgs, type WorkspaceSandboxConnectionHandlerOptions, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRuntimeProxyArgs, type WorkspaceSandboxRuntimeProxyHandlerOptions, type WorkspaceSandboxTerminalUpgradeHandlerOptions, type WriteProfileFilesOptions, assertEnvWithinLimits, assertProvisionPayloadWithinCap, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, buildSandboxToolFileMounts, buildSandboxToolPathSetupScript, classifySeveredStream, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mergeHistoryIntoParts, mintSandboxScopedToken, mintTerminalProxyToken, readSecret, resetClientCache, resolveModel, resolveSandboxClientCredentials, runSandboxPrompt, runSandboxToolPathSetup, sandboxToolBinDir, sandboxToolPath, sandboxToolRootDir, secretStoreFromClient, splitDeferredProfileFiles, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken, writeProfileFilesToBox };
@@ -1,6 +1,11 @@
1
1
  import {
2
2
  DEFAULT_SANDBOX_RESOURCES,
3
+ ENV_TOTAL_MAX_BYTES,
4
+ ENV_VALUE_MAX_BYTES,
5
+ PROVISION_PAYLOAD_MAX_BYTES,
3
6
  SandboxRuntimeAuthRefreshError,
7
+ assertEnvWithinLimits,
8
+ assertProvisionPayloadWithinCap,
4
9
  attachReasoningEffort,
5
10
  bearerSubprotocolToken,
6
11
  bearerToken,
@@ -48,7 +53,7 @@ import {
48
53
  verifySandboxTerminalToken,
49
54
  verifyTerminalProxyToken,
50
55
  writeProfileFilesToBox
51
- } from "../chunk-3PK3T4KD.js";
56
+ } from "../chunk-JJGZ54EB.js";
52
57
  import "../chunk-E7QYOOON.js";
53
58
  import "../chunk-3II3AWHY.js";
54
59
  import "../chunk-7EVZUIHW.js";
@@ -56,7 +61,12 @@ import "../chunk-7W5XSTUF.js";
56
61
  import "../chunk-MFRCM32T.js";
57
62
  export {
58
63
  DEFAULT_SANDBOX_RESOURCES,
64
+ ENV_TOTAL_MAX_BYTES,
65
+ ENV_VALUE_MAX_BYTES,
66
+ PROVISION_PAYLOAD_MAX_BYTES,
59
67
  SandboxRuntimeAuthRefreshError,
68
+ assertEnvWithinLimits,
69
+ assertProvisionPayloadWithinCap,
60
70
  attachReasoningEffort,
61
71
  bearerSubprotocolToken,
62
72
  bearerToken,
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Request guards for agent-app routes: session auth (302 redirect for pages,
3
+ * JSON 401 for APIs), admin allowlisting (404 — the route stays invisible to
4
+ * non-admins), and the billable-balance gate (402 with a stable code).
5
+ * Session resolution is a seam; thrown Responses follow the router convention
6
+ * of surfacing a thrown Response as the route result.
7
+ */
8
+ interface AuthGuardOptions<Session> {
9
+ /** e.g. a better-auth `auth.api.getSession` wrapped by the app. */
10
+ getSession(request: Request): Promise<Session | null | undefined>;
11
+ /** Default '/login'. */
12
+ loginPath?: string;
13
+ }
14
+ interface AuthGuard<Session> {
15
+ /** Page guard — throws a 302 redirect Response to `loginPath`. */
16
+ requireUser(request: Request): Promise<Session>;
17
+ /** API guard — throws JSON 401 `{ error: 'Unauthorized', code: 'auth.unauthenticated' }`. */
18
+ requireApiUser(request: Request): Promise<Session>;
19
+ /** `apiResponse` selects the 401 JSON path over the redirect. */
20
+ requireSession(request: Request, opts?: {
21
+ apiResponse?: boolean;
22
+ }): Promise<Session>;
23
+ getOptionalSession(request: Request): Promise<Session | null>;
24
+ }
25
+ declare function createAuthGuard<Session>(opts: AuthGuardOptions<Session>): AuthGuard<Session>;
26
+ type GuardResolution<T> = {
27
+ ok: true;
28
+ value: T;
29
+ } | {
30
+ ok: false;
31
+ response: Response;
32
+ };
33
+ /**
34
+ * Adapt a guard that THROWS a Response (the quartet above — the router
35
+ * convention) to the `{ok: true, value} | {ok: false, response}` resolution
36
+ * shape the route factories take (`/chat-routes` `authorize`,
37
+ * `/interactions` `resolveConnection`, `/chat-routes` upload `authorize`).
38
+ * Every product wrote this try/catch by hand; it lives here once.
39
+ */
40
+ declare function guardResolution<T>(run: () => Promise<T>): Promise<GuardResolution<T>>;
41
+ /** Comma/whitespace separated → trimmed, lowercased, empties dropped. */
42
+ declare function parseAdminEmails(raw: string | null | undefined): string[];
43
+ interface AdminGuardOptions<Session> {
44
+ requireUser(request: Request): Promise<Session>;
45
+ emailOf(session: Session): string | null | undefined;
46
+ /** Resolved per request; an EMPTY allowlist refuses everyone. */
47
+ allowedEmails(): string[];
48
+ }
49
+ /** Non-admins (and empty allowlists) get 404, keeping the route invisible —
50
+ * better than a "forbidden" footprint that advertises its existence. */
51
+ declare function createAdminGuard<Session>(opts: AdminGuardOptions<Session>): (request: Request) => Promise<Session>;
52
+ interface BillableBalanceState {
53
+ overageAllowed: boolean;
54
+ remainingBalanceUsd: number;
55
+ }
56
+ interface AssertBillableBalanceOptions {
57
+ env?: Record<string, string | undefined>;
58
+ /** App-specific enforcement override flag (e.g. 'GTM_BILLING_ENFORCEMENT'),
59
+ * fed to `isTangleBillingEnforcementDisabled`. */
60
+ enforcementEnvVar?: string;
61
+ /** Default 'Add balance or upgrade your plan to invoke this agent.'. */
62
+ errorMessage?: string;
63
+ /** Merged into the 402 JSON body (e.g. `{ organizationId }`). */
64
+ errorBody?: Record<string, unknown>;
65
+ }
66
+ /**
67
+ * Gate a billable turn: passes when enforcement is disabled (dev default),
68
+ * the tier allows overage, or remaining balance is positive. Otherwise throws
69
+ * a 402 Response with the stable `billing.balance_required` code so clients
70
+ * can route to the billing screen.
71
+ */
72
+ declare function assertBillableBalance(state: BillableBalanceState, opts?: AssertBillableBalanceOptions): void;
73
+
74
+ /**
75
+ * Cross-site Tangle SSO for agent apps: signed-state CSRF cookies plus the
76
+ * full start/callback orchestration against the platform's /cross-site
77
+ * bridge. The platform wire client and account persistence are structural
78
+ * seams (`TangleSsoAuthClient` / `TangleSsoAccountStore`), so this module
79
+ * never imports agent-runtime, an auth framework, or a database driver.
80
+ * WebCrypto only — runs in workerd without node compatibility flags.
81
+ */
82
+ interface SsoStateConfig {
83
+ /** HMAC-SHA256 secret (e.g. the app's auth secret). */
84
+ secret: string;
85
+ /** State lifetime in ms. Default 600 000. */
86
+ ttlMs?: number;
87
+ /** Injectable clock (ms since epoch). Default Date.now. */
88
+ now?: () => number;
89
+ }
90
+ /** Mint a `<randomHex32>.<timestamp36>.<hmacHex>` state value. The timestamp
91
+ * is inside the signed payload, so expiry survives cookie-attribute tampering. */
92
+ declare function createSignedSsoState(config: SsoStateConfig): Promise<string>;
93
+ /** Verify the MAC (constant-time) and the signed TTL. */
94
+ declare function verifySignedSsoState(state: string, config: SsoStateConfig): Promise<boolean>;
95
+ interface TangleSsoExchangeResult {
96
+ apiKey: string;
97
+ user: {
98
+ id: string;
99
+ email: string;
100
+ name?: string | null;
101
+ };
102
+ plan?: {
103
+ tier: string;
104
+ } | null;
105
+ }
106
+ /** Structural mirror of the platform auth wire client — any object with these
107
+ * two methods satisfies it without this module importing the concrete class. */
108
+ interface TangleSsoAuthClient {
109
+ authorizeUrl(options: {
110
+ state: string;
111
+ redirectUri?: string;
112
+ }): string;
113
+ exchange(code: string): Promise<TangleSsoExchangeResult>;
114
+ }
115
+ /** Thrown by `upsertUserByEmail` when the app-local user row cannot be
116
+ * created; the callback handler maps it to `?error=tangle_user_create_failed`.
117
+ * Any other store error propagates. */
118
+ declare class TangleSsoUserCreateError extends Error {
119
+ constructor(message?: string);
120
+ }
121
+ /**
122
+ * Account persistence seam. Covers both storage styles in use: link-table
123
+ * apps (a per-user platform-link row) and session-column apps (the key on the
124
+ * session row) — `saveTangleLink` receives both `userId` and `sessionToken`,
125
+ * and each app persists with the key it needs. `createSession` runs first so
126
+ * the token is always available to `saveTangleLink`.
127
+ */
128
+ interface TangleSsoAccountStore {
129
+ /** Find-or-create the app-local user. `tangleUserId` is the platform's
130
+ * stable user id — match on it first when the app stores it (emails are
131
+ * mutable on the platform; the id is not), falling back to email for
132
+ * first-time logins. */
133
+ upsertUserByEmail(input: {
134
+ email: string;
135
+ name: string | null;
136
+ tangleUserId: string;
137
+ }): Promise<{
138
+ userId: string;
139
+ }>;
140
+ /** Create an app session row; returns the session-cookie token value. */
141
+ createSession(input: {
142
+ userId: string;
143
+ expiresAt: Date;
144
+ ipAddress: string | null;
145
+ userAgent: string | null;
146
+ }): Promise<{
147
+ token: string;
148
+ }>;
149
+ /** Persist the platform link (API key + platform identity). */
150
+ saveTangleLink(input: {
151
+ userId: string;
152
+ sessionToken: string;
153
+ tangleUserId: string;
154
+ email: string;
155
+ name: string | null;
156
+ apiKey: string;
157
+ planTier: string | null;
158
+ }): Promise<void>;
159
+ }
160
+ /** Successful-login context handed to the `setSessionCookie` seam. */
161
+ interface TangleSsoSessionCookieArgs {
162
+ /** Session token returned by `store.createSession`. */
163
+ token: string;
164
+ /** Session expiry (now + `sessionTtlSeconds`). */
165
+ expiresAt: Date;
166
+ /** Mirrors `sessionTtlSeconds` after defaulting. */
167
+ ttlSeconds: number;
168
+ /** Mirrors `TangleSsoHandlerOptions.secureCookies`. */
169
+ secure: boolean;
170
+ }
171
+ /**
172
+ * Sign a session token to better-call's signed-cookie contract — the value
173
+ * better-auth's `getSignedCookie` verifies: `<token>.<signature>` where the
174
+ * signature is the raw HMAC-SHA256 of the token under `secret`, encoded as
175
+ * STANDARD base64 WITH padding (32 bytes → 44 chars ending `=`; better-call
176
+ * rejects any other length or suffix, so url-safe/unpadded variants read back
177
+ * as a null session). The joined value is percent-encoded once at cookie
178
+ * serialization, matching better-call's `serializeSignedCookie` byte-exactly.
179
+ */
180
+ declare function signSessionCookieValue(token: string, secret: string): Promise<string>;
181
+ /** Structural slice of a `betterAuth()` instance — only what cookie minting
182
+ * reads. No better-auth import: the signing contract is implemented by
183
+ * `signSessionCookieValue`, byte-compatible with better-auth's own
184
+ * `makeSignature`. */
185
+ interface BetterAuthSessionCookieSource {
186
+ $context: PromiseLike<{
187
+ secret: string;
188
+ authCookies: {
189
+ sessionToken: {
190
+ /** Final cookie name — better-auth decides the `__Secure-` prefix
191
+ * (and any `advanced.cookiePrefix`) once at `betterAuth()` init. */
192
+ name: string;
193
+ attributes: {
194
+ secure?: boolean;
195
+ sameSite?: string;
196
+ path?: string;
197
+ httpOnly?: boolean;
198
+ domain?: string;
199
+ };
200
+ };
201
+ };
202
+ }>;
203
+ }
204
+ interface BetterAuthSessionCookieMinterOptions {
205
+ /** Receives the shadowed-cookie-name warning (see below). Default
206
+ * console.warn. */
207
+ warn?: (message: string) => void;
208
+ }
209
+ /**
210
+ * Canonical `setSessionCookie` wiring for better-auth apps: mint the session
211
+ * Set-Cookie exactly as better-auth's own login flows do — name + attributes
212
+ * from `auth.$context.authCookies.sessionToken` (better-auth stays
213
+ * authoritative over prefix/name/attributes) and the value signed to
214
+ * better-call's `getSignedCookie` contract. A raw unprefixed
215
+ * `better-auth.session_token` left by an earlier login is explicitly expired
216
+ * so it cannot shadow the real cookie.
217
+ *
218
+ * Warns when the app's session cookie still has better-auth's DEFAULT name:
219
+ * the Tangle platform (id.tangle.tools) sets a `Domain=.tangle.tools` cookie
220
+ * under that exact name, and equal-path cookies are sent oldest-first — the
221
+ * platform's cookie is always older (the user signs in there before the app's
222
+ * callback runs), so the app reads the platform's token, fails its own
223
+ * signature check, and every fresh login lands logged-out. Per-app
224
+ * `advanced.cookiePrefix` is the fix.
225
+ *
226
+ * Throws on a domain-scoped session cookie for the same reason: a
227
+ * `Domain=`-wide session cookie is exactly the shadowing footgun.
228
+ */
229
+ declare function createBetterAuthSessionCookieMinter(auth: BetterAuthSessionCookieSource, options?: BetterAuthSessionCookieMinterOptions): (args: TangleSsoSessionCookieArgs) => Promise<string[]>;
230
+ interface TangleSsoHandlerOptions {
231
+ auth: TangleSsoAuthClient;
232
+ store: TangleSsoAccountStore;
233
+ /** HMAC secret for the state cookie. */
234
+ stateSecret: string;
235
+ /** Absolute callback URL registered with the platform. */
236
+ callbackUrl: string;
237
+ stateCookieName: string;
238
+ /** Default 'better-auth.session_token'. Ignored when `setSessionCookie` is
239
+ * provided. The default path prepends `__Secure-` iff `secureCookies`. */
240
+ sessionCookieName?: string;
241
+ /** Mint the host auth framework's own session cookie(s); return complete
242
+ * Set-Cookie header values (the handler appends them verbatim and sets no
243
+ * session cookie itself). Supply this when the framework should stay
244
+ * authoritative over name/prefix/signing/attributes — e.g. better-auth:
245
+ * `auth.$context.authCookies.sessionToken` + `makeSignature`. */
246
+ setSessionCookie?: (args: TangleSsoSessionCookieArgs) => readonly string[] | Promise<readonly string[]>;
247
+ /** HMAC-SHA256 secret the host auth framework verifies session cookies with
248
+ * (better-auth: its `secret`). Required when `setSessionCookie` is absent —
249
+ * the default cookie is minted to better-call's signed contract via
250
+ * `signSessionCookieValue`; an unsigned or mis-signed value reads back as a
251
+ * null session, so there is deliberately no fallback to `stateSecret`
252
+ * (which is not guaranteed to be the auth secret). */
253
+ sessionCookieSecret?: string;
254
+ /** Adds `Secure` to every cookie this module sets, and (default session
255
+ * cookie only) the `__Secure-` name prefix. Must match the auth
256
+ * framework's own secure-cookie decision (better-auth: https `baseURL` /
257
+ * `advanced.useSecureCookies`), or it will look up a different cookie name
258
+ * than the one set here. */
259
+ secureCookies: boolean;
260
+ /** Default 604 800 (7 days). */
261
+ sessionTtlSeconds?: number;
262
+ /** Default 600. Applies to both the cookie Max-Age and the signed TTL. */
263
+ stateTtlSeconds?: number;
264
+ /** Default '/app'. */
265
+ defaultRedirectPath?: string;
266
+ /** Default '/login'. */
267
+ loginPath?: string;
268
+ /** Failure log hook (e.g. console.error). Default no-op. */
269
+ log?: (message: string, error?: unknown) => void;
270
+ now?: () => number;
271
+ }
272
+ interface TangleSsoHandlers {
273
+ /** GET start route: mint + sign state, set the state cookie, 302 to the
274
+ * platform authorize URL. `?redirect=` carries the post-login path. */
275
+ start(request: Request): Promise<Response>;
276
+ /** GET callback route: verify state, exchange the code, upsert the user,
277
+ * create the session, save the platform link, set the session cookie
278
+ * (via the `setSessionCookie` seam, else signed to better-call's contract
279
+ * with `sessionCookieSecret`), 302 to the saved redirect. Every failure
280
+ * 302s to `loginPath?error=…` with the state cookie cleared. */
281
+ callback(request: Request): Promise<Response>;
282
+ }
283
+ declare function createTangleSsoHandlers(opts: TangleSsoHandlerOptions): TangleSsoHandlers;
284
+
285
+ export { type AuthGuard as A, type BetterAuthSessionCookieMinterOptions as B, type GuardResolution as G, type SsoStateConfig as S, type TangleSsoHandlers as T, type TangleSsoAuthClient as a, type TangleSsoAccountStore as b, type AdminGuardOptions as c, type AssertBillableBalanceOptions as d, type AuthGuardOptions as e, type BetterAuthSessionCookieSource as f, type BillableBalanceState as g, type TangleSsoExchangeResult as h, type TangleSsoHandlerOptions as i, type TangleSsoSessionCookieArgs as j, TangleSsoUserCreateError as k, assertBillableBalance as l, createAdminGuard as m, createAuthGuard as n, createBetterAuthSessionCookieMinter as o, createSignedSsoState as p, createTangleSsoHandlers as q, guardResolution as r, parseAdminEmails as s, signSessionCookieValue as t, verifySignedSsoState as v };
@@ -23,7 +23,7 @@ import {
23
23
  resolveChatTurn,
24
24
  resolveToolId,
25
25
  resolveToolName
26
- } from "../chunk-77RLNLFP.js";
26
+ } from "../chunk-ATRJULKZ.js";
27
27
  export {
28
28
  TURN_EVENTS_MIGRATION_SQL,
29
29
  TURN_STATUS_SCOPE_MIGRATION_SQL,
@@ -3,6 +3,15 @@ import {
3
3
  isInviteTokenShape,
4
4
  validateInviteToken
5
5
  } from "../chunk-3SVAA3MA.js";
6
+ import {
7
+ INVITATION_EXPIRY_DAYS,
8
+ generateInvitationToken,
9
+ getInvitationExpiresAt,
10
+ inviteUrlForToken,
11
+ normalizeInvitationEmail,
12
+ parseInvitationPermission,
13
+ renderInvitationEmail
14
+ } from "../chunk-WEBBJBDH.js";
6
15
  import {
7
16
  ASSIGNABLE_WORKSPACE_ROLES,
8
17
  ORGANIZATION_ROLES,
@@ -18,15 +27,6 @@ import {
18
27
  workspaceRoleToCollaborationAccess,
19
28
  workspaceRoleToSandboxRole
20
29
  } from "../chunk-63CE7FEZ.js";
21
- import {
22
- INVITATION_EXPIRY_DAYS,
23
- generateInvitationToken,
24
- getInvitationExpiresAt,
25
- inviteUrlForToken,
26
- normalizeInvitationEmail,
27
- parseInvitationPermission,
28
- renderInvitationEmail
29
- } from "../chunk-WEBBJBDH.js";
30
30
  export {
31
31
  ASSIGNABLE_WORKSPACE_ROLES,
32
32
  INVITATION_EXPIRY_DAYS,
@@ -2,9 +2,6 @@ import {
2
2
  SeatLimitError
3
3
  } from "../chunk-SWUVTGMR.js";
4
4
  import "../chunk-3SVAA3MA.js";
5
- import {
6
- hasWorkspaceRole
7
- } from "../chunk-63CE7FEZ.js";
8
5
  import {
9
6
  generateInvitationToken,
10
7
  getInvitationExpiresAt,
@@ -12,6 +9,9 @@ import {
12
9
  normalizeInvitationEmail,
13
10
  parseInvitationPermission
14
11
  } from "../chunk-WEBBJBDH.js";
12
+ import {
13
+ hasWorkspaceRole
14
+ } from "../chunk-63CE7FEZ.js";
15
15
 
16
16
  // src/teams/invitations-api.ts
17
17
  import { and, eq, lte, sql } from "drizzle-orm";