zidane 4.1.9 → 5.0.0

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 (54) hide show
  1. package/README.md +11 -2
  2. package/dist/{agent-CMIhYhDz.d.ts → agent-JhicgLOV.d.ts} +78 -4
  3. package/dist/agent-JhicgLOV.d.ts.map +1 -0
  4. package/dist/chat.d.ts +336 -6
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +2 -2
  7. package/dist/{index-DAaKyadO.d.ts → index-2yLUyTbc.d.ts} +34 -4
  8. package/dist/{index-DAaKyadO.d.ts.map → index-2yLUyTbc.d.ts.map} +1 -1
  9. package/dist/{index-D6Dd6Kc0.d.ts → index-t_W9i7Ql.d.ts} +8 -3
  10. package/dist/index-t_W9i7Ql.d.ts.map +1 -0
  11. package/dist/index.d.ts +3 -3
  12. package/dist/index.js +4 -4
  13. package/dist/{interpolate-BydkV1eT.js → interpolate-Ck970-61.js} +9 -2
  14. package/dist/{interpolate-BydkV1eT.js.map → interpolate-Ck970-61.js.map} +1 -1
  15. package/dist/mcp.d.ts +1 -1
  16. package/dist/presets-BRFH2qsQ.js +90 -0
  17. package/dist/presets-BRFH2qsQ.js.map +1 -0
  18. package/dist/presets.d.ts +3 -2
  19. package/dist/presets.js +2 -2
  20. package/dist/providers.d.ts +1 -1
  21. package/dist/session/sqlite.d.ts +1 -1
  22. package/dist/session/sqlite.d.ts.map +1 -1
  23. package/dist/session/sqlite.js +28 -13
  24. package/dist/session/sqlite.js.map +1 -1
  25. package/dist/{session-B1RN0uoi.js → session-791hhrFa.js} +24 -1
  26. package/dist/session-791hhrFa.js.map +1 -0
  27. package/dist/session.d.ts +1 -1
  28. package/dist/session.js +1 -1
  29. package/dist/skills.d.ts +2 -2
  30. package/dist/skills.js +1 -1
  31. package/dist/theme-pJv47erq.d.ts +1202 -0
  32. package/dist/theme-pJv47erq.d.ts.map +1 -0
  33. package/dist/{tools-BdQENveS.js → tools-CLazLRb4.js} +81 -21
  34. package/dist/tools-CLazLRb4.js.map +1 -0
  35. package/dist/tools.d.ts +2 -2
  36. package/dist/tools.js +1 -1
  37. package/dist/tui.d.ts +258 -30
  38. package/dist/tui.d.ts.map +1 -1
  39. package/dist/tui.js +2957 -499
  40. package/dist/tui.js.map +1 -1
  41. package/dist/turn-operations-5aQu4dJg.js +3587 -0
  42. package/dist/turn-operations-5aQu4dJg.js.map +1 -0
  43. package/dist/types.d.ts +2 -2
  44. package/package.json +1 -1
  45. package/dist/agent-CMIhYhDz.d.ts.map +0 -1
  46. package/dist/index-D6Dd6Kc0.d.ts.map +0 -1
  47. package/dist/presets-4zCJzCYw.js +0 -39
  48. package/dist/presets-4zCJzCYw.js.map +0 -1
  49. package/dist/session-B1RN0uoi.js.map +0 -1
  50. package/dist/theme-Caf4AvTO.d.ts +0 -637
  51. package/dist/theme-Caf4AvTO.d.ts.map +0 -1
  52. package/dist/theme-context-DQM2lx4U.js +0 -1853
  53. package/dist/theme-context-DQM2lx4U.js.map +0 -1
  54. package/dist/tools-BdQENveS.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"interpolate-BydkV1eT.js","names":[],"sources":["../src/skills/activation.ts","../src/skills/validate.ts","../src/skills/allowed-tools.ts","../src/xml.ts","../src/skills/catalog.ts","../src/skills/discovery.ts","../src/skills/writer.ts","../src/skills/resolve.ts","../src/skills/interpolate.ts"],"sourcesContent":["/**\n * Per-agent skill activation state machine.\n *\n * Tracks which skills are active across a run. The three skills tools\n * (`skills_use` / `skills_read` / `skills_run_script`) read from this state\n * for gating + listing. Allowed-tools enforcement reads from it too.\n *\n * Lifecycle:\n * - Storage lives on the agent instance (created once in `createAgent`), but\n * every `run()` ends with an implicit deactivate-all pass (reason `'run-end'`)\n * so activation does **not** persist across run boundaries. To keep a skill\n * active across successive runs, call `agent.activateSkill(name)` before each\n * run — the explicit-activation path is idempotent.\n * - `agent.reset()` clears the state with reason `'reset'`.\n * - On session-resume, carried-forward `skills_use` tool_call blocks (in prior\n * assistant turns) are replayed at run-start to rehydrate state with\n * `via: 'resume'`.\n *\n * The caps (`maxActive` from SkillsConfig) is enforced here — returning `false`\n * from `activate()` when the cap is hit lets the caller surface an actionable\n * \"max skills active\" error to the model.\n */\n\nimport type { SkillConfig } from './types'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** How a skill was activated. Surfaced in `skills:activate` hook ctx. */\nexport type ActivationVia = 'model' | 'explicit' | 'resume'\n\n/** Reason a skill was deactivated. Surfaced in `skills:deactivate` hook ctx. */\nexport type DeactivationReason = 'run-end' | 'explicit' | 'reset'\n\n/** A skill currently active in the state machine. */\nexport interface ActiveSkill {\n skill: SkillConfig\n activatedAt: number\n activatedVia: ActivationVia\n}\n\n/**\n * Per-agent skill activation state. Public read-surface is the `active()` list\n * and `isActive(name)` predicate; writes go through `activate()` / `deactivate()`.\n */\nexport interface SkillActivationState {\n /** List of currently active skills in activation order. Returns a snapshot. */\n active: () => readonly ActiveSkill[]\n /** Is the skill with this canonical name currently active? */\n isActive: (name: string) => boolean\n /** Retrieve the `ActiveSkill` record by name, or `undefined`. */\n get: (name: string) => ActiveSkill | undefined\n /**\n * Mark a skill as active.\n * - Returns `'ok'` on a fresh activation (caller should fire `skills:activate`).\n * - Returns `'already-active'` if the skill was already in the set (idempotent).\n * - Returns `'cap-reached'` if the `maxActive` cap would be exceeded. State is unchanged.\n */\n activate: (skill: SkillConfig, via: ActivationVia) => 'ok' | 'already-active' | 'cap-reached'\n /**\n * Mark a skill as inactive. Returns the removed `ActiveSkill` record or `undefined`\n * if it wasn't active. Callers fire `skills:deactivate` on removal.\n */\n deactivate: (name: string) => ActiveSkill | undefined\n /** Remove every active skill. Returns the list of removed records. */\n clear: () => readonly ActiveSkill[]\n}\n\nexport interface SkillActivationStateOptions {\n /**\n * Cap on concurrent activations. `undefined` (the default) disables the cap.\n * When set, `activate()` returns `'cap-reached'` once the set is at size `maxActive`.\n */\n maxActive?: number\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createSkillActivationState(\n options: SkillActivationStateOptions = {},\n): SkillActivationState {\n const byName = new Map<string, ActiveSkill>()\n const maxActive = typeof options.maxActive === 'number' && options.maxActive > 0\n ? options.maxActive\n : undefined\n\n return {\n active() {\n return [...byName.values()]\n },\n\n isActive(name) {\n return byName.has(name)\n },\n\n get(name) {\n return byName.get(name)\n },\n\n activate(skill, via) {\n if (byName.has(skill.name))\n return 'already-active'\n if (maxActive !== undefined && byName.size >= maxActive)\n return 'cap-reached'\n byName.set(skill.name, {\n skill,\n activatedAt: Date.now(),\n activatedVia: via,\n })\n return 'ok'\n },\n\n deactivate(name) {\n const existing = byName.get(name)\n if (!existing)\n return undefined\n byName.delete(name)\n return existing\n },\n\n clear() {\n const snapshot = [...byName.values()]\n byName.clear()\n return snapshot\n },\n }\n}\n","/**\n * Strict validators for Agent Skills.\n *\n * Applied on the authoring path (`defineSkill`, `writeSkillToDisk`). Parser-time\n * validation is intentionally lenient — see `parseSkillFile` for diagnostic collection.\n */\n\nimport type { SkillConfig } from './types'\nimport { basename } from 'node:path'\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst NAME_MAX = 64\nconst DESCRIPTION_MAX = 1024\nconst COMPATIBILITY_MAX = 500\n\nconst SKILL_NAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/\nconst CONSECUTIVE_HYPHENS_RE = /--/\n\n// Accepts both Claude-Code-style PascalCase (`Bash`, `Read`) AND snake_case\n// (`skills_use`, `read_file`, `mcp_fs_read`) tool names. Spec leaves the\n// naming convention to the client.\nconst ALLOWED_TOOL_PATTERN_RE = /^([\\w-]+)(?:\\(([^)]*)\\))?$/\n\n// Path-sandbox regexes (hoisted to avoid per-call recompilation).\nconst ABS_WINDOWS_PATH_RE = /^[a-z]:[\\\\/]/i\nconst PATH_SEPARATOR_RE = /[\\\\/]/\nconst TRAILING_SLASHES_RE = /\\/+$/\n\n// ---------------------------------------------------------------------------\n// Validation result\n// ---------------------------------------------------------------------------\n\n/**\n * A single issue surfaced by skill-authoring validation.\n * Distinct from `ValidationResult` in `tools/validation.ts` (tool-input JSON\n * schema validation) — different domain, different module.\n */\nexport interface SkillValidationIssue {\n /** Stable machine-readable code for consumer matching. */\n code: string\n /** Human-readable description. */\n message: string\n /** Frontmatter field name the issue relates to, when applicable. */\n field?: string\n}\n\nexport interface SkillValidationResult {\n valid: boolean\n errors: SkillValidationIssue[]\n}\n\n// ---------------------------------------------------------------------------\n// Name validation (strict — spec rules)\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a skill name per the spec:\n * - 1–64 characters\n * - Lowercase alphanumeric + hyphens only\n * - Must not start or end with a hyphen\n * - Must not contain consecutive hyphens\n *\n * The parent-directory match is validated separately (it requires knowing the\n * skill's `location`, which this function does not).\n */\nexport function validateSkillName(name: string): boolean {\n if (typeof name !== 'string')\n return false\n if (name.length < 1 || name.length > NAME_MAX)\n return false\n if (CONSECUTIVE_HYPHENS_RE.test(name))\n return false\n return SKILL_NAME_RE.test(name)\n}\n\n// ---------------------------------------------------------------------------\n// Authoring-time validation (`defineSkill`, `writeSkillToDisk`)\n// ---------------------------------------------------------------------------\n\n/**\n * Strict validation for a skill that is about to be authored / written to disk.\n * Rejects anything that would violate the spec. Use the lenient parser path\n * (`parseSkillFile`) for loading third-party skills.\n */\nexport function validateSkillForWrite(skill: SkillConfig): SkillValidationResult {\n const errors: SkillValidationIssue[] = []\n\n // Name\n if (!validateSkillName(skill.name)) {\n errors.push({\n code: 'invalid-name',\n message: `Skill name \"${skill.name}\" must be 1-64 chars, lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens.`,\n field: 'name',\n })\n }\n\n // Name must match parent directory when location is present\n if (skill.location) {\n const dirName = basename(skill.baseDir ?? '')\n if (dirName && dirName !== skill.name) {\n errors.push({\n code: 'name-mismatch-directory',\n message: `Skill name \"${skill.name}\" must match parent directory name \"${dirName}\".`,\n field: 'name',\n })\n }\n }\n\n // Description\n if (typeof skill.description !== 'string' || skill.description.length < 1) {\n errors.push({\n code: 'missing-description',\n message: 'Skill description is required (non-empty).',\n field: 'description',\n })\n }\n else if (skill.description.length > DESCRIPTION_MAX) {\n errors.push({\n code: 'description-too-long',\n message: `Skill description must be at most ${DESCRIPTION_MAX} characters (got ${skill.description.length}).`,\n field: 'description',\n })\n }\n\n // Compatibility (optional)\n if (skill.compatibility !== undefined) {\n if (typeof skill.compatibility !== 'string' || skill.compatibility.length === 0) {\n errors.push({\n code: 'invalid-compatibility',\n message: 'Compatibility must be a non-empty string when provided.',\n field: 'compatibility',\n })\n }\n else if (skill.compatibility.length > COMPATIBILITY_MAX) {\n errors.push({\n code: 'compatibility-too-long',\n message: `Compatibility must be at most ${COMPATIBILITY_MAX} characters (got ${skill.compatibility.length}).`,\n field: 'compatibility',\n })\n }\n }\n\n // Metadata values must be strings per spec\n if (skill.metadata) {\n for (const [key, value] of Object.entries(skill.metadata)) {\n if (typeof value !== 'string') {\n errors.push({\n code: 'invalid-metadata-value',\n message: `Metadata value for \"${key}\" must be a string (spec: \"A map from string keys to string values\").`,\n field: 'metadata',\n })\n }\n }\n }\n\n // Allowed-tools entries must be parseable patterns\n if (skill.allowedTools) {\n for (const pattern of skill.allowedTools) {\n if (!ALLOWED_TOOL_PATTERN_RE.test(pattern)) {\n errors.push({\n code: 'invalid-allowed-tool-pattern',\n message: `Allowed-tools entry \"${pattern}\" is not a recognized pattern (expected \"ToolName\" or \"ToolName(arg:*)\").`,\n field: 'allowed-tools',\n })\n }\n }\n }\n\n return { valid: errors.length === 0, errors }\n}\n\n// ---------------------------------------------------------------------------\n// Resource path sandbox\n// ---------------------------------------------------------------------------\n\n/**\n * Validate that `relPath` stays inside `baseDir` when resolved.\n *\n * Rejects:\n * - Absolute paths (`/etc/passwd`, `C:\\…`)\n * - Parent traversal (`..` segments that escape baseDir)\n * - Null-byte tricks\n *\n * Returns `{ valid: true, absolutePath }` on success or `{ valid: false, error }`\n * with a human-readable reason.\n */\nexport function validateResourcePath(\n relPath: string,\n baseDir: string,\n): { valid: true, absolutePath: string } | { valid: false, error: string } {\n if (typeof relPath !== 'string' || relPath.length === 0)\n return { valid: false, error: 'Resource path must be a non-empty string.' }\n\n if (relPath.includes('\\0'))\n return { valid: false, error: 'Resource path contains a null byte.' }\n\n // Reject absolute paths upfront — both POSIX and Windows forms.\n if (relPath.startsWith('/') || ABS_WINDOWS_PATH_RE.test(relPath))\n return { valid: false, error: `Absolute paths are not allowed (\"${relPath}\").` }\n\n // Normalize and ensure we stay under baseDir.\n const segments: string[] = []\n for (const segment of relPath.split(PATH_SEPARATOR_RE)) {\n if (segment === '' || segment === '.')\n continue\n if (segment === '..') {\n if (segments.length === 0) {\n return {\n valid: false,\n error: `Resource path \"${relPath}\" escapes the skill directory.`,\n }\n }\n segments.pop()\n continue\n }\n segments.push(segment)\n }\n\n if (segments.length === 0)\n return { valid: false, error: 'Resource path resolves to the skill root itself.' }\n\n const absolutePath = `${baseDir.replace(TRAILING_SLASHES_RE, '')}/${segments.join('/')}`\n return { valid: true, absolutePath }\n}\n\n// ---------------------------------------------------------------------------\n// Allowed-tools matching\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a single `allowed-tools` entry into its tool name + optional argument pattern.\n *\n * Examples:\n * - `Read` → `{ tool: 'Read' }`\n * - `Bash(git:*)` → `{ tool: 'Bash', argPrefix: 'git' }`\n */\nexport function parseAllowedToolPattern(entry: string): { tool: string, argPrefix?: string } | null {\n const m = entry.trim().match(ALLOWED_TOOL_PATTERN_RE)\n if (!m)\n return null\n const tool = m[1]\n const arg = m[2]\n if (!arg)\n return { tool }\n\n // Accept `git:*` (prefix wildcard) and `git` (literal). Future: full glob.\n if (arg.endsWith(':*'))\n return { tool, argPrefix: arg.slice(0, -2) }\n return { tool, argPrefix: arg }\n}\n\n/**\n * Check whether a tool call (identified by its wire/displayName and argument input)\n * matches a skill's allow-list entry.\n *\n * Matching rules (Zidane's interpretation of the spec's unspecified syntax):\n * - Exact match: displayName === pattern (no parens).\n * - Prefix match: for `Tool(arg:*)`, displayName === 'Tool' AND **any** string\n * value in the input object starts with `arg`. This is intentionally\n * permissive: it doesn't depend on a convention about which property carries\n * the \"command\" (schemas vary across tools), and for the common\n * `shell({ command: 'git …' })` shape it behaves identically to a \"primary\n * string\" rule.\n * - For tools whose inputs carry no string values, the arg-match returns false.\n */\nexport function matchesAllowedTool(\n displayName: string,\n input: Record<string, unknown>,\n pattern: string,\n): boolean {\n const parsed = parseAllowedToolPattern(pattern)\n if (!parsed)\n return false\n if (parsed.tool !== displayName)\n return false\n if (parsed.argPrefix === undefined)\n return true\n\n // Match if any string input value starts with the arg prefix. More robust\n // than picking a single \"first\" value (object property order is a subtle\n // brittleness surface we avoid).\n for (const value of Object.values(input)) {\n if (typeof value === 'string' && value.startsWith(parsed.argPrefix))\n return true\n }\n return false\n}\n\n/**\n * Test whether a tool call is allowed by the union of `allowedTools` across a set\n * of active skills. Returns `true` when the union is empty (permissive default)\n * OR when any entry matches.\n */\nexport function isToolAllowedByUnion(\n displayName: string,\n input: Record<string, unknown>,\n union: readonly string[],\n): boolean {\n if (union.length === 0)\n return true\n return union.some(pattern => matchesAllowedTool(displayName, input, pattern))\n}\n","/**\n * `allowed-tools` enforcement middleware.\n *\n * Installed by the agent on `tool:gate` and `mcp:tool:gate`. Computes the\n * union of `allowedTools` patterns across currently-active skills; blocks any\n * tool call whose `displayName` is not in that union. The three injected\n * skills tools (`skills_use` / `skills_read` / `skills_run_script`) are\n * implicitly allowed so a skill declaring an allow-list cannot lock out the\n * scaffolding that lets it run at all.\n *\n * The match target is `displayName` (wire / LLM-facing name), not the\n * canonical name, because skill authors write allow-list entries to match\n * what the model sees.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from '../agent'\nimport type { McpToolHookContext, ToolHookContext } from '../types'\nimport type { SkillActivationState } from './activation'\nimport { AgentToolNotAllowedError } from '../errors'\nimport { isToolAllowedByUnion } from './validate'\n\n/** Tools that are always allowed regardless of the active skills' allow-list. */\nexport const IMPLICITLY_ALLOWED_SKILL_TOOLS: readonly string[] = [\n 'skills_use',\n 'skills_read',\n 'skills_run_script',\n]\n\n/**\n * Register `tool:gate` / `mcp:tool:gate` handlers that enforce the union of\n * `allowedTools` across active skills.\n *\n * No-op when no active skill declares an allow-list (permissive default —\n * matches the spec's \"experimental\" note for `allowed-tools`).\n *\n * Returns an `uninstall` fn. The agent calls this at run end to detach the\n * handlers and prevent cross-run hook leaks.\n */\nexport function installAllowedToolsGate(\n hooks: Hookable<AgentHooks>,\n state: SkillActivationState,\n): () => void {\n function effectiveUnion(): { union: string[], active: string[] } {\n const active = state.active()\n const declared: string[] = []\n for (const record of active) {\n if (record.skill.allowedTools?.length)\n declared.push(...record.skill.allowedTools)\n }\n return {\n union: declared.length > 0 ? [...declared, ...IMPLICITLY_ALLOWED_SKILL_TOOLS] : [],\n active: active.map(a => a.skill.name),\n }\n }\n\n function gateHandler(ctx: ToolHookContext & { block: boolean, reason: string }) {\n // Honor a prior gate's refusal — keeps the gate stack order-independent.\n // Without this, a later-registered gate could silently overwrite an\n // earlier refusal's reason on the same call.\n if (ctx.block)\n return\n const { union, active } = effectiveUnion()\n if (union.length === 0)\n return\n if (isToolAllowedByUnion(ctx.displayName, ctx.input, union))\n return\n const err = new AgentToolNotAllowedError({\n toolName: ctx.name,\n displayName: ctx.displayName,\n allowedUnion: union,\n activeSkills: active,\n })\n ctx.block = true\n ctx.reason = err.message\n }\n\n function mcpGateHandler(ctx: McpToolHookContext & { block: boolean, reason: string }) {\n if (ctx.block)\n return\n const { union, active } = effectiveUnion()\n if (union.length === 0)\n return\n if (isToolAllowedByUnion(ctx.displayName, ctx.input, union))\n return\n const err = new AgentToolNotAllowedError({\n toolName: `mcp_${ctx.server}_${ctx.tool}`,\n displayName: ctx.displayName,\n allowedUnion: union,\n activeSkills: active,\n })\n ctx.block = true\n ctx.reason = err.message\n }\n\n const unregisterTool = hooks.hook('tool:gate', gateHandler)\n const unregisterMcp = hooks.hook('mcp:tool:gate', mcpGateHandler)\n\n return function uninstall() {\n unregisterTool()\n unregisterMcp()\n }\n}\n","/**\n * Minimal XML helpers used by catalog/result builders that emit\n * model-facing pseudo-XML (skills catalog, searchable_tools, tool_search\n * results, skill_content wrappers).\n *\n * Scope: attribute / text-node escaping only — we do NOT emit a full XML\n * document, the model sees these strings as free-form text. Centralised\n * here so the four near-identical copies in `agent.ts`, `skills/catalog.ts`,\n * `tools/skills-use.ts`, and `tools/tool-search.ts` don't drift.\n */\n\nconst RE_AMP = /&/g\nconst RE_LT = /</g\nconst RE_GT = />/g\nconst RE_QUOT = /\"/g\n\nexport function escapeXml(str: string): string {\n return str\n .replace(RE_AMP, '&amp;')\n .replace(RE_LT, '&lt;')\n .replace(RE_GT, '&gt;')\n .replace(RE_QUOT, '&quot;')\n}\n","/**\n * Skill catalog generation.\n *\n * Builds the system prompt section that discloses available skills to the\n * agent — tier 1 of progressive disclosure per the Agent Skills spec.\n *\n * The catalog's behavioral prose branches on which activation mechanism is\n * active for the run:\n *\n * - When the `skills_use` tool is auto-injected (the default for a non-empty\n * catalog), the prose tells the model to call `skills_use` with the skill's\n * name. The agent returns a structured `<skill_content>` block.\n * - When the skills tool is opted out (`SkillsConfig.tool: false`), the prose\n * falls back to the file-read pattern — the model reads `SKILL.md` itself\n * via its file-read tool.\n */\n\nimport type { SkillConfig } from './types'\nimport { escapeXml } from '../xml'\n\nexport interface BuildCatalogOptions {\n /**\n * When true (the default), the prose instructs the model to call\n * `skills_use`. Set to false when `SkillsConfig.tool === false` so the\n * system prompt matches the active activation mechanism.\n */\n skillsToolRegistered?: boolean\n /**\n * Name of the tool the model should use to read `SKILL.md` files when\n * `skillsToolRegistered` is false. Defaults to `'read_file'`.\n */\n readToolName?: string\n}\n\n/**\n * Build the skill catalog XML and behavioral instructions for the system prompt.\n */\nexport function buildCatalog(\n skills: SkillConfig[],\n options: BuildCatalogOptions = {},\n): string {\n if (skills.length === 0)\n return ''\n\n const skillsToolRegistered = options.skillsToolRegistered ?? true\n const readToolName = options.readToolName ?? 'read_file'\n\n const entries = skills.map((skill) => {\n const locationLine = skill.location\n ? `\\n <location>${escapeXml(skill.location)}</location>`\n : ''\n return ` <skill>\n <name>${escapeXml(skill.name)}</name>\n <description>${escapeXml(skill.description)}</description>${locationLine}\n </skill>`\n }).join('\\n')\n\n // Separate instructions for fs-based vs inline skills\n const hasFsSkills = skills.some(s => s.location)\n const hasInlineSkills = skills.some(s => !s.location)\n\n const behavioralParts: string[] = []\n\n behavioralParts.push(\n 'The following skills provide specialized instructions for specific tasks.',\n 'When a task matches a skill\\'s description, activate the skill to load its full instructions before proceeding.',\n )\n\n if (skillsToolRegistered) {\n behavioralParts.push(\n 'To activate a skill, call the `skills_use` tool with the skill\\'s name. '\n + 'The response contains the full instructions and any bundled resources you can then load '\n + 'via `skills_read` (reference files) or execute via `skills_run_script` (scripts/ directory).',\n 'Relative paths referenced in a skill\\'s instructions resolve against the skill directory noted in the `skills_use` response.',\n )\n }\n else if (hasFsSkills) {\n behavioralParts.push(\n `For skills with a <location>, use the ${readToolName} tool to read the SKILL.md file at that path.`,\n 'When a skill references relative paths, resolve them against the skill\\'s directory (the parent of SKILL.md) and use absolute paths in tool calls.',\n )\n }\n\n if (hasInlineSkills && !skillsToolRegistered) {\n behavioralParts.push(\n 'Skills without a <location> have their instructions included directly in <instructions> tags below.',\n )\n }\n\n // Build the full catalog block\n const parts: string[] = [\n behavioralParts.join('\\n'),\n '',\n '<available_skills>',\n entries,\n '</available_skills>',\n ]\n\n // Append inline skill instructions directly — but only when the model has no\n // other way to reach them (`skills_use` also handles inline skills when\n // registered, so we skip the duplicate copy in that case).\n if (hasInlineSkills && !skillsToolRegistered) {\n parts.push('')\n for (const skill of skills) {\n if (!skill.location && skill.instructions) {\n parts.push(`<skill_instructions name=\"${escapeXml(skill.name)}\">`)\n parts.push(skill.instructions)\n if (skill.resources && skill.resources.length > 0) {\n parts.push('')\n parts.push('<skill_resources>')\n for (const res of skill.resources) {\n parts.push(` <file type=\"${res.type}\">${escapeXml(res.path)}</file>`)\n }\n parts.push('</skill_resources>')\n }\n parts.push('</skill_instructions>')\n }\n }\n }\n\n return parts.join('\\n')\n}\n","/**\n * Skill discovery and parsing.\n *\n * Scans filesystem directories for SKILL.md files following the\n * Agent Skills specification (agentskills.io/specification).\n *\n * Parsing is intentionally **lenient** — per the spec's client-implementation\n * guide: warn, don't block. Diagnostics are attached to the returned\n * SkillConfig for host UIs to surface; only unrecoverable errors (missing\n * description, unparseable YAML) skip the skill entirely.\n */\n\nimport type { SkillConfig, SkillDiagnostic, SkillResource, SkillSource } from './types'\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport { validateSkillName } from './validate'\n\n// ---------------------------------------------------------------------------\n// Regexes\n// ---------------------------------------------------------------------------\n\n// Frontmatter regex — opening ---, content, closing ---, optional body.\n// Tolerates CRLF line endings on the opening and closing fences (authoring\n// tools on Windows sometimes persist them mid-file).\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/\n\nconst INDENT_RE = /^[ \\t]{2,}/\nconst KV_RE = /^([^:]+):(.*)$/\nconst DOUBLE_QUOTED_RE = /^\"((?:\\\\.|[^\"\\\\])*)\"$/\nconst SINGLE_QUOTED_RE = /^'((?:''|[^'])*)'$/\nconst DQ_ESCAPE_RE = /\\\\([\"\\\\/bfnrt])/g\nconst WHITESPACE_SPLIT_RE = /\\s+/\nconst PARAGRAPH_SPLIT_RE = /\\n\\n/\nconst COMMA_OR_SPACE_RE = /[,\\s]+/\n\n// ---------------------------------------------------------------------------\n// Frontmatter parsing\n// ---------------------------------------------------------------------------\n\ninterface ParsedSkillFile {\n frontmatter: Record<string, unknown>\n body: string\n diagnostics: SkillDiagnostic[]\n}\n\n/**\n * Parse a SKILL.md file into frontmatter + body.\n *\n * Uses a simple regex-based YAML extractor that handles:\n * - Flat key: value pairs\n * - Quoted values\n * - One-level nested maps (for `metadata:`)\n * - Lenient recovery from unquoted-colon values (e.g.\n * `description: Use when: the user asks …`) via a quote-wrap retry.\n */\nexport function parseFrontmatter(content: string): ParsedSkillFile {\n const diagnostics: SkillDiagnostic[] = []\n const match = content.match(FRONTMATTER_RE)\n if (!match) {\n return { frontmatter: {}, body: content.trim(), diagnostics }\n }\n\n const yamlBlock = match[1]\n const body = match[2].trim()\n\n // Simple YAML parser for flat and one-level nested key-value pairs\n const frontmatter: Record<string, unknown> = {}\n let currentKey: string | null = null\n let currentMap: Record<string, string> | null = null\n\n for (const line of yamlBlock.split('\\n')) {\n // Skip empty lines and comments\n if (!line.trim() || line.trim().startsWith('#'))\n continue\n\n // Nested key (indented under a map key)\n if (currentKey && currentMap && INDENT_RE.test(line)) {\n const nestedMatch = line.trim().match(KV_RE)\n if (nestedMatch) {\n const val = nestedMatch[2].trim()\n currentMap[nestedMatch[1].trim()] = unquoteYaml(val)\n }\n continue\n }\n\n // Top-level key\n if (currentKey && currentMap) {\n frontmatter[currentKey] = currentMap\n currentKey = null\n currentMap = null\n }\n\n // Split on first colon only — lenient for values containing colons.\n const kvMatch = matchFirstColon(line)\n if (!kvMatch)\n continue\n\n const key = kvMatch.key.trim()\n const rawValue = kvMatch.value.trim()\n\n if (!rawValue) {\n // Possibly a map — next lines will be indented\n currentKey = key\n currentMap = {}\n }\n else {\n frontmatter[key] = unquoteYaml(rawValue)\n }\n }\n\n // Flush any pending map\n if (currentKey && currentMap) {\n frontmatter[currentKey] = currentMap\n }\n\n return { frontmatter, body, diagnostics }\n}\n\nfunction matchFirstColon(line: string): { key: string, value: string } | null {\n const idx = line.indexOf(':')\n if (idx < 0)\n return null\n const key = line.slice(0, idx)\n const value = line.slice(idx + 1)\n // Reject accidental matches where key contains spaces before the colon wider\n // than a normal YAML key (defensive against \"some prose: foo\" in body leak).\n if (!KV_RE.test(`${key}:`))\n return null\n return { key, value }\n}\n\n/**\n * Strip outer quotes and decode YAML-style escapes.\n *\n * - Double-quoted values honor `\\\"`, `\\\\`, `\\n`, `\\r`, `\\t`, `\\b`, `\\f`, `\\/`.\n * - Single-quoted values honor `''` (the YAML single-quote escape for a literal `'`).\n * - Trailing inline comments on unquoted values (` # comment`) are dropped.\n * - Unquoted values are returned as-is (trimmed by caller).\n *\n * Skill frontmatter in the wild uses a narrow YAML subset — this is\n * sufficient for every field in the current spec and the common authoring\n * tools. Block scalars (`|`, `>`) and flow sequences are intentionally left\n * unsupported: they'd only appear in a bespoke workflow, and silently\n * half-supporting them is worse than rejecting them obviously.\n */\nfunction unquoteYaml(val: string): string {\n const dq = val.match(DOUBLE_QUOTED_RE)\n if (dq) {\n return dq[1].replace(DQ_ESCAPE_RE, (_, ch) => {\n switch (ch) {\n case '\"': return '\"'\n case '\\\\': return '\\\\'\n case '/': return '/'\n case 'b': return '\\b'\n case 'f': return '\\f'\n case 'n': return '\\n'\n case 'r': return '\\r'\n case 't': return '\\t'\n default: return ch\n }\n })\n }\n const sq = val.match(SINGLE_QUOTED_RE)\n if (sq) {\n return sq[1].replace(/''/g, '\\'')\n }\n // Strip trailing inline comment only when clearly separated (\"value # note\"\n // is a comment; \"value#tag\" is a literal). Preserves content on values that\n // legitimately contain `#` as part of text.\n const hashIdx = val.indexOf(' #')\n if (hashIdx >= 0)\n return val.slice(0, hashIdx).trimEnd()\n return val\n}\n\n/**\n * Narrow a frontmatter field to a string. Produces a diagnostic when present\n * but not a string, so malformed YAML (e.g. `name: 123`) surfaces as a\n * warning instead of silently coerced downstream via `as string`.\n */\nfunction takeString(\n frontmatter: Record<string, unknown>,\n key: string,\n diagnostics: SkillDiagnostic[],\n): string | undefined {\n const raw = frontmatter[key]\n if (raw === undefined || raw === null)\n return undefined\n if (typeof raw === 'string')\n return raw\n diagnostics.push({\n severity: 'warning',\n code: 'invalid-field-type',\n message: `Frontmatter field \"${key}\" expected string, got ${typeof raw}. Coerced.`,\n field: key,\n })\n return String(raw)\n}\n\n// ---------------------------------------------------------------------------\n// Resource enumeration\n// ---------------------------------------------------------------------------\n\nconst RESOURCE_DIRS: Record<string, SkillResource['type']> = {\n scripts: 'script',\n references: 'reference',\n assets: 'asset',\n}\n\nfunction enumerateResources(baseDir: string): SkillResource[] {\n const resources: SkillResource[] = []\n\n for (const [dir, type] of Object.entries(RESOURCE_DIRS)) {\n const dirPath = join(baseDir, dir)\n if (!existsSync(dirPath) || !statSync(dirPath).isDirectory())\n continue\n try {\n // `readdirSync` with `recursive: true` returns `string | Buffer`; we\n // opt into the `encoding: 'utf8'` overload via `withFileTypes: false`\n // default and string-encoded filenames. Guard the Buffer case in case\n // a platform quirk (non-UTF8 filenames on Linux) slips through.\n const files = readdirSync(dirPath, { recursive: true })\n for (const file of files) {\n const rel = typeof file === 'string' ? file : file.toString('utf-8')\n const fullPath = join(dirPath, rel)\n if (statSync(fullPath).isFile()) {\n resources.push({ path: join(dir, rel), type })\n }\n }\n }\n catch {\n // Skip unreadable directories\n }\n }\n\n // Also enumerate other files in the root (excluding SKILL.md)\n try {\n for (const entry of readdirSync(baseDir)) {\n if (entry === 'SKILL.md')\n continue\n const entryPath = join(baseDir, entry)\n if (statSync(entryPath).isFile()) {\n resources.push({ path: entry, type: 'other' })\n }\n }\n }\n catch {\n // Skip unreadable root\n }\n\n return resources\n}\n\n// ---------------------------------------------------------------------------\n// Parse a single SKILL.md file\n// ---------------------------------------------------------------------------\n\ninterface ParseSkillOptions {\n /** Source tag to attach to the returned SkillConfig. */\n source?: SkillSource\n}\n\n/**\n * Parse a SKILL.md file into a SkillConfig (lenient).\n *\n * Returns `null` only when the skill is fundamentally unusable:\n * - The file is missing\n * - The description is absent (required by spec for disclosure)\n *\n * All other issues are surfaced as `SkillConfig.diagnostics` with severity\n * `warning`. Deprecated top-level fields (`paths`, `model`, `thinking`) are\n * auto-migrated into `metadata['zidane.*']`.\n */\nexport async function parseSkillFile(\n filePath: string,\n options: ParseSkillOptions = {},\n): Promise<SkillConfig | null> {\n const absPath = resolve(filePath)\n if (!existsSync(absPath))\n return null\n\n const content = readFileSync(absPath, 'utf-8')\n const { frontmatter, body, diagnostics } = parseFrontmatter(content)\n\n // Description: frontmatter > first paragraph of body > skip\n let description = takeString(frontmatter, 'description', diagnostics)\n if (!description && body) {\n const firstParagraph = body.split(PARAGRAPH_SPLIT_RE)[0]?.trim()\n if (firstParagraph)\n description = firstParagraph\n }\n if (!description)\n return null\n\n if (description.length > 1024) {\n diagnostics.push({\n severity: 'warning',\n code: 'description-too-long',\n message: `Description exceeds spec limit of 1024 characters (got ${description.length}). Loading anyway.`,\n field: 'description',\n })\n }\n\n const baseDir = dirname(absPath)\n const dirName = basename(baseDir)\n const frontmatterName = takeString(frontmatter, 'name', diagnostics)\n const name = frontmatterName || dirName\n\n // Lenient name checks\n if (frontmatterName && frontmatterName !== dirName) {\n diagnostics.push({\n severity: 'warning',\n code: 'name-mismatch-directory',\n message: `Skill name \"${frontmatterName}\" does not match parent directory \"${dirName}\". Loading anyway.`,\n field: 'name',\n })\n }\n if (name.length > 64) {\n diagnostics.push({\n severity: 'warning',\n code: 'name-too-long',\n message: `Skill name \"${name}\" exceeds spec limit of 64 characters. Loading anyway.`,\n field: 'name',\n })\n }\n if (!validateSkillName(name)) {\n diagnostics.push({\n severity: 'warning',\n code: 'invalid-name-format',\n message: `Skill name \"${name}\" does not match spec format (lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens). Loading anyway.`,\n field: 'name',\n })\n }\n\n const config: SkillConfig = {\n name,\n description,\n instructions: body,\n source: options.source ?? 'project',\n location: absPath,\n baseDir,\n resources: enumerateResources(baseDir),\n }\n\n const license = takeString(frontmatter, 'license', diagnostics)\n if (license)\n config.license = license\n\n const compatibility = takeString(frontmatter, 'compatibility', diagnostics)\n if (compatibility) {\n if (compatibility.length > 500) {\n diagnostics.push({\n severity: 'warning',\n code: 'compatibility-too-long',\n message: `Compatibility exceeds spec limit of 500 characters (got ${compatibility.length}). Loading anyway.`,\n field: 'compatibility',\n })\n }\n config.compatibility = compatibility\n }\n\n // Metadata — spec-compliant flat bag\n const metadata: Record<string, string> = {}\n const rawMetadata = frontmatter.metadata\n if (rawMetadata && typeof rawMetadata === 'object' && !Array.isArray(rawMetadata)) {\n for (const [k, v] of Object.entries(rawMetadata as Record<string, unknown>)) {\n if (typeof v !== 'string') {\n diagnostics.push({\n severity: 'warning',\n code: 'invalid-metadata-value',\n message: `Metadata value for \"${k}\" is not a string; coerced. (Spec requires string values.)`,\n field: 'metadata',\n })\n metadata[k] = String(v)\n continue\n }\n metadata[k] = v\n }\n }\n else if (rawMetadata !== undefined) {\n diagnostics.push({\n severity: 'warning',\n code: 'invalid-metadata-shape',\n message: `Frontmatter \"metadata\" expected a map, got ${Array.isArray(rawMetadata) ? 'array' : typeof rawMetadata}. Ignored.`,\n field: 'metadata',\n })\n }\n\n // Auto-migrate deprecated top-level fields into metadata.zidane.*\n const pathsField = takeString(frontmatter, 'paths', diagnostics)\n if (pathsField) {\n metadata['zidane.paths'] = pathsField.split(COMMA_OR_SPACE_RE).filter(Boolean).join(',')\n diagnostics.push({\n severity: 'warning',\n code: 'deprecated-top-level-field',\n message: '`paths` is not a spec field and is deprecated — moved to `metadata[\"zidane.paths\"]`.',\n field: 'paths',\n })\n }\n const modelField = takeString(frontmatter, 'model', diagnostics)\n if (modelField) {\n metadata['zidane.model'] = modelField\n diagnostics.push({\n severity: 'warning',\n code: 'deprecated-top-level-field',\n message: '`model` is not a spec field and is deprecated — moved to `metadata[\"zidane.model\"]`.',\n field: 'model',\n })\n }\n const thinkingField = takeString(frontmatter, 'thinking', diagnostics)\n const effortField = thinkingField ? undefined : takeString(frontmatter, 'effort', diagnostics)\n const legacyThinking = thinkingField ?? effortField\n if (legacyThinking) {\n metadata['zidane.thinking'] = legacyThinking\n diagnostics.push({\n severity: 'warning',\n code: 'deprecated-top-level-field',\n message: `\\`${thinkingField ? 'thinking' : 'effort'}\\` is not a spec field and is deprecated — moved to \\`metadata[\"zidane.thinking\"]\\`.`,\n field: thinkingField ? 'thinking' : 'effort',\n })\n }\n\n if (Object.keys(metadata).length > 0)\n config.metadata = metadata\n\n const allowedTools = takeString(frontmatter, 'allowed-tools', diagnostics)\n if (allowedTools) {\n config.allowedTools = allowedTools.split(WHITESPACE_SPLIT_RE).filter(Boolean)\n }\n\n if (diagnostics.length > 0)\n config.diagnostics = diagnostics\n\n return config\n}\n\n// ---------------------------------------------------------------------------\n// Directory scanning\n// ---------------------------------------------------------------------------\n\nconst SKIP_DIRS = new Set(['.git', 'node_modules', '.DS_Store', 'dist', 'build'])\n\nfunction findSkillDirs(root: string, maxDepth = 4, _depth = 0): string[] {\n if (_depth > maxDepth)\n return []\n if (!existsSync(root) || !statSync(root).isDirectory())\n return []\n\n const results: string[] = []\n try {\n for (const entry of readdirSync(root)) {\n if (SKIP_DIRS.has(entry))\n continue\n const entryPath = join(root, entry)\n if (!statSync(entryPath).isDirectory())\n continue\n\n const skillFile = join(entryPath, 'SKILL.md')\n if (existsSync(skillFile) && statSync(skillFile).isFile()) {\n results.push(skillFile)\n }\n else {\n // Recurse deeper\n results.push(...findSkillDirs(entryPath, maxDepth, _depth + 1))\n }\n }\n }\n catch {\n // Skip unreadable directories\n }\n return results\n}\n\n// ---------------------------------------------------------------------------\n// Default scan paths\n// ---------------------------------------------------------------------------\n\n/** A scan path paired with the source tag that should be attached to any skills found in it. */\nexport interface SourcedScanPath {\n path: string\n source: SkillSource\n}\n\n/**\n * Return the default scan paths tagged by source. Project-scope paths come\n * first; their skills therefore win on name collisions against user-scope\n * skills (first-found wins in discovery).\n */\nexport function getDefaultScanPaths(): SourcedScanPath[] {\n const home = homedir()\n const cwd = process.cwd()\n return [\n // Project-level (higher priority)\n { path: join(cwd, '.agents', 'skills'), source: 'project' },\n { path: join(cwd, '.zidane', 'skills'), source: 'project' },\n // User-level (lower priority)\n { path: join(home, '.agents', 'skills'), source: 'user' },\n { path: join(home, '.zidane', 'skills'), source: 'user' },\n ]\n}\n\n/**\n * Infer a source tag for a user-provided scan path.\n * Paths under `$HOME` are treated as 'user'; everything else as 'project'.\n */\nexport function inferSource(path: string): SkillSource {\n return path.startsWith(homedir()) ? 'user' : 'project'\n}\n\n// ---------------------------------------------------------------------------\n// Discover skills from filesystem\n// ---------------------------------------------------------------------------\n\n/**\n * Discover skills from sourced filesystem paths.\n * Each path is scanned for subdirectories containing SKILL.md.\n * Earlier paths have higher priority (first-found wins on name collision).\n */\nexport async function discoverSkills(paths: SourcedScanPath[]): Promise<SkillConfig[]> {\n const skillsByName = new Map<string, SkillConfig>()\n\n for (const { path: scanPath, source } of paths) {\n const skillFiles = findSkillDirs(resolve(scanPath))\n for (const file of skillFiles) {\n const skill = await parseSkillFile(file, { source })\n if (!skill)\n continue\n if (skillsByName.has(skill.name)) {\n // First-found wins — append a collision diagnostic to the winning skill.\n const existing = skillsByName.get(skill.name)!\n const diag: SkillDiagnostic = {\n severity: 'warning',\n code: 'name-collision-shadowed',\n message: `A skill with name \"${skill.name}\" was also found at ${file} (source: ${source}); shadowed by ${existing.location} (source: ${existing.source}).`,\n }\n existing.diagnostics = [...(existing.diagnostics ?? []), diag]\n continue\n }\n skillsByName.set(skill.name, skill)\n }\n }\n\n return [...skillsByName.values()]\n}\n","/**\n * Skill writer — materializes inline SkillConfig objects to disk as proper\n * SKILL.md files so they participate fully in the filesystem-based skill system.\n *\n * Strict-validates each skill before writing (see `validateSkillForWrite`).\n */\n\nimport type { SkillConfig } from './types'\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { validateSkillForWrite } from './validate'\n\n// ---------------------------------------------------------------------------\n// Frontmatter serialization\n// ---------------------------------------------------------------------------\n\nconst YAML_RESERVED_RE = /[:#&*!|>%@`]/\nconst YAML_EDGE_OR_QUOTE_RE = /^\\s|\\s$|[\"']/\nconst DQUOTE_RE = /\"/g\nconst LEADING_NEWLINES_RE = /^\\n+/\n\nfunction yamlEscape(value: string): string {\n // Quote values that contain YAML-reserved characters (colons, quotes, leading\n // special chars). Keeps round-trip safety for descriptions like\n // \"Use when: the user asks …\".\n if (YAML_RESERVED_RE.test(value) || YAML_EDGE_OR_QUOTE_RE.test(value) || value === '')\n return `\"${value.replace(DQUOTE_RE, '\\\\\"')}\"`\n return value\n}\n\nfunction serializeFrontmatter(skill: SkillConfig): string {\n const lines: string[] = ['---']\n\n lines.push(`name: ${yamlEscape(skill.name)}`)\n lines.push(`description: ${yamlEscape(skill.description)}`)\n\n if (skill.license)\n lines.push(`license: ${yamlEscape(skill.license)}`)\n if (skill.compatibility)\n lines.push(`compatibility: ${yamlEscape(skill.compatibility)}`)\n if (skill.allowedTools?.length)\n lines.push(`allowed-tools: ${skill.allowedTools.join(' ')}`)\n\n if (skill.metadata && Object.keys(skill.metadata).length > 0) {\n lines.push('metadata:')\n for (const [key, value] of Object.entries(skill.metadata)) {\n // Always quote metadata values — preserves string-looking numerics (\"1.0\")\n // and string-valued booleans (\"true\") from YAML's implicit type coercion.\n // Dot-keys (e.g. `zidane.paths`) are accepted verbatim by YAML.\n lines.push(` ${key}: \"${value.replace(DQUOTE_RE, '\\\\\"')}\"`)\n }\n }\n\n lines.push('---')\n return lines.join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Write a single skill to disk\n// ---------------------------------------------------------------------------\n\n/**\n * Write a `SkillConfig` to disk as a proper skill directory with SKILL.md.\n * Returns the path to the written SKILL.md file.\n *\n * Throws if the skill fails `validateSkillForWrite` — the authoring path is\n * strict on purpose. For loading third-party skills, use `parseSkillFile`\n * directly (lenient).\n */\nexport function writeSkillToDisk(skill: SkillConfig, targetDir: string): string {\n const result = validateSkillForWrite(skill)\n if (!result.valid) {\n const summary = result.errors\n .map(e => ` - [${e.code}] ${e.message}`)\n .join('\\n')\n throw new Error(`Cannot write invalid skill \"${skill.name}\":\\n${summary}`)\n }\n\n const skillDir = join(targetDir, skill.name)\n mkdirSync(skillDir, { recursive: true })\n\n const frontmatter = serializeFrontmatter(skill)\n // Normalize: strip any leading newlines on the body so we never emit a\n // double-blank-line between the frontmatter fence and the instructions,\n // and always end the file with a single trailing newline.\n const bodyTrimmed = skill.instructions ? skill.instructions.replace(LEADING_NEWLINES_RE, '') : ''\n const content = bodyTrimmed\n ? `${frontmatter}\\n\\n${bodyTrimmed}\\n`\n : `${frontmatter}\\n`\n\n const skillPath = join(skillDir, 'SKILL.md')\n writeFileSync(skillPath, content)\n\n return skillPath\n}\n\n// ---------------------------------------------------------------------------\n// Write multiple skills to a directory\n// ---------------------------------------------------------------------------\n\n/**\n * Write multiple `SkillConfig` objects to a target directory.\n * Each skill gets its own subdirectory with a SKILL.md file.\n * Returns the target directory path (for use as a scan path).\n */\nexport function writeSkillsToDisk(skills: SkillConfig[], targetDir: string): string {\n mkdirSync(targetDir, { recursive: true })\n for (const skill of skills) {\n writeSkillToDisk(skill, targetDir)\n }\n return targetDir\n}\n","/**\n * Skill resolution — discovers filesystem skills, writes dynamic skills,\n * merges everything, and applies filtering.\n */\n\nimport type { SourcedScanPath } from './discovery'\nimport type { SkillConfig, SkillsConfig } from './types'\nimport { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { discoverSkills, getDefaultScanPaths, inferSource } from './discovery'\nimport { writeSkillsToDisk } from './writer'\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolved-skills bundle: the materialized list plus a `cleanup` fn that\n * removes any temporary directory created for `config.write` skills.\n *\n * Inline skills must live on disk for the lifetime of the agent (the\n * `skills_read` / `skills_run_script` tools resolve relative paths against\n * `baseDir`), so cleanup is deferred to `agent.destroy()`. When no temp\n * directory was created, `cleanup` is a no-op.\n */\nexport interface ResolvedSkillsBundle {\n skills: SkillConfig[]\n cleanup: () => void\n}\n\n/**\n * Resolve all skills from a SkillsConfig:\n *\n * 1. Materialize `config.write` entries to a temp directory, tagged as `inline`\n * 2. Combine with default + user-provided scan paths (project-first, user-next)\n * 3. Run lenient discovery\n * 4. Apply filters: `exclude`, `enabled` allowlist, optional project-skill trust gate\n *\n * Returns `{ skills, cleanup }` — call `cleanup()` on agent destroy to remove\n * the temp directory created for inline `config.write` skills. The agent\n * factory wires this automatically; standalone callers must invoke it\n * themselves to avoid leaking OS temp.\n */\nexport async function resolveSkills(config: SkillsConfig): Promise<ResolvedSkillsBundle> {\n const sourcedPaths: SourcedScanPath[] = []\n let writeDir: string | undefined\n\n if (!config.skipDefaultPaths) {\n sourcedPaths.push(...getDefaultScanPaths())\n }\n\n // User-provided scan paths: infer source from path prefix.\n for (const p of config.scan ?? []) {\n sourcedPaths.push({ path: p, source: inferSource(p) })\n }\n\n // Inline skills materialized to a temp directory and scanned as 'inline'.\n if (config.write?.length) {\n writeDir = mkdtempSync(join(tmpdir(), 'zidane-skills-'))\n writeSkillsToDisk(config.write, writeDir)\n sourcedPaths.push({ path: writeDir, source: 'inline' })\n }\n\n // Discover all skills from filesystem (first-found-wins on name collision).\n let skills = await discoverSkills(sourcedPaths)\n\n // Optional trust gate: drop project-scoped skills when the host hasn't\n // marked the project as trusted. Undefined source is treated as `'project'`\n // (conservative default — an untrusted environment should skip unclassified\n // skills rather than silently loading them).\n if (config.trustProjectSkills === false) {\n skills = skills.filter(s => s.source !== undefined && s.source !== 'project')\n }\n\n // Filter out `exclude`\n const exclude = new Set(config.exclude ?? [])\n let filtered = skills.filter(s => !exclude.has(s.name))\n\n // Apply `enabled` allowlist when it's an array of skill names\n if (Array.isArray(config.enabled)) {\n const allowlist = new Set(config.enabled)\n filtered = filtered.filter(s => allowlist.has(s.name))\n }\n\n const cleanup = writeDir\n ? () => {\n try {\n rmSync(writeDir!, { recursive: true, force: true })\n }\n catch {\n // Best-effort — temp directory may have been removed by the OS or\n // a sibling cleanup. Don't crash agent destroy.\n }\n }\n : () => {}\n\n return { skills: filtered, cleanup }\n}\n","/**\n * Shell interpolation for skill instructions.\n *\n * The `!\\`command\\`` syntax runs shell commands as a preprocessing step\n * before skill content is sent to the agent. The command output replaces\n * the placeholder — the agent only sees the final result.\n *\n * Example:\n * - PR diff: !\\`gh pr diff\\`\n * → - PR diff: <actual diff output>\n */\n\nimport type { ExecutionContext, ExecutionHandle } from '../contexts'\n\n/** Regex to match !`command` patterns in skill instructions */\nconst SHELL_INTERPOLATION_RE = /!`([^`]+)`/g\n\n/**\n * Interpolate shell commands in skill instructions.\n *\n * Runs each !\\`command\\` through the execution context and replaces\n * the placeholder with the command's stdout. If a command fails,\n * the placeholder is replaced with an error message.\n *\n * @param instructions - Raw skill instructions with potential !\\`command\\` patterns\n * @param execution - The execution context to run commands in\n * @param handle - The active execution handle\n * @returns Instructions with all !\\`command\\` patterns replaced by output\n */\nexport async function interpolateShellCommands(\n instructions: string,\n execution: ExecutionContext,\n handle: ExecutionHandle,\n): Promise<string> {\n const matches = [...instructions.matchAll(SHELL_INTERPOLATION_RE)]\n if (matches.length === 0)\n return instructions\n\n // Collect all commands with their positions for precise replacement\n const replacements: { index: number, length: number, output: string }[] = []\n\n for (const match of matches) {\n const command = match[1]\n const index = match.index!\n const length = match[0].length\n\n try {\n const result = await execution.exec(handle, command, { timeout: 30 })\n const output = result.exitCode === 0\n ? result.stdout.trim()\n : `[command failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}]`\n replacements.push({ index, length, output })\n }\n catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n replacements.push({ index, length, output: `[command error: ${message}]` })\n }\n }\n\n // Apply replacements in reverse order to preserve positions\n let result = instructions\n for (let i = replacements.length - 1; i >= 0; i--) {\n const { index, length, output } = replacements[i]\n result = result.slice(0, index) + output + result.slice(index + length)\n }\n\n return result\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"interpolate-Ck970-61.js","names":[],"sources":["../src/skills/activation.ts","../src/skills/validate.ts","../src/skills/allowed-tools.ts","../src/xml.ts","../src/skills/catalog.ts","../src/skills/discovery.ts","../src/skills/writer.ts","../src/skills/resolve.ts","../src/skills/interpolate.ts"],"sourcesContent":["/**\n * Per-agent skill activation state machine.\n *\n * Tracks which skills are active across a run. The three skills tools\n * (`skills_use` / `skills_read` / `skills_run_script`) read from this state\n * for gating + listing. Allowed-tools enforcement reads from it too.\n *\n * Lifecycle:\n * - Storage lives on the agent instance (created once in `createAgent`), but\n * every `run()` ends with an implicit deactivate-all pass (reason `'run-end'`)\n * so activation does **not** persist across run boundaries. To keep a skill\n * active across successive runs, call `agent.activateSkill(name)` before each\n * run — the explicit-activation path is idempotent.\n * - `agent.reset()` clears the state with reason `'reset'`.\n * - On session-resume, carried-forward `skills_use` tool_call blocks (in prior\n * assistant turns) are replayed at run-start to rehydrate state with\n * `via: 'resume'`.\n *\n * The caps (`maxActive` from SkillsConfig) is enforced here — returning `false`\n * from `activate()` when the cap is hit lets the caller surface an actionable\n * \"max skills active\" error to the model.\n */\n\nimport type { SkillConfig } from './types'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** How a skill was activated. Surfaced in `skills:activate` hook ctx. */\nexport type ActivationVia = 'model' | 'explicit' | 'resume'\n\n/** Reason a skill was deactivated. Surfaced in `skills:deactivate` hook ctx. */\nexport type DeactivationReason = 'run-end' | 'explicit' | 'reset'\n\n/** A skill currently active in the state machine. */\nexport interface ActiveSkill {\n skill: SkillConfig\n activatedAt: number\n activatedVia: ActivationVia\n}\n\n/**\n * Per-agent skill activation state. Public read-surface is the `active()` list\n * and `isActive(name)` predicate; writes go through `activate()` / `deactivate()`.\n */\nexport interface SkillActivationState {\n /** List of currently active skills in activation order. Returns a snapshot. */\n active: () => readonly ActiveSkill[]\n /** Is the skill with this canonical name currently active? */\n isActive: (name: string) => boolean\n /** Retrieve the `ActiveSkill` record by name, or `undefined`. */\n get: (name: string) => ActiveSkill | undefined\n /**\n * Mark a skill as active.\n * - Returns `'ok'` on a fresh activation (caller should fire `skills:activate`).\n * - Returns `'already-active'` if the skill was already in the set (idempotent).\n * - Returns `'cap-reached'` if the `maxActive` cap would be exceeded. State is unchanged.\n */\n activate: (skill: SkillConfig, via: ActivationVia) => 'ok' | 'already-active' | 'cap-reached'\n /**\n * Mark a skill as inactive. Returns the removed `ActiveSkill` record or `undefined`\n * if it wasn't active. Callers fire `skills:deactivate` on removal.\n */\n deactivate: (name: string) => ActiveSkill | undefined\n /** Remove every active skill. Returns the list of removed records. */\n clear: () => readonly ActiveSkill[]\n}\n\nexport interface SkillActivationStateOptions {\n /**\n * Cap on concurrent activations. `undefined` (the default) disables the cap.\n * When set, `activate()` returns `'cap-reached'` once the set is at size `maxActive`.\n */\n maxActive?: number\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createSkillActivationState(\n options: SkillActivationStateOptions = {},\n): SkillActivationState {\n const byName = new Map<string, ActiveSkill>()\n const maxActive = typeof options.maxActive === 'number' && options.maxActive > 0\n ? options.maxActive\n : undefined\n\n return {\n active() {\n return [...byName.values()]\n },\n\n isActive(name) {\n return byName.has(name)\n },\n\n get(name) {\n return byName.get(name)\n },\n\n activate(skill, via) {\n if (byName.has(skill.name))\n return 'already-active'\n if (maxActive !== undefined && byName.size >= maxActive)\n return 'cap-reached'\n byName.set(skill.name, {\n skill,\n activatedAt: Date.now(),\n activatedVia: via,\n })\n return 'ok'\n },\n\n deactivate(name) {\n const existing = byName.get(name)\n if (!existing)\n return undefined\n byName.delete(name)\n return existing\n },\n\n clear() {\n const snapshot = [...byName.values()]\n byName.clear()\n return snapshot\n },\n }\n}\n","/**\n * Strict validators for Agent Skills.\n *\n * Applied on the authoring path (`defineSkill`, `writeSkillToDisk`). Parser-time\n * validation is intentionally lenient — see `parseSkillFile` for diagnostic collection.\n */\n\nimport type { SkillConfig } from './types'\nimport { basename } from 'node:path'\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst NAME_MAX = 64\nconst DESCRIPTION_MAX = 1024\nconst COMPATIBILITY_MAX = 500\n\nconst SKILL_NAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/\nconst CONSECUTIVE_HYPHENS_RE = /--/\n\n// Accepts both Claude-Code-style PascalCase (`Bash`, `Read`) AND snake_case\n// (`skills_use`, `read_file`, `mcp_fs_read`) tool names. Spec leaves the\n// naming convention to the client.\nconst ALLOWED_TOOL_PATTERN_RE = /^([\\w-]+)(?:\\(([^)]*)\\))?$/\n\n// Path-sandbox regexes (hoisted to avoid per-call recompilation).\nconst ABS_WINDOWS_PATH_RE = /^[a-z]:[\\\\/]/i\nconst PATH_SEPARATOR_RE = /[\\\\/]/\nconst TRAILING_SLASHES_RE = /\\/+$/\n\n// ---------------------------------------------------------------------------\n// Validation result\n// ---------------------------------------------------------------------------\n\n/**\n * A single issue surfaced by skill-authoring validation.\n * Distinct from `ValidationResult` in `tools/validation.ts` (tool-input JSON\n * schema validation) — different domain, different module.\n */\nexport interface SkillValidationIssue {\n /** Stable machine-readable code for consumer matching. */\n code: string\n /** Human-readable description. */\n message: string\n /** Frontmatter field name the issue relates to, when applicable. */\n field?: string\n}\n\nexport interface SkillValidationResult {\n valid: boolean\n errors: SkillValidationIssue[]\n}\n\n// ---------------------------------------------------------------------------\n// Name validation (strict — spec rules)\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a skill name per the spec:\n * - 1–64 characters\n * - Lowercase alphanumeric + hyphens only\n * - Must not start or end with a hyphen\n * - Must not contain consecutive hyphens\n *\n * The parent-directory match is validated separately (it requires knowing the\n * skill's `location`, which this function does not).\n */\nexport function validateSkillName(name: string): boolean {\n if (typeof name !== 'string')\n return false\n if (name.length < 1 || name.length > NAME_MAX)\n return false\n if (CONSECUTIVE_HYPHENS_RE.test(name))\n return false\n return SKILL_NAME_RE.test(name)\n}\n\n// ---------------------------------------------------------------------------\n// Authoring-time validation (`defineSkill`, `writeSkillToDisk`)\n// ---------------------------------------------------------------------------\n\n/**\n * Strict validation for a skill that is about to be authored / written to disk.\n * Rejects anything that would violate the spec. Use the lenient parser path\n * (`parseSkillFile`) for loading third-party skills.\n */\nexport function validateSkillForWrite(skill: SkillConfig): SkillValidationResult {\n const errors: SkillValidationIssue[] = []\n\n // Name\n if (!validateSkillName(skill.name)) {\n errors.push({\n code: 'invalid-name',\n message: `Skill name \"${skill.name}\" must be 1-64 chars, lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens.`,\n field: 'name',\n })\n }\n\n // Name must match parent directory when location is present\n if (skill.location) {\n const dirName = basename(skill.baseDir ?? '')\n if (dirName && dirName !== skill.name) {\n errors.push({\n code: 'name-mismatch-directory',\n message: `Skill name \"${skill.name}\" must match parent directory name \"${dirName}\".`,\n field: 'name',\n })\n }\n }\n\n // Description\n if (typeof skill.description !== 'string' || skill.description.length < 1) {\n errors.push({\n code: 'missing-description',\n message: 'Skill description is required (non-empty).',\n field: 'description',\n })\n }\n else if (skill.description.length > DESCRIPTION_MAX) {\n errors.push({\n code: 'description-too-long',\n message: `Skill description must be at most ${DESCRIPTION_MAX} characters (got ${skill.description.length}).`,\n field: 'description',\n })\n }\n\n // Compatibility (optional)\n if (skill.compatibility !== undefined) {\n if (typeof skill.compatibility !== 'string' || skill.compatibility.length === 0) {\n errors.push({\n code: 'invalid-compatibility',\n message: 'Compatibility must be a non-empty string when provided.',\n field: 'compatibility',\n })\n }\n else if (skill.compatibility.length > COMPATIBILITY_MAX) {\n errors.push({\n code: 'compatibility-too-long',\n message: `Compatibility must be at most ${COMPATIBILITY_MAX} characters (got ${skill.compatibility.length}).`,\n field: 'compatibility',\n })\n }\n }\n\n // Metadata values must be strings per spec\n if (skill.metadata) {\n for (const [key, value] of Object.entries(skill.metadata)) {\n if (typeof value !== 'string') {\n errors.push({\n code: 'invalid-metadata-value',\n message: `Metadata value for \"${key}\" must be a string (spec: \"A map from string keys to string values\").`,\n field: 'metadata',\n })\n }\n }\n }\n\n // Allowed-tools entries must be parseable patterns\n if (skill.allowedTools) {\n for (const pattern of skill.allowedTools) {\n if (!ALLOWED_TOOL_PATTERN_RE.test(pattern)) {\n errors.push({\n code: 'invalid-allowed-tool-pattern',\n message: `Allowed-tools entry \"${pattern}\" is not a recognized pattern (expected \"ToolName\" or \"ToolName(arg:*)\").`,\n field: 'allowed-tools',\n })\n }\n }\n }\n\n return { valid: errors.length === 0, errors }\n}\n\n// ---------------------------------------------------------------------------\n// Resource path sandbox\n// ---------------------------------------------------------------------------\n\n/**\n * Validate that `relPath` stays inside `baseDir` when resolved.\n *\n * Rejects:\n * - Absolute paths (`/etc/passwd`, `C:\\…`)\n * - Parent traversal (`..` segments that escape baseDir)\n * - Null-byte tricks\n *\n * Returns `{ valid: true, absolutePath }` on success or `{ valid: false, error }`\n * with a human-readable reason.\n */\nexport function validateResourcePath(\n relPath: string,\n baseDir: string,\n): { valid: true, absolutePath: string } | { valid: false, error: string } {\n if (typeof relPath !== 'string' || relPath.length === 0)\n return { valid: false, error: 'Resource path must be a non-empty string.' }\n\n if (relPath.includes('\\0'))\n return { valid: false, error: 'Resource path contains a null byte.' }\n\n // Reject absolute paths upfront — both POSIX and Windows forms.\n if (relPath.startsWith('/') || ABS_WINDOWS_PATH_RE.test(relPath))\n return { valid: false, error: `Absolute paths are not allowed (\"${relPath}\").` }\n\n // Normalize and ensure we stay under baseDir.\n const segments: string[] = []\n for (const segment of relPath.split(PATH_SEPARATOR_RE)) {\n if (segment === '' || segment === '.')\n continue\n if (segment === '..') {\n if (segments.length === 0) {\n return {\n valid: false,\n error: `Resource path \"${relPath}\" escapes the skill directory.`,\n }\n }\n segments.pop()\n continue\n }\n segments.push(segment)\n }\n\n if (segments.length === 0)\n return { valid: false, error: 'Resource path resolves to the skill root itself.' }\n\n const absolutePath = `${baseDir.replace(TRAILING_SLASHES_RE, '')}/${segments.join('/')}`\n return { valid: true, absolutePath }\n}\n\n// ---------------------------------------------------------------------------\n// Allowed-tools matching\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a single `allowed-tools` entry into its tool name + optional argument pattern.\n *\n * Examples:\n * - `Read` → `{ tool: 'Read' }`\n * - `Bash(git:*)` → `{ tool: 'Bash', argPrefix: 'git' }`\n */\nexport function parseAllowedToolPattern(entry: string): { tool: string, argPrefix?: string } | null {\n const m = entry.trim().match(ALLOWED_TOOL_PATTERN_RE)\n if (!m)\n return null\n const tool = m[1]\n const arg = m[2]\n if (!arg)\n return { tool }\n\n // Accept `git:*` (prefix wildcard) and `git` (literal). Future: full glob.\n if (arg.endsWith(':*'))\n return { tool, argPrefix: arg.slice(0, -2) }\n return { tool, argPrefix: arg }\n}\n\n/**\n * Check whether a tool call (identified by its wire/displayName and argument input)\n * matches a skill's allow-list entry.\n *\n * Matching rules (Zidane's interpretation of the spec's unspecified syntax):\n * - Exact match: displayName === pattern (no parens).\n * - Prefix match: for `Tool(arg:*)`, displayName === 'Tool' AND **any** string\n * value in the input object starts with `arg`. This is intentionally\n * permissive: it doesn't depend on a convention about which property carries\n * the \"command\" (schemas vary across tools), and for the common\n * `shell({ command: 'git …' })` shape it behaves identically to a \"primary\n * string\" rule.\n * - For tools whose inputs carry no string values, the arg-match returns false.\n */\nexport function matchesAllowedTool(\n displayName: string,\n input: Record<string, unknown>,\n pattern: string,\n): boolean {\n const parsed = parseAllowedToolPattern(pattern)\n if (!parsed)\n return false\n if (parsed.tool !== displayName)\n return false\n if (parsed.argPrefix === undefined)\n return true\n\n // Match if any string input value starts with the arg prefix. More robust\n // than picking a single \"first\" value (object property order is a subtle\n // brittleness surface we avoid).\n for (const value of Object.values(input)) {\n if (typeof value === 'string' && value.startsWith(parsed.argPrefix))\n return true\n }\n return false\n}\n\n/**\n * Test whether a tool call is allowed by the union of `allowedTools` across a set\n * of active skills. Returns `true` when the union is empty (permissive default)\n * OR when any entry matches.\n */\nexport function isToolAllowedByUnion(\n displayName: string,\n input: Record<string, unknown>,\n union: readonly string[],\n): boolean {\n if (union.length === 0)\n return true\n return union.some(pattern => matchesAllowedTool(displayName, input, pattern))\n}\n","/**\n * `allowed-tools` enforcement middleware.\n *\n * Installed by the agent on `tool:gate` and `mcp:tool:gate`. Computes the\n * union of `allowedTools` patterns across currently-active skills; blocks any\n * tool call whose `displayName` is not in that union. The three injected\n * skills tools (`skills_use` / `skills_read` / `skills_run_script`) are\n * implicitly allowed so a skill declaring an allow-list cannot lock out the\n * scaffolding that lets it run at all.\n *\n * The match target is `displayName` (wire / LLM-facing name), not the\n * canonical name, because skill authors write allow-list entries to match\n * what the model sees.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from '../agent'\nimport type { McpToolHookContext, ToolHookContext } from '../types'\nimport type { SkillActivationState } from './activation'\nimport { AgentToolNotAllowedError } from '../errors'\nimport { isToolAllowedByUnion } from './validate'\n\n/** Tools that are always allowed regardless of the active skills' allow-list. */\nexport const IMPLICITLY_ALLOWED_SKILL_TOOLS: readonly string[] = [\n 'skills_use',\n 'skills_read',\n 'skills_run_script',\n]\n\n/**\n * Register `tool:gate` / `mcp:tool:gate` handlers that enforce the union of\n * `allowedTools` across active skills.\n *\n * No-op when no active skill declares an allow-list (permissive default —\n * matches the spec's \"experimental\" note for `allowed-tools`).\n *\n * Returns an `uninstall` fn. The agent calls this at run end to detach the\n * handlers and prevent cross-run hook leaks.\n */\nexport function installAllowedToolsGate(\n hooks: Hookable<AgentHooks>,\n state: SkillActivationState,\n): () => void {\n function effectiveUnion(): { union: string[], active: string[] } {\n const active = state.active()\n const declared: string[] = []\n for (const record of active) {\n if (record.skill.allowedTools?.length)\n declared.push(...record.skill.allowedTools)\n }\n return {\n union: declared.length > 0 ? [...declared, ...IMPLICITLY_ALLOWED_SKILL_TOOLS] : [],\n active: active.map(a => a.skill.name),\n }\n }\n\n function gateHandler(ctx: ToolHookContext & { block: boolean, reason: string }) {\n // Honor a prior gate's refusal — keeps the gate stack order-independent.\n // Without this, a later-registered gate could silently overwrite an\n // earlier refusal's reason on the same call.\n if (ctx.block)\n return\n const { union, active } = effectiveUnion()\n if (union.length === 0)\n return\n if (isToolAllowedByUnion(ctx.displayName, ctx.input, union))\n return\n const err = new AgentToolNotAllowedError({\n toolName: ctx.name,\n displayName: ctx.displayName,\n allowedUnion: union,\n activeSkills: active,\n })\n ctx.block = true\n ctx.reason = err.message\n }\n\n function mcpGateHandler(ctx: McpToolHookContext & { block: boolean, reason: string }) {\n if (ctx.block)\n return\n const { union, active } = effectiveUnion()\n if (union.length === 0)\n return\n if (isToolAllowedByUnion(ctx.displayName, ctx.input, union))\n return\n const err = new AgentToolNotAllowedError({\n toolName: `mcp_${ctx.server}_${ctx.tool}`,\n displayName: ctx.displayName,\n allowedUnion: union,\n activeSkills: active,\n })\n ctx.block = true\n ctx.reason = err.message\n }\n\n const unregisterTool = hooks.hook('tool:gate', gateHandler)\n const unregisterMcp = hooks.hook('mcp:tool:gate', mcpGateHandler)\n\n return function uninstall() {\n unregisterTool()\n unregisterMcp()\n }\n}\n","/**\n * Minimal XML helpers used by catalog/result builders that emit\n * model-facing pseudo-XML (skills catalog, searchable_tools, tool_search\n * results, skill_content wrappers).\n *\n * Scope: attribute / text-node escaping only — we do NOT emit a full XML\n * document, the model sees these strings as free-form text. Centralised\n * here so the four near-identical copies in `agent.ts`, `skills/catalog.ts`,\n * `tools/skills-use.ts`, and `tools/tool-search.ts` don't drift.\n */\n\nconst RE_AMP = /&/g\nconst RE_LT = /</g\nconst RE_GT = />/g\nconst RE_QUOT = /\"/g\n\nexport function escapeXml(str: string): string {\n return str\n .replace(RE_AMP, '&amp;')\n .replace(RE_LT, '&lt;')\n .replace(RE_GT, '&gt;')\n .replace(RE_QUOT, '&quot;')\n}\n","/**\n * Skill catalog generation.\n *\n * Builds the system prompt section that discloses available skills to the\n * agent — tier 1 of progressive disclosure per the Agent Skills spec.\n *\n * The catalog's behavioral prose branches on which activation mechanism is\n * active for the run:\n *\n * - When the `skills_use` tool is auto-injected (the default for a non-empty\n * catalog), the prose tells the model to call `skills_use` with the skill's\n * name. The agent returns a structured `<skill_content>` block.\n * - When the skills tool is opted out (`SkillsConfig.tool: false`), the prose\n * falls back to the file-read pattern — the model reads `SKILL.md` itself\n * via its file-read tool.\n */\n\nimport type { SkillConfig } from './types'\nimport { escapeXml } from '../xml'\n\nexport interface BuildCatalogOptions {\n /**\n * When true (the default), the prose instructs the model to call\n * `skills_use`. Set to false when `SkillsConfig.tool === false` so the\n * system prompt matches the active activation mechanism.\n */\n skillsToolRegistered?: boolean\n /**\n * Name of the tool the model should use to read `SKILL.md` files when\n * `skillsToolRegistered` is false. Defaults to `'read_file'`.\n */\n readToolName?: string\n}\n\n/**\n * Build the skill catalog XML and behavioral instructions for the system prompt.\n */\nexport function buildCatalog(\n skills: SkillConfig[],\n options: BuildCatalogOptions = {},\n): string {\n if (skills.length === 0)\n return ''\n\n const skillsToolRegistered = options.skillsToolRegistered ?? true\n const readToolName = options.readToolName ?? 'read_file'\n\n const entries = skills.map((skill) => {\n const locationLine = skill.location\n ? `\\n <location>${escapeXml(skill.location)}</location>`\n : ''\n return ` <skill>\n <name>${escapeXml(skill.name)}</name>\n <description>${escapeXml(skill.description)}</description>${locationLine}\n </skill>`\n }).join('\\n')\n\n // Separate instructions for fs-based vs inline skills\n const hasFsSkills = skills.some(s => s.location)\n const hasInlineSkills = skills.some(s => !s.location)\n\n const behavioralParts: string[] = []\n\n behavioralParts.push(\n 'The following skills provide specialized instructions for specific tasks.',\n 'When a task matches a skill\\'s description, activate the skill to load its full instructions before proceeding.',\n )\n\n if (skillsToolRegistered) {\n behavioralParts.push(\n 'To activate a skill, call the `skills_use` tool with the skill\\'s name. '\n + 'The response contains the full instructions and any bundled resources you can then load '\n + 'via `skills_read` (reference files) or execute via `skills_run_script` (scripts/ directory).',\n 'Relative paths referenced in a skill\\'s instructions resolve against the skill directory noted in the `skills_use` response.',\n )\n }\n else if (hasFsSkills) {\n behavioralParts.push(\n `For skills with a <location>, use the ${readToolName} tool to read the SKILL.md file at that path.`,\n 'When a skill references relative paths, resolve them against the skill\\'s directory (the parent of SKILL.md) and use absolute paths in tool calls.',\n )\n }\n\n if (hasInlineSkills && !skillsToolRegistered) {\n behavioralParts.push(\n 'Skills without a <location> have their instructions included directly in <instructions> tags below.',\n )\n }\n\n // Build the full catalog block\n const parts: string[] = [\n behavioralParts.join('\\n'),\n '',\n '<available_skills>',\n entries,\n '</available_skills>',\n ]\n\n // Append inline skill instructions directly — but only when the model has no\n // other way to reach them (`skills_use` also handles inline skills when\n // registered, so we skip the duplicate copy in that case).\n if (hasInlineSkills && !skillsToolRegistered) {\n parts.push('')\n for (const skill of skills) {\n if (!skill.location && skill.instructions) {\n parts.push(`<skill_instructions name=\"${escapeXml(skill.name)}\">`)\n parts.push(skill.instructions)\n if (skill.resources && skill.resources.length > 0) {\n parts.push('')\n parts.push('<skill_resources>')\n for (const res of skill.resources) {\n parts.push(` <file type=\"${res.type}\">${escapeXml(res.path)}</file>`)\n }\n parts.push('</skill_resources>')\n }\n parts.push('</skill_instructions>')\n }\n }\n }\n\n return parts.join('\\n')\n}\n","/**\n * Skill discovery and parsing.\n *\n * Scans filesystem directories for SKILL.md files following the\n * Agent Skills specification (agentskills.io/specification).\n *\n * Parsing is intentionally **lenient** — per the spec's client-implementation\n * guide: warn, don't block. Diagnostics are attached to the returned\n * SkillConfig for host UIs to surface; only unrecoverable errors (missing\n * description, unparseable YAML) skip the skill entirely.\n */\n\nimport type { SkillConfig, SkillDiagnostic, SkillResource, SkillSource } from './types'\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport { validateSkillName } from './validate'\n\n// ---------------------------------------------------------------------------\n// Regexes\n// ---------------------------------------------------------------------------\n\n// Frontmatter regex — opening ---, content, closing ---, optional body.\n// Tolerates CRLF line endings on the opening and closing fences (authoring\n// tools on Windows sometimes persist them mid-file).\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/\n\nconst INDENT_RE = /^[ \\t]{2,}/\nconst KV_RE = /^([^:]+):(.*)$/\nconst DOUBLE_QUOTED_RE = /^\"((?:\\\\.|[^\"\\\\])*)\"$/\nconst SINGLE_QUOTED_RE = /^'((?:''|[^'])*)'$/\nconst DQ_ESCAPE_RE = /\\\\([\"\\\\/bfnrt])/g\nconst WHITESPACE_SPLIT_RE = /\\s+/\nconst PARAGRAPH_SPLIT_RE = /\\n\\n/\nconst COMMA_OR_SPACE_RE = /[,\\s]+/\n\n// ---------------------------------------------------------------------------\n// Frontmatter parsing\n// ---------------------------------------------------------------------------\n\ninterface ParsedSkillFile {\n frontmatter: Record<string, unknown>\n body: string\n diagnostics: SkillDiagnostic[]\n}\n\n/**\n * Parse a SKILL.md file into frontmatter + body.\n *\n * Uses a simple regex-based YAML extractor that handles:\n * - Flat key: value pairs\n * - Quoted values\n * - One-level nested maps (for `metadata:`)\n * - Lenient recovery from unquoted-colon values (e.g.\n * `description: Use when: the user asks …`) via a quote-wrap retry.\n */\nexport function parseFrontmatter(content: string): ParsedSkillFile {\n const diagnostics: SkillDiagnostic[] = []\n const match = content.match(FRONTMATTER_RE)\n if (!match) {\n return { frontmatter: {}, body: content.trim(), diagnostics }\n }\n\n const yamlBlock = match[1]\n const body = match[2].trim()\n\n // Simple YAML parser for flat and one-level nested key-value pairs\n const frontmatter: Record<string, unknown> = {}\n let currentKey: string | null = null\n let currentMap: Record<string, string> | null = null\n\n for (const line of yamlBlock.split('\\n')) {\n // Skip empty lines and comments\n if (!line.trim() || line.trim().startsWith('#'))\n continue\n\n // Nested key (indented under a map key)\n if (currentKey && currentMap && INDENT_RE.test(line)) {\n const nestedMatch = line.trim().match(KV_RE)\n if (nestedMatch) {\n const val = nestedMatch[2].trim()\n currentMap[nestedMatch[1].trim()] = unquoteYaml(val)\n }\n continue\n }\n\n // Top-level key\n if (currentKey && currentMap) {\n frontmatter[currentKey] = currentMap\n currentKey = null\n currentMap = null\n }\n\n // Split on first colon only — lenient for values containing colons.\n const kvMatch = matchFirstColon(line)\n if (!kvMatch)\n continue\n\n const key = kvMatch.key.trim()\n const rawValue = kvMatch.value.trim()\n\n if (!rawValue) {\n // Possibly a map — next lines will be indented\n currentKey = key\n currentMap = {}\n }\n else {\n frontmatter[key] = unquoteYaml(rawValue)\n }\n }\n\n // Flush any pending map\n if (currentKey && currentMap) {\n frontmatter[currentKey] = currentMap\n }\n\n return { frontmatter, body, diagnostics }\n}\n\nfunction matchFirstColon(line: string): { key: string, value: string } | null {\n const idx = line.indexOf(':')\n if (idx < 0)\n return null\n const key = line.slice(0, idx)\n const value = line.slice(idx + 1)\n // Reject accidental matches where key contains spaces before the colon wider\n // than a normal YAML key (defensive against \"some prose: foo\" in body leak).\n if (!KV_RE.test(`${key}:`))\n return null\n return { key, value }\n}\n\n/**\n * Strip outer quotes and decode YAML-style escapes.\n *\n * - Double-quoted values honor `\\\"`, `\\\\`, `\\n`, `\\r`, `\\t`, `\\b`, `\\f`, `\\/`.\n * - Single-quoted values honor `''` (the YAML single-quote escape for a literal `'`).\n * - Trailing inline comments on unquoted values (` # comment`) are dropped.\n * - Unquoted values are returned as-is (trimmed by caller).\n *\n * Skill frontmatter in the wild uses a narrow YAML subset — this is\n * sufficient for every field in the current spec and the common authoring\n * tools. Block scalars (`|`, `>`) and flow sequences are intentionally left\n * unsupported: they'd only appear in a bespoke workflow, and silently\n * half-supporting them is worse than rejecting them obviously.\n */\nfunction unquoteYaml(val: string): string {\n const dq = val.match(DOUBLE_QUOTED_RE)\n if (dq) {\n return dq[1].replace(DQ_ESCAPE_RE, (_, ch) => {\n switch (ch) {\n case '\"': return '\"'\n case '\\\\': return '\\\\'\n case '/': return '/'\n case 'b': return '\\b'\n case 'f': return '\\f'\n case 'n': return '\\n'\n case 'r': return '\\r'\n case 't': return '\\t'\n default: return ch\n }\n })\n }\n const sq = val.match(SINGLE_QUOTED_RE)\n if (sq) {\n return sq[1].replace(/''/g, '\\'')\n }\n // Strip trailing inline comment only when clearly separated (\"value # note\"\n // is a comment; \"value#tag\" is a literal). Preserves content on values that\n // legitimately contain `#` as part of text.\n const hashIdx = val.indexOf(' #')\n if (hashIdx >= 0)\n return val.slice(0, hashIdx).trimEnd()\n return val\n}\n\n/**\n * Narrow a frontmatter field to a string. Produces a diagnostic when present\n * but not a string, so malformed YAML (e.g. `name: 123`) surfaces as a\n * warning instead of silently coerced downstream via `as string`.\n */\nfunction takeString(\n frontmatter: Record<string, unknown>,\n key: string,\n diagnostics: SkillDiagnostic[],\n): string | undefined {\n const raw = frontmatter[key]\n if (raw === undefined || raw === null)\n return undefined\n if (typeof raw === 'string')\n return raw\n diagnostics.push({\n severity: 'warning',\n code: 'invalid-field-type',\n message: `Frontmatter field \"${key}\" expected string, got ${typeof raw}. Coerced.`,\n field: key,\n })\n return String(raw)\n}\n\n// ---------------------------------------------------------------------------\n// Resource enumeration\n// ---------------------------------------------------------------------------\n\nconst RESOURCE_DIRS: Record<string, SkillResource['type']> = {\n scripts: 'script',\n references: 'reference',\n assets: 'asset',\n}\n\nfunction enumerateResources(baseDir: string): SkillResource[] {\n const resources: SkillResource[] = []\n\n for (const [dir, type] of Object.entries(RESOURCE_DIRS)) {\n const dirPath = join(baseDir, dir)\n if (!existsSync(dirPath) || !statSync(dirPath).isDirectory())\n continue\n try {\n // `readdirSync` with `recursive: true` returns `string | Buffer`; we\n // opt into the `encoding: 'utf8'` overload via `withFileTypes: false`\n // default and string-encoded filenames. Guard the Buffer case in case\n // a platform quirk (non-UTF8 filenames on Linux) slips through.\n const files = readdirSync(dirPath, { recursive: true })\n for (const file of files) {\n const rel = typeof file === 'string' ? file : file.toString('utf-8')\n const fullPath = join(dirPath, rel)\n if (statSync(fullPath).isFile()) {\n resources.push({ path: join(dir, rel), type })\n }\n }\n }\n catch {\n // Skip unreadable directories\n }\n }\n\n // Also enumerate other files in the root (excluding SKILL.md)\n try {\n for (const entry of readdirSync(baseDir)) {\n if (entry === 'SKILL.md')\n continue\n const entryPath = join(baseDir, entry)\n if (statSync(entryPath).isFile()) {\n resources.push({ path: entry, type: 'other' })\n }\n }\n }\n catch {\n // Skip unreadable root\n }\n\n return resources\n}\n\n// ---------------------------------------------------------------------------\n// Parse a single SKILL.md file\n// ---------------------------------------------------------------------------\n\ninterface ParseSkillOptions {\n /** Source tag to attach to the returned SkillConfig. */\n source?: SkillSource\n}\n\n/**\n * Parse a SKILL.md file into a SkillConfig (lenient).\n *\n * Returns `null` only when the skill is fundamentally unusable:\n * - The file is missing\n * - The description is absent (required by spec for disclosure)\n *\n * All other issues are surfaced as `SkillConfig.diagnostics` with severity\n * `warning`. Deprecated top-level fields (`paths`, `model`, `thinking`) are\n * auto-migrated into `metadata['zidane.*']`.\n */\nexport async function parseSkillFile(\n filePath: string,\n options: ParseSkillOptions = {},\n): Promise<SkillConfig | null> {\n const absPath = resolve(filePath)\n if (!existsSync(absPath))\n return null\n\n const content = readFileSync(absPath, 'utf-8')\n const { frontmatter, body, diagnostics } = parseFrontmatter(content)\n\n // Description: frontmatter > first paragraph of body > skip\n let description = takeString(frontmatter, 'description', diagnostics)\n if (!description && body) {\n const firstParagraph = body.split(PARAGRAPH_SPLIT_RE)[0]?.trim()\n if (firstParagraph)\n description = firstParagraph\n }\n if (!description)\n return null\n\n if (description.length > 1024) {\n diagnostics.push({\n severity: 'warning',\n code: 'description-too-long',\n message: `Description exceeds spec limit of 1024 characters (got ${description.length}). Loading anyway.`,\n field: 'description',\n })\n }\n\n const baseDir = dirname(absPath)\n const dirName = basename(baseDir)\n const frontmatterName = takeString(frontmatter, 'name', diagnostics)\n const name = frontmatterName || dirName\n\n // Lenient name checks\n if (frontmatterName && frontmatterName !== dirName) {\n diagnostics.push({\n severity: 'warning',\n code: 'name-mismatch-directory',\n message: `Skill name \"${frontmatterName}\" does not match parent directory \"${dirName}\". Loading anyway.`,\n field: 'name',\n })\n }\n if (name.length > 64) {\n diagnostics.push({\n severity: 'warning',\n code: 'name-too-long',\n message: `Skill name \"${name}\" exceeds spec limit of 64 characters. Loading anyway.`,\n field: 'name',\n })\n }\n if (!validateSkillName(name)) {\n diagnostics.push({\n severity: 'warning',\n code: 'invalid-name-format',\n message: `Skill name \"${name}\" does not match spec format (lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens). Loading anyway.`,\n field: 'name',\n })\n }\n\n const config: SkillConfig = {\n name,\n description,\n instructions: body,\n source: options.source ?? 'project',\n location: absPath,\n baseDir,\n resources: enumerateResources(baseDir),\n }\n\n const license = takeString(frontmatter, 'license', diagnostics)\n if (license)\n config.license = license\n\n const compatibility = takeString(frontmatter, 'compatibility', diagnostics)\n if (compatibility) {\n if (compatibility.length > 500) {\n diagnostics.push({\n severity: 'warning',\n code: 'compatibility-too-long',\n message: `Compatibility exceeds spec limit of 500 characters (got ${compatibility.length}). Loading anyway.`,\n field: 'compatibility',\n })\n }\n config.compatibility = compatibility\n }\n\n // Metadata — spec-compliant flat bag\n const metadata: Record<string, string> = {}\n const rawMetadata = frontmatter.metadata\n if (rawMetadata && typeof rawMetadata === 'object' && !Array.isArray(rawMetadata)) {\n for (const [k, v] of Object.entries(rawMetadata as Record<string, unknown>)) {\n if (typeof v !== 'string') {\n diagnostics.push({\n severity: 'warning',\n code: 'invalid-metadata-value',\n message: `Metadata value for \"${k}\" is not a string; coerced. (Spec requires string values.)`,\n field: 'metadata',\n })\n metadata[k] = String(v)\n continue\n }\n metadata[k] = v\n }\n }\n else if (rawMetadata !== undefined) {\n diagnostics.push({\n severity: 'warning',\n code: 'invalid-metadata-shape',\n message: `Frontmatter \"metadata\" expected a map, got ${Array.isArray(rawMetadata) ? 'array' : typeof rawMetadata}. Ignored.`,\n field: 'metadata',\n })\n }\n\n // Auto-migrate deprecated top-level fields into metadata.zidane.*\n const pathsField = takeString(frontmatter, 'paths', diagnostics)\n if (pathsField) {\n metadata['zidane.paths'] = pathsField.split(COMMA_OR_SPACE_RE).filter(Boolean).join(',')\n diagnostics.push({\n severity: 'warning',\n code: 'deprecated-top-level-field',\n message: '`paths` is not a spec field and is deprecated — moved to `metadata[\"zidane.paths\"]`.',\n field: 'paths',\n })\n }\n const modelField = takeString(frontmatter, 'model', diagnostics)\n if (modelField) {\n metadata['zidane.model'] = modelField\n diagnostics.push({\n severity: 'warning',\n code: 'deprecated-top-level-field',\n message: '`model` is not a spec field and is deprecated — moved to `metadata[\"zidane.model\"]`.',\n field: 'model',\n })\n }\n const thinkingField = takeString(frontmatter, 'thinking', diagnostics)\n const effortField = thinkingField ? undefined : takeString(frontmatter, 'effort', diagnostics)\n const legacyThinking = thinkingField ?? effortField\n if (legacyThinking) {\n metadata['zidane.thinking'] = legacyThinking\n diagnostics.push({\n severity: 'warning',\n code: 'deprecated-top-level-field',\n message: `\\`${thinkingField ? 'thinking' : 'effort'}\\` is not a spec field and is deprecated — moved to \\`metadata[\"zidane.thinking\"]\\`.`,\n field: thinkingField ? 'thinking' : 'effort',\n })\n }\n\n if (Object.keys(metadata).length > 0)\n config.metadata = metadata\n\n const allowedTools = takeString(frontmatter, 'allowed-tools', diagnostics)\n if (allowedTools) {\n config.allowedTools = allowedTools.split(WHITESPACE_SPLIT_RE).filter(Boolean)\n }\n\n if (diagnostics.length > 0)\n config.diagnostics = diagnostics\n\n return config\n}\n\n// ---------------------------------------------------------------------------\n// Directory scanning\n// ---------------------------------------------------------------------------\n\nconst SKIP_DIRS = new Set(['.git', 'node_modules', '.DS_Store', 'dist', 'build'])\n\nfunction findSkillDirs(root: string, maxDepth = 4, _depth = 0): string[] {\n if (_depth > maxDepth)\n return []\n if (!existsSync(root) || !statSync(root).isDirectory())\n return []\n\n const results: string[] = []\n try {\n for (const entry of readdirSync(root)) {\n if (SKIP_DIRS.has(entry))\n continue\n const entryPath = join(root, entry)\n if (!statSync(entryPath).isDirectory())\n continue\n\n const skillFile = join(entryPath, 'SKILL.md')\n if (existsSync(skillFile) && statSync(skillFile).isFile()) {\n results.push(skillFile)\n }\n else {\n // Recurse deeper\n results.push(...findSkillDirs(entryPath, maxDepth, _depth + 1))\n }\n }\n }\n catch {\n // Skip unreadable directories\n }\n return results\n}\n\n// ---------------------------------------------------------------------------\n// Default scan paths\n// ---------------------------------------------------------------------------\n\n/** A scan path paired with the source tag that should be attached to any skills found in it. */\nexport interface SourcedScanPath {\n path: string\n source: SkillSource\n}\n\n/**\n * Return the default scan paths tagged by source. Project-scope paths come\n * first; their skills therefore win on name collisions against user-scope\n * skills (first-found wins in discovery).\n */\nexport function getDefaultScanPaths(): SourcedScanPath[] {\n const home = homedir()\n const cwd = process.cwd()\n return [\n // Project-level (higher priority)\n { path: join(cwd, '.agents', 'skills'), source: 'project' },\n { path: join(cwd, '.zidane', 'skills'), source: 'project' },\n // User-level (lower priority)\n { path: join(home, '.agents', 'skills'), source: 'user' },\n { path: join(home, '.zidane', 'skills'), source: 'user' },\n ]\n}\n\n/**\n * Infer a source tag for a user-provided scan path.\n * Paths under `$HOME` are treated as 'user'; everything else as 'project'.\n */\nexport function inferSource(path: string): SkillSource {\n return path.startsWith(homedir()) ? 'user' : 'project'\n}\n\n// ---------------------------------------------------------------------------\n// Discover skills from filesystem\n// ---------------------------------------------------------------------------\n\n/**\n * Discover skills from sourced filesystem paths.\n * Each path is scanned for subdirectories containing SKILL.md.\n * Earlier paths have higher priority (first-found wins on name collision).\n *\n * `signal` is checked between each `parseSkillFile` so a rapid `cwd` switch\n * in the host (TUI / GUI) can stop a long scan without leaking I/O. Aborts\n * reject with the signal's `reason` (or a default `AbortError`) — same shape\n * `AbortSignal.throwIfAborted()` produces in modern runtimes.\n */\nexport async function discoverSkills(\n paths: SourcedScanPath[],\n signal?: AbortSignal,\n): Promise<SkillConfig[]> {\n const skillsByName = new Map<string, SkillConfig>()\n\n for (const { path: scanPath, source } of paths) {\n signal?.throwIfAborted?.()\n const skillFiles = findSkillDirs(resolve(scanPath))\n for (const file of skillFiles) {\n signal?.throwIfAborted?.()\n const skill = await parseSkillFile(file, { source })\n if (!skill)\n continue\n if (skillsByName.has(skill.name)) {\n // First-found wins — append a collision diagnostic to the winning skill.\n const existing = skillsByName.get(skill.name)!\n const diag: SkillDiagnostic = {\n severity: 'warning',\n code: 'name-collision-shadowed',\n message: `A skill with name \"${skill.name}\" was also found at ${file} (source: ${source}); shadowed by ${existing.location} (source: ${existing.source}).`,\n }\n existing.diagnostics = [...(existing.diagnostics ?? []), diag]\n continue\n }\n skillsByName.set(skill.name, skill)\n }\n }\n\n return [...skillsByName.values()]\n}\n","/**\n * Skill writer — materializes inline SkillConfig objects to disk as proper\n * SKILL.md files so they participate fully in the filesystem-based skill system.\n *\n * Strict-validates each skill before writing (see `validateSkillForWrite`).\n */\n\nimport type { SkillConfig } from './types'\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { validateSkillForWrite } from './validate'\n\n// ---------------------------------------------------------------------------\n// Frontmatter serialization\n// ---------------------------------------------------------------------------\n\nconst YAML_RESERVED_RE = /[:#&*!|>%@`]/\nconst YAML_EDGE_OR_QUOTE_RE = /^\\s|\\s$|[\"']/\nconst DQUOTE_RE = /\"/g\nconst LEADING_NEWLINES_RE = /^\\n+/\n\nfunction yamlEscape(value: string): string {\n // Quote values that contain YAML-reserved characters (colons, quotes, leading\n // special chars). Keeps round-trip safety for descriptions like\n // \"Use when: the user asks …\".\n if (YAML_RESERVED_RE.test(value) || YAML_EDGE_OR_QUOTE_RE.test(value) || value === '')\n return `\"${value.replace(DQUOTE_RE, '\\\\\"')}\"`\n return value\n}\n\nfunction serializeFrontmatter(skill: SkillConfig): string {\n const lines: string[] = ['---']\n\n lines.push(`name: ${yamlEscape(skill.name)}`)\n lines.push(`description: ${yamlEscape(skill.description)}`)\n\n if (skill.license)\n lines.push(`license: ${yamlEscape(skill.license)}`)\n if (skill.compatibility)\n lines.push(`compatibility: ${yamlEscape(skill.compatibility)}`)\n if (skill.allowedTools?.length)\n lines.push(`allowed-tools: ${skill.allowedTools.join(' ')}`)\n\n if (skill.metadata && Object.keys(skill.metadata).length > 0) {\n lines.push('metadata:')\n for (const [key, value] of Object.entries(skill.metadata)) {\n // Always quote metadata values — preserves string-looking numerics (\"1.0\")\n // and string-valued booleans (\"true\") from YAML's implicit type coercion.\n // Dot-keys (e.g. `zidane.paths`) are accepted verbatim by YAML.\n lines.push(` ${key}: \"${value.replace(DQUOTE_RE, '\\\\\"')}\"`)\n }\n }\n\n lines.push('---')\n return lines.join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Write a single skill to disk\n// ---------------------------------------------------------------------------\n\n/**\n * Write a `SkillConfig` to disk as a proper skill directory with SKILL.md.\n * Returns the path to the written SKILL.md file.\n *\n * Throws if the skill fails `validateSkillForWrite` — the authoring path is\n * strict on purpose. For loading third-party skills, use `parseSkillFile`\n * directly (lenient).\n */\nexport function writeSkillToDisk(skill: SkillConfig, targetDir: string): string {\n const result = validateSkillForWrite(skill)\n if (!result.valid) {\n const summary = result.errors\n .map(e => ` - [${e.code}] ${e.message}`)\n .join('\\n')\n throw new Error(`Cannot write invalid skill \"${skill.name}\":\\n${summary}`)\n }\n\n const skillDir = join(targetDir, skill.name)\n mkdirSync(skillDir, { recursive: true })\n\n const frontmatter = serializeFrontmatter(skill)\n // Normalize: strip any leading newlines on the body so we never emit a\n // double-blank-line between the frontmatter fence and the instructions,\n // and always end the file with a single trailing newline.\n const bodyTrimmed = skill.instructions ? skill.instructions.replace(LEADING_NEWLINES_RE, '') : ''\n const content = bodyTrimmed\n ? `${frontmatter}\\n\\n${bodyTrimmed}\\n`\n : `${frontmatter}\\n`\n\n const skillPath = join(skillDir, 'SKILL.md')\n writeFileSync(skillPath, content)\n\n return skillPath\n}\n\n// ---------------------------------------------------------------------------\n// Write multiple skills to a directory\n// ---------------------------------------------------------------------------\n\n/**\n * Write multiple `SkillConfig` objects to a target directory.\n * Each skill gets its own subdirectory with a SKILL.md file.\n * Returns the target directory path (for use as a scan path).\n */\nexport function writeSkillsToDisk(skills: SkillConfig[], targetDir: string): string {\n mkdirSync(targetDir, { recursive: true })\n for (const skill of skills) {\n writeSkillToDisk(skill, targetDir)\n }\n return targetDir\n}\n","/**\n * Skill resolution — discovers filesystem skills, writes dynamic skills,\n * merges everything, and applies filtering.\n */\n\nimport type { SourcedScanPath } from './discovery'\nimport type { SkillConfig, SkillsConfig } from './types'\nimport { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { discoverSkills, getDefaultScanPaths, inferSource } from './discovery'\nimport { writeSkillsToDisk } from './writer'\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolved-skills bundle: the materialized list plus a `cleanup` fn that\n * removes any temporary directory created for `config.write` skills.\n *\n * Inline skills must live on disk for the lifetime of the agent (the\n * `skills_read` / `skills_run_script` tools resolve relative paths against\n * `baseDir`), so cleanup is deferred to `agent.destroy()`. When no temp\n * directory was created, `cleanup` is a no-op.\n */\nexport interface ResolvedSkillsBundle {\n skills: SkillConfig[]\n cleanup: () => void\n}\n\n/**\n * Resolve all skills from a SkillsConfig:\n *\n * 1. Materialize `config.write` entries to a temp directory, tagged as `inline`\n * 2. Combine with default + user-provided scan paths (project-first, user-next)\n * 3. Run lenient discovery\n * 4. Apply filters: `exclude`, `enabled` allowlist, optional project-skill trust gate\n *\n * Returns `{ skills, cleanup }` — call `cleanup()` on agent destroy to remove\n * the temp directory created for inline `config.write` skills. The agent\n * factory wires this automatically; standalone callers must invoke it\n * themselves to avoid leaking OS temp.\n */\nexport async function resolveSkills(config: SkillsConfig): Promise<ResolvedSkillsBundle> {\n const sourcedPaths: SourcedScanPath[] = []\n let writeDir: string | undefined\n\n if (!config.skipDefaultPaths) {\n sourcedPaths.push(...getDefaultScanPaths())\n }\n\n // User-provided scan paths: infer source from path prefix.\n for (const p of config.scan ?? []) {\n sourcedPaths.push({ path: p, source: inferSource(p) })\n }\n\n // Inline skills materialized to a temp directory and scanned as 'inline'.\n if (config.write?.length) {\n writeDir = mkdtempSync(join(tmpdir(), 'zidane-skills-'))\n writeSkillsToDisk(config.write, writeDir)\n sourcedPaths.push({ path: writeDir, source: 'inline' })\n }\n\n // Discover all skills from filesystem (first-found-wins on name collision).\n let skills = await discoverSkills(sourcedPaths)\n\n // Optional trust gate: drop project-scoped skills when the host hasn't\n // marked the project as trusted. Undefined source is treated as `'project'`\n // (conservative default — an untrusted environment should skip unclassified\n // skills rather than silently loading them).\n if (config.trustProjectSkills === false) {\n skills = skills.filter(s => s.source !== undefined && s.source !== 'project')\n }\n\n // Filter out `exclude`\n const exclude = new Set(config.exclude ?? [])\n let filtered = skills.filter(s => !exclude.has(s.name))\n\n // Apply `enabled` allowlist when it's an array of skill names\n if (Array.isArray(config.enabled)) {\n const allowlist = new Set(config.enabled)\n filtered = filtered.filter(s => allowlist.has(s.name))\n }\n\n const cleanup = writeDir\n ? () => {\n try {\n rmSync(writeDir!, { recursive: true, force: true })\n }\n catch {\n // Best-effort — temp directory may have been removed by the OS or\n // a sibling cleanup. Don't crash agent destroy.\n }\n }\n : () => {}\n\n return { skills: filtered, cleanup }\n}\n","/**\n * Shell interpolation for skill instructions.\n *\n * The `!\\`command\\`` syntax runs shell commands as a preprocessing step\n * before skill content is sent to the agent. The command output replaces\n * the placeholder — the agent only sees the final result.\n *\n * Example:\n * - PR diff: !\\`gh pr diff\\`\n * → - PR diff: <actual diff output>\n */\n\nimport type { ExecutionContext, ExecutionHandle } from '../contexts'\n\n/** Regex to match !`command` patterns in skill instructions */\nconst SHELL_INTERPOLATION_RE = /!`([^`]+)`/g\n\n/**\n * Interpolate shell commands in skill instructions.\n *\n * Runs each !\\`command\\` through the execution context and replaces\n * the placeholder with the command's stdout. If a command fails,\n * the placeholder is replaced with an error message.\n *\n * @param instructions - Raw skill instructions with potential !\\`command\\` patterns\n * @param execution - The execution context to run commands in\n * @param handle - The active execution handle\n * @returns Instructions with all !\\`command\\` patterns replaced by output\n */\nexport async function interpolateShellCommands(\n instructions: string,\n execution: ExecutionContext,\n handle: ExecutionHandle,\n): Promise<string> {\n const matches = [...instructions.matchAll(SHELL_INTERPOLATION_RE)]\n if (matches.length === 0)\n return instructions\n\n // Collect all commands with their positions for precise replacement\n const replacements: { index: number, length: number, output: string }[] = []\n\n for (const match of matches) {\n const command = match[1]\n const index = match.index!\n const length = match[0].length\n\n try {\n const result = await execution.exec(handle, command, { timeout: 30 })\n const output = result.exitCode === 0\n ? result.stdout.trim()\n : `[command failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}]`\n replacements.push({ index, length, output })\n }\n catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n replacements.push({ index, length, output: `[command error: ${message}]` })\n }\n }\n\n // Apply replacements in reverse order to preserve positions\n let result = instructions\n for (let i = replacements.length - 1; i >= 0; i--) {\n const { index, length, output } = replacements[i]\n result = result.slice(0, index) + output + result.slice(index + length)\n }\n\n return result\n}\n"],"mappings":"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"}
package/dist/mcp.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { d as McpConnection, f as connectMcpServers, h as resultToString, m as normalizeMcpServers, mt as McpServerConfig, p as normalizeMcpBlocks } from "./agent-CMIhYhDz.js";
1
+ import { f as McpConnection, g as resultToString, h as normalizeMcpServers, ht as McpServerConfig, m as normalizeMcpBlocks, p as connectMcpServers } from "./agent-JhicgLOV.js";
2
2
  export { McpConnection, McpServerConfig, connectMcpServers, normalizeMcpBlocks, normalizeMcpServers, resultToString };
@@ -0,0 +1,90 @@
1
+ import { a as multiEdit, i as readFile, n as createSpawnTool, o as listFiles, r as shell, t as writeFile, u as edit } from "./tools-CLazLRb4.js";
2
+ //#region src/presets/basic.ts
3
+ /**
4
+ * Core tools available in every basic preset (without spawn).
5
+ *
6
+ * `edit` and `multi_edit` ship in the basic set because surgical edits are the
7
+ * default modality for production agents — `write_file` is for full overwrites.
8
+ * `glob` and `grep` are exported but opt-in: not every agent needs codebase
9
+ * search, and shipping them by default would force `tool:gate` work onto
10
+ * consumers that prefer the model to use `shell` + classic Unix tools.
11
+ */
12
+ const basicTools = {
13
+ shell,
14
+ readFile,
15
+ writeFile,
16
+ listFiles,
17
+ edit,
18
+ multiEdit
19
+ };
20
+ var basic_default = definePreset({
21
+ name: "basic",
22
+ system: "You are a helpful assistant with access to shell, file reading, file writing, surgical and multi-edit tools, directory listing, and sub-agent spawning. Prefer `edit` / `multi_edit` for in-place changes and `write_file` for full file overwrites. Use them to accomplish tasks in the project directory.",
23
+ tools: {
24
+ ...basicTools,
25
+ spawn: createSpawnTool({ persist: true })
26
+ }
27
+ });
28
+ //#endregion
29
+ //#region src/presets/index.ts
30
+ /**
31
+ * Identity helper for type inference when defining a preset.
32
+ */
33
+ function definePreset(config) {
34
+ return config;
35
+ }
36
+ /**
37
+ * Field-aware composition of presets. Right-most preset wins for scalar fields;
38
+ * objects shallow-merge; arrays and hook handler lists concatenate. Designed so
39
+ * stacking presets does the obvious thing without the spread-collision footgun:
40
+ *
41
+ * - `name`, `system`, `eager`, `skills` → last-defined wins
42
+ * - `tools`, `toolAliases`, `behavior` → shallow-merge (later keys override)
43
+ * - `mcpServers` → concat with last-wins on `name` collision
44
+ * - `hooks` → per-event concat; every handler fires
45
+ *
46
+ * `hooks` always emerges as `event → handler[]` so downstream registration
47
+ * (in `createAgent`) sees a uniform shape. Order of handlers within an event
48
+ * follows preset order: earlier presets register first.
49
+ *
50
+ * `mcpServers` is deduped by `name` because shipping two servers with the same
51
+ * name would trip the connector at runtime — a later preset overriding an
52
+ * earlier preset's `github` server is the practical intent.
53
+ */
54
+ function composePresets(...presets) {
55
+ const out = {};
56
+ const hooksByEvent = {};
57
+ const mcpByName = /* @__PURE__ */ new Map();
58
+ for (const p of presets) {
59
+ if (p.name !== void 0) out.name = p.name;
60
+ if (p.system !== void 0) out.system = p.system;
61
+ if (p.eager !== void 0) out.eager = p.eager;
62
+ if (p.skills !== void 0) out.skills = p.skills;
63
+ if (p.tools) out.tools = {
64
+ ...out.tools,
65
+ ...p.tools
66
+ };
67
+ if (p.toolAliases) out.toolAliases = {
68
+ ...out.toolAliases,
69
+ ...p.toolAliases
70
+ };
71
+ if (p.behavior) out.behavior = {
72
+ ...out.behavior,
73
+ ...p.behavior
74
+ };
75
+ if (p.mcpServers) for (const server of p.mcpServers) mcpByName.set(server.name, server);
76
+ if (p.hooks) for (const [event, handler] of Object.entries(p.hooks)) {
77
+ if (handler === void 0) continue;
78
+ const list = Array.isArray(handler) ? handler : [handler];
79
+ const key = event;
80
+ (hooksByEvent[key] ??= []).push(...list);
81
+ }
82
+ }
83
+ if (mcpByName.size > 0) out.mcpServers = [...mcpByName.values()];
84
+ if (Object.keys(hooksByEvent).length > 0) out.hooks = hooksByEvent;
85
+ return out;
86
+ }
87
+ //#endregion
88
+ export { basic_default as i, definePreset as n, basicTools as r, composePresets as t };
89
+
90
+ //# sourceMappingURL=presets-BRFH2qsQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets-BRFH2qsQ.js","names":[],"sources":["../src/presets/basic.ts","../src/presets/index.ts"],"sourcesContent":["import { definePreset } from '.'\nimport { edit, listFiles, multiEdit, readFile, shell, writeFile } from '../tools'\nimport { createSpawnTool } from '../tools/spawn'\n\n/**\n * Core tools available in every basic preset (without spawn).\n *\n * `edit` and `multi_edit` ship in the basic set because surgical edits are the\n * default modality for production agents — `write_file` is for full overwrites.\n * `glob` and `grep` are exported but opt-in: not every agent needs codebase\n * search, and shipping them by default would force `tool:gate` work onto\n * consumers that prefer the model to use `shell` + classic Unix tools.\n */\nexport const basicTools = { shell, readFile, writeFile, listFiles, edit, multiEdit }\n\nexport default definePreset({\n name: 'basic',\n system: 'You are a helpful assistant with access to shell, file reading, file writing, surgical and multi-edit tools, directory listing, and sub-agent spawning. Prefer `edit` / `multi_edit` for in-place changes and `write_file` for full file overwrites. Use them to accomplish tasks in the project directory.',\n // `persist: true` shares the parent's session with every child agent — child\n // turns land in `session.turns` tagged with their own `runId`, and the run\n // itself is recorded in `session.runs` with `parentRunId` + `depth`. That's\n // what lets a reloaded TUI session reconstruct the full subagent tree (see\n // `eventsFromTurns` in `tui/store.ts`). Hosts that want children in-memory\n // only can construct their own preset with `createSpawnTool()`.\n tools: { ...basicTools, spawn: createSpawnTool({ persist: true }) },\n})\n","import type { AgentHooks, AgentOptions } from '../agent'\n\nexport type { AgentHookMap } from '../agent'\n\n/**\n * A preset is a reusable slice of `AgentOptions` — spread it into `createAgent()`\n * to configure tools, a default system prompt, aliases, behavior defaults, and\n * agent-lifetime hooks.\n *\n * `provider`, `execution`, `session`, and internal fields are excluded so presets\n * remain shareable and composable.\n *\n * ```ts\n * import { basic } from 'zidane/presets'\n * createAgent({ ...basic, provider })\n * ```\n *\n * ### Composing multiple presets\n *\n * Bare `...spread` is shallow — `{ ...a, ...b }` overwrites every key `b`\n * defines, including `hooks`. Use {@link composePresets} when you want\n * field-aware merging (per-event hook concat, tools shallow-merge, etc.):\n *\n * ```ts\n * createAgent({ ...composePresets(basic, telemetry, mine), provider })\n * ```\n */\nexport type Preset = Omit<Partial<AgentOptions>, 'provider' | 'execution' | 'session' | 'mcpConnector'>\n\n/**\n * Identity helper for type inference when defining a preset.\n */\nexport function definePreset(config: Preset): Preset {\n return config\n}\n\n/**\n * Field-aware composition of presets. Right-most preset wins for scalar fields;\n * objects shallow-merge; arrays and hook handler lists concatenate. Designed so\n * stacking presets does the obvious thing without the spread-collision footgun:\n *\n * - `name`, `system`, `eager`, `skills` → last-defined wins\n * - `tools`, `toolAliases`, `behavior` → shallow-merge (later keys override)\n * - `mcpServers` → concat with last-wins on `name` collision\n * - `hooks` → per-event concat; every handler fires\n *\n * `hooks` always emerges as `event → handler[]` so downstream registration\n * (in `createAgent`) sees a uniform shape. Order of handlers within an event\n * follows preset order: earlier presets register first.\n *\n * `mcpServers` is deduped by `name` because shipping two servers with the same\n * name would trip the connector at runtime — a later preset overriding an\n * earlier preset's `github` server is the practical intent.\n */\nexport function composePresets(...presets: Preset[]): Preset {\n const out: Preset = {}\n const hooksByEvent: { [K in keyof AgentHooks]?: AgentHooks[K][] } = {}\n // Keep mcpServers in source-order on first sight, but allow later\n // declarations to override earlier ones with the same `name`. A `Map`\n // keyed by name gives O(1) override + stable iteration.\n const mcpByName = new Map<string, NonNullable<Preset['mcpServers']>[number]>()\n\n for (const p of presets) {\n if (p.name !== undefined)\n out.name = p.name\n if (p.system !== undefined)\n out.system = p.system\n if (p.eager !== undefined)\n out.eager = p.eager\n if (p.skills !== undefined)\n out.skills = p.skills\n if (p.tools)\n out.tools = { ...out.tools, ...p.tools }\n if (p.toolAliases)\n out.toolAliases = { ...out.toolAliases, ...p.toolAliases }\n if (p.behavior)\n out.behavior = { ...out.behavior, ...p.behavior }\n if (p.mcpServers) {\n for (const server of p.mcpServers)\n mcpByName.set(server.name, server)\n }\n if (p.hooks) {\n for (const [event, handler] of Object.entries(p.hooks)) {\n if (handler === undefined)\n continue\n const list = Array.isArray(handler) ? handler : [handler]\n const key = event as keyof AgentHooks\n // Safe cast: we read the loose `AgentHookMap` shape (handler-or-array)\n // and re-emit only as arrays. Each `list` element matches the event's\n // handler signature by construction (the input was typed `AgentHookMap`).\n const bucket = (hooksByEvent[key] ??= []) as unknown[]\n bucket.push(...(list as unknown[]))\n }\n }\n }\n\n if (mcpByName.size > 0)\n out.mcpServers = [...mcpByName.values()]\n\n if (Object.keys(hooksByEvent).length > 0)\n out.hooks = hooksByEvent\n\n return out\n}\n\nexport { default as basic, basicTools } from './basic'\n"],"mappings":";;;;;;;;;;;AAaA,MAAa,aAAa;CAAE;CAAO;CAAU;CAAW;CAAW;CAAM;CAAW;AAEpF,IAAA,gBAAe,aAAa;CAC1B,MAAM;CACN,QAAQ;CAOR,OAAO;EAAE,GAAG;EAAY,OAAO,gBAAgB,EAAE,SAAS,MAAM,CAAC;EAAE;CACpE,CAAC;;;;;;ACOF,SAAgB,aAAa,QAAwB;CACnD,OAAO;;;;;;;;;;;;;;;;;;;;AAqBT,SAAgB,eAAe,GAAG,SAA2B;CAC3D,MAAM,MAAc,EAAE;CACtB,MAAM,eAA8D,EAAE;CAItE,MAAM,4BAAY,IAAI,KAAwD;CAE9E,KAAK,MAAM,KAAK,SAAS;EACvB,IAAI,EAAE,SAAS,KAAA,GACb,IAAI,OAAO,EAAE;EACf,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,UAAU,KAAA,GACd,IAAI,QAAQ,EAAE;EAChB,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,OACJ,IAAI,QAAQ;GAAE,GAAG,IAAI;GAAO,GAAG,EAAE;GAAO;EAC1C,IAAI,EAAE,aACJ,IAAI,cAAc;GAAE,GAAG,IAAI;GAAa,GAAG,EAAE;GAAa;EAC5D,IAAI,EAAE,UACJ,IAAI,WAAW;GAAE,GAAG,IAAI;GAAU,GAAG,EAAE;GAAU;EACnD,IAAI,EAAE,YACJ,KAAK,MAAM,UAAU,EAAE,YACrB,UAAU,IAAI,OAAO,MAAM,OAAO;EAEtC,IAAI,EAAE,OACJ,KAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,EAAE,MAAM,EAAE;GACtD,IAAI,YAAY,KAAA,GACd;GACF,MAAM,OAAO,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;GACzD,MAAM,MAAM;GAKZ,CADgB,aAAa,SAAS,EAAE,EACjC,KAAK,GAAI,KAAmB;;;CAKzC,IAAI,UAAU,OAAO,GACnB,IAAI,aAAa,CAAC,GAAG,UAAU,QAAQ,CAAC;CAE1C,IAAI,OAAO,KAAK,aAAa,CAAC,SAAS,GACrC,IAAI,QAAQ;CAEd,OAAO"}
package/dist/presets.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import { i as basicTools, n as definePreset, r as _default, t as Preset } from "./index-DAaKyadO.js";
2
- export { Preset, _default as basic, basicTools, definePreset };
1
+ import { n as AgentHookMap } from "./agent-JhicgLOV.js";
2
+ import { a as basicTools, i as _default, n as composePresets, r as definePreset, t as Preset } from "./index-2yLUyTbc.js";
3
+ export { AgentHookMap, Preset, _default as basic, basicTools, composePresets, definePreset };
package/dist/presets.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as basicTools, r as basic_default, t as definePreset } from "./presets-4zCJzCYw.js";
2
- export { basic_default as basic, basicTools, definePreset };
1
+ import { i as basic_default, n as definePreset, r as basicTools, t as composePresets } from "./presets-BRFH2qsQ.js";
2
+ export { basic_default as basic, basicTools, composePresets, definePreset };
@@ -1,2 +1,2 @@
1
- import { $ as OpenAICompatHttpError, G as StreamOptions, H as Provider, J as ToolSpec, K as ToolCall, Q as OpenAICompatAuthHeader, U as ProviderCapabilities, W as StreamCallbacks, X as OpenRouterParams, Y as TurnResult, Z as openrouter, at as openai, ct as AnthropicParams, et as OpenAICompatParams, it as OpenAIParams, lt as anthropic, nt as mapOAIFinishReason, ot as CerebrasParams, q as ToolResult, rt as openaiCompat, st as cerebras, tt as classifyOpenAICompatError } from "./agent-CMIhYhDz.js";
1
+ import { $ as OpenAICompatAuthHeader, G as StreamCallbacks, J as ToolResult, K as StreamOptions, Q as openrouter, U as Provider, W as ProviderCapabilities, X as TurnResult, Y as ToolSpec, Z as OpenRouterParams, at as OpenAIParams, ct as cerebras, et as OpenAICompatHttpError, it as openaiCompat, lt as AnthropicParams, nt as classifyOpenAICompatError, ot as openai, q as ToolCall, rt as mapOAIFinishReason, st as CerebrasParams, tt as OpenAICompatParams, ut as anthropic } from "./agent-JhicgLOV.js";
2
2
  export { AnthropicParams, CerebrasParams, OpenAICompatAuthHeader, OpenAICompatHttpError, OpenAICompatParams, OpenAIParams, OpenRouterParams, Provider, ProviderCapabilities, StreamCallbacks, StreamOptions, ToolCall, ToolResult, ToolSpec, TurnResult, anthropic, cerebras, classifyOpenAICompatError, mapOAIFinishReason, openai, openaiCompat, openrouter };
@@ -1,4 +1,4 @@
1
- import { O as SessionStore } from "../agent-CMIhYhDz.js";
1
+ import { k as SessionStore } from "../agent-JhicgLOV.js";
2
2
 
3
3
  //#region src/session/sqlite.d.ts
4
4
  interface SqliteStoreOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","names":[],"sources":["../../src/session/sqlite.ts"],"mappings":";;;UA4CiB,kBAAA;;EAEf,IAAA;AAAA;AAAA,iBAGc,iBAAA,CAAkB,OAAA,EAAS,kBAAA,GAAqB,YAAA;;;;;;;;;;;iBA+JhD,cAAA,CAAe,MAAA,WAAiB,YAAA"}
1
+ {"version":3,"file":"sqlite.d.ts","names":[],"sources":["../../src/session/sqlite.ts"],"mappings":";;;UAgDiB,kBAAA;;EAEf,IAAA;AAAA;AAAA,iBAGc,iBAAA,CAAkB,OAAA,EAAS,kBAAA,GAAqB,YAAA;;;;;;;;;;;iBA2LhD,cAAA,CAAe,MAAA,WAAiB,YAAA"}
@@ -9,8 +9,12 @@ import { Database } from "bun:sqlite";
9
9
  * 2 — `parentRunId` / `depth` on runs; cache-aware `totalUsage`
10
10
  * fields; cache-aware token rendering. Pre-2 rows load via
11
11
  * defensive `?? 0` fallbacks.
12
+ * 3 — `project_root` column + index. Sessions are tagged with the
13
+ * project they belong to (git root / cwd) so the TUI can list
14
+ * per-project. v2→v3 migration is non-destructive: we add the
15
+ * column NULL, existing rows stay readable as "untagged".
12
16
  */
13
- const SCHEMA_VERSION = 2;
17
+ const SCHEMA_VERSION = 3;
14
18
  function createSqliteStore(options) {
15
19
  const db = new Database(options.path);
16
20
  db.run("PRAGMA journal_mode = WAL");
@@ -18,34 +22,34 @@ function createSqliteStore(options) {
18
22
  CREATE TABLE IF NOT EXISTS sessions (
19
23
  id TEXT PRIMARY KEY,
20
24
  agent_id TEXT,
25
+ project_root TEXT,
21
26
  data TEXT NOT NULL,
22
27
  created_at INTEGER NOT NULL,
23
28
  updated_at INTEGER NOT NULL
24
29
  )
25
30
  `);
26
31
  db.run(`CREATE INDEX IF NOT EXISTS idx_sessions_agent_id ON sessions(agent_id)`);
32
+ if (!db.query("PRAGMA table_info(sessions)").all().some((c) => c.name === "project_root")) db.run("ALTER TABLE sessions ADD COLUMN project_root TEXT");
33
+ db.run("CREATE INDEX IF NOT EXISTS idx_sessions_project_root ON sessions(project_root)");
27
34
  if ((db.query("PRAGMA user_version").get()?.user_version ?? 0) < SCHEMA_VERSION) db.run(`PRAGMA user_version = ${SCHEMA_VERSION}`);
28
35
  const stmtLoad = db.prepare("SELECT data FROM sessions WHERE id = ?");
29
36
  const stmtUpsert = db.prepare(`
30
- INSERT INTO sessions (id, agent_id, data, created_at, updated_at)
31
- VALUES (?, ?, ?, ?, ?)
37
+ INSERT INTO sessions (id, agent_id, project_root, data, created_at, updated_at)
38
+ VALUES (?, ?, ?, ?, ?, ?)
32
39
  ON CONFLICT(id) DO UPDATE SET
33
40
  agent_id = excluded.agent_id,
41
+ project_root = excluded.project_root,
34
42
  data = excluded.data,
35
43
  updated_at = excluded.updated_at
36
44
  `);
37
45
  const stmtDelete = db.prepare("DELETE FROM sessions WHERE id = ?");
38
- const stmtList = db.prepare("SELECT id FROM sessions ORDER BY updated_at DESC");
39
- const stmtListLimited = db.prepare("SELECT id FROM sessions ORDER BY updated_at DESC LIMIT ?");
40
- const stmtListByAgent = db.prepare("SELECT id FROM sessions WHERE agent_id = ? ORDER BY updated_at DESC");
41
- const stmtListByAgentLimited = db.prepare("SELECT id FROM sessions WHERE agent_id = ? ORDER BY updated_at DESC LIMIT ?");
42
46
  const loadSync = (sessionId) => {
43
47
  const row = stmtLoad.get(sessionId);
44
48
  if (!row) return null;
45
49
  return JSON.parse(row.data);
46
50
  };
47
51
  const saveSync = (session) => {
48
- stmtUpsert.run(session.id, session.agentId ?? null, JSON.stringify(session), session.createdAt, session.updatedAt);
52
+ stmtUpsert.run(session.id, session.agentId ?? null, session.projectRoot ?? null, JSON.stringify(session), session.createdAt, session.updatedAt);
49
53
  };
50
54
  const txnAppendTurns = db.transaction((sessionId, turns) => {
51
55
  const data = loadSync(sessionId);
@@ -80,11 +84,22 @@ function createSqliteStore(options) {
80
84
  stmtDelete.run(sessionId);
81
85
  },
82
86
  async list(filter) {
83
- const limit = typeof filter?.limit === "number" && filter.limit > 0 ? filter.limit : void 0;
84
- let rows;
85
- if (filter?.agentId) rows = limit ? stmtListByAgentLimited.all(filter.agentId, limit) : stmtListByAgent.all(filter.agentId);
86
- else rows = limit ? stmtListLimited.all(limit) : stmtList.all();
87
- return rows.map((r) => r.id);
87
+ const conditions = [];
88
+ const params = [];
89
+ if (filter?.agentId) {
90
+ conditions.push("agent_id = ?");
91
+ params.push(filter.agentId);
92
+ }
93
+ if ("projectRoot" in (filter ?? {})) {
94
+ const v = filter.projectRoot;
95
+ if (v === null) conditions.push("project_root IS NULL");
96
+ else if (typeof v === "string") {
97
+ conditions.push("project_root = ?");
98
+ params.push(v);
99
+ }
100
+ }
101
+ const sql = `SELECT id FROM sessions ${conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""} ORDER BY updated_at DESC ${typeof filter?.limit === "number" && filter.limit > 0 ? `LIMIT ${filter.limit}` : ""}`.replace(/\s+/g, " ").trim();
102
+ return db.query(sql).all(...params).map((r) => r.id);
88
103
  },
89
104
  async appendTurns(sessionId, turns) {
90
105
  txnAppendTurns.immediate(sessionId, turns);