@tangle-network/agent-app 0.43.47 → 0.43.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant/index.d.ts +3 -2
- package/dist/assistant/index.js +4 -3
- package/dist/assistant/index.js.map +1 -1
- package/dist/chat-routes/index.d.ts +394 -4
- package/dist/chat-routes/index.js +510 -12
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +3 -2
- package/dist/chat-store/index.js +19 -1
- package/dist/chat-store/index.js.map +1 -1
- package/dist/chunk-23LUHHHN.js +282 -0
- package/dist/chunk-23LUHHHN.js.map +1 -0
- package/dist/{chunk-UHXQ3KNX.js → chunk-54GSK2KK.js} +4 -4
- package/dist/{chunk-4AUQIAYU.js → chunk-5MG74GVQ.js} +2 -281
- package/dist/chunk-5MG74GVQ.js.map +1 -0
- package/dist/{chunk-5RJNEEO2.js → chunk-HRJJH3RX.js} +2 -2
- package/dist/{chunk-6E2XJSCT.js → chunk-JGYOYY5D.js} +65 -2
- package/dist/chunk-JGYOYY5D.js.map +1 -0
- package/dist/{chunk-E7QYOOON.js → chunk-MCJSS6SM.js} +2 -1
- package/dist/chunk-MCJSS6SM.js.map +1 -0
- package/dist/chunk-NBSBRZ6F.js +337 -0
- package/dist/chunk-NBSBRZ6F.js.map +1 -0
- package/dist/{chunk-7VMUOD3G.js → chunk-O6H2WD3I.js} +2 -2
- package/dist/chunk-O6H2WD3I.js.map +1 -0
- package/dist/{file-index-Bn6sitKb.d.ts → file-index-b26ee-_R.d.ts} +1 -1
- package/dist/harness/index.d.ts +1 -1
- package/dist/harness/index.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +119 -103
- package/dist/{parts-1_3y2JmR.d.ts → parts-IB-Kbb7z.d.ts} +146 -2
- package/dist/preset-cloudflare/index.js +2 -2
- package/dist/profile/index.d.ts +13 -2
- package/dist/profile/index.js +25 -4
- package/dist/profile/index.js.map +1 -1
- package/dist/prompt/index.d.ts +2 -1
- package/dist/prompt/index.js.map +1 -1
- package/dist/run/index.js +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +2 -2
- package/dist/skills/index.d.ts +183 -8
- package/dist/skills/index.js +21 -3
- package/dist/skills-placement/index.d.ts +52 -0
- package/dist/skills-placement/index.js +44 -0
- package/dist/skills-placement/index.js.map +1 -0
- package/dist/stream/index.d.ts +4 -47
- package/dist/stream/index.js +14 -12
- package/dist/stream-normalizer-DWvtmY6F.d.ts +48 -0
- package/dist/web-react/index.d.ts +4 -3
- package/dist/web-react/index.js +25 -3
- package/package.json +12 -2
- package/dist/chunk-4AUQIAYU.js.map +0 -1
- package/dist/chunk-6E2XJSCT.js.map +0 -1
- package/dist/chunk-7VMUOD3G.js.map +0 -1
- package/dist/chunk-E7QYOOON.js.map +0 -1
- package/dist/chunk-KOG473C4.js +0 -132
- package/dist/chunk-KOG473C4.js.map +0 -1
- /package/dist/{chunk-UHXQ3KNX.js.map → chunk-54GSK2KK.js.map} +0 -0
- /package/dist/{chunk-5RJNEEO2.js.map → chunk-HRJJH3RX.js.map} +0 -0
package/dist/profile/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assertSkillDeliveryDisjoint,
|
|
2
3
|
composeShellResources,
|
|
4
|
+
composeSkills,
|
|
3
5
|
corpusSkills,
|
|
4
6
|
loadMarkdownCorpus,
|
|
7
|
+
mergeComposedSkills,
|
|
8
|
+
parseCorpusSkills,
|
|
9
|
+
parseSkillFrontmatter,
|
|
5
10
|
registrySkills,
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
renderInlineSkills,
|
|
12
|
+
renderSkillIndex,
|
|
13
|
+
skillEntryFromMarkdown,
|
|
14
|
+
skillMountPath,
|
|
15
|
+
skillRefs
|
|
16
|
+
} from "../chunk-NBSBRZ6F.js";
|
|
8
17
|
|
|
9
18
|
// src/profile/index.ts
|
|
10
19
|
import { mergeAgentProfiles } from "@tangle-network/sandbox";
|
|
@@ -53,7 +62,7 @@ function composeAgentProfile(base, channels = {}, overlay = {}, budget = {}) {
|
|
|
53
62
|
skills: channels.skills,
|
|
54
63
|
knowledge: channels.knowledge,
|
|
55
64
|
evolvable: channels.evolvable,
|
|
56
|
-
registry: channels.registry ? registrySkills(channels.registry) : void 0,
|
|
65
|
+
registry: channels.registry ? registrySkills(channels.registry, channels.registryTier ?? "free") : void 0,
|
|
57
66
|
predicate: channels.filesPredicate
|
|
58
67
|
};
|
|
59
68
|
const channelFiles = composeShellResources(shellInput);
|
|
@@ -67,7 +76,10 @@ function composeAgentProfile(base, channels = {}, overlay = {}, budget = {}) {
|
|
|
67
76
|
...overlay.name ? { name: overlay.name } : {},
|
|
68
77
|
...Object.keys(promptOverlay).length > 0 ? { prompt: promptOverlay } : {},
|
|
69
78
|
...overlay.mcp ? { mcp: overlay.mcp } : {},
|
|
70
|
-
resources: {
|
|
79
|
+
resources: {
|
|
80
|
+
files,
|
|
81
|
+
...channels.skillRefs && channels.skillRefs.length > 0 ? { skills: channels.skillRefs } : {}
|
|
82
|
+
}
|
|
71
83
|
};
|
|
72
84
|
const merged = mergeAgentProfiles(base, overlayProfile);
|
|
73
85
|
if (!merged)
|
|
@@ -95,16 +107,25 @@ function makeEvolvableSection(input) {
|
|
|
95
107
|
}
|
|
96
108
|
export {
|
|
97
109
|
DEFAULT_MAX_SYSTEM_PROMPT_BYTES,
|
|
110
|
+
assertSkillDeliveryDisjoint,
|
|
98
111
|
assertSystemPromptWithinBudget,
|
|
99
112
|
composeAgentProfile,
|
|
100
113
|
composeShellResources,
|
|
114
|
+
composeSkills,
|
|
101
115
|
corpusSkills,
|
|
102
116
|
largestPromptSections,
|
|
103
117
|
loadMarkdownCorpus,
|
|
104
118
|
makeEvolvableSection,
|
|
119
|
+
mergeComposedSkills,
|
|
120
|
+
parseCorpusSkills,
|
|
121
|
+
parseSkillFrontmatter,
|
|
105
122
|
profile,
|
|
106
123
|
registrySkills,
|
|
124
|
+
renderInlineSkills,
|
|
125
|
+
renderSkillIndex,
|
|
126
|
+
skillEntryFromMarkdown,
|
|
107
127
|
skillMountPath,
|
|
128
|
+
skillRefs,
|
|
108
129
|
stripComments,
|
|
109
130
|
userSkillMounts
|
|
110
131
|
};
|
|
@@ -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/** 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"]}
|
|
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 AgentProfileResourceRef,\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 /** Typed `resources.skills` channel — refs the platform materializer places\n * at the harness-native skill dir (see {@link skillRefs} and\n * `@tangle-network/agent-app/skills-placement`'s `composeSkillsForHarness`).\n * The successor to path-baked mounts: `registry`/`userSkills` above mount\n * files at the hardcoded claude-code path via {@link skillMountPath};\n * `skillRefs` instead rides the provider-neutral `resources.skills` field\n * the platform resolves per harness. */\n skillRefs?: AgentProfileResourceRef[]\n /** Tier passed to {@link registrySkills} for the `registry` channel.\n * Previously hardcoded `'free'`; default unchanged. */\n registryTier?: string\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\n ? registrySkills(channels.registry, channels.registryTier ?? 'free')\n : 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: {\n files,\n ...(channels.skillRefs && channels.skillRefs.length > 0 ? { skills: channels.skillRefs } : {}),\n },\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 assertSkillDeliveryDisjoint,\n composeShellResources,\n composeSkills,\n corpusSkills,\n loadMarkdownCorpus,\n mergeComposedSkills,\n parseCorpusSkills,\n parseSkillFrontmatter,\n registrySkills,\n renderInlineSkills,\n renderSkillIndex,\n skillEntryFromMarkdown,\n skillMountPath,\n skillRefs,\n} from '../skills/index'\nexport type {\n ComposedSkills,\n ComposeShellResourcesInput,\n CorpusEntry,\n CorpusLoadResult,\n GlobModules,\n LoadCorpusOptions,\n ParsedSkill,\n SkillDeliveryMode,\n SkillEntry,\n SkillFrontmatter,\n} from '../skills/index'\n"],"mappings":"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","names":["profile"]}
|
package/dist/prompt/index.d.ts
CHANGED
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
* `@tangle-network/agent-app/skills` subpath loads the corpus and returns file
|
|
33
33
|
* mounts (`AgentProfileFileMount[]`); the PRODUCT renders any `## Skills` text
|
|
34
34
|
* section and folds it into `base` before calling this assembler. This module
|
|
35
|
-
* never renders a skills heading
|
|
35
|
+
* never renders a skills heading — `@tangle-network/agent-app/skills`'s
|
|
36
|
+
* `renderInlineSkills`/`renderSkillIndex` are the source of that section text.
|
|
36
37
|
*/
|
|
37
38
|
/** Inputs to {@link assembleSystemPrompt}. */
|
|
38
39
|
interface AssembleSystemPromptInput {
|
package/dist/prompt/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/prompt/index.ts"],"sourcesContent":["/**\n * System-prompt assembler for agent products.\n *\n * Every agent product composes its system prompt the same way: a base profile\n * prompt (the operator persona + tools block + any skills section the consumer\n * already rendered), an always-on operating directive, then an ordered list of\n * optional per-turn context sections (known-context, board, approval history,\n * learned-style, pending questions, the active artifact). The ORDERING and the\n * whitespace contract are identical across products; only the section BODIES are\n * domain. This module owns the ordering and the joins; the product injects every\n * body as an already-rendered string through the `sections` array.\n *\n * Whitespace contract (preserves the join/concat/trim algebra of the per-product\n * pre-lift composers — a product adopting this asserts byte-for-byte parity in\n * its own suite):\n * - base is emitted verbatim, with no leading/trailing normalization.\n * - directive is appended after base with a single `\\n\\n` join.\n * - each section in `sections` is appended with NO separator: a section is\n * either '' (absent — contributes nothing) or already carries its own\n * leading `\\n\\n` (and its `## ` heading). The assembler concatenates them\n * unconditionally, which is why the conditional-prefix-or-empty contract\n * lives in the product's section renderers, not here.\n * - `trim` (default false) applies a final `.trim()` to the whole result.\n * Branches that historically trimmed pass `trim: true`; branches that did\n * not (e.g. the new-workspace paths) leave it false, preserving that\n * asymmetry rather than silently changing trailing whitespace.\n *\n * Pure string composition: no SDK runtime symbol, no node builtins, no glob.\n * The base profile prompt and the skills-section insertion point both live\n * INSIDE the product-built `base` string — the assembler never reaches into an\n * AgentProfile and never loads a corpus. The sibling\n * `@tangle-network/agent-app/skills` subpath loads the corpus and returns file\n * mounts (`AgentProfileFileMount[]`); the PRODUCT renders any `## Skills` text\n * section and folds it into `base` before calling this assembler. This module\n * never renders a skills heading.\n */\n\n/** Inputs to {@link assembleSystemPrompt}. */\nexport interface AssembleSystemPromptInput {\n /** The product's already-composed base block: persona/system prompt + tools\n * block + (optionally) the rendered skills section. The product is\n * responsible for resolving its profile system prompt and failing loud if it\n * is absent — an empty base reaches this assembler only as a programmer\n * error, which it rejects (see {@link AssembleResult}). */\n base: string\n /** The always-on operating directive, placed immediately after base with a\n * `\\n\\n` join. Pass '' to omit it (the join is then suppressed). */\n directive?: string\n /** Ordered per-turn context sections, each already rendered by the product to\n * either '' (absent) or a `\\n\\n## …`-prefixed string. Concatenated in order\n * with no added separator. */\n sections?: string[]\n /** Apply a final `.trim()` to the composed result. Default false — set true\n * only on branches whose pre-lift output was trimmed. */\n trim?: boolean\n}\n\n/** Typed outcome of {@link assembleSystemPrompt}. `succeeded: false` is returned\n * for a programmer error (an empty base) rather than emitting a roleless\n * prompt — callers MUST inspect `succeeded` before using `prompt`. */\nexport type AssembleResult =\n | { succeeded: true; prompt: string }\n | { succeeded: false; error: string }\n\n/** True when a string is empty or whitespace-only. */\nfunction isBlank(value: string): boolean {\n return value.trim().length === 0\n}\n\n/**\n * Assemble a system prompt from a base block, an operating directive, and an\n * ordered list of pre-rendered context sections.\n *\n * Returns a typed outcome: an empty/blank `base` is a defect (an agent with no\n * persona/system prompt), so it fails loud instead of silently producing a\n * prompt with no role. The product resolves and validates its profile prompt\n * upstream; this is the last-line guard at the seam.\n */\nexport function assembleSystemPrompt(input: AssembleSystemPromptInput): AssembleResult {\n const { base, directive = '', sections = [], trim = false } = input\n\n if (isBlank(base)) {\n return {\n succeeded: false,\n error: 'assembleSystemPrompt: base is empty — a system prompt with no persona/base block is a defect, not a default',\n }\n }\n\n let prompt = directive.length > 0 ? `${base}\\n\\n${directive}` : base\n for (const section of sections) prompt += section\n\n return { succeeded: true, prompt: trim ? prompt.trim() : prompt }\n}"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/prompt/index.ts"],"sourcesContent":["/**\n * System-prompt assembler for agent products.\n *\n * Every agent product composes its system prompt the same way: a base profile\n * prompt (the operator persona + tools block + any skills section the consumer\n * already rendered), an always-on operating directive, then an ordered list of\n * optional per-turn context sections (known-context, board, approval history,\n * learned-style, pending questions, the active artifact). The ORDERING and the\n * whitespace contract are identical across products; only the section BODIES are\n * domain. This module owns the ordering and the joins; the product injects every\n * body as an already-rendered string through the `sections` array.\n *\n * Whitespace contract (preserves the join/concat/trim algebra of the per-product\n * pre-lift composers — a product adopting this asserts byte-for-byte parity in\n * its own suite):\n * - base is emitted verbatim, with no leading/trailing normalization.\n * - directive is appended after base with a single `\\n\\n` join.\n * - each section in `sections` is appended with NO separator: a section is\n * either '' (absent — contributes nothing) or already carries its own\n * leading `\\n\\n` (and its `## ` heading). The assembler concatenates them\n * unconditionally, which is why the conditional-prefix-or-empty contract\n * lives in the product's section renderers, not here.\n * - `trim` (default false) applies a final `.trim()` to the whole result.\n * Branches that historically trimmed pass `trim: true`; branches that did\n * not (e.g. the new-workspace paths) leave it false, preserving that\n * asymmetry rather than silently changing trailing whitespace.\n *\n * Pure string composition: no SDK runtime symbol, no node builtins, no glob.\n * The base profile prompt and the skills-section insertion point both live\n * INSIDE the product-built `base` string — the assembler never reaches into an\n * AgentProfile and never loads a corpus. The sibling\n * `@tangle-network/agent-app/skills` subpath loads the corpus and returns file\n * mounts (`AgentProfileFileMount[]`); the PRODUCT renders any `## Skills` text\n * section and folds it into `base` before calling this assembler. This module\n * never renders a skills heading — `@tangle-network/agent-app/skills`'s\n * `renderInlineSkills`/`renderSkillIndex` are the source of that section text.\n */\n\n/** Inputs to {@link assembleSystemPrompt}. */\nexport interface AssembleSystemPromptInput {\n /** The product's already-composed base block: persona/system prompt + tools\n * block + (optionally) the rendered skills section. The product is\n * responsible for resolving its profile system prompt and failing loud if it\n * is absent — an empty base reaches this assembler only as a programmer\n * error, which it rejects (see {@link AssembleResult}). */\n base: string\n /** The always-on operating directive, placed immediately after base with a\n * `\\n\\n` join. Pass '' to omit it (the join is then suppressed). */\n directive?: string\n /** Ordered per-turn context sections, each already rendered by the product to\n * either '' (absent) or a `\\n\\n## …`-prefixed string. Concatenated in order\n * with no added separator. */\n sections?: string[]\n /** Apply a final `.trim()` to the composed result. Default false — set true\n * only on branches whose pre-lift output was trimmed. */\n trim?: boolean\n}\n\n/** Typed outcome of {@link assembleSystemPrompt}. `succeeded: false` is returned\n * for a programmer error (an empty base) rather than emitting a roleless\n * prompt — callers MUST inspect `succeeded` before using `prompt`. */\nexport type AssembleResult =\n | { succeeded: true; prompt: string }\n | { succeeded: false; error: string }\n\n/** True when a string is empty or whitespace-only. */\nfunction isBlank(value: string): boolean {\n return value.trim().length === 0\n}\n\n/**\n * Assemble a system prompt from a base block, an operating directive, and an\n * ordered list of pre-rendered context sections.\n *\n * Returns a typed outcome: an empty/blank `base` is a defect (an agent with no\n * persona/system prompt), so it fails loud instead of silently producing a\n * prompt with no role. The product resolves and validates its profile prompt\n * upstream; this is the last-line guard at the seam.\n */\nexport function assembleSystemPrompt(input: AssembleSystemPromptInput): AssembleResult {\n const { base, directive = '', sections = [], trim = false } = input\n\n if (isBlank(base)) {\n return {\n succeeded: false,\n error: 'assembleSystemPrompt: base is empty — a system prompt with no persona/base block is a defect, not a default',\n }\n }\n\n let prompt = directive.length > 0 ? `${base}\\n\\n${directive}` : base\n for (const section of sections) prompt += section\n\n return { succeeded: true, prompt: trim ? prompt.trim() : prompt }\n}"],"mappings":";AAkEA,SAAS,QAAQ,OAAwB;AACvC,SAAO,MAAM,KAAK,EAAE,WAAW;AACjC;AAWO,SAAS,qBAAqB,OAAkD;AACrF,QAAM,EAAE,MAAM,YAAY,IAAI,WAAW,CAAC,GAAG,OAAO,MAAM,IAAI;AAE9D,MAAI,QAAQ,IAAI,GAAG;AACjB,WAAO;AAAA,MACL,WAAW;AAAA,MACX,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI,SAAS,UAAU,SAAS,IAAI,GAAG,IAAI;AAAA;AAAA,EAAO,SAAS,KAAK;AAChE,aAAW,WAAW,SAAU,WAAU;AAE1C,SAAO,EAAE,WAAW,MAAM,QAAQ,OAAO,OAAO,KAAK,IAAI,OAAO;AAClE;","names":[]}
|
package/dist/run/index.js
CHANGED
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentProfileMcpServer, ProvisionEvent,
|
|
1
|
+
import { SandboxInstance, AgentProfileMcpServer, ProvisionEvent, AgentProfileFileMount, AgentProfile, StorageConfig, ScopedTokenScope, TurnDriveResult, Sandbox } from '@tangle-network/sandbox';
|
|
2
2
|
export { StorageConfig } from '@tangle-network/sandbox';
|
|
3
3
|
import { a as ToolHeaderNames } from '../auth-DuptSkWh.js';
|
|
4
4
|
import { f as AppToolName, c as AppToolContext } from '../types-BEOvc_ue.js';
|
package/dist/sandbox/index.js
CHANGED
|
@@ -58,8 +58,8 @@ import {
|
|
|
58
58
|
verifySandboxTerminalToken,
|
|
59
59
|
verifyTerminalProxyToken,
|
|
60
60
|
writeProfileFilesToBox
|
|
61
|
-
} from "../chunk-
|
|
62
|
-
import "../chunk-
|
|
61
|
+
} from "../chunk-HRJJH3RX.js";
|
|
62
|
+
import "../chunk-MCJSS6SM.js";
|
|
63
63
|
import "../chunk-PJC4NXPA.js";
|
|
64
64
|
import "../chunk-7EVZUIHW.js";
|
|
65
65
|
import "../chunk-S5SRJJQG.js";
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentProfileFileMount } from '@tangle-network/sandbox';
|
|
1
|
+
import { AgentProfileFileMount, AgentProfileResourceRef } from '@tangle-network/sandbox';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Unified skill + corpus mounter for agent products.
|
|
@@ -17,13 +17,30 @@ import { AgentProfileFileMount } from '@tangle-network/sandbox';
|
|
|
17
17
|
* fs fallback), a registry adapter that tier-gates, and a single
|
|
18
18
|
* `composeShellResources` that projects either onto the SDK file-mount shape.
|
|
19
19
|
*
|
|
20
|
+
* A THIRD surface lives alongside those two: adoptable `SkillEntry`s sourced
|
|
21
|
+
* from `SKILL.md` frontmatter rather than hand-authored fields.
|
|
22
|
+
* `parseSkillFrontmatter` is the ONE frontmatter parser (hand-rolled, no YAML
|
|
23
|
+
* dependency — fail loud on a malformed block rather than silently mis-reading
|
|
24
|
+
* a field); `skillEntryFromMarkdown`/`parseCorpusSkills` turn raw markdown into
|
|
25
|
+
* `SkillEntry`s. From there a skill reaches the agent by one of two DELIVERY
|
|
26
|
+
* MODES: `inline` renders the skill body straight into the system prompt (every
|
|
27
|
+
* harness can read it, at prompt-byte cost), or `mounted` projects it onto the
|
|
28
|
+
* typed `resources.skills` channel (`AgentProfileResourceRef[]`) plus an index
|
|
29
|
+
* section that just names the file, and lets the platform materializer place it
|
|
30
|
+
* at the harness-native skill dir. `composeSkills` builds either shape;
|
|
31
|
+
* `mergeComposedSkills` combines batches and throws (via
|
|
32
|
+
* `assertSkillDeliveryDisjoint`) on a skill accidentally delivered both
|
|
33
|
+
* ways. Picking WHICH harnesses can take `mounted` delivery is platform-bound
|
|
34
|
+
* (see `@tangle-network/agent-app/skills-placement`) and deliberately kept out
|
|
35
|
+
* of this substrate-free module.
|
|
36
|
+
*
|
|
20
37
|
* Substrate-free over storage, exact over the SDK boundary: the only inbound
|
|
21
38
|
* seam is the glob-result map the consumer passes in (its call site keeps the
|
|
22
39
|
* literal `import.meta.glob` Vite must static-analyze); the only outbound seam
|
|
23
|
-
* is `@tangle-network/sandbox`'s `AgentProfileFileMount[]`,
|
|
24
|
-
*
|
|
25
|
-
* via `process.getBuiltinModule` so a static `node:*`
|
|
26
|
-
* Vite SSR bundle.
|
|
40
|
+
* is `@tangle-network/sandbox`'s `AgentProfileFileMount[]`/`AgentProfileResourceRef[]`,
|
|
41
|
+
* the exact shapes `resources.files`/`resources.skills` consume. Node builtins
|
|
42
|
+
* are resolved lazily via `process.getBuiltinModule` so a static `node:*`
|
|
43
|
+
* import never reaches the Vite SSR bundle.
|
|
27
44
|
*/
|
|
28
45
|
|
|
29
46
|
/** A Vite eager `?raw` glob result: glob key -> raw file body. The consumer
|
|
@@ -63,8 +80,18 @@ interface SkillEntry {
|
|
|
63
80
|
tier: string;
|
|
64
81
|
skillMd: string;
|
|
65
82
|
}
|
|
66
|
-
/** Harness skill-discovery path the Claude Code
|
|
67
|
-
*
|
|
83
|
+
/** Harness skill-discovery path the Claude Code backend reads natively. The
|
|
84
|
+
* registry mounts here; the corpus mounts at its relative path.
|
|
85
|
+
*
|
|
86
|
+
* @deprecated Hardcodes the claude-code path (`~/.claude/skills/<id>/SKILL.md`).
|
|
87
|
+
* It is NOT a path other harnesses read — OpenCode discovers `.opencode/skills`,
|
|
88
|
+
* not `~/.claude/skills` (the doc comment here previously claimed otherwise);
|
|
89
|
+
* codex, kimi-code, and the rest each have their own dir or none at all. Use
|
|
90
|
+
* {@link skillRefs} to put a skill on the typed `resources.skills` channel and
|
|
91
|
+
* let the platform materializer place it correctly, or resolve the
|
|
92
|
+
* harness-native dir directly via `@tangle-network/agent-app/skills-placement`.
|
|
93
|
+
* Kept only for the pre-existing `registrySkills`/`userSkillMounts` callers;
|
|
94
|
+
* do not add new call sites. */
|
|
68
95
|
declare function skillMountPath(id: string): string;
|
|
69
96
|
/** Options for {@link loadMarkdownCorpus}. */
|
|
70
97
|
interface LoadCorpusOptions {
|
|
@@ -132,5 +159,153 @@ interface ComposeShellResourcesInput {
|
|
|
132
159
|
* `profile.resources.files` with no cast.
|
|
133
160
|
*/
|
|
134
161
|
declare function composeShellResources(input: ComposeShellResourcesInput): AgentProfileFileMount[];
|
|
162
|
+
/** Fields a `SKILL.md` frontmatter block may declare. All optional — absent
|
|
163
|
+
* frontmatter (or an absent field within it) is legal; callers fill defaults
|
|
164
|
+
* (see {@link skillEntryFromMarkdown}). */
|
|
165
|
+
interface SkillFrontmatter {
|
|
166
|
+
id?: string;
|
|
167
|
+
name?: string;
|
|
168
|
+
description?: string;
|
|
169
|
+
author?: {
|
|
170
|
+
name: string;
|
|
171
|
+
url?: string;
|
|
172
|
+
};
|
|
173
|
+
source?: string;
|
|
174
|
+
category?: string;
|
|
175
|
+
tags?: string[];
|
|
176
|
+
tier?: string;
|
|
177
|
+
}
|
|
178
|
+
/** The result of {@link parseSkillFrontmatter}: the parsed fields, the body
|
|
179
|
+
* with the frontmatter block stripped, and the original untouched text. */
|
|
180
|
+
interface ParsedSkill {
|
|
181
|
+
frontmatter: SkillFrontmatter;
|
|
182
|
+
body: string;
|
|
183
|
+
raw: string;
|
|
184
|
+
}
|
|
185
|
+
/** THE one `SKILL.md` frontmatter parser — hand-rolled, no YAML dependency.
|
|
186
|
+
*
|
|
187
|
+
* Absent frontmatter (text does not open with a `---` delimiter line) is
|
|
188
|
+
* legal: returns `{frontmatter: {}, body: raw, raw}`. An OPENED block with no
|
|
189
|
+
* closing `---` is truncated input and throws. Inside the block: scalar
|
|
190
|
+
* `key: value` lines (value optionally double-quoted, decoded via
|
|
191
|
+
* `JSON.parse`); a nested `author:` block whose indented `name:`/`url:` lines
|
|
192
|
+
* are the only children it accepts; `tags:` as an inline `[a, b]` list or as
|
|
193
|
+
* an indented `- item` block. Unknown scalar keys are ignored (forward-compat)
|
|
194
|
+
* — but a line that matches NONE of these shapes (no colon, an orphaned
|
|
195
|
+
* indented line, a bad dash) throws naming the offending line. Silently
|
|
196
|
+
* mis-parsed metadata is the bug class this parser exists to kill; an
|
|
197
|
+
* unrecognized shape is never guessed at.
|
|
198
|
+
*/
|
|
199
|
+
declare function parseSkillFrontmatter(raw: string): ParsedSkill;
|
|
200
|
+
/** Build a {@link SkillEntry} from a raw `SKILL.md` body. `id` comes from
|
|
201
|
+
* frontmatter, falling back to `fallbackId` (typically the corpus entry's
|
|
202
|
+
* slug/filename); neither present throws. `name` defaults to `id`,
|
|
203
|
+
* `description` to `''`, `tier` to `'free'`. `skillMd` is always the
|
|
204
|
+
* untouched `raw` input — the full file, frontmatter included, is what a
|
|
205
|
+
* `mounted` delivery writes to disk and what `renderInlineSkills` strips per
|
|
206
|
+
* render. */
|
|
207
|
+
declare function skillEntryFromMarkdown(raw: string, fallbackId?: string): SkillEntry;
|
|
208
|
+
/** Map a loaded corpus (see {@link loadMarkdownCorpus}) onto `SkillEntry`s,
|
|
209
|
+
* using each entry's `id` as the fallback when its `SKILL.md` carries no
|
|
210
|
+
* frontmatter `id` of its own. */
|
|
211
|
+
declare function parseCorpusSkills(corpus: CorpusEntry[]): SkillEntry[];
|
|
212
|
+
/** Project skills onto the typed `resources.skills` channel
|
|
213
|
+
* (`AgentProfileResourceRef[]`), tier-filtered (same `s.tier === tier`
|
|
214
|
+
* semantics as {@link registrySkills}) when `opts.tier` is given, sorted by
|
|
215
|
+
* id for determinism. `ref.name` MUST be the skill id: the platform
|
|
216
|
+
* materializer writes each ref to `${skillDir}/${name}/SKILL.md`. */
|
|
217
|
+
declare function skillRefs(skills: SkillEntry[], opts?: {
|
|
218
|
+
tier?: string;
|
|
219
|
+
}): AgentProfileResourceRef[];
|
|
220
|
+
/** Inputs to {@link renderInlineSkills}. */
|
|
221
|
+
interface RenderInlineSkillsInput {
|
|
222
|
+
skills: SkillEntry[];
|
|
223
|
+
/** Section heading. Default `'## Skills'`. */
|
|
224
|
+
heading?: string;
|
|
225
|
+
/** Tier filter — same semantics as {@link skillRefs}. */
|
|
226
|
+
tier?: string;
|
|
227
|
+
/** Per-skill body renderer. Default strips frontmatter and emits
|
|
228
|
+
* `### <name>\n\n<body>`. */
|
|
229
|
+
body?: (skill: SkillEntry) => string;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Render every (tier-filtered) skill's full body inline into the prompt — the
|
|
233
|
+
* `inline` delivery mode. Returns `''` when no skill survives the filter, else
|
|
234
|
+
* a section starting `\n\n<heading>\n\n` with each skill's body joined by
|
|
235
|
+
* `\n\n` — the same "already carries its own leading `\n\n`" shape
|
|
236
|
+
* `assembleSystemPrompt` expects from every section it concatenates.
|
|
237
|
+
*/
|
|
238
|
+
declare function renderInlineSkills(input: RenderInlineSkillsInput): string;
|
|
239
|
+
/** Inputs to {@link renderSkillIndex}. */
|
|
240
|
+
interface RenderSkillIndexInput {
|
|
241
|
+
skills: SkillEntry[];
|
|
242
|
+
/** cwd-relative directory the skills are mounted under (e.g.
|
|
243
|
+
* `.opencode/skills`) — named in each index line so the agent knows where
|
|
244
|
+
* to read the full `SKILL.md`. */
|
|
245
|
+
skillDir: string;
|
|
246
|
+
/** Section heading. Default `'## Skills'`. */
|
|
247
|
+
heading?: string;
|
|
248
|
+
/** Tier filter — same semantics as {@link skillRefs}. */
|
|
249
|
+
tier?: string;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Render a one-line-per-skill INDEX (name, description, and the path to read
|
|
253
|
+
* the full body) — the `mounted` delivery mode's prompt section, paired with
|
|
254
|
+
* {@link skillRefs} putting the actual files on `resources.skills`. Same
|
|
255
|
+
* empty/section shape as {@link renderInlineSkills}.
|
|
256
|
+
*/
|
|
257
|
+
declare function renderSkillIndex(input: RenderSkillIndexInput): string;
|
|
258
|
+
/** How a skill reaches the agent: `inline` renders its full body into the
|
|
259
|
+
* system prompt; `mounted` puts it on the typed `resources.skills` channel
|
|
260
|
+
* and renders only an index line. */
|
|
261
|
+
type SkillDeliveryMode = 'inline' | 'mounted';
|
|
262
|
+
/** The output of {@link composeSkills}: the refs to attach to
|
|
263
|
+
* `resources.skills` (empty for `inline`) and the prompt section to fold into
|
|
264
|
+
* the system prompt (already carries its own leading `\n\n`, or `''`).
|
|
265
|
+
* `inlineIds`/`mountedIds` record which (tier-filtered) skills were delivered
|
|
266
|
+
* in each mode so {@link mergeComposedSkills} can enforce the modes stay
|
|
267
|
+
* disjoint when batches are combined. */
|
|
268
|
+
interface ComposedSkills {
|
|
269
|
+
refs: AgentProfileResourceRef[];
|
|
270
|
+
promptSection: string;
|
|
271
|
+
inlineIds: string[];
|
|
272
|
+
mountedIds: string[];
|
|
273
|
+
}
|
|
274
|
+
/** Inputs to {@link composeSkills}. */
|
|
275
|
+
interface ComposeSkillsInput {
|
|
276
|
+
skills: SkillEntry[];
|
|
277
|
+
mode: SkillDeliveryMode;
|
|
278
|
+
tier?: string;
|
|
279
|
+
heading?: string;
|
|
280
|
+
/** REQUIRED (non-null) for `mode: 'mounted'` — the cwd-relative skill dir
|
|
281
|
+
* the harness reads. Resolve it with `@tangle-network/agent-app/skills-placement`
|
|
282
|
+
* rather than hardcoding it; omitted or `null` throws. */
|
|
283
|
+
skillDir?: string | null;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Build the {@link ComposedSkills} for one delivery mode. `inline` never
|
|
287
|
+
* touches `resources.skills` — `refs` is always `[]`. `mounted` requires a
|
|
288
|
+
* non-null `skillDir` (the harness must have a native skill-discovery
|
|
289
|
+
* directory); a null/absent one throws rather than silently falling back, so
|
|
290
|
+
* the caller resolves the fallback deliberately (see
|
|
291
|
+
* `@tangle-network/agent-app/skills-placement`'s `composeSkillsForHarness`,
|
|
292
|
+
* which does exactly that).
|
|
293
|
+
*/
|
|
294
|
+
declare function composeSkills(input: ComposeSkillsInput): ComposedSkills;
|
|
295
|
+
/** Throw when the same skill id is delivered both `inline` and `mounted` —
|
|
296
|
+
* the agent would see it twice (once in the prompt body, once as a mounted
|
|
297
|
+
* file it's told to go read), doubling prompt bytes and inviting drift
|
|
298
|
+
* between the two copies. Lists every offending id in the message. */
|
|
299
|
+
declare function assertSkillDeliveryDisjoint(inlineIds: Iterable<string>, mountedIds: Iterable<string>): void;
|
|
300
|
+
/**
|
|
301
|
+
* Combine multiple {@link ComposedSkills} batches (e.g. a built-in corpus and
|
|
302
|
+
* an installed catalog) into one, concatenating refs and prompt sections in
|
|
303
|
+
* batch order. This is the seam where inline and mounted deliveries can first
|
|
304
|
+
* collide, so it applies {@link assertSkillDeliveryDisjoint} — a skill id
|
|
305
|
+
* delivered inline by one batch and mounted by another throws instead of
|
|
306
|
+
* reaching the agent twice. Merge through this rather than spreading batch
|
|
307
|
+
* fields by hand.
|
|
308
|
+
*/
|
|
309
|
+
declare function mergeComposedSkills(batches: ComposedSkills[]): ComposedSkills;
|
|
135
310
|
|
|
136
|
-
export { type ComposeShellResourcesInput, type CorpusEntry, type CorpusLoadResult, type GlobModules, type LoadCorpusOptions, type SkillEntry, composeShellResources, corpusSkills, loadMarkdownCorpus, registrySkills, skillMountPath };
|
|
311
|
+
export { type ComposeShellResourcesInput, type ComposeSkillsInput, type ComposedSkills, type CorpusEntry, type CorpusLoadResult, type GlobModules, type LoadCorpusOptions, type ParsedSkill, type RenderInlineSkillsInput, type RenderSkillIndexInput, type SkillDeliveryMode, type SkillEntry, type SkillFrontmatter, assertSkillDeliveryDisjoint, composeShellResources, composeSkills, corpusSkills, loadMarkdownCorpus, mergeComposedSkills, parseCorpusSkills, parseSkillFrontmatter, registrySkills, renderInlineSkills, renderSkillIndex, skillEntryFromMarkdown, skillMountPath, skillRefs };
|
package/dist/skills/index.js
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assertSkillDeliveryDisjoint,
|
|
2
3
|
composeShellResources,
|
|
4
|
+
composeSkills,
|
|
3
5
|
corpusSkills,
|
|
4
6
|
loadMarkdownCorpus,
|
|
7
|
+
mergeComposedSkills,
|
|
8
|
+
parseCorpusSkills,
|
|
9
|
+
parseSkillFrontmatter,
|
|
5
10
|
registrySkills,
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
renderInlineSkills,
|
|
12
|
+
renderSkillIndex,
|
|
13
|
+
skillEntryFromMarkdown,
|
|
14
|
+
skillMountPath,
|
|
15
|
+
skillRefs
|
|
16
|
+
} from "../chunk-NBSBRZ6F.js";
|
|
8
17
|
export {
|
|
18
|
+
assertSkillDeliveryDisjoint,
|
|
9
19
|
composeShellResources,
|
|
20
|
+
composeSkills,
|
|
10
21
|
corpusSkills,
|
|
11
22
|
loadMarkdownCorpus,
|
|
23
|
+
mergeComposedSkills,
|
|
24
|
+
parseCorpusSkills,
|
|
25
|
+
parseSkillFrontmatter,
|
|
12
26
|
registrySkills,
|
|
13
|
-
|
|
27
|
+
renderInlineSkills,
|
|
28
|
+
renderSkillIndex,
|
|
29
|
+
skillEntryFromMarkdown,
|
|
30
|
+
skillMountPath,
|
|
31
|
+
skillRefs
|
|
14
32
|
};
|
|
15
33
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Harness } from '../harness/index.js';
|
|
2
|
+
import { SkillEntry, ComposedSkills } from '../skills/index.js';
|
|
3
|
+
import '@tangle-network/agent-interface';
|
|
4
|
+
import '@tangle-network/sandbox';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Harness-native skill directory resolution — the one place agent-app binds
|
|
8
|
+
* to the platform's authoritative per-harness skill-dir map.
|
|
9
|
+
*
|
|
10
|
+
* `../skills` renders skill CONTENT (parse, tier-filter, `inline`/`mounted`
|
|
11
|
+
* delivery) but deliberately stops short of naming WHICH cwd path a `mounted`
|
|
12
|
+
* skill lands at on a given harness — that mapping is owned by the platform
|
|
13
|
+
* materializer (`@tangle-network/agent-profile-materialize`'s
|
|
14
|
+
* `skillDirForHarness`), not by app-shell. This subpath exists so no product
|
|
15
|
+
* — and no other agent-app module — ever writes a skill path literal
|
|
16
|
+
* (`~/.claude/skills/...`, `.opencode/skills`, ...) of its own; it bridges
|
|
17
|
+
* agent-app's `Harness` taxonomy onto the platform's `HarnessId` and asks the
|
|
18
|
+
* platform for the answer.
|
|
19
|
+
*
|
|
20
|
+
* Requires the OPTIONAL peer `@tangle-network/agent-profile-materialize`.
|
|
21
|
+
* Products that don't install it simply don't import this subpath — every
|
|
22
|
+
* other agent-app skills surface (`../skills`, `ProfileChannels.skillRefs`)
|
|
23
|
+
* works without it, falling back to `inline` delivery.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Resolve the cwd-relative skill dir `resources.skills` refs materialize
|
|
27
|
+
* into on `harness` — via the platform's `skillDirForHarness`. `null` when
|
|
28
|
+
* `harness` isn't bridged (see {@link HARNESS_BRIDGE}) or when the platform
|
|
29
|
+
* itself has no cwd skill primitive for it (e.g. `hermes`, user-dir-only). */
|
|
30
|
+
declare function resolveSkillDir(harness: Harness): string | null;
|
|
31
|
+
/** Filter `harnesses` down to those with no mounted skill dir (deduped,
|
|
32
|
+
* first-seen order preserved) — the set that must fall back to `inline`
|
|
33
|
+
* delivery, or that a caller should warn about before offering "mounted"
|
|
34
|
+
* install UX. */
|
|
35
|
+
declare function unsupportedSkillHarnesses(harnesses: Iterable<Harness>): Harness[];
|
|
36
|
+
/** Inputs to {@link composeSkillsForHarness}. */
|
|
37
|
+
interface ComposeSkillsForHarnessInput {
|
|
38
|
+
skills: SkillEntry[];
|
|
39
|
+
harness: Harness;
|
|
40
|
+
tier?: string;
|
|
41
|
+
heading?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Compose {@link ComposedSkills} for `harness`: `mounted` delivery when the
|
|
45
|
+
* platform names a cwd skill dir for it, `inline` delivery (the automatic
|
|
46
|
+
* fallback that keeps every skill available on every harness) otherwise. The
|
|
47
|
+
* one function a product calls instead of hand-checking `resolveSkillDir`
|
|
48
|
+
* and branching between {@link composeSkills}'s two modes itself.
|
|
49
|
+
*/
|
|
50
|
+
declare function composeSkillsForHarness(input: ComposeSkillsForHarnessInput): ComposedSkills;
|
|
51
|
+
|
|
52
|
+
export { type ComposeSkillsForHarnessInput, ComposedSkills, SkillEntry, composeSkillsForHarness, resolveSkillDir, unsupportedSkillHarnesses };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
composeSkills
|
|
3
|
+
} from "../chunk-NBSBRZ6F.js";
|
|
4
|
+
|
|
5
|
+
// src/skills-placement/index.ts
|
|
6
|
+
import { skillDirForHarness } from "@tangle-network/agent-profile-materialize";
|
|
7
|
+
var HARNESS_BRIDGE = {
|
|
8
|
+
opencode: "opencode",
|
|
9
|
+
"claude-code": "claude-code",
|
|
10
|
+
nanoclaw: "nanoclaw",
|
|
11
|
+
"kimi-code": "kimi-code",
|
|
12
|
+
codex: "codex",
|
|
13
|
+
pi: "pi",
|
|
14
|
+
hermes: "hermes",
|
|
15
|
+
openclaw: "openclaw"
|
|
16
|
+
};
|
|
17
|
+
function resolveSkillDir(harness) {
|
|
18
|
+
const bridged = HARNESS_BRIDGE[harness];
|
|
19
|
+
if (!bridged) return null;
|
|
20
|
+
return skillDirForHarness(bridged);
|
|
21
|
+
}
|
|
22
|
+
function unsupportedSkillHarnesses(harnesses) {
|
|
23
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const harness of harnesses) {
|
|
26
|
+
if (resolveSkillDir(harness) !== null) continue;
|
|
27
|
+
if (seen.has(harness)) continue;
|
|
28
|
+
seen.add(harness);
|
|
29
|
+
out.push(harness);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function composeSkillsForHarness(input) {
|
|
34
|
+
const { skills, harness, tier, heading } = input;
|
|
35
|
+
const skillDir = resolveSkillDir(harness);
|
|
36
|
+
if (skillDir) return composeSkills({ skills, mode: "mounted", skillDir, tier, heading });
|
|
37
|
+
return composeSkills({ skills, mode: "inline", tier, heading });
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
composeSkillsForHarness,
|
|
41
|
+
resolveSkillDir,
|
|
42
|
+
unsupportedSkillHarnesses
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/skills-placement/index.ts"],"sourcesContent":["/**\n * Harness-native skill directory resolution — the one place agent-app binds\n * to the platform's authoritative per-harness skill-dir map.\n *\n * `../skills` renders skill CONTENT (parse, tier-filter, `inline`/`mounted`\n * delivery) but deliberately stops short of naming WHICH cwd path a `mounted`\n * skill lands at on a given harness — that mapping is owned by the platform\n * materializer (`@tangle-network/agent-profile-materialize`'s\n * `skillDirForHarness`), not by app-shell. This subpath exists so no product\n * — and no other agent-app module — ever writes a skill path literal\n * (`~/.claude/skills/...`, `.opencode/skills`, ...) of its own; it bridges\n * agent-app's `Harness` taxonomy onto the platform's `HarnessId` and asks the\n * platform for the answer.\n *\n * Requires the OPTIONAL peer `@tangle-network/agent-profile-materialize`.\n * Products that don't install it simply don't import this subpath — every\n * other agent-app skills surface (`../skills`, `ProfileChannels.skillRefs`)\n * works without it, falling back to `inline` delivery.\n */\n\nimport type { Harness } from '../harness/index'\nimport { skillDirForHarness, type HarnessId } from '@tangle-network/agent-profile-materialize'\nimport { composeSkills, type ComposedSkills, type SkillEntry } from '../skills/index'\n\n/** agent-app `Harness` -> platform `HarnessId`, identity-mapped for exactly\n * the harnesses the platform map covers. Harnesses absent here (`amp`,\n * `factory-droids`, `forge`, `acp`, `cursor`, `cli-base`) resolve to `null` —\n * callers fall back to `inline` delivery. `cursor`'s adapter supports\n * `resources.skills` bespokely but isn't in the platform map yet; treating it\n * as unbridged (inline fallback) is the safe posture until the map covers\n * it, rather than guessing its cwd skill dir here. */\nconst HARNESS_BRIDGE: Partial<Record<Harness, HarnessId>> = {\n opencode: 'opencode',\n 'claude-code': 'claude-code',\n nanoclaw: 'nanoclaw',\n 'kimi-code': 'kimi-code',\n codex: 'codex',\n pi: 'pi',\n hermes: 'hermes',\n openclaw: 'openclaw',\n}\n\n/** Resolve the cwd-relative skill dir `resources.skills` refs materialize\n * into on `harness` — via the platform's `skillDirForHarness`. `null` when\n * `harness` isn't bridged (see {@link HARNESS_BRIDGE}) or when the platform\n * itself has no cwd skill primitive for it (e.g. `hermes`, user-dir-only). */\nexport function resolveSkillDir(harness: Harness): string | null {\n const bridged = HARNESS_BRIDGE[harness]\n if (!bridged) return null\n return skillDirForHarness(bridged)\n}\n\n/** Filter `harnesses` down to those with no mounted skill dir (deduped,\n * first-seen order preserved) — the set that must fall back to `inline`\n * delivery, or that a caller should warn about before offering \"mounted\"\n * install UX. */\nexport function unsupportedSkillHarnesses(harnesses: Iterable<Harness>): Harness[] {\n const seen = new Set<Harness>()\n const out: Harness[] = []\n for (const harness of harnesses) {\n if (resolveSkillDir(harness) !== null) continue\n if (seen.has(harness)) continue\n seen.add(harness)\n out.push(harness)\n }\n return out\n}\n\n/** Inputs to {@link composeSkillsForHarness}. */\nexport interface ComposeSkillsForHarnessInput {\n skills: SkillEntry[]\n harness: Harness\n tier?: string\n heading?: string\n}\n\n/**\n * Compose {@link ComposedSkills} for `harness`: `mounted` delivery when the\n * platform names a cwd skill dir for it, `inline` delivery (the automatic\n * fallback that keeps every skill available on every harness) otherwise. The\n * one function a product calls instead of hand-checking `resolveSkillDir`\n * and branching between {@link composeSkills}'s two modes itself.\n */\nexport function composeSkillsForHarness(input: ComposeSkillsForHarnessInput): ComposedSkills {\n const { skills, harness, tier, heading } = input\n const skillDir = resolveSkillDir(harness)\n if (skillDir) return composeSkills({ skills, mode: 'mounted', skillDir, tier, heading })\n return composeSkills({ skills, mode: 'inline', tier, heading })\n}\n\nexport type { ComposedSkills, SkillEntry } from '../skills/index'\n"],"mappings":";;;;;AAqBA,SAAS,0BAA0C;AAUnD,IAAM,iBAAsD;AAAA,EAC1D,UAAU;AAAA,EACV,eAAe;AAAA,EACf,UAAU;AAAA,EACV,aAAa;AAAA,EACb,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,UAAU;AACZ;AAMO,SAAS,gBAAgB,SAAiC;AAC/D,QAAM,UAAU,eAAe,OAAO;AACtC,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,mBAAmB,OAAO;AACnC;AAMO,SAAS,0BAA0B,WAAyC;AACjF,QAAM,OAAO,oBAAI,IAAa;AAC9B,QAAM,MAAiB,CAAC;AACxB,aAAW,WAAW,WAAW;AAC/B,QAAI,gBAAgB,OAAO,MAAM,KAAM;AACvC,QAAI,KAAK,IAAI,OAAO,EAAG;AACvB,SAAK,IAAI,OAAO;AAChB,QAAI,KAAK,OAAO;AAAA,EAClB;AACA,SAAO;AACT;AAiBO,SAAS,wBAAwB,OAAqD;AAC3F,QAAM,EAAE,QAAQ,SAAS,MAAM,QAAQ,IAAI;AAC3C,QAAM,WAAW,gBAAgB,OAAO;AACxC,MAAI,SAAU,QAAO,cAAc,EAAE,QAAQ,MAAM,WAAW,UAAU,MAAM,QAAQ,CAAC;AACvF,SAAO,cAAc,EAAE,QAAQ,MAAM,UAAU,MAAM,QAAQ,CAAC;AAChE;","names":[]}
|