minimal-agent 0.8.2 → 0.10.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 (484) hide show
  1. package/README.md +14 -12
  2. package/package.json +34 -1
  3. package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
  4. package/plugins/cron-runner/commands/cron.md +26 -0
  5. package/plugins/cron-runner/commands/crons.md +7 -0
  6. package/plugins/cron-runner/plugin.js +34 -0
  7. package/plugins/cron-runner/src/commands.js +348 -0
  8. package/plugins/cron-runner/src/cronTime.js +148 -0
  9. package/plugins/cron-runner/src/lock.js +68 -0
  10. package/plugins/cron-runner/src/runs.js +89 -0
  11. package/plugins/cron-runner/src/scheduleEngine.js +72 -0
  12. package/plugins/cron-runner/src/scheduler.js +355 -0
  13. package/plugins/cron-runner/src/schema.js +140 -0
  14. package/plugins/cron-runner/src/service.js +167 -0
  15. package/plugins/cron-runner/src/store.js +298 -0
  16. package/plugins/cron-runner/src/types.js +1 -0
  17. package/plugins/workflow-runner/commands/workflow.md +4 -4
  18. package/plugins/workflow-runner/commands/workflows.md +1 -1
  19. package/plugins/workflow-runner/plugin.js +3 -1
  20. package/plugins/workflow-runner/src/agentCommon.js +249 -51
  21. package/plugins/workflow-runner/src/dslManifest.js +72 -0
  22. package/plugins/workflow-runner/src/expressions.js +198 -15
  23. package/plugins/workflow-runner/src/index.js +40 -8
  24. package/plugins/workflow-runner/src/jsonPayload.js +1 -1
  25. package/plugins/workflow-runner/src/loader.js +286 -72
  26. package/plugins/workflow-runner/src/multimodal.js +37 -0
  27. package/plugins/workflow-runner/src/recovery.js +49 -39
  28. package/plugins/workflow-runner/src/runner.js +802 -287
  29. package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
  30. package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
  31. package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
  32. package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
  33. package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
  34. package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
  35. package/plugins/workflow-runner/src/taskIdentity.js +296 -0
  36. package/plugins/workflow-runner/src/types.js +19 -9
  37. package/plugins/workflow-runner/src/workflowState.js +516 -33
  38. package/skills/adversarial-review/SKILL.md +0 -1
  39. package/skills/ai-image/SKILL.md +89 -0
  40. package/skills/ai-image/scripts/generate.mjs +497 -0
  41. package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
  42. package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
  43. package/skills/ai-image/scripts/generate_test.mjs +74 -0
  44. package/skills/browser-automation/SKILL.md +47 -0
  45. package/skills/docx/SKILL.md +25 -3
  46. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  47. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  48. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  49. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  50. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  51. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  52. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  53. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  54. package/skills/docx/scripts/opc_check.py +253 -0
  55. package/skills/image-gen-openrouter/SKILL.md +10 -2
  56. package/skills/image-stats/SKILL.md +65 -0
  57. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  58. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  59. package/skills/image-stats/scripts/image_stats.py +368 -0
  60. package/skills/image-stats/scripts/image_stats_test.py +181 -0
  61. package/skills/pptx/SKILL.md +123 -95
  62. package/skills/pptx/design.md +82 -0
  63. package/skills/pptx/examples/README.md +29 -0
  64. package/skills/pptx/examples/hybrid_example.mjs +107 -0
  65. package/skills/pptx/examples/hybrid_example.pptx +0 -0
  66. package/skills/pptx/pptxgenjs.md +41 -0
  67. package/skills/pptx/scripts/deck_helpers.mjs +224 -0
  68. package/skills/pptx/scripts/layout_lint.py +293 -0
  69. package/skills/pptx/scripts/layout_lint_test.py +160 -0
  70. package/skills/pptx/scripts/opc_check.py +339 -0
  71. package/skills/pptx/scripts/svg_fallback.mjs +145 -0
  72. package/skills/pptx/svg.md +67 -0
  73. package/skills/tts-zh/SKILL.md +19 -2
  74. package/skills/ui-design/SKILL.md +158 -0
  75. package/skills/ui-design/claude_design.md +309 -0
  76. package/skills/ui-design/evals/evals.json +254 -0
  77. package/skills/ui-design/references/aesthetic-direction.md +83 -0
  78. package/skills/ui-design/references/claude-design-protocol.md +36 -0
  79. package/skills/ui-design/references/design-context.md +53 -0
  80. package/skills/ui-design/references/design-execution-playbook.md +82 -0
  81. package/skills/ui-design/references/export-pptx.md +124 -0
  82. package/skills/ui-design/references/layout-composition.md +654 -0
  83. package/skills/ui-design/references/preview-feedback.md +37 -0
  84. package/skills/ui-design/references/source-image-layout.md +189 -0
  85. package/skills/ui-design/references/starter-components.md +78 -0
  86. package/skills/ui-design/references/vi-aegis-vector.md +144 -0
  87. package/skills/ui-design/references/vi-amber-prism.md +133 -0
  88. package/skills/ui-design/references/vi-aqua-glass.md +200 -0
  89. package/skills/ui-design/references/vi-comfort-vision.md +147 -0
  90. package/skills/ui-design/references/vi-ember-prism.md +140 -0
  91. package/skills/ui-design/references/vi-nebula-console.md +151 -0
  92. package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
  93. package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
  94. package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
  95. package/skills/ui-design/references/vi-precision-horizon.md +215 -0
  96. package/skills/ui-design/references/vi-solar-relay.md +130 -0
  97. package/skills/ui-design/references/vi-template-workflow.md +48 -0
  98. package/skills/ui-design/references/vi-verdant-grid.md +141 -0
  99. package/skills/ui-design/references/vi-violet-meridian.md +172 -0
  100. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  101. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  102. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  103. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  104. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  105. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  106. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  107. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  108. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  109. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  110. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  111. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  112. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  113. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  114. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  115. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  116. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  117. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  118. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  119. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  120. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  121. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  122. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  123. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  124. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  125. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  126. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  127. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  128. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  129. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  130. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  131. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  132. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  133. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  134. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  135. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  136. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  137. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  138. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  139. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  140. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  141. package/skills/ui-design/scripts/assemble_material.py +217 -0
  142. package/skills/ui-design/scripts/assemble_material_test.py +149 -0
  143. package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
  144. package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
  145. package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
  146. package/skills/ui-design/scripts/check_language_policy.py +351 -0
  147. package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
  148. package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
  149. package/skills/ui-design/scripts/deck_pages_check.py +158 -0
  150. package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
  151. package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
  152. package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
  153. package/skills/ui-design/scripts/design_budget_check.py +271 -0
  154. package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
  155. package/skills/ui-design/scripts/design_repair_hints.py +162 -0
  156. package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
  157. package/skills/ui-design/scripts/design_style_check.py +397 -0
  158. package/skills/ui-design/scripts/design_style_check_test.py +233 -0
  159. package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
  160. package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
  161. package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
  162. package/skills/ui-design/scripts/extract_source_assets.py +607 -0
  163. package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
  164. package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
  165. package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
  166. package/skills/ui-design/scripts/image_plan_lint.py +571 -0
  167. package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
  168. package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
  169. package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
  170. package/skills/ui-design/scripts/layout_balance.py +881 -0
  171. package/skills/ui-design/scripts/layout_balance_test.py +927 -0
  172. package/skills/ui-design/scripts/normalize_design_json.py +272 -0
  173. package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
  174. package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
  175. package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
  176. package/skills/ui-design/scripts/resolve_source_input.py +270 -0
  177. package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
  178. package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
  179. package/skills/ui-design/scripts/source_usage_check.py +581 -0
  180. package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
  181. package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
  182. package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
  183. package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
  184. package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
  185. package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
  186. package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
  187. package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
  188. package/skills/ui-design/tests/e2e/rubric.json +54 -0
  189. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
  190. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
  191. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
  192. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
  193. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
  194. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
  195. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
  196. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
  197. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
  198. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
  199. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
  200. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
  201. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  202. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  203. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  204. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  205. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  206. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  207. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
  208. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
  209. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
  210. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
  211. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
  212. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
  213. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
  214. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
  215. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
  216. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
  217. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
  218. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
  219. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
  220. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
  221. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
  222. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
  223. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
  224. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
  225. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
  226. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
  227. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
  228. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
  229. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
  230. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
  231. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
  232. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
  233. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
  234. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
  235. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  236. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  237. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  238. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  239. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  240. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  241. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
  242. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
  243. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
  244. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
  245. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
  246. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
  247. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
  248. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
  249. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
  250. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
  251. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
  252. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
  253. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
  254. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
  255. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
  256. package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
  257. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
  258. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
  259. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
  260. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
  261. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
  262. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
  263. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
  264. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
  265. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
  266. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
  267. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
  268. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
  269. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
  270. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
  271. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
  272. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
  273. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
  274. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
  275. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
  276. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
  277. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
  278. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
  279. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
  280. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
  281. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
  282. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
  283. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
  284. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
  285. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
  286. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
  287. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
  288. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
  289. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
  290. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
  291. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
  292. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
  293. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
  294. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
  295. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
  296. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
  297. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
  298. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
  299. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
  300. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
  301. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
  302. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
  303. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
  304. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
  305. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
  306. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
  307. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
  308. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
  309. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
  310. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
  311. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
  312. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
  313. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
  314. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
  315. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
  316. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
  317. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
  318. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
  319. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
  320. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
  321. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
  322. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
  323. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
  324. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
  325. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
  326. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
  327. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
  328. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
  329. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
  330. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
  331. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
  332. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
  333. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
  334. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
  335. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
  336. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
  337. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
  338. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
  339. package/skills/video-compose/SKILL.md +8 -1
  340. package/skills/web-crawler/SKILL.md +70 -0
  341. package/skills/web-crawler/scripts/crawl.mjs +538 -0
  342. package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
  343. package/skills/xlsx/SKILL.md +12 -0
  344. package/skills/xlsx/scripts/opc_check.py +253 -0
  345. package/src/advisorState.js +22 -0
  346. package/src/askBridge.js +97 -3
  347. package/src/bootstrap/workingDir.js +12 -1
  348. package/src/cli/args.js +17 -1
  349. package/src/cli/configMenu.js +236 -0
  350. package/src/cli/configWizard.js +7 -5
  351. package/src/cli/print.js +37 -4
  352. package/src/cli/streamJson.js +77 -24
  353. package/src/cli/wizards/advisorWizard.js +147 -0
  354. package/src/cli/wizards/fieldForm.js +149 -0
  355. package/src/cli/wizards/imageWizard.js +60 -0
  356. package/src/cli/wizards/openRouterWizard.js +23 -0
  357. package/src/cli/wizards/providerPresets.js +95 -0
  358. package/src/cli/wizards/providerWizard.js +219 -0
  359. package/src/cli/wizards/searchWizard.js +23 -0
  360. package/src/cli/wizards/ttsWizard.js +34 -0
  361. package/src/config/configFile.js +441 -83
  362. package/src/config/configService.js +446 -0
  363. package/src/config.js +24 -64
  364. package/src/context/agentHome.js +9 -0
  365. package/src/context/archive.js +7 -2
  366. package/src/context/compact.js +8 -5
  367. package/src/context/contextLock.js +81 -0
  368. package/src/context/persistContext.js +30 -11
  369. package/src/context/reactiveCompact.js +4 -2
  370. package/src/context/recentDirs.js +21 -61
  371. package/src/context/sessionPath.js +97 -23
  372. package/src/context/sessionRegistry.js +303 -24
  373. package/src/context/tokenCounter.js +18 -0
  374. package/src/context/workspaceIndex.js +731 -0
  375. package/src/infra/verification.js +28 -15
  376. package/src/llm/client.js +394 -159
  377. package/src/llm/providers/anthropicFormat.js +241 -0
  378. package/src/llm/providers/openaiFormat.js +190 -0
  379. package/src/loop.js +219 -32
  380. package/src/main.js +61 -11
  381. package/src/messageQueue.js +97 -0
  382. package/src/multimodal.js +203 -0
  383. package/src/plugin-sdk.js +15 -0
  384. package/src/plugins/commandRouter.js +3 -0
  385. package/src/plugins/hookEngine.js +5 -1
  386. package/src/plugins/pluginLifecycle.js +28 -0
  387. package/src/plugins/pluginLoader.js +2 -1
  388. package/src/plugins/pluginRunner.js +49 -21
  389. package/src/plugins/transcript.js +4 -3
  390. package/src/prompts/pluginList.js +5 -5
  391. package/src/prompts/projectInstructions.js +3 -3
  392. package/src/prompts/promptAssembly.js +133 -144
  393. package/src/prompts/skillList.js +12 -22
  394. package/src/prompts/system.js +0 -1
  395. package/src/session/runAgentTurn.js +70 -0
  396. package/src/session/runTurn.js +41 -6
  397. package/src/toolGate.js +17 -0
  398. package/src/tools/advisor/advisor.js +379 -0
  399. package/src/tools/agent/agent.js +555 -0
  400. package/src/tools/agent/agentTypes.js +59 -0
  401. package/src/tools/agent/coordinator.js +93 -0
  402. package/src/tools/agent/worktree.js +270 -0
  403. package/src/tools/ask/ask.js +7 -2
  404. package/src/tools/bash/bash.js +112 -18
  405. package/src/tools/download/download.js +144 -0
  406. package/src/tools/edit/edit.js +1 -0
  407. package/src/tools/edit/multi-edit.js +1 -0
  408. package/src/tools/genimage/genimage.js +242 -0
  409. package/src/tools/genspeech/genspeech.js +67 -0
  410. package/src/tools/glob/glob.js +1 -0
  411. package/src/tools/grep/grep.js +3 -0
  412. package/src/tools/grep/rgPath.js +2 -0
  413. package/src/tools/index.js +25 -2
  414. package/src/tools/read/read.js +1 -0
  415. package/src/tools/shared/killTree.js +52 -0
  416. package/src/tools/toolProgressBridge.js +48 -0
  417. package/src/tools/webbrowser/agentBrowserPath.js +258 -0
  418. package/src/tools/webbrowser/browser.js +59 -42
  419. package/src/tools/webbrowser/browserControl.js +118 -0
  420. package/src/tools/webbrowser/browserDaemon.js +97 -0
  421. package/src/tools/webbrowser/browserService.js +27 -0
  422. package/src/tools/webbrowser/browserStream.js +119 -0
  423. package/src/tools/webbrowser/webbrowser.js +699 -130
  424. package/src/tools/webfetch/webfetch.js +1 -0
  425. package/src/tools/websearch/websearch.js +134 -51
  426. package/src/tools/write/write.js +1 -0
  427. package/src/ui/App.js +249 -22
  428. package/src/ui/AskQuestion.js +29 -10
  429. package/src/ui/InputBox.js +104 -12
  430. package/src/ui/LiveArea.js +112 -19
  431. package/src/ui/MessageList.js +100 -14
  432. package/src/ui/ModelPicker.js +56 -0
  433. package/src/ui/QueuedMessagesBanner.js +14 -0
  434. package/src/ui/Root.js +18 -10
  435. package/src/ui/SessionPicker.js +102 -0
  436. package/src/ui/StatusLine.js +13 -4
  437. package/src/ui/WorkspacePicker.js +55 -0
  438. package/src/ui/figures.js +44 -0
  439. package/src/ui/hooks/askSlot.js +82 -0
  440. package/src/ui/hooks/useChat.js +358 -64
  441. package/src/ui/hooks/usePasteHandler.js +11 -1
  442. package/src/ui/hooks/useTextBuffer.js +1 -3
  443. package/src/ui/liveViewport.js +5 -1
  444. package/src/ui/markdown.js +206 -0
  445. package/src/ui/textBuffer.js +0 -7
  446. package/src/ui/theme.js +32 -0
  447. package/src/utils/debugLog.js +0 -4
  448. package/src/utils/resourcePaths.js +2 -2
  449. package/src/utils/stripThink.js +50 -0
  450. package/src/web/browserBridge.js +447 -0
  451. package/src/web/protocol.js +19 -0
  452. package/workflows/adaptive-research.yaml +13 -5
  453. package/workflows/book-review-short.yaml +36 -10
  454. package/workflows/e2e-write-greet.yaml +6 -2
  455. package/workflows/illustrate.yaml +227 -0
  456. package/workflows/plan.yaml +460 -179
  457. package/workflows/ppt.yaml +1659 -0
  458. package/workflows/ralph-loop.yaml +199 -0
  459. package/workflows/schema.json +81 -23
  460. package/workflows/webppt.yaml +496 -0
  461. package/workflows/word.yaml +710 -0
  462. package/workflows/youtube-shorts.yaml +46 -15
  463. package/MINIMAL-TRACE.md +0 -77
  464. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
  465. package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
  466. package/plugins/ralph-wiggum/README.md +0 -197
  467. package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
  468. package/plugins/ralph-wiggum/commands/help.md +0 -137
  469. package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
  470. package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
  471. package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
  472. package/plugins/ralph-wiggum/plugin.js +0 -280
  473. package/plugins/ralph-wiggum/src/goalState.js +0 -258
  474. package/plugins/ralph-wiggum/src/sentinels.js +0 -76
  475. package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
  476. package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
  477. package/plugins/workflow-runner/src/miniReAct.js +0 -223
  478. package/plugins/workflow-runner/src/voteHelpers.js +0 -110
  479. package/skills/config/SKILL.md +0 -123
  480. package/skills/frontend-design/LICENSE.txt +0 -177
  481. package/skills/frontend-design/SKILL.md +0 -42
  482. package/src/ui/ProgressPanel.js +0 -54
  483. package/src/ui/StreamingBlock.js +0 -11
  484. package/src/ui/ToolStatus.js +0 -11
