@yadsh/dsh-qa-surface 0.5.0 → 0.6.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.
Files changed (380) hide show
  1. package/README.md +206 -11
  2. package/compatibility.json +10 -1
  3. package/cordis.patch.yml +11 -1
  4. package/lib/account-remotes.js +10 -0
  5. package/lib/account-remotes.js.map +1 -1
  6. package/lib/accounts/errors.js.map +1 -1
  7. package/lib/accounts/file.js.map +1 -1
  8. package/lib/accounts/store.js +22 -0
  9. package/lib/accounts/store.js.map +1 -1
  10. package/lib/attestation.js +6 -0
  11. package/lib/attestation.js.map +1 -1
  12. package/lib/client/QaAccountsController.js +29 -0
  13. package/lib/client/QaAccountsController.js.map +1 -1
  14. package/lib/client/QaSessionController.js +205 -61
  15. package/lib/client/QaSessionController.js.map +1 -1
  16. package/lib/client/QaSurface.js +125 -56
  17. package/lib/client/QaSurface.js.map +1 -1
  18. package/lib/client/QaSurfaceGuard.js +96 -0
  19. package/lib/client/QaSurfaceGuard.js.map +1 -0
  20. package/lib/client/QaTranscriptAdapter.js +11 -5
  21. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  22. package/lib/client/attestation.js +14 -2
  23. package/lib/client/attestation.js.map +1 -1
  24. package/lib/client/components/QaChangelog.js +44 -0
  25. package/lib/client/components/QaChangelog.js.map +1 -1
  26. package/lib/client/components/QaComposer.js +1 -1
  27. package/lib/client/components/QaComposer.js.map +1 -1
  28. package/lib/client/components/QaHeader.js +2 -8
  29. package/lib/client/components/QaHeader.js.map +1 -1
  30. package/lib/client/components/QaMessage.js +20 -7
  31. package/lib/client/components/QaMessage.js.map +1 -1
  32. package/lib/client/components/QaModal.js +24 -6
  33. package/lib/client/components/QaModal.js.map +1 -1
  34. package/lib/client/components/QaSidebar.js +2 -6
  35. package/lib/client/components/QaSidebar.js.map +1 -1
  36. package/lib/client/components/QaSourcesPanel.js +6 -5
  37. package/lib/client/components/QaSourcesPanel.js.map +1 -1
  38. package/lib/client/components/QaWidthHandle.js +21 -1
  39. package/lib/client/components/QaWidthHandle.js.map +1 -1
  40. package/lib/client/components/surface-utils.js +2 -0
  41. package/lib/client/components/surface-utils.js.map +1 -1
  42. package/lib/client/index.js +72 -24
  43. package/lib/client/index.js.map +1 -1
  44. package/lib/client/panels/PanelHost.js +209 -0
  45. package/lib/client/panels/PanelHost.js.map +1 -0
  46. package/lib/client/panels/PanelLauncher.js +39 -0
  47. package/lib/client/panels/PanelLauncher.js.map +1 -0
  48. package/lib/client/panels/contract.js +2 -0
  49. package/lib/client/panels/contract.js.map +1 -0
  50. package/lib/client/panels/index.js +2 -0
  51. package/lib/client/panels/index.js.map +1 -0
  52. package/lib/client/panels/registry.js +171 -0
  53. package/lib/client/panels/registry.js.map +1 -0
  54. package/lib/client/project-session-state.js +13 -5
  55. package/lib/client/project-session-state.js.map +1 -1
  56. package/lib/client/settings/card.js +2 -2
  57. package/lib/client/settings/card.js.map +1 -1
  58. package/lib/client/settings/sections/accounts.js +3 -1
  59. package/lib/client/settings/sections/accounts.js.map +1 -1
  60. package/lib/client/settings/sections/documents.js +88 -0
  61. package/lib/client/settings/sections/documents.js.map +1 -0
  62. package/lib/client/settings/sections/interface.js +2 -0
  63. package/lib/client/settings/sections/interface.js.map +1 -1
  64. package/lib/client/settings/sections.js +1 -0
  65. package/lib/client/settings/sections.js.map +1 -1
  66. package/lib/client/settings-extensions/contract.js +2 -0
  67. package/lib/client/settings-extensions/contract.js.map +1 -0
  68. package/lib/client/settings-extensions/index.js +2 -0
  69. package/lib/client/settings-extensions/index.js.map +1 -0
  70. package/lib/client/settings-extensions/registry.js +63 -0
  71. package/lib/client/settings-extensions/registry.js.map +1 -0
  72. package/lib/client/settlement.js +51 -5
  73. package/lib/client/settlement.js.map +1 -1
  74. package/lib/client/source-preview.js +24 -0
  75. package/lib/client/source-preview.js.map +1 -0
  76. package/lib/client/styles.js +158 -32
  77. package/lib/client/styles.js.map +1 -1
  78. package/lib/client/subagent-codenames.js +109 -0
  79. package/lib/client/subagent-codenames.js.map +1 -0
  80. package/lib/client/types.js +2 -0
  81. package/lib/client/types.js.map +1 -1
  82. package/lib/client/user-settings/GeneralSettingsPage.js +13 -0
  83. package/lib/client/user-settings/GeneralSettingsPage.js.map +1 -0
  84. package/lib/client/user-settings/ProfileSettingsPage.js +59 -0
  85. package/lib/client/user-settings/ProfileSettingsPage.js.map +1 -0
  86. package/lib/client/user-settings/SkillCatalog.js +23 -0
  87. package/lib/client/user-settings/SkillCatalog.js.map +1 -0
  88. package/lib/client/user-settings/SkillEditor.js +89 -0
  89. package/lib/client/user-settings/SkillEditor.js.map +1 -0
  90. package/lib/client/user-settings/SkillToolPicker.js +58 -0
  91. package/lib/client/user-settings/SkillToolPicker.js.map +1 -0
  92. package/lib/client/user-settings/SkillsSettingsPage.js +171 -0
  93. package/lib/client/user-settings/SkillsSettingsPage.js.map +1 -0
  94. package/lib/client/user-settings/StartersSettingsPage.js +78 -0
  95. package/lib/client/user-settings/StartersSettingsPage.js.map +1 -0
  96. package/lib/client/user-settings/UserSettingsDialog.js +50 -0
  97. package/lib/client/user-settings/UserSettingsDialog.js.map +1 -0
  98. package/lib/client/user-settings/copy.js +84 -0
  99. package/lib/client/user-settings/copy.js.map +1 -0
  100. package/lib/client/user-settings/draft.js +127 -0
  101. package/lib/client/user-settings/draft.js.map +1 -0
  102. package/lib/client/user-settings/fields.js +24 -0
  103. package/lib/client/user-settings/fields.js.map +1 -0
  104. package/lib/client.js +5554 -1415
  105. package/lib/client.js.map +1 -1
  106. package/lib/config-resolvers/accounts.js +30 -0
  107. package/lib/config-resolvers/accounts.js.map +1 -1
  108. package/lib/config-resolvers/defaults.js +23 -0
  109. package/lib/config-resolvers/defaults.js.map +1 -1
  110. package/lib/config-resolvers/tools.js +37 -0
  111. package/lib/config-resolvers/tools.js.map +1 -0
  112. package/lib/config-resolvers/ui.js +2 -0
  113. package/lib/config-resolvers/ui.js.map +1 -1
  114. package/lib/config.js +288 -0
  115. package/lib/config.js.map +1 -1
  116. package/lib/documents/artifacts/ids.js +43 -0
  117. package/lib/documents/artifacts/ids.js.map +1 -0
  118. package/lib/documents/artifacts/manifest.js +66 -0
  119. package/lib/documents/artifacts/manifest.js.map +1 -0
  120. package/lib/documents/artifacts/store.js +169 -0
  121. package/lib/documents/artifacts/store.js.map +1 -0
  122. package/lib/documents/capabilities.js +93 -0
  123. package/lib/documents/capabilities.js.map +1 -0
  124. package/lib/documents/config.js +302 -0
  125. package/lib/documents/config.js.map +1 -0
  126. package/lib/documents/defaults.js +78 -0
  127. package/lib/documents/defaults.js.map +1 -0
  128. package/lib/documents/errors.js +101 -0
  129. package/lib/documents/errors.js.map +1 -0
  130. package/lib/documents/index.js +80 -0
  131. package/lib/documents/index.js.map +1 -0
  132. package/lib/documents/inspect/facts.js +161 -0
  133. package/lib/documents/inspect/facts.js.map +1 -0
  134. package/lib/documents/inspect/zip.js +108 -0
  135. package/lib/documents/inspect/zip.js.map +1 -0
  136. package/lib/documents/markdown/directives.js +135 -0
  137. package/lib/documents/markdown/directives.js.map +1 -0
  138. package/lib/documents/markdown/frontmatter.js +35 -0
  139. package/lib/documents/markdown/frontmatter.js.map +1 -0
  140. package/lib/documents/markdown/normalize.js +113 -0
  141. package/lib/documents/markdown/normalize.js.map +1 -0
  142. package/lib/documents/orchestrator/assets.js +243 -0
  143. package/lib/documents/orchestrator/assets.js.map +1 -0
  144. package/lib/documents/orchestrator/convert-document.js +186 -0
  145. package/lib/documents/orchestrator/convert-document.js.map +1 -0
  146. package/lib/documents/orchestrator/create-document.js +292 -0
  147. package/lib/documents/orchestrator/create-document.js.map +1 -0
  148. package/lib/documents/orchestrator/extract-document.js +162 -0
  149. package/lib/documents/orchestrator/extract-document.js.map +1 -0
  150. package/lib/documents/orchestrator/inspect-document.js +89 -0
  151. package/lib/documents/orchestrator/inspect-document.js.map +1 -0
  152. package/lib/documents/orchestrator/runtime-deps.js +59 -0
  153. package/lib/documents/orchestrator/runtime-deps.js.map +1 -0
  154. package/lib/documents/orchestrator/scope.js +132 -0
  155. package/lib/documents/orchestrator/scope.js.map +1 -0
  156. package/lib/documents/providers/docling.js +247 -0
  157. package/lib/documents/providers/docling.js.map +1 -0
  158. package/lib/documents/providers/libreoffice.js +130 -0
  159. package/lib/documents/providers/libreoffice.js.map +1 -0
  160. package/lib/documents/providers/markitdown.js +89 -0
  161. package/lib/documents/providers/markitdown.js.map +1 -0
  162. package/lib/documents/providers/pandoc.js +161 -0
  163. package/lib/documents/providers/pandoc.js.map +1 -0
  164. package/lib/documents/providers/process.js +164 -0
  165. package/lib/documents/providers/process.js.map +1 -0
  166. package/lib/documents/providers/registry.js +152 -0
  167. package/lib/documents/providers/registry.js.map +1 -0
  168. package/lib/documents/providers/shared.js +75 -0
  169. package/lib/documents/providers/shared.js.map +1 -0
  170. package/lib/documents/runtime.js +114 -0
  171. package/lib/documents/runtime.js.map +1 -0
  172. package/lib/documents/security/file-types.js +188 -0
  173. package/lib/documents/security/file-types.js.map +1 -0
  174. package/lib/documents/security/limits.js +78 -0
  175. package/lib/documents/security/limits.js.map +1 -0
  176. package/lib/documents/security/mime.js +43 -0
  177. package/lib/documents/security/mime.js.map +1 -0
  178. package/lib/documents/security/paths.js +133 -0
  179. package/lib/documents/security/paths.js.map +1 -0
  180. package/lib/documents/templates/registry.js +147 -0
  181. package/lib/documents/templates/registry.js.map +1 -0
  182. package/lib/documents/templates/resolver.js +59 -0
  183. package/lib/documents/templates/resolver.js.map +1 -0
  184. package/lib/documents/tools/convert.js +125 -0
  185. package/lib/documents/tools/convert.js.map +1 -0
  186. package/lib/documents/tools/create.js +187 -0
  187. package/lib/documents/tools/create.js.map +1 -0
  188. package/lib/documents/tools/index.js +41 -0
  189. package/lib/documents/tools/index.js.map +1 -0
  190. package/lib/documents/tools/inspect.js +135 -0
  191. package/lib/documents/tools/inspect.js.map +1 -0
  192. package/lib/documents/tools/shared.js +88 -0
  193. package/lib/documents/tools/shared.js.map +1 -0
  194. package/lib/documents/tools/to-markdown.js +125 -0
  195. package/lib/documents/tools/to-markdown.js.map +1 -0
  196. package/lib/documents/types.js +12 -0
  197. package/lib/documents/types.js.map +1 -0
  198. package/lib/index.js +231 -22
  199. package/lib/index.js.map +1 -1
  200. package/lib/integration-principals.js +22 -0
  201. package/lib/integration-principals.js.map +1 -0
  202. package/lib/lockdown-policy.js +15 -3
  203. package/lib/lockdown-policy.js.map +1 -1
  204. package/lib/personal-skills/errors.js +9 -0
  205. package/lib/personal-skills/errors.js.map +1 -0
  206. package/lib/personal-skills/host.js +80 -0
  207. package/lib/personal-skills/host.js.map +1 -0
  208. package/lib/personal-skills/index.js +10 -0
  209. package/lib/personal-skills/index.js.map +1 -0
  210. package/lib/personal-skills/paths.js +127 -0
  211. package/lib/personal-skills/paths.js.map +1 -0
  212. package/lib/personal-skills/provider.js +109 -0
  213. package/lib/personal-skills/provider.js.map +1 -0
  214. package/lib/personal-skills/remotes.js +40 -0
  215. package/lib/personal-skills/remotes.js.map +1 -0
  216. package/lib/personal-skills/service.js +686 -0
  217. package/lib/personal-skills/service.js.map +1 -0
  218. package/lib/personal-skills/skill-file.js +278 -0
  219. package/lib/personal-skills/skill-file.js.map +1 -0
  220. package/lib/personal-skills/skill-format.js +202 -0
  221. package/lib/personal-skills/skill-format.js.map +1 -0
  222. package/lib/personal-skills/watcher.js +96 -0
  223. package/lib/personal-skills/watcher.js.map +1 -0
  224. package/lib/profile.js +6 -4
  225. package/lib/profile.js.map +1 -1
  226. package/lib/prompt-notes.js +19 -0
  227. package/lib/prompt-notes.js.map +1 -1
  228. package/lib/provenance/file-preview.js +68 -21
  229. package/lib/provenance/file-preview.js.map +1 -1
  230. package/lib/provenance/host-store.js +44 -11
  231. package/lib/provenance/host-store.js.map +1 -1
  232. package/lib/provenance/index.js +1 -0
  233. package/lib/provenance/index.js.map +1 -1
  234. package/lib/provenance/normalize.js +3 -1
  235. package/lib/provenance/normalize.js.map +1 -1
  236. package/lib/provenance/snapshot-store.js +154 -0
  237. package/lib/provenance/snapshot-store.js.map +1 -0
  238. package/lib/qa-tools/activation-detector.js +53 -0
  239. package/lib/qa-tools/activation-detector.js.map +1 -0
  240. package/lib/qa-tools/activation-manager.js +111 -0
  241. package/lib/qa-tools/activation-manager.js.map +1 -0
  242. package/lib/qa-tools/catalog.js +108 -0
  243. package/lib/qa-tools/catalog.js.map +1 -0
  244. package/lib/qa-tools/durable-marker.js +115 -0
  245. package/lib/qa-tools/durable-marker.js.map +1 -0
  246. package/lib/qa-tools/index.js +83 -0
  247. package/lib/qa-tools/index.js.map +1 -0
  248. package/lib/qa-tools/lifecycle.js +64 -0
  249. package/lib/qa-tools/lifecycle.js.map +1 -0
  250. package/lib/qa-tools/types.js +2 -0
  251. package/lib/qa-tools/types.js.map +1 -0
  252. package/lib/resolve-config.js +5 -0
  253. package/lib/resolve-config.js.map +1 -1
  254. package/lib/secure-session.js +75 -18
  255. package/lib/secure-session.js.map +1 -1
  256. package/lib/starters.js +117 -0
  257. package/lib/starters.js.map +1 -0
  258. package/lib/typert.host.js +767 -22
  259. package/lib/typert.remote-client.d.ts +17 -1
  260. package/lib/typert.remote-client.js +609 -17
  261. package/lib/types/account-remotes.d.ts +3 -1
  262. package/lib/types/accounts/errors.d.ts +1 -1
  263. package/lib/types/accounts/file.d.ts +10 -0
  264. package/lib/types/accounts/store.d.ts +7 -1
  265. package/lib/types/attestation.d.ts +2 -0
  266. package/lib/types/client/QaAccountsController.d.ts +7 -1
  267. package/lib/types/client/QaSessionController.d.ts +14 -0
  268. package/lib/types/client/QaSurface.d.ts +14 -4
  269. package/lib/types/client/QaSurfaceGuard.d.ts +27 -0
  270. package/lib/types/client/QaTranscriptAdapter.d.ts +7 -0
  271. package/lib/types/client/attestation.d.ts +8 -1
  272. package/lib/types/client/components/QaComposer.d.ts +3 -1
  273. package/lib/types/client/components/QaHeader.d.ts +3 -1
  274. package/lib/types/client/components/QaModal.d.ts +5 -4
  275. package/lib/types/client/components/QaSidebar.d.ts +8 -11
  276. package/lib/types/client/components/QaWidthHandle.d.ts +13 -0
  277. package/lib/types/client/index.d.ts +1 -0
  278. package/lib/types/client/panels/PanelHost.d.ts +18 -0
  279. package/lib/types/client/panels/PanelLauncher.d.ts +5 -0
  280. package/lib/types/client/panels/contract.d.ts +83 -0
  281. package/lib/types/client/panels/index.d.ts +3 -0
  282. package/lib/types/client/panels/registry.d.ts +40 -0
  283. package/lib/types/client/project-session-state.d.ts +7 -0
  284. package/lib/types/client/settings/sections/documents.d.ts +13 -0
  285. package/lib/types/client/settings/sections.d.ts +1 -0
  286. package/lib/types/client/settings-extensions/contract.d.ts +32 -0
  287. package/lib/types/client/settings-extensions/index.d.ts +3 -0
  288. package/lib/types/client/settings-extensions/registry.d.ts +15 -0
  289. package/lib/types/client/settlement.d.ts +41 -5
  290. package/lib/types/client/source-preview.d.ts +8 -0
  291. package/lib/types/client/styles.d.ts +2 -0
  292. package/lib/types/client/subagent-codenames.d.ts +18 -0
  293. package/lib/types/client/types.d.ts +35 -3
  294. package/lib/types/client/user-settings/GeneralSettingsPage.d.ts +14 -0
  295. package/lib/types/client/user-settings/ProfileSettingsPage.d.ts +18 -0
  296. package/lib/types/client/user-settings/SkillCatalog.d.ts +16 -0
  297. package/lib/types/client/user-settings/SkillEditor.d.ts +42 -0
  298. package/lib/types/client/user-settings/SkillToolPicker.d.ts +17 -0
  299. package/lib/types/client/user-settings/SkillsSettingsPage.d.ts +15 -0
  300. package/lib/types/client/user-settings/StartersSettingsPage.d.ts +14 -0
  301. package/lib/types/client/user-settings/UserSettingsDialog.d.ts +43 -0
  302. package/lib/types/client/user-settings/copy.d.ts +25 -0
  303. package/lib/types/client/user-settings/draft.d.ts +51 -0
  304. package/lib/types/client/user-settings/fields.d.ts +43 -0
  305. package/lib/types/config-resolvers/tools.d.ts +6 -0
  306. package/lib/types/documents/artifacts/ids.d.ts +14 -0
  307. package/lib/types/documents/artifacts/manifest.d.ts +32 -0
  308. package/lib/types/documents/artifacts/store.d.ts +67 -0
  309. package/lib/types/documents/capabilities.d.ts +32 -0
  310. package/lib/types/documents/config.d.ts +27 -0
  311. package/lib/types/documents/defaults.d.ts +161 -0
  312. package/lib/types/documents/errors.d.ts +42 -0
  313. package/lib/types/documents/index.d.ts +63 -0
  314. package/lib/types/documents/inspect/facts.d.ts +43 -0
  315. package/lib/types/documents/inspect/zip.d.ts +28 -0
  316. package/lib/types/documents/markdown/directives.d.ts +39 -0
  317. package/lib/types/documents/markdown/frontmatter.d.ts +15 -0
  318. package/lib/types/documents/markdown/normalize.d.ts +30 -0
  319. package/lib/types/documents/orchestrator/assets.d.ts +55 -0
  320. package/lib/types/documents/orchestrator/convert-document.d.ts +25 -0
  321. package/lib/types/documents/orchestrator/create-document.d.ts +18 -0
  322. package/lib/types/documents/orchestrator/extract-document.d.ts +24 -0
  323. package/lib/types/documents/orchestrator/inspect-document.d.ts +12 -0
  324. package/lib/types/documents/orchestrator/runtime-deps.d.ts +43 -0
  325. package/lib/types/documents/orchestrator/scope.d.ts +52 -0
  326. package/lib/types/documents/providers/docling.d.ts +54 -0
  327. package/lib/types/documents/providers/libreoffice.d.ts +40 -0
  328. package/lib/types/documents/providers/markitdown.d.ts +28 -0
  329. package/lib/types/documents/providers/pandoc.d.ts +42 -0
  330. package/lib/types/documents/providers/process.d.ts +54 -0
  331. package/lib/types/documents/providers/registry.d.ts +68 -0
  332. package/lib/types/documents/providers/shared.d.ts +27 -0
  333. package/lib/types/documents/runtime.d.ts +59 -0
  334. package/lib/types/documents/security/file-types.d.ts +39 -0
  335. package/lib/types/documents/security/limits.d.ts +24 -0
  336. package/lib/types/documents/security/mime.d.ts +13 -0
  337. package/lib/types/documents/security/paths.d.ts +38 -0
  338. package/lib/types/documents/templates/registry.d.ts +50 -0
  339. package/lib/types/documents/templates/resolver.d.ts +30 -0
  340. package/lib/types/documents/tools/convert.d.ts +9 -0
  341. package/lib/types/documents/tools/create.d.ts +14 -0
  342. package/lib/types/documents/tools/index.d.ts +33 -0
  343. package/lib/types/documents/tools/inspect.d.ts +9 -0
  344. package/lib/types/documents/tools/shared.d.ts +77 -0
  345. package/lib/types/documents/tools/to-markdown.d.ts +12 -0
  346. package/lib/types/documents/types.d.ts +294 -0
  347. package/lib/types/index.d.ts +75 -3
  348. package/lib/types/integration-principals.d.ts +9 -0
  349. package/lib/types/lockdown-policy.d.ts +12 -2
  350. package/lib/types/personal-skills/errors.d.ts +11 -0
  351. package/lib/types/personal-skills/host.d.ts +42 -0
  352. package/lib/types/personal-skills/index.d.ts +10 -0
  353. package/lib/types/personal-skills/paths.d.ts +39 -0
  354. package/lib/types/personal-skills/provider.d.ts +33 -0
  355. package/lib/types/personal-skills/remotes.d.ts +35 -0
  356. package/lib/types/personal-skills/service.d.ts +131 -0
  357. package/lib/types/personal-skills/skill-file.d.ts +64 -0
  358. package/lib/types/personal-skills/skill-format.d.ts +100 -0
  359. package/lib/types/personal-skills/watcher.d.ts +39 -0
  360. package/lib/types/profile.d.ts +13 -0
  361. package/lib/types/prompt-notes.d.ts +9 -0
  362. package/lib/types/provenance/file-preview.d.ts +47 -4
  363. package/lib/types/provenance/host-store.d.ts +7 -1
  364. package/lib/types/provenance/index.d.ts +1 -0
  365. package/lib/types/provenance/snapshot-store.d.ts +23 -0
  366. package/lib/types/qa-tools/activation-detector.d.ts +29 -0
  367. package/lib/types/qa-tools/activation-manager.d.ts +47 -0
  368. package/lib/types/qa-tools/catalog.d.ts +40 -0
  369. package/lib/types/qa-tools/durable-marker.d.ts +16 -0
  370. package/lib/types/qa-tools/index.d.ts +40 -0
  371. package/lib/types/qa-tools/lifecycle.d.ts +44 -0
  372. package/lib/types/qa-tools/types.d.ts +45 -0
  373. package/lib/types/secure-session.d.ts +33 -2
  374. package/lib/types/starters.d.ts +45 -0
  375. package/lib/types/types.d.ts +208 -3
  376. package/package.json +22 -9
  377. package/scripts/repair-session-events.mjs +260 -0
  378. package/lib/client/components/QaProfile.js +0 -64
  379. package/lib/client/components/QaProfile.js.map +0 -1
  380. package/lib/types/client/components/QaProfile.d.ts +0 -25
