berry-agent 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKILL.md frontmatter 解析与校验(06 §11.2——向 agentskills.io 标准靠拢;
|
|
3
|
+
* 装载层护栏:description 超 1024 截断装载 + invalid-metadata 警告注明「已截断」、
|
|
4
|
+
* name 超长不截断仅警告——name 是身份键,截断会伪造碰撞)。
|
|
5
|
+
*
|
|
6
|
+
* 宽容度语义:校验失败一律警告 + 对应处置(截断/丢弃字段/跳过文件)不抛——
|
|
7
|
+
* 坏文件不炸装载(与 berry 装载实证同律);唯一拒载条件 = description 缺席
|
|
8
|
+
* (清单行 = 模型选择依据,无描述即无渐进披露面)与 frontmatter 不可解析。
|
|
9
|
+
*/
|
|
10
|
+
import { basename, dirname } from 'node:path';
|
|
11
|
+
import { parse as parseYaml } from 'yaml';
|
|
12
|
+
import { SKILL_DESCRIPTION_MAX, SKILL_NAME_MAX, SKILL_PROVENANCE_MEMORIES_MAX, SKILL_REFS_MAX } from './types.js';
|
|
13
|
+
import { splitSkillSections } from './sections.js';
|
|
14
|
+
/**
|
|
15
|
+
* 解析 SKILL.md:剥 BOM、CRLF/CR 统一 LF、`---` 开闭行包裹的 YAML 头。
|
|
16
|
+
*
|
|
17
|
+
* 开闭行判定:首行恰 `---`(行尾空白容差);闭合 = 后续首个恰 `---` 的行。
|
|
18
|
+
* 无开行/无闭合行 → 错误(SKILL.md 的 description 必填,头缺失即无装载面);
|
|
19
|
+
* 空头(`---` 紧邻 `---`)与字面 null 按空映射续走——description 缺席判在装载层。
|
|
20
|
+
*/
|
|
21
|
+
export function parseSkillFrontmatter(raw) {
|
|
22
|
+
// BOM 剥离(U+FEFF——编辑器写入时常见;charCode 判比字面量嵌入可检)
|
|
23
|
+
const unbommed = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
|
|
24
|
+
const normalized = unbommed.replace(/\r\n?/g, '\n');
|
|
25
|
+
const lines = normalized.split('\n');
|
|
26
|
+
const first = (lines[0] ?? '').trimEnd();
|
|
27
|
+
if (first !== '---') {
|
|
28
|
+
return { error: '缺 frontmatter 开行 ---' };
|
|
29
|
+
}
|
|
30
|
+
let closeIndex = -1;
|
|
31
|
+
for (let i = 1; i < lines.length; i++) {
|
|
32
|
+
if ((lines[i] ?? '').trimEnd() === '---') {
|
|
33
|
+
closeIndex = i;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (closeIndex === -1) {
|
|
38
|
+
return { error: 'frontmatter 未闭合(缺 --- 闭合行)' };
|
|
39
|
+
}
|
|
40
|
+
const yamlText = lines.slice(1, closeIndex).join('\n');
|
|
41
|
+
let parsed;
|
|
42
|
+
if (yamlText.trim() !== '') {
|
|
43
|
+
try {
|
|
44
|
+
parsed = parseYaml(yamlText);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return { error: `YAML 解析失败:${error instanceof Error ? error.message : String(error)}` };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// 空头/字面 null 按空映射续走(正文仍在——不可因头空丢正文);数组/标量才拒
|
|
51
|
+
let frontmatter;
|
|
52
|
+
if (parsed === null || parsed === undefined) {
|
|
53
|
+
frontmatter = {};
|
|
54
|
+
}
|
|
55
|
+
else if (typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
56
|
+
return { error: 'frontmatter 须为映射(键值对)' };
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
frontmatter = parsed;
|
|
60
|
+
}
|
|
61
|
+
const bodyStart = lines.slice(0, closeIndex + 1).join('\n').length + 1; // +1 = 跳过闭合行后的换行
|
|
62
|
+
const body = normalized.slice(bodyStart);
|
|
63
|
+
return { frontmatter, body, bodyStart };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 技能名词法校验(agentskills.io 标准 + 06 §11.2:小写字母/数字/连字符)。
|
|
67
|
+
* 返回违例清单(空 = 合法)——装载面与 skill_manage create 同一真源。
|
|
68
|
+
*/
|
|
69
|
+
export function validateSkillName(name) {
|
|
70
|
+
const errors = [];
|
|
71
|
+
if (name.length > SKILL_NAME_MAX) {
|
|
72
|
+
errors.push(`name 超 ${SKILL_NAME_MAX} 字符(现 ${name.length})`);
|
|
73
|
+
}
|
|
74
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
75
|
+
errors.push('name 含非法字符(只许小写字母/数字/连字符)');
|
|
76
|
+
}
|
|
77
|
+
if (name.startsWith('-') || name.endsWith('-')) {
|
|
78
|
+
errors.push('name 首尾不得为连字符');
|
|
79
|
+
}
|
|
80
|
+
if (name.includes('--')) {
|
|
81
|
+
errors.push('name 不得含连续连字符');
|
|
82
|
+
}
|
|
83
|
+
return errors;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 技能引用清单校验(06 §11.6 skills 键形状护栏——帽 8 / 词法 / 重复三验单源)。
|
|
87
|
+
*
|
|
88
|
+
* 消费面两腿同一真源:agents frontmatter 解析层 + 子代理工厂 spawn 前复验
|
|
89
|
+
* (解析层管形状、工厂复验兜底——程序化注册与直呼 SubagentRequest 绕过解析层,
|
|
90
|
+
* 工厂侧是唯一全覆盖执法点)。存在性不在本函数查(装载序晚于装配期物化,
|
|
91
|
+
* 存在性归工厂 resolve 时点)。返回违例清单(空 = 合法)。
|
|
92
|
+
*/
|
|
93
|
+
export function validateSkillRefList(names) {
|
|
94
|
+
const errors = [];
|
|
95
|
+
if (names.length > SKILL_REFS_MAX) {
|
|
96
|
+
errors.push(`skills 引用超 ${SKILL_REFS_MAX} 项(现 ${names.length})`);
|
|
97
|
+
}
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
for (const name of names) {
|
|
100
|
+
for (const error of validateSkillName(name)) {
|
|
101
|
+
errors.push(`skills 项「${name}」${error}`);
|
|
102
|
+
}
|
|
103
|
+
if (seen.has(name)) {
|
|
104
|
+
errors.push(`skills 项「${name}」重复`);
|
|
105
|
+
}
|
|
106
|
+
seen.add(name);
|
|
107
|
+
}
|
|
108
|
+
return errors;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 从 SKILL.md 全文装载技能(校验 + 剥离 + 节骨架)。
|
|
112
|
+
*
|
|
113
|
+
* 处置逐条(06 §11.2/§11.3/§9.1 第 2 项):
|
|
114
|
+
* - description 非字符串或空白 → 拒载(唯一硬条件);
|
|
115
|
+
* - description > 1024 → 截断装载 + invalid-metadata「已截断」;
|
|
116
|
+
* - name 缺席 → 回落父目录基名;词法/长度/与父目录同名违例 → 警告(仍装载);
|
|
117
|
+
* - provenance 形状非法(非对象/memories 非字符串数组/超 50)→ 警告 + 字段丢弃
|
|
118
|
+
* (宿主只读解析不校验记忆 id 存在性——跨面校验属审批/诊断面);
|
|
119
|
+
* - metadata 非纯对象 → 警告 + 丢弃;
|
|
120
|
+
* - license/compatibility/allowed-tools 等未采用字段 → 静默忽略(非警告面)。
|
|
121
|
+
*/
|
|
122
|
+
export function loadSkillFromText(raw, context) {
|
|
123
|
+
const diagnostics = [];
|
|
124
|
+
const parsed = parseSkillFrontmatter(raw);
|
|
125
|
+
if ('error' in parsed) {
|
|
126
|
+
diagnostics.push({ type: 'invalid-metadata', message: parsed.error, path: context.filePath });
|
|
127
|
+
return { diagnostics };
|
|
128
|
+
}
|
|
129
|
+
// description:必填(清单行 = 模型选择依据)
|
|
130
|
+
const rawDescription = parsed.frontmatter['description'];
|
|
131
|
+
if (typeof rawDescription !== 'string' || rawDescription.trim() === '') {
|
|
132
|
+
diagnostics.push({
|
|
133
|
+
type: 'invalid-metadata',
|
|
134
|
+
message: 'description 缺席或为空(必填——渐进披露清单行的唯一依据)',
|
|
135
|
+
path: context.filePath,
|
|
136
|
+
});
|
|
137
|
+
return { diagnostics };
|
|
138
|
+
}
|
|
139
|
+
let description = rawDescription;
|
|
140
|
+
if (description.length > SKILL_DESCRIPTION_MAX) {
|
|
141
|
+
description = description.slice(0, SKILL_DESCRIPTION_MAX);
|
|
142
|
+
diagnostics.push({
|
|
143
|
+
type: 'invalid-metadata',
|
|
144
|
+
message: `description 超 ${SKILL_DESCRIPTION_MAX} 已截断装载(全文可经 read 读原文件)`,
|
|
145
|
+
path: context.filePath,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// name:缺席回落父目录基名;违例仅警告(name 是身份键不截断)
|
|
149
|
+
const rawName = parsed.frontmatter['name'];
|
|
150
|
+
const skillDir = dirname(context.filePath);
|
|
151
|
+
const parentName = basename(skillDir);
|
|
152
|
+
const name = typeof rawName === 'string' && rawName !== '' ? rawName : parentName;
|
|
153
|
+
for (const error of validateSkillName(name)) {
|
|
154
|
+
diagnostics.push({ type: 'invalid-metadata', message: error, path: context.filePath });
|
|
155
|
+
}
|
|
156
|
+
if (name !== parentName) {
|
|
157
|
+
diagnostics.push({
|
|
158
|
+
type: 'invalid-metadata',
|
|
159
|
+
message: `name 与父目录不同名(${name} ≠ ${parentName})——技能目录约定 = 同名目录承载同名技能`,
|
|
160
|
+
path: context.filePath,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
// provenance:形状校验宽容丢弃(§9.1 第 2 项——不校验记忆存在性)
|
|
164
|
+
let provenance;
|
|
165
|
+
const rawProvenance = parsed.frontmatter['provenance'];
|
|
166
|
+
if (rawProvenance !== undefined) {
|
|
167
|
+
const valid = typeof rawProvenance === 'object' &&
|
|
168
|
+
rawProvenance !== null &&
|
|
169
|
+
!Array.isArray(rawProvenance) &&
|
|
170
|
+
Array.isArray(rawProvenance.memories) &&
|
|
171
|
+
rawProvenance.memories.every((m) => typeof m === 'string') &&
|
|
172
|
+
rawProvenance.memories.length <= SKILL_PROVENANCE_MEMORIES_MAX;
|
|
173
|
+
if (valid) {
|
|
174
|
+
provenance = { memories: rawProvenance.memories };
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
diagnostics.push({
|
|
178
|
+
type: 'invalid-metadata',
|
|
179
|
+
message: `provenance 形状非法须 { memories: [id, …] } 且 ≤ ${SKILL_PROVENANCE_MEMORIES_MAX}——字段丢弃`,
|
|
180
|
+
path: context.filePath,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// metadata:客户端扩展槽——非纯对象丢弃
|
|
185
|
+
let metadata;
|
|
186
|
+
const rawMetadata = parsed.frontmatter['metadata'];
|
|
187
|
+
if (rawMetadata !== undefined) {
|
|
188
|
+
if (typeof rawMetadata === 'object' && rawMetadata !== null && !Array.isArray(rawMetadata)) {
|
|
189
|
+
metadata = rawMetadata;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
diagnostics.push({ type: 'invalid-metadata', message: 'metadata 须为对象——字段丢弃', path: context.filePath });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const content = parsed.body.trim();
|
|
196
|
+
const sections = splitSkillSections(content).map((s) => ({
|
|
197
|
+
path: s.path,
|
|
198
|
+
level: s.level,
|
|
199
|
+
startLine: s.startLine,
|
|
200
|
+
endLine: s.endLine,
|
|
201
|
+
}));
|
|
202
|
+
const skill = {
|
|
203
|
+
name,
|
|
204
|
+
description,
|
|
205
|
+
filePath: context.filePath,
|
|
206
|
+
baseDir: skillDir,
|
|
207
|
+
providerId: context.providerId,
|
|
208
|
+
content,
|
|
209
|
+
disableModelInvocation: parsed.frontmatter['disable-model-invocation'] === true,
|
|
210
|
+
...(provenance !== undefined ? { provenance } : {}),
|
|
211
|
+
...(metadata !== undefined ? { metadata } : {}),
|
|
212
|
+
sections,
|
|
213
|
+
};
|
|
214
|
+
return { skill, diagnostics };
|
|
215
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skills 件公开面(02 §4.1 席 13:SKILL.md 双层结构 + 发现六位序列 + 注册表 +
|
|
3
|
+
* 渐进披露三层护栏 + skill_manage——deps {contracts, context})。
|
|
4
|
+
*
|
|
5
|
+
* 装载态集成归批 12 装载面后装配批:标准六位层经 createStandardLayers 构造、
|
|
6
|
+
* registry.onChange 桥接 skills_change 事件(03 §6.1——载荷 = 现行 provider id
|
|
7
|
+
* 清单);清单重物化消费面(系统提示词拼装)归 conversation/host 消费批。
|
|
8
|
+
* 出厂技能目录内容(三件样例含 plugins-quickstart)挂 07 出厂清单定名批。
|
|
9
|
+
*/
|
|
10
|
+
import './codes.js';
|
|
11
|
+
// 常量与类型
|
|
12
|
+
export { SKILL_DESCRIPTION_MAX, SKILL_MANIFEST_BYTE_CAP, SKILL_NAME_MAX, SKILL_PROVENANCE_MEMORIES_MAX, SKILL_REFS_MAX, SKILL_SNAPSHOT_CAP, } from './types.js';
|
|
13
|
+
// frontmatter 解析与单文件装载
|
|
14
|
+
export { loadSkillFromText, parseSkillFrontmatter, validateSkillName, validateSkillRefList } from './frontmatter.js';
|
|
15
|
+
// 渐进披露第三层细化(节级寻址/行级过滤/模式词表推导)
|
|
16
|
+
export { deriveModeVocabulary, filterSkillBody, findSkillSection, splitSkillSections } from './sections.js';
|
|
17
|
+
// 发现六位序列(目录扫描 + 标准层构造)
|
|
18
|
+
export { createDirProvider, createStandardLayers, resolveFactorySkillsDir, scanSkillsDir } from './discovery.js';
|
|
19
|
+
// 声明式子代理解析层(06 §11.6——解析层住本件、机器住 core:subagent)
|
|
20
|
+
export { collectAgentDefs, createAgentLayerProvider, createStandardAgentLayers, parseAgentDef } from './agents.js';
|
|
21
|
+
// 注册表
|
|
22
|
+
export { createSkillsRegistry } from './registry.js';
|
|
23
|
+
// 渐进披露渲染层(清单块 + 具名块单源 + 激活包装)
|
|
24
|
+
export { formatSkillBlock, formatSkillInvocation, parseSkillInvocation, renderAvailableSkills } from './render.js';
|
|
25
|
+
// skill_manage 工具(§12.1 管理动词)
|
|
26
|
+
export { createSkillManageTool } from './manage.js';
|
|
27
|
+
// load_skill 工具(§11.5(a) 按需拉取模——具名通道)
|
|
28
|
+
export { createLoadSkillTool } from './load.js';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* load_skill 工具(06 §11.5(a) 按需拉取模——双模装载第二模)。
|
|
3
|
+
*
|
|
4
|
+
* 注册位:core:skills 件注册的全局工具(boot 全局层同 skill_manage 恒挂载);
|
|
5
|
+
* 只读无审批对(不写任何根——effect 面空)。与 read 的分工:read 是 FS 通道
|
|
6
|
+
* (模型自持路径语义)、本工具是具名通道(注册表解析 + 节级寻址/行级过滤
|
|
7
|
+
* 两细化承载)——两通道等价合法。
|
|
8
|
+
*
|
|
9
|
+
* 错误面纪律:name 未命中复用 SKILLS_NOT_FOUND 词面(回执指路清单不内联);
|
|
10
|
+
* disable-model-invocation 件拒载并指路用户显式通道(清单滤除律配套执法);
|
|
11
|
+
* section/mode 错误 = 无码 guidance 文本(isError true);一切错误走文本回执
|
|
12
|
+
* 不 throw(模型可读可自纠——同 skill_manage 错误面形)。
|
|
13
|
+
*/
|
|
14
|
+
import { Type } from 'typebox';
|
|
15
|
+
import { formatSkillBlock } from './render.js';
|
|
16
|
+
import { deriveModeVocabulary, filterSkillBody, findSkillSection } from './sections.js';
|
|
17
|
+
/** 错误结果工厂(数据面——[码] 人读信息;与 skill_manage 同形) */
|
|
18
|
+
function toolError(code, message) {
|
|
19
|
+
return { content: [{ type: 'text', text: `[${code}] ${message}` }], isError: true };
|
|
20
|
+
}
|
|
21
|
+
/** 无码 guidance 回执(section/mode 错误面——非注册表错,码册外指路文本) */
|
|
22
|
+
function toolGuidance(message) {
|
|
23
|
+
return { content: [{ type: 'text', text: message }], isError: true };
|
|
24
|
+
}
|
|
25
|
+
/** 成功结果工厂 */
|
|
26
|
+
function toolText(text) {
|
|
27
|
+
return { content: [{ type: 'text', text }] };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 按层级重建节标题串(06 §11.5 节级寻址——重建层号规则)。
|
|
31
|
+
*
|
|
32
|
+
* 祖先标题自 section.level−1 逐级递降、下限 1——重建是节档结构的确定性呈现
|
|
33
|
+
* 非原文层号保真(SkillSection 只记自身层号,跳级标题下祖先层号以本规则赋值,
|
|
34
|
+
* 两实现者同读法)。
|
|
35
|
+
*/
|
|
36
|
+
function rebuildSectionHeading(section) {
|
|
37
|
+
const ancestors = section.path.split(' > ');
|
|
38
|
+
const own = ancestors[ancestors.length - 1] ?? section.title;
|
|
39
|
+
const lines = [];
|
|
40
|
+
const depth = ancestors.length - 1; // 祖先数(不含本节)
|
|
41
|
+
for (let i = 0; i < depth; i++) {
|
|
42
|
+
const level = Math.max(1, section.level - (depth - i));
|
|
43
|
+
lines.push(`${'#'.repeat(level)} ${ancestors[i]}`);
|
|
44
|
+
}
|
|
45
|
+
lines.push(`${'#'.repeat(section.level)} ${own}`);
|
|
46
|
+
return lines.join('\n');
|
|
47
|
+
}
|
|
48
|
+
/** 节回执内容 = 重建标题串 + 空行 + 节正文(正文不含标题行——splitSkillSections 契约) */
|
|
49
|
+
function sectionContent(section) {
|
|
50
|
+
return `${rebuildSectionHeading(section)}\n\n${section.body}`;
|
|
51
|
+
}
|
|
52
|
+
/** trim + 小写归一(与 filterSkillBody 比对同源) */
|
|
53
|
+
function normalizeModeLabel(value) {
|
|
54
|
+
return value.trim().toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 创建 load_skill 工具定义(装配注册进工具表——03 §2.3 defineTool 形)。
|
|
58
|
+
*/
|
|
59
|
+
export function createLoadSkillTool(deps) {
|
|
60
|
+
return {
|
|
61
|
+
name: 'load_skill',
|
|
62
|
+
description: '按名装载技能内容(注册表具名通道——与 read 按 location 直读等价,另承节级寻址与' +
|
|
63
|
+
'行级过滤两细化):name 必填;section 缺省装载全文、给祖先路径("A > B" 式)只装' +
|
|
64
|
+
'命中节;mode 按双形词表行级过滤正文(词表由内容自身的强度表行与带引号示例行' +
|
|
65
|
+
'交集推导)。回执为具名技能块。',
|
|
66
|
+
parameters: Type.Object({
|
|
67
|
+
name: Type.String({ description: '技能名(<available_skills> 清单 name 位)' }),
|
|
68
|
+
section: Type.Optional(Type.String({ description: '节祖先路径("Mode 3 > Workflow" 式;缺省 = 全文)' })),
|
|
69
|
+
mode: Type.Optional(Type.String({ description: '行级过滤模式名(缺省 = 不过滤)' })),
|
|
70
|
+
}, { additionalProperties: false }),
|
|
71
|
+
execute: async (args) => {
|
|
72
|
+
// typebox 已在管道层验形——此处收窄仅满足 TS(非 string 形结构性不达)
|
|
73
|
+
const name = typeof args.name === 'string' ? args.name : '';
|
|
74
|
+
const section = typeof args.section === 'string' ? args.section : undefined;
|
|
75
|
+
const mode = typeof args.mode === 'string' ? args.mode : undefined;
|
|
76
|
+
const skill = deps.registry.get(name);
|
|
77
|
+
if (skill === undefined) {
|
|
78
|
+
return toolError('SKILLS_NOT_FOUND', `技能「${name}」不在册——核对 <available_skills> 清单的 name 位(同名以高优先层为准)`);
|
|
79
|
+
}
|
|
80
|
+
// 清单滤除律配套执法:隐藏件不在模型装载面,指路用户显式通道
|
|
81
|
+
if (skill.disableModelInvocation) {
|
|
82
|
+
return toolGuidance(`技能「${skill.name}」不对模型自动装载(disable-model-invocation)——` +
|
|
83
|
+
`可请用户显式激活(/skill:${skill.name})`);
|
|
84
|
+
}
|
|
85
|
+
// 节级寻址:报错不猜(歧义带行号、未命中列全部可寻址 path——指路而非裸拒)
|
|
86
|
+
let content = skill.content;
|
|
87
|
+
if (section !== undefined) {
|
|
88
|
+
const found = findSkillSection(skill.content, section);
|
|
89
|
+
if (!found.ok) {
|
|
90
|
+
if (found.reason === 'ambiguous') {
|
|
91
|
+
return toolGuidance(`节「${section}」歧义(${found.candidates.length} 处同名)——候选:\n` +
|
|
92
|
+
found.candidates.map((c) => `- ${c}`).join('\n'));
|
|
93
|
+
}
|
|
94
|
+
return toolGuidance(`节「${section}」未命中——可寻址节:\n` +
|
|
95
|
+
(found.candidates.length > 0
|
|
96
|
+
? found.candidates.map((c) => `- ${c}`).join('\n')
|
|
97
|
+
: '(正文无可寻址节——不含任何标题)'));
|
|
98
|
+
}
|
|
99
|
+
content = sectionContent(found.section);
|
|
100
|
+
}
|
|
101
|
+
// 行级过滤:词表推导域 = 待回执内容自身(节形即节内容——mode+section 组合时词表随节重推导)
|
|
102
|
+
if (mode !== undefined && mode.trim() !== '') {
|
|
103
|
+
const vocabulary = deriveModeVocabulary(content);
|
|
104
|
+
if (vocabulary.length === 0) {
|
|
105
|
+
// 词表空 = mode 无可作用行——原样回执 + 就地注明(诚实无操作,非静默忽略)
|
|
106
|
+
return toolText(`注:该内容未推导出模式词表(无强度表行 ∩ 带引号示例行的双形标签),` +
|
|
107
|
+
`mode「${mode}」未生效,按原样装载。\n\n${formatSkillBlock(skill, content)}`);
|
|
108
|
+
}
|
|
109
|
+
const active = normalizeModeLabel(mode);
|
|
110
|
+
if (!vocabulary.includes(active)) {
|
|
111
|
+
return toolGuidance(`mode「${mode}」不在词表——可用模式:${vocabulary.join(' / ')}`);
|
|
112
|
+
}
|
|
113
|
+
content = filterSkillBody(content, { modes: vocabulary, active: mode });
|
|
114
|
+
}
|
|
115
|
+
return toolText(formatSkillBlock(skill, content));
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill_manage 工具(06 §12.1——技能的面内管理动词:list / create / patch)。
|
|
3
|
+
*
|
|
4
|
+
* - 恒 effect:'write'(list 也不例外——同工具三动词统一审批面,守门段管道执法);
|
|
5
|
+
* - create 恒 project 层 `<workspace>/.agents/skills/<名>/SKILL.md`;同名亮拒不
|
|
6
|
+
* 覆写——判据 = 注册表在册名 ∪ 盘上文件在场(坏 frontmatter 文件对 get()
|
|
7
|
+
* 隐身,只查在册名会静默毁文件);
|
|
8
|
+
* - patch 仅 project 层可改(user/package 层拒并指路人面);find 串零/多匹配
|
|
9
|
+
* 均拒(不猜改点);替换区间限 frontmatter 闭合 --- 之后(正文域);replace
|
|
10
|
+
* 字面写出(split/join——防 $&/$` 展开家族);校验域与替换域统一在 CRLF→LF
|
|
11
|
+
* 归一化整文件;
|
|
12
|
+
* - 写点前置可写根断言:物理写(含 mkdir)前走装配注入的 writableRoots 推导,
|
|
13
|
+
* read-only 档空根即拒(绕过 fence 的直写即缺陷);
|
|
14
|
+
* - 写后自动 registry.refresh()(技能面刷新 + 渐进披露清单重物化同径)。
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { join, resolve, sep } from 'node:path';
|
|
18
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
19
|
+
import { Type } from 'typebox';
|
|
20
|
+
import { parseSkillFrontmatter, validateSkillName } from './frontmatter.js';
|
|
21
|
+
import { SKILL_DESCRIPTION_MAX, SKILL_PROVENANCE_MEMORIES_MAX } from './types.js';
|
|
22
|
+
/** 错误结果工厂(数据面——[码] 人读信息;与 exec 族同形) */
|
|
23
|
+
function toolError(code, message) {
|
|
24
|
+
return { content: [{ type: 'text', text: `[${code}] ${message}` }], isError: true };
|
|
25
|
+
}
|
|
26
|
+
/** 成功结果工厂 */
|
|
27
|
+
function toolText(text) {
|
|
28
|
+
return { content: [{ type: 'text', text }] };
|
|
29
|
+
}
|
|
30
|
+
/** 路径落在可写根集合内(resolve 归一 + 边界分隔符守卫——`/a` 不匹配 `/ab`) */
|
|
31
|
+
function isWithinRoots(path, roots) {
|
|
32
|
+
const abs = resolve(path);
|
|
33
|
+
return roots.some((root) => {
|
|
34
|
+
const base = resolve(root);
|
|
35
|
+
return abs === base || abs.startsWith(base + sep);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** 校验非空字符串参数(schema 之外的语义校验面——空串 trim 后拒) */
|
|
39
|
+
function nonEmptyString(value) {
|
|
40
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 创建 skill_manage 工具定义(装配注册进工具表——03 §2.3 defineTool 形)。
|
|
44
|
+
*/
|
|
45
|
+
export function createSkillManageTool(deps) {
|
|
46
|
+
return {
|
|
47
|
+
name: 'skill_manage',
|
|
48
|
+
description: '管理技能(渐进披露清单的来源文件):list 列出在册技能(含来源层与溯源);' +
|
|
49
|
+
'create 在 project 层 .agents/skills/<名>/ 下新建 SKILL.md(同名亮拒不覆写);' +
|
|
50
|
+
'patch 对 project 层技能正文做单点 find-replace(零/多匹配均拒;user/出厂层' +
|
|
51
|
+
'只读)。写后自动刷新技能面。',
|
|
52
|
+
parameters: Type.Object({
|
|
53
|
+
action: Type.Union([Type.Literal('list'), Type.Literal('create'), Type.Literal('patch')], {
|
|
54
|
+
description: '管理动词',
|
|
55
|
+
}),
|
|
56
|
+
name: Type.Optional(Type.String({ description: '技能名(create/patch 必填)' })),
|
|
57
|
+
description: Type.Optional(Type.String({ description: `技能描述(create 必填,≤ ${SKILL_DESCRIPTION_MAX} 字符)` })),
|
|
58
|
+
body: Type.Optional(Type.String({ description: '技能正文 Markdown(create 必填)' })),
|
|
59
|
+
provenance_memories: Type.Optional(Type.Array(Type.String(), {
|
|
60
|
+
description: `溯源记忆 id 清单(可选,≤ ${SKILL_PROVENANCE_MEMORIES_MAX} 条)`,
|
|
61
|
+
})),
|
|
62
|
+
find: Type.Optional(Type.String({ description: 'patch 查找串(正文域单点匹配)' })),
|
|
63
|
+
replace: Type.Optional(Type.String({ description: 'patch 替换串(字面写出——$& 等不展开)' })),
|
|
64
|
+
}, { additionalProperties: false }),
|
|
65
|
+
// 三动词统一走写面审批(06 §12.1 恒 effect:'write'——守门段管道执法)
|
|
66
|
+
effect: 'write',
|
|
67
|
+
execute: async (args) => {
|
|
68
|
+
const action = args.action;
|
|
69
|
+
if (action === 'list')
|
|
70
|
+
return executeList(deps);
|
|
71
|
+
if (action === 'create')
|
|
72
|
+
return executeCreate(deps, args);
|
|
73
|
+
return executePatch(deps, args);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** list:清单 + 来源层 + 溯源(含隐藏件——管理面见全量) */
|
|
78
|
+
function executeList(deps) {
|
|
79
|
+
const skills = deps.registry.list();
|
|
80
|
+
const diagnostics = deps.registry.diagnostics();
|
|
81
|
+
if (skills.length === 0) {
|
|
82
|
+
const diagNote = diagnostics.length > 0
|
|
83
|
+
? `\n诊断 ${diagnostics.length} 条:\n${diagnostics.map((d) => `- [${d.type}] ${d.message}`).join('\n')}`
|
|
84
|
+
: '';
|
|
85
|
+
return toolText(`在册技能 0 件。${diagNote}`);
|
|
86
|
+
}
|
|
87
|
+
const rows = skills.map((skill) => {
|
|
88
|
+
const flags = [
|
|
89
|
+
skill.disableModelInvocation ? '隐藏' : '',
|
|
90
|
+
skill.provenance ? `溯源 ${skill.provenance.memories.length}` : '',
|
|
91
|
+
]
|
|
92
|
+
.filter((f) => f !== '')
|
|
93
|
+
.map((f) => `[${f}]`)
|
|
94
|
+
.join(' ');
|
|
95
|
+
return `- ${skill.name}(${skill.providerId} 层)${flags ? `${flags} ` : ''}${skill.filePath}`;
|
|
96
|
+
});
|
|
97
|
+
const diagNote = diagnostics.length > 0
|
|
98
|
+
? `\n诊断 ${diagnostics.length} 条:\n${diagnostics.map((d) => `- [${d.type}] ${d.message}`).join('\n')}`
|
|
99
|
+
: '';
|
|
100
|
+
return toolText(`在册技能 ${skills.length} 件:\n${rows.join('\n')}${diagNote}`);
|
|
101
|
+
}
|
|
102
|
+
/** create:project 层新建(校验链 → 同名亮拒 → 可写根断言 → 写盘 → 刷新) */
|
|
103
|
+
async function executeCreate(deps, args) {
|
|
104
|
+
const name = nonEmptyString(args.name);
|
|
105
|
+
if (name === undefined) {
|
|
106
|
+
return toolError('SKILLS_NAME_INVALID', 'create 须携带技能名(name)');
|
|
107
|
+
}
|
|
108
|
+
const nameErrors = validateSkillName(name);
|
|
109
|
+
if (nameErrors.length > 0) {
|
|
110
|
+
return toolError('SKILLS_NAME_INVALID', `技能名违例:${nameErrors.join(';')}`);
|
|
111
|
+
}
|
|
112
|
+
const description = nonEmptyString(args.description);
|
|
113
|
+
if (description === undefined || description.length > SKILL_DESCRIPTION_MAX) {
|
|
114
|
+
return toolError('SKILLS_CONTENT_INVALID', `description 须非空且 ≤ ${SKILL_DESCRIPTION_MAX} 字符(现 ${typeof args.description === 'string' ? args.description.length : '缺席'})`);
|
|
115
|
+
}
|
|
116
|
+
const body = nonEmptyString(args.body);
|
|
117
|
+
if (body === undefined) {
|
|
118
|
+
return toolError('SKILLS_CONTENT_INVALID', '正文(body)须非空');
|
|
119
|
+
}
|
|
120
|
+
const rawMemories = args.provenance_memories;
|
|
121
|
+
if (rawMemories !== undefined &&
|
|
122
|
+
(!Array.isArray(rawMemories) ||
|
|
123
|
+
!rawMemories.every((m) => typeof m === 'string') ||
|
|
124
|
+
rawMemories.length > SKILL_PROVENANCE_MEMORIES_MAX)) {
|
|
125
|
+
return toolError('SKILLS_CONTENT_INVALID', `provenance_memories 须字符串数组且 ≤ ${SKILL_PROVENANCE_MEMORIES_MAX} 条`);
|
|
126
|
+
}
|
|
127
|
+
const memories = rawMemories;
|
|
128
|
+
// 同名亮拒不覆写:在册名 ∪ 盘上文件在场(坏文件对 get() 隐身——只查在册名会静默毁文件)
|
|
129
|
+
if (deps.registry.get(name) !== undefined) {
|
|
130
|
+
return toolError('SKILLS_NAME_EXISTS', `技能 ${name} 已在册(${deps.registry.get(name)?.providerId} 层)——create 不覆写,改内容走 patch 或先人面清理`);
|
|
131
|
+
}
|
|
132
|
+
for (const root of deps.registry.scanRoots()) {
|
|
133
|
+
if (existsSync(join(root, name, 'SKILL.md'))) {
|
|
134
|
+
return toolError('SKILLS_NAME_EXISTS', `盘上已有 ${join(root, name, 'SKILL.md')}(可能未入册——坏 frontmatter 文件同算占用)——create 不覆写`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const target = join(deps.workspaceRoot(), '.agents', 'skills', name, 'SKILL.md');
|
|
138
|
+
if (!isWithinRoots(target, deps.writableRoots())) {
|
|
139
|
+
return toolError('SKILLS_WRITE_ROOT_DENIED', `写点 ${target} 不在可写根内(read-only 档或根未授权)——技能创建需可写的工作区`);
|
|
140
|
+
}
|
|
141
|
+
// frontmatter 物化(yaml stringify——描述含冒号/引号/换行均安全)
|
|
142
|
+
const fm = { name, description };
|
|
143
|
+
if (memories !== undefined && memories.length > 0) {
|
|
144
|
+
fm['provenance'] = { memories };
|
|
145
|
+
}
|
|
146
|
+
const fileContent = `---\n${stringifyYaml(fm)}---\n\n${body}\n`;
|
|
147
|
+
mkdirSync(join(deps.workspaceRoot(), '.agents', 'skills', name), { recursive: true });
|
|
148
|
+
writeFileSync(target, fileContent, 'utf8');
|
|
149
|
+
await deps.registry.refresh(); // 写后自动技能面刷新 + 清单重物化(同径)
|
|
150
|
+
return toolText(`已创建技能 ${name}:${target}\n(project 层;技能面已刷新,在册 ${deps.registry.list().length} 件)`);
|
|
151
|
+
}
|
|
152
|
+
/** patch:project 层正文单点 find-replace(域限定 + 单点匹配 + 字面替换 + 归一化) */
|
|
153
|
+
async function executePatch(deps, args) {
|
|
154
|
+
const name = nonEmptyString(args.name);
|
|
155
|
+
const find = nonEmptyString(args.find);
|
|
156
|
+
const replace = typeof args.replace === 'string' ? args.replace : undefined;
|
|
157
|
+
if (name === undefined)
|
|
158
|
+
return toolError('SKILLS_NOT_FOUND', 'patch 须携带技能名(name)');
|
|
159
|
+
if (find === undefined || replace === undefined) {
|
|
160
|
+
return toolError('SKILLS_PATCH_MATCH_INVALID', 'patch 须携带 find(非空)与 replace(可为空串=删除)');
|
|
161
|
+
}
|
|
162
|
+
const skill = deps.registry.get(name);
|
|
163
|
+
if (skill === undefined) {
|
|
164
|
+
return toolError('SKILLS_NOT_FOUND', `技能 ${name} 不在册(先刷新;盘上坏 frontmatter 文件不入册者同报——修坏文件走人面)`);
|
|
165
|
+
}
|
|
166
|
+
const provider = deps.registry.getProvider(skill.providerId);
|
|
167
|
+
if (provider === undefined || provider.writable !== true) {
|
|
168
|
+
return toolError('SKILLS_LAYER_READONLY', `技能 ${name} 属 ${skill.providerId} 层——patch 仅 project 层可改(user/跨库/出厂层是用户自持面,走文件编辑器人面改)`);
|
|
169
|
+
}
|
|
170
|
+
// 校验域与替换域统一:BOM 剥离 + CRLF/CR→LF 归一化整文件(写回归一化全文)
|
|
171
|
+
let raw;
|
|
172
|
+
try {
|
|
173
|
+
raw = readFileSync(skill.filePath, 'utf8');
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return toolError('SKILLS_NOT_FOUND', `技能文件读取失败:${error instanceof Error ? error.message : String(error)}`);
|
|
177
|
+
}
|
|
178
|
+
const unbommed = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
|
|
179
|
+
const normalized = unbommed.replace(/\r\n?/g, '\n');
|
|
180
|
+
const parsed = parseSkillFrontmatter(normalized);
|
|
181
|
+
if ('error' in parsed) {
|
|
182
|
+
return toolError('SKILLS_CONTENT_INVALID', `技能文件不可解析:${parsed.error}`);
|
|
183
|
+
}
|
|
184
|
+
// 替换区间限 frontmatter 闭合 --- 之后(正文域——frontmatter 是结构面不走自由文本改)
|
|
185
|
+
const domain = normalized.slice(parsed.bodyStart);
|
|
186
|
+
const occurrences = domain.split(find).length - 1;
|
|
187
|
+
if (occurrences === 0) {
|
|
188
|
+
return toolError('SKILLS_PATCH_MATCH_INVALID', `find 串在正文域零匹配(frontmatter 域不计入)——检查拼写或换 find 串`);
|
|
189
|
+
}
|
|
190
|
+
if (occurrences > 1) {
|
|
191
|
+
return toolError('SKILLS_PATCH_MATCH_INVALID', `find 串在正文域 ${occurrences} 处匹配——patch 只做单点替换(不猜改点),加长 find 串到唯一`);
|
|
192
|
+
}
|
|
193
|
+
if (!isWithinRoots(skill.filePath, deps.writableRoots())) {
|
|
194
|
+
return toolError('SKILLS_WRITE_ROOT_DENIED', `写点 ${skill.filePath} 不在可写根内(read-only 档或根未授权)——拒绝直写`);
|
|
195
|
+
}
|
|
196
|
+
// 字面替换(split/join——String.replace 的 $&/$`/$' 展开家族不入场)
|
|
197
|
+
const patched = normalized.slice(0, parsed.bodyStart) + domain.split(find).join(replace);
|
|
198
|
+
writeFileSync(skill.filePath, patched, 'utf8');
|
|
199
|
+
await deps.registry.refresh();
|
|
200
|
+
return toolText(`已修改技能 ${name} 正文 1 处(${skill.filePath};技能面已刷新)`);
|
|
201
|
+
}
|