@sema-agent/core 7.51.2 → 7.52.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 (363) hide show
  1. package/CHANGELOG.md +504 -2
  2. package/dist/agents/agent-transcript-tool.d.ts +1 -1
  3. package/dist/agents/cascade.js +4 -0
  4. package/dist/agents/cumulative-stats.d.ts +3 -0
  5. package/dist/agents/cumulative-stats.js +3 -0
  6. package/dist/agents/list-agents-tool.d.ts +1 -1
  7. package/dist/agents/read-inbox-tool.d.ts +1 -1
  8. package/dist/agents/repair-loop.js +4 -0
  9. package/dist/agents/send-message-tool.d.ts +11 -16
  10. package/dist/agents/send-message-tool.js +11 -13
  11. package/dist/agents/subagent.js +6 -4
  12. package/dist/agents/tool-filter.d.ts +40 -13
  13. package/dist/agents/tool-filter.js +28 -14
  14. package/dist/bench/metrics.d.ts +1 -0
  15. package/dist/brain/anthropic.js +16 -12
  16. package/dist/brain/model-presets.d.ts +4 -5
  17. package/dist/brain/model-presets.js +4 -4
  18. package/dist/brain/open-responses.js +16 -12
  19. package/dist/brain/openai.js +16 -12
  20. package/dist/brain/request-params.d.ts +12 -33
  21. package/dist/brain/request-params.js +5 -2
  22. package/dist/brain/request-route.d.ts +9 -8
  23. package/dist/brain/request-route.js +1 -0
  24. package/dist/brain/route-adjudicator.d.ts +117 -47
  25. package/dist/brain/route-adjudicator.js +171 -42
  26. package/dist/brain/route-conformance.js +2 -2
  27. package/dist/brain/status-sink.d.ts +18 -0
  28. package/dist/brain/status-sink.js +7 -0
  29. package/dist/core/allow-entry.d.ts +40 -8
  30. package/dist/core/allow-entry.js +23 -16
  31. package/dist/core/auto-compaction.d.ts +23 -20
  32. package/dist/core/auto-compaction.js +37 -16
  33. package/dist/core/auto-promote.d.ts +2 -1
  34. package/dist/core/auto-promote.js +2 -2
  35. package/dist/core/caller-tool-name.d.ts +9 -0
  36. package/dist/core/caller-tool-name.js +13 -0
  37. package/dist/core/checkpoint-requirements.d.ts +7 -1
  38. package/dist/core/checkpoint-requirements.js +2 -0
  39. package/dist/core/checkpoint-store.d.ts +36 -12
  40. package/dist/core/checkpoint-store.js +1 -1
  41. package/dist/core/child-clamp-seats.d.ts +6 -0
  42. package/dist/core/child-clamp-seats.js +2 -1
  43. package/dist/core/credential-vocabulary.d.ts +33 -8
  44. package/dist/core/credential-vocabulary.js +32 -7
  45. package/dist/core/decision-receipt.d.ts +2 -2
  46. package/dist/core/delegation-frames.d.ts +9 -2
  47. package/dist/core/delegation-name.d.ts +18 -0
  48. package/dist/core/delegation-name.js +47 -0
  49. package/dist/core/engine-capabilities.d.ts +8 -1
  50. package/dist/core/engine-capabilities.js +1 -1
  51. package/dist/core/engine-notice.d.ts +36 -19
  52. package/dist/core/frontmatter-fence.d.ts +10 -0
  53. package/dist/core/frontmatter-fence.js +6 -0
  54. package/dist/core/gate-fold.js +5 -23
  55. package/dist/core/gate-lanes.js +356 -270
  56. package/dist/core/governance-codes.d.ts +9 -4
  57. package/dist/core/governance-codes.js +9 -3
  58. package/dist/core/hooks.d.ts +276 -734
  59. package/dist/core/hooks.js +97 -192
  60. package/dist/{tools/fs → core}/image-admission.d.ts +21 -3
  61. package/dist/{tools/fs → core}/image-admission.js +24 -6
  62. package/dist/core/mcp-connection-pool.d.ts +4 -1
  63. package/dist/core/mcp-connection-pool.js +7 -2
  64. package/dist/core/mcp-probe.d.ts +12 -9
  65. package/dist/core/mcp-probe.js +2 -2
  66. package/dist/core/mcp-server-spec.d.ts +16 -0
  67. package/dist/core/mcp.d.ts +11 -7
  68. package/dist/core/mcp.js +11 -5
  69. package/dist/core/memory-engine/data-plane.js +5 -1
  70. package/dist/core/memory-engine/distiller.js +16 -2
  71. package/dist/core/memory-engine/engine.d.ts +8 -11
  72. package/dist/core/memory-engine/engine.js +9 -5
  73. package/dist/core/memory-engine/frontmatter.d.ts +6 -0
  74. package/dist/core/memory-engine/frontmatter.js +19 -16
  75. package/dist/core/memory.d.ts +2 -2
  76. package/dist/core/mode-policy.d.ts +4 -0
  77. package/dist/core/mode-policy.js +7 -5
  78. package/dist/core/model-seat.d.ts +4 -1
  79. package/dist/core/mods/dollar.d.ts +109 -0
  80. package/dist/core/mods/dollar.js +154 -0
  81. package/dist/core/mods/host.d.ts +23 -0
  82. package/dist/core/mods/host.js +21 -0
  83. package/dist/core/mods/kernel/budget.d.ts +43 -0
  84. package/dist/core/mods/kernel/budget.js +83 -0
  85. package/dist/core/mods/kernel/dispatch.d.ts +100 -0
  86. package/dist/core/mods/kernel/dispatch.js +701 -0
  87. package/dist/core/mods/kernel/events.d.ts +242 -0
  88. package/dist/core/mods/kernel/events.js +210 -0
  89. package/dist/core/mods/kernel/index.d.ts +6 -0
  90. package/dist/core/mods/kernel/index.js +5 -0
  91. package/dist/core/mods/kernel/receive.d.ts +65 -0
  92. package/dist/core/mods/kernel/receive.js +123 -0
  93. package/dist/core/mods/kernel/registration.d.ts +131 -0
  94. package/dist/core/mods/kernel/registration.js +102 -0
  95. package/dist/core/mods/kernel/site-rules.d.ts +84 -0
  96. package/dist/core/mods/kernel/site-rules.js +104 -0
  97. package/dist/core/mods/stations.d.ts +350 -0
  98. package/dist/core/mods/stations.js +444 -0
  99. package/dist/core/own-property-receipt.js +11 -23
  100. package/dist/core/parent-spec-seat.d.ts +18 -7
  101. package/dist/core/parent-spec-seat.js +6 -2
  102. package/dist/core/path-identity.js +2 -1
  103. package/dist/core/permission-rule-syntax.d.ts +15 -0
  104. package/dist/core/permission-rule-syntax.js +24 -16
  105. package/dist/core/permission-rules.d.ts +7 -1
  106. package/dist/core/permission-rules.js +26 -15
  107. package/dist/core/prompt-section-seat.d.ts +15 -27
  108. package/dist/core/prompt-section-seat.js +73 -40
  109. package/dist/core/protocol-http-auth.d.ts +3 -1
  110. package/dist/core/protocol-http-auth.js +2 -0
  111. package/dist/core/reminder-mint.d.ts +2 -2
  112. package/dist/core/remote-env.d.ts +24 -12
  113. package/dist/core/remote-env.js +1 -0
  114. package/dist/core/resume-startup-cause.d.ts +16 -1
  115. package/dist/core/resume-startup-cause.js +4 -1
  116. package/dist/core/retired-keys.d.ts +4 -0
  117. package/dist/core/retired-keys.js +3 -0
  118. package/dist/core/runner/announce-once-ledger.d.ts +31 -1
  119. package/dist/core/runner/announce-once-ledger.js +22 -0
  120. package/dist/core/runner/assemble-result.d.ts +9 -0
  121. package/dist/core/runner/assemble-result.js +3 -1
  122. package/dist/core/runner/call-hook-stations.d.ts +23 -27
  123. package/dist/core/runner/call-hook-stations.js +58 -42
  124. package/dist/core/runner/caller-stop-gate.d.ts +25 -0
  125. package/dist/core/runner/caller-stop-gate.js +7 -0
  126. package/dist/core/runner/compaction-seams.d.ts +8 -22
  127. package/dist/core/runner/compaction-seams.js +28 -53
  128. package/dist/core/runner/construction-doors.js +11 -1
  129. package/dist/core/runner/contracts.d.ts +85 -83
  130. package/dist/core/runner/credential-origin-seat.d.ts +2 -0
  131. package/dist/core/runner/credential-origin-seat.js +19 -0
  132. package/dist/core/runner/extra-body-dropped-seat.d.ts +2 -6
  133. package/dist/core/runner/gate-exit.d.ts +47 -36
  134. package/dist/core/runner/gate-exit.js +8 -2
  135. package/dist/core/runner/image.js +1 -2
  136. package/dist/core/runner/inline-image.js +1 -1
  137. package/dist/core/runner/notice-seat-input.d.ts +11 -0
  138. package/dist/core/runner/notice-seat-input.js +1 -0
  139. package/dist/core/runner/person-path-rows.js +1 -0
  140. package/dist/core/runner/prepare-acquire-reconcile.js +5 -1
  141. package/dist/core/runner/prepare-ask-lane.d.ts +5 -7
  142. package/dist/core/runner/prepare-ask-lane.js +6 -15
  143. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  144. package/dist/core/runner/prepare-caps-and-workflow.js +25 -8
  145. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  146. package/dist/core/runner/prepare-config-doors.js +9 -0
  147. package/dist/core/runner/prepare-defer-classify.js +3 -4
  148. package/dist/core/runner/prepare-gate-stations.d.ts +8 -11
  149. package/dist/core/runner/prepare-gate-stations.js +11 -10
  150. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -12
  151. package/dist/core/runner/prepare-inherited-gate.js +8 -21
  152. package/dist/core/runner/prepare-memory-engine-session.js +1 -1
  153. package/dist/core/runner/prepare-park-ask.js +10 -14
  154. package/dist/core/runner/prepare-policy-chain.d.ts +17 -15
  155. package/dist/core/runner/prepare-policy-chain.js +95 -100
  156. package/dist/core/runner/prepare-project-context.js +7 -10
  157. package/dist/core/runner/prepare-prompt-assembly.d.ts +45 -40
  158. package/dist/core/runner/prepare-prompt-assembly.js +134 -101
  159. package/dist/core/runner/prepare-prompt-inputs.d.ts +18 -19
  160. package/dist/core/runner/prepare-prompt-inputs.js +5 -7
  161. package/dist/core/runner/prepare-protocol-tools.d.ts +6 -4
  162. package/dist/core/runner/prepare-protocol-tools.js +2 -2
  163. package/dist/core/runner/prepare-safety-scan.js +6 -10
  164. package/dist/core/runner/prepare-suspend-saga.d.ts +6 -0
  165. package/dist/core/runner/prepare-suspend-saga.js +7 -5
  166. package/dist/core/runner/prepare-task.js +33 -15
  167. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -15
  168. package/dist/core/runner/prepare-turn-wiring.js +29 -27
  169. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -6
  170. package/dist/core/runner/prepare-wiring-manifest.js +7 -7
  171. package/dist/core/runner/resume-apply.d.ts +5 -2
  172. package/dist/core/runner/resume-apply.js +24 -42
  173. package/dist/core/runner/resume-checkpoint-screen.d.ts +1 -0
  174. package/dist/core/runner/resume-checkpoint-screen.js +9 -1
  175. package/dist/core/runner/resume-preflight.js +38 -16
  176. package/dist/core/runner/retired-runner-deps.js +3 -1
  177. package/dist/core/runner/run-brain-sinks.d.ts +3 -2
  178. package/dist/core/runner/run-brain-sinks.js +5 -1
  179. package/dist/core/runner/run-compaction-machinery.d.ts +7 -1
  180. package/dist/core/runner/run-compaction-machinery.js +7 -33
  181. package/dist/core/runner/run-harness-handlers.js +4 -2
  182. package/dist/core/runner/run-identity-wiring.d.ts +3 -1
  183. package/dist/core/runner/run-identity-wiring.js +3 -2
  184. package/dist/core/runner/run-leg.js +62 -84
  185. package/dist/core/runner/run-notification-lane.js +1 -0
  186. package/dist/core/runner/run-recovery-lanes.d.ts +3 -0
  187. package/dist/core/runner/run-recovery-lanes.js +11 -10
  188. package/dist/core/runner/run-settle-and-teardown.js +9 -28
  189. package/dist/core/runner/run-stop-and-final-verify.js +18 -25
  190. package/dist/core/runner/run-terminal-adoption.d.ts +3 -0
  191. package/dist/core/runner/run-terminal-adoption.js +33 -22
  192. package/dist/core/runner/run-tool-mount-facts.js +5 -3
  193. package/dist/core/runner/run-turn-boundary.js +23 -34
  194. package/dist/core/runner/runtask.d.ts +6 -7
  195. package/dist/core/runner/runtask.js +14 -72
  196. package/dist/core/runner/session-rule-policy.d.ts +5 -5
  197. package/dist/core/runner/session-rule-policy.js +13 -8
  198. package/dist/core/runner/side-call-account.d.ts +68 -0
  199. package/dist/core/runner/side-call-account.js +61 -0
  200. package/dist/core/runner/stream-halt-verbs.js +12 -17
  201. package/dist/core/runner/stream-lifecycle-verbs.js +8 -5
  202. package/dist/core/runner/stream-steer-verb.d.ts +1 -1
  203. package/dist/core/runner/stream-steer-verb.js +20 -38
  204. package/dist/core/runner/sub-agent-deny-lists.d.ts +121 -22
  205. package/dist/core/runner/sub-agent-deny-lists.js +228 -71
  206. package/dist/core/runner/tool-end-body.d.ts +5 -6
  207. package/dist/core/runner/tool-end-body.js +6 -4
  208. package/dist/core/runner/tool-not-executed.d.ts +15 -0
  209. package/dist/core/runner/tool-not-executed.js +30 -0
  210. package/dist/core/runner/tool-occurrence.d.ts +8 -7
  211. package/dist/core/runner/tool-occurrence.js +5 -5
  212. package/dist/core/runner/tool-result-media-seat.d.ts +1 -1
  213. package/dist/core/runner-deps.d.ts +20 -28
  214. package/dist/core/safety-merge-corpus.js +18 -2
  215. package/dist/core/secret-env.d.ts +88 -20
  216. package/dist/core/secret-env.js +192 -38
  217. package/dist/core/session-policy-store.d.ts +52 -16
  218. package/dist/core/session-policy-store.js +118 -28
  219. package/dist/core/session-reconcile.d.ts +4 -0
  220. package/dist/core/session-reconcile.js +2 -1
  221. package/dist/core/shell-read-silence.js +3 -32
  222. package/dist/core/side-query.d.ts +5 -3
  223. package/dist/core/skill-grant.d.ts +3 -2
  224. package/dist/core/skill-grant.js +17 -9
  225. package/dist/core/skills-directory.d.ts +31 -11
  226. package/dist/core/skills-directory.js +655 -104
  227. package/dist/core/task-event.d.ts +13 -3
  228. package/dist/core/task-result.d.ts +8 -0
  229. package/dist/core/task-spec.d.ts +20 -19
  230. package/dist/core/task-stream.d.ts +14 -4
  231. package/dist/core/tighten-task-spec.d.ts +4 -3
  232. package/dist/core/tighten-task-spec.js +2 -4
  233. package/dist/core/tool-check-reading.d.ts +74 -0
  234. package/dist/core/tool-check-reading.js +80 -0
  235. package/dist/core/tool-policy.d.ts +59 -18
  236. package/dist/core/tool-policy.js +77 -12
  237. package/dist/core/tool-registry.d.ts +20 -0
  238. package/dist/core/tool-registry.js +4 -0
  239. package/dist/core/tool-spec.d.ts +10 -6
  240. package/dist/core/tools.js +2 -0
  241. package/dist/core/trace.d.ts +51 -32
  242. package/dist/core/unc-spelling.d.ts +10 -0
  243. package/dist/core/unc-spelling.js +3 -0
  244. package/dist/core/untrusted-egress.d.ts +8 -0
  245. package/dist/core/untrusted-egress.js +4 -1
  246. package/dist/core/user-attachment.d.ts +12 -2
  247. package/dist/core/user-attachment.js +17 -2
  248. package/dist/core/wiring-manifest.d.ts +14 -13
  249. package/dist/core/wiring-manifest.js +2 -2
  250. package/dist/engine/compaction/utils.js +1 -2
  251. package/dist/engine/execution-env/node-execution-env.d.ts +4 -3
  252. package/dist/engine/execution-env/symlink-chain.js +1 -1
  253. package/dist/engine/harness/agent-harness.d.ts +1 -1
  254. package/dist/engine/harness/agent-harness.js +3 -3
  255. package/dist/engine/harness/types.d.ts +17 -16
  256. package/dist/engine/harness/types.js +7 -1
  257. package/dist/engine/llm/types.d.ts +12 -7
  258. package/dist/engine/loop/agent-loop.js +1 -1
  259. package/dist/engine/loop/types.d.ts +1 -1
  260. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  261. package/dist/engine/session/import-validate.js +3 -10
  262. package/dist/engine/session/session.d.ts +4 -4
  263. package/dist/engine/session/session.js +4 -4
  264. package/dist/index.d.ts +20 -16
  265. package/dist/index.js +13 -11
  266. package/dist/internal/harness.d.ts +1 -1
  267. package/dist/internal/harness.js +1 -1
  268. package/dist/orchestration/run-spec.d.ts +4 -6
  269. package/dist/orchestration/run-spec.js +39 -63
  270. package/dist/orchestration/run-workflow-tool.js +1 -2
  271. package/dist/orchestration/workflow.d.ts +11 -3
  272. package/dist/orchestration/workflow.js +11 -2
  273. package/dist/prompt-assembly/artifact-store.d.ts +37 -56
  274. package/dist/prompt-assembly/artifact-store.js +31 -49
  275. package/dist/prompt-assembly/artifact.d.ts +89 -76
  276. package/dist/prompt-assembly/artifact.js +167 -146
  277. package/dist/prompt-assembly/assemble.d.ts +34 -29
  278. package/dist/prompt-assembly/assemble.js +33 -220
  279. package/dist/prompt-assembly/bands.d.ts +20 -0
  280. package/dist/prompt-assembly/bands.js +18 -0
  281. package/dist/prompt-assembly/catalog/constitution.d.ts +23 -0
  282. package/dist/prompt-assembly/catalog/constitution.js +69 -0
  283. package/dist/prompt-assembly/catalog/core-behavior.d.ts +9 -0
  284. package/dist/prompt-assembly/catalog/core-behavior.js +85 -0
  285. package/dist/prompt-assembly/catalog/index.d.ts +11 -0
  286. package/dist/prompt-assembly/catalog/index.js +59 -0
  287. package/dist/prompt-assembly/catalog/modes.d.ts +15 -0
  288. package/dist/prompt-assembly/catalog/modes.js +137 -0
  289. package/dist/prompt-assembly/catalog/role.d.ts +10 -0
  290. package/dist/prompt-assembly/catalog/role.js +157 -0
  291. package/dist/prompt-assembly/catalog/schema.d.ts +124 -0
  292. package/dist/prompt-assembly/catalog/schema.js +25 -0
  293. package/dist/prompt-assembly/catalog/sections.d.ts +9 -0
  294. package/dist/prompt-assembly/catalog/sections.js +98 -0
  295. package/dist/prompt-assembly/catalog/team.d.ts +4 -0
  296. package/dist/prompt-assembly/catalog/team.js +258 -0
  297. package/dist/prompt-assembly/composer.d.ts +24 -28
  298. package/dist/prompt-assembly/composer.js +124 -46
  299. package/dist/prompt-assembly/epoch.d.ts +89 -79
  300. package/dist/prompt-assembly/epoch.js +115 -142
  301. package/dist/prompt-assembly/explain.d.ts +58 -34
  302. package/dist/prompt-assembly/explain.js +21 -24
  303. package/dist/prompt-assembly/overlay.d.ts +127 -0
  304. package/dist/prompt-assembly/overlay.js +301 -0
  305. package/dist/prompt-assembly/renderers.d.ts +21 -0
  306. package/dist/prompt-assembly/renderers.js +39 -0
  307. package/dist/prompt-assembly/section-usage.d.ts +3 -3
  308. package/dist/prompt-assembly/section-usage.js +1 -1
  309. package/dist/prompt-assembly/select.d.ts +56 -0
  310. package/dist/prompt-assembly/select.js +69 -0
  311. package/dist/prompt-assembly/sugar.d.ts +31 -0
  312. package/dist/prompt-assembly/sugar.js +20 -0
  313. package/dist/prompt-assembly/turn-snapshot.d.ts +3 -2
  314. package/dist/prompt-assembly/types.d.ts +45 -148
  315. package/dist/prompts/coordinator.d.ts +2 -21
  316. package/dist/prompts/coordinator.js +3 -255
  317. package/dist/prompts/cowork.d.ts +27 -16
  318. package/dist/prompts/cowork.js +2 -23
  319. package/dist/prompts/default.d.ts +38 -500
  320. package/dist/prompts/default.js +20 -285
  321. package/dist/prompts/supervisor.d.ts +6 -71
  322. package/dist/prompts/supervisor.js +6 -117
  323. package/dist/tools/fs/bash-readonly-classifier.d.ts +6 -5
  324. package/dist/tools/fs/dir-entries.d.ts +4 -2
  325. package/dist/tools/fs/dir-entries.js +16 -4
  326. package/dist/tools/fs/fs-bash.d.ts +1 -1
  327. package/dist/tools/fs/fs-bash.js +22 -11
  328. package/dist/tools/fs/fs-pdf.js +17 -13
  329. package/dist/tools/fs/fs-read.js +4 -3
  330. package/dist/tools/fs/fs-shared.d.ts +1 -10
  331. package/dist/tools/fs/fs-shared.js +3 -4
  332. package/dist/tools/fs/fs-write.js +2 -1
  333. package/dist/tools/fs/notebook.js +1 -1
  334. package/dist/tools/fs/pdf.d.ts +20 -10
  335. package/dist/tools/fs/pdf.js +19 -10
  336. package/dist/tools/fs/program-read-model.d.ts +68 -8
  337. package/dist/tools/fs/program-read-model.js +112 -33
  338. package/dist/tools/fs/program-read-model.rows.js +30 -10
  339. package/dist/tools/fs/read-deny-home.d.ts +25 -9
  340. package/dist/tools/fs/read-deny-home.js +28 -27
  341. package/dist/tools/fs/read-deny.d.ts +41 -11
  342. package/dist/tools/fs/read-deny.js +51 -5
  343. package/dist/tools/fs/safety.d.ts +4 -26
  344. package/dist/tools/fs/safety.js +10 -32
  345. package/dist/tools/fs/search.js +15 -10
  346. package/dist/tools/fs/shell-read-argv.d.ts +3 -2
  347. package/dist/tools/fs/shell-read-argv.js +118 -38
  348. package/dist/tools/fs/shell-read-establish.d.ts +5 -5
  349. package/dist/tools/fs/shell-read-establish.js +11 -61
  350. package/dist/tools/fs/shell-read-identity.js +6 -3
  351. package/dist/tools/fs/shell-read-judge.d.ts +13 -15
  352. package/dist/tools/fs/shell-read-judge.js +18 -4
  353. package/dist/tools/fs/shell-read-plan.d.ts +2 -2
  354. package/dist/tools/web.js +1 -1
  355. package/package.json +13 -1
  356. package/test/export-surface.snapshot.json +242 -99
  357. package/wire-changes/core@7.52.0.json +5859 -0
  358. package/dist/core/hook-seats.d.ts +0 -66
  359. package/dist/core/hook-seats.js +0 -37
  360. package/dist/prompt-assembly/packs/sema-default.d.ts +0 -2
  361. package/dist/prompt-assembly/packs/sema-default.js +0 -147
  362. package/dist/prompts/simple-sections.d.ts +0 -58
  363. package/dist/prompts/simple-sections.js +0 -43