@@ -0,0 +1,686 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { closeSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
+ import path from "node:path";
4
+ import { existingQaUserWorkspace, prepareQaUserWorkspace, QA_USER_WORKSPACES_DIRECTORY, } from "../user-workspace.js";
5
+ import { QaPersonalSkillError } from "./errors.js";
6
+ import { directoryExists, ensureSkillRoots, listSkillDirectories, resolveSkillRoots, skillDirectory, skillFilePath, } from "./paths.js";
7
+ import { normalizeAllowedTools, parseSkillFile, serializeSkillFile, skillFileBody, skillFileBytes, skillNameProblem, validateSkillDraft, QA_SKILL_DESCRIPTION_MAX, QA_SKILL_FILE_MAX_BYTES, QA_SKILL_MAX_TOOLS, QA_SKILL_WHEN_TO_USE_MAX, } from "./skill-file.js";
8
+ /**
9
+ * Resource entries a skill may carry. v1 never writes them; anything else
10
+ * found beside `SKILL.md` earns a warning so the user knows the editor is not
11
+ * managing it.
12
+ */
13
+ const RECOGNIZED_RESOURCES = new Set(["references", "assets", "scripts"]);
14
+ /**
15
+ * Personal Skills over one account's own directory.
16
+ *
17
+ * The service is the only writer: it derives every path from the account id,
18
+ * validates a draft before touching the disk, writes `SKILL.md` through a
19
+ * temporary file and a rename, and keeps a removed skill recoverable in the
20
+ * trash beside its skills root. Reading is deliberately permissive — a skill
21
+ * whose file the parser rejects still returns a document carrying the
22
+ * diagnostic that says why, so the editor can repair it.
23
+ */
24
+ export class QaPersonalSkills {
25
+ ctx;
26
+ options;
27
+ constructor(ctx, options) {
28
+ this.ctx = ctx;
29
+ this.options = options;
30
+ }
31
+ /** Whether the feature is available on this deployment at all. */
32
+ get enabled() {
33
+ return this.options.getConfig().accounts.skills.enabled;
34
+ }
35
+ /** Resolve one account's storage roots, or refuse the operation. */
36
+ rootsFor(context) {
37
+ const config = this.options.getConfig();
38
+ if (!config.accounts.skills.enabled) {
39
+ throw new QaPersonalSkillError("skills-disabled", "personal skills are not enabled on this deployment");
40
+ }
41
+ const workspacePath = this.options.workspacePath();
42
+ if (workspacePath === undefined) {
43
+ throw new QaPersonalSkillError("workspace-unavailable", "the configured QA workspace is not registered");
44
+ }
45
+ let personalRoot;
46
+ try {
47
+ // `prepare` tolerates a missing account directory: reading one's own
48
+ // skills must not require an unrelated session to exist first.
49
+ personalRoot = prepareQaUserWorkspace(workspacePath, context.userId);
50
+ }
51
+ catch (error) {
52
+ throw new QaPersonalSkillError("workspace-unavailable", error instanceof Error ? error.message : String(error));
53
+ }
54
+ return resolveSkillRoots(personalRoot, config.accounts.skills.relativeRoot);
55
+ }
56
+ /**
57
+ * Resolve the storage roots a session cwd names. This is the discovery
58
+ * provider's entry point and takes no account token: the cwd itself is the
59
+ * boundary, validated as a provisioned `<workspace>/.qa-users/<uuid>`
60
+ * directory, so no arbitrary directory can name another account's storage.
61
+ */
62
+ rootsForCwd(cwd, relativeRoot) {
63
+ const personalRoot = qaUserWorkspaceFromCwd(cwd);
64
+ if (personalRoot === undefined)
65
+ return undefined;
66
+ try {
67
+ return resolveSkillRoots(personalRoot, relativeRoot);
68
+ }
69
+ catch {
70
+ return undefined;
71
+ }
72
+ }
73
+ /** Every skill of one account, sorted by directory name. */
74
+ list(context) {
75
+ const roots = this.rootsFor(context);
76
+ const available = this.availableTools();
77
+ return listSkillDirectories(roots).map((directoryName) => this.summarize(this.load(roots, directoryName), available));
78
+ }
79
+ /** One skill with everything the editor needs. */
80
+ get(context, name) {
81
+ const roots = this.rootsFor(context);
82
+ const stored = this.load(roots, name);
83
+ if (!stored.exists) {
84
+ throw new QaPersonalSkillError("skill-not-found", `no skill directory named ${name}`);
85
+ }
86
+ return this.document(stored, this.availableTools());
87
+ }
88
+ /**
89
+ * Check one unsaved draft without touching storage: the serializer is the
90
+ * one a Save uses and the preserved frontmatter is the stored file's, so the
91
+ * preview the editor shows is byte-for-byte the file that save would write,
92
+ * and the diagnostics carry the operator's own limit (which the browser
93
+ * cannot know). `name` is the stored skill being edited, or null to create.
94
+ */
95
+ validate(context, name, input) {
96
+ const config = this.options.getConfig();
97
+ const prepared = this.prepareWrite(input, config, false);
98
+ const extraFrontmatter = name === null ? {} : this.storedFrontmatter(this.rootsFor(context), name);
99
+ const text = serializeSkillFile({ ...prepared.draft, extraFrontmatter });
100
+ return {
101
+ preview: text,
102
+ diagnostics: validateSkillDraft({
103
+ ...prepared.draft,
104
+ sizeBytes: skillFileBytes(text),
105
+ maxBytes: config.accounts.skills.maxSkillBytes,
106
+ availableTools: [...this.availableTools()],
107
+ rejectedTools: prepared.rejectedTools,
108
+ extraFieldNames: Object.keys(extraFrontmatter),
109
+ }),
110
+ };
111
+ }
112
+ /** Whether one account stores a skill under this name. */
113
+ has(context, name) {
114
+ try {
115
+ const roots = this.rootsFor(context);
116
+ return directoryExists(skillDirectory(roots, name));
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ }
122
+ /** Create one skill directory and its `SKILL.md`. */
123
+ create(context, input) {
124
+ const roots = this.rootsFor(context);
125
+ const prepared = this.prepareWrite(input, this.options.getConfig(), true);
126
+ ensureSkillRoots(roots);
127
+ const directory = skillDirectory(roots, prepared.name);
128
+ if (directoryExists(directory)) {
129
+ throw new QaPersonalSkillError("skill-exists", `a skill named ${prepared.name} already exists`);
130
+ }
131
+ try {
132
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
133
+ this.writeSkillFile(skillFilePath(roots, prepared.name), prepared.text);
134
+ }
135
+ catch (error) {
136
+ rmSync(directory, { recursive: true, force: true });
137
+ throw this.storageError(error);
138
+ }
139
+ this.refreshCatalog("skill.create", context, prepared.name);
140
+ return this.get(context, prepared.name);
141
+ }
142
+ /**
143
+ * Replace one skill's file, preserving the frontmatter fields this editor
144
+ * does not own and moving the whole directory when the name changed.
145
+ */
146
+ update(context, name, input) {
147
+ const roots = this.rootsFor(context);
148
+ const prepared = this.prepareWrite(input, this.options.getConfig(), true);
149
+ const current = this.load(roots, name);
150
+ if (!current.exists) {
151
+ throw new QaPersonalSkillError("skill-not-found", `no skill directory named ${name}`);
152
+ }
153
+ this.assertRevision(name, input.expectedRevision, current.revision);
154
+ const text = serializeSkillFile({
155
+ ...prepared.draft,
156
+ // Foreign frontmatter is read from the file, not from the browser: the
157
+ // stored copy is the authority on what a Save must not lose.
158
+ extraFrontmatter: current.contents?.extraFrontmatter ?? {},
159
+ });
160
+ const target = skillDirectory(roots, prepared.name);
161
+ if (current.directory === target) {
162
+ this.writeSkillFile(current.filePath, text);
163
+ }
164
+ else {
165
+ this.renameSkillDirectory(current.directory, target, text, prepared.name);
166
+ }
167
+ this.refreshCatalog("skill.update", context, prepared.name);
168
+ return this.get(context, prepared.name);
169
+ }
170
+ /** Move one skill directory into the trash beside its skills root. */
171
+ remove(context, name, expectedRevision) {
172
+ const roots = this.rootsFor(context);
173
+ const current = this.load(roots, name);
174
+ if (!current.exists) {
175
+ throw new QaPersonalSkillError("skill-not-found", `no skill directory named ${name}`);
176
+ }
177
+ this.assertRevision(name, expectedRevision, current.revision);
178
+ try {
179
+ mkdirSync(roots.trash, { recursive: true, mode: 0o700 });
180
+ renameSync(current.directory, this.trashTarget(roots.trash, name));
181
+ }
182
+ catch (error) {
183
+ throw this.storageError(error);
184
+ }
185
+ this.refreshCatalog("skill.delete", context, name);
186
+ return { name, trashed: true };
187
+ }
188
+ /**
189
+ * The tool catalog the picker offers. Availability is the QA session's own
190
+ * scope — the deployment's reviewed allow-list — never a suggestion of what
191
+ * a skill may grant: a declared tool the scope excludes stays listed and
192
+ * stays unavailable.
193
+ */
194
+ tools(context) {
195
+ const available = this.availableTools();
196
+ const described = new Map();
197
+ for (const schema of this.ctx.tools.schemas()) {
198
+ described.set(schema.name, schema.description);
199
+ }
200
+ const names = new Set(described.keys());
201
+ for (const name of available)
202
+ names.add(name);
203
+ // Declared names come last: an imported tool the registry never had has to
204
+ // stay removable from the skill that declares it.
205
+ for (const name of this.declaredTools(context))
206
+ names.add(name);
207
+ const descriptors = [...names].map((name) => ({
208
+ name,
209
+ description: described.get(name) ?? "",
210
+ available: available.has(name),
211
+ }));
212
+ descriptors.sort((left, right) => {
213
+ if (left.available !== right.available)
214
+ return left.available ? -1 : 1;
215
+ return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
216
+ });
217
+ return descriptors;
218
+ }
219
+ /**
220
+ * The skills one session cwd exposes to DSH. A file the parser rejects, or
221
+ * one whose name disagrees with its directory, is reported in the editor and
222
+ * never handed to the model as a half-valid skill.
223
+ */
224
+ discover(cwd) {
225
+ const config = this.options.getConfig();
226
+ if (!config.accounts.skills.enabled)
227
+ return [];
228
+ const roots = this.rootsForCwd(cwd, config.accounts.skills.relativeRoot);
229
+ if (roots === undefined)
230
+ return [];
231
+ this.observe(roots.skills);
232
+ return listSkillDirectories(roots)
233
+ .map((directoryName) => this.load(roots, directoryName))
234
+ .filter((skill) => this.isDiscoverable(skill));
235
+ }
236
+ /** Re-read one skill of a session cwd, re-checking the same boundary. */
237
+ read(cwd, directoryName) {
238
+ const config = this.options.getConfig();
239
+ if (!config.accounts.skills.enabled)
240
+ return undefined;
241
+ const roots = this.rootsForCwd(cwd, config.accounts.skills.relativeRoot);
242
+ if (roots === undefined)
243
+ return undefined;
244
+ let directory;
245
+ try {
246
+ directory = skillDirectory(roots, directoryName);
247
+ }
248
+ catch {
249
+ return undefined;
250
+ }
251
+ if (!directoryExists(directory))
252
+ return undefined;
253
+ const stored = this.load(roots, directoryName);
254
+ return this.isDiscoverable(stored) ? stored : undefined;
255
+ }
256
+ observe(root) {
257
+ this.options.onRootDiscovered?.(root);
258
+ }
259
+ isDiscoverable(skill) {
260
+ const contents = skill.contents;
261
+ if (!skill.exists || contents === undefined)
262
+ return false;
263
+ return contents.name === skill.directoryName;
264
+ }
265
+ availableTools() {
266
+ return new Set(this.options.getConfig().lockdown.toolPolicy.allow);
267
+ }
268
+ declaredTools(context) {
269
+ const names = new Set();
270
+ let roots;
271
+ try {
272
+ roots = this.rootsFor(context);
273
+ }
274
+ catch {
275
+ return [];
276
+ }
277
+ for (const directoryName of listSkillDirectories(roots)) {
278
+ const stored = this.load(roots, directoryName);
279
+ for (const tool of stored.contents?.allowedTools ?? [])
280
+ names.add(tool);
281
+ }
282
+ return [...names];
283
+ }
284
+ /** The foreign frontmatter a save must keep, read from the stored file. */
285
+ storedFrontmatter(roots, name) {
286
+ return this.load(roots, name).contents?.extraFrontmatter ?? {};
287
+ }
288
+ /** Read one skill directory, tolerating every failure the editor must see. */
289
+ load(roots, directoryName) {
290
+ const absent = {
291
+ exists: false,
292
+ directoryName,
293
+ directory: path.join(roots.skills, directoryName),
294
+ filePath: path.join(roots.skills, directoryName, "SKILL.md"),
295
+ raw: "",
296
+ revision: "",
297
+ updatedAt: null,
298
+ contents: undefined,
299
+ diagnostics: [],
300
+ resourceCount: 0,
301
+ };
302
+ let directory;
303
+ try {
304
+ directory = skillDirectory(roots, directoryName);
305
+ }
306
+ catch {
307
+ return absent;
308
+ }
309
+ if (!directoryExists(directory))
310
+ return absent;
311
+ const filePath = path.join(directory, "SKILL.md");
312
+ const maxBytes = this.options.getConfig().accounts.skills.maxSkillBytes;
313
+ const read = readSkillText(filePath, Math.max(maxBytes, QA_SKILL_FILE_MAX_BYTES));
314
+ const resources = this.resources(directory);
315
+ const base = {
316
+ exists: true,
317
+ directoryName,
318
+ directory,
319
+ filePath,
320
+ resourceCount: resources.count,
321
+ };
322
+ if (read === undefined) {
323
+ return {
324
+ ...base,
325
+ raw: "",
326
+ revision: "",
327
+ updatedAt: null,
328
+ contents: undefined,
329
+ diagnostics: [
330
+ ...resources.diagnostics,
331
+ {
332
+ code: "skill-file-missing",
333
+ severity: "error",
334
+ field: null,
335
+ detail: "SKILL.md",
336
+ },
337
+ ],
338
+ };
339
+ }
340
+ const parsed = parseSkillFile(read.text);
341
+ const diagnostics = [
342
+ ...resources.diagnostics,
343
+ ...(parsed.ok ? parsed.value.warnings : []),
344
+ ];
345
+ if (!parsed.ok) {
346
+ diagnostics.push({
347
+ code: parsed.code,
348
+ severity: "error",
349
+ field: null,
350
+ detail: parsed.detail,
351
+ });
352
+ }
353
+ else {
354
+ diagnostics.push(...validateSkillDraft({
355
+ name: parsed.value.name,
356
+ description: parsed.value.description,
357
+ whenToUse: parsed.value.whenToUse,
358
+ modelInvocable: parsed.value.modelInvocable,
359
+ userInvocable: parsed.value.userInvocable,
360
+ allowedTools: parsed.value.allowedTools,
361
+ sizeBytes: skillFileBytes(read.text),
362
+ maxBytes,
363
+ availableTools: [...this.availableTools()],
364
+ }));
365
+ if (parsed.value.name !== directoryName) {
366
+ diagnostics.push({
367
+ code: "name-mismatch",
368
+ severity: "warning",
369
+ field: "name",
370
+ detail: parsed.value.name,
371
+ });
372
+ }
373
+ }
374
+ return {
375
+ ...base,
376
+ raw: read.text,
377
+ revision: hashText(read.text),
378
+ updatedAt: read.updatedAt,
379
+ contents: parsed.ok ? parsed.value : undefined,
380
+ diagnostics,
381
+ };
382
+ }
383
+ resources(directory) {
384
+ let entries;
385
+ try {
386
+ entries = readdirSync(directory).filter((entry) => entry !== "SKILL.md");
387
+ }
388
+ catch {
389
+ return { count: 0, diagnostics: [] };
390
+ }
391
+ const diagnostics = [];
392
+ for (const entry of entries) {
393
+ if (RECOGNIZED_RESOURCES.has(entry))
394
+ continue;
395
+ diagnostics.push({
396
+ code: "resource-unsupported",
397
+ severity: "warning",
398
+ field: null,
399
+ detail: entry,
400
+ });
401
+ }
402
+ return { count: entries.length, diagnostics };
403
+ }
404
+ /**
405
+ * Normalize one editor draft into the file to write. `strict` is the
406
+ * difference between a save and a check: a save refuses a draft the Host
407
+ * will not store, while a check hands the same problems back as diagnostics,
408
+ * because that is what the editor asked for.
409
+ */
410
+ prepareWrite(input, config, strict) {
411
+ const name = typeof input.name === "string" ? input.name.trim() : "";
412
+ const description = typeof input.description === "string" ? input.description.trim() : "";
413
+ const normalized = normalizeAllowedTools(input.allowedTools) ?? {
414
+ tools: [],
415
+ rejected: [],
416
+ };
417
+ const whenToUse = typeof input.whenToUse === "string" && input.whenToUse.trim() !== ""
418
+ ? input.whenToUse.trim()
419
+ : null;
420
+ const draft = {
421
+ name,
422
+ description,
423
+ whenToUse,
424
+ modelInvocable: input.modelInvocable !== false,
425
+ userInvocable: input.userInvocable !== false,
426
+ allowedTools: normalized.tools,
427
+ extraFrontmatter: {},
428
+ body: typeof input.body === "string" ? input.body : "",
429
+ };
430
+ const text = serializeSkillFile(draft);
431
+ if (!strict) {
432
+ return { name, text, draft, rejectedTools: normalized.rejected };
433
+ }
434
+ if (skillNameProblem(name) !== null) {
435
+ throw new QaPersonalSkillError("skill-name-invalid", "the skill name must be lowercase kebab-case");
436
+ }
437
+ if (description === "") {
438
+ throw new QaPersonalSkillError("skill-invalid", "the skill description must not be empty");
439
+ }
440
+ if (normalized.rejected.length > 0) {
441
+ throw new QaPersonalSkillError("skill-invalid", `unusable tool name(s): ${normalized.rejected.join(", ")}`);
442
+ }
443
+ if (normalized.tools.length > QA_SKILL_MAX_TOOLS) {
444
+ throw new QaPersonalSkillError("skill-invalid", `at most ${QA_SKILL_MAX_TOOLS} tools may be declared`);
445
+ }
446
+ const blocking = validateSkillDraft({
447
+ ...draft,
448
+ sizeBytes: skillFileBytes(text),
449
+ maxBytes: config.accounts.skills.maxSkillBytes,
450
+ }).find((entry) => entry.severity === "error");
451
+ if (blocking !== undefined) {
452
+ this.options.logger.warn("skill.validation-failed", {
453
+ code: blocking.code,
454
+ detail: blocking.detail,
455
+ name,
456
+ });
457
+ throw new QaPersonalSkillError("skill-invalid", `the skill is not savable: ${blocking.code}`);
458
+ }
459
+ if (description.length > QA_SKILL_DESCRIPTION_MAX) {
460
+ this.options.logger.warn("skill.validation-warning", {
461
+ code: "description-too-long",
462
+ name,
463
+ });
464
+ }
465
+ if (whenToUse !== null && whenToUse.length > QA_SKILL_WHEN_TO_USE_MAX) {
466
+ this.options.logger.warn("skill.validation-warning", {
467
+ code: "when-to-use-too-long",
468
+ name,
469
+ });
470
+ }
471
+ return { name, text, draft, rejectedTools: [] };
472
+ }
473
+ /** Move a renamed skill's whole directory, resources included. */
474
+ renameSkillDirectory(from, to, text, name) {
475
+ if (directoryExists(to)) {
476
+ throw new QaPersonalSkillError("skill-exists", `a skill named ${name} already exists`);
477
+ }
478
+ const moved = [];
479
+ try {
480
+ mkdirSync(to, { recursive: true, mode: 0o700 });
481
+ for (const entry of readdirSync(from)) {
482
+ renameSync(path.join(from, entry), path.join(to, entry));
483
+ moved.push(entry);
484
+ }
485
+ this.writeSkillFile(path.join(to, "SKILL.md"), text);
486
+ }
487
+ catch (error) {
488
+ // Put every entry back before reporting: a half-moved skill directory is
489
+ // worse than a refused save.
490
+ for (const entry of moved) {
491
+ try {
492
+ renameSync(path.join(to, entry), path.join(from, entry));
493
+ }
494
+ catch {
495
+ // The original move already failed; the skill stays where it is and
496
+ // the operator gets the error below.
497
+ }
498
+ }
499
+ try {
500
+ rmSync(to, { recursive: true, force: true });
501
+ }
502
+ catch {
503
+ // A leftover empty directory is harmless.
504
+ }
505
+ throw this.storageError(error);
506
+ }
507
+ try {
508
+ rmSync(from, { recursive: true, force: true });
509
+ }
510
+ catch {
511
+ // The contents already moved; an empty directory left behind is not
512
+ // worth failing a save over.
513
+ }
514
+ }
515
+ /** Write one file through a temporary sibling and a rename. */
516
+ writeSkillFile(filePath, text) {
517
+ const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
518
+ try {
519
+ writeFileSync(temporary, text, "utf8");
520
+ renameSync(temporary, filePath);
521
+ }
522
+ catch (error) {
523
+ try {
524
+ rmSync(temporary, { force: true });
525
+ }
526
+ catch {
527
+ // A leftover temporary file never shadows the real one.
528
+ }
529
+ throw error;
530
+ }
531
+ }
532
+ trashTarget(trash, name) {
533
+ const stamp = new Date()
534
+ .toISOString()
535
+ .replace(/[-:]/gu, "")
536
+ .replace(/\.\d+Zu?$/u, "Z");
537
+ let candidate = path.join(trash, `${name}-${stamp}`);
538
+ for (let attempt = 2; attempt < 100; attempt += 1) {
539
+ if (!pathExists(candidate))
540
+ break;
541
+ candidate = path.join(trash, `${name}-${stamp}-${attempt}`);
542
+ }
543
+ return candidate;
544
+ }
545
+ assertRevision(name, expected, current) {
546
+ if (expected !== null && expected !== current) {
547
+ throw new QaPersonalSkillError("skill-conflict", `skill ${name} changed after it was read`);
548
+ }
549
+ }
550
+ summarize(stored, available) {
551
+ const contents = stored.contents;
552
+ return {
553
+ name: stored.directoryName,
554
+ description: contents?.description ?? "",
555
+ whenToUse: contents?.whenToUse ?? null,
556
+ modelInvocable: contents?.modelInvocable ?? true,
557
+ userInvocable: contents?.userInvocable ?? true,
558
+ allowedTools: contents?.allowedTools ?? [],
559
+ unavailableTools: (contents?.allowedTools ?? []).filter((tool) => !available.has(tool)),
560
+ resourceCount: stored.resourceCount,
561
+ valid: !stored.diagnostics.some((entry) => entry.severity === "error"),
562
+ diagnostics: stored.diagnostics,
563
+ updatedAt: stored.updatedAt,
564
+ revision: stored.revision,
565
+ };
566
+ }
567
+ document(stored, available) {
568
+ const contents = stored.contents;
569
+ return {
570
+ ...this.summarize(stored, available),
571
+ body: contents?.body ?? skillFileBody(stored.raw),
572
+ extraFrontmatter: contents?.extraFrontmatter ?? {},
573
+ sourcePath: stored.filePath,
574
+ preview: contents === undefined ? stored.raw : serializeSkillFile(contents),
575
+ };
576
+ }
577
+ storageError(error) {
578
+ if (error instanceof QaPersonalSkillError)
579
+ return error;
580
+ return new QaPersonalSkillError("storage-unavailable", error instanceof Error ? error.message : String(error));
581
+ }
582
+ /**
583
+ * Publish one write: audit it, then refresh the DSH catalog. The two are
584
+ * separate stages on purpose — a stored skill whose catalog refresh failed
585
+ * is still stored, and the deployment needs to see which half broke.
586
+ */
587
+ refreshCatalog(action, context, name) {
588
+ // The account id is hashed: an audit line names no personal data.
589
+ this.options.logger.info(action, {
590
+ user: createHash("sha256")
591
+ .update(context.userId)
592
+ .digest("hex")
593
+ .slice(0, 12),
594
+ skill: name,
595
+ });
596
+ try {
597
+ this.options.invalidate();
598
+ }
599
+ catch (error) {
600
+ this.options.logger.error("skill.provider.invalidate-failed", {
601
+ skill: name,
602
+ message: error instanceof Error ? error.message : String(error),
603
+ });
604
+ }
605
+ }
606
+ }
607
+ /**
608
+ * The one place the feature defines what an account-scoped cwd looks like:
609
+ * `<registered workspace>/.qa-users/<uuid>`. Anything else names no personal
610
+ * root, which is what keeps the discovery provider from reading arbitrary
611
+ * directories a session happens to be pinned to.
612
+ */
613
+ function qaUserWorkspaceFromCwd(cwd) {
614
+ const segments = path.resolve(cwd).split(/[/\\]+/u);
615
+ const userId = segments.at(-1);
616
+ const usersDirectory = segments.at(-2);
617
+ if (userId === undefined || usersDirectory === undefined)
618
+ return undefined;
619
+ const matches = process.platform === "win32"
620
+ ? usersDirectory.toLowerCase() === QA_USER_WORKSPACES_DIRECTORY
621
+ : usersDirectory === QA_USER_WORKSPACES_DIRECTORY;
622
+ if (!matches)
623
+ return undefined;
624
+ try {
625
+ return existingQaUserWorkspace(path.resolve(cwd, "..", ".."), userId);
626
+ }
627
+ catch {
628
+ return undefined;
629
+ }
630
+ }
631
+ function hashText(text) {
632
+ return createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex");
633
+ }
634
+ function pathExists(input) {
635
+ try {
636
+ statSync(input);
637
+ return true;
638
+ }
639
+ catch {
640
+ return false;
641
+ }
642
+ }
643
+ /**
644
+ * Read one skill file, refusing to hold an unbounded file in memory: an
645
+ * oversized `SKILL.md` is read only up to the ceiling and reported by the
646
+ * validator, which keeps the editor usable instead of the Host unresponsive.
647
+ * The file is lstat-ed, so a symlinked `SKILL.md` is treated as absent rather
648
+ * than followed out of the account's directory.
649
+ */
650
+ function readSkillText(filePath, maxBytes) {
651
+ let size;
652
+ let updatedAt;
653
+ try {
654
+ const stat = lstatSync(filePath);
655
+ if (!stat.isFile())
656
+ return undefined;
657
+ size = stat.size;
658
+ updatedAt = stat.mtime.toISOString();
659
+ }
660
+ catch {
661
+ return undefined;
662
+ }
663
+ if (size <= maxBytes) {
664
+ try {
665
+ return { text: readFileSync(filePath, "utf8"), updatedAt };
666
+ }
667
+ catch {
668
+ return undefined;
669
+ }
670
+ }
671
+ try {
672
+ const handle = openSync(filePath, "r");
673
+ try {
674
+ const buffer = Buffer.alloc(maxBytes);
675
+ const read = readSync(handle, buffer, 0, maxBytes, 0);
676
+ return { text: buffer.subarray(0, read).toString("utf8"), updatedAt };
677
+ }
678
+ finally {
679
+ closeSync(handle);
680
+ }
681
+ }
682
+ catch {
683
+ return undefined;
684
+ }
685
+ }
686
+ //# sourceMappingURL=service.js.map