dsh-desktop-kit 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -2
  3. package/client.js +4101 -0
  4. package/cordis.patch.yml +5 -0
  5. package/docs/ARCHITECTURE.md +114 -0
  6. package/docs/COMPATIBILITY.md +61 -0
  7. package/docs/INSTALL.md +84 -0
  8. package/docs/PRIVACY.md +50 -0
  9. package/docs/ROADMAP.md +57 -0
  10. package/docs/TOKENS.md +118 -0
  11. package/docs/TROUBLESHOOTING.md +68 -0
  12. package/docs/images/hero.png +0 -0
  13. package/index.js +84 -0
  14. package/package.json +70 -4
  15. package/plugins/dsh-gkb-ru-ui/cordis.patch.yml +6 -0
  16. package/plugins/dsh-gkb-ru-ui/lib/client.js +1331 -0
  17. package/plugins/dsh-gkb-ru-ui/lib/index.js +186 -0
  18. package/plugins/dsh-gkb-ru-ui/lib/skills.js +570 -0
  19. package/plugins/dsh-gkb-ru-ui/package.json +28 -0
  20. package/plugins/dsh-gkb-ru-ui/screenshots/01-home-tiles-1440-light.png +0 -0
  21. package/plugins/dsh-gkb-ru-ui/screenshots/02-skills-1440-light.png +0 -0
  22. package/plugins/dsh-gkb-ru-ui/screenshots/03-skills-editor-1440-light.png +0 -0
  23. package/plugins/dsh-gkb-ru-ui/screenshots/04-skills-1440-dark.png +0 -0
  24. package/plugins/dsh-gkb-ru-ui/screenshots/05-skills-390-light.png +0 -0
  25. package/plugins/dsh-gkb-ru-ui/screenshots/06-home-tiles-1440-dark.png +0 -0
  26. package/plugins/dsh-peak-guard/README.md +70 -0
  27. package/plugins/dsh-peak-guard/client.js +741 -0
  28. package/plugins/dsh-peak-guard/cordis.patch.yml +5 -0
  29. package/plugins/dsh-peak-guard/icon.svg +12 -0
  30. package/plugins/dsh-peak-guard/index.js +2 -0
  31. package/plugins/dsh-peak-guard/locale/en.json +6 -0
  32. package/plugins/dsh-peak-guard/locale/ru.json +6 -0
  33. package/plugins/dsh-peak-guard/locale/zh.json +6 -0
  34. package/plugins/dsh-peak-guard/package.json +33 -0
  35. package/plugins/dsh-peak-guard/screenshots/01-real-time-peak-start.png +0 -0
  36. package/plugins/dsh-peak-guard/screenshots/02-real-time-peak-end.png +0 -0
  37. package/plugins/dsh-peak-guard/screenshots/03-peak-card-1440-light.png +0 -0
  38. package/plugins/dsh-peak-guard/screenshots/04-peak-card-1440-dark.png +0 -0
  39. package/plugins/dsh-peak-guard/screenshots/05-peak-card-390-light.png +0 -0
  40. package/plugins/dsh-peak-guard/screenshots/05-peak-card-768-light.png +0 -0
  41. package/plugins/dsh-peak-guard/screenshots/06-peak-card-390-light.png +0 -0
  42. package/plugins/dsh-peak-guard/screenshots/07-live-preview-check.png +0 -0
  43. package/plugins/dsh-peak-guard/screenshots/07-pre-peak-1440-light.png +0 -0
  44. package/plugins/dsh-peak-guard/screenshots/08-peak-start-1440-light.png +0 -0
  45. package/plugins/dsh-peak-guard/screenshots/09-peak-end-1440-light.png +0 -0
  46. package/plugins/dsh-peak-guard/screenshots/10-pre-peak-card-1440-light.png +0 -0
  47. package/plugins/dsh-peak-guard/screenshots/11-pre-peak-card-1440-dark.png +0 -0
  48. package/plugins/dsh-peak-guard/screenshots/12-pre-peak-card-390-light.png +0 -0
  49. package/plugins/dsh-peak-guard/screenshots/qa-report.json +74 -0
  50. package/plugins/dsh-profile-transfer/README.md +62 -0
  51. package/plugins/dsh-profile-transfer/bootstrap/01-INSTALL.cmd +7 -0
  52. package/plugins/dsh-profile-transfer/bootstrap/README-ARCHIVE.md +54 -0
  53. package/plugins/dsh-profile-transfer/bootstrap/README-/320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/232/320/220.md +42 -0
  54. package/plugins/dsh-profile-transfer/bootstrap/install-on-clean-machine.ps1 +119 -0
  55. package/plugins/dsh-profile-transfer/bootstrap//320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/230/320/242/320/254.cmd +5 -0
  56. package/plugins/dsh-profile-transfer/client.js +806 -0
  57. package/plugins/dsh-profile-transfer/cordis.patch.yml +3 -0
  58. package/plugins/dsh-profile-transfer/icon.svg +13 -0
  59. package/plugins/dsh-profile-transfer/index.js +375 -0
  60. package/plugins/dsh-profile-transfer/locale/en.json +6 -0
  61. package/plugins/dsh-profile-transfer/locale/ru.json +6 -0
  62. package/plugins/dsh-profile-transfer/locale/zh.json +6 -0
  63. package/plugins/dsh-profile-transfer/package.json +36 -0
  64. package/plugins/dsh-profile-transfer/screenshots/01-settings-transfer-1440-light.png +0 -0
  65. package/plugins/dsh-profile-transfer/screenshots/02-settings-transfer-preview-1440-light.png +0 -0
  66. package/plugins/dsh-profile-transfer/screenshots/03-settings-transfer-1440-dark.png +0 -0
  67. package/plugins/dsh-profile-transfer/screenshots/04-settings-transfer-390-light.png +0 -0
  68. package/plugins/dsh-profile-transfer/screenshots/05-settings-transfer-768-light.png +0 -0
  69. package/plugins/dsh-profile-transfer/screenshots/06-settings-transfer-390-dark.png +0 -0
  70. package/plugins/dsh-profile-transfer/screenshots/07-settings-transfer-export-result.png +0 -0
  71. package/plugins/dsh-profile-transfer/screenshots/08-settings-transfer-workspaces-1440-light.png +0 -0
  72. package/plugins/dsh-profile-transfer/screenshots/09-settings-transfer-workspaces-1440-dark.png +0 -0
  73. package/plugins/dsh-profile-transfer/screenshots/10-settings-transfer-workspaces-390-light.png +0 -0
  74. package/plugins/dsh-profile-transfer/transfer.js +1004 -0
  75. package/plugins/dsh-project-memory/README.md +71 -0
  76. package/plugins/dsh-project-memory/cordis.patch.yml +4 -0
  77. package/plugins/dsh-project-memory/icon.svg +12 -0
  78. package/plugins/dsh-project-memory/index.js +1240 -0
  79. package/plugins/dsh-project-memory/locale/en.json +6 -0
  80. package/plugins/dsh-project-memory/locale/ru.json +6 -0
  81. package/plugins/dsh-project-memory/locale/zh.json +6 -0
  82. package/plugins/dsh-project-memory/package.json +26 -0
  83. package/plugins/dsh-session-move/README.md +67 -0
  84. package/plugins/dsh-session-move/client.js +719 -0
  85. package/plugins/dsh-session-move/cordis.patch.yml +5 -0
  86. package/plugins/dsh-session-move/icon.svg +14 -0
  87. package/plugins/dsh-session-move/index.js +497 -0
  88. package/plugins/dsh-session-move/locale/en.json +6 -0
  89. package/plugins/dsh-session-move/locale/ru.json +6 -0
  90. package/plugins/dsh-session-move/locale/zh.json +6 -0
  91. package/plugins/dsh-session-move/package.json +31 -0
  92. package/plugins/dsh-session-move/screenshots/01-row-menu-1440-light.png +0 -0
  93. package/plugins/dsh-session-move/screenshots/02-dialog-1440-light.png +0 -0
  94. package/plugins/dsh-session-move/screenshots/03-dialog-390-light.png +0 -0
  95. package/plugins/dsh-session-move/screenshots/04-dialog-1440-dark.png +0 -0
  96. package/plugins/dsh-session-move/screenshots/05-drag-highlight-1440-light.png +0 -0
  97. package/plugins/dsh-session-move/screenshots/06-drop-ungrouped-highlight.png +0 -0
  98. package/plugins/dsh-session-move/screenshots/07-after-drop-ungrouped.png +0 -0
  99. package/plugins/dsh-session-move/screenshots/08-move-dialog.png +0 -0
  100. package/plugins/dsh-session-move/screenshots/09-moved-to-second-workspace.png +0 -0
  101. package/plugins/dsh-session-move/screenshots/qa-report.json +62 -0
  102. package/plugins/dsh-skill-autopilot/cordis.patch.yml +3 -0
  103. package/plugins/dsh-skill-autopilot/index.js +444 -0
  104. package/plugins/dsh-skill-autopilot/package.json +19 -0
  105. package/plugins/dsh-token-lab/cordis.patch.yml +3 -0
  106. package/plugins/dsh-token-lab/lib/client.js +504 -0
  107. package/plugins/dsh-token-lab/lib/index.js +275 -0
  108. package/plugins/dsh-token-lab/package.json +26 -0
  109. package/plugins/dsh-token-lab/screenshots/01-token-lab-1440-light.png +0 -0
  110. package/plugins/dsh-token-lab/screenshots/02-token-lab-1440-dark.png +0 -0
  111. package/skills/code-review-checklist/SKILL.md +55 -0
  112. package/skills/content-writing/README.md +87 -0
  113. package/skills/content-writing/SKILL.md +85 -0
  114. package/skills/content-writing/assets/checklists/fact-check.md +53 -0
  115. package/skills/content-writing/assets/checklists/publish.md +65 -0
  116. package/skills/content-writing/assets/checklists/social.md +40 -0
  117. package/skills/content-writing/assets/templates/article.md +47 -0
  118. package/skills/content-writing/assets/templates/brief.md +44 -0
  119. package/skills/content-writing/assets/templates/doctor-card.md +41 -0
  120. package/skills/content-writing/assets/templates/image-prompts.md +128 -0
  121. package/skills/content-writing/assets/templates/post-telegram.md +36 -0
  122. package/skills/content-writing/assets/templates/post-vk.md +42 -0
  123. package/skills/content-writing/assets/templates/press-release.md +56 -0
  124. package/skills/content-writing/assets/templates/science-pop-note.md +55 -0
  125. package/skills/content-writing/assets/templates/service-card.md +46 -0
  126. package/skills/content-writing/references/anti-ai-style.md +248 -0
  127. package/skills/content-writing/references/editing.md +185 -0
  128. package/skills/content-writing/references/fact-checking.md +205 -0
  129. package/skills/content-writing/references/government-medical.md +246 -0
  130. package/skills/content-writing/references/images.md +212 -0
  131. package/skills/content-writing/references/journalism.md +252 -0
  132. package/skills/content-writing/references/process.md +123 -0
  133. package/skills/content-writing/references/quick-recipes.md +99 -0
  134. package/skills/content-writing/references/rules.md +85 -0
  135. package/skills/content-writing/references/science-popular.md +218 -0
  136. package/skills/content-writing/references/seo-content.md +172 -0
  137. package/skills/content-writing/references/social-media.md +253 -0
  138. package/skills/content-writing/references/sources.md +49 -0
  139. package/skills/content-writing/references/templates.md +291 -0
  140. package/skills/context-economy/SKILL.md +60 -0
  141. package/skills/context-economy/references/long-session.md +35 -0
  142. package/skills/context-economy/references/token-math.md +42 -0
  143. package/skills/context-economy/references/tool-recipes.md +34 -0
  144. package/skills/database-safety/SKILL.md +47 -0
  145. package/skills/debugging-playbook/SKILL.md +42 -0
  146. package/skills/design-critique/SKILL.md +65 -0
  147. package/skills/design-critique/references/a11y.md +25 -0
  148. package/skills/design-critique/references/diagnoses.md +33 -0
  149. package/skills/design-critique/references/quick-tests.md +30 -0
  150. package/skills/design-critique/references/report-template.md +63 -0
  151. package/skills/design-critique/references/rubric-fast.md +73 -0
  152. package/skills/dev-workflow/SKILL.md +48 -0
  153. package/skills/dev-workflow/references/definition-of-done.md +44 -0
  154. package/skills/dev-workflow/references/memory-and-boundaries.md +31 -0
  155. package/skills/dev-workflow/references/stages.md +67 -0
  156. package/skills/git-and-ci/SKILL.md +60 -0
  157. package/skills/pg-upgrade-verify/SKILL.md +67 -0
  158. package/skills/project-deep-dive/SKILL.md +45 -0
  159. package/skills/project-deep-dive/references/recon-commands.md +90 -0
  160. package/skills/project-deep-dive/references/section-templates.md +104 -0
  161. package/skills/project-deep-dive/references/tool-and-process.md +86 -0
  162. package/skills/project-memory-maintenance/SKILL.md +55 -0
  163. package/skills/project-memory-maintenance/references/api-and-checklist.md +99 -0
  164. package/skills/project-memory-maintenance/references/journal-template.md +50 -0
  165. package/skills/project-memory-maintenance/references/section-rules.md +59 -0
  166. package/skills/release-and-deploy/SKILL.md +53 -0
  167. package/skills/requirements-and-planning/SKILL.md +49 -0
  168. package/skills/requirements-and-planning/references/antipatterns-and-checklist.md +20 -0
  169. package/skills/requirements-and-planning/references/process-steps.md +52 -0
  170. package/skills/requirements-and-planning/references/templates-and-estimates.md +29 -0
  171. package/skills/security-review/SKILL.md +40 -0
  172. package/skills/seo/README.md +60 -0
  173. package/skills/seo/SKILL.md +77 -0
  174. package/skills/seo/assets/README.md +66 -0
  175. package/skills/seo/assets/scripts/LICENSE-playwright-core.txt +202 -0
  176. package/skills/seo/assets/scripts/README.md +179 -0
  177. package/skills/seo/assets/scripts/lib/args.mjs +105 -0
  178. package/skills/seo/assets/scripts/lib/browser.mjs +620 -0
  179. package/skills/seo/assets/scripts/lib/zip.mjs +191 -0
  180. package/skills/seo/assets/scripts/seo-audit.mjs +1675 -0
  181. package/skills/seo/assets/snippets/jsonld-breadcrumb.json +30 -0
  182. package/skills/seo/assets/snippets/jsonld-faq.json +39 -0
  183. package/skills/seo/assets/snippets/jsonld-medical-clinic.json +49 -0
  184. package/skills/seo/assets/snippets/jsonld-medical-webpage.json +33 -0
  185. package/skills/seo/assets/snippets/jsonld-organization.json +74 -0
  186. package/skills/seo/assets/snippets/jsonld-physician.json +46 -0
  187. package/skills/seo/assets/snippets/meta.blade.php +61 -0
  188. package/skills/seo/assets/snippets/robots.txt +61 -0
  189. package/skills/seo/assets/snippets/sitemap-urlset-example.xml +72 -0
  190. package/skills/seo/assets/snippets/sitemap.xml +36 -0
  191. package/skills/seo/references/antipatterns.md +66 -0
  192. package/skills/seo/references/audit-checklist.md +319 -0
  193. package/skills/seo/references/content-seo.md +204 -0
  194. package/skills/seo/references/eeat-medical.md +219 -0
  195. package/skills/seo/references/measurement.md +250 -0
  196. package/skills/seo/references/on-page.md +214 -0
  197. package/skills/seo/references/page-templates.md +56 -0
  198. package/skills/seo/references/stack-recipes.md +695 -0
  199. package/skills/seo/references/structured-data.md +487 -0
  200. package/skills/seo/references/technical-seo.md +296 -0
  201. package/skills/service-desk-conventions/SKILL.md +52 -0
  202. package/skills/service-desk-db-safety/SKILL.md +63 -0
  203. package/skills/service-desk-release/SKILL.md +61 -0
  204. package/skills/service-desk-testing/SKILL.md +61 -0
  205. package/skills/service-desk-ui/SKILL.md +52 -0
  206. package/skills/skills-router/SKILL.md +53 -0
  207. package/skills/skills-router/references/applying-skills.md +25 -0
  208. package/skills/skills-router/references/routing-matrix.md +52 -0
  209. package/skills/testing-strategy/SKILL.md +52 -0
  210. package/skills/ui-design-system/SKILL.md +50 -0
  211. package/skills/ui-design-system/references/accessibility-and-responsive.md +50 -0
  212. package/skills/ui-design-system/references/tokens-and-components.md +67 -0
  213. package/skills/ui-design-system/references/visual-qa.md +44 -0
  214. package/skills/web-research/SKILL.md +47 -0
  215. package/skills/web-research/references/answer-and-citations.md +60 -0
  216. package/skills/web-research/references/query-recipes.md +44 -0
  217. package/skills/wow-design/SKILL.md +89 -0
  218. package/skills/wow-design/assets/kit/README.md +195 -0
  219. package/skills/wow-design/assets/kit/assets/css/base.css +158 -0
  220. package/skills/wow-design/assets/kit/assets/css/components.css +1001 -0
  221. package/skills/wow-design/assets/kit/assets/css/fonts.css +73 -0
  222. package/skills/wow-design/assets/kit/assets/css/pages.css +737 -0
  223. package/skills/wow-design/assets/kit/assets/css/tokens.css +218 -0
  224. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-golos-text.txt +93 -0
  225. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-lora.txt +93 -0
  226. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-unbounded.txt +93 -0
  227. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-400-normal.woff2 +0 -0
  228. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-500-normal.woff2 +0 -0
  229. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-600-normal.woff2 +0 -0
  230. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-700-normal.woff2 +0 -0
  231. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-400-normal.woff2 +0 -0
  232. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-500-normal.woff2 +0 -0
  233. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-600-normal.woff2 +0 -0
  234. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-700-normal.woff2 +0 -0
  235. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-italic.woff2 +0 -0
  236. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-normal.woff2 +0 -0
  237. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-500-normal.woff2 +0 -0
  238. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-600-normal.woff2 +0 -0
  239. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-400-normal.woff2 +0 -0
  240. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-500-normal.woff2 +0 -0
  241. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-600-normal.woff2 +0 -0
  242. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-400-normal.woff2 +0 -0
  243. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-500-normal.woff2 +0 -0
  244. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-600-normal.woff2 +0 -0
  245. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-400-normal.woff2 +0 -0
  246. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-500-normal.woff2 +0 -0
  247. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-600-normal.woff2 +0 -0
  248. package/skills/wow-design/assets/kit/assets/js/kit.js +550 -0
  249. package/skills/wow-design/assets/kit/pages/dashboard.html +539 -0
  250. package/skills/wow-design/assets/kit/pages/hospital.html +430 -0
  251. package/skills/wow-design/assets/kit/showcase.html +1164 -0
  252. package/skills/wow-design/assets/scripts/LICENSE-axe-core.txt +362 -0
  253. package/skills/wow-design/assets/scripts/LICENSE-playwright-core.txt +202 -0
  254. package/skills/wow-design/assets/scripts/README.md +312 -0
  255. package/skills/wow-design/assets/scripts/audit.mjs +1200 -0
  256. package/skills/wow-design/assets/scripts/capture.mjs +337 -0
  257. package/skills/wow-design/assets/scripts/package.json +30 -0
  258. package/skills/wow-design/references/anti-patterns.md +252 -0
  259. package/skills/wow-design/references/critique-rubric.md +262 -0
  260. package/skills/wow-design/references/design-brief-template.md +48 -0
  261. package/skills/wow-design/references/inspiration.md +203 -0
  262. package/skills/wow-design/references/presets.md +99 -0
  263. package/skills/wow-design/references/principles.md +371 -0
  264. package/skills/wow-design/references/quick-presets.md +50 -0
  265. package/skills/wow-design/references/report-template.md +21 -0
  266. package/skills/wow-design/references/stack-recipes.md +96 -0
  267. package/skills/wow-design/references/visual-qa.md +82 -0
  268. package/skills/wow-design/references/wow-levers.md +43 -0
  269. package/tools/skill-cost.mjs +76 -0
