@xiaobuyu/nodesign 0.0.1 → 0.0.2

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 (282) hide show
  1. package/README.md +156 -106
  2. package/bin/nodesign.js +0 -0
  3. package/package.json +1 -1
  4. package/server/api/README.md +45 -45
  5. package/server/api/_guard.js +75 -75
  6. package/server/api/admin.js +187 -187
  7. package/server/api/assets/docx-page.js +103 -103
  8. package/server/api/assets/helpers.js +22 -22
  9. package/server/api/assets/notes.js +144 -144
  10. package/server/api/assets.js +896 -896
  11. package/server/api/board.js +57 -57
  12. package/server/api/browse.js +138 -138
  13. package/server/api/canvas.js +444 -444
  14. package/server/api/chatai.js +324 -324
  15. package/server/api/exports/build-standalone.js +980 -980
  16. package/server/api/exports/cards.js +118 -118
  17. package/server/api/exports/docx-pdf.js +46 -46
  18. package/server/api/exports/export-page.js +142 -142
  19. package/server/api/exports/handoff.js +147 -147
  20. package/server/api/exports.js +690 -690
  21. package/server/api/instruction.js +75 -75
  22. package/server/api/me.js +116 -116
  23. package/server/api/memory.js +185 -185
  24. package/server/api/pending-changes.js +366 -366
  25. package/server/api/plugins.js +164 -164
  26. package/server/api/projects.js +220 -220
  27. package/server/api/publish.js +64 -64
  28. package/server/api/recent.js +73 -73
  29. package/server/api/sessions.js +589 -589
  30. package/server/api/skills.js +77 -77
  31. package/server/api/standalone-fit.js +168 -168
  32. package/server/api/turn-compose.js +180 -180
  33. package/server/api/turn-inflight.js +44 -44
  34. package/server/auth/README.md +17 -17
  35. package/server/auth/middleware.js +138 -138
  36. package/server/auth/origin-guard.js +71 -71
  37. package/server/auth/session.js +108 -108
  38. package/server/auth/tier.js +113 -113
  39. package/server/auth/users-store.js +348 -348
  40. package/server/edit/README.md +29 -29
  41. package/server/engine/README.md +113 -113
  42. package/server/engine/agent/agent-shared.js +550 -550
  43. package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
  44. package/server/engine/agent/auto-mode-rules.js +78 -78
  45. package/server/engine/agent/context.js +418 -418
  46. package/server/engine/agent/events.js +464 -464
  47. package/server/engine/agent/hooks/canvas-validate.js +199 -199
  48. package/server/engine/agent/hooks/failure.js +120 -120
  49. package/server/engine/agent/hooks/file-events.js +75 -75
  50. package/server/engine/agent/hooks/lifecycle.js +233 -233
  51. package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
  52. package/server/engine/agent/hooks/post-guidance.js +206 -206
  53. package/server/engine/agent/hooks/post-subagent-report.js +104 -104
  54. package/server/engine/agent/hooks/post-trim.js +58 -58
  55. package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
  56. package/server/engine/agent/hooks/pre-defaults.js +84 -84
  57. package/server/engine/agent/hooks/pre-injectors.js +277 -277
  58. package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
  59. package/server/engine/agent/hooks/pre-starter-files.js +61 -61
  60. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
  61. package/server/engine/agent/hooks/site-validate.js +94 -94
  62. package/server/engine/agent/hooks/tool-prompts.js +31 -31
  63. package/server/engine/agent/hooks/turn-state-memory.js +56 -56
  64. package/server/engine/agent/hooks/user-prompt-submit.js +215 -215
  65. package/server/engine/agent/hooks.js +354 -354
  66. package/server/engine/agent/init-contract.js +69 -69
  67. package/server/engine/agent/isolation.js +159 -159
  68. package/server/engine/agent/model-table.js +292 -292
  69. package/server/engine/agent/plugin-loader.js +266 -266
  70. package/server/engine/agent/prompts/nodesign-prelude.md +336 -336
  71. package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
  72. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
  73. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
  74. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
  75. package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
  76. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
  77. package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
  78. package/server/engine/agent/prompts/tools/site-reference.md +106 -106
  79. package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
  80. package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
  81. package/server/engine/agent/session-loop.js +1024 -1024
  82. package/server/engine/agent/session-model.js +164 -164
  83. package/server/engine/agent/skill.js +204 -204
  84. package/server/engine/agent/task-events.js +78 -78
  85. package/server/engine/agents/ds-extractor.md +132 -132
  86. package/server/engine/agents/explorer.md +190 -190
  87. package/server/engine/agents/index.js +230 -230
  88. package/server/engine/agents/schemas/design-system.json +156 -156
  89. package/server/engine/agents/schemas/tweak-schema.json +113 -113
  90. package/server/engine/agents/tweak-proposer.md +127 -127
  91. package/server/engine/agents/vision-checker.md +254 -254
  92. package/server/engine/browse/capture.js +422 -422
  93. package/server/engine/browse/card.js +137 -137
  94. package/server/engine/browse/handover.js +59 -59
  95. package/server/engine/browse/page-digest.js +119 -119
  96. package/server/engine/browse/refs.js +196 -196
  97. package/server/engine/browse/registry.js +307 -307
  98. package/server/engine/browse/screencast.js +170 -170
  99. package/server/engine/browse/state.js +121 -121
  100. package/server/engine/chatai/chat-log.js +79 -79
  101. package/server/engine/chatai/index.js +193 -193
  102. package/server/engine/chatai/openai-compat.js +152 -152
  103. package/server/engine/chatai/orchestrate.js +309 -309
  104. package/server/engine/chatai/perform.js +40 -40
  105. package/server/engine/chatai/summarize.js +79 -79
  106. package/server/engine/mcp/capability-gate.js +52 -52
  107. package/server/engine/mcp/index.js +339 -339
  108. package/server/engine/mcp/param-sanitizer.js +142 -142
  109. package/server/engine/mcp/tools/arrange-on-board.js +124 -124
  110. package/server/engine/mcp/tools/artifact-session.js +278 -278
  111. package/server/engine/mcp/tools/browse-computer.js +421 -421
  112. package/server/engine/mcp/tools/browse-find-batch.js +183 -183
  113. package/server/engine/mcp/tools/browse-screenshot.js +157 -157
  114. package/server/engine/mcp/tools/browse.js +480 -480
  115. package/server/engine/mcp/tools/build-docx.js +101 -101
  116. package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
  117. package/server/engine/mcp/tools/create-on-board.js +155 -155
  118. package/server/engine/mcp/tools/crystallize-skill.js +168 -168
  119. package/server/engine/mcp/tools/explain-style.js +237 -237
  120. package/server/engine/mcp/tools/export-handoff.js +133 -133
  121. package/server/engine/mcp/tools/expose-tweaks.js +149 -149
  122. package/server/engine/mcp/tools/generate-image.js +842 -842
  123. package/server/engine/mcp/tools/get-computed-styles.js +164 -164
  124. package/server/engine/mcp/tools/get-pending-changes.js +205 -205
  125. package/server/engine/mcp/tools/h3box-ssh.js +84 -84
  126. package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
  127. package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
  128. package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
  129. package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
  130. package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
  131. package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
  132. package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
  133. package/server/engine/mcp/tools/highlight.js +68 -68
  134. package/server/engine/mcp/tools/list-pages.js +222 -222
  135. package/server/engine/mcp/tools/lookup-tags.js +59 -59
  136. package/server/engine/mcp/tools/navigate-to-page.js +60 -60
  137. package/server/engine/mcp/tools/organize-board.js +64 -64
  138. package/server/engine/mcp/tools/paint-still.js +434 -434
  139. package/server/engine/mcp/tools/pin-to-board.js +130 -130
  140. package/server/engine/mcp/tools/preview-deck.js +130 -130
  141. package/server/engine/mcp/tools/profile-scroll.js +305 -305
  142. package/server/engine/mcp/tools/publish-site.js +102 -102
  143. package/server/engine/mcp/tools/query-elements.js +195 -195
  144. package/server/engine/mcp/tools/read-board.js +103 -103
  145. package/server/engine/mcp/tools/read-document.js +93 -93
  146. package/server/engine/mcp/tools/read-page.js +264 -264
  147. package/server/engine/mcp/tools/read-tavern-json.js +143 -143
  148. package/server/engine/mcp/tools/record-decision.js +140 -140
  149. package/server/engine/mcp/tools/relate-on-board.js +110 -110
  150. package/server/engine/mcp/tools/report-issue.js +115 -115
  151. package/server/engine/mcp/tools/roll-film.js +266 -266
  152. package/server/engine/mcp/tools/screenshot-docx.js +138 -138
  153. package/server/engine/mcp/tools/screenshot-url.js +195 -195
  154. package/server/engine/mcp/tools/screenshot.js +583 -583
  155. package/server/engine/mcp/tools/tier-gate.js +96 -96
  156. package/server/engine/mcp/tools/trace-motion.js +215 -215
  157. package/server/engine/mcp/tools/web-search.js +548 -548
  158. package/server/engine/motion/inventory.js +349 -349
  159. package/server/engine/perception/session.js +235 -235
  160. package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
  161. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -192
  162. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
  163. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
  164. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
  165. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
  166. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
  167. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
  168. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
  169. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -162
  170. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
  171. package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
  172. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -228
  173. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -78
  174. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
  175. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -323
  176. package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
  177. package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
  178. package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
  179. package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
  180. package/server/engine/runs/active-runs.js +698 -698
  181. package/server/engine/runs/live-turn.js +252 -252
  182. package/server/engine/runs/store.js +364 -364
  183. package/server/engine/runs/turn-relay.js +217 -217
  184. package/server/engine/runtime/workspace.js +164 -164
  185. package/server/lib/artifact-file-path.js +80 -80
  186. package/server/lib/artifact-target.js +284 -284
  187. package/server/lib/asset-refs.js +239 -239
  188. package/server/lib/async-queue.js +104 -104
  189. package/server/lib/auto-relations.js +130 -130
  190. package/server/lib/binding-types.js +60 -60
  191. package/server/lib/board-kind-sizes.js +62 -62
  192. package/server/lib/board-relations.js +133 -133
  193. package/server/lib/browse-proxy.js +180 -180
  194. package/server/lib/canvas-id.js +36 -36
  195. package/server/lib/cover.js +227 -227
  196. package/server/lib/danbooru-tags.js +291 -291
  197. package/server/lib/doc-extract.js +156 -156
  198. package/server/lib/docx/build-from-source.js +260 -260
  199. package/server/lib/docx/build.js +490 -490
  200. package/server/lib/docx/dump-styles.js +287 -287
  201. package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
  202. package/server/lib/docx/merge-runs.js +129 -129
  203. package/server/lib/docx/numbering.js +218 -218
  204. package/server/lib/docx/order.js +152 -152
  205. package/server/lib/docx/rawzip.js +172 -172
  206. package/server/lib/docx/render.js +92 -92
  207. package/server/lib/docx/text-lint.js +225 -225
  208. package/server/lib/docx/tokens.js +335 -335
  209. package/server/lib/docx/units.js +29 -29
  210. package/server/lib/docx/xml.js +291 -291
  211. package/server/lib/docx-pages.js +187 -187
  212. package/server/lib/export-collect.js +289 -289
  213. package/server/lib/export-package.js +210 -210
  214. package/server/lib/html-srcset.js +145 -145
  215. package/server/lib/image-variant.js +460 -460
  216. package/server/lib/ingress/forward-openai-chat.js +305 -305
  217. package/server/lib/ingress/openai-chat.js +491 -491
  218. package/server/lib/ingress/session-notice.js +66 -66
  219. package/server/lib/ingress/session-routes.js +68 -68
  220. package/server/lib/ingress/slot-probe.js +130 -130
  221. package/server/lib/ingress/upstream-billing.js +52 -52
  222. package/server/lib/ingress/upstream-fail-streak.js +72 -72
  223. package/server/lib/ingress/upstream-truncation.js +48 -48
  224. package/server/lib/issues-store.js +182 -182
  225. package/server/lib/kinds/deck.js +72 -72
  226. package/server/lib/kinds/docx.js +283 -283
  227. package/server/lib/kinds/file-kinds.js +113 -113
  228. package/server/lib/kinds/index.js +202 -202
  229. package/server/lib/kinds/site.js +161 -161
  230. package/server/lib/model-ingress.js +543 -543
  231. package/server/lib/moderation.js +255 -255
  232. package/server/lib/notice-store.js +103 -103
  233. package/server/lib/plugin-install.js +104 -104
  234. package/server/lib/plugin-validator.js +721 -721
  235. package/server/lib/publish-store.js +115 -115
  236. package/server/lib/quick-summary.js +52 -52
  237. package/server/lib/quota.js +270 -270
  238. package/server/lib/rate-window.js +29 -29
  239. package/server/lib/reference-assets.js +92 -92
  240. package/server/lib/region-shot.js +135 -135
  241. package/server/lib/safe-path.js +73 -73
  242. package/server/lib/sdk-session.js +33 -33
  243. package/server/lib/showcase-store.js +92 -92
  244. package/server/lib/site-publish.js +465 -465
  245. package/server/lib/ssrf-guard.js +432 -432
  246. package/server/lib/task-scan.js +158 -158
  247. package/server/lib/tavern-json.js +154 -154
  248. package/server/lib/video-variant.js +237 -237
  249. package/server/lib/workspace-path.js +33 -33
  250. package/server/ops/fix-sdk-musl.mjs +40 -40
  251. package/server/ops/install-macos-fonts.sh +114 -114
  252. package/server/ops/macos-fonts.conf +336 -336
  253. package/server/ops/sandbox-shim/bwrap +57 -57
  254. package/server/projects/assets-summary.js +119 -119
  255. package/server/projects/auto-name.js +53 -53
  256. package/server/projects/board-store.js +640 -640
  257. package/server/projects/move-entry.js +103 -103
  258. package/server/projects/store.js +270 -270
  259. package/server/projects/ui-config.js +54 -54
  260. package/server/projects/workspace-templates.js +53 -53
  261. package/server/projects/workspace.js +1025 -1025
  262. package/server/runtime/local-config.js +177 -177
  263. package/server/runtime/profile.js +72 -72
  264. package/server/services/rembg-service.py +334 -334
  265. package/server/shared/README.md +25 -25
  266. package/server/shared/deck.js +54 -54
  267. package/server/shared/time.js +49 -49
  268. package/server/style-pipeline/README.md +53 -53
  269. package/server/ws/broker.js +36 -36
  270. package/server/ws/browse-channel.js +177 -177
  271. package/server/ws/index.js +386 -386
  272. package/web/README.md +89 -89
  273. package/web/dist/assets/BrowserWindow-BYCgizHw.js +11 -0
  274. package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-pNydEM4L.js} +1 -1
  275. package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-PPgzh3Gq.js} +1 -1
  276. package/web/dist/assets/{SiteWindow-D6eS-2gM.js → SiteWindow-DM0LQPCO.js} +1 -1
  277. package/web/dist/assets/{index-Cbl3ATvp.js → index-BHTBJlpY.js} +32 -32
  278. package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-C0I13CbD.js} +1 -1
  279. package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-B1UQ8Hld.js} +1 -1
  280. package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-8lrgqmfv.js} +1 -1
  281. package/web/dist/index.html +17 -17
  282. package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