@@ -1,8 +1,11 @@
1
1
  import { readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { isFrontmatterFence, UNCLOSED_FRONTMATTER_REASON } from "./frontmatter-fence.js";
3
4
  import { physicalPathOf } from "./physical-path.js";
4
5
  import { isPhysicallyWithin } from "../tools/fs/path-containment.js";
5
6
  import { assertNoRetiredKeys } from "./retired-keys.js";
7
+ import { ruleSpellingOf } from "./permission-rule-syntax.js";
8
+ import { inlineUntrusted } from "./untrusted-text.js";
6
9
  import { thrownText } from "./thrown-value.js";
7
10
  const SKILL_FILE = "SKILL.md";
8
11
  const RESOURCE_DIRS = ["assets", "references", "scripts"];
@@ -10,84 +13,685 @@ const NAME_MAX_CHARS = 64;
10
13
  const NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
11
14
  const DESCRIPTION_MAX_CHARS = 1024;
12
15
  const DEFAULT_MAX_ATTACHMENT_BYTES = 256 * 1024;
13
- const FENCE = "---";
16
+ const ALLOWED_TOOLS_KEY = "allowed-tools";
17
+ const DISALLOWED_TOOLS_KEY = "disallowed-tools";
18
+ const DISALLOWED_TOOLS_ALIAS = "disallowedTools";
19
+ const TOOL_LIST_KEYS = new Set([ALLOWED_TOOLS_KEY, DISALLOWED_TOOLS_KEY, DISALLOWED_TOOLS_ALIAS]);
20
+ const UNREAD_CONTINUATION = "it is followed by indented lines the frontmatter reader does not read";
21
+ const TEXT_AFTER_END = "text follows its closing quote or its closing `]` on the same line";
22
+ const SEQUENCE_NOT_AN_ENTRY = "its `- name` entries are not indented alike";
23
+ const SEQUENCE_NOT_A_NAME = "an entry is not a plain or quoted name (nested, mapping, anchor, tag, flow and block-scalar entries, and number, boolean and null entries, are not read)";
24
+ const VALUE_IS_A_MAPPING = "its value is a mapping or holds one (`key: value` or `{…}`), not a list of names";
25
+ const VALUE_NOT_A_STRING = "its value is a number or a boolean, not a list of names";
26
+ const QUOTE_NOT_CLOSED = "a quote in it does not close";
27
+ const flowNotClosed = (opener) => `its \`${opener}\` does not close within its lines`;
28
+ const strayLine = (line) => `a flush-left line that is no key follows it (line ${line})`;
29
+ const NOT_A_KEY_LINE = "a flush-left frontmatter line is a key — letters, digits, `_` and `-`, or one quoted scalar — then `:` and a blank or nothing; a comment starts with `#`";
30
+ const PARENS_NOT_ONE_ZONE = "an entry's parentheses are not one group that closes it (a `(` that does not close, a `)` with no `(`, or text after the closing `)` — `\\(` / `\\)` are a rule's own text)";
31
+ const KEY_REPEATED = "the key is written more than once";
32
+ const notARule = (position) => `entry ${position} is no rule spelling (a tool name of ASCII letters, digits and \`_ - . : *\`, starting with a letter, a digit or \`_\`, then at most one parenthesized rule)`;
33
+ const keyInsideValue = (inner) => `an indented line under its value is a mapping key, \`${inlineUntrusted(inner, 64)}\` — YAML reads no key there (under a value that is not a mapping, or indented less than the nested block it follows)`;
34
+ const toolListOutOfPlace = (inner) => `it holds the tool-list key \`${inner}\` — a tool list is read only as a key of the frontmatter, written flush left; anywhere else its list would be lost`;
35
+ const isBlank = (ch) => ch === " " || ch === "\t";
36
+ function leadingBlanks(text) {
37
+ let n = 0;
38
+ while (n < text.length && isBlank(text.charAt(n)))
39
+ n++;
40
+ return n;
41
+ }
42
+ const trimLeadingBlanks = (text) => text.slice(leadingBlanks(text));
43
+ function trimTrailingBlanks(text) {
44
+ let to = text.length;
45
+ while (to > 0 && isBlank(text.charAt(to - 1)))
46
+ to--;
47
+ return text.slice(0, to);
48
+ }
49
+ const trimBlanks = (text) => trimLeadingBlanks(trimTrailingBlanks(text));
50
+ function isSkippable(line) {
51
+ const body = trimLeadingBlanks(line);
52
+ return body === "" || body.startsWith("#");
53
+ }
54
+ function keyTokenOf(line) {
55
+ const quote = line.charAt(0);
56
+ const close = quote === '"' || quote === "'" ? closingQuote(line, 0) : -1;
57
+ const key = close > 0 ? decodeQuoted(line.slice(1, close), quote) : /^[A-Za-z][A-Za-z0-9_-]*/.exec(line)?.[0];
58
+ if (key === undefined)
59
+ return undefined;
60
+ return { key, after: trimLeadingBlanks(line.slice(close > 0 ? close + 1 : key.length)) };
61
+ }
62
+ function keyLineOf(line) {
63
+ const token = keyTokenOf(line);
64
+ return token !== undefined && token.after.startsWith(":") ? { key: token.key, rest: token.after.slice(1) } : undefined;
65
+ }
66
+ function yamlKeyLineOf(text) {
67
+ const line = keyLineOf(text);
68
+ return line !== undefined && (line.rest === "" || isBlank(line.rest.charAt(0))) ? line : undefined;
69
+ }
70
+ const yamlKeyOf = (text) => yamlKeyLineOf(text)?.key;
71
+ const isDashEntry = (text) => text.charAt(0) === "-" && (text.length === 1 || isBlank(text.charAt(1)));
72
+ function nestedLineHead(line) {
73
+ let text = trimLeadingBlanks(line);
74
+ let explicit = false;
75
+ for (;;) {
76
+ const marker = text.charAt(0);
77
+ if (text.length < 2 || (marker !== "-" && marker !== "?") || !isBlank(text.charAt(1)))
78
+ break;
79
+ explicit ||= marker === "?";
80
+ text = trimLeadingBlanks(text.slice(1));
81
+ }
82
+ const token = keyTokenOf(text);
83
+ const keyLine = yamlKeyLineOf(text);
84
+ const key = keyLine?.key ?? (explicit && token !== undefined && (token.after === "" || token.after.startsWith("#")) ? token.key : undefined);
85
+ if (key !== undefined && TOOL_LIST_KEYS.has(key))
86
+ return { key };
87
+ const value = trimLeadingBlanks(keyLine !== undefined ? keyLine.rest : text);
88
+ return value.charAt(0) === "{" || value.charAt(0) === "[" ? { flow: value } : {};
89
+ }
90
+ function toolListKeyIn(line) {
91
+ const head = nestedLineHead(line);
92
+ return head.key ?? (head.flow !== undefined ? scanValue(head.flow, "value").flowKey : undefined);
93
+ }
94
+ function nestedFlowAt(lines, at, end, flow, holds) {
95
+ const floor = Math.max(holds - 1, 0);
96
+ for (let budget = flow.length + 1;; budget *= 2) {
97
+ const region = valueRegion(lines, at, end, flow, floor, budget, false);
98
+ const scan = scanValue(region.lines.join("\n"), "value");
99
+ if (scan.unterminated === undefined || !region.capped)
100
+ return { ...(scan.flowKey !== undefined ? { key: scan.flowKey } : {}), held: Math.max(1, scan.held) };
101
+ }
102
+ }
103
+ function toolListKeyAfter(trailing) {
104
+ for (let p = 0; p < trailing.length; p++) {
105
+ if (isBlank(trailing.charAt(p)) || (p > 0 && !isBlank(trailing.charAt(p - 1))))
106
+ continue;
107
+ const key = yamlKeyOf(trailing.slice(p));
108
+ if (key !== undefined && TOOL_LIST_KEYS.has(key))
109
+ return key;
110
+ }
111
+ return undefined;
112
+ }
113
+ const opensNestedBlock = (text) => isDashEntry(text) || (text.charAt(0) === "?" && (text.length === 1 || isBlank(text.charAt(1)))) || yamlKeyOf(text) !== undefined || scanValue(text, "value").mapping === true;
14
114
  function parseSkillFile(text) {
15
115
  const fields = new Map();
16
- const normalized = text.replace(/\r\n/g, "\n");
116
+ const lists = new Map();
117
+ const normalized = (text.charCodeAt(0) === 0xfeff ? text.slice(1) : text).replace(/\r\n/g, "\n");
17
118
  const lines = normalized.split("\n");
18
- if (lines[0]?.trim() !== FENCE)
19
- return { fields, body: normalized, hadFrontmatter: false };
119
+ const unread = [];
120
+ const refused = new Set();
121
+ const refuse = (key, why, list) => {
122
+ if (refused.has(key))
123
+ return;
124
+ refused.add(key);
125
+ unread.push({ key, why, list });
126
+ };
127
+ let strayRefused = false;
128
+ const keep = (key, value) => {
129
+ if (!fields.has(key))
130
+ fields.set(key, value);
131
+ };
132
+ if (!isFrontmatterFence(lineAt(lines, 0)))
133
+ return { fields, lists, body: normalized, hadFrontmatter: false, unclosed: false, unread };
20
134
  let end = -1;
21
135
  for (let i = 1; i < lines.length; i++) {
22
- if (lines[i].trim() === FENCE) {
136
+ if (isFrontmatterFence(lineAt(lines, i))) {
23
137
  end = i;
24
138
  break;
25
139
  }
26
140
  }
27
141
  if (end === -1)
28
- return { fields, body: normalized, hadFrontmatter: false };
142
+ return { fields, lists, body: normalized, hadFrontmatter: false, unclosed: true, unread };
143
+ const front = [];
144
+ const fileLine = [];
145
+ for (let k = 0; k < end; k++) {
146
+ for (const piece of lineAt(lines, k).split("\r")) {
147
+ front.push(piece);
148
+ fileLine.push(k + 1);
149
+ }
150
+ }
151
+ const frontEnd = front.length;
29
152
  let i = 1;
30
- while (i < end) {
31
- const line = lines[i];
32
- const trimmed = line.trim();
33
- if (trimmed === "" || trimmed.startsWith("#")) {
153
+ let owner;
154
+ const listKeysSeen = new Set();
155
+ const valued = new Set();
156
+ let aliasSwallows;
157
+ const lost = (at, why) => {
158
+ refuse(at.key, why, at.list);
159
+ if (at.key === DISALLOWED_TOOLS_ALIAS)
160
+ aliasSwallows ??= why;
161
+ };
162
+ const listLostAfter = (at, trailing) => {
163
+ const inner = trailing !== undefined ? toolListKeyAfter(trailing) : undefined;
164
+ if (inner !== undefined)
165
+ lost(at, toolListOutOfPlace(inner));
166
+ };
167
+ while (i < frontEnd) {
168
+ const line = lineAt(front, i);
169
+ if (isSkippable(line)) {
34
170
  i++;
35
171
  continue;
36
172
  }
37
- if (/^\s/.test(line)) {
173
+ if (owner !== undefined && (isBlank(line.charAt(0)) ? leadingBlanks(line) >= owner.holds : owner.holds === 0 && isDashEntry(line))) {
174
+ const head = nestedLineHead(line);
175
+ const flow = head.key === undefined && head.flow !== undefined ? nestedFlowAt(front, i, frontEnd, head.flow, owner.holds) : undefined;
176
+ const held = flow?.held ?? 1;
177
+ let inner = head.key ?? flow?.key;
178
+ for (let at = i + 1; inner === undefined && at < i + held; at++)
179
+ inner = toolListKeyIn(lineAt(front, at));
180
+ if (inner !== undefined)
181
+ lost(owner, toolListOutOfPlace(inner));
182
+ i += held;
183
+ continue;
184
+ }
185
+ if (isBlank(line.charAt(0))) {
186
+ if (owner !== undefined) {
187
+ const inner = yamlKeyOf(trimLeadingBlanks(line));
188
+ const list = inner === undefined ? toolListKeyIn(line) : undefined;
189
+ if (inner !== undefined)
190
+ lost(owner, keyInsideValue(inner));
191
+ else if (list !== undefined)
192
+ lost(owner, toolListOutOfPlace(list));
193
+ else if (owner.list)
194
+ refuse(owner.key, UNREAD_CONTINUATION, true);
195
+ }
38
196
  i++;
39
197
  continue;
40
198
  }
41
- const kv = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/.exec(trimmed);
42
- if (!kv) {
199
+ const keyLine = yamlKeyLineOf(line);
200
+ if (keyLine === undefined) {
201
+ if (owner?.list === true)
202
+ lost(owner, strayLine(fileLine[i] ?? i + 1));
203
+ else if (!strayRefused) {
204
+ strayRefused = true;
205
+ unread.push({ line: fileLine[i] ?? i + 1, why: NOT_A_KEY_LINE });
206
+ }
207
+ owner = undefined;
43
208
  i++;
44
209
  continue;
45
210
  }
46
- const [, key, raw] = kv;
47
- const rawValue = raw.trim();
48
- const block = parseBlockScalarHeader(rawValue);
49
- if (block !== undefined) {
50
- const read = readBlockScalar(lines, i + 1, end, block);
211
+ const { key, rest } = keyLine;
212
+ const tail = trimBlanks(rest);
213
+ owner = { key, list: TOOL_LIST_KEYS.has(key), holds: Number.POSITIVE_INFINITY };
214
+ if (owner.list) {
215
+ if (listKeysSeen.has(key))
216
+ refuse(key, KEY_REPEATED, true);
217
+ listKeysSeen.add(key);
218
+ const read = readToolList(front, i, frontEnd, trimLeadingBlanks(rest));
51
219
  i = read.next;
52
- if (!fields.has(key))
53
- fields.set(key, read.value);
220
+ listLostAfter(owner, read.trailing);
221
+ if (key === DISALLOWED_TOOLS_ALIAS)
222
+ aliasSwallows ??= read.unterminated;
223
+ const unspelled = read.problem === undefined ? read.entries.findIndex((e) => ruleSpellingOf(e) === undefined) : -1;
224
+ const problem = read.problem ?? (unspelled >= 0 ? notARule(unspelled + 1) : undefined);
225
+ if (problem !== undefined || read.null !== true)
226
+ valued.add(key);
227
+ if (problem !== undefined)
228
+ refuse(key, problem, true);
229
+ else if (read.null !== true && !lists.has(key))
230
+ lists.set(key, read.entries);
54
231
  continue;
55
232
  }
56
- if (rawValue.startsWith("[") && !isFlowSequenceClosed(rawValue)) {
57
- const joined = readFlowSequenceContinuation(lines, i + 1, end, rawValue);
58
- i = joined.next;
59
- if (!fields.has(key))
60
- fields.set(key, joined.value);
233
+ const start = valueStartOf(front, i, frontEnd, trimLeadingBlanks(rest));
234
+ if (start === undefined || (start.at > i && opensNestedBlock(start.first))) {
235
+ owner.holds = start === undefined ? 0 : leadingBlanks(lineAt(front, start.at));
236
+ keep(key, tail);
237
+ i++;
61
238
  continue;
62
239
  }
63
- i++;
64
- if (fields.has(key))
240
+ const block = parseBlockScalarHeader(trimBlanks(start.first));
241
+ if (block !== undefined) {
242
+ const read = readBlockScalar(front, start.at + 1, frontEnd, block);
243
+ keep(key, start.at > i ? tail : read.value);
244
+ i = read.next;
65
245
  continue;
66
- fields.set(key, unquote(rawValue));
246
+ }
247
+ const region = valueRegion(front, start.at, frontEnd, start.first, 0);
248
+ const scan = scanValue(region.lines.join("\n"), "value");
249
+ if (scan.unterminated !== undefined)
250
+ refuse(key, scan.unterminated, false);
251
+ listLostAfter(owner, scan.trailing);
252
+ if (scan.flowKey !== undefined)
253
+ lost(owner, toolListOutOfPlace(scan.flowKey));
254
+ const flowLines = region.lines.slice(0, scan.held).filter((l) => !isSkippable(l)).map(trimBlanks);
255
+ keep(key, start.at > i ? tail : (scan.quoted ?? (tail.startsWith("[") && flowLines.length > 1 ? flowLines.join(" ") : tail)));
256
+ i = start.at + scan.held;
257
+ }
258
+ if (valued.has(DISALLOWED_TOOLS_KEY) && listKeysSeen.has(DISALLOWED_TOOLS_ALIAS)) {
259
+ lists.delete(DISALLOWED_TOOLS_ALIAS);
260
+ const at = unread.findIndex((u) => "key" in u && u.key === DISALLOWED_TOOLS_ALIAS);
261
+ if (at >= 0)
262
+ unread.splice(at, 1);
263
+ if (aliasSwallows !== undefined)
264
+ unread.push({ key: DISALLOWED_TOOLS_ALIAS, why: aliasSwallows, list: false });
67
265
  }
68
266
  let body = lines.slice(end + 1).join("\n");
69
267
  if (body.startsWith("\n"))
70
268
  body = body.slice(1);
71
- return { fields, body, hadFrontmatter: true };
269
+ return { fields, lists, body, hadFrontmatter: true, unclosed: false, unread };
270
+ }
271
+ function lineAt(lines, index) {
272
+ return lines[index] ?? "";
273
+ }
274
+ function valueStartOf(lines, keyIdx, end, value) {
275
+ if (!isSkippable(value))
276
+ return { at: keyIdx, first: value };
277
+ let j = keyIdx + 1;
278
+ while (j < end && isSkippable(lineAt(lines, j)))
279
+ j++;
280
+ const line = lineAt(lines, j);
281
+ return j < end && isBlank(line.charAt(0)) ? { at: j, first: trimLeadingBlanks(line) } : undefined;
282
+ }
283
+ function readToolList(lines, keyIdx, end, value) {
284
+ const start = valueStartOf(lines, keyIdx, end, value);
285
+ if (start === undefined || (start.at > keyIdx && start.first.startsWith("-")))
286
+ return readBlockSequence(lines, keyIdx + 1, end);
287
+ return readValue(lines, start.at, end, start.first);
288
+ }
289
+ function readValue(lines, valueIdx, end, first) {
290
+ const header = parseBlockScalarHeader(trimBlanks(first));
291
+ if (header !== undefined) {
292
+ const read = readBlockScalar(lines, valueIdx + 1, end, header);
293
+ const cut = new EntryCutter();
294
+ for (const line of read.value.split("\n")) {
295
+ for (const ch of line)
296
+ cut.push(ch);
297
+ cut.endScalar();
298
+ }
299
+ return { entries: cut.entries, ...(cut.problem !== undefined ? { problem: cut.problem } : {}), next: read.next };
300
+ }
301
+ const region = valueRegion(lines, valueIdx, end, first, 0);
302
+ const { held, mapping: _mapping, quoted: _quoted, flowKey: _flowKey, ...scan } = scanValue(region.lines.join("\n"), "value");
303
+ void _mapping;
304
+ void _quoted;
305
+ void _flowKey;
306
+ return { ...scan, next: valueIdx + held };
307
+ }
308
+ function valueRegion(lines, from, end, first, floor, budget = Number.POSITIVE_INFINITY, flushLeftCloses = true) {
309
+ const taken = [first];
310
+ const flow = first.startsWith("[") || first.startsWith("{");
311
+ let size = first.length + 1;
312
+ let j = from + 1;
313
+ for (; j < end; j++) {
314
+ if (size >= budget)
315
+ return { lines: taken, next: j, capped: true };
316
+ const line = lineAt(lines, j);
317
+ const body = trimLeadingBlanks(line);
318
+ const closes = flow && (body.startsWith("]") || body.startsWith("}")) && (flushLeftCloses || body.length < line.length);
319
+ if (!isSkippable(line) && leadingBlanks(line) <= floor && !closes)
320
+ break;
321
+ taken.push(body);
322
+ size += body.length + 1;
323
+ }
324
+ return { lines: taken, next: j, capped: false };
325
+ }
326
+ function readBlockSequence(lines, start, end) {
327
+ const entries = [];
328
+ let indent;
329
+ let j = start;
330
+ while (j < end) {
331
+ const line = lineAt(lines, j);
332
+ if (isSkippable(line)) {
333
+ j++;
334
+ continue;
335
+ }
336
+ const dashAt = leadingBlanks(line);
337
+ if (!isDashEntry(line.slice(dashAt)))
338
+ break;
339
+ const dashIndent = line.slice(0, dashAt);
340
+ if (indent !== undefined && dashIndent !== indent)
341
+ return { entries, next: j, problem: SEQUENCE_NOT_AN_ENTRY };
342
+ indent = dashIndent;
343
+ const item = valueRegion(lines, j, end, trimLeadingBlanks(line.slice(dashAt + 1)), dashAt);
344
+ const scan = scanValue(item.lines.join("\n"), "item");
345
+ if (scan.problem !== undefined)
346
+ return { entries, next: j + scan.held, problem: scan.problem, ...(scan.unterminated !== undefined ? { unterminated: scan.unterminated } : {}), ...(scan.trailing !== undefined ? { trailing: scan.trailing } : {}) };
347
+ for (const entry of scan.entries)
348
+ entries.push(entry);
349
+ j += scan.held;
350
+ }
351
+ return { entries, next: j, ...(indent === undefined ? { null: true } : {}) };
352
+ }
353
+ const YAML_NULL = /^(?:null|Null|NULL|~)$/;
354
+ const YAML_BOOL_OR_NUMBER = /^(?:true|True|TRUE|false|False|FALSE|[-+]?[0-9]+|0o[0-7]+|0x[0-9a-fA-F]+|[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+]?[0-9]+)?|[-+]?\.(?:inf|Inf|INF)|\.(?:nan|NaN|NAN))$/;
355
+ class EntryCutter {
356
+ entries = [];
357
+ problem;
358
+ depth = 0;
359
+ closed = false;
360
+ current = "";
361
+ blanks = "";
362
+ escaping = false;
363
+ push(ch) {
364
+ const escaped = this.escaping;
365
+ this.escaping = ch === "\\" && !escaped;
366
+ if (this.depth === 0 && (ch === "," || ch === " ")) {
367
+ this.cut();
368
+ return;
369
+ }
370
+ if (isBlank(ch)) {
371
+ if (this.current !== "")
372
+ this.blanks += ch;
373
+ return;
374
+ }
375
+ if (this.closed)
376
+ this.fail(PARENS_NOT_ONE_ZONE);
377
+ if (ch === "(" && !escaped)
378
+ this.depth++;
379
+ else if (ch === ")" && !escaped) {
380
+ if (this.depth === 0)
381
+ this.fail(PARENS_NOT_ONE_ZONE);
382
+ else
383
+ this.closed = --this.depth === 0;
384
+ }
385
+ this.current += this.blanks + ch;
386
+ this.blanks = "";
387
+ }
388
+ fold(breaks) {
389
+ this.blanks = "";
390
+ if (breaks === 1)
391
+ this.push(" ");
392
+ else
393
+ for (let n = 1; n < breaks; n++)
394
+ this.push("\n");
395
+ }
396
+ endScalar() {
397
+ if (this.depth > 0)
398
+ this.fail(PARENS_NOT_ONE_ZONE);
399
+ this.depth = 0;
400
+ this.escaping = false;
401
+ this.cut();
402
+ }
403
+ fail(why) {
404
+ this.problem ??= why;
405
+ }
406
+ cut() {
407
+ const entry = this.current;
408
+ this.current = "";
409
+ this.blanks = "";
410
+ this.closed = false;
411
+ if (entry !== "")
412
+ this.entries.push(entry);
413
+ }
414
+ }
415
+ function scanValue(text, seat) {
416
+ const cut = new EntryCutter();
417
+ let state = "start";
418
+ const opened = [];
419
+ const closes = (ch) => opened.length > 0 && ch === (opened[opened.length - 1] === "{" ? "}" : "]");
420
+ let held = text.split("\n").length;
421
+ let mapping = false;
422
+ let inComment = false;
423
+ let crossedLine = false;
424
+ let plain = "";
425
+ let breaks = 0;
426
+ let itemFrom = 0;
427
+ let isNull = false;
428
+ let unterminated;
429
+ let quoted;
430
+ let trailing;
431
+ let flowKey;
432
+ let quotedItem;
433
+ const endPlain = () => {
434
+ const written = trimBlanks(plain);
435
+ plain = "";
436
+ breaks = 0;
437
+ if (opened.length > 0 || seat === "item") {
438
+ if (YAML_NULL.test(written) || YAML_BOOL_OR_NUMBER.test(written))
439
+ cut.fail(SEQUENCE_NOT_A_NAME);
440
+ }
441
+ else if (YAML_NULL.test(written))
442
+ isNull = true;
443
+ else if (YAML_BOOL_OR_NUMBER.test(written))
444
+ cut.fail(VALUE_NOT_A_STRING);
445
+ cut.endScalar();
446
+ };
447
+ const mappingMarkAt = (at) => {
448
+ const next = text.charAt(at + 1);
449
+ return next === "" || next === "\n" || isBlank(next) || (opened.length > 0 && (next === "," || next === "]" || next === "}"));
450
+ };
451
+ const ended = (item) => {
452
+ quotedItem = undefined;
453
+ if (item && opened.length > 0 && cut.entries.length === itemFrom)
454
+ cut.fail(SEQUENCE_NOT_A_NAME);
455
+ state = opened.length > 0 ? "after" : "done";
456
+ crossedLine = false;
457
+ };
458
+ for (let k = 0; k < text.length; k++) {
459
+ const ch = text.charAt(k);
460
+ if (inComment && ch !== "\n")
461
+ continue;
462
+ inComment = false;
463
+ const comment = ch === "#" && (k === 0 || isBlank(text.charAt(k - 1)) || text.charAt(k - 1) === "\n");
464
+ if (state !== "plain") {
465
+ if (isBlank(ch))
466
+ continue;
467
+ if (ch === "\n" || comment) {
468
+ crossedLine ||= ch === "\n";
469
+ inComment = comment;
470
+ continue;
471
+ }
472
+ if (state === "done") {
473
+ if (crossedLine) {
474
+ held = linesBefore(text, k);
475
+ break;
476
+ }
477
+ cut.fail(TEXT_AFTER_END);
478
+ quoted = undefined;
479
+ trailing ??= text.slice(k, lineEndOf(text, k));
480
+ continue;
481
+ }
482
+ if (opened.length > 0 && (ch === "," || ch === "]" || ch === "}")) {
483
+ if (ch === ",")
484
+ state = "start";
485
+ else if (closes(ch)) {
486
+ opened.pop();
487
+ ended(false);
488
+ }
489
+ else
490
+ cut.fail(SEQUENCE_NOT_A_NAME);
491
+ continue;
492
+ }
493
+ if (state === "after" && ch === ":") {
494
+ cut.fail(VALUE_IS_A_MAPPING);
495
+ if (quotedItem !== undefined && TOOL_LIST_KEYS.has(quotedItem))
496
+ flowKey ??= quotedItem;
497
+ state = "start";
498
+ continue;
499
+ }
500
+ if (state === "after")
501
+ cut.fail(TEXT_AFTER_END);
502
+ const opens = state === "start";
503
+ itemFrom = cut.entries.length;
504
+ if (opens && (ch === '"' || ch === "'")) {
505
+ const close = closingQuote(text, k);
506
+ if (close < 0) {
507
+ cut.fail((unterminated = QUOTE_NOT_CLOSED));
508
+ break;
509
+ }
510
+ const decoded = decodeQuoted(text.slice(k + 1, close), ch);
511
+ if (opened.length === 0)
512
+ quoted = decoded;
513
+ for (const c of decoded)
514
+ cut.push(c);
515
+ cut.endScalar();
516
+ ended(true);
517
+ if (opened.length > 0)
518
+ quotedItem = decoded;
519
+ k = close;
520
+ continue;
521
+ }
522
+ if (opens && (ch === "[" || ch === "{") && (opened.length > 0 || seat === "value")) {
523
+ if (opened.length > 0)
524
+ cut.fail(SEQUENCE_NOT_A_NAME);
525
+ else if (ch === "{")
526
+ cut.fail(VALUE_IS_A_MAPPING);
527
+ opened.push(ch);
528
+ continue;
529
+ }
530
+ state = "plain";
531
+ }
532
+ if (ch === "\n") {
533
+ breaks++;
534
+ continue;
535
+ }
536
+ if (breaks > 0 && opened.length === 0 && yamlKeyOf(trimLeadingBlanks(text.slice(k, lineEndOf(text, k)))) !== undefined) {
537
+ endPlain();
538
+ ended(true);
539
+ held = linesBefore(text, k);
540
+ break;
541
+ }
542
+ if (comment) {
543
+ endPlain();
544
+ ended(true);
545
+ inComment = true;
546
+ continue;
547
+ }
548
+ if (opened.length > 0 && (ch === "," || closes(ch))) {
549
+ endPlain();
550
+ ended(true);
551
+ if (ch === ",")
552
+ state = "start";
553
+ else {
554
+ opened.pop();
555
+ ended(false);
556
+ }
557
+ continue;
558
+ }
559
+ if (opened.length > 0 && (ch === "[" || ch === "{" || ch === "]" || ch === "}")) {
560
+ cut.fail(SEQUENCE_NOT_A_NAME);
561
+ if (ch === "[" || ch === "{")
562
+ opened.push(ch);
563
+ }
564
+ if (ch === ":" && mappingMarkAt(k)) {
565
+ cut.fail(VALUE_IS_A_MAPPING);
566
+ if (opened.length === 0)
567
+ mapping = true;
568
+ else {
569
+ const written = trimBlanks(plain);
570
+ if (TOOL_LIST_KEYS.has(written))
571
+ flowKey ??= written;
572
+ endPlain();
573
+ state = "start";
574
+ continue;
575
+ }
576
+ }
577
+ if (breaks > 0) {
578
+ cut.fold(breaks);
579
+ plain += breaks === 1 ? " " : "\n".repeat(breaks - 1);
580
+ breaks = 0;
581
+ }
582
+ cut.push(ch);
583
+ plain += ch;
584
+ }
585
+ if (opened.length > 0) {
586
+ const why = flowNotClosed(opened[0] ?? "[");
587
+ cut.fail(why);
588
+ unterminated ??= why;
589
+ }
590
+ if (state === "plain")
591
+ endPlain();
592
+ if (seat === "item" && cut.entries.length === 0)
593
+ cut.fail(SEQUENCE_NOT_A_NAME);
594
+ return {
595
+ entries: isNull ? [] : cut.entries,
596
+ ...(cut.problem !== undefined ? { problem: cut.problem } : {}),
597
+ ...(unterminated !== undefined ? { unterminated } : {}),
598
+ ...(isNull ? { null: true } : {}),
599
+ held,
600
+ ...(mapping ? { mapping: true } : {}),
601
+ ...(quoted !== undefined ? { quoted } : {}),
602
+ ...(trailing !== undefined ? { trailing } : {}),
603
+ ...(flowKey !== undefined ? { flowKey } : {}),
604
+ };
605
+ }
606
+ function linesBefore(text, at) {
607
+ let n = 0;
608
+ for (let k = text.indexOf("\n"); k >= 0 && k < at; k = text.indexOf("\n", k + 1))
609
+ n++;
610
+ return n;
611
+ }
612
+ function lineEndOf(text, at) {
613
+ const k = text.indexOf("\n", at);
614
+ return k < 0 ? text.length : k;
615
+ }
616
+ function closingQuote(text, open) {
617
+ const quote = text.charAt(open);
618
+ for (let k = open + 1; k < text.length; k++) {
619
+ const ch = text.charAt(k);
620
+ if (quote === '"' && ch === "\\")
621
+ k++;
622
+ else if (ch === quote) {
623
+ if (quote === "'" && text.charAt(k + 1) === "'")
624
+ k++;
625
+ else
626
+ return k;
627
+ }
628
+ }
629
+ return -1;
630
+ }
631
+ const DOUBLE_QUOTE_ESCAPES = new Map(Object.entries({ 0: 0, a: 7, b: 8, t: 9, "\t": 9, n: 10, v: 11, f: 12, r: 13, e: 0x1b, " ": 32, '"': 34, "/": 47, "\\": 92, N: 0x85, _: 0xa0, L: 0x2028, P: 0x2029 }).map(([letter, code]) => [letter, String.fromCharCode(code)]));
632
+ function decodeQuoted(inner, quote) {
633
+ let out = "";
634
+ let blanks = "";
635
+ let breaks = 0;
636
+ let joined = false;
637
+ const fold = () => (breaks === 0 ? blanks : joined ? "\n".repeat(breaks) : breaks === 1 ? " " : "\n".repeat(breaks - 1));
638
+ for (let k = 0; k < inner.length; k++) {
639
+ const ch = inner.charAt(k);
640
+ if (ch === "\n") {
641
+ blanks = "";
642
+ breaks++;
643
+ continue;
644
+ }
645
+ if (isBlank(ch)) {
646
+ if (breaks === 0 && !joined)
647
+ blanks += ch;
648
+ continue;
649
+ }
650
+ out += fold();
651
+ blanks = "";
652
+ breaks = 0;
653
+ joined = false;
654
+ if (quote === "'" || ch !== "\\") {
655
+ out += ch;
656
+ if (quote === "'" && ch === "'")
657
+ k++;
658
+ continue;
659
+ }
660
+ const escape = /^(?:x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8}|[\s\S])/.exec(inner.slice(k + 1, k + 10))?.[0] ?? "";
661
+ const code = escape.length > 1 ? Number.parseInt(escape.slice(1), 16) : -1;
662
+ if (code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
663
+ out += `\\${escape.charAt(0)}`;
664
+ k += 1;
665
+ continue;
666
+ }
667
+ k += escape.length;
668
+ joined = escape === "\n";
669
+ if (joined)
670
+ continue;
671
+ out += code >= 0 ? String.fromCodePoint(code) : (DOUBLE_QUOTE_ESCAPES.get(escape) ?? `\\${escape}`);
672
+ }
673
+ return out + fold();
72
674
  }
73
675
  function parseBlockScalarHeader(value) {
74
- const m = /^([|>])([0-9+-]*)$/.exec(value);
676
+ const m = /^([|>])([0-9+-]*)([\s\S]*)$/.exec(value);
75
677
  if (!m)
76
678
  return undefined;
77
- const rest = m[2];
679
+ const [, style = "", rest = "", comment = ""] = m;
680
+ if (comment !== "" && !(isBlank(comment.charAt(0)) && trimLeadingBlanks(comment).startsWith("#")))
681
+ return undefined;
78
682
  if (!/^(?:[1-9]?[+-]?|[+-][1-9]?)$/.test(rest))
79
683
  return undefined;
80
684
  const digit = /[1-9]/.exec(rest)?.[0];
81
- return { style: m[1] === "|" ? "literal" : "folded", ...(digit !== undefined ? { indent: Number(digit) } : {}) };
685
+ return { style: style === "|" ? "literal" : "folded", ...(digit !== undefined ? { indent: Number(digit) } : {}) };
82
686
  }
83
687
  function readBlockScalar(lines, start, end, header) {
84
- const indentOf = (s) => /^[ \t]*/.exec(s)[0].length;
688
+ const indentOf = leadingBlanks;
85
689
  let indent = header.indent;
86
690
  if (indent === undefined) {
87
691
  for (let j = start; j < end; j++) {
88
- if (lines[j].trim() === "")
692
+ if (trimBlanks(lineAt(lines, j)) === "")
89
693
  continue;
90
- indent = indentOf(lines[j]);
694
+ indent = indentOf(lineAt(lines, j));
91
695
  break;
92
696
  }
93
697
  }
@@ -96,8 +700,8 @@ function readBlockScalar(lines, start, end, header) {
96
700
  const taken = [];
97
701
  let j = start;
98
702
  for (; j < end; j++) {
99
- const line = lines[j];
100
- if (line.trim() === "") {
703
+ const line = lineAt(lines, j);
704
+ if (trimBlanks(line) === "") {
101
705
  taken.push("");
102
706
  continue;
103
707
  }
@@ -137,37 +741,6 @@ function readBlockScalar(lines, start, end, header) {
137
741
  }
138
742
  return { value: folded, next: j };
139
743
  }
140
- function isFlowSequenceClosed(text) {
141
- let depth = 0;
142
- for (const ch of text) {
143
- if (ch === "[")
144
- depth++;
145
- else if (ch === "]")
146
- depth--;
147
- }
148
- return depth <= 0;
149
- }
150
- function readFlowSequenceContinuation(lines, start, end, head) {
151
- let acc = head;
152
- let j = start;
153
- for (; j < end; j++) {
154
- acc += ` ${lines[j].trim()}`;
155
- if (isFlowSequenceClosed(acc)) {
156
- j++;
157
- break;
158
- }
159
- }
160
- return { value: acc, next: j };
161
- }
162
- function unquote(value) {
163
- if (value.length < 2)
164
- return value;
165
- const q = value[0];
166
- if ((q !== '"' && q !== "'") || value[value.length - 1] !== q)
167
- return value;
168
- const inner = value.slice(1, -1);
169
- return q === '"' ? inner.replace(/\\t/g, "\t").replace(/\\n/g, "\n") : inner;
170
- }
171
744
  function listRelativeFiles(base, dir, prefix, out, walk) {
172
745
  let entries;
173
746
  try {
@@ -280,39 +853,6 @@ function isDecodableText(bytes) {
280
853
  function errText(err) {
281
854
  return thrownText(err);
282
855
  }
283
- function splitToolNames(declared) {
284
- let text = declared.trim();
285
- if (text.startsWith("[") && text.endsWith("]"))
286
- text = text.slice(1, -1);
287
- const names = [];
288
- let current = "";
289
- let depth = 0;
290
- const flush = () => {
291
- const name = unquote(current.trim());
292
- if (name !== "")
293
- names.push(name);
294
- current = "";
295
- };
296
- for (const ch of text) {
297
- if (ch === "(") {
298
- depth++;
299
- current += ch;
300
- }
301
- else if (ch === ")") {
302
- if (depth > 0)
303
- depth--;
304
- current += ch;
305
- }
306
- else if (depth === 0 && (ch === "," || /\s/.test(ch))) {
307
- flush();
308
- }
309
- else {
310
- current += ch;
311
- }
312
- }
313
- flush();
314
- return names;
315
- }
316
856
  const RETIRED_SKILLS_DIRECTORY_OPTION_KEYS = Object.freeze({
317
857
  deployedTools: "the loader copies a skill's `allowed-tools` / `disallowed-tools` into `SkillSpec.allowedTools` / `disallowedTools` as written and the run's grant table judges them (an entry naming a tool nothing mounts simply never matches) — drop the option",
318
858
  });
@@ -376,7 +916,18 @@ export function createSkillsFromDirectory(dir, options = {}) {
376
916
  }
377
917
  const parsed = parseSkillFile(text);
378
918
  if (!parsed.hadFrontmatter) {
379
- warn({ code: "no_frontmatter", skill: name, detail: `${SKILL_FILE} has no fenced --- frontmatter block — skipped` });
919
+ const detail = parsed.unclosed ? `${SKILL_FILE} ${UNCLOSED_FRONTMATTER_REASON} — skipped` : `${SKILL_FILE} has no fenced --- frontmatter block — skipped`;
920
+ warn({ code: "no_frontmatter", skill: name, detail });
921
+ continue;
922
+ }
923
+ if (parsed.unread.length > 0) {
924
+ const lists = parsed.unread.some((u) => "list" in u && u.list);
925
+ const said = parsed.unread.map((u) => ("line" in u ? `${SKILL_FILE} line ${u.line} is no key line (${u.why})` : `\`${u.key}\` has a value the reader cannot read${u.list ? " as a list" : ""} (${u.why})`));
926
+ warn({
927
+ code: "no_frontmatter",
928
+ skill: name,
929
+ detail: `${said.join("; ")}${lists ? " — write each list as one value (`a b`, `a, b`, `[a, b]`), a block scalar, or `- name` lines under the key" : ""} — skipped${lists ? ", not installed with a missing or shorter list" : ""}`,
930
+ });
380
931
  continue;
381
932
  }
382
933
  const declaredName = parsed.fields.get("name");
@@ -405,8 +956,8 @@ export function createSkillsFromDirectory(dir, options = {}) {
405
956
  warn({ code: "description_too_long", skill: name, detail: `\`description\` is ${description.length} chars, over the ${DESCRIPTION_MAX_CHARS} cap — skipped` });
406
957
  continue;
407
958
  }
408
- const allowedTools = splitToolNames(parsed.fields.get("allowed-tools") ?? "");
409
- const disallowedTools = splitToolNames(parsed.fields.get("disallowed-tools") ?? "");
959
+ const allowedTools = parsed.lists.get(ALLOWED_TOOLS_KEY) ?? [];
960
+ const disallowedTools = parsed.lists.get(DISALLOWED_TOOLS_KEY) ?? parsed.lists.get(DISALLOWED_TOOLS_ALIAS) ?? [];
410
961
  const baseDir = skillPhysicalRoot(skillDir, name, warn);
411
962
  const files = baseDir === undefined ? [] : readAttachments(skillDir, baseDir, name, budget, warn);
412
963
  skills.push({