@@ -0,0 +1,5 @@
1
+ # Host half of the "Move chats between Workspaces" plugin.
2
+ # The browser half is loaded from this package's dsh.client entry.
3
+ - insert:
4
+ - id: session-move
5
+ name: dsh-session-move
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Move chat between workspaces">
2
+ <defs>
3
+ <linearGradient id="sm-a" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#2f7df6"/>
5
+ <stop offset="1" stop-color="#1f4fbf"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect x="2" y="2" width="60" height="60" rx="14" fill="url(#sm-a)"/>
9
+ <rect x="10" y="14" width="18" height="13" rx="3" fill="#ffffff" opacity=".92"/>
10
+ <rect x="10" y="31" width="18" height="13" rx="3" fill="#ffffff" opacity=".55"/>
11
+ <rect x="36" y="14" width="18" height="13" rx="3" fill="#ffffff" opacity=".35"/>
12
+ <rect x="36" y="31" width="18" height="13" rx="3" fill="#ffffff" opacity=".2"/>
13
+ <path d="M31 38.5 L41 45.5 L31 52.5" fill="none" stroke="#ffffff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" opacity=".95"/>
14
+ </svg>
@@ -0,0 +1,497 @@
1
+ /**
2
+ * dsh-session-move — Host half.
3
+ *
4
+ * Adds "move this chat to another Workspace" to the DSH Web GUI:
5
+ * - re-accounts the Session in the durable Workspace registry (target Workspace
6
+ * gains the Session, the previous one loses it),
7
+ * - switches the Session's effective working directory to the target
8
+ * Workspace directory (so the model really works in that project),
9
+ * - remembers the override in a small JSON file, because a Session's stored
10
+ * header keeps the original project directory: the durable membership is
11
+ * re-applied on every Host start.
12
+ *
13
+ * The browser half talks to this half through one authenticated exact Fetch
14
+ * route family mounted under `/api/dsh-session-move/*` on the existing
15
+ * Connection carrier (`ctx.connection.fetch.register`). No new model-facing
16
+ * tool, prompt, or Session event is produced.
17
+ *
18
+ * Workspace membership is normally derived from the immutable Session header
19
+ * `cwd`. This plugin therefore maintains an in-memory `sessionPaths` override
20
+ * in `ctx.workspaceRegistry` plus the durable Workspace records; see
21
+ * README.md for the exact contract and failure modes.
22
+ */
23
+
24
+ import fs from "node:fs";
25
+ import os from "node:os";
26
+ import path from "node:path";
27
+
28
+ /** Mounted route family; the browser calls `/api/dsh-session-move/<op>`. */
29
+ const ROUTE_BASE = "/api/dsh-session-move";
30
+ /** RPC method prefix carried inside the Connection envelope. */
31
+ const RPC_NAMESPACE = "dsh-session-move";
32
+ /** Version of the local move-state file. */
33
+ const STATE_VERSION = 1;
34
+
35
+ export const inject = ["connection", "workspaceRegistry"];
36
+
37
+ /** Structured failure with a stable machine code. */
38
+ class SessionMoveError extends Error {
39
+ constructor(code, message, details = {}) {
40
+ super(message);
41
+ this.name = "SessionMoveError";
42
+ this.code = code;
43
+ this.details = details;
44
+ }
45
+ }
46
+
47
+ function pluginDataDirectory() {
48
+ const home = process.env.DSH_HOME ?? path.join(os.homedir(), ".dsh");
49
+ return path.join(home, "dsh-session-move");
50
+ }
51
+
52
+ function stateFilePath() {
53
+ return path.join(pluginDataDirectory(), "state.json");
54
+ }
55
+
56
+ function stringOrNull(value) {
57
+ return typeof value === "string" && value.length > 0 ? value : null;
58
+ }
59
+
60
+ /**
61
+ * Read persisted overrides. A malformed file is ignored (and re-written on the
62
+ * next change) instead of failing plugin activation.
63
+ */
64
+ function readMoves(logger) {
65
+ const moves = new Map();
66
+ let raw;
67
+ try {
68
+ raw = fs.readFileSync(stateFilePath(), "utf8");
69
+ } catch (error) {
70
+ if (error?.code !== "ENOENT") logger?.warn?.(`[session-move] cannot read state: ${String(error)}`);
71
+ return moves;
72
+ }
73
+ let parsed;
74
+ try {
75
+ parsed = JSON.parse(raw);
76
+ } catch (error) {
77
+ logger?.warn?.(`[session-move] state.json is not JSON, ignoring: ${String(error)}`);
78
+ return moves;
79
+ }
80
+ if (!parsed || parsed.version !== STATE_VERSION || !parsed.moves || typeof parsed.moves !== "object") {
81
+ logger?.warn?.("[session-move] state.json has an unsupported shape, ignoring");
82
+ return moves;
83
+ }
84
+ for (const [sessionId, entry] of Object.entries(parsed.moves)) {
85
+ if (typeof sessionId !== "string" || sessionId.length === 0) continue;
86
+ if (!entry || typeof entry !== "object") continue;
87
+ const workspaceId = stringOrNull(entry.workspaceId);
88
+ if (workspaceId === null && entry.workspaceId !== null) continue;
89
+ moves.set(sessionId, {
90
+ workspaceId,
91
+ homeWorkspaceId: stringOrNull(entry.homeWorkspaceId),
92
+ updatedAt: typeof entry.updatedAt === "string" ? entry.updatedAt : new Date(0).toISOString(),
93
+ });
94
+ }
95
+ return moves;
96
+ }
97
+
98
+ /** Atomic replace: a crash between write and rename cannot truncate the state. */
99
+ function writeMoves(moves) {
100
+ const directory = pluginDataDirectory();
101
+ fs.mkdirSync(directory, { recursive: true });
102
+ const target = stateFilePath();
103
+ const temporary = `${target}.${process.pid}.tmp`;
104
+ const payload = {
105
+ version: STATE_VERSION,
106
+ updatedAt: new Date().toISOString(),
107
+ moves: Object.fromEntries(moves),
108
+ };
109
+ fs.writeFileSync(temporary, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
110
+ fs.renameSync(temporary, target);
111
+ }
112
+
113
+ /** Id of the Workspace whose durable record currently accounts for a Session. */
114
+ function accountedWorkspaceId(registry, sessionId) {
115
+ const table = registry.table;
116
+ if (table !== undefined && typeof table.entries === "function") {
117
+ for (const [workspaceId, record] of table.entries()) {
118
+ if (Array.isArray(record?.sessionIds) && record.sessionIds.includes(sessionId)) return String(workspaceId);
119
+ }
120
+ }
121
+ for (const workspace of registry.list()) {
122
+ const record = workspace.record;
123
+ if (Array.isArray(record?.sessionIds) && record.sessionIds.includes(sessionId)) return String(workspace.id);
124
+ }
125
+ return undefined;
126
+ }
127
+
128
+ async function readHeader(registry, sessionId) {
129
+ try {
130
+ return await registry.readSessionHeader(sessionId);
131
+ } catch {
132
+ return undefined;
133
+ }
134
+ }
135
+
136
+ /** Workspace that owns the Session's original (header) directory, when registered. */
137
+ async function homeWorkspace(registry, header) {
138
+ if (typeof header?.cwd !== "string" || header.cwd.length === 0) return undefined;
139
+ try {
140
+ return await registry.resolveByPath(header.cwd);
141
+ } catch {
142
+ return undefined;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Attach a Session to a Workspace even though its stored header names another
148
+ * directory. `attachSession` validates the header `cwd`; the temporary override
149
+ * makes that validation see the target path, while the durable header is never
150
+ * touched. The registry itself then records the new account and updates its
151
+ * canonical-path cache (`rememberSessionPath`).
152
+ */
153
+ async function attachAcrossProjects(registry, workspace, sessionId, header) {
154
+ const original = registry.readSessionHeader;
155
+ try {
156
+ registry.readSessionHeader = async (id) =>
157
+ id === sessionId ? { ...header, cwd: workspace.path } : original.call(registry, id);
158
+ await workspace.attachSession(sessionId);
159
+ } finally {
160
+ registry.readSessionHeader = original;
161
+ }
162
+ }
163
+
164
+ /** Force the in-memory canonical-path index to accept the override. */
165
+ function rememberOverride(registry, sessionId, workspacePath) {
166
+ registry.sessionPaths.set(sessionId, workspacePath);
167
+ registry.missingSessionPaths.delete(sessionId);
168
+ registry.invalidSessionPaths.delete(sessionId);
169
+ }
170
+
171
+ /** Stop filtering the Session out of `Workspace.sessionIds` (Ungrouped bucket). */
172
+ function forgetOverride(registry, sessionId) {
173
+ registry.sessionPaths.delete(sessionId);
174
+ registry.missingSessionPaths.delete(sessionId);
175
+ registry.invalidSessionPaths.delete(sessionId);
176
+ }
177
+
178
+ /**
179
+ * Make the Session's effective working directory follow the target project.
180
+ * A live Agent is reused; a cold Session is resumed quietly through Session
181
+ * Controller (the same service other Host domains use). Failures are reported
182
+ * to the caller but never roll the membership move back.
183
+ */
184
+ async function followProjectDirectory(ctx, sessionId, directory) {
185
+ const workingDirectory = ctx.get("workingDirectory");
186
+ if (workingDirectory === undefined) return "unavailable";
187
+ let agent = ctx.get("agents")?.get(sessionId);
188
+ if (agent === undefined) {
189
+ const sessionController = ctx.get("sessionController");
190
+ if (sessionController === undefined) return "unavailable";
191
+ let resolved;
192
+ try {
193
+ resolved = await sessionController.resolveAgent(sessionId);
194
+ } catch (error) {
195
+ return `skipped: ${error instanceof Error ? error.message : String(error)}`;
196
+ }
197
+ if (resolved === undefined || resolved.agent === undefined) {
198
+ const reason = resolved?.error?.code ?? "no-agent";
199
+ return `skipped: ${reason}`;
200
+ }
201
+ agent = resolved.agent;
202
+ }
203
+ try {
204
+ await workingDirectory.set(agent, directory);
205
+ return "ok";
206
+ } catch (error) {
207
+ return `skipped: ${error instanceof Error ? error.message : String(error)}`;
208
+ }
209
+ }
210
+
211
+ function workspaceSummary(workspace) {
212
+ return {
213
+ workspaceId: String(workspace.id),
214
+ title: workspace.title,
215
+ path: workspace.path,
216
+ };
217
+ }
218
+
219
+ async function readState(registry, moves, payload) {
220
+ const workspaces = registry.list().map(workspaceSummary);
221
+ let session = null;
222
+ const sessionId = stringOrNull(payload?.sessionId);
223
+ if (sessionId !== null) {
224
+ const header = await readHeader(registry, sessionId);
225
+ if (header === undefined) throw new SessionMoveError("session-move/session-unknown", "Session not found", { sessionId });
226
+ const home = await homeWorkspace(registry, header);
227
+ session = {
228
+ sessionId,
229
+ workspaceId: accountedWorkspaceId(registry, sessionId) ?? null,
230
+ homeWorkspaceId: home === undefined ? null : String(home.id),
231
+ archived: registry.archivedSessionIds.includes(sessionId),
232
+ subagent: header.origin === "subagent",
233
+ };
234
+ }
235
+ return {
236
+ workspaces,
237
+ session,
238
+ moves: [...moves.entries()].map(([movedSessionId, entry]) => ({ sessionId: movedSessionId, ...entry })),
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Serialize moves of the same Session. The state file and the registry queue
244
+ * stay authoritative; this only prevents a double drop from racing itself.
245
+ */
246
+ const inFlight = new Set();
247
+
248
+ async function moveSession(ctx, registry, moves, payload) {
249
+ const sessionId = stringOrNull(payload?.sessionId);
250
+ if (sessionId === null) throw new SessionMoveError("session-move/bad-request", "sessionId is required", {});
251
+ const rawTarget = payload?.workspaceId;
252
+ if (rawTarget !== null && rawTarget !== undefined && typeof rawTarget !== "string") {
253
+ throw new SessionMoveError("session-move/bad-request", "workspaceId must be a string or null", {});
254
+ }
255
+ const targetId = stringOrNull(rawTarget);
256
+ if (rawTarget !== null && rawTarget !== undefined && targetId === null) {
257
+ throw new SessionMoveError("session-move/bad-request", "workspaceId must not be empty", {});
258
+ }
259
+ if (inFlight.has(sessionId)) throw new SessionMoveError("session-move/busy", "Another move of this Session is already running", { sessionId });
260
+ inFlight.add(sessionId);
261
+ try {
262
+ const header = await readHeader(registry, sessionId);
263
+ if (header === undefined) throw new SessionMoveError("session-move/session-unknown", "Session not found", { sessionId });
264
+ if (header.origin === "subagent") throw new SessionMoveError("session-move/subagent", "Subagent Sessions cannot be moved", { sessionId });
265
+
266
+ const target = targetId === null ? undefined : registry.get(targetId);
267
+ if (targetId !== null && target === undefined) {
268
+ throw new SessionMoveError("session-move/workspace-unknown", "Workspace not found", { workspaceId: targetId });
269
+ }
270
+ if (target !== undefined) {
271
+ const status = await target.status();
272
+ if (status !== "ok") {
273
+ throw new SessionMoveError("session-move/workspace-missing-dir", "The Workspace directory is missing", {
274
+ workspaceId: targetId,
275
+ path: target.path,
276
+ });
277
+ }
278
+ }
279
+
280
+ let previousWorkspaceId = null;
281
+ let changed = false;
282
+ await registry.enqueueOperation(async () => {
283
+ const currentId = accountedWorkspaceId(registry, sessionId);
284
+ previousWorkspaceId = currentId ?? null;
285
+ if (target !== undefined ? currentId === String(target.id) : currentId === undefined) return;
286
+ if (currentId !== undefined) {
287
+ const current = registry.get(currentId);
288
+ if (current !== undefined) await current.detachSession(sessionId);
289
+ }
290
+ if (target !== undefined) await attachAcrossProjects(registry, target, sessionId, header);
291
+ else forgetOverride(registry, sessionId);
292
+ changed = true;
293
+ });
294
+
295
+ const home = await homeWorkspace(registry, header);
296
+ const homeId = home === undefined ? null : String(home.id);
297
+ if (!changed) {
298
+ return {
299
+ changed: false,
300
+ sessionId,
301
+ previousWorkspaceId,
302
+ workspaceId: target === undefined ? null : String(target.id),
303
+ workingDirectory: "unchanged",
304
+ };
305
+ }
306
+
307
+ if (target === undefined || (homeId !== null && String(target.id) === homeId)) moves.delete(sessionId);
308
+ else {
309
+ moves.set(sessionId, {
310
+ workspaceId: String(target.id),
311
+ homeWorkspaceId: homeId,
312
+ updatedAt: new Date().toISOString(),
313
+ });
314
+ }
315
+ writeMoves(moves);
316
+
317
+ const workingDirectory = target === undefined ? "unchanged" : await followProjectDirectory(ctx, sessionId, target.path);
318
+ return {
319
+ changed: true,
320
+ sessionId,
321
+ previousWorkspaceId,
322
+ workspaceId: target === undefined ? null : String(target.id),
323
+ workingDirectory,
324
+ };
325
+ } finally {
326
+ inFlight.delete(sessionId);
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Re-apply persisted overrides after a Host start. The durable Workspace
332
+ * records already contain the moved Sessions; this only restores the
333
+ * canonical-path cache that drives `Workspace.sessionIds`, and repairs records
334
+ * whose target Workspace disappeared.
335
+ */
336
+ async function restoreMappings(ctx, registry, moves) {
337
+ let dirty = false;
338
+ for (const [sessionId, entry] of [...moves.entries()]) {
339
+ const header = await readHeader(registry, sessionId);
340
+ if (header === undefined || header.origin === "subagent") {
341
+ moves.delete(sessionId);
342
+ dirty = true;
343
+ continue;
344
+ }
345
+ if (entry.workspaceId === null) continue;
346
+ const target = registry.get(entry.workspaceId);
347
+ if (target === undefined) {
348
+ moves.delete(sessionId);
349
+ dirty = true;
350
+ const home = await homeWorkspace(registry, header);
351
+ if (home === undefined) continue;
352
+ try {
353
+ await registry.enqueueOperation(async () => {
354
+ const currentId = accountedWorkspaceId(registry, sessionId);
355
+ if (currentId !== undefined && currentId !== String(home.id)) {
356
+ const current = registry.get(currentId);
357
+ if (current !== undefined) await current.detachSession(sessionId);
358
+ }
359
+ rememberOverride(registry, sessionId, home.path);
360
+ const record = registry.table?.get(home.id);
361
+ if (record !== undefined && !record.sessionIds.includes(sessionId)) {
362
+ await attachAcrossProjects(registry, home, sessionId, header);
363
+ }
364
+ });
365
+ } catch (error) {
366
+ ctx.logger?.warn?.(`[session-move] could not re-home Session ${sessionId}: ${String(error)}`);
367
+ }
368
+ continue;
369
+ }
370
+ rememberOverride(registry, sessionId, target.path);
371
+ const record = registry.table?.get(target.id);
372
+ if (record !== undefined && !record.sessionIds.includes(sessionId)) {
373
+ try {
374
+ await registry.enqueueOperation(async () => {
375
+ const currentId = accountedWorkspaceId(registry, sessionId);
376
+ if (currentId !== undefined && currentId !== String(target.id)) {
377
+ const current = registry.get(currentId);
378
+ if (current !== undefined) await current.detachSession(sessionId);
379
+ }
380
+ await attachAcrossProjects(registry, target, sessionId, header);
381
+ });
382
+ } catch (error) {
383
+ ctx.logger?.warn?.(`[session-move] could not re-attach Session ${sessionId}: ${String(error)}`);
384
+ }
385
+ }
386
+ }
387
+ if (dirty) {
388
+ try {
389
+ writeMoves(moves);
390
+ } catch (error) {
391
+ ctx.logger?.warn?.(`[session-move] could not persist the repaired move state: ${String(error)}`);
392
+ }
393
+ }
394
+ }
395
+
396
+ /* ------------------------------------------------------------------ */
397
+ /* Wire envelope helpers */
398
+ /* ------------------------------------------------------------------ */
399
+
400
+ function okEnvelope(rpcId, value) {
401
+ return Response.json({ type: "server-response", rpcId, result: { ok: true, value } });
402
+ }
403
+
404
+ function errorEnvelope(rpcId, code, message, details = {}) {
405
+ return Response.json({ type: "server-response", rpcId, result: { ok: false, error: { code, message, details } } });
406
+ }
407
+
408
+ async function readEnvelope(request) {
409
+ let body;
410
+ try {
411
+ body = await request.json();
412
+ } catch {
413
+ throw new SessionMoveError("session-move/bad-request", "Request body is not JSON", {});
414
+ }
415
+ if (
416
+ body === null ||
417
+ typeof body !== "object" ||
418
+ body.type !== "client-request" ||
419
+ typeof body.rpcId !== "string" ||
420
+ typeof body.method !== "string"
421
+ ) {
422
+ throw new SessionMoveError("session-move/bad-request", "Invalid Connection envelope", {});
423
+ }
424
+ return { rpcId: body.rpcId, method: body.method, payload: body.payload ?? {} };
425
+ }
426
+
427
+ function registerOperation(ctx, operation, handler) {
428
+ const route = {
429
+ path: `${ROUTE_BASE}/${operation}`,
430
+ methods: ["POST"],
431
+ requestBody: "buffered",
432
+ fetch: async (request) => {
433
+ let rpcId = "invalid-request";
434
+ try {
435
+ const envelope = await readEnvelope(request);
436
+ rpcId = envelope.rpcId;
437
+ if (envelope.method !== `${RPC_NAMESPACE}/${operation}`) {
438
+ return errorEnvelope(rpcId, "session-move/bad-request", "Envelope method does not match its endpoint", {
439
+ expected: `${RPC_NAMESPACE}/${operation}`,
440
+ actual: envelope.method,
441
+ });
442
+ }
443
+ const value = await handler(envelope.payload);
444
+ return okEnvelope(rpcId, value);
445
+ } catch (error) {
446
+ const code = typeof error?.code === "string" && error.code.startsWith("session-move/")
447
+ ? error.code
448
+ : "session-move/internal";
449
+ const message = error instanceof Error ? error.message : String(error);
450
+ const details = error?.details && typeof error.details === "object" ? error.details : {};
451
+ if (code === "session-move/internal") ctx.logger?.warn?.(`[session-move] ${operation} failed: ${String(error)}`);
452
+ return errorEnvelope(rpcId, code, message, details);
453
+ }
454
+ },
455
+ };
456
+ ctx.effect(() => {
457
+ const dispose = ctx.connection.fetch.register(route);
458
+ return () => {
459
+ try {
460
+ void Promise.resolve(dispose()).catch(() => {});
461
+ } catch {
462
+ /* the carrier may already be unloading */
463
+ }
464
+ };
465
+ }, `session-move: ${operation} route`);
466
+ }
467
+
468
+ /* ------------------------------------------------------------------ */
469
+ /* Plugin entry */
470
+ /* ------------------------------------------------------------------ */
471
+
472
+ export async function apply(ctx) {
473
+ const logger = ctx.logger;
474
+ const registry = ctx.workspaceRegistry;
475
+ const moves = readMoves(logger);
476
+ try {
477
+ fs.mkdirSync(pluginDataDirectory(), { recursive: true });
478
+ } catch {
479
+ /* read-only home: moves still work in memory for this process */
480
+ }
481
+ try {
482
+ await restoreMappings(ctx, registry, moves);
483
+ } catch (error) {
484
+ logger?.warn?.(`[session-move] restoring persisted moves failed: ${String(error)}`);
485
+ }
486
+
487
+ registerOperation(ctx, "state", (payload) => readState(registry, moves, payload));
488
+ registerOperation(ctx, "move", (payload) => moveSession(ctx, registry, moves, payload));
489
+ registerOperation(ctx, "diag", () => ({
490
+ version: 1,
491
+ pid: process.pid,
492
+ host: process.version,
493
+ stateFile: stateFilePath(),
494
+ moves: moves.size,
495
+ workspaces: registry.list().length,
496
+ }));
497
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Move chats between workspaces",
4
+ "description": "Drag a chat from one workspace to another in the sidebar and keep it there: the chat's project and working directory follow the drop target."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Перенос чатов между рабочими областями",
4
+ "description": "Перетащите чат из одной рабочей области в другую в боковой панели — чат останется там: его проект и рабочая директория переключатся на цель."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "在工作区之间移动对话",
4
+ "description": "在侧边栏把对话从一个工作区拖到另一个工作区,对话会留在那里:所属项目和工作目录都会切换到目标工作区。"
5
+ }
6
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "dsh-session-move",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "Drag & drop chats between Workspaces in the DSH Web GUI, and keep them there.",
6
+ "type": "module",
7
+ "main": "index.js",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./client": "./client.js",
11
+ "./locale/*.json": "./locale/*.json",
12
+ "./icon": "./icon.svg"
13
+ },
14
+ "files": [
15
+ "index.js",
16
+ "client.js",
17
+ "cordis.patch.yml",
18
+ "locale/*.json",
19
+ "icon.svg",
20
+ "README.md"
21
+ ],
22
+ "dsh": {
23
+ "manifestVersion": 1,
24
+ "bundle": { "patch": "./cordis.patch.yml" },
25
+ "client": {
26
+ "platform": "web",
27
+ "immediately": true,
28
+ "inject": []
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "steps": [
3
+ {
4
+ "name": "visible-session",
5
+ "value": {
6
+ "key": "session:session-73e5c76d-f3ae-43db-ac82-64e23969f6e4",
7
+ "id": "session-73e5c76d-f3ae-43db-ac82-64e23969f6e4",
8
+ "title": "БегЛокальная база знаний по проектам для ИИ1мин"
9
+ }
10
+ },
11
+ {
12
+ "name": "axe-dialog",
13
+ "value": {
14
+ "violations": []
15
+ }
16
+ },
17
+ {
18
+ "name": "viewport-390",
19
+ "value": {
20
+ "scrollWidth": 390,
21
+ "clientWidth": 390,
22
+ "dialogWidth": 342,
23
+ "viewport": 390
24
+ }
25
+ },
26
+ {
27
+ "name": "dark-theme",
28
+ "value": {
29
+ "source": "system",
30
+ "background": "rgb(21, 21, 23)"
31
+ }
32
+ },
33
+ {
34
+ "name": "dialog-closed",
35
+ "value": true
36
+ },
37
+ {
38
+ "name": "drag-highlight",
39
+ "value": {
40
+ "dragInfo": {
41
+ "ok": true
42
+ },
43
+ "visible": false
44
+ }
45
+ }
46
+ ],
47
+ "errors": [],
48
+ "screenshots": [
49
+ "%USERPROFILE%/Documents/deepseek-harness/default-workspace/projects/dsh-session-move/screenshots/01-row-menu-1440-light.png",
50
+ "%USERPROFILE%/Documents/deepseek-harness/default-workspace/projects/dsh-session-move/screenshots/02-dialog-1440-light.png",
51
+ "%USERPROFILE%/Documents/deepseek-harness/default-workspace/projects/dsh-session-move/screenshots/03-dialog-390-light.png",
52
+ "%USERPROFILE%/Documents/deepseek-harness/default-workspace/projects/dsh-session-move/screenshots/04-dialog-1440-dark.png",
53
+ "%USERPROFILE%/Documents/deepseek-harness/default-workspace/projects/dsh-session-move/screenshots/05-drag-highlight-1440-light.png"
54
+ ],
55
+ "axe": {
56
+ "violations": []
57
+ },
58
+ "logs": [
59
+ "[info] [session-move] ready",
60
+ "[error] [deepseek-account] account read failed {field: profile}"
61
+ ]
62
+ }
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: dsh-skill-autopilot
3
+ name: dsh-skill-autopilot