kennel 0.2.1 → 0.2.3

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 (872) hide show
  1. package/README.md +18 -5
  2. package/dist/cli/dispatch.js +9 -0
  3. package/dist/cli/src/automations/notifications.d.ts +40 -0
  4. package/dist/cli/src/automations/notifications.js +220 -0
  5. package/dist/cli/src/cli/dispatch.d.ts +1 -0
  6. package/dist/cli/src/cli/dispatch.js +125 -0
  7. package/dist/cli/src/cli/usage.d.ts +1 -0
  8. package/dist/cli/src/cli/usage.js +25 -0
  9. package/dist/cli/src/core/api-client.d.ts +13 -0
  10. package/dist/cli/src/core/api-client.js +1 -0
  11. package/dist/cli/src/core/command.d.ts +1 -0
  12. package/dist/cli/src/core/command.js +69 -0
  13. package/dist/cli/src/core/core-folders.d.ts +1 -0
  14. package/dist/cli/src/core/core-folders.js +42 -0
  15. package/dist/cli/src/core/init.d.ts +1 -0
  16. package/dist/cli/src/core/init.js +141 -0
  17. package/dist/cli/src/core/runtime.d.ts +14 -0
  18. package/dist/cli/src/core/runtime.js +1 -0
  19. package/dist/cli/src/deploy/cloudflare.d.ts +6 -0
  20. package/dist/cli/src/deploy/cloudflare.js +100 -0
  21. package/dist/cli/src/deploy/environment.d.ts +2 -0
  22. package/dist/cli/src/deploy/environment.js +47 -0
  23. package/dist/cli/src/deploy/smoke-tests.d.ts +4 -0
  24. package/dist/cli/src/deploy/smoke-tests.js +92 -0
  25. package/dist/cli/src/deploy/trigger-dev.d.ts +2 -0
  26. package/dist/cli/src/deploy/trigger-dev.js +255 -0
  27. package/dist/cli/src/deploy/vercel.d.ts +3 -0
  28. package/dist/cli/src/deploy/vercel.js +67 -0
  29. package/dist/cli/src/main.d.ts +1 -0
  30. package/dist/cli/src/main.js +9 -0
  31. package/dist/cli/src/mcp/agent-tools.d.ts +8 -0
  32. package/dist/cli/src/mcp/agent-tools.js +247 -0
  33. package/dist/cli/src/mcp/config.d.ts +25 -0
  34. package/dist/cli/src/mcp/config.js +185 -0
  35. package/dist/cli/src/mcp/function-source.d.ts +7 -0
  36. package/dist/cli/src/mcp/function-source.js +42 -0
  37. package/dist/cli/src/mcp/generate.d.ts +1 -0
  38. package/dist/cli/src/mcp/generate.js +47 -0
  39. package/dist/cli/src/mcp/install-page.d.ts +12 -0
  40. package/dist/cli/src/mcp/install-page.js +5 -0
  41. package/dist/cli/src/notifications/email-worker.d.ts +8 -0
  42. package/dist/cli/src/notifications/email-worker.js +137 -0
  43. package/dist/cli/src/package/mcp-data.d.ts +1 -0
  44. package/dist/cli/src/package/mcp-data.js +1 -0
  45. package/dist/cli/src/package/mcp-email-worker.d.ts +1 -0
  46. package/dist/cli/src/package/mcp-email-worker.js +1 -0
  47. package/dist/cli/src/package/mcp-http.d.ts +1 -0
  48. package/dist/cli/src/package/mcp-http.js +1 -0
  49. package/dist/cli/src/package/mcp-stdio.d.ts +1 -0
  50. package/dist/cli/src/package/mcp-stdio.js +1 -0
  51. package/dist/cli/src/package/mcp.d.ts +1 -0
  52. package/dist/cli/src/package/mcp.js +1 -0
  53. package/dist/cli/src/package/preview.d.ts +9 -0
  54. package/dist/cli/src/package/preview.js +1 -0
  55. package/dist/cli/src/plugins/external.d.ts +36 -0
  56. package/dist/cli/src/plugins/external.js +503 -0
  57. package/dist/cli/src/publish/cloudflare-init.d.ts +12 -0
  58. package/dist/cli/src/publish/cloudflare-init.js +101 -0
  59. package/dist/cli/src/publish/config.d.ts +28 -0
  60. package/dist/cli/src/publish/config.js +178 -0
  61. package/dist/cli/src/publish/options.d.ts +13 -0
  62. package/dist/cli/src/publish/options.js +78 -0
  63. package/dist/cli/src/publish/vercel-init.d.ts +9 -0
  64. package/dist/cli/src/publish/vercel-init.js +52 -0
  65. package/dist/cli/src/shared/json.d.ts +4 -0
  66. package/dist/cli/src/shared/json.js +56 -0
  67. package/dist/cli/src/shared/object.d.ts +2 -0
  68. package/dist/cli/src/shared/object.js +3 -0
  69. package/dist/cli/src/shared/package-manager.d.ts +11 -0
  70. package/dist/cli/src/shared/package-manager.js +39 -0
  71. package/dist/cli/src/shared/path.d.ts +4 -0
  72. package/dist/cli/src/shared/path.js +40 -0
  73. package/dist/cli/src/shared/process.d.ts +7 -0
  74. package/dist/cli/src/shared/process.js +12 -0
  75. package/dist/cli/src/shared/project-config.d.ts +2 -0
  76. package/dist/cli/src/shared/project-config.js +26 -0
  77. package/dist/cli/src/site/dev.d.ts +10 -0
  78. package/dist/cli/src/site/dev.js +126 -0
  79. package/dist/cli/src/site/extension.d.ts +8 -0
  80. package/dist/cli/src/site/extension.js +113 -0
  81. package/dist/cli/src/site/folder-icons.d.ts +46 -0
  82. package/dist/cli/src/site/folder-icons.js +86 -0
  83. package/dist/cli/src/site/hot-viewer.d.ts +2 -0
  84. package/dist/cli/src/site/hot-viewer.js +231 -0
  85. package/dist/cli/src/site/kinds.d.ts +1 -0
  86. package/dist/cli/src/site/kinds.js +1 -0
  87. package/dist/cli/src/site/mail.d.ts +1 -0
  88. package/dist/cli/src/site/mail.js +10 -0
  89. package/dist/cli/src/site/renderers.d.ts +61 -0
  90. package/dist/cli/src/site/renderers.js +219 -0
  91. package/dist/cli/src/site/server-plugins.d.ts +54 -0
  92. package/dist/cli/src/site/server-plugins.js +1 -0
  93. package/dist/cli/src/site/viewer.d.ts +36 -0
  94. package/dist/cli/src/site/viewer.js +352 -0
  95. package/dist/cli/src/site/vitepress.d.ts +1 -0
  96. package/dist/cli/src/site/vitepress.js +1 -0
  97. package/dist/cli/usage.js +1 -0
  98. package/dist/core/api-client.js +75 -44
  99. package/dist/core/command.js +6 -3
  100. package/dist/core/core-folders.js +38280 -33
  101. package/dist/core/init.js +59 -4
  102. package/dist/core/runtime.js +57163 -41853
  103. package/dist/package/preview.js +40589 -17894
  104. package/dist/plugins/external.d.ts +36 -0
  105. package/dist/plugins/external.js +503 -0
  106. package/dist/site/server-plugins.js +394 -59
  107. package/dist/site/viewer.js +18 -5
  108. package/kernel/apps/web/.env.example +11 -0
  109. package/kernel/apps/web/components.json +20 -0
  110. package/kernel/apps/web/eslint.config.mjs +60 -0
  111. package/kernel/apps/web/index.html +17 -0
  112. package/kernel/apps/web/package.json +79 -0
  113. package/kernel/apps/web/public/favicon.svg +6 -0
  114. package/kernel/apps/web/public/providers/claude.svg +1 -0
  115. package/kernel/apps/web/public/providers/codex.svg +1 -0
  116. package/kernel/apps/web/public/providers/cursor.svg +1 -0
  117. package/kernel/apps/web/public/providers/opencode.svg +1 -0
  118. package/kernel/apps/web/public/providers/pi.svg +1 -0
  119. package/kernel/apps/web/server/acp/index.ts +1 -0
  120. package/kernel/apps/web/server/acp/live-agent-connection.ts +1 -0
  121. package/kernel/apps/web/server/acp/process-manager.ts +1 -0
  122. package/kernel/apps/web/server/acp/registry.ts +1 -0
  123. package/kernel/apps/web/server/acp/sdk.ts +1 -0
  124. package/kernel/apps/web/server/acp/transport.ts +1 -0
  125. package/kernel/apps/web/server/acp/types.ts +1 -0
  126. package/kernel/apps/web/server/acp-live-plugin.ts +30 -0
  127. package/kernel/apps/web/server/api/document-edit.ts +1 -0
  128. package/kernel/apps/web/server/api/open-path.ts +1 -0
  129. package/kernel/apps/web/server/core-plugin.ts +49 -0
  130. package/kernel/apps/web/server/folder-favicon.ts +1 -0
  131. package/kernel/apps/web/server/folder-icons.ts +1 -0
  132. package/kernel/apps/web/server/folder-metadata-plugin.ts +31 -0
  133. package/kernel/apps/web/server/plugins.ts +1 -0
  134. package/kernel/apps/web/server/preview-kinds.ts +1 -0
  135. package/kernel/apps/web/src/ai-input.tsx +62 -0
  136. package/kernel/apps/web/src/annotations.tsx +430 -0
  137. package/kernel/apps/web/src/api/client.ts +3 -0
  138. package/kernel/apps/web/src/api/errors.ts +1 -0
  139. package/kernel/apps/web/src/api/events.ts +42 -0
  140. package/kernel/apps/web/src/app.css +332 -0
  141. package/kernel/apps/web/src/app.tsx +667 -0
  142. package/kernel/apps/web/src/ask-draft.ts +164 -0
  143. package/kernel/apps/web/src/ask-tray.tsx +256 -0
  144. package/kernel/apps/web/src/comment-editor.tsx +93 -0
  145. package/kernel/apps/web/src/comment-form.tsx +162 -0
  146. package/kernel/apps/web/src/copy-path.ts +18 -0
  147. package/kernel/apps/web/src/deferred-ask-tray.tsx +20 -0
  148. package/kernel/apps/web/src/document-url.ts +46 -0
  149. package/kernel/apps/web/src/documents-url.ts +23 -0
  150. package/kernel/apps/web/src/dropped-folder.ts +64 -0
  151. package/kernel/apps/web/src/external-plugins.ts +3 -0
  152. package/kernel/apps/web/src/file-viewer.tsx +171 -0
  153. package/kernel/apps/web/src/folder-filter-icons.tsx +39 -0
  154. package/kernel/apps/web/src/folder-metadata.ts +76 -0
  155. package/kernel/apps/web/src/folder-plugin-view.tsx +87 -0
  156. package/kernel/apps/web/src/folder-query-cache.ts +66 -0
  157. package/kernel/apps/web/src/host-adapters.ts +209 -0
  158. package/kernel/apps/web/src/host-bridge.ts +214 -0
  159. package/kernel/apps/web/src/lib/entity-folder.ts +2 -0
  160. package/kernel/apps/web/src/linked-session-header.tsx +43 -0
  161. package/kernel/apps/web/src/live-session-controls.tsx +326 -0
  162. package/kernel/apps/web/src/local-source.ts +32 -0
  163. package/kernel/apps/web/src/main.tsx +24 -0
  164. package/kernel/apps/web/src/markdown-page.tsx +444 -0
  165. package/kernel/apps/web/src/notes-page.tsx +103 -0
  166. package/kernel/apps/web/src/plugins.ts +53 -0
  167. package/kernel/apps/web/src/project-search-dialog.tsx +257 -0
  168. package/kernel/apps/web/src/project-search.ts +18 -0
  169. package/kernel/apps/web/src/query-client.ts +4 -0
  170. package/kernel/apps/web/src/rehype-source-lines.ts +22 -0
  171. package/kernel/apps/web/src/routes/document.tsx +1 -0
  172. package/kernel/apps/web/src/routes/entities.tsx +277 -0
  173. package/kernel/apps/web/src/routes/search.tsx +60 -0
  174. package/kernel/apps/web/src/selection-context.ts +68 -0
  175. package/kernel/apps/web/src/selection-input.tsx +58 -0
  176. package/kernel/apps/web/src/selection-popup.tsx +155 -0
  177. package/kernel/apps/web/src/session-transcript-page.tsx +152 -0
  178. package/kernel/apps/web/src/use-annotations.ts +162 -0
  179. package/kernel/apps/web/src/use-ask-ai.ts +26 -0
  180. package/kernel/apps/web/src/use-ask-draft.ts +27 -0
  181. package/kernel/apps/web/src/use-document.ts +37 -0
  182. package/kernel/apps/web/src/use-folder-notes.ts +49 -0
  183. package/kernel/apps/web/src/use-linked-session.ts +33 -0
  184. package/kernel/apps/web/src/view-context.tsx +69 -0
  185. package/kernel/apps/web/src/view-header.tsx +66 -0
  186. package/kernel/apps/web/src/viewer-capabilities.ts +33 -0
  187. package/kernel/apps/web/src/viewer-route.ts +1 -0
  188. package/kernel/apps/web/src/viewport-height.ts +33 -0
  189. package/kernel/apps/web/tsconfig.json +24 -0
  190. package/kernel/apps/web/vite.config.ts +252 -0
  191. package/kernel/packages/agents/eslint.config.js +10 -0
  192. package/kernel/packages/agents/package.json +75 -0
  193. package/kernel/packages/agents/src/agent-card.tsx +334 -0
  194. package/kernel/packages/agents/src/client.tsx +13 -0
  195. package/kernel/packages/agents/src/index.ts +18 -0
  196. package/kernel/packages/agents/src/manifest.ts +7 -0
  197. package/kernel/packages/agents/src/node.ts +447 -0
  198. package/kernel/packages/agents/src/profiles.ts +137 -0
  199. package/kernel/packages/agents/src/resolve.ts +69 -0
  200. package/kernel/packages/agents/src/server.ts +60 -0
  201. package/kernel/packages/agents/src/types.ts +82 -0
  202. package/kernel/packages/agents/src/viewer.tsx +370 -0
  203. package/kernel/packages/agents/test/profiles.test.mjs +88 -0
  204. package/kernel/packages/agents/test/resolve.test.mjs +729 -0
  205. package/kernel/packages/agents/tsconfig.json +17 -0
  206. package/kernel/packages/api/README.md +73 -0
  207. package/kernel/packages/api/openapi.json +3368 -0
  208. package/kernel/packages/api/package.json +41 -0
  209. package/kernel/packages/api/src/client/errors.ts +1 -0
  210. package/kernel/packages/api/src/client/index.ts +2 -0
  211. package/kernel/packages/api/src/contracts/schemas.ts +277 -0
  212. package/kernel/packages/api/src/contracts.ts +20 -0
  213. package/kernel/packages/api/src/index.ts +34 -0
  214. package/kernel/packages/api/src/server/acp/index.ts +35 -0
  215. package/kernel/packages/api/src/server/acp/live-agent-connection.ts +44 -0
  216. package/kernel/packages/api/src/server/acp/process-manager.ts +291 -0
  217. package/kernel/packages/api/src/server/acp/registry.ts +140 -0
  218. package/kernel/packages/api/src/server/acp/sdk.ts +15 -0
  219. package/kernel/packages/api/src/server/acp/transport.ts +115 -0
  220. package/kernel/packages/api/src/server/acp/types.ts +89 -0
  221. package/kernel/packages/api/src/server/acp-live-plugin.ts +423 -0
  222. package/kernel/packages/api/src/server/api/create-agent-file.ts +46 -0
  223. package/kernel/packages/api/src/server/api/document-edit.ts +125 -0
  224. package/kernel/packages/api/src/server/api/open-path.ts +40 -0
  225. package/kernel/packages/api/src/server/contracts.ts +85 -0
  226. package/kernel/packages/api/src/server/entity-folder.ts +353 -0
  227. package/kernel/packages/api/src/server/folder-favicon.ts +17 -0
  228. package/kernel/packages/api/src/server/folder-icons.ts +113 -0
  229. package/kernel/packages/api/src/server/folder-metadata-plugin.ts +1758 -0
  230. package/kernel/packages/api/src/server/host.ts +21 -0
  231. package/kernel/packages/api/src/server/index.ts +197 -0
  232. package/kernel/packages/api/src/server/linked-folder-discovery.ts +158 -0
  233. package/kernel/packages/api/src/server/plugins.ts +205 -0
  234. package/kernel/packages/api/src/server/project-discovery.ts +69 -0
  235. package/kernel/packages/api/src/server/watcher.ts +46 -0
  236. package/kernel/packages/api/src/shared/selection.ts +16 -0
  237. package/kernel/packages/api/src/shared/viewer-route.ts +200 -0
  238. package/kernel/packages/api/test/agents-loading.test.ts +94 -0
  239. package/kernel/packages/api/test/core.test.ts +364 -0
  240. package/kernel/packages/api/test/entity-folder.test.ts +131 -0
  241. package/kernel/packages/api/test/linked-folder-discovery.test.ts +78 -0
  242. package/kernel/packages/api/test/media-endpoint.test.ts +83 -0
  243. package/kernel/packages/api/test/partial-loading.test.ts +43 -0
  244. package/kernel/packages/api/test/project-discovery.test.ts +44 -0
  245. package/kernel/packages/api/tsconfig.json +11 -0
  246. package/kernel/packages/api-client/README.md +72 -0
  247. package/kernel/packages/api-client/package.json +26 -0
  248. package/kernel/packages/api-client/scripts/generate.ts +68 -0
  249. package/kernel/packages/api-client/src/errors.ts +59 -0
  250. package/kernel/packages/api-client/src/generated.d.ts +1248 -0
  251. package/kernel/packages/api-client/src/index.ts +206 -0
  252. package/kernel/packages/api-client/src/responses.ts +19 -0
  253. package/kernel/packages/api-client/test/client.test.ts +143 -0
  254. package/kernel/packages/api-client/tsconfig.json +12 -0
  255. package/kernel/packages/folder-plugin/eslint.config.js +10 -0
  256. package/kernel/packages/folder-plugin/package.json +50 -0
  257. package/kernel/packages/folder-plugin/src/client.ts +73 -0
  258. package/kernel/packages/folder-plugin/src/endpoint.ts +273 -0
  259. package/kernel/packages/folder-plugin/src/index.ts +15 -0
  260. package/kernel/packages/folder-plugin/src/server.ts +78 -0
  261. package/kernel/packages/folder-plugin/src/subpath.ts +34 -0
  262. package/kernel/packages/folder-plugin/src/types.ts +86 -0
  263. package/kernel/packages/folder-plugin/src/validation.ts +56 -0
  264. package/kernel/packages/folder-plugin/test/contract.test.mjs +133 -0
  265. package/kernel/packages/folder-plugin/tsconfig.json +17 -0
  266. package/kernel/packages/kinds/package.json +24 -0
  267. package/kernel/packages/kinds/src/kinds.ts +172 -0
  268. package/kernel/packages/markdown/README.md +40 -0
  269. package/kernel/packages/markdown/explorer.css +1 -0
  270. package/kernel/packages/markdown/package.json +30 -0
  271. package/kernel/packages/markdown/src/documents.ts +1 -0
  272. package/kernel/packages/markdown/src/explorer.ts +1 -0
  273. package/kernel/packages/markdown/src/index.ts +2 -0
  274. package/kernel/packages/markdown/src/metadata.ts +63 -0
  275. package/kernel/packages/markdown/src/node.ts +1 -0
  276. package/kernel/packages/markdown/styles.css +1 -0
  277. package/kernel/packages/notes/SKILL.md +19 -0
  278. package/kernel/packages/notes/eslint.config.js +10 -0
  279. package/kernel/packages/notes/package.json +72 -0
  280. package/kernel/packages/notes/src/client.tsx +12 -0
  281. package/kernel/packages/notes/src/data/ai-input.ts +58 -0
  282. package/kernel/packages/notes/src/data/api.ts +40 -0
  283. package/kernel/packages/notes/src/data/parse.ts +15 -0
  284. package/kernel/packages/notes/src/data/routing.ts +33 -0
  285. package/kernel/packages/notes/src/data/use-folder-notes.ts +49 -0
  286. package/kernel/packages/notes/src/index.ts +2 -0
  287. package/kernel/packages/notes/src/manifest.ts +6 -0
  288. package/kernel/packages/notes/src/pages/notes-page.tsx +79 -0
  289. package/kernel/packages/notes/src/pages/single-note-page.tsx +57 -0
  290. package/kernel/packages/notes/src/server.ts +88 -0
  291. package/kernel/packages/notes/src/ui/ai-composer-controls.tsx +70 -0
  292. package/kernel/packages/notes/src/ui/folder-notes-list.tsx +140 -0
  293. package/kernel/packages/notes/tsconfig.json +16 -0
  294. package/kernel/packages/schemas/ICON-POLICY.md +43 -0
  295. package/kernel/packages/schemas/SKILL.md +185 -0
  296. package/kernel/packages/schemas/eslint.config.js +10 -0
  297. package/kernel/packages/schemas/package.json +81 -0
  298. package/kernel/packages/schemas/src/client.tsx +137 -0
  299. package/kernel/packages/schemas/src/data/config-file.ts +37 -0
  300. package/kernel/packages/schemas/src/data/contract.ts +174 -0
  301. package/kernel/packages/schemas/src/data/favicon.ts +218 -0
  302. package/kernel/packages/schemas/src/data/fields.ts +96 -0
  303. package/kernel/packages/schemas/src/data/icon-image.ts +53 -0
  304. package/kernel/packages/schemas/src/data/icon.ts +97 -0
  305. package/kernel/packages/schemas/src/data/load.ts +161 -0
  306. package/kernel/packages/schemas/src/data/params.ts +103 -0
  307. package/kernel/packages/schemas/src/data/query.ts +33 -0
  308. package/kernel/packages/schemas/src/data/sort.ts +50 -0
  309. package/kernel/packages/schemas/src/data/workspace.ts +101 -0
  310. package/kernel/packages/schemas/src/index.ts +22 -0
  311. package/kernel/packages/schemas/src/manifest.ts +4 -0
  312. package/kernel/packages/schemas/src/node.ts +384 -0
  313. package/kernel/packages/schemas/src/pages/collection.tsx +252 -0
  314. package/kernel/packages/schemas/src/server.ts +208 -0
  315. package/kernel/packages/schemas/src/ui/schema-entity-list.tsx +124 -0
  316. package/kernel/packages/schemas/src/ui/schema-filters.tsx +186 -0
  317. package/kernel/packages/schemas/src/ui/schema-search.tsx +35 -0
  318. package/kernel/packages/schemas/src/ui/use-query-param-filter-setting.tsx +15 -0
  319. package/kernel/packages/schemas/test/contract.test.mjs +26 -0
  320. package/kernel/packages/schemas/test/fields.test.mjs +187 -0
  321. package/kernel/packages/schemas/test/load.test.mjs +12 -0
  322. package/kernel/packages/schemas/test/node.test.mjs +412 -0
  323. package/kernel/packages/schemas/test/query.test.mjs +134 -0
  324. package/kernel/packages/schemas/test/workspace.test.mjs +86 -0
  325. package/kernel/packages/schemas/tsconfig.json +17 -0
  326. package/kernel/packages/session-finding/package.json +33 -0
  327. package/kernel/packages/session-finding/src/index.ts +120 -0
  328. package/kernel/packages/session-finding/test/list-sessions.test.mjs +139 -0
  329. package/kernel/packages/session-finding/tsconfig.json +12 -0
  330. package/kernel/packages/ui/LICENSE +21 -0
  331. package/kernel/packages/ui/components.json +20 -0
  332. package/kernel/packages/ui/eslint.config.js +8 -0
  333. package/kernel/packages/ui/package.json +106 -0
  334. package/kernel/packages/ui/src/chat/file-link.ts +178 -0
  335. package/kernel/packages/ui/src/chat/index.ts +18 -0
  336. package/kernel/packages/ui/src/chat/transcript.tsx +348 -0
  337. package/kernel/packages/ui/src/chat/turns.ts +76 -0
  338. package/kernel/packages/ui/src/chat/types.ts +23 -0
  339. package/kernel/packages/ui/src/chat/use-overflowing.ts +56 -0
  340. package/kernel/packages/ui/src/components/accordion.tsx +74 -0
  341. package/kernel/packages/ui/src/components/alert-dialog.tsx +164 -0
  342. package/kernel/packages/ui/src/components/alert.tsx +68 -0
  343. package/kernel/packages/ui/src/components/app-breadcrumbs.tsx +170 -0
  344. package/kernel/packages/ui/src/components/app-header.tsx +125 -0
  345. package/kernel/packages/ui/src/components/app-options.tsx +87 -0
  346. package/kernel/packages/ui/src/components/app-view-header.tsx +34 -0
  347. package/kernel/packages/ui/src/components/aspect-ratio.tsx +11 -0
  348. package/kernel/packages/ui/src/components/avatar.tsx +53 -0
  349. package/kernel/packages/ui/src/components/badge.tsx +47 -0
  350. package/kernel/packages/ui/src/components/base-dialog.tsx +3 -0
  351. package/kernel/packages/ui/src/components/base-tabs.tsx +63 -0
  352. package/kernel/packages/ui/src/components/breadcrumb.tsx +110 -0
  353. package/kernel/packages/ui/src/components/button-group.tsx +86 -0
  354. package/kernel/packages/ui/src/components/button.tsx +65 -0
  355. package/kernel/packages/ui/src/components/calendar.tsx +240 -0
  356. package/kernel/packages/ui/src/components/card.tsx +95 -0
  357. package/kernel/packages/ui/src/components/carousel.tsx +240 -0
  358. package/kernel/packages/ui/src/components/chart.tsx +375 -0
  359. package/kernel/packages/ui/src/components/checkbox.tsx +39 -0
  360. package/kernel/packages/ui/src/components/collapsible.tsx +33 -0
  361. package/kernel/packages/ui/src/components/collection-icon.tsx +28 -0
  362. package/kernel/packages/ui/src/components/command.tsx +205 -0
  363. package/kernel/packages/ui/src/components/content-issues.tsx +63 -0
  364. package/kernel/packages/ui/src/components/context-menu.tsx +298 -0
  365. package/kernel/packages/ui/src/components/copy-context.tsx +134 -0
  366. package/kernel/packages/ui/src/components/dialog.tsx +157 -0
  367. package/kernel/packages/ui/src/components/drawer.tsx +162 -0
  368. package/kernel/packages/ui/src/components/dropdown-menu.tsx +303 -0
  369. package/kernel/packages/ui/src/components/empty.tsx +108 -0
  370. package/kernel/packages/ui/src/components/field.tsx +260 -0
  371. package/kernel/packages/ui/src/components/file-preview.tsx +152 -0
  372. package/kernel/packages/ui/src/components/file-renderer.tsx +466 -0
  373. package/kernel/packages/ui/src/components/filter-chip-panel.tsx +157 -0
  374. package/kernel/packages/ui/src/components/filter-select.tsx +127 -0
  375. package/kernel/packages/ui/src/components/folder-activity.tsx +646 -0
  376. package/kernel/packages/ui/src/components/folder-file-list.tsx +58 -0
  377. package/kernel/packages/ui/src/components/folder-filter-bar.tsx +191 -0
  378. package/kernel/packages/ui/src/components/folder-image.tsx +18 -0
  379. package/kernel/packages/ui/src/components/folder-landing.tsx +70 -0
  380. package/kernel/packages/ui/src/components/folder-list.tsx +140 -0
  381. package/kernel/packages/ui/src/components/folder-metadata.tsx +107 -0
  382. package/kernel/packages/ui/src/components/folder-mobile-bar.tsx +60 -0
  383. package/kernel/packages/ui/src/components/folder-project-list.tsx +133 -0
  384. package/kernel/packages/ui/src/components/folder-session-list.tsx +113 -0
  385. package/kernel/packages/ui/src/components/folder-session-page.tsx +126 -0
  386. package/kernel/packages/ui/src/components/folder-sidebar.tsx +52 -0
  387. package/kernel/packages/ui/src/components/folder-skill-list.tsx +62 -0
  388. package/kernel/packages/ui/src/components/folder-tabs.tsx +156 -0
  389. package/kernel/packages/ui/src/components/form.tsx +167 -0
  390. package/kernel/packages/ui/src/components/hover-card.tsx +52 -0
  391. package/kernel/packages/ui/src/components/icons.tsx +34 -0
  392. package/kernel/packages/ui/src/components/input-group.tsx +184 -0
  393. package/kernel/packages/ui/src/components/input-otp.tsx +90 -0
  394. package/kernel/packages/ui/src/components/input.tsx +29 -0
  395. package/kernel/packages/ui/src/components/item.tsx +195 -0
  396. package/kernel/packages/ui/src/components/kbd.tsx +32 -0
  397. package/kernel/packages/ui/src/components/label.tsx +27 -0
  398. package/kernel/packages/ui/src/components/link.tsx +45 -0
  399. package/kernel/packages/ui/src/components/live-tool-call.tsx +193 -0
  400. package/kernel/packages/ui/src/components/menubar.tsx +323 -0
  401. package/kernel/packages/ui/src/components/navigation-menu.tsx +210 -0
  402. package/kernel/packages/ui/src/components/note-card.tsx +149 -0
  403. package/kernel/packages/ui/src/components/pagination.tsx +128 -0
  404. package/kernel/packages/ui/src/components/popover.tsx +56 -0
  405. package/kernel/packages/ui/src/components/progress.tsx +31 -0
  406. package/kernel/packages/ui/src/components/radio-group.tsx +54 -0
  407. package/kernel/packages/ui/src/components/scroll-area.tsx +61 -0
  408. package/kernel/packages/ui/src/components/select.tsx +219 -0
  409. package/kernel/packages/ui/src/components/separator.tsx +30 -0
  410. package/kernel/packages/ui/src/components/session-icon.tsx +124 -0
  411. package/kernel/packages/ui/src/components/sheet.tsx +157 -0
  412. package/kernel/packages/ui/src/components/sidebar.tsx +789 -0
  413. package/kernel/packages/ui/src/components/skeleton.tsx +13 -0
  414. package/kernel/packages/ui/src/components/slider.tsx +73 -0
  415. package/kernel/packages/ui/src/components/sonner.tsx +41 -0
  416. package/kernel/packages/ui/src/components/spinner.tsx +16 -0
  417. package/kernel/packages/ui/src/components/switch.tsx +40 -0
  418. package/kernel/packages/ui/src/components/table.tsx +120 -0
  419. package/kernel/packages/ui/src/components/tabs.tsx +78 -0
  420. package/kernel/packages/ui/src/components/textarea.tsx +25 -0
  421. package/kernel/packages/ui/src/components/toggle-group.tsx +87 -0
  422. package/kernel/packages/ui/src/components/toggle.tsx +48 -0
  423. package/kernel/packages/ui/src/components/tooltip.tsx +71 -0
  424. package/kernel/packages/ui/src/components/viewer-options.tsx +166 -0
  425. package/kernel/packages/ui/src/hooks/use-folder-filter.ts +103 -0
  426. package/kernel/packages/ui/src/hooks/use-mobile.ts +19 -0
  427. package/kernel/packages/ui/src/index.ts +13 -0
  428. package/kernel/packages/ui/src/lib/document-extensions.ts +83 -0
  429. package/kernel/packages/ui/src/lib/folder-activity.ts +143 -0
  430. package/kernel/packages/ui/src/lib/folder-filter.ts +284 -0
  431. package/kernel/packages/ui/src/lib/live-tool-call.tsx +109 -0
  432. package/kernel/packages/ui/src/lib/media-urls.ts +176 -0
  433. package/kernel/packages/ui/src/lib/obsidian-links.ts +120 -0
  434. package/kernel/packages/ui/src/lib/source-components.tsx +65 -0
  435. package/kernel/packages/ui/src/lib/utils.tsx +7 -0
  436. package/kernel/packages/ui/src/pages/entity-detail-page.tsx +82 -0
  437. package/kernel/packages/ui/src/pages/entity-list-page.tsx +50 -0
  438. package/kernel/packages/ui/src/pages/entity-page.tsx +69 -0
  439. package/kernel/packages/ui/src/styles/folder-activity.css +2151 -0
  440. package/kernel/packages/ui/test/copy-context.test.mjs +48 -0
  441. package/kernel/packages/ui/test/folder-filter.test.mjs +484 -0
  442. package/kernel/packages/ui/tsconfig.json +15 -0
  443. package/kernel/plugins/calendar/SKILL.md +173 -0
  444. package/kernel/plugins/calendar/eslint.config.js +10 -0
  445. package/kernel/plugins/calendar/package.json +73 -0
  446. package/kernel/plugins/calendar/schema.json +137 -0
  447. package/kernel/plugins/calendar/src/calendar.tsx +328 -0
  448. package/kernel/plugins/calendar/src/client.tsx +139 -0
  449. package/kernel/plugins/calendar/src/context.ts +48 -0
  450. package/kernel/plugins/calendar/src/copy-button.tsx +66 -0
  451. package/kernel/plugins/calendar/src/event-dialog.tsx +270 -0
  452. package/kernel/plugins/calendar/src/index.ts +4 -0
  453. package/kernel/plugins/calendar/src/manifest.ts +7 -0
  454. package/kernel/plugins/calendar/src/parse.ts +610 -0
  455. package/kernel/plugins/calendar/src/server.ts +160 -0
  456. package/kernel/plugins/calendar/src/utils.ts +80 -0
  457. package/kernel/plugins/calendar/src/views.tsx +418 -0
  458. package/kernel/plugins/calendar/test/context.test.mjs +60 -0
  459. package/kernel/plugins/calendar/test/package.test.mjs +43 -0
  460. package/kernel/plugins/calendar/test/parse.test.mjs +392 -0
  461. package/kernel/plugins/calendar/test/server.test.mjs +126 -0
  462. package/kernel/plugins/calendar/tsconfig.json +17 -0
  463. package/kernel/plugins/mail/SKILL.md +97 -0
  464. package/kernel/plugins/mail/eslint.config.js +10 -0
  465. package/kernel/plugins/mail/package.json +79 -0
  466. package/kernel/plugins/mail/src/api.ts +359 -0
  467. package/kernel/plugins/mail/src/bun-ffi.d.ts +14 -0
  468. package/kernel/plugins/mail/src/client.tsx +14 -0
  469. package/kernel/plugins/mail/src/flock.ts +137 -0
  470. package/kernel/plugins/mail/src/folders.ts +37 -0
  471. package/kernel/plugins/mail/src/history.ts +485 -0
  472. package/kernel/plugins/mail/src/index.ts +3 -0
  473. package/kernel/plugins/mail/src/mail-card.tsx +123 -0
  474. package/kernel/plugins/mail/src/mail-conversation.tsx +153 -0
  475. package/kernel/plugins/mail/src/mail-detail-page.tsx +467 -0
  476. package/kernel/plugins/mail/src/mail-hooks.ts +252 -0
  477. package/kernel/plugins/mail/src/mail-page.tsx +109 -0
  478. package/kernel/plugins/mail/src/mail-request.ts +78 -0
  479. package/kernel/plugins/mail/src/mail-send-box.tsx +175 -0
  480. package/kernel/plugins/mail/src/mailos.ts +723 -0
  481. package/kernel/plugins/mail/src/manifest.ts +7 -0
  482. package/kernel/plugins/mail/src/node.ts +171 -0
  483. package/kernel/plugins/mail/src/parse.ts +139 -0
  484. package/kernel/plugins/mail/src/server.ts +125 -0
  485. package/kernel/plugins/mail/src/types.ts +10 -0
  486. package/kernel/plugins/mail/src/viewer.tsx +147 -0
  487. package/kernel/plugins/mail/test/flock.test.mjs +156 -0
  488. package/kernel/plugins/mail/test/folders.test.mjs +138 -0
  489. package/kernel/plugins/mail/test/history.test.mjs +460 -0
  490. package/kernel/plugins/mail/test/mailos.test.mjs +752 -0
  491. package/kernel/plugins/mail/test/node.test.mjs +207 -0
  492. package/kernel/plugins/mail/test/package.test.mjs +54 -0
  493. package/kernel/plugins/mail/test/parse.test.mjs +32 -0
  494. package/kernel/plugins/mail/test/server.test.mjs +116 -0
  495. package/kernel/plugins/mail/test/viewer.test.mjs +192 -0
  496. package/kernel/plugins/mail/tsconfig.json +17 -0
  497. package/kernel/plugins/projects/SKILL.md +73 -0
  498. package/kernel/plugins/projects/eslint.config.js +10 -0
  499. package/kernel/plugins/projects/package.json +70 -0
  500. package/kernel/plugins/projects/src/client.tsx +17 -0
  501. package/kernel/plugins/projects/src/data/model.ts +129 -0
  502. package/kernel/plugins/projects/src/index.ts +17 -0
  503. package/kernel/plugins/projects/src/manifest.ts +7 -0
  504. package/kernel/plugins/projects/src/project-context.ts +41 -0
  505. package/kernel/plugins/projects/src/server.ts +276 -0
  506. package/kernel/plugins/projects/src/ui/project-card.tsx +61 -0
  507. package/kernel/plugins/projects/src/ui/projects-page.tsx +102 -0
  508. package/kernel/plugins/projects/test/package.test.mjs +51 -0
  509. package/kernel/plugins/projects/tsconfig.json +17 -0
  510. package/kernel/plugins/tasks/SKILL.md +341 -0
  511. package/kernel/plugins/tasks/eslint.config.js +10 -0
  512. package/kernel/plugins/tasks/package.json +74 -0
  513. package/kernel/plugins/tasks/schema.json +939 -0
  514. package/kernel/plugins/tasks/src/api.ts +212 -0
  515. package/kernel/plugins/tasks/src/client.tsx +23 -0
  516. package/kernel/plugins/tasks/src/data/parse.ts +544 -0
  517. package/kernel/plugins/tasks/src/data/task-id.ts +56 -0
  518. package/kernel/plugins/tasks/src/data/todo-model.ts +795 -0
  519. package/kernel/plugins/tasks/src/index.ts +34 -0
  520. package/kernel/plugins/tasks/src/manifest.ts +7 -0
  521. package/kernel/plugins/tasks/src/pages/task-detail.tsx +48 -0
  522. package/kernel/plugins/tasks/src/pages/task-list.tsx +88 -0
  523. package/kernel/plugins/tasks/src/server.ts +447 -0
  524. package/kernel/plugins/tasks/src/styles.css +9 -0
  525. package/kernel/plugins/tasks/src/ui/compact-todo-list.tsx +372 -0
  526. package/kernel/plugins/tasks/src/ui/error-details.tsx +78 -0
  527. package/kernel/plugins/tasks/src/ui/task-page.tsx +392 -0
  528. package/kernel/plugins/tasks/src/ui/todo-agent-filter.tsx +36 -0
  529. package/kernel/plugins/tasks/src/ui/todo-filter-bar.tsx +66 -0
  530. package/kernel/plugins/tasks/src/ui/todo-item.tsx +494 -0
  531. package/kernel/plugins/tasks/src/ui/todo-status-filter.tsx +60 -0
  532. package/kernel/plugins/tasks/src/ui/todo-tag-filter.tsx +48 -0
  533. package/kernel/plugins/tasks/src/ui/todo-warnings.tsx +106 -0
  534. package/kernel/plugins/tasks/test/api.test.mjs +99 -0
  535. package/kernel/plugins/tasks/test/folder-sources.test.mjs +46 -0
  536. package/kernel/plugins/tasks/test/package.test.mjs +54 -0
  537. package/kernel/plugins/tasks/test/parse.test.mjs +225 -0
  538. package/kernel/plugins/tasks/test/server.test.mjs +386 -0
  539. package/kernel/plugins/tasks/tsconfig.json +17 -0
  540. package/node_modules/@agentclientprotocol/sdk/LICENSE +191 -0
  541. package/node_modules/@agentclientprotocol/sdk/README.md +70 -0
  542. package/node_modules/@agentclientprotocol/sdk/dist/acp.d.ts +1835 -0
  543. package/node_modules/@agentclientprotocol/sdk/dist/acp.js +1885 -0
  544. package/node_modules/@agentclientprotocol/sdk/dist/acp.js.map +1 -0
  545. package/node_modules/@agentclientprotocol/sdk/dist/acp.test.d.ts +1 -0
  546. package/node_modules/@agentclientprotocol/sdk/dist/acp.test.js +5036 -0
  547. package/node_modules/@agentclientprotocol/sdk/dist/acp.test.js.map +1 -0
  548. package/node_modules/@agentclientprotocol/sdk/dist/connection.d.ts +82 -0
  549. package/node_modules/@agentclientprotocol/sdk/dist/connection.js +428 -0
  550. package/node_modules/@agentclientprotocol/sdk/dist/connection.js.map +1 -0
  551. package/node_modules/@agentclientprotocol/sdk/dist/connection.test.d.ts +1 -0
  552. package/node_modules/@agentclientprotocol/sdk/dist/connection.test.js +470 -0
  553. package/node_modules/@agentclientprotocol/sdk/dist/connection.test.js.map +1 -0
  554. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.d.ts +26 -0
  555. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.js +96 -0
  556. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.js.map +1 -0
  557. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.d.ts +1 -0
  558. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.js +77 -0
  559. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.js.map +1 -0
  560. package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.d.ts +2 -0
  561. package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js +232 -0
  562. package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js.map +1 -0
  563. package/node_modules/@agentclientprotocol/sdk/dist/examples/client.d.ts +2 -0
  564. package/node_modules/@agentclientprotocol/sdk/dist/examples/client.js +132 -0
  565. package/node_modules/@agentclientprotocol/sdk/dist/examples/client.js.map +1 -0
  566. package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.d.ts +9 -0
  567. package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.js +221 -0
  568. package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.js.map +1 -0
  569. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.d.ts +2 -0
  570. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.js +86 -0
  571. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.js.map +1 -0
  572. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.d.ts +2 -0
  573. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.js +112 -0
  574. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.js.map +1 -0
  575. package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.d.ts +2 -0
  576. package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.js +88 -0
  577. package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.js.map +1 -0
  578. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.d.ts +33 -0
  579. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.js +435 -0
  580. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.js.map +1 -0
  581. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.d.ts +1 -0
  582. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.js +1380 -0
  583. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.js.map +1 -0
  584. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.d.ts +603 -0
  585. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js +1064 -0
  586. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js.map +1 -0
  587. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.d.ts +1 -0
  588. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.js +851 -0
  589. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.js.map +1 -0
  590. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.d.ts +22 -0
  591. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.js +64 -0
  592. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.js.map +1 -0
  593. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.d.ts +1 -0
  594. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.js +45 -0
  595. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.js.map +1 -0
  596. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.d.ts +17 -0
  597. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.js +426 -0
  598. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.js.map +1 -0
  599. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.d.ts +1 -0
  600. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.js +499 -0
  601. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.js.map +1 -0
  602. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.d.ts +37 -0
  603. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.js +474 -0
  604. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.js.map +1 -0
  605. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.d.ts +1 -0
  606. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.js +535 -0
  607. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.js.map +1 -0
  608. package/node_modules/@agentclientprotocol/sdk/dist/protocol.d.ts +11 -0
  609. package/node_modules/@agentclientprotocol/sdk/dist/protocol.js +57 -0
  610. package/node_modules/@agentclientprotocol/sdk/dist/protocol.js.map +1 -0
  611. package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.d.ts +1 -0
  612. package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.js +73 -0
  613. package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.js.map +1 -0
  614. package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.d.ts +199 -0
  615. package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js +211 -0
  616. package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js.map +1 -0
  617. package/node_modules/@agentclientprotocol/sdk/dist/schema/index.d.ts +51 -0
  618. package/node_modules/@agentclientprotocol/sdk/dist/schema/index.js +52 -0
  619. package/node_modules/@agentclientprotocol/sdk/dist/schema/index.js.map +1 -0
  620. package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.d.ts +6118 -0
  621. package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js +3 -0
  622. package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js.map +1 -0
  623. package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.d.ts +14160 -0
  624. package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js +3066 -0
  625. package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js.map +1 -0
  626. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.d.ts +30 -0
  627. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js +98 -0
  628. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js.map +1 -0
  629. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.d.ts +1 -0
  630. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.js +93 -0
  631. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.js.map +1 -0
  632. package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.d.ts +1 -0
  633. package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.js +261 -0
  634. package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.js.map +1 -0
  635. package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.d.ts +1 -0
  636. package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.js +490 -0
  637. package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.js.map +1 -0
  638. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.d.ts +2 -0
  639. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.js +104 -0
  640. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.js.map +1 -0
  641. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.d.ts +1 -0
  642. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.js +52 -0
  643. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.js.map +1 -0
  644. package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.d.ts +1 -0
  645. package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.js +946 -0
  646. package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.js.map +1 -0
  647. package/node_modules/@agentclientprotocol/sdk/dist/server.d.ts +81 -0
  648. package/node_modules/@agentclientprotocol/sdk/dist/server.js +380 -0
  649. package/node_modules/@agentclientprotocol/sdk/dist/server.js.map +1 -0
  650. package/node_modules/@agentclientprotocol/sdk/dist/server.test.d.ts +1 -0
  651. package/node_modules/@agentclientprotocol/sdk/dist/server.test.js +1082 -0
  652. package/node_modules/@agentclientprotocol/sdk/dist/server.test.js.map +1 -0
  653. package/node_modules/@agentclientprotocol/sdk/dist/sse.d.ts +4 -0
  654. package/node_modules/@agentclientprotocol/sdk/dist/sse.js +91 -0
  655. package/node_modules/@agentclientprotocol/sdk/dist/sse.js.map +1 -0
  656. package/node_modules/@agentclientprotocol/sdk/dist/sse.test.d.ts +1 -0
  657. package/node_modules/@agentclientprotocol/sdk/dist/sse.test.js +126 -0
  658. package/node_modules/@agentclientprotocol/sdk/dist/sse.test.js.map +1 -0
  659. package/node_modules/@agentclientprotocol/sdk/dist/stream.d.ts +35 -0
  660. package/node_modules/@agentclientprotocol/sdk/dist/stream.js +106 -0
  661. package/node_modules/@agentclientprotocol/sdk/dist/stream.js.map +1 -0
  662. package/node_modules/@agentclientprotocol/sdk/dist/stream.test.d.ts +1 -0
  663. package/node_modules/@agentclientprotocol/sdk/dist/stream.test.js +206 -0
  664. package/node_modules/@agentclientprotocol/sdk/dist/stream.test.js.map +1 -0
  665. package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.d.ts +8 -0
  666. package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.js +51 -0
  667. package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.js.map +1 -0
  668. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.d.ts +24 -0
  669. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.js +163 -0
  670. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.js.map +1 -0
  671. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.d.ts +9 -0
  672. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.js +74 -0
  673. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.js.map +1 -0
  674. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.d.ts +806 -0
  675. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.js +1183 -0
  676. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.js.map +1 -0
  677. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.d.ts +1 -0
  678. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.js +512 -0
  679. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.js.map +1 -0
  680. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.d.ts +954 -0
  681. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.js +1035 -0
  682. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.js.map +1 -0
  683. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.d.ts +42 -0
  684. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.js +43 -0
  685. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.js.map +1 -0
  686. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.d.ts +6146 -0
  687. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.js +3 -0
  688. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.js.map +1 -0
  689. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.d.ts +30552 -0
  690. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.js +3204 -0
  691. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.js.map +1 -0
  692. package/node_modules/@agentclientprotocol/sdk/dist/ws-server.d.ts +14 -0
  693. package/node_modules/@agentclientprotocol/sdk/dist/ws-server.js +358 -0
  694. package/node_modules/@agentclientprotocol/sdk/dist/ws-server.js.map +1 -0
  695. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.d.ts +54 -0
  696. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.js +246 -0
  697. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.js.map +1 -0
  698. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.d.ts +1 -0
  699. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.js +833 -0
  700. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.js.map +1 -0
  701. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.d.ts +13 -0
  702. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.js +78 -0
  703. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.js.map +1 -0
  704. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.d.ts +1 -0
  705. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.js +42 -0
  706. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.js.map +1 -0
  707. package/node_modules/@agentclientprotocol/sdk/package.json +105 -0
  708. package/node_modules/@agentclientprotocol/sdk/schema/schema.json +8819 -0
  709. package/node_modules/@agentclientprotocol/sdk/schema/v2/schema.unstable.json +10098 -0
  710. package/package.json +52 -6
  711. package/web/assets/{abnfDiagram-VRR7QNED-Nfq-O04D.js → abnfDiagram-VRR7QNED-Bgfb1nEB.js} +1 -1
  712. package/web/assets/app-DoGr2Zi7.js +2 -0
  713. package/web/assets/app-options-D-RJiQ-X.js +25 -0
  714. package/web/assets/app-view-header-BLvLq02T.js +1 -0
  715. package/web/assets/arc-Dk_KI36B.js +1 -0
  716. package/web/assets/{architectureDiagram-ZJ3FMSHR-qhRwPNcQ.js → architectureDiagram-ZJ3FMSHR-NR_KhRCf.js} +1 -1
  717. package/web/assets/ask-tray-TzeTcO6-.js +14 -0
  718. package/web/assets/{blockDiagram-677ZJIJ3-C56P3bGu.js → blockDiagram-677ZJIJ3-CkLkwx9C.js} +1 -1
  719. package/web/assets/button-C6arH7zt.js +1 -0
  720. package/web/assets/{c4Diagram-LMCZKHZV-Ey1M72zD.js → c4Diagram-LMCZKHZV-CFCUH8e_.js} +1 -1
  721. package/web/assets/channel-uMEjV8qc.js +1 -0
  722. package/web/assets/{chunk-2Q5K7J3B-a6isk9VU.js → chunk-2Q5K7J3B-B_BPLWsW.js} +1 -1
  723. package/web/assets/{chunk-32BRIVSS-uy4kdSLL.js → chunk-32BRIVSS-B1VB1hOy.js} +1 -1
  724. package/web/assets/{chunk-5VM5RSS4-oj2sBFd7.js → chunk-5VM5RSS4-KFcYr8Co.js} +1 -1
  725. package/web/assets/{chunk-EX3LRPZG-Bzl1ukAi.js → chunk-EX3LRPZG-CpBX3kuK.js} +3 -3
  726. package/web/assets/{chunk-JWPE2WC7-sUph7I_t.js → chunk-JWPE2WC7-BPSl40wG.js} +1 -1
  727. package/web/assets/{chunk-MOJQB5TN-C0ERSuVK.js → chunk-MOJQB5TN-D2RcYmoU.js} +1 -1
  728. package/web/assets/{chunk-RYQCIY6F-Dyw1Hfwp.js → chunk-RYQCIY6F-m7Gyn-t3.js} +1 -1
  729. package/web/assets/{chunk-V7JOEXUC-DnSz22rb.js → chunk-V7JOEXUC-TDvdopxS.js} +1 -1
  730. package/web/assets/{chunk-VR4S4FIN-Bp2SNel7.js → chunk-VR4S4FIN-DZTsG51a.js} +1 -1
  731. package/web/assets/{chunk-XXDRQBXY-Dqtar746.js → chunk-XXDRQBXY-Dp0--RE2.js} +1 -1
  732. package/web/assets/classDiagram-OUVF2IWQ-fbZocVor.js +1 -0
  733. package/web/assets/classDiagram-v2-EOCWNBFH-fbZocVor.js +1 -0
  734. package/web/assets/client-C6Nxdfif.js +2 -0
  735. package/web/assets/client-CXYfBko5.js +17 -0
  736. package/web/assets/client-CoKtOq-X.js +7 -0
  737. package/web/assets/client-DyRQz5RU.js +53 -0
  738. package/web/assets/client-OY6HUzuD.js +98 -0
  739. package/web/assets/client-YOYWZIFi.js +34 -0
  740. package/web/assets/client-j2kQmxsO.js +22 -0
  741. package/web/assets/command-BugZBGXE.js +38 -0
  742. package/web/assets/copy-context-C0Uqsp-3.js +1 -0
  743. package/web/assets/{cose-bilkent-JH36ORCC-Dx1VqMjj.js → cose-bilkent-JH36ORCC-CPu3PI8h.js} +1 -1
  744. package/web/assets/{cynefin-VYW2F7L2-SjhCWmIV.js → cynefin-VYW2F7L2-DWxftYAm.js} +1 -1
  745. package/web/assets/{cynefinDiagram-TSTJHNR4-Dvqz2594.js → cynefinDiagram-TSTJHNR4-DsmN2V-p.js} +1 -1
  746. package/web/assets/{dagre-VKFMJZFB-Ju2m2qXt.js → dagre-VKFMJZFB-COEbApyY.js} +1 -1
  747. package/web/assets/{diagram-FQU43EPY-DsHKTt0k.js → diagram-FQU43EPY-0O9WGWKB.js} +1 -1
  748. package/web/assets/{diagram-G47NLZAW-DWHAcLkJ.js → diagram-G47NLZAW-CutEdMVh.js} +1 -1
  749. package/web/assets/{diagram-NH7WQ7WH-uUMz46ZD.js → diagram-NH7WQ7WH-C9KZc3B2.js} +1 -1
  750. package/web/assets/{diagram-OA4YK3LP-BRjVzjdp.js → diagram-OA4YK3LP-D8kkPXbY.js} +1 -1
  751. package/web/assets/{diagram-WEI45ONY-Cb-FhBpQ.js → diagram-WEI45ONY-_YqiTaGP.js} +1 -1
  752. package/web/assets/document-Dgt5eZFj.js +19 -0
  753. package/web/assets/{ebnfDiagram-CCIWWBDH-IhhxX3IB.js → ebnfDiagram-CCIWWBDH-JO-zQh6g.js} +1 -1
  754. package/web/assets/{entities-DUnx74kH.js → entities-C3wz7FCx.js} +12 -12
  755. package/web/assets/{erDiagram-Q63AITRT-D1frgeWf.js → erDiagram-Q63AITRT-9UjQwJ4P.js} +1 -1
  756. package/web/assets/file-viewer-DZJ8nYnl.js +1 -0
  757. package/web/assets/{flowDiagram-23GEKE2U-BiPPM-FY.js → flowDiagram-23GEKE2U-Ca08tnYM.js} +5 -5
  758. package/web/assets/folder-image-BfGgxo7i.js +1 -0
  759. package/web/assets/folder-landing-qUKBCnG-.js +54 -0
  760. package/web/assets/folder-list-BfFjv7rR.js +1 -0
  761. package/web/assets/folder-metadata-DCw8xzHk.js +2 -0
  762. package/web/assets/folder-mobile-bar-Ubz4babf.js +1 -0
  763. package/web/assets/folder-plugin-view-DsWR1zmQ.js +6 -0
  764. package/web/assets/{ganttDiagram-NO4QXBWP-CmEOlHD7.js → ganttDiagram-NO4QXBWP-KnHzX5W3.js} +1 -1
  765. package/web/assets/{gitGraphDiagram-IHSO6WYX-tTjcpZpU.js → gitGraphDiagram-IHSO6WYX-Cm8woNtK.js} +1 -1
  766. package/web/assets/index-BaRnbYRw.js +1 -0
  767. package/web/assets/index-BexmLxIP.css +1 -0
  768. package/web/assets/index-Bp9DidgD.js +1 -0
  769. package/web/assets/{index-eL1k96k8.js → index-CCXE8zc9.js} +1 -1
  770. package/web/assets/index-CNyy98F8.js +2 -0
  771. package/web/assets/{index-DmrKjrlM.js → index-CXhBn-ar.js} +1 -1
  772. package/web/assets/index-DDirZMgx.js +1 -0
  773. package/web/assets/{infoDiagram-FWYZ7A6U-Ok2sdbc1.js → infoDiagram-FWYZ7A6U-yyqmUtIb.js} +1 -1
  774. package/web/assets/{ishikawaDiagram-FXEZZL3T-kPnM5psX.js → ishikawaDiagram-FXEZZL3T-BfvR_Zy4.js} +4 -4
  775. package/web/assets/{journeyDiagram-5HDEW3XC-BkoT0-45.js → journeyDiagram-5HDEW3XC-BKuw06HM.js} +1 -1
  776. package/web/assets/{kanban-definition-HUTT4EX6-xnm_eFoU.js → kanban-definition-HUTT4EX6-n0010rmQ.js} +8 -8
  777. package/web/assets/{linear-CLZpgLH-.js → linear-C0d0lT8O.js} +1 -1
  778. package/web/assets/linked-session-header-BkEweU0Z.js +3 -0
  779. package/web/assets/{markdown-njUwQKsR.js → markdown-Bw9RgUGR.js} +9 -9
  780. package/web/assets/{mermaid.core-Bvxj3RoN.js → mermaid.core-BDVbShyO.js} +6 -6
  781. package/web/assets/metadata-KV0dpp4G.js +1 -0
  782. package/web/assets/{mindmap-definition-LN4V7U3C-42Uoox79.js → mindmap-definition-LN4V7U3C--_0ks3GF.js} +1 -1
  783. package/web/assets/note-card-DBfX6gW1.js +6 -0
  784. package/web/assets/{pegDiagram-2B236MQR-B-GZCw3w.js → pegDiagram-2B236MQR-CBoNIpKR.js} +1 -1
  785. package/web/assets/{pieDiagram-ENE6RG2P-DRmmssIB.js → pieDiagram-ENE6RG2P-MA5tkYh5.js} +3 -3
  786. package/web/assets/popover-DBD7FF-W.js +9 -0
  787. package/web/assets/{project-search-dialog-B730oZ1S.js → project-search-dialog-DM1IkSZu.js} +6 -6
  788. package/web/assets/{quadrantDiagram-ABIIQ3AL-BfhVQ7V9.js → quadrantDiagram-ABIIQ3AL-CWT1ryqF.js} +1 -1
  789. package/web/assets/{railroadDiagram-RFXS5EU6-DxKx7XRp.js → railroadDiagram-RFXS5EU6-ZiLB5FGi.js} +1 -1
  790. package/web/assets/{react-DZzLHL-l.js → react-B9jn2M18.js} +2 -2
  791. package/web/assets/{requirementDiagram-TGXJPOKE-NUzj9fw1.js → requirementDiagram-TGXJPOKE-CQq9uneq.js} +1 -1
  792. package/web/assets/{sankeyDiagram-HTMAVEWB-Cy9OMnl-.js → sankeyDiagram-HTMAVEWB-qDG-0JqI.js} +1 -1
  793. package/web/assets/search-DNrQA5fF.js +2 -0
  794. package/web/assets/select-D8cqNgcp.js +30 -0
  795. package/web/assets/selection-popup-DmtTeRs2.js +7 -0
  796. package/web/assets/{sequenceDiagram-DBY2YBRQ-CBNE9mzn.js → sequenceDiagram-DBY2YBRQ-CiApurLO.js} +1 -1
  797. package/web/assets/{sizeCapture-X5ZJPWSS-S4HhfX0n.js → sizeCapture-X5ZJPWSS-DZSR5QXm.js} +1 -1
  798. package/web/assets/stateDiagram-2N3HPSRC-CjJJR9_z.js +1 -0
  799. package/web/assets/stateDiagram-v2-6OUMAXLB-BpeGSCu9.js +1 -0
  800. package/web/assets/{swimlanes-5IMT3BWC-DJ2aLWal.js → swimlanes-5IMT3BWC-CKmo1sg6.js} +2 -2
  801. package/web/assets/swimlanesDiagram-G3AALYLV-D9RjLKb9.js +8 -0
  802. package/web/assets/tanstack-YzX_Mi37.js +3 -0
  803. package/web/assets/{timeline-definition-FHXFAJF6-vFDUVPCX.js → timeline-definition-FHXFAJF6-D-NMwS3y.js} +1 -1
  804. package/web/assets/{use-folder-filter-BbTUlXgG.js → use-folder-filter-LQyXns5Q.js} +1 -1
  805. package/web/assets/{utils-CwHGE3BB.js → utils-DCaHk0Gm.js} +1 -1
  806. package/web/assets/{vennDiagram-L72KCM5P-AsnhK2zW.js → vennDiagram-L72KCM5P-CfL6LBWw.js} +1 -1
  807. package/web/assets/view-context-gyDl_p2V.js +229 -0
  808. package/web/assets/viewer-capabilities-Bpp8Uvcg.js +1 -0
  809. package/web/assets/{wardleyDiagram-EHGQE667-Cj1wFbNV.js → wardleyDiagram-EHGQE667-DgldMxaj.js} +1 -1
  810. package/web/assets/{xychartDiagram-FW5EYKEG-Bbvnutsm.js → xychartDiagram-FW5EYKEG-CIoAIFX8.js} +1 -1
  811. package/web/favicon.svg +6 -0
  812. package/web/index.html +5 -4
  813. package/web/assets/app-CPSmsokP.js +0 -2
  814. package/web/assets/app-options-XEWvevsz.js +0 -25
  815. package/web/assets/app-view-header-BKZ90ToF.js +0 -1
  816. package/web/assets/arc-DbpbZvCt.js +0 -1
  817. package/web/assets/arrow-left-CFeHb-Z1.js +0 -1
  818. package/web/assets/ask-tray-D2YHtbPj.js +0 -14
  819. package/web/assets/button-BJ7z-ZA7.js +0 -1
  820. package/web/assets/calendar-days-DWh2XMO2.js +0 -1
  821. package/web/assets/channel-dSRp_qTo.js +0 -1
  822. package/web/assets/chevron-down-C5oRtqbX.js +0 -1
  823. package/web/assets/chevron-right-CsPtvh8C.js +0 -1
  824. package/web/assets/chevron-up-DjpU6Pyv.js +0 -1
  825. package/web/assets/classDiagram-OUVF2IWQ-B7B2zLzM.js +0 -1
  826. package/web/assets/classDiagram-v2-EOCWNBFH-B7B2zLzM.js +0 -1
  827. package/web/assets/client-CAudjYV9.js +0 -34
  828. package/web/assets/client-Dn9-_Gm_.js +0 -14
  829. package/web/assets/client-Dxrf2tyq.js +0 -102
  830. package/web/assets/client-aFI2M4pE.js +0 -53
  831. package/web/assets/collection-icon-TbOVyORG.js +0 -1
  832. package/web/assets/collection-page-Du6FKkuU.js +0 -10
  833. package/web/assets/command-C_g5E1Wx.js +0 -38
  834. package/web/assets/copy-Ikmr6Iq2.js +0 -1
  835. package/web/assets/copy-context-Cl1TE2PJ.js +0 -1
  836. package/web/assets/document-DcjNSJCk.js +0 -2
  837. package/web/assets/external-link-BXmSXqqF.js +0 -1
  838. package/web/assets/file-renderer-BRZTrYx8.js +0 -19
  839. package/web/assets/file-text-CiiSsCDq.js +0 -1
  840. package/web/assets/file-viewer-C1wAeVDK.js +0 -1
  841. package/web/assets/folder-image-AXBrHUcU.js +0 -1
  842. package/web/assets/folder-landing-BhRg7hwn.js +0 -54
  843. package/web/assets/folder-list-rIdszVde.js +0 -1
  844. package/web/assets/folder-metadata-puTC5QqJ.js +0 -2
  845. package/web/assets/folder-mobile-bar-DjjRQo3T.js +0 -1
  846. package/web/assets/folder-plugin-view-BU_kzR8j.js +0 -6
  847. package/web/assets/index-DS2Wmd9I.css +0 -1
  848. package/web/assets/index-DgqzSeIR.js +0 -1
  849. package/web/assets/index-DnlTDqaa.js +0 -1
  850. package/web/assets/index-DztTMDFz.js +0 -2
  851. package/web/assets/index-kY75qc96.js +0 -1
  852. package/web/assets/index-p38i_yyY.js +0 -1
  853. package/web/assets/link-2-BWKA6t--.js +0 -1
  854. package/web/assets/link-DC_1Z6Yy.js +0 -11
  855. package/web/assets/linked-session-header-DJYWZ_-q.js +0 -3
  856. package/web/assets/mail-2rCdIFul.js +0 -1
  857. package/web/assets/markdown-title-dncAYYiP.js +0 -131
  858. package/web/assets/notes-page-DIW6rgJp.js +0 -10
  859. package/web/assets/pencil-8cobiSSq.js +0 -1
  860. package/web/assets/plus-BHV2wJr7.js +0 -1
  861. package/web/assets/popover-Cmmgj-MJ.js +0 -9
  862. package/web/assets/search-CvlYUkN6.js +0 -2
  863. package/web/assets/select-1SvaKQsS.js +0 -30
  864. package/web/assets/selection-popup-ntjycEVh.js +0 -7
  865. package/web/assets/sparkles-CBhGREdc.js +0 -1
  866. package/web/assets/stateDiagram-2N3HPSRC-BEw1Mheu.js +0 -1
  867. package/web/assets/stateDiagram-v2-6OUMAXLB-BHtjl1iD.js +0 -1
  868. package/web/assets/swimlanesDiagram-G3AALYLV-Z362aroZ.js +0 -8
  869. package/web/assets/tanstack-CD46mJkU.js +0 -1
  870. package/web/assets/view-context-D-fh9AbH.js +0 -81
  871. package/web/assets/viewer-capabilities-CROIaoqh.js +0 -1
  872. package/web/assets/x-BUeE--I4.js +0 -1
@@ -0,0 +1,1758 @@
1
+ import { createHash } from "node:crypto";
2
+ import { sendContract, validateQuery } from "./contracts";
3
+ import { realpathSync } from "node:fs";
4
+ import { readdir, readFile, stat } from "node:fs/promises";
5
+ import { homedir } from "node:os";
6
+ import {
7
+ basename,
8
+ dirname,
9
+ extname,
10
+ isAbsolute,
11
+ relative,
12
+ resolve,
13
+ sep,
14
+ } from "node:path";
15
+ import type { ServerResponse } from "node:http";
16
+ import type { ApiHost } from "./host";
17
+ import { markdownMetadata } from "@raggle-ai/markdown";
18
+ import { readFolder } from "@raggle-ai/markdown/node";
19
+ import { execFile } from "node:child_process";
20
+ import { sameOrigin } from "@raggle-ai/folder-plugin/endpoint";
21
+ import { parse as parseYaml } from "yaml";
22
+
23
+ import type { FolderPluginServer } from "@raggle-ai/folder-plugin/server";
24
+ import type { LocalMarkdownFileSummary, LocalProject } from "@raggle-ai/local";
25
+ import type { MailApiContext, MailPreviewer } from "@raggle-ai/mail/api";
26
+ import type { FolderSkill } from "@raggle-ai/ui/lib/folder-activity";
27
+ import { runApiRoute } from "@raggle-ai/folder-plugin/endpoint";
28
+ import { FolderPluginFailure } from "@raggle-ai/folder-plugin/server";
29
+ import { folderWorkspace } from "@raggle-ai/schemas/workspace";
30
+ import { createSchemaCollectionServer } from "@raggle-ai/schemas/server";
31
+ import local from "@raggle-ai/local";
32
+ import { handleMailApiRoutes } from "@raggle-ai/mail/api";
33
+ import { handleTasksApiRoutes } from "@raggle-ai/tasks/api";
34
+ import {
35
+ listFolderSessions,
36
+ readSessionTranscript,
37
+ } from "@raggle-ai/session-finding";
38
+
39
+ import { loadEntityWorkspace } from "./entity-folder.js";
40
+ import { documentEditRoute } from "./api/document-edit";
41
+ import { createAgentFileRoute } from "./api/create-agent-file";
42
+ import { openPathRoute } from "./api/open-path";
43
+ import { folderWebsite } from "./folder-favicon.js";
44
+ import { folderIcon, workspaceBreadcrumbIcons } from "./folder-icons.js";
45
+ import { faviconService } from "@raggle-ai/schemas/favicon";
46
+ import {
47
+ configuredFolderServerRegistry,
48
+ folderServerRegistryAsync,
49
+ type FolderPluginRegistryConfig,
50
+ } from "./plugins.js";
51
+ import { createProjectDiscovery } from "./project-discovery";
52
+ import {
53
+ createLinkedFolderDiscovery,
54
+ type LinkedFolderLoader,
55
+ } from "./linked-folder-discovery";
56
+ import {
57
+ mediaRawMime,
58
+ previewKind,
59
+ previewRawMime,
60
+ previewTextKind,
61
+ } from "@raggle-ai/kennel-kinds";
62
+
63
+ const {
64
+ applyProjectFavorites,
65
+ buildProjectCatalog,
66
+ buildProjectSearchIndex,
67
+ catalogProjectToLocalProject,
68
+ discoverProjectIcon,
69
+ discoverProjectsUnderFolder,
70
+ listLocalMarkdownFiles,
71
+ projectRepositoryAccessory,
72
+ projectSubtitle,
73
+ projectTitle,
74
+ readRaggleUserSettings,
75
+ searchProjectIndex,
76
+ sortProjectList,
77
+ } = local;
78
+
79
+ type SkillSchema = {
80
+ description?: unknown;
81
+ };
82
+
83
+ function skillDescription(content: string) {
84
+ if (!content.startsWith("---\n")) return undefined;
85
+ const end = content.indexOf("\n---", 4);
86
+ if (end < 0) return undefined;
87
+ const schema = parseYaml(content.slice(4, end)) as SkillSchema;
88
+ return typeof schema.description === "string"
89
+ ? schema.description
90
+ : undefined;
91
+ }
92
+
93
+ function skillSource(folder: string, root: string): FolderSkill["source"] {
94
+ if (root === folder) return "local";
95
+ if (root.startsWith(resolve(folder, ".gtl") + sep)) return "local";
96
+ return "global";
97
+ }
98
+
99
+ type IconAsset = {
100
+ mediaType: "image/jpeg" | "image/png";
101
+ source: "local" | "favicon";
102
+ url: string;
103
+ };
104
+
105
+ type ProjectWithIcon = {
106
+ iconAssetKey?: string;
107
+ worktree: string;
108
+ };
109
+
110
+ const maxIconBytes = 512 * 1024;
111
+ const iconTypes = new Map<string, "image/jpeg" | "image/png">([
112
+ [".jpeg", "image/jpeg"],
113
+ [".jpg", "image/jpeg"],
114
+ [".png", "image/png"],
115
+ ]);
116
+ const iconExtensions = new Map<"image/jpeg" | "image/png", string>([
117
+ ["image/jpeg", "jpg"],
118
+ ["image/png", "png"],
119
+ ]);
120
+ const searchIconTypes = new Map<string, string>([
121
+ [".gif", "image/gif"],
122
+ [".ico", "image/x-icon"],
123
+ [".jpeg", "image/jpeg"],
124
+ [".jpg", "image/jpeg"],
125
+ [".png", "image/png"],
126
+ [".svg", "image/svg+xml"],
127
+ [".webp", "image/webp"],
128
+ ]);
129
+
130
+ type SessionFinding = {
131
+ listFolderSessions: typeof listFolderSessions;
132
+ readSessionTranscript: typeof readSessionTranscript;
133
+ };
134
+
135
+ export type FolderMetadataPluginOptions = {
136
+ viewerName?: string;
137
+ restrictToRoot?: boolean;
138
+ root: string;
139
+ sessionFinding?: SessionFinding;
140
+ mailPreviewer?: MailPreviewer;
141
+ /**
142
+ * Inject a MailOS service (tests only). Defaults to a real subprocess
143
+ * service resolved from PATH.
144
+ */
145
+ mailos?: MailApiContext["mailos"];
146
+ /**
147
+ * Override MailOS mail directory. Intended for tests.
148
+ * Defaults to ~/.email/drafts.
149
+ */
150
+ mailDir?: string;
151
+ /** Override the home directory used for Agent global files. Intended for tests. */
152
+ agentsHomeDir?: string;
153
+ /** Override isolated linked-project discovery, for tests. */
154
+ linkedFolderLoader?: LinkedFolderLoader;
155
+ /**
156
+ * Serve an explicitly remote address (0.0.0.0/LAN). Disables server host
157
+ * actions (open in default app) and ACP live sessions; the plugin reports
158
+ * a capability surface the client can render against.
159
+ */
160
+ remoteAccess?: boolean;
161
+ /**
162
+ * Single-file pop lock (`kennel pop <file>`): document reads (and metadata
163
+ * file listings) resolve only this one file; siblings are not served.
164
+ */
165
+ lockedFile?: string;
166
+ };
167
+
168
+ type LocalProjectSearchCache = {
169
+ index: ReturnType<typeof buildProjectSearchIndex<LocalProject>>;
170
+ loadedAt: number;
171
+ };
172
+
173
+ const projectSearchCacheMs = 5_000;
174
+
175
+ const metadataExtensions = new Set([
176
+ ".html",
177
+ ".jpeg",
178
+ ".jpg",
179
+ ".json",
180
+ ".md",
181
+ ".png",
182
+ ".rtf",
183
+ ".svg",
184
+ ".txt",
185
+ ]);
186
+
187
+ function metadataFile(path: string, root: string) {
188
+ const candidate = relative(root, path);
189
+ if (!candidate || candidate.startsWith("..") || isAbsolute(candidate))
190
+ return false;
191
+ const parts = candidate.split(sep);
192
+ if (parts.some((part) => part === "node_modules")) return false;
193
+ return metadataExtensions.has(extname(candidate).toLowerCase());
194
+ }
195
+
196
+ async function configuredAiInputModule(folder: string) {
197
+ const config = folderWorkspace(folder).config as {
198
+ aiInput?: { module?: unknown };
199
+ };
200
+ const module = config.aiInput?.module;
201
+ return typeof module === "string" && module.trim() ? module.trim() : null;
202
+ }
203
+
204
+ function label(value: string) {
205
+ return value
206
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
207
+ .replace(/[-_]+/g, " ")
208
+ .replace(/\b\w/g, (match) => match.toUpperCase());
209
+ }
210
+
211
+ function folderPath(value: string | null, root: string, lockedRoot?: string) {
212
+ const folder = realpathSync(resolve(value ?? root));
213
+ if (!lockedRoot) return folder;
214
+ const path = relative(lockedRoot, folder);
215
+ if (!path || (!path.startsWith("..") && !isAbsolute(path))) return folder;
216
+ throw new Error(`Folder is outside the viewer root: ${folder}`);
217
+ }
218
+
219
+ function openPathHref(path: string) {
220
+ const url = new URL("/api/open-path", "http://127.0.0.1");
221
+ url.searchParams.set("path", path);
222
+ return `${url.pathname}${url.search}`;
223
+ }
224
+
225
+ const loopbackServerHosts = new Set([
226
+ "127.0.0.1",
227
+ "::1",
228
+ "localhost",
229
+ undefined,
230
+ false,
231
+ ] as const);
232
+
233
+ export function isLoopbackServerHost(host: unknown): boolean {
234
+ if (host === undefined || host === false) return true;
235
+ if (typeof host !== "string") return false;
236
+ return (
237
+ loopbackServerHosts.has(host as "localhost") ||
238
+ host.startsWith("127.") ||
239
+ host === "::ffff:127.0.0.1"
240
+ );
241
+ }
242
+
243
+ /**
244
+ * DNS-rebinding guard for the API middleware. Loopback aliases always pass;
245
+ * remote servers additionally accept IP literals, `.local` mDNS names, and
246
+ * hosts from the environment allowlist (`KENNEL_VIEWER_HOSTS`). Arbitrary
247
+ * domain Host headers are rejected before any data is served.
248
+ */
249
+ export function apiHostAllowed(
250
+ value: string | undefined,
251
+ remote: boolean,
252
+ ): boolean {
253
+ // A request without a Host header is not a browser (curl, tests, bots):
254
+ // DNS-rebinding protection targets browser requests, which always send
255
+ // Host, so missing-host requests stay allowed. Arbitrary domain Host
256
+ // headers are still rejected before any data is served.
257
+ if (!value) return true;
258
+ let hostname = value;
259
+ try {
260
+ const parsed = new URL(
261
+ hostname.includes("://") ? hostname : `http://${hostname}`,
262
+ );
263
+ hostname = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
264
+ } catch {
265
+ return false;
266
+ }
267
+ if (isLoopbackServerHost(hostname)) return true;
268
+ if (!remote) return false;
269
+ if (/^[\d.]+$/.test(hostname) || hostname.includes(":")) return true;
270
+ if (hostname.endsWith(".local") || !hostname.includes(".")) return true;
271
+ const allowlist = (process.env.KENNEL_VIEWER_HOSTS ?? "")
272
+ .split(",")
273
+ .map((host) => host.trim().toLowerCase())
274
+ .filter(Boolean);
275
+ return allowlist.includes(hostname);
276
+ }
277
+
278
+ function assetKey(value: Buffer) {
279
+ return createHash("sha256").update(value).digest("hex");
280
+ }
281
+
282
+ function encodeBase64Url(value: string) {
283
+ return Buffer.from(value, "utf8")
284
+ .toString("base64")
285
+ .replaceAll("+", "-")
286
+ .replaceAll("/", "_")
287
+ .replaceAll("=", "");
288
+ }
289
+
290
+ function decodeBase64Url(value: string) {
291
+ const padded = `${value}${"=".repeat((4 - (value.length % 4)) % 4)}`;
292
+ return Buffer.from(
293
+ padded.replaceAll("-", "+").replaceAll("_", "/"),
294
+ "base64",
295
+ ).toString("utf8");
296
+ }
297
+
298
+ function iconUrl(
299
+ folder: string,
300
+ hash: string,
301
+ mediaType: IconAsset["mediaType"],
302
+ ) {
303
+ return `/api/folders/${encodeBase64Url(folder)}/assets/${hash}.${iconExtensions.get(mediaType)}`;
304
+ }
305
+
306
+ async function searchIconDataUrl(icon: string | undefined) {
307
+ if (!icon) return undefined;
308
+ if (icon.startsWith("data:")) return icon;
309
+
310
+ const mediaType = searchIconTypes.get(extname(icon).toLowerCase());
311
+ if (!mediaType) return undefined;
312
+
313
+ const details = await stat(icon).catch(() => undefined);
314
+ if (!details?.size || details.size > maxIconBytes) return undefined;
315
+
316
+ return `data:${mediaType};base64,${(await readFile(icon)).toString("base64")}`;
317
+ }
318
+
319
+ async function iconWithFaviconFallback(
320
+ folder: string,
321
+ assets: Record<string, IconAsset>,
322
+ assetFiles: Map<string, { mediaType: IconAsset["mediaType"]; path: string }>,
323
+ ) {
324
+ return folderIcon(folder, assets, assetFiles, async (folderName) => {
325
+ if (process.env.KENNEL_EXTERNAL_ICONS !== "1") return undefined;
326
+ const host = await folderWebsite(folderName);
327
+ if (!host) return undefined;
328
+ const key = `favicon:${host}`;
329
+ assets[key] = {
330
+ mediaType: "image/png",
331
+ source: "favicon",
332
+ url: `/api/folder-favicon?path=${encodeURIComponent(folderName)}&host=${encodeURIComponent(host)}`,
333
+ };
334
+ return key;
335
+ });
336
+ }
337
+
338
+ async function hydrateAssets(
339
+ metadata: {
340
+ folder: string;
341
+ projects?: { items: ProjectWithIcon[] };
342
+ },
343
+ assetFiles: Map<string, { mediaType: IconAsset["mediaType"]; path: string }>,
344
+ ) {
345
+ const assets: Record<string, IconAsset> = {};
346
+ const projects = metadata.projects?.items ?? [];
347
+ const [iconAssetKey, projectItems] = await Promise.all([
348
+ iconWithFaviconFallback(metadata.folder, assets, assetFiles),
349
+ Promise.all(
350
+ projects.map(async (project) => ({
351
+ ...project,
352
+ iconAssetKey: await iconWithFaviconFallback(
353
+ project.worktree,
354
+ assets,
355
+ assetFiles,
356
+ ),
357
+ })),
358
+ ),
359
+ ]);
360
+ return {
361
+ assets,
362
+ iconAssetKey,
363
+ projects: { items: projectItems },
364
+ };
365
+ }
366
+
367
+ function unique(values: string[]) {
368
+ return [...new Set(values)];
369
+ }
370
+
371
+ async function existingDirectory(path: string) {
372
+ const details = await stat(path).catch(() => undefined);
373
+ return details?.isDirectory() ? path : undefined;
374
+ }
375
+
376
+ function metadataPaths(metadata: {
377
+ files?: { items: Array<{ path?: string; relativePath?: string }> };
378
+ }) {
379
+ return (metadata.files?.items ?? [])
380
+ .flatMap((file) => [file.path, file.relativePath])
381
+ .filter((path): path is string => Boolean(path));
382
+ }
383
+
384
+ function ancestorFolders(folder: string) {
385
+ const folders = [];
386
+ for (
387
+ let current = folder;
388
+ dirname(current) !== current;
389
+ current = dirname(current)
390
+ ) {
391
+ folders.push(current);
392
+ }
393
+ return folders;
394
+ }
395
+
396
+ async function linkedSkillRoots(
397
+ folder: string,
398
+ metadata: {
399
+ files?: { items: Array<{ path?: string; relativePath?: string }> };
400
+ },
401
+ ) {
402
+ const gtlNames = metadataPaths(metadata)
403
+ .map((path) => path.split(/[\\/]\.gtl[\\/]/)[1])
404
+ .filter((path): path is string => Boolean(path))
405
+ .map((path) => path.split(/[\\/]/)[0])
406
+ .filter((name): name is string => Boolean(name));
407
+ const gtlRoots = gtlNames.flatMap((name) => [
408
+ resolve(folder, ".gtl", name),
409
+ ...ancestorFolders(folder).map((ancestor) => resolve(ancestor, name)),
410
+ ]);
411
+
412
+ const name = folder
413
+ .replace(/[\\/]+$/, "")
414
+ .split(/[\\/]/)
415
+ .at(-1);
416
+ const siblingIndex = name
417
+ ? [
418
+ resolve(dirname(folder), `${name}-index`),
419
+ resolve(dirname(dirname(folder)), `${name}-index`),
420
+ ]
421
+ : [];
422
+ const roots = await Promise.all(
423
+ unique([...gtlRoots, ...siblingIndex]).map(existingDirectory),
424
+ );
425
+ return roots.filter((path): path is string => Boolean(path));
426
+ }
427
+
428
+ async function listFolderSkills(
429
+ folder: string,
430
+ metadata: {
431
+ files?: { items: Array<{ path?: string; relativePath?: string }> };
432
+ },
433
+ ) {
434
+ const roots = unique([folder, ...(await linkedSkillRoots(folder, metadata))]);
435
+ const skills = await Promise.all(
436
+ roots.map(async (root) => {
437
+ const skillsFolder = resolve(root, ".agents", "skills");
438
+ const entries = await readdir(skillsFolder, {
439
+ withFileTypes: true,
440
+ }).catch(() => []);
441
+ return (
442
+ await Promise.all(
443
+ entries
444
+ .filter(
445
+ (entry) => entry.isDirectory() && !entry.name.startsWith("."),
446
+ )
447
+ .map(async (entry) => {
448
+ const path = resolve(skillsFolder, entry.name);
449
+ const skillPath = resolve(path, "SKILL.md");
450
+ const details = await stat(skillPath).catch(() => undefined);
451
+ if (!details?.isFile()) return undefined;
452
+ const content = await readFile(skillPath, "utf8").catch(() => "");
453
+ return {
454
+ description: skillDescription(content),
455
+ name: label(entry.name),
456
+ openHref: openPathHref(path),
457
+ relativePath: relative(root, path),
458
+ source: skillSource(folder, root),
459
+ };
460
+ }),
461
+ )
462
+ ).filter((skill) => skill);
463
+ }),
464
+ );
465
+ return unique(skills.flat().map((skill) => JSON.stringify(skill)))
466
+ .map((skill) => JSON.parse(skill) as FolderSkill)
467
+ .sort((left, right) => left.name.localeCompare(right.name));
468
+ }
469
+
470
+ function isMachineRoot(folder: string) {
471
+ return folder === homedir() || dirname(folder) === folder;
472
+ }
473
+
474
+ async function shallowMetadata(folder: string) {
475
+ const entries = await readdir(folder, { withFileTypes: true });
476
+ const visible = entries.filter(
477
+ (entry) => !entry.name.startsWith(".") && entry.name !== "node_modules",
478
+ );
479
+ const files = await Promise.all(
480
+ visible
481
+ .filter(
482
+ (entry) =>
483
+ entry.isFile() &&
484
+ [".markdown", ".md"].includes(extname(entry.name).toLowerCase()),
485
+ )
486
+ .map(async (entry) => {
487
+ const path = resolve(folder, entry.name);
488
+ const details = await stat(path);
489
+ const content = await readFile(path, "utf8").catch(() => "");
490
+ const title = markdownMetadata(content).title;
491
+ return {
492
+ activityAt: details.mtimeMs,
493
+ localEditAt: details.mtimeMs,
494
+ name: title ?? entry.name,
495
+ openHref: openPathHref(path),
496
+ path,
497
+ relativePath: entry.name,
498
+ size: details.size,
499
+ };
500
+ }),
501
+ );
502
+ const projects = visible
503
+ .filter((entry) => entry.isDirectory())
504
+ .map((entry) => ({
505
+ id: createHash("sha1").update(resolve(folder, entry.name)).digest("hex"),
506
+ name: entry.name,
507
+ relativePath: entry.name,
508
+ worktree: resolve(folder, entry.name),
509
+ }))
510
+ .sort((left, right) => left.name.localeCompare(right.name));
511
+
512
+ return {
513
+ changes: { items: [] },
514
+ files: {
515
+ items: files.sort((left, right) => right.activityAt - left.activityAt),
516
+ },
517
+ folder,
518
+ instructions: { items: [] },
519
+ projects: { items: projects },
520
+ schemaVersion: 3 as const,
521
+ sessions: { items: [] },
522
+ skills: { items: [] },
523
+ };
524
+ }
525
+
526
+ async function loadFolderData(folder: string) {
527
+ const [files, projects] = await Promise.all([
528
+ listLocalMarkdownFiles({ folder, limit: 200 }),
529
+ discoverProjectsUnderFolder({ folder }),
530
+ ]);
531
+ return {
532
+ files: { items: files },
533
+ folder,
534
+ projects: {
535
+ items: projects.map((project) => ({
536
+ id: project.id,
537
+ worktree: project.worktree,
538
+ name: project.name ?? basename(project.worktree),
539
+ relativePath: relative(folder, project.worktree),
540
+ })),
541
+ },
542
+ };
543
+ }
544
+
545
+ async function notesFiles(
546
+ folder: string,
547
+ files: LocalMarkdownFileSummary[],
548
+ origin?: string,
549
+ ) {
550
+ return {
551
+ items: await Promise.all(
552
+ files.map(async (file) => {
553
+ const path =
554
+ file.path ??
555
+ (file.relativePath ? resolve(folder, file.relativePath) : undefined);
556
+ if (!path) return origin ? { ...file, folder: origin } : file;
557
+ const content = [".markdown", ".md"].includes(
558
+ extname(path).toLowerCase(),
559
+ )
560
+ ? await readFile(path, "utf8").catch(() => "")
561
+ : "";
562
+ const title = content ? markdownMetadata(content).title : undefined;
563
+ return {
564
+ ...file,
565
+ folder: origin,
566
+ name: title ?? file.name,
567
+ openHref: openPathHref(path),
568
+ };
569
+ }),
570
+ ),
571
+ };
572
+ }
573
+
574
+ async function localProjectSearchCache(
575
+ cache: LocalProjectSearchCache | undefined,
576
+ ) {
577
+ if (cache && Date.now() - cache.loadedAt < projectSearchCacheMs) return cache;
578
+
579
+ const settings = readRaggleUserSettings();
580
+ const projects = applyProjectFavorites(
581
+ (
582
+ await buildProjectCatalog({
583
+ cloneSources: settings.cloneDirectories.map((cloneDirectory) => ({
584
+ cloneDirectory,
585
+ })),
586
+ localRepositoryLinks: settings.localRepositoryLinks,
587
+ projectRoots: settings.projectDirectories,
588
+ repositoryLocations: settings.repositoryLocations,
589
+ schemaScanRoots: settings.projectDirectories,
590
+ })
591
+ ).map(catalogProjectToLocalProject),
592
+ settings.favoriteProjects,
593
+ );
594
+ const ordered = sortProjectList(projects, "recentActivity");
595
+ return {
596
+ index: buildProjectSearchIndex(ordered),
597
+ loadedAt: Date.now(),
598
+ };
599
+ }
600
+
601
+ /**
602
+ * Resolves a media file (image/video/audio) referenced by a document. Mirrors
603
+ * `documentTarget` containment: the target must live inside the folder, must
604
+ * be a regular file, and must have a servable media type. Unlike documents,
605
+ * no preview-kind gating — any media-typed file inside the folder is fair
606
+ * game, since notes legitimately reference assets in sibling directories.
607
+ */
608
+ function mediaTarget(
609
+ folder: string,
610
+ file: string | null,
611
+ lockedTarget?: string,
612
+ ) {
613
+ if (!file) return undefined;
614
+ const rootReal = realpathSync(resolve(folder));
615
+ const path = resolve(folder, file);
616
+ let pathReal: string;
617
+ try {
618
+ pathReal = realpathSync(path);
619
+ } catch {
620
+ // Missing, unreadable, or dangling-symlink targets are not media.
621
+ return undefined;
622
+ }
623
+ const rel = relative(rootReal, pathReal);
624
+ if (rel.startsWith("..") || isAbsolute(rel)) return undefined;
625
+ // Single-file pop lock: only the popped file is previewable.
626
+ if (lockedTarget && pathReal !== lockedTarget) return undefined;
627
+ const mime = mediaRawMime(pathReal);
628
+ if (!mime) return undefined;
629
+ return { mime, path: pathReal, root: rootReal };
630
+ }
631
+
632
+ /** Parses a single `bytes=start-end` Range header into a satisfiable slice. */
633
+ export function parseByteRange(
634
+ header: string | undefined,
635
+ size: number,
636
+ ): { end: number; start: number } | "invalid" | undefined {
637
+ if (!header) return undefined;
638
+ const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
639
+ if (!match) return undefined;
640
+ const [, rawStart, rawEnd] = match;
641
+ if (!rawStart && !rawEnd) return undefined;
642
+ if (!rawStart) {
643
+ // Suffix range: last N bytes.
644
+ const length = Number(rawEnd);
645
+ if (!length) return "invalid";
646
+ return { end: size - 1, start: Math.max(0, size - length) };
647
+ }
648
+ const start = Number(rawStart);
649
+ const end = rawEnd ? Math.min(Number(rawEnd), size - 1) : size - 1;
650
+ if (start >= size || start > end) return "invalid";
651
+ return { end, start };
652
+ }
653
+
654
+ function documentError(response: ServerResponse) {
655
+ response.writeHead(404, {
656
+ "Content-Type": "application/json; charset=utf-8",
657
+ });
658
+ response.end(JSON.stringify({ error: "Document not found." }));
659
+ }
660
+
661
+ function documentTarget(
662
+ folder: string,
663
+ file: string | null,
664
+ lockedTarget?: string,
665
+ ) {
666
+ if (!file) return undefined;
667
+ const rootReal = realpathSync(resolve(folder));
668
+ const path = resolve(folder, file);
669
+ let pathReal: string;
670
+ try {
671
+ pathReal = realpathSync(path);
672
+ } catch {
673
+ // Missing, unreadable, or dangling-symlink targets are not documents.
674
+ return undefined;
675
+ }
676
+ const rel = relative(rootReal, pathReal);
677
+ if (rel.startsWith("..") || isAbsolute(rel)) return undefined;
678
+ // Single-file pop lock: only the popped file is previewable.
679
+ if (lockedTarget && pathReal !== lockedTarget) return undefined;
680
+ const kind = previewKind(pathReal);
681
+ if (!kind) return undefined;
682
+ return { kind, path: pathReal, root: rootReal };
683
+ }
684
+
685
+ function documentEtag(details: { mtimeMs: number; size: number }) {
686
+ return `"${createHash("sha256")
687
+ .update(`${details.mtimeMs}:${details.size}`)
688
+ .digest("hex")}"`;
689
+ }
690
+
691
+ async function projectSearchItem(
692
+ project: LocalProject,
693
+ match?: {
694
+ matchedField: string;
695
+ matchTier: string;
696
+ score: number;
697
+ },
698
+ ) {
699
+ const icon =
700
+ (await searchIconDataUrl(project.icon)) ??
701
+ (await searchIconDataUrl(discoverProjectIcon(project.worktree)));
702
+
703
+ return {
704
+ id: project.id,
705
+ icon,
706
+ latestSessionTitle: project.latestSessionTitle,
707
+ matchedField: match?.matchedField ?? "none",
708
+ matchTier: match?.matchTier ?? "emptyQuery",
709
+ relativePath: project.relativePath,
710
+ repositoryLabel: projectRepositoryAccessory(project),
711
+ score: match?.score ?? 0,
712
+ subtitle: projectSubtitle(project),
713
+ title: projectTitle(project.name, project.worktree),
714
+ worktree: project.worktree,
715
+ };
716
+ }
717
+
718
+ const defaultPluginRegistryConfig: FolderPluginRegistryConfig = {
719
+ plugins: { disable: [], order: [] },
720
+ };
721
+
722
+ type RawPluginRegistryConfig = {
723
+ plugins?: {
724
+ disable?: unknown;
725
+ order?: unknown;
726
+ };
727
+ };
728
+
729
+ function stringArray(value: unknown): string[] {
730
+ return Array.isArray(value)
731
+ ? value.filter((item): item is string => typeof item === "string")
732
+ : [];
733
+ }
734
+
735
+ function parsePluginRegistryConfig(content: string): FolderPluginRegistryConfig {
736
+ const parsed = JSON.parse(content) as unknown;
737
+ const config: RawPluginRegistryConfig =
738
+ parsed && typeof parsed === "object"
739
+ ? (parsed as RawPluginRegistryConfig)
740
+ : {};
741
+ return {
742
+ plugins: {
743
+ disable: stringArray(config.plugins?.disable),
744
+ order: stringArray(config.plugins?.order),
745
+ },
746
+ };
747
+ }
748
+
749
+ async function nearestKennelConfig(folder: string) {
750
+ for (let current = folder; ; current = dirname(current)) {
751
+ const candidate = resolve(current, "kennel.json");
752
+ const details = await stat(candidate).catch(() => undefined);
753
+ if (details?.isFile()) return candidate;
754
+ if (dirname(current) === current) return undefined;
755
+ }
756
+ }
757
+
758
+ async function folderPluginRegistryConfig(
759
+ folder: string,
760
+ ): Promise<FolderPluginRegistryConfig> {
761
+ const path = await nearestKennelConfig(folder);
762
+ if (!path) return defaultPluginRegistryConfig;
763
+ try {
764
+ return parsePluginRegistryConfig(await readFile(path, "utf8"));
765
+ } catch {
766
+ return defaultPluginRegistryConfig;
767
+ }
768
+ }
769
+
770
+ function pluginConfigWarnings(
771
+ registry: Map<string, FolderPluginServer>,
772
+ config: FolderPluginRegistryConfig,
773
+ ) {
774
+ const ids = unique([...config.plugins.disable, ...config.plugins.order]);
775
+ return ids
776
+ .filter((id) => !registry.has(id))
777
+ .map((id) => `unknown plugin id "${id}" — not bundled in this build`);
778
+ }
779
+
780
+ async function pluginAvailability(
781
+ registry: Map<string, FolderPluginServer>,
782
+ folder: string,
783
+ linkedFolders: string[],
784
+ configWarnings: string[] = [],
785
+ ) {
786
+ const items: Array<{
787
+ count?: number;
788
+ error?: string;
789
+ id: string;
790
+ label: string;
791
+ }> = [];
792
+ for (const plugin of registry.values()) {
793
+ try {
794
+ const detection = await plugin.detect({
795
+ folder,
796
+ linkedFolders,
797
+ });
798
+ if (detection.status === "available") {
799
+ items.push({
800
+ count: detection.count,
801
+ id: plugin.id,
802
+ label: plugin.label,
803
+ });
804
+ } else if (detection.status === "error") {
805
+ // A plugin that found its content file but could not validate it is
806
+ // still a hit: keep the item visible and flag the problem so the
807
+ // shell can surface it instead of treating it as absent content.
808
+ items.push({
809
+ error: detection.error,
810
+ id: plugin.id,
811
+ label: plugin.label,
812
+ });
813
+ }
814
+ } catch {
815
+ // Isolate plugin detection: a failure hides the item but never blocks
816
+ // the folder or other plugins.
817
+ }
818
+ }
819
+ return { configWarnings, items };
820
+ }
821
+
822
+ export function createFolderApi({
823
+ viewerName = "kennel-core",
824
+ restrictToRoot = false,
825
+ root,
826
+ sessionFinding,
827
+ mailPreviewer,
828
+ mailDir,
829
+ mailos,
830
+ agentsHomeDir,
831
+ linkedFolderLoader,
832
+ remoteAccess = false,
833
+ lockedFile,
834
+ }: FolderMetadataPluginOptions) {
835
+ let lockedRoot = restrictToRoot ? realpathSync(root) : undefined;
836
+ const lockedTarget = lockedFile ? realpathSync(lockedFile) : undefined;
837
+ const resolveSession = sessionFinding ?? {
838
+ listFolderSessions,
839
+ readSessionTranscript,
840
+ };
841
+ const basePlugins = folderServerRegistryAsync({
842
+ mailDir,
843
+ previewer: mailPreviewer,
844
+ agentsHomeDir,
845
+ });
846
+ const assetFiles = new Map<
847
+ string,
848
+ { mediaType: IconAsset["mediaType"]; path: string }
849
+ >();
850
+ let projectSearchCache: LocalProjectSearchCache | undefined;
851
+ const projectDiscovery = createProjectDiscovery();
852
+ let discoveryChanged = (_folder: string) => {};
853
+ const linkedDiscovery = createLinkedFolderDiscovery(
854
+ linkedFolderLoader,
855
+ (folder) => discoveryChanged(folder),
856
+ );
857
+ const linkedState = (folder: string) =>
858
+ lockedRoot || isMachineRoot(folder)
859
+ ? { folders: [], status: "ready" as const }
860
+ : linkedDiscovery.read(folder);
861
+ const resolveLinkedFolders = (folder: string) =>
862
+ lockedRoot || isMachineRoot(folder)
863
+ ? Promise.resolve([])
864
+ : linkedDiscovery.folders(folder);
865
+ const favicon = faviconService();
866
+
867
+ return {
868
+ name: "kennel-folder-metadata",
869
+ close() {
870
+ linkedDiscovery.close();
871
+ projectDiscovery.close();
872
+ },
873
+ attach(server: ApiHost) {
874
+ discoveryChanged = (path) =>
875
+ server.emit({
876
+ data: { path },
877
+ event: "kennel:linked-folders-changed",
878
+ type: "custom",
879
+ });
880
+ // Effective remote mode comes from the explicit flag OR the resolved
881
+ // Vite host (0.0.0.0/true/non-loopback), so a bare `vite dev
882
+ // --host 0.0.0.0` gets the same restricted surface as the CLI's --lan.
883
+ const serverHost = server.host;
884
+ const remote =
885
+ remoteAccess ||
886
+ serverHost === true ||
887
+ (typeof serverHost === "string" && !isLoopbackServerHost(serverHost));
888
+ if (remote) lockedRoot = lockedRoot ?? realpathSync(root);
889
+
890
+ const collectionServer = createSchemaCollectionServer(server, {
891
+ boundary: lockedRoot,
892
+ lockedTarget,
893
+ });
894
+ const readWorkspace = collectionServer.summaries;
895
+ const watched = new Set<string>();
896
+ const contexts = new Set<string>();
897
+ const watch = (folder: string) => {
898
+ if (watched.has(folder)) return;
899
+ watched.add(folder);
900
+ server.watch(folder);
901
+ };
902
+ const refresh = (path: string) => {
903
+ collectionServer.invalidate(path);
904
+ if (contexts.has(path))
905
+ server.emit({
906
+ data: { path },
907
+ event: "kennel:workspace-changed",
908
+ type: "custom",
909
+ });
910
+ const folder = [...watched].find((candidate) =>
911
+ metadataFile(path, candidate),
912
+ );
913
+ if (!folder) return;
914
+ server.emit({
915
+ data: { path: relative(folder, path) },
916
+ event: "kennel:metadata-changed",
917
+ type: "custom",
918
+ });
919
+ };
920
+
921
+ if (restrictToRoot || remote) watch(root);
922
+ server.onChange(refresh);
923
+
924
+ server.use(async (request, response, next) => {
925
+ const url = new URL(request.url ?? "/", "http://127.0.0.1");
926
+ if (!apiHostAllowed(request.headers.host, remote)) {
927
+ response.writeHead(403, {
928
+ "Content-Type": "text/plain; charset=utf-8",
929
+ });
930
+ response.end("Untrusted Host header");
931
+ return;
932
+ }
933
+ if (!validateQuery(request, response, url)) return;
934
+ if (url.pathname === "/api/capabilities") {
935
+ sendContract(
936
+ response,
937
+ url,
938
+ {
939
+ acp: !remote,
940
+ download: true,
941
+ hostActions: remote ? "none" : "local",
942
+ local: !remote,
943
+ viewer: viewerName,
944
+ },
945
+ { "Cache-Control": "no-store" },
946
+ );
947
+ return;
948
+ }
949
+
950
+ if (url.pathname === "/api/config") {
951
+ const folder = folderPath(
952
+ url.searchParams.get("path"),
953
+ root,
954
+ lockedRoot,
955
+ );
956
+ sendContract(
957
+ response,
958
+ url,
959
+ { aiInputModule: await configuredAiInputModule(folder) },
960
+ { "Cache-Control": "no-store" },
961
+ );
962
+ return;
963
+ }
964
+
965
+ if (url.pathname === "/api/health") {
966
+ sendContract(
967
+ response,
968
+ url,
969
+ { viewer: viewerName, root, kernelKey: process.env.KENNEL_KERNEL_KEY ?? "core" },
970
+ { "Cache-Control": "no-store" },
971
+ );
972
+ return;
973
+ }
974
+ if (url.pathname === "/api/folder-favicon") {
975
+ const data = await (async () => {
976
+ if (process.env.KENNEL_EXTERNAL_ICONS !== "1") return undefined;
977
+ const folder = folderPath(
978
+ url.searchParams.get("path"),
979
+ root,
980
+ lockedRoot,
981
+ );
982
+ const host = await folderWebsite(folder);
983
+ if (!host || host !== url.searchParams.get("host"))
984
+ return undefined;
985
+ return favicon(host);
986
+ })().catch(() => undefined);
987
+ response.writeHead(data ? 200 : 404, {
988
+ "Cache-Control": data
989
+ ? "private, max-age=86400"
990
+ : "private, max-age=300",
991
+ "Content-Type": data ? "image/png" : "text/plain; charset=utf-8",
992
+ "X-Content-Type-Options": "nosniff",
993
+ });
994
+ response.end(data ?? "Icon unavailable");
995
+ return;
996
+ }
997
+ const assetMatch =
998
+ /^\/api\/folders\/([^/]+)\/assets\/([a-f0-9]{64})\.(?:jpg|png)$/.exec(
999
+ url.pathname,
1000
+ );
1001
+ if (assetMatch) {
1002
+ try {
1003
+ const [, folderId, hash] = assetMatch;
1004
+ if (!folderId || !hash) throw new Error("Asset not found");
1005
+ const folder = folderPath(
1006
+ decodeBase64Url(folderId),
1007
+ root,
1008
+ lockedRoot,
1009
+ );
1010
+ watch(folder);
1011
+ let asset = assetFiles.get(hash);
1012
+ if (!asset) {
1013
+ const assets: Record<string, IconAsset> = {};
1014
+ await iconWithFaviconFallback(folder, assets, assetFiles);
1015
+ asset = assetFiles.get(hash);
1016
+ }
1017
+ if (!asset) {
1018
+ response.writeHead(404, {
1019
+ "Content-Type": "text/plain; charset=utf-8",
1020
+ });
1021
+ response.end("Asset not found");
1022
+ return;
1023
+ }
1024
+ response.writeHead(200, {
1025
+ "Cache-Control": "private, max-age=31536000, immutable",
1026
+ "Content-Type": asset.mediaType,
1027
+ ETag: `"${hash}"`,
1028
+ });
1029
+ response.end(await readFile(asset.path));
1030
+ } catch (error) {
1031
+ response.writeHead(404, {
1032
+ "Content-Type": "text/plain; charset=utf-8",
1033
+ });
1034
+ response.end(
1035
+ error instanceof Error ? error.message : "Asset not found",
1036
+ );
1037
+ }
1038
+ return;
1039
+ }
1040
+ if (
1041
+ remote &&
1042
+ ["/api/project-search", "/api/entities"].includes(url.pathname)
1043
+ ) {
1044
+ response.writeHead(403);
1045
+ response.end("Global discovery is disabled for remote access");
1046
+ return;
1047
+ }
1048
+ if (url.pathname === "/api/project-search") {
1049
+ const query = url.searchParams.get("q")?.trim() ?? "";
1050
+ const cache = await localProjectSearchCache(projectSearchCache);
1051
+ projectSearchCache = cache;
1052
+ const result = searchProjectIndex(cache.index, query, {
1053
+ limit: query ? 40 : 20,
1054
+ });
1055
+ sendContract(
1056
+ response,
1057
+ url,
1058
+ {
1059
+ items: await Promise.all(
1060
+ result.hits.map((hit) => projectSearchItem(hit.project, hit)),
1061
+ ),
1062
+ query,
1063
+ scanned: result.scanned,
1064
+ total: result.total,
1065
+ },
1066
+ { "Cache-Control": "no-store" },
1067
+ );
1068
+ return;
1069
+ }
1070
+ if (url.pathname === "/api/entities") {
1071
+ try {
1072
+ const workspace = await loadEntityWorkspace(
1073
+ url.searchParams.get("root") ?? undefined,
1074
+ url.searchParams.get("q") ?? "",
1075
+ url.searchParams.get("entity"),
1076
+ root,
1077
+ );
1078
+ sendContract(response, url, workspace, {
1079
+ "Cache-Control": "no-store",
1080
+ });
1081
+ } catch (error) {
1082
+ response.writeHead(400, {
1083
+ "Content-Type": "text/plain; charset=utf-8",
1084
+ });
1085
+ response.end(
1086
+ error instanceof Error
1087
+ ? error.message
1088
+ : "Could not load entities",
1089
+ );
1090
+ }
1091
+ return;
1092
+ }
1093
+ if (url.pathname === "/api/document/edit") {
1094
+ if (remote) {
1095
+ response.writeHead(403, {
1096
+ "Content-Type": "text/plain; charset=utf-8",
1097
+ });
1098
+ response.end("Host actions are disabled for remote access");
1099
+ return;
1100
+ }
1101
+ await runApiRoute(request, response, url, documentEditRoute, {
1102
+ lockedFile: lockedTarget,
1103
+ authorize: (folder) => folderPath(folder, root, lockedRoot),
1104
+ });
1105
+ return;
1106
+ }
1107
+ if (url.pathname === "/api/open-folder") {
1108
+ if (remote || request.method !== "POST" || !sameOrigin(request)) {
1109
+ response.writeHead(remote || request.method === "POST" ? 403 : 405);
1110
+ response.end("Folder action not allowed");
1111
+ return;
1112
+ }
1113
+ const folder = folderPath(
1114
+ url.searchParams.get("path"),
1115
+ root,
1116
+ lockedRoot,
1117
+ );
1118
+ const command =
1119
+ process.platform === "darwin"
1120
+ ? "open"
1121
+ : process.platform === "win32"
1122
+ ? "explorer.exe"
1123
+ : "xdg-open";
1124
+ await new Promise<void>((done, fail) =>
1125
+ execFile(command, [folder], (error) =>
1126
+ error ? fail(error) : done(),
1127
+ ),
1128
+ );
1129
+ response.writeHead(204);
1130
+ response.end();
1131
+ return;
1132
+ }
1133
+ if (url.pathname === "/api/documents") {
1134
+ const folder = folderPath(
1135
+ url.searchParams.get("path"),
1136
+ root,
1137
+ lockedRoot,
1138
+ );
1139
+ const target = lockedTarget
1140
+ ? documentTarget(
1141
+ folder,
1142
+ relative(folder, lockedTarget),
1143
+ lockedTarget,
1144
+ )
1145
+ : undefined;
1146
+ const dataset = lockedTarget
1147
+ ? {
1148
+ root: folder,
1149
+ documents: target
1150
+ ? [
1151
+ {
1152
+ path: relative(folder, target.path),
1153
+ kind: target.kind,
1154
+ content: previewTextKind(target.kind)
1155
+ ? await readFile(target.path, "utf8")
1156
+ : "",
1157
+ },
1158
+ ]
1159
+ : [],
1160
+ }
1161
+ : await readFolder(folder);
1162
+ watch(folder);
1163
+ sendContract(response, url, dataset, { "Cache-Control": "no-store" });
1164
+ return;
1165
+ }
1166
+ if (url.pathname === "/api/document") {
1167
+ try {
1168
+ const folder = folderPath(
1169
+ url.searchParams.get("path"),
1170
+ root,
1171
+ lockedRoot,
1172
+ );
1173
+ const target = documentTarget(
1174
+ folder,
1175
+ url.searchParams.get("file"),
1176
+ lockedTarget,
1177
+ );
1178
+ if (!target) {
1179
+ documentError(response);
1180
+ return;
1181
+ }
1182
+ const details = await stat(target.path);
1183
+ if (!details.isFile()) {
1184
+ documentError(response);
1185
+ return;
1186
+ }
1187
+ const etag = documentEtag(details);
1188
+ if (request.headers["if-none-match"] === etag) {
1189
+ response.writeHead(304, { ETag: etag });
1190
+ response.end();
1191
+ return;
1192
+ }
1193
+ watch(folder);
1194
+ const content = previewTextKind(target.kind)
1195
+ ? await readFile(target.path, "utf8")
1196
+ : "";
1197
+ sendContract(
1198
+ response,
1199
+ url,
1200
+ {
1201
+ content,
1202
+ kind: target.kind,
1203
+ path: relative(target.root, target.path),
1204
+ root: folder,
1205
+ ...(previewTextKind(target.kind)
1206
+ ? {}
1207
+ : {
1208
+ rawUrl: `/api/raw?${new URLSearchParams({
1209
+ path: folder,
1210
+ file: relative(target.root, target.path),
1211
+ })}`,
1212
+ }),
1213
+ },
1214
+ {
1215
+ "Cache-Control": "private, max-age=0, must-revalidate",
1216
+ ETag: etag,
1217
+ },
1218
+ );
1219
+ } catch (error) {
1220
+ documentError(response);
1221
+ }
1222
+ return;
1223
+ }
1224
+ if (url.pathname === "/api/raw") {
1225
+ try {
1226
+ const folder = folderPath(
1227
+ url.searchParams.get("path"),
1228
+ root,
1229
+ lockedRoot,
1230
+ );
1231
+ const target = documentTarget(
1232
+ folder,
1233
+ url.searchParams.get("file"),
1234
+ lockedTarget,
1235
+ );
1236
+ if (!target) {
1237
+ documentError(response);
1238
+ return;
1239
+ }
1240
+ const details = await stat(target.path);
1241
+ if (!details.isFile()) {
1242
+ documentError(response);
1243
+ return;
1244
+ }
1245
+ const etag = documentEtag(details);
1246
+ if (request.headers["if-none-match"] === etag) {
1247
+ response.writeHead(304, { ETag: etag });
1248
+ response.end();
1249
+ return;
1250
+ }
1251
+ watch(folder);
1252
+ // Active content stays inert from the raw endpoint: HTML download
1253
+ // only as opaque bytes; SVG (scripts allowed in top-level/iframe
1254
+ // navigation) always downloads as an attachment with nosniff.
1255
+ const active =
1256
+ target.kind === "html" ||
1257
+ target.path.toLowerCase().endsWith(".svg");
1258
+ const download = url.searchParams.get("download") === "1" || active;
1259
+ const mime =
1260
+ target.kind === "html"
1261
+ ? "application/octet-stream"
1262
+ : (previewRawMime(target.path) ?? "application/octet-stream");
1263
+ response.writeHead(200, {
1264
+ "Cache-Control": "private, max-age=0, must-revalidate",
1265
+ "Content-Type": mime,
1266
+ ETag: etag,
1267
+ "X-Content-Type-Options": "nosniff",
1268
+ ...(download
1269
+ ? {
1270
+ "Content-Disposition": `attachment; filename="${basename(target.path)}"`,
1271
+ }
1272
+ : {}),
1273
+ });
1274
+ response.end(await readFile(target.path));
1275
+ } catch (error) {
1276
+ documentError(response);
1277
+ }
1278
+ return;
1279
+ }
1280
+ if (url.pathname === "/api/media") {
1281
+ try {
1282
+ const folder = folderPath(
1283
+ url.searchParams.get("path"),
1284
+ root,
1285
+ lockedRoot,
1286
+ );
1287
+ const target = mediaTarget(
1288
+ folder,
1289
+ url.searchParams.get("file"),
1290
+ lockedTarget,
1291
+ );
1292
+ if (!target) {
1293
+ documentError(response);
1294
+ return;
1295
+ }
1296
+ const details = await stat(target.path);
1297
+ if (!details.isFile()) {
1298
+ documentError(response);
1299
+ return;
1300
+ }
1301
+ const etag = documentEtag(details);
1302
+ if (request.headers["if-none-match"] === etag) {
1303
+ response.writeHead(304, { ETag: etag });
1304
+ response.end();
1305
+ return;
1306
+ }
1307
+ watch(folder);
1308
+ // Media is read whole and sliced for ranges: clips are small
1309
+ // (single-digit MB) and browsers need 206 support to seek.
1310
+ const data = await readFile(target.path);
1311
+ const baseHeaders = {
1312
+ "Accept-Ranges": "bytes",
1313
+ "Cache-Control": "private, max-age=0, must-revalidate",
1314
+ "Content-Type": target.mime,
1315
+ ETag: etag,
1316
+ "X-Content-Type-Options": "nosniff",
1317
+ };
1318
+ const range = parseByteRange(request.headers.range, data.length);
1319
+ if (range === "invalid") {
1320
+ response.writeHead(416, {
1321
+ ...baseHeaders,
1322
+ "Content-Range": `bytes */${data.length}`,
1323
+ });
1324
+ response.end();
1325
+ return;
1326
+ }
1327
+ if (range) {
1328
+ const slice = data.subarray(range.start, range.end + 1);
1329
+ response.writeHead(206, {
1330
+ ...baseHeaders,
1331
+ "Content-Length": String(slice.length),
1332
+ "Content-Range": `bytes ${range.start}-${range.end}/${data.length}`,
1333
+ });
1334
+ response.end(slice);
1335
+ return;
1336
+ }
1337
+ response.writeHead(200, {
1338
+ ...baseHeaders,
1339
+ "Content-Length": String(data.length),
1340
+ });
1341
+ response.end(data);
1342
+ } catch (error) {
1343
+ documentError(response);
1344
+ }
1345
+ return;
1346
+ }
1347
+ if (url.pathname === "/api/session") {
1348
+ try {
1349
+ const sessionId = url.searchParams.get("sessionId");
1350
+ if (!sessionId) throw new Error("No sessionId parameter");
1351
+ const session =
1352
+ await resolveSession.readSessionTranscript(sessionId);
1353
+ response.writeHead(200, {
1354
+ "Cache-Control": "private, max-age=10, must-revalidate",
1355
+ "Content-Type": "application/json; charset=utf-8",
1356
+ });
1357
+ response.end(JSON.stringify(session));
1358
+ } catch (error) {
1359
+ response.writeHead(404, {
1360
+ "Content-Type": "text/plain; charset=utf-8",
1361
+ });
1362
+ response.end(
1363
+ error instanceof Error ? error.message : "Session not found",
1364
+ );
1365
+ }
1366
+ return;
1367
+ }
1368
+ const pluginMatch = /^\/api\/plugins\/([^/]+)$/.exec(url.pathname);
1369
+ if (pluginMatch) {
1370
+ try {
1371
+ const pluginId = pluginMatch[1];
1372
+ if (!pluginId) throw new Error("Unknown plugin");
1373
+ const folder = folderPath(
1374
+ url.searchParams.get("path"),
1375
+ root,
1376
+ lockedRoot,
1377
+ );
1378
+ const pluginConfig = await folderPluginRegistryConfig(folder);
1379
+ const plugins = configuredFolderServerRegistry(
1380
+ await basePlugins,
1381
+ pluginConfig,
1382
+ );
1383
+ const plugin = plugins.get(pluginId);
1384
+ if (!plugin) throw new Error("Unknown plugin");
1385
+ watch(folder);
1386
+ const subpath = url.searchParams.get("subpath") ?? "";
1387
+ // Local content never waits for optional linked-project discovery.
1388
+ if (
1389
+ !lockedRoot &&
1390
+ !isMachineRoot(folder) &&
1391
+ request.headers["x-kennel-retry-linked"] === "1"
1392
+ )
1393
+ linkedDiscovery.retry(folder);
1394
+ const discovery =
1395
+ pluginId === "agents" || pluginId === "tasks"
1396
+ ? linkedState(folder)
1397
+ : undefined;
1398
+ const result = await plugin.handle(
1399
+ {
1400
+ folder,
1401
+ linkedFolders: discovery?.folders ?? [],
1402
+ },
1403
+ subpath,
1404
+ );
1405
+ if (result.kind === "unavailable") {
1406
+ response.writeHead(200, {
1407
+ "Cache-Control": "private, max-age=0, must-revalidate",
1408
+ "Content-Type": "application/json; charset=utf-8",
1409
+ });
1410
+ response.end(
1411
+ JSON.stringify({
1412
+ folder,
1413
+ plugin: pluginId,
1414
+ status: "unavailable",
1415
+ ...(discovery ? { linkedStatus: discovery.status } : {}),
1416
+ }),
1417
+ );
1418
+ return;
1419
+ }
1420
+ response.writeHead(200, {
1421
+ "Cache-Control": "private, max-age=0, must-revalidate",
1422
+ "Content-Type": "application/json; charset=utf-8",
1423
+ });
1424
+ const data = result.data as Record<string, unknown>;
1425
+ response.end(
1426
+ JSON.stringify({
1427
+ folder,
1428
+ plugin: pluginId,
1429
+ ...data,
1430
+ ...(discovery ? { linkedStatus: discovery.status } : {}),
1431
+ }),
1432
+ );
1433
+ } catch (error) {
1434
+ if (error instanceof FolderPluginFailure) {
1435
+ response.writeHead(500, {
1436
+ "Content-Type": "text/plain; charset=utf-8",
1437
+ });
1438
+ response.end(error.message);
1439
+ return;
1440
+ }
1441
+ response.writeHead(404, {
1442
+ "Content-Type": "text/plain; charset=utf-8",
1443
+ });
1444
+ response.end(
1445
+ error instanceof Error ? error.message : "Plugin not found",
1446
+ );
1447
+ }
1448
+ return;
1449
+ }
1450
+ if (
1451
+ await handleMailApiRoutes(request, response, {
1452
+ authorizeFolder: (value) => folderPath(value, root, lockedRoot),
1453
+ mailDir,
1454
+ mailos,
1455
+ mailPreviewer,
1456
+ watch,
1457
+ })
1458
+ )
1459
+ return;
1460
+ if (
1461
+ await handleTasksApiRoutes(request, response, {
1462
+ authorizeFolder: (value) => folderPath(value, root, lockedRoot),
1463
+ readOnly: remote,
1464
+ watch,
1465
+ })
1466
+ )
1467
+ return;
1468
+ if (url.pathname === "/api/create-agent-file") {
1469
+ if (remote) {
1470
+ response.writeHead(403, {
1471
+ "Content-Type": "text/plain; charset=utf-8",
1472
+ });
1473
+ response.end("Host actions are disabled for remote access");
1474
+ return;
1475
+ }
1476
+ await runApiRoute(request, response, url, createAgentFileRoute, {
1477
+ resolve: (value: string | null) =>
1478
+ folderPath(value, root, lockedRoot),
1479
+ });
1480
+ return;
1481
+ }
1482
+ if (url.pathname === "/api/open-path") {
1483
+ if (remote) {
1484
+ response.writeHead(403, {
1485
+ "Content-Type": "text/plain; charset=utf-8",
1486
+ });
1487
+ response.end("Host actions are disabled for remote access");
1488
+ return;
1489
+ }
1490
+ await runApiRoute(request, response, url, openPathRoute, {
1491
+ resolve: (value: string | null) =>
1492
+ folderPath(value, root, lockedRoot),
1493
+ });
1494
+ return;
1495
+ }
1496
+ if (url.pathname !== "/api/metadata") {
1497
+ next();
1498
+ return;
1499
+ }
1500
+
1501
+ try {
1502
+ const part = url.searchParams.get("part");
1503
+ const folder = folderPath(
1504
+ url.searchParams.get("path"),
1505
+ root,
1506
+ lockedRoot,
1507
+ );
1508
+ if (lockedTarget && part !== "workspace") {
1509
+ // Single-file pop lock: workspace stays (config resolution, no
1510
+ // file contents); every other metadata part returns a folder-only
1511
+ // body so siblings are never listed or summarized.
1512
+ if (part === "collections") {
1513
+ response.writeHead(404, { "Content-Type": "application/json" });
1514
+ response.end(JSON.stringify({ error: "Collection not found" }));
1515
+ return;
1516
+ }
1517
+ sendContract(
1518
+ response,
1519
+ url,
1520
+ {
1521
+ assets: {},
1522
+ files: { items: [] },
1523
+ folder,
1524
+ projects: { items: [] },
1525
+ sessions: { items: [] },
1526
+ skills: { items: [] },
1527
+ workspace: await readWorkspace(folder),
1528
+ },
1529
+ { "Cache-Control": "private, max-age=0, must-revalidate" },
1530
+ );
1531
+ return;
1532
+ }
1533
+ if (part === "workspace") {
1534
+ const workspace = await readWorkspace(folder);
1535
+ workspace.breadcrumbs = await workspaceBreadcrumbIcons(
1536
+ workspace.breadcrumbs ?? [],
1537
+ assetFiles,
1538
+ );
1539
+ const files = [
1540
+ workspace.configPath,
1541
+ resolve(workspace.root, "raggle.json"),
1542
+ ].filter((path): path is string => Boolean(path));
1543
+ for (const path of files) contexts.add(path);
1544
+ server.watch(files);
1545
+ sendContract(
1546
+ response,
1547
+ url,
1548
+ { folder, workspace },
1549
+ { "Cache-Control": "private, max-age=0, must-revalidate" },
1550
+ );
1551
+ return;
1552
+ }
1553
+ watch(folder);
1554
+ if (part === "collections") {
1555
+ const sort = url.searchParams.get("sort");
1556
+ await collectionServer.handleMetadataPart({
1557
+ folder,
1558
+ id: url.searchParams.get("id"),
1559
+ response,
1560
+ send: (body, headers) =>
1561
+ sendContract(response, url, body, headers),
1562
+ sort: sort === "alpha" || sort === "recent" ? sort : undefined,
1563
+ url,
1564
+ });
1565
+ return;
1566
+ }
1567
+
1568
+ // Sessions functionality is commented out (can be re-enabled).
1569
+ // if (part === "sessions") {
1570
+ // const sessions = isMachineRoot(folder)
1571
+ // ? { items: [] }
1572
+ // : await resolveSession.listFolderSessions({ folder, limit: 100 })
1573
+ // response.writeHead(200, {
1574
+ // "Cache-Control": "private, max-age=0, must-revalidate",
1575
+ // "Content-Type": "application/json; charset=utf-8",
1576
+ // })
1577
+ // response.end(JSON.stringify({ folder, sessions }))
1578
+ // return
1579
+ // }
1580
+
1581
+ const machine = isMachineRoot(folder);
1582
+
1583
+ if (part === "notes") {
1584
+ // The viewer requests local and linked notes independently so slow
1585
+ // repository discovery cannot hold the local list hostage. Omitted
1586
+ // scope preserves the combined API response for existing clients.
1587
+ const scope = url.searchParams.get("notesScope");
1588
+ const localFiles =
1589
+ scope === "linked"
1590
+ ? Promise.resolve({ items: [] })
1591
+ : (machine
1592
+ ? shallowMetadata(folder).then((data) => data.files.items)
1593
+ : listLocalMarkdownFiles({ folder, limit: 200 })
1594
+ ).then((files) => notesFiles(folder, files));
1595
+ const linkedFiles = async () => {
1596
+ if (scope === "local" || machine || lockedRoot) return [];
1597
+ if (request.headers["x-kennel-retry-linked"] === "1")
1598
+ linkedDiscovery.retry(folder);
1599
+ const linkedFolders = await resolveLinkedFolders(folder);
1600
+ if (linkedState(folder).status === "error")
1601
+ throw new Error(
1602
+ "Linked notes discovery failed. Retry to try again.",
1603
+ );
1604
+ return Promise.all(
1605
+ linkedFolders.map(async (linkedFolder) => {
1606
+ watch(linkedFolder);
1607
+ return notesFiles(
1608
+ linkedFolder,
1609
+ await listLocalMarkdownFiles({
1610
+ folder: linkedFolder,
1611
+ limit: 200,
1612
+ }),
1613
+ linkedFolder,
1614
+ );
1615
+ }),
1616
+ );
1617
+ };
1618
+ const [files, linked] = await Promise.all([
1619
+ localFiles,
1620
+ linkedFiles(),
1621
+ ]);
1622
+ sendContract(
1623
+ response,
1624
+ url,
1625
+ {
1626
+ files: {
1627
+ items: [
1628
+ ...files.items,
1629
+ ...linked.flatMap((group) => group.items),
1630
+ ],
1631
+ },
1632
+ folder,
1633
+ },
1634
+ { "Cache-Control": "private, max-age=0, must-revalidate" },
1635
+ );
1636
+ return;
1637
+ }
1638
+
1639
+ // Plugin availability is isolated from core discovery: a plugin
1640
+ // failure hides its own item but never blocks Notes or other plugins.
1641
+ if (part === "plugins") {
1642
+ const pluginConfig = await folderPluginRegistryConfig(folder);
1643
+ const registry = await basePlugins;
1644
+ const plugins = configuredFolderServerRegistry(
1645
+ registry,
1646
+ pluginConfig,
1647
+ );
1648
+ const configWarnings = pluginConfigWarnings(
1649
+ registry,
1650
+ pluginConfig,
1651
+ );
1652
+ sendContract(
1653
+ response,
1654
+ url,
1655
+ {
1656
+ configWarnings,
1657
+ folder,
1658
+ plugins: await pluginAvailability(
1659
+ plugins,
1660
+ folder,
1661
+ lockedRoot || isMachineRoot(folder)
1662
+ ? []
1663
+ : linkedDiscovery.peek(folder).folders,
1664
+ configWarnings,
1665
+ ),
1666
+ },
1667
+ { "Cache-Control": "private, max-age=0, must-revalidate" },
1668
+ );
1669
+ return;
1670
+ }
1671
+
1672
+ const respond = (body: unknown) =>
1673
+ sendContract(response, url, body, {
1674
+ "Cache-Control": "private, max-age=0, must-revalidate",
1675
+ });
1676
+
1677
+ if (part === "identity") {
1678
+ const hydrated = await hydrateAssets({ folder }, assetFiles);
1679
+ respond({
1680
+ assets: hydrated.assets,
1681
+ folder,
1682
+ iconAssetKey: hydrated.iconAssetKey,
1683
+ });
1684
+ return;
1685
+ }
1686
+
1687
+ if (part === "discovery") {
1688
+ // Navigation needs projects, not a recursive Markdown inventory.
1689
+ const controller = new AbortController();
1690
+ const cancel = () => controller.abort();
1691
+ response.once("close", cancel);
1692
+ let projects;
1693
+ try {
1694
+ projects = machine
1695
+ ? (await shallowMetadata(folder)).projects.items
1696
+ : await projectDiscovery.read(folder, controller.signal);
1697
+ } finally {
1698
+ response.off("close", cancel);
1699
+ }
1700
+ const [hydrated, workspace] = await Promise.all([
1701
+ hydrateAssets(
1702
+ { folder, projects: { items: projects } },
1703
+ assetFiles,
1704
+ ),
1705
+ readWorkspace(folder),
1706
+ ]);
1707
+ workspace.breadcrumbs = await workspaceBreadcrumbIcons(
1708
+ workspace.breadcrumbs ?? [],
1709
+ assetFiles,
1710
+ hydrated.assets,
1711
+ );
1712
+ respond({
1713
+ assets: hydrated.assets,
1714
+ folder,
1715
+ projects: hydrated.projects,
1716
+ workspace,
1717
+ });
1718
+ return;
1719
+ }
1720
+
1721
+ const metadata = machine
1722
+ ? await shallowMetadata(folder)
1723
+ : await loadFolderData(folder);
1724
+
1725
+ if (part === "skills") {
1726
+ respond({
1727
+ folder: metadata.folder,
1728
+ skills: {
1729
+ items: await listFolderSkills(metadata.folder, metadata),
1730
+ },
1731
+ });
1732
+ return;
1733
+ }
1734
+
1735
+ const [assets, skills] = await Promise.all([
1736
+ hydrateAssets(metadata, assetFiles),
1737
+ listFolderSkills(metadata.folder, metadata),
1738
+ ]);
1739
+ const body = {
1740
+ ...metadata,
1741
+ ...assets,
1742
+ sessions: { items: [] },
1743
+ files: await notesFiles(metadata.folder, metadata.files.items),
1744
+ skills: { items: skills },
1745
+ };
1746
+ respond(body);
1747
+ } catch (error) {
1748
+ response.writeHead(404, {
1749
+ "Content-Type": "text/plain; charset=utf-8",
1750
+ });
1751
+ response.end(
1752
+ error instanceof Error ? error.message : "Folder activity failed",
1753
+ );
1754
+ }
1755
+ });
1756
+ },
1757
+ };
1758
+ }