import type { SkillSpec } from "@sema-agent/core"; /** * Built-in skills baked into the image, loaded ONCE at startup (no per-task disk IO). A skill is a * Markdown capability (frontmatter + body). core uses **progressive disclosure** (core ≥1.98.0): * a `` block in the STABLE system prefix lists each skill's name+description, and the BODY is * fetched only when the model INVOKES the `skill` tool. Since core 1.293.0 the invoke path is * zero-truncation (the load gate is SKILL_CONTENT_MAX_CHARS = 1 MiB, whole-skill reject). ⚠️ Requires * core ^1.98.0 — before that the skill()/formatSkillInvocation reader was UNREACHABLE and merged skills * silently never reached the model; the skill-fire e2e regression guards this. * * Two on-disk shapes, coexisting under the skills dir: * - flat: `skills/.md` (frontmatter + body, as before) * - directory: `skills//SKILL.md` as the body + every OTHER file under the directory * (recursive, e.g. `references/*.md`, `scripts/*.js`) attached as `spec.files[{path,content}]` * with the path RELATIVE to the skill directory. core renders attachments in full on invoke * under a total budget (SKILL_ATTACHMENTS_MAX_CHARS = 50_000 chars, in-band omission disclosure * past it) — the baked-skills test pins that budget so an oversize edit fails loud here, not * silently truncates there. * A flat/directory NAME collision throws (fail-loud) — a silent overwrite would ship whichever * shape sorts later and drop the other one's body without a trace. * * Optional frontmatter (same on flat files and SKILL.md): * --- * name: my-skill * description: when to use it * scenarios: code-review, oa # which scenarios get it; omit = all scenarios * --- */ export interface LoadedSkill { spec: SkillSpec; /** Scenarios this skill applies to; empty = global (every scenario). */ scenarios: string[]; } /** 加载 lane 的信任级。缺席(受控 lane:烤制进镜像的 skills 目录)= 维持既有语义,symlink 照跟 * ——那个便利是有意的(`foo.md -> 共享文件`)。`confineTo` 在场(**不可信 lane**:第三方 git checkout, * 见 center-plugins 的 plugin 装载)= 每个被读条目 realpath 后必须仍在该根之内,逃逸即抛。 * 🔴 为什么信任级必须是**参数**而不是注释里的假设:本函数的头注原写着「skills dir 全受控」,而 * center-plugins 把它原样复用在 不可信来源的 git checkout 上——git 原样保留仓里的 symlink, * 于是 checkout 里某个 skill 条目(`任意 .md -> /宿主/任意文件`)会被读进 spec.files 进入提示词。resolvePluginSkillsRoot * 只 realpath 守了 skills **根**,根下逐条目没守。 */ export interface LoadSkillsOptions { /** 不可信 lane 的约束根(通常=clone root)。缺席=受控 lane,不做逐条目约束。 */ confineTo?: string; /** * S-377 —— 「**工具跑的那台机器**的盘就是本进程这台盘吗」。真 ⇒ 目录形态技能带上自己的目录 * (`SkillSpec.baseDir`);假/缺席 ⇒ **不带**。判据的单一属主 = `task-cwd.ts` 的 * {@link import("../task-cwd.js").toolsRunOnThisHost}(调用方按它算,别在这里第二次判 lane)。 * * 🔴 **缺席 = 不带**,是**故意的 fail-closed 方向**:带了一条工具那边不存在的路径,伤是「模型按它去 * 读、扑空,顺带看到本机目录布局」;不带的伤只是少一条线索(附件本来就随 invoke 全文内联投递, * core 的律也明写缺席 ⇒ 结果逐字节与 baseDir 到货之前相同)。两个方向不对称 ⇒ 默认取轻的那边。 */ toolsShareThisFilesystem?: boolean; } /** `p` 的 realpath 必须落在 `rootReal` 之内(含自身),否则抛。`sep` 边界判定防 `/a/root-evil` 撞 * `/a/root` 前缀。目标不存在 ⇒ realpath 抛 ENOENT,同样是拒绝(悬空链接不该被读)。 * `label` = 错误文本里的对象名(center-plugins 用它约束 manifest 本体,报错不该指成 skill entry)。 */ export declare function assertConfined(p: string, rootReal: string, label?: string): void; export declare function loadSkills(dir: string, opts?: LoadSkillsOptions): LoadedSkill[]; /** Skills applicable to a scenario: those tagged with it, plus untagged (global) ones. * [ref]:alias 窗撤销,单键查询——退役旧名标签不再命中(迁移面在 7.44 发车帖/MIGRATION 记载)。 */ export declare function skillsForScenario(loaded: LoadedSkill[], scenario: string): SkillSpec[]; export declare function parseFrontmatter(raw: string): { meta: Record; body: string; }; //# sourceMappingURL=skills.d.ts.map