@@ -0,0 +1,731 @@
1
+ /** Global workspace/session index shared by CLI, TUI, Desktop and Cron hosts. */
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { constants } from 'node:fs';
4
+ import { copyFile, mkdir, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile, } from 'node:fs/promises';
5
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
+ import { agentStateDir } from './agentHome.js';
7
+ import { legacyPathSessionFileFor, validateSessionId } from './sessionPath.js';
8
+ const INDEX_VERSION = 1;
9
+ const LOCK_STALE_MS = 120_000;
10
+ const LOCK_WAIT_MS = 15_000;
11
+ const HISTORY_PREFIX_LIMIT = 1024 * 1024;
12
+ let mutationQueue = Promise.resolve();
13
+ function indexPath() {
14
+ return join(agentStateDir(), 'workspace-index.json');
15
+ }
16
+ function lockPath() {
17
+ return join(agentStateDir(), 'workspace-index.lock');
18
+ }
19
+ function recentDirsPath() {
20
+ return join(agentStateDir(), 'recent-dirs.json');
21
+ }
22
+ function metadataPath() {
23
+ return join(agentStateDir(), 'session-metadata.json');
24
+ }
25
+ function activeSessionsPath() {
26
+ return join(agentStateDir(), 'active-sessions.json');
27
+ }
28
+ function sessionsPath() {
29
+ return join(agentStateDir(), 'sessions');
30
+ }
31
+ function outboxPath() {
32
+ return join(agentStateDir(), 'workspace-index-outbox');
33
+ }
34
+ function cwdHash(cwd) {
35
+ return createHash('sha1').update(cwd).digest('hex').slice(0, 12);
36
+ }
37
+ function processIsAlive(pid) {
38
+ if (!Number.isInteger(pid) || pid <= 0)
39
+ return false;
40
+ try {
41
+ process.kill(pid, 0);
42
+ return true;
43
+ }
44
+ catch (error) {
45
+ return error.code === 'EPERM';
46
+ }
47
+ }
48
+ async function acquireLock() {
49
+ const path = lockPath();
50
+ const ownerFile = join(path, 'owner.json');
51
+ const startedAt = Date.now();
52
+ await mkdir(agentStateDir(), { recursive: true, mode: 0o700 });
53
+ while (true) {
54
+ try {
55
+ await mkdir(path, { mode: 0o700 });
56
+ await writeFile(ownerFile, JSON.stringify({ pid: process.pid, createdAt: Date.now() }), { mode: 0o600 });
57
+ return async () => { await rm(path, { recursive: true, force: true }); };
58
+ }
59
+ catch (error) {
60
+ if (error.code !== 'EEXIST')
61
+ throw error;
62
+ let stale = false;
63
+ try {
64
+ const owner = JSON.parse(await readFile(ownerFile, 'utf8'));
65
+ stale = typeof owner.pid === 'number' && !processIsAlive(owner.pid);
66
+ }
67
+ catch {
68
+ try {
69
+ stale = Date.now() - (await stat(path)).mtimeMs > LOCK_STALE_MS;
70
+ }
71
+ catch {
72
+ continue;
73
+ }
74
+ }
75
+ if (stale) {
76
+ await rm(path, { recursive: true, force: true });
77
+ continue;
78
+ }
79
+ if (Date.now() - startedAt > LOCK_WAIT_MS)
80
+ throw new Error('等待工作区索引锁超时');
81
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 25));
82
+ }
83
+ }
84
+ }
85
+ function enqueueMutation(mutate) {
86
+ const run = async () => {
87
+ const release = await acquireLock();
88
+ try {
89
+ return await mutate();
90
+ }
91
+ finally {
92
+ await release();
93
+ }
94
+ };
95
+ const operation = mutationQueue.then(run, run);
96
+ mutationQueue = operation.then(() => undefined, () => undefined);
97
+ return operation;
98
+ }
99
+ async function volumeRemountCandidates(displayPath) {
100
+ const match = displayPath.match(/^\/Volumes\/([^/]+)(\/.*)?$/);
101
+ if (!match)
102
+ return [];
103
+ const requestedName = match[1] ?? '';
104
+ const suffix = match[2] ?? '';
105
+ const baseName = requestedName.replace(/ \d+$/, '');
106
+ let names = [];
107
+ try {
108
+ names = await readdir('/Volumes');
109
+ }
110
+ catch {
111
+ return [];
112
+ }
113
+ return names
114
+ .filter((name) => name.replace(/ \d+$/, '') === baseName && name !== requestedName)
115
+ .sort((a, b) => Number(a.includes(' ')) - Number(b.includes(' ')))
116
+ .map((name) => `/Volumes/${name}${suffix}`);
117
+ }
118
+ async function canonicalize(path) {
119
+ const displayPath = resolve(path);
120
+ for (const candidate of [displayPath, ...(await volumeRemountCandidates(displayPath))]) {
121
+ try {
122
+ const canonicalPath = await realpath(candidate);
123
+ if (!(await stat(canonicalPath)).isDirectory())
124
+ continue;
125
+ return { canonicalPath, displayPath: candidate, available: true };
126
+ }
127
+ catch { /* Try another spelling of the same removable volume. */ }
128
+ }
129
+ return { canonicalPath: displayPath, displayPath, available: false };
130
+ }
131
+ function normalizeSession(value) {
132
+ if (!value || typeof value !== 'object')
133
+ return null;
134
+ const raw = value;
135
+ if (typeof raw.id !== 'string')
136
+ return null;
137
+ try {
138
+ validateSessionId(raw.id);
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ const now = Date.now();
144
+ return {
145
+ id: raw.id,
146
+ title: typeof raw.title === 'string' && raw.title.trim() ? raw.title : raw.id === 'default' ? '默认会话' : raw.id,
147
+ createdAt: typeof raw.createdAt === 'number' ? raw.createdAt : now,
148
+ updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : now,
149
+ ...(typeof raw.lastMessagePreview === 'string' ? { lastMessagePreview: raw.lastMessagePreview } : {}),
150
+ ...(typeof raw.modelId === 'string' && raw.modelId.trim() ? { modelId: raw.modelId.trim() } : {}),
151
+ };
152
+ }
153
+ function normalizeIndex(value) {
154
+ if (!value || typeof value !== 'object')
155
+ return null;
156
+ const raw = value;
157
+ if (raw.version !== INDEX_VERSION || !Array.isArray(raw.workspaces))
158
+ return null;
159
+ const workspaces = raw.workspaces.flatMap((item) => {
160
+ if (!item || typeof item !== 'object')
161
+ return [];
162
+ const workspace = item;
163
+ if (typeof workspace.id !== 'string' || typeof workspace.canonicalPath !== 'string')
164
+ return [];
165
+ const displayPath = typeof workspace.displayPath === 'string' ? workspace.displayPath : workspace.canonicalPath;
166
+ return [{
167
+ id: workspace.id,
168
+ canonicalPath: resolve(workspace.canonicalPath),
169
+ displayPath: resolve(displayPath),
170
+ displayName: typeof workspace.displayName === 'string' ? workspace.displayName : basename(displayPath),
171
+ pathAliases: Array.isArray(workspace.pathAliases)
172
+ ? [...new Set(workspace.pathAliases.filter((path) => typeof path === 'string').map((path) => resolve(path)))]
173
+ : [],
174
+ available: workspace.available !== false,
175
+ lastActiveAt: typeof workspace.lastActiveAt === 'number' ? workspace.lastActiveAt : 0,
176
+ ...(typeof workspace.activeSessionId === 'string' ? { activeSessionId: workspace.activeSessionId } : {}),
177
+ sessions: Array.isArray(workspace.sessions) ? workspace.sessions.flatMap((session) => {
178
+ const normalized = normalizeSession(session);
179
+ return normalized ? [normalized] : [];
180
+ }) : [],
181
+ }];
182
+ });
183
+ return {
184
+ version: INDEX_VERSION,
185
+ revision: typeof raw.revision === 'number' ? raw.revision : 0,
186
+ workspaces,
187
+ recoverable: Array.isArray(raw.recoverable) ? raw.recoverable : [],
188
+ migratedAt: typeof raw.migratedAt === 'number' ? raw.migratedAt : Date.now(),
189
+ };
190
+ }
191
+ async function readIndexIfPresent() {
192
+ try {
193
+ const parsed = JSON.parse(await readFile(indexPath(), 'utf8'));
194
+ const index = normalizeIndex(parsed);
195
+ if (!index)
196
+ throw new Error('工作区索引格式无效');
197
+ return index;
198
+ }
199
+ catch (error) {
200
+ if (error.code === 'ENOENT')
201
+ return null;
202
+ throw new Error(`无法读取工作区索引:${error instanceof Error ? error.message : String(error)}`);
203
+ }
204
+ }
205
+ async function writeIndex(index) {
206
+ const file = indexPath();
207
+ await mkdir(dirname(file), { recursive: true, mode: 0o700 });
208
+ const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
209
+ const persisted = {
210
+ ...index,
211
+ workspaces: index.workspaces.map((workspace) => ({ ...workspace })),
212
+ };
213
+ try {
214
+ await writeFile(temp, `${JSON.stringify(persisted, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
215
+ await rename(temp, file);
216
+ }
217
+ finally {
218
+ await unlink(temp).catch(() => { });
219
+ }
220
+ }
221
+ async function readJsonObject(path) {
222
+ try {
223
+ const value = JSON.parse(await readFile(path, 'utf8'));
224
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
225
+ }
226
+ catch {
227
+ return {};
228
+ }
229
+ }
230
+ function isTemporaryWorkspace(path) {
231
+ const normalized = path.replace(/\\/g, '/').toLowerCase();
232
+ const internalRoot = resolve(agentStateDir());
233
+ const insideAgentState = relative(internalRoot, resolve(path));
234
+ return normalized.includes('/cli-e2e-')
235
+ || normalized.includes('/cron-workflow-cwd-')
236
+ || isInternalWorkspace(path, normalized, insideAgentState);
237
+ }
238
+ function isNavigationTestWorkspace(path) {
239
+ const normalized = path.replace(/\\/g, '/').toLowerCase();
240
+ return normalized.includes('/cli-e2e-')
241
+ || normalized.includes('/cron-workflow-cwd-')
242
+ || normalized.includes('/run-agent-turn-code-');
243
+ }
244
+ function isInternalWorkspace(path, normalized = path.replace(/\\/g, '/').toLowerCase(), insideAgentState) {
245
+ const relativeToState = insideAgentState ?? relative(resolve(agentStateDir()), resolve(path));
246
+ // !isAbsolute:Windows 上跨盘(工作区在 D:、agent home 在 C:)时 path.relative 返回
247
+ // 目标绝对路径(不带 ..),会被下面的「不以 .. 开头 = 在 agent 目录内」误判成内部目录
248
+ // → 该盘工作区被静默丢弃(真机事故:home C:\ + 项目 D:\ 的用户索引里看不到项目)。
249
+ return normalized.includes('/.minimal-agent/gateway/')
250
+ || normalized.endsWith('/.minimal-agent/plain-chat-workspace')
251
+ || (relativeToState !== '' && relativeToState !== '..'
252
+ && !relativeToState.startsWith(`..${sep}`) && !isAbsolute(relativeToState));
253
+ }
254
+ async function readPrefix(path) {
255
+ const file = await open(path, 'r');
256
+ try {
257
+ const buffer = Buffer.alloc(HISTORY_PREFIX_LIMIT);
258
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
259
+ return buffer.subarray(0, bytesRead).toString('utf8');
260
+ }
261
+ finally {
262
+ await file.close();
263
+ }
264
+ }
265
+ function cwdFromHistoryPrefix(raw) {
266
+ const match = raw.match(/当前工作目录:\s*([^\\\n\r"]+)/);
267
+ return match?.[1]?.trim() || null;
268
+ }
269
+ function sessionIdFromFile(name) {
270
+ const match = name.match(/--([^/]+)\.json$/);
271
+ const id = match?.[1] ?? 'default';
272
+ try {
273
+ return validateSessionId(id);
274
+ }
275
+ catch {
276
+ return null;
277
+ }
278
+ }
279
+ function workspaceIdFromStableFile(name) {
280
+ const match = name.match(/^workspace-([a-zA-Z0-9._-]+)--[^/]+\.json$/);
281
+ return match?.[1] ?? null;
282
+ }
283
+ function fileMatchesWorkspace(fileName, cwd, sessionId) {
284
+ if (workspaceIdFromStableFile(fileName))
285
+ return true;
286
+ const expected = basename(legacyPathSessionFileFor(cwd, sessionId));
287
+ if (fileName === expected)
288
+ return true;
289
+ return sessionId === 'default' && fileName === expected.replace(/--default\.json$/, '.json');
290
+ }
291
+ function mergeSession(workspace, session) {
292
+ const previous = workspace.sessions.find((item) => item.id === session.id);
293
+ if (!previous)
294
+ workspace.sessions.push(session);
295
+ else
296
+ Object.assign(previous, session, { createdAt: Math.min(previous.createdAt, session.createdAt) });
297
+ workspace.sessions.sort((a, b) => {
298
+ if (a.id === 'default')
299
+ return -1;
300
+ if (b.id === 'default')
301
+ return 1;
302
+ return a.id.localeCompare(b.id);
303
+ });
304
+ }
305
+ async function legacyRecovery() {
306
+ const now = Date.now();
307
+ const workspaces = new Map();
308
+ const recoverable = [];
309
+ const metadata = await readJsonObject(metadataPath());
310
+ const active = await readJsonObject(activeSessionsPath());
311
+ const ensureWorkspace = async (path, lastActiveAt = 0, preferredId) => {
312
+ const resolved = await canonicalize(path);
313
+ const key = resolved.canonicalPath;
314
+ let workspace = workspaces.get(key);
315
+ if (!workspace) {
316
+ workspace = {
317
+ id: preferredId ?? randomUUID(),
318
+ canonicalPath: resolved.canonicalPath,
319
+ displayPath: resolved.displayPath,
320
+ displayName: basename(resolved.displayPath) || resolved.displayPath,
321
+ available: resolved.available,
322
+ lastActiveAt,
323
+ sessions: [],
324
+ };
325
+ workspaces.set(key, workspace);
326
+ }
327
+ else {
328
+ workspace.lastActiveAt = Math.max(workspace.lastActiveAt, lastActiveAt);
329
+ }
330
+ return workspace;
331
+ };
332
+ try {
333
+ const recent = JSON.parse(await readFile(recentDirsPath(), 'utf8'));
334
+ if (Array.isArray(recent)) {
335
+ for (const value of recent) {
336
+ if (!value || typeof value !== 'object' || typeof value.path !== 'string')
337
+ continue;
338
+ const raw = value;
339
+ const workspace = await ensureWorkspace(raw.path, typeof raw.lastUsedAt === 'number' ? raw.lastUsedAt : 0);
340
+ if (typeof raw.name === 'string')
341
+ workspace.displayName = raw.name;
342
+ }
343
+ }
344
+ }
345
+ catch { /* Session scan can still recover workspaces. */ }
346
+ let files = [];
347
+ try {
348
+ files = (await readdir(sessionsPath())).filter((name) => name.endsWith('.json') && !name.includes('.tmp'));
349
+ }
350
+ catch { }
351
+ for (const fileName of files) {
352
+ const sessionId = sessionIdFromFile(fileName);
353
+ if (!sessionId) {
354
+ recoverable.push({ fileName, reason: 'invalid_session' });
355
+ continue;
356
+ }
357
+ let cwd = null;
358
+ try {
359
+ cwd = cwdFromHistoryPrefix(await readPrefix(join(sessionsPath(), fileName)));
360
+ }
361
+ catch { }
362
+ if (!cwd) {
363
+ recoverable.push({ fileName, reason: 'missing_cwd' });
364
+ continue;
365
+ }
366
+ if (isTemporaryWorkspace(cwd)) {
367
+ recoverable.push({ fileName, reason: 'temporary', suggestedPath: cwd });
368
+ continue;
369
+ }
370
+ if (!fileMatchesWorkspace(fileName, cwd, sessionId)) {
371
+ recoverable.push({ fileName, reason: 'hash_mismatch', suggestedPath: cwd });
372
+ continue;
373
+ }
374
+ const info = await stat(join(sessionsPath(), fileName));
375
+ const workspace = await ensureWorkspace(cwd, info.mtimeMs, workspaceIdFromStableFile(fileName) ?? undefined);
376
+ const hash = cwdHash(cwd);
377
+ const group = metadata[hash] && typeof metadata[hash] === 'object' ? metadata[hash] : {};
378
+ const stored = normalizeSession(group[sessionId]);
379
+ mergeSession(workspace, stored ?? {
380
+ id: sessionId,
381
+ title: sessionId === 'default' ? '默认会话' : sessionId,
382
+ createdAt: info.birthtimeMs || info.mtimeMs,
383
+ updatedAt: info.mtimeMs,
384
+ });
385
+ if (typeof active[hash] === 'string')
386
+ workspace.activeSessionId = active[hash];
387
+ }
388
+ return {
389
+ version: INDEX_VERSION,
390
+ revision: 1,
391
+ workspaces: [...workspaces.values()].sort((a, b) => a.displayName.localeCompare(b.displayName, 'zh-CN')),
392
+ recoverable,
393
+ migratedAt: now,
394
+ };
395
+ }
396
+ async function backupLegacyFiles() {
397
+ const backupDir = join(agentStateDir(), 'workspace-index-migration-v1');
398
+ await mkdir(backupDir, { recursive: true, mode: 0o700 });
399
+ for (const source of [recentDirsPath(), metadataPath(), activeSessionsPath()]) {
400
+ await copyFile(source, join(backupDir, basename(source)), constants.COPYFILE_EXCL).catch(() => { });
401
+ }
402
+ }
403
+ async function backupCorruptIndex(error) {
404
+ const source = indexPath();
405
+ const target = `${source}.corrupt-${Date.now()}`;
406
+ try {
407
+ await copyFile(source, target, constants.COPYFILE_EXCL);
408
+ await writeFile(`${target}.reason.txt`, error instanceof Error ? error.message : String(error), { mode: 0o600 });
409
+ }
410
+ catch {
411
+ // If the file disappeared between read and backup, legacy recovery is still safe.
412
+ }
413
+ }
414
+ function addAlias(workspace, path) {
415
+ const alias = resolve(path);
416
+ const aliases = new Set(workspace.pathAliases ?? []);
417
+ if (alias !== workspace.canonicalPath && alias !== workspace.displayPath)
418
+ aliases.add(alias);
419
+ workspace.pathAliases = [...aliases];
420
+ }
421
+ async function applyPendingMutation(index, mutation) {
422
+ const resolved = await canonicalize(mutation.path);
423
+ const workspace = upsertWorkspace(index, resolved, mutation.path);
424
+ if (mutation.type === 'touch') {
425
+ mergeSession(workspace, mutation.session);
426
+ workspace.lastActiveAt = Math.max(workspace.lastActiveAt, mutation.session.updatedAt);
427
+ }
428
+ else if (mutation.type === 'active') {
429
+ workspace.activeSessionId = validateSessionId(mutation.sessionId);
430
+ workspace.lastActiveAt = Math.max(workspace.lastActiveAt, mutation.createdAt);
431
+ }
432
+ else {
433
+ workspace.sessions = workspace.sessions.filter((session) => session.id !== mutation.sessionId);
434
+ if (workspace.activeSessionId === mutation.sessionId)
435
+ workspace.activeSessionId = workspace.sessions[0]?.id;
436
+ }
437
+ }
438
+ async function applyOutbox(index) {
439
+ let files = [];
440
+ try {
441
+ files = (await readdir(outboxPath())).filter((name) => name.endsWith('.json'));
442
+ }
443
+ catch {
444
+ return [];
445
+ }
446
+ const applied = [];
447
+ for (const name of files.sort()) {
448
+ const file = join(outboxPath(), name);
449
+ try {
450
+ const mutation = JSON.parse(await readFile(file, 'utf8'));
451
+ if (!mutation || typeof mutation.path !== 'string' || typeof mutation.type !== 'string')
452
+ throw new Error('invalid outbox record');
453
+ await applyPendingMutation(index, mutation);
454
+ applied.push(file);
455
+ }
456
+ catch (error) {
457
+ try {
458
+ process.stderr.write(`[minimal-agent] 工作区索引补写失败:${error instanceof Error ? error.message : String(error)}\n`);
459
+ }
460
+ catch { }
461
+ }
462
+ }
463
+ return applied;
464
+ }
465
+ async function acknowledgeOutbox(files) {
466
+ await Promise.all(files.map((file) => unlink(file).catch(() => { })));
467
+ }
468
+ async function ensureIndexUnlocked() {
469
+ let existing = null;
470
+ try {
471
+ existing = await readIndexIfPresent();
472
+ }
473
+ catch (error) {
474
+ await backupCorruptIndex(error);
475
+ }
476
+ if (existing) {
477
+ const reconciled = await reconcileExistingIndex(existing);
478
+ const applied = await applyOutbox(reconciled);
479
+ if (applied.length) {
480
+ reconciled.revision += 1;
481
+ await writeIndex(reconciled);
482
+ await acknowledgeOutbox(applied);
483
+ }
484
+ return reconciled;
485
+ }
486
+ await backupLegacyFiles();
487
+ const recovered = await legacyRecovery();
488
+ const applied = await applyOutbox(recovered);
489
+ await writeIndex(recovered);
490
+ await acknowledgeOutbox(applied);
491
+ return recovered;
492
+ }
493
+ async function reconcileExistingIndex(index) {
494
+ const before = JSON.stringify(index.workspaces);
495
+ const merged = new Map();
496
+ const recoverableKeys = new Set(index.recoverable.map((item) => `${item.fileName}:${item.reason}`));
497
+ for (const workspace of index.workspaces) {
498
+ if (isInternalWorkspace(workspace.displayPath))
499
+ continue;
500
+ if (isNavigationTestWorkspace(workspace.displayPath) || isNavigationTestWorkspace(workspace.canonicalPath)) {
501
+ for (const session of workspace.sessions) {
502
+ const fileName = `workspace-${workspace.id}--${session.id}.json`;
503
+ const key = `${fileName}:temporary`;
504
+ if (recoverableKeys.has(key))
505
+ continue;
506
+ index.recoverable.push({
507
+ fileName,
508
+ reason: 'temporary',
509
+ suggestedPath: workspace.displayPath,
510
+ });
511
+ recoverableKeys.add(key);
512
+ }
513
+ continue;
514
+ }
515
+ const resolved = await canonicalize(workspace.displayPath);
516
+ const normalized = {
517
+ ...workspace,
518
+ canonicalPath: resolved.canonicalPath,
519
+ displayPath: resolved.displayPath,
520
+ displayName: basename(resolved.displayPath) || workspace.displayName,
521
+ available: resolved.available,
522
+ pathAliases: [...new Set([
523
+ ...(workspace.pathAliases ?? []),
524
+ workspace.canonicalPath,
525
+ workspace.displayPath,
526
+ ].map((path) => resolve(path)).filter((path) => path !== resolved.canonicalPath && path !== resolved.displayPath))],
527
+ sessions: [...workspace.sessions],
528
+ };
529
+ const current = merged.get(normalized.canonicalPath);
530
+ if (!current) {
531
+ merged.set(normalized.canonicalPath, normalized);
532
+ continue;
533
+ }
534
+ for (const session of normalized.sessions)
535
+ mergeSession(current, session);
536
+ for (const alias of normalized.pathAliases ?? [])
537
+ addAlias(current, alias);
538
+ if (normalized.lastActiveAt > current.lastActiveAt) {
539
+ current.lastActiveAt = normalized.lastActiveAt;
540
+ current.activeSessionId = normalized.activeSessionId ?? current.activeSessionId;
541
+ current.displayPath = normalized.displayPath;
542
+ current.displayName = normalized.displayName;
543
+ }
544
+ current.available ||= normalized.available;
545
+ }
546
+ index.workspaces = [...merged.values()].sort((a, b) => a.displayName.localeCompare(b.displayName, 'zh-CN'));
547
+ if (before !== JSON.stringify(index.workspaces)) {
548
+ index.revision += 1;
549
+ await writeIndex(index);
550
+ }
551
+ return index;
552
+ }
553
+ async function refreshAvailability(index) {
554
+ const workspaces = await Promise.all(index.workspaces.map(async (workspace) => {
555
+ try {
556
+ return { ...workspace, available: (await stat(workspace.canonicalPath)).isDirectory() };
557
+ }
558
+ catch {
559
+ return { ...workspace, available: false };
560
+ }
561
+ }));
562
+ // 已登记工作区是用户导航数据,即使当前离线且尚无 Session 也必须保留。
563
+ // 外接盘暂时卸载不等于用户要求移除工作区;删除只能走显式 removeWorkspace。
564
+ const changed = workspaces.some((workspace, indexPosition) => workspace.available !== index.workspaces[indexPosition]?.available);
565
+ if (changed) {
566
+ index.workspaces = workspaces.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
567
+ index.revision += 1;
568
+ await writeIndex(index);
569
+ }
570
+ return { ...index, workspaces };
571
+ }
572
+ async function mutate(fn) {
573
+ return enqueueMutation(async () => {
574
+ const index = await ensureIndexUnlocked();
575
+ const result = await fn(index);
576
+ for (const workspace of index.workspaces) {
577
+ workspace.sessions.sort((a, b) => {
578
+ if (a.id === 'default')
579
+ return -1;
580
+ if (b.id === 'default')
581
+ return 1;
582
+ return a.id.localeCompare(b.id);
583
+ });
584
+ }
585
+ index.workspaces.sort((a, b) => a.displayName.localeCompare(b.displayName, 'zh-CN'));
586
+ index.revision += 1;
587
+ await writeIndex(index);
588
+ return result;
589
+ });
590
+ }
591
+ function upsertWorkspace(index, resolved, requestedPath) {
592
+ const candidates = new Set([resolved.canonicalPath, resolved.displayPath, ...(requestedPath ? [resolve(requestedPath)] : [])]);
593
+ let workspace = index.workspaces.find((item) => [
594
+ item.canonicalPath,
595
+ item.displayPath,
596
+ ...(item.pathAliases ?? []),
597
+ ].some((path) => candidates.has(path)));
598
+ if (!workspace) {
599
+ workspace = {
600
+ id: randomUUID(),
601
+ canonicalPath: resolved.canonicalPath,
602
+ displayPath: resolved.displayPath,
603
+ displayName: basename(resolved.displayPath) || resolved.displayPath,
604
+ pathAliases: requestedPath ? [resolve(requestedPath)].filter((path) => path !== resolved.displayPath) : [],
605
+ available: resolved.available,
606
+ lastActiveAt: Date.now(),
607
+ sessions: [],
608
+ };
609
+ index.workspaces.push(workspace);
610
+ }
611
+ else {
612
+ addAlias(workspace, workspace.canonicalPath);
613
+ addAlias(workspace, workspace.displayPath);
614
+ if (requestedPath)
615
+ addAlias(workspace, requestedPath);
616
+ workspace.canonicalPath = resolved.canonicalPath;
617
+ workspace.displayPath = resolved.displayPath;
618
+ workspace.available = resolved.available;
619
+ workspace.lastActiveAt = Date.now();
620
+ }
621
+ return workspace;
622
+ }
623
+ export const workspaceIndexService = {
624
+ async recordPendingMutation(mutation) {
625
+ await mkdir(outboxPath(), { recursive: true, mode: 0o700 });
626
+ const target = join(outboxPath(), `${Date.now()}-${process.pid}-${randomUUID()}.json`);
627
+ const temp = `${target}.tmp`;
628
+ try {
629
+ await writeFile(temp, `${JSON.stringify({ ...mutation, createdAt: Date.now() })}\n`, { mode: 0o600 });
630
+ await rename(temp, target);
631
+ }
632
+ finally {
633
+ await unlink(temp).catch(() => { });
634
+ }
635
+ },
636
+ async list() {
637
+ return enqueueMutation(async () => {
638
+ const index = await ensureIndexUnlocked();
639
+ return (await refreshAvailability(index)).workspaces.sort((a, b) => a.displayName.localeCompare(b.displayName, 'zh-CN'));
640
+ });
641
+ },
642
+ async snapshot() {
643
+ return enqueueMutation(async () => {
644
+ const index = await ensureIndexUnlocked();
645
+ return refreshAvailability(index);
646
+ });
647
+ },
648
+ async register(path, title) {
649
+ const resolved = await canonicalize(path);
650
+ if (!resolved.available)
651
+ throw new Error('所选工作目录不存在或不可读取');
652
+ return mutate((index) => {
653
+ const workspace = upsertWorkspace(index, resolved, path);
654
+ if (title && !workspace.sessions.length)
655
+ workspace.displayName ||= title.slice(0, 80);
656
+ return { ...workspace, sessions: [...workspace.sessions] };
657
+ });
658
+ },
659
+ async findByPath(path) {
660
+ const resolved = await canonicalize(path);
661
+ const workspaces = await workspaceIndexService.list();
662
+ const requested = resolve(path);
663
+ return workspaces.find((item) => [item.canonicalPath, item.displayPath, ...(item.pathAliases ?? [])]
664
+ .some((candidate) => candidate === resolved.canonicalPath || candidate === resolved.displayPath || candidate === requested)) ?? null;
665
+ },
666
+ async touchSession(path, session) {
667
+ if (isTemporaryWorkspace(path))
668
+ return;
669
+ const resolved = await canonicalize(path);
670
+ await mutate((index) => {
671
+ const current = upsertWorkspace(index, resolved, path);
672
+ mergeSession(current, session);
673
+ current.lastActiveAt = Math.max(current.lastActiveAt, session.updatedAt);
674
+ });
675
+ },
676
+ async setActive(path, sessionId) {
677
+ if (isTemporaryWorkspace(path))
678
+ return;
679
+ const id = validateSessionId(sessionId);
680
+ const resolved = await canonicalize(path);
681
+ await mutate((index) => {
682
+ const current = upsertWorkspace(index, resolved, path);
683
+ current.activeSessionId = id;
684
+ });
685
+ },
686
+ async removeSession(path, sessionId) {
687
+ const workspace = await workspaceIndexService.findByPath(path);
688
+ if (!workspace)
689
+ return;
690
+ await mutate((index) => {
691
+ const current = index.workspaces.find((item) => item.id === workspace.id);
692
+ if (!current)
693
+ return;
694
+ current.sessions = current.sessions.filter((session) => session.id !== sessionId);
695
+ if (current.activeSessionId === sessionId)
696
+ current.activeSessionId = current.sessions[0]?.id;
697
+ });
698
+ },
699
+ async removeWorkspace(pathOrId) {
700
+ const resolved = resolve(pathOrId);
701
+ await mutate((index) => {
702
+ index.workspaces = index.workspaces.filter((item) => item.id !== pathOrId && item.canonicalPath !== resolved && item.displayPath !== resolved);
703
+ });
704
+ },
705
+ async recoverLegacy() {
706
+ return mutate(async (index) => {
707
+ const recovered = await legacyRecovery();
708
+ for (const candidate of recovered.workspaces) {
709
+ const current = index.workspaces.find((item) => item.canonicalPath === candidate.canonicalPath);
710
+ if (!current)
711
+ index.workspaces.push(candidate);
712
+ else {
713
+ for (const session of candidate.sessions)
714
+ mergeSession(current, session);
715
+ current.lastActiveAt = Math.max(current.lastActiveAt, candidate.lastActiveAt);
716
+ current.available ||= candidate.available;
717
+ }
718
+ }
719
+ const known = new Set(index.recoverable.map((item) => `${item.fileName}:${item.reason}`));
720
+ for (const item of recovered.recoverable) {
721
+ if (!known.has(`${item.fileName}:${item.reason}`))
722
+ index.recoverable.push(item);
723
+ }
724
+ return {
725
+ workspaces: recovered.workspaces.length,
726
+ sessions: recovered.workspaces.reduce((sum, workspace) => sum + workspace.sessions.length, 0),
727
+ recoverable: recovered.recoverable,
728
+ };
729
+ });
730
+ },
731
+ };