@@ -1,204 +1,204 @@
1
- /**
2
- * skill.js — Skill 加载器(YAML frontmatter + Markdown body)
3
- *
4
- * 目录结构(2026-05-18 起,对齐 Claude Code plugin convention):
5
- * server/engine/plugins/nodesign/
6
- * .claude-plugin/plugin.json — plugin 元数据
7
- * skills/<skill-id>/SKILL.md — 各 skill
8
- * skills/<skill-id>/<其他起手文件>
9
- *
10
- * SKILL.md 格式(YAML frontmatter + body):
11
- * ---
12
- * name: deskskill-engine-mini
13
- * version: 0.0.1
14
- * description: 给 LLM 看的"什么时候用我"的描述
15
- * ---
16
- *
17
- * # System Prompt
18
- *
19
- * 你是一个 deck 设计 agent...(任意 markdown)
20
- *
21
- * 本模块职责:
22
- * - 解析 SKILL.md 的 YAML frontmatter + body(loadSkill)
23
- * - 把 skill 起手文件(canvas.template.html 等)拷进 session cwd(ensureSkillStarterFiles)
24
- *
25
- * 注:批量扫 plugin 下所有 skill 走 plugin-loader.js `listInstalledPluginsDetailed`,
26
- * 不在本模块(API 层 server/api/skills.js 用那个)。
27
- *
28
- * SDK 集成(在 session-loop.js):
29
- * - plugins: [{ type: 'local', path: PLUGIN_ROOT }] → 让 SDK 识别本 plugin
30
- * - skills: [<skill-id>] → 让 SDK 把 description 注入 system prompt
31
- * - SKILL.md body 通过 SDK 内置 Skill 工具按需加载(不再走 systemPrompt.append 恒驻)
32
- */
33
-
34
- import { promises as fs } from 'fs';
35
- import path from 'path';
36
- import { fileURLToPath } from 'url';
37
-
38
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
39
-
40
- /** Plugin 根目录(含 .claude-plugin/plugin.json + skills/)*/
41
- export const PLUGIN_ROOT = path.resolve(
42
- process.env.NODESIGN_PLUGIN_DIR || path.join(__dirname, '../plugins/nodesign')
43
- );
44
-
45
- /** Skills 子目录(plugin convention:<plugin>/skills/<skill-id>/SKILL.md)*/
46
- export const SKILLS_ROOT = path.resolve(
47
- process.env.NODESIGN_SKILLS_DIR || path.join(PLUGIN_ROOT, 'skills')
48
- );
49
-
50
- /**
51
- * 加载一个 skill。返回 { id, name, version, description, systemPrompt, raw }。
52
- * 找不到时抛 Error(code='SKILL_NOT_FOUND')。
53
- */
54
- export async function loadSkill(skillId) {
55
- if (!skillId || typeof skillId !== 'string') {
56
- throw Object.assign(new Error('loadSkill: skillId required'), { code: 'INVALID_SKILL_ID' });
57
- }
58
-
59
- const skillDir = path.join(SKILLS_ROOT, skillId);
60
- const skillFile = path.join(skillDir, 'SKILL.md');
61
-
62
- let raw;
63
- try {
64
- raw = await fs.readFile(skillFile, 'utf8');
65
- } catch (err) {
66
- if (err.code === 'ENOENT') {
67
- throw Object.assign(
68
- new Error(`skill not found: ${skillId} (expected at ${skillFile})`),
69
- { code: 'SKILL_NOT_FOUND' }
70
- );
71
- }
72
- throw err;
73
- }
74
-
75
- const { frontmatter, body } = parseFrontmatter(raw);
76
-
77
- return {
78
- id: skillId,
79
- name: frontmatter.name || skillId,
80
- version: frontmatter.version || '0.0.0',
81
- description: frontmatter.description || '',
82
- systemPrompt: body.trim(),
83
- frontmatter,
84
- raw,
85
- };
86
- }
87
-
88
- /**
89
- * 把 skill 自带的工作区起手文件(目前主要是 canvas.template.html)拷贝
90
- * 进 session cwd,让 agent 能直接 `Read canvas.template.html` 而不必去
91
- * 仓库相对路径找(agent 看不到 server/engine/plugins/nodesign/skills/)。
92
- *
93
- * **拷贝不软链**:
94
- * - 跨平台稳(Windows symlink 要 admin / Linux bwrap 不解析 symlink)
95
- * - 防 agent 误 Edit 改源模板(影响其他 session)
96
- * - session 是一次性沙盒,拷贝快照语义合理
97
- *
98
- * **幂等**:文件已存在就不覆盖(agent 改过的、或上次 session init 拷过的不动)。
99
- *
100
- * **起手文件是自动发现的**(2026-07-28 从硬编码 `['canvas.template.html']` 改来):
101
- * skill 目录里凡是文件名带 `.template.` 的都算起手文件。加第二个 skill
102
- * (站点)时不必再回来改这里 —— 硬编码一个文件名意味着新 skill 的模板永远
103
- * 拷不出来,而且是静默的:agent 只会 `Read` 失败然后凭空写。
104
- *
105
- * @param {string} sessionRoot - sessions/<sid>/ 绝对路径
106
- * @param {string} skillId - 同 loadSkill 的 skillId
107
- * @returns {Promise<{ copied: string[], skipped: string[] }>}
108
- */
109
- /** 内置 plugin 里现有的 skill id(目录名即 id) */
110
- export async function listSkillIds() {
111
- try {
112
- const entries = await fs.readdir(SKILLS_ROOT, { withFileTypes: true });
113
- return entries.filter(e => e.isDirectory() && !e.name.startsWith('.')).map(e => e.name);
114
- } catch { return []; }
115
- }
116
-
117
- export async function listSkillStarterFiles(skillId) {
118
- if (!skillId) return [];
119
- try {
120
- const entries = await fs.readdir(path.join(SKILLS_ROOT, skillId), { withFileTypes: true });
121
- return entries
122
- .filter(e => e.isFile() && e.name.includes('.template.'))
123
- .map(e => e.name)
124
- .sort();
125
- } catch { return []; }
126
- }
127
-
128
- export async function ensureSkillStarterFiles(sessionRoot, skillId) {
129
- const result = { copied: [], skipped: [] };
130
- if (!sessionRoot || !skillId) return result;
131
-
132
- const skillDir = path.join(SKILLS_ROOT, skillId);
133
- const starterFiles = await listSkillStarterFiles(skillId);
134
-
135
- for (const name of starterFiles) {
136
- const src = path.join(skillDir, name);
137
- const dst = path.join(sessionRoot, name);
138
-
139
- let srcExists = false;
140
- try {
141
- await fs.access(src);
142
- srcExists = true;
143
- } catch { /* skill 没这个起手文件 */ }
144
- if (!srcExists) continue;
145
-
146
- let dstExists = false;
147
- try {
148
- await fs.access(dst);
149
- dstExists = true;
150
- } catch { /* dst 不存在,需要拷 */ }
151
- if (dstExists) {
152
- result.skipped.push(name);
153
- continue;
154
- }
155
-
156
- try {
157
- await fs.copyFile(src, dst);
158
- result.copied.push(name);
159
- } catch (err) {
160
- console.warn(
161
- `[skill] copy ${name} failed (${err.code || err.message}); `
162
- + `agent will not have it in cwd`,
163
- );
164
- }
165
- }
166
-
167
- return result;
168
- }
169
-
170
- // ── frontmatter 解析(极简 YAML:只支持 key: value)──
171
-
172
- const FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
173
-
174
- /**
175
- * 解析 SKILL.md 的 frontmatter + body。
176
- * 不支持嵌套 / 多行 / list — 只接受 `key: value` 形态。
177
- *
178
- * 没 frontmatter 的文件:返回 { frontmatter: {}, body: <整个文件> }
179
- */
180
- function parseFrontmatter(raw) {
181
- const m = FRONTMATTER_RE.exec(raw);
182
- if (!m) return { frontmatter: {}, body: raw };
183
-
184
- const [, yamlText, body] = m;
185
- const frontmatter = {};
186
- for (const line of yamlText.split('\n')) {
187
- const trimmed = line.trim();
188
- if (!trimmed || trimmed.startsWith('#')) continue;
189
- const colonIdx = trimmed.indexOf(':');
190
- if (colonIdx < 1) continue;
191
- const key = trimmed.slice(0, colonIdx).trim();
192
- let value = trimmed.slice(colonIdx + 1).trim();
193
- // 去掉成对引号
194
- if ((value.startsWith('"') && value.endsWith('"')) ||
195
- (value.startsWith("'") && value.endsWith("'"))) {
196
- value = value.slice(1, -1);
197
- }
198
- frontmatter[key] = value;
199
- }
200
- return { frontmatter, body: body || '' };
201
- }
202
-
203
- // 暴露供单测
204
- export { parseFrontmatter };
1
+ /**
2
+ * skill.js — Skill 加载器(YAML frontmatter + Markdown body)
3
+ *
4
+ * 目录结构(2026-05-18 起,对齐 Claude Code plugin convention):
5
+ * server/engine/plugins/nodesign/
6
+ * .claude-plugin/plugin.json — plugin 元数据
7
+ * skills/<skill-id>/SKILL.md — 各 skill
8
+ * skills/<skill-id>/<其他起手文件>
9
+ *
10
+ * SKILL.md 格式(YAML frontmatter + body):
11
+ * ---
12
+ * name: deskskill-engine-mini
13
+ * version: 0.0.1
14
+ * description: 给 LLM 看的"什么时候用我"的描述
15
+ * ---
16
+ *
17
+ * # System Prompt
18
+ *
19
+ * 你是一个 deck 设计 agent...(任意 markdown)
20
+ *
21
+ * 本模块职责:
22
+ * - 解析 SKILL.md 的 YAML frontmatter + body(loadSkill)
23
+ * - 把 skill 起手文件(canvas.template.html 等)拷进 session cwd(ensureSkillStarterFiles)
24
+ *
25
+ * 注:批量扫 plugin 下所有 skill 走 plugin-loader.js `listInstalledPluginsDetailed`,
26
+ * 不在本模块(API 层 server/api/skills.js 用那个)。
27
+ *
28
+ * SDK 集成(在 session-loop.js):
29
+ * - plugins: [{ type: 'local', path: PLUGIN_ROOT }] → 让 SDK 识别本 plugin
30
+ * - skills: [<skill-id>] → 让 SDK 把 description 注入 system prompt
31
+ * - SKILL.md body 通过 SDK 内置 Skill 工具按需加载(不再走 systemPrompt.append 恒驻)
32
+ */
33
+
34
+ import { promises as fs } from 'fs';
35
+ import path from 'path';
36
+ import { fileURLToPath } from 'url';
37
+
38
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
39
+
40
+ /** Plugin 根目录(含 .claude-plugin/plugin.json + skills/)*/
41
+ export const PLUGIN_ROOT = path.resolve(
42
+ process.env.NODESIGN_PLUGIN_DIR || path.join(__dirname, '../plugins/nodesign')
43
+ );
44
+
45
+ /** Skills 子目录(plugin convention:<plugin>/skills/<skill-id>/SKILL.md)*/
46
+ export const SKILLS_ROOT = path.resolve(
47
+ process.env.NODESIGN_SKILLS_DIR || path.join(PLUGIN_ROOT, 'skills')
48
+ );
49
+
50
+ /**
51
+ * 加载一个 skill。返回 { id, name, version, description, systemPrompt, raw }。
52
+ * 找不到时抛 Error(code='SKILL_NOT_FOUND')。
53
+ */
54
+ export async function loadSkill(skillId) {
55
+ if (!skillId || typeof skillId !== 'string') {
56
+ throw Object.assign(new Error('loadSkill: skillId required'), { code: 'INVALID_SKILL_ID' });
57
+ }
58
+
59
+ const skillDir = path.join(SKILLS_ROOT, skillId);
60
+ const skillFile = path.join(skillDir, 'SKILL.md');
61
+
62
+ let raw;
63
+ try {
64
+ raw = await fs.readFile(skillFile, 'utf8');
65
+ } catch (err) {
66
+ if (err.code === 'ENOENT') {
67
+ throw Object.assign(
68
+ new Error(`skill not found: ${skillId} (expected at ${skillFile})`),
69
+ { code: 'SKILL_NOT_FOUND' }
70
+ );
71
+ }
72
+ throw err;
73
+ }
74
+
75
+ const { frontmatter, body } = parseFrontmatter(raw);
76
+
77
+ return {
78
+ id: skillId,
79
+ name: frontmatter.name || skillId,
80
+ version: frontmatter.version || '0.0.0',
81
+ description: frontmatter.description || '',
82
+ systemPrompt: body.trim(),
83
+ frontmatter,
84
+ raw,
85
+ };
86
+ }
87
+
88
+ /**
89
+ * 把 skill 自带的工作区起手文件(目前主要是 canvas.template.html)拷贝
90
+ * 进 session cwd,让 agent 能直接 `Read canvas.template.html` 而不必去
91
+ * 仓库相对路径找(agent 看不到 server/engine/plugins/nodesign/skills/)。
92
+ *
93
+ * **拷贝不软链**:
94
+ * - 跨平台稳(Windows symlink 要 admin / Linux bwrap 不解析 symlink)
95
+ * - 防 agent 误 Edit 改源模板(影响其他 session)
96
+ * - session 是一次性沙盒,拷贝快照语义合理
97
+ *
98
+ * **幂等**:文件已存在就不覆盖(agent 改过的、或上次 session init 拷过的不动)。
99
+ *
100
+ * **起手文件是自动发现的**(2026-07-28 从硬编码 `['canvas.template.html']` 改来):
101
+ * skill 目录里凡是文件名带 `.template.` 的都算起手文件。加第二个 skill
102
+ * (站点)时不必再回来改这里 —— 硬编码一个文件名意味着新 skill 的模板永远
103
+ * 拷不出来,而且是静默的:agent 只会 `Read` 失败然后凭空写。
104
+ *
105
+ * @param {string} sessionRoot - sessions/<sid>/ 绝对路径
106
+ * @param {string} skillId - 同 loadSkill 的 skillId
107
+ * @returns {Promise<{ copied: string[], skipped: string[] }>}
108
+ */
109
+ /** 内置 plugin 里现有的 skill id(目录名即 id) */
110
+ export async function listSkillIds() {
111
+ try {
112
+ const entries = await fs.readdir(SKILLS_ROOT, { withFileTypes: true });
113
+ return entries.filter(e => e.isDirectory() && !e.name.startsWith('.')).map(e => e.name);
114
+ } catch { return []; }
115
+ }
116
+
117
+ export async function listSkillStarterFiles(skillId) {
118
+ if (!skillId) return [];
119
+ try {
120
+ const entries = await fs.readdir(path.join(SKILLS_ROOT, skillId), { withFileTypes: true });
121
+ return entries
122
+ .filter(e => e.isFile() && e.name.includes('.template.'))
123
+ .map(e => e.name)
124
+ .sort();
125
+ } catch { return []; }
126
+ }
127
+
128
+ export async function ensureSkillStarterFiles(sessionRoot, skillId) {
129
+ const result = { copied: [], skipped: [] };
130
+ if (!sessionRoot || !skillId) return result;
131
+
132
+ const skillDir = path.join(SKILLS_ROOT, skillId);
133
+ const starterFiles = await listSkillStarterFiles(skillId);
134
+
135
+ for (const name of starterFiles) {
136
+ const src = path.join(skillDir, name);
137
+ const dst = path.join(sessionRoot, name);
138
+
139
+ let srcExists = false;
140
+ try {
141
+ await fs.access(src);
142
+ srcExists = true;
143
+ } catch { /* skill 没这个起手文件 */ }
144
+ if (!srcExists) continue;
145
+
146
+ let dstExists = false;
147
+ try {
148
+ await fs.access(dst);
149
+ dstExists = true;
150
+ } catch { /* dst 不存在,需要拷 */ }
151
+ if (dstExists) {
152
+ result.skipped.push(name);
153
+ continue;
154
+ }
155
+
156
+ try {
157
+ await fs.copyFile(src, dst);
158
+ result.copied.push(name);
159
+ } catch (err) {
160
+ console.warn(
161
+ `[skill] copy ${name} failed (${err.code || err.message}); `
162
+ + `agent will not have it in cwd`,
163
+ );
164
+ }
165
+ }
166
+
167
+ return result;
168
+ }
169
+
170
+ // ── frontmatter 解析(极简 YAML:只支持 key: value)──
171
+
172
+ const FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
173
+
174
+ /**
175
+ * 解析 SKILL.md 的 frontmatter + body。
176
+ * 不支持嵌套 / 多行 / list — 只接受 `key: value` 形态。
177
+ *
178
+ * 没 frontmatter 的文件:返回 { frontmatter: {}, body: <整个文件> }
179
+ */
180
+ function parseFrontmatter(raw) {
181
+ const m = FRONTMATTER_RE.exec(raw);
182
+ if (!m) return { frontmatter: {}, body: raw };
183
+
184
+ const [, yamlText, body] = m;
185
+ const frontmatter = {};
186
+ for (const line of yamlText.split('\n')) {
187
+ const trimmed = line.trim();
188
+ if (!trimmed || trimmed.startsWith('#')) continue;
189
+ const colonIdx = trimmed.indexOf(':');
190
+ if (colonIdx < 1) continue;
191
+ const key = trimmed.slice(0, colonIdx).trim();
192
+ let value = trimmed.slice(colonIdx + 1).trim();
193
+ // 去掉成对引号
194
+ if ((value.startsWith('"') && value.endsWith('"')) ||
195
+ (value.startsWith("'") && value.endsWith("'"))) {
196
+ value = value.slice(1, -1);
197
+ }
198
+ frontmatter[key] = value;
199
+ }
200
+ return { frontmatter, body: body || '' };
201
+ }
202
+
203
+ // 暴露供单测
204
+ export { parseFrontmatter };
@@ -1,78 +1,78 @@
1
- /**
2
- * task-events.js — SDK 统一任务消息族(task_*)的收口翻译(2026-08-18,
3
- * 从 agent-shared.js handleSystemMessage 拆出)
4
- *
5
- * SDK 0.3.x 起「任务」统一化:task_* 消息不再只代表 Task 子代理 —— 后台
6
- * Bash(task_type 'local_bash')、Workflow('local_workflow')等也走同一条。
7
- * 我们 run.task.* 事件的语义是「子代理」,在这个咽喉点收口:只放真子代理
8
- * 过去,其余不进事件流。不收口的话每条 bash 命令都会在前端顶着「子代理」
9
- * 的名头出现(舞台便利贴/在场徽记/侧栏状态位全线噪音,2026-08-18 事故)。
10
- */
11
-
12
- import { Events } from './events.js';
13
- import { recordTaskNotification } from './hooks/post-subagent-report.js';
14
-
15
- /**
16
- * 这条 task_* 消息说的是不是真子代理(Task 工具)。靠 subagent_type /
17
- * task_type 区分;skip_transcript 是 SDK 标的 ambient/housekeeping 任务,
18
- * 按 SDK 约定对用户隐藏。
19
- */
20
- export function isSubagentTask(msg) {
21
- if (msg.skip_transcript) return false;
22
- return !!msg.subagent_type || msg.task_type === 'local_agent';
23
- }
24
-
25
- /**
26
- * 本轮已放行的子代理 task_id —— task_progress / task_notification 不带
27
- * subagent_type,靠它过滤非子代理任务。挂在 ctx 上惰性建:task_* 消息
28
- * 不带 parent_tool_use_id,进来的恒是真 ctx(不是子代理的派生 ctx),
29
- * own property 不会写丢。
30
- */
31
- function subagentTaskIds(ctx) {
32
- if (!ctx._subagentTaskIds) ctx._subagentTaskIds = new Set();
33
- return ctx._subagentTaskIds;
34
- }
35
-
36
- /** handleSystemMessage 的 task_* 四兄弟统一入口 */
37
- export function handleTaskMessage(ctx, msg) {
38
- switch (msg.subtype) {
39
- case 'task_started':
40
- if (!isSubagentTask(msg)) return;
41
- subagentTaskIds(ctx).add(msg.task_id);
42
- ctx.emit(Events.taskStarted(
43
- msg.task_id, msg.description, msg.subagent_type, msg.task_type, msg.prompt, msg.tool_use_id,
44
- ));
45
- return;
46
-
47
- case 'task_progress':
48
- // agentProgressSummaries: true 时每 ~30s 一次:"正在调整字号节奏" 之类
49
- if (!subagentTaskIds(ctx).has(msg.task_id)) return;
50
- ctx.emit(Events.taskProgress(
51
- msg.task_id, msg.description, msg.summary, msg.last_tool_name, msg.usage, msg.tool_use_id,
52
- ));
53
- return;
54
-
55
- case 'task_updated':
56
- if (!subagentTaskIds(ctx).has(msg.task_id)) return;
57
- ctx.emit(Events.taskUpdated(msg.task_id, msg.patch, msg.tool_use_id));
58
- return;
59
-
60
- case 'task_notification':
61
- // 收尾用量记账不分任务类型(进 metadata 可观测,不进主 token 列 ——
62
- // 防止与 result.usage 双重计数,详见 context.absorbSubagentUsage):
63
- // 将来若接 workflow 之类带真用量的本地任务,账也不能漏。
64
- ctx.absorbSubagentUsage?.(msg.usage);
65
- // 报告丢失的兜底也在闸**前**:把 output_file 记下来,PostToolUse(Agent)
66
- // 的 handler 在报告看起来是空的时候把转录路径递给主 agent(见
67
- // hooks/post-subagent-report.js)。它按 tool_use_id 查、只被 Agent 工具
68
- // 问到,多记非子代理任务无害;安全网不该依赖过滤闸的状态(存储有界 200 条)。
69
- recordTaskNotification(msg);
70
- if (!subagentTaskIds(ctx).has(msg.task_id)) return;
71
- ctx.emit(Events.taskNotification(
72
- msg.task_id, msg.status, msg.summary, msg.usage, msg.tool_use_id,
73
- ));
74
- return;
75
-
76
- default:
77
- }
78
- }
1
+ /**
2
+ * task-events.js — SDK 统一任务消息族(task_*)的收口翻译(2026-08-18,
3
+ * 从 agent-shared.js handleSystemMessage 拆出)
4
+ *
5
+ * SDK 0.3.x 起「任务」统一化:task_* 消息不再只代表 Task 子代理 —— 后台
6
+ * Bash(task_type 'local_bash')、Workflow('local_workflow')等也走同一条。
7
+ * 我们 run.task.* 事件的语义是「子代理」,在这个咽喉点收口:只放真子代理
8
+ * 过去,其余不进事件流。不收口的话每条 bash 命令都会在前端顶着「子代理」
9
+ * 的名头出现(舞台便利贴/在场徽记/侧栏状态位全线噪音,2026-08-18 事故)。
10
+ */
11
+
12
+ import { Events } from './events.js';
13
+ import { recordTaskNotification } from './hooks/post-subagent-report.js';
14
+
15
+ /**
16
+ * 这条 task_* 消息说的是不是真子代理(Task 工具)。靠 subagent_type /
17
+ * task_type 区分;skip_transcript 是 SDK 标的 ambient/housekeeping 任务,
18
+ * 按 SDK 约定对用户隐藏。
19
+ */
20
+ export function isSubagentTask(msg) {
21
+ if (msg.skip_transcript) return false;
22
+ return !!msg.subagent_type || msg.task_type === 'local_agent';
23
+ }
24
+
25
+ /**
26
+ * 本轮已放行的子代理 task_id —— task_progress / task_notification 不带
27
+ * subagent_type,靠它过滤非子代理任务。挂在 ctx 上惰性建:task_* 消息
28
+ * 不带 parent_tool_use_id,进来的恒是真 ctx(不是子代理的派生 ctx),
29
+ * own property 不会写丢。
30
+ */
31
+ function subagentTaskIds(ctx) {
32
+ if (!ctx._subagentTaskIds) ctx._subagentTaskIds = new Set();
33
+ return ctx._subagentTaskIds;
34
+ }
35
+
36
+ /** handleSystemMessage 的 task_* 四兄弟统一入口 */
37
+ export function handleTaskMessage(ctx, msg) {
38
+ switch (msg.subtype) {
39
+ case 'task_started':
40
+ if (!isSubagentTask(msg)) return;
41
+ subagentTaskIds(ctx).add(msg.task_id);
42
+ ctx.emit(Events.taskStarted(
43
+ msg.task_id, msg.description, msg.subagent_type, msg.task_type, msg.prompt, msg.tool_use_id,
44
+ ));
45
+ return;
46
+
47
+ case 'task_progress':
48
+ // agentProgressSummaries: true 时每 ~30s 一次:"正在调整字号节奏" 之类
49
+ if (!subagentTaskIds(ctx).has(msg.task_id)) return;
50
+ ctx.emit(Events.taskProgress(
51
+ msg.task_id, msg.description, msg.summary, msg.last_tool_name, msg.usage, msg.tool_use_id,
52
+ ));
53
+ return;
54
+
55
+ case 'task_updated':
56
+ if (!subagentTaskIds(ctx).has(msg.task_id)) return;
57
+ ctx.emit(Events.taskUpdated(msg.task_id, msg.patch, msg.tool_use_id));
58
+ return;
59
+
60
+ case 'task_notification':
61
+ // 收尾用量记账不分任务类型(进 metadata 可观测,不进主 token 列 ——
62
+ // 防止与 result.usage 双重计数,详见 context.absorbSubagentUsage):
63
+ // 将来若接 workflow 之类带真用量的本地任务,账也不能漏。
64
+ ctx.absorbSubagentUsage?.(msg.usage);
65
+ // 报告丢失的兜底也在闸**前**:把 output_file 记下来,PostToolUse(Agent)
66
+ // 的 handler 在报告看起来是空的时候把转录路径递给主 agent(见
67
+ // hooks/post-subagent-report.js)。它按 tool_use_id 查、只被 Agent 工具
68
+ // 问到,多记非子代理任务无害;安全网不该依赖过滤闸的状态(存储有界 200 条)。
69
+ recordTaskNotification(msg);
70
+ if (!subagentTaskIds(ctx).has(msg.task_id)) return;
71
+ ctx.emit(Events.taskNotification(
72
+ msg.task_id, msg.status, msg.summary, msg.usage, msg.tool_use_id,
73
+ ));
74
+ return;
75
+
76
+ default:
77
+ }
78
+ }