dsh-openwrite 0.2.8

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 (275) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +107 -0
  3. package/THIRD_PARTY_NOTICES.md +33 -0
  4. package/assets/logo-dark.svg +31 -0
  5. package/assets/logo-light.svg +31 -0
  6. package/cordis.patch.yml +11 -0
  7. package/docs/BENCHMARK_TASKS.md +54 -0
  8. package/docs/COST_MEASUREMENT.md +39 -0
  9. package/docs/DEVELOPMENT.md +65 -0
  10. package/docs/DSH_STORE.md +58 -0
  11. package/docs/FRAMEWORK_ADVANTAGES.md +288 -0
  12. package/docs/IMPROVEMENT_PLAN.md +252 -0
  13. package/docs/INSTALL.md +99 -0
  14. package/docs/LEARNING.md +68 -0
  15. package/docs/OPEN_SOURCE_NOVEL_AUDIT.md +191 -0
  16. package/docs/PLUGIN_MAINTENANCE.md +54 -0
  17. package/docs/PLUGIN_MAINTENANCE_RC7.md +157 -0
  18. package/docs/QA_20260908.md +103 -0
  19. package/docs/QA_20260909_BENCHMARK.md +24 -0
  20. package/docs/RELEASE_ACCEPTANCE.md +116 -0
  21. package/docs/REVIEW_DAG_FRAMEWORK.md +82 -0
  22. package/docs/TARGETED_NOVEL_PROJECT_REVIEW.md +167 -0
  23. package/docs/WORKFLOWS.md +74 -0
  24. package/docs/WORKSPACE_CONTEXT_CONTRACT.md +125 -0
  25. package/docs/WORKSPACE_INTEGRATION_GOAL.md +278 -0
  26. package/docs/research/autosave-race-repro.mjs +133 -0
  27. package/docs/research/framework-stats-2026-09-05.json +116 -0
  28. package/dog-tools.mjs +11 -0
  29. package/package.json +132 -0
  30. package/packages/openwrite-bridge/LICENSE +201 -0
  31. package/packages/openwrite-bridge/cordis.patch.yml +10 -0
  32. package/packages/openwrite-bridge/lib/client.d.ts +95 -0
  33. package/packages/openwrite-bridge/lib/client.js +193 -0
  34. package/packages/openwrite-bridge/lib/contracts-generated.d.ts +367 -0
  35. package/packages/openwrite-bridge/lib/contracts-generated.js +1764 -0
  36. package/packages/openwrite-bridge/lib/dog-delivery.d.ts +12 -0
  37. package/packages/openwrite-bridge/lib/dog-delivery.js +362 -0
  38. package/packages/openwrite-bridge/lib/dog-review.d.ts +50 -0
  39. package/packages/openwrite-bridge/lib/dog-review.js +470 -0
  40. package/packages/openwrite-bridge/lib/domain.d.ts +70 -0
  41. package/packages/openwrite-bridge/lib/domain.js +496 -0
  42. package/packages/openwrite-bridge/lib/index.d.ts +17 -0
  43. package/packages/openwrite-bridge/lib/index.js +89 -0
  44. package/packages/openwrite-bridge/lib/managed-runtime.d.ts +95 -0
  45. package/packages/openwrite-bridge/lib/managed-runtime.js +510 -0
  46. package/packages/openwrite-bridge/lib/preset-tools.d.ts +13 -0
  47. package/packages/openwrite-bridge/lib/preset-tools.js +18 -0
  48. package/packages/openwrite-bridge/lib/tool-policy.d.ts +6 -0
  49. package/packages/openwrite-bridge/lib/tool-policy.js +56 -0
  50. package/packages/openwrite-bridge/lib/tools.d.ts +24 -0
  51. package/packages/openwrite-bridge/lib/tools.js +2404 -0
  52. package/packages/openwrite-bridge/package.json +67 -0
  53. package/packages/studio-panel/LICENSE +21 -0
  54. package/packages/studio-panel/README.md +51 -0
  55. package/packages/studio-panel/cordis.patch.yml +5 -0
  56. package/packages/studio-panel/lib/client.js +158087 -0
  57. package/packages/studio-panel/lib/client.js.map +1 -0
  58. package/packages/studio-panel/lib/index.js +50 -0
  59. package/packages/studio-panel/lib/types/client/AssetEditor.d.ts +122 -0
  60. package/packages/studio-panel/lib/types/client/AssetsView.d.ts +43 -0
  61. package/packages/studio-panel/lib/types/client/BenchmarkView.d.ts +9 -0
  62. package/packages/studio-panel/lib/types/client/ContinuousReader.d.ts +13 -0
  63. package/packages/studio-panel/lib/types/client/CreationView.d.ts +6 -0
  64. package/packages/studio-panel/lib/types/client/DagCanvas.d.ts +11 -0
  65. package/packages/studio-panel/lib/types/client/DiscardDraftDialog.d.ts +8 -0
  66. package/packages/studio-panel/lib/types/client/DomainToolCard.d.ts +9 -0
  67. package/packages/studio-panel/lib/types/client/GraphView.d.ts +46 -0
  68. package/packages/studio-panel/lib/types/client/HeaderChrome.d.ts +18 -0
  69. package/packages/studio-panel/lib/types/client/LaunchOpenWrite.d.ts +13 -0
  70. package/packages/studio-panel/lib/types/client/LibraryView.d.ts +6 -0
  71. package/packages/studio-panel/lib/types/client/MarkdownText.d.ts +3 -0
  72. package/packages/studio-panel/lib/types/client/ModelView.d.ts +9 -0
  73. package/packages/studio-panel/lib/types/client/OperationsView.d.ts +6 -0
  74. package/packages/studio-panel/lib/types/client/OutlinePlans.d.ts +37 -0
  75. package/packages/studio-panel/lib/types/client/OutlineView.d.ts +29 -0
  76. package/packages/studio-panel/lib/types/client/ResearchView.d.ts +7 -0
  77. package/packages/studio-panel/lib/types/client/ReviewCard.d.ts +28 -0
  78. package/packages/studio-panel/lib/types/client/SceneWorkbench.d.ts +55 -0
  79. package/packages/studio-panel/lib/types/client/SearchView.d.ts +33 -0
  80. package/packages/studio-panel/lib/types/client/TasksView.d.ts +11 -0
  81. package/packages/studio-panel/lib/types/client/TransferWorkspaces.d.ts +19 -0
  82. package/packages/studio-panel/lib/types/client/TurnMutationSummary.d.ts +62 -0
  83. package/packages/studio-panel/lib/types/client/VditorBody.d.ts +55 -0
  84. package/packages/studio-panel/lib/types/client/WorkbenchStore.d.ts +121 -0
  85. package/packages/studio-panel/lib/types/client/api.d.ts +72 -0
  86. package/packages/studio-panel/lib/types/client/asset-drafts.d.ts +20 -0
  87. package/packages/studio-panel/lib/types/client/benchmark-options.d.ts +43 -0
  88. package/packages/studio-panel/lib/types/client/draft-store.d.ts +26 -0
  89. package/packages/studio-panel/lib/types/client/dto.d.ts +517 -0
  90. package/packages/studio-panel/lib/types/client/index.d.ts +4 -0
  91. package/packages/studio-panel/lib/types/client/locales.d.ts +15 -0
  92. package/packages/studio-panel/lib/types/client/manuscript-annotations.d.ts +59 -0
  93. package/packages/studio-panel/lib/types/client/manuscript-markers.d.ts +61 -0
  94. package/packages/studio-panel/lib/types/client/manuscript-mentions.d.ts +30 -0
  95. package/packages/studio-panel/lib/types/client/manuscript-overlay-dom.d.ts +7 -0
  96. package/packages/studio-panel/lib/types/client/manuscript-overlay.d.ts +42 -0
  97. package/packages/studio-panel/lib/types/client/manuscript-selection.d.ts +59 -0
  98. package/packages/studio-panel/lib/types/client/model-view-memory.d.ts +17 -0
  99. package/packages/studio-panel/lib/types/client/storage.d.ts +8 -0
  100. package/packages/studio-panel/lib/types/client/workbench-epochs.d.ts +16 -0
  101. package/packages/studio-panel/lib/types/client/workspace-context.d.ts +52 -0
  102. package/packages/studio-panel/lib/types/client/writing-scope.d.ts +7 -0
  103. package/packages/studio-panel/lib/types/index.d.ts +5 -0
  104. package/packages/studio-panel/package.json +105 -0
  105. package/packages/studio-panel/vendor/vditor/LICENSE +21 -0
  106. package/packages/studio-panel/vendor/vditor/dist/css/content-theme/dark.css +123 -0
  107. package/packages/studio-panel/vendor/vditor/dist/css/content-theme/light.css +101 -0
  108. package/packages/studio-panel/vendor/vditor/dist/index.css +1823 -0
  109. package/packages/studio-panel/vendor/vditor/dist/index.min.js +1 -0
  110. package/packages/studio-panel/vendor/vditor/dist/js/i18n/zh_CN.js +86 -0
  111. package/packages/studio-panel/vendor/vditor/dist/js/icons/ant.js +174 -0
  112. package/packages/studio-panel/vendor/vditor/dist/js/lute/lute.min.js +72 -0
  113. package/plugin.d.ts +3 -0
  114. package/plugin.mjs +80 -0
  115. package/presets/README.md +32 -0
  116. package/presets/openwrite/agent.cordis.yml +317 -0
  117. package/presets/openwrite/preset.yml +3 -0
  118. package/presets/openwrite/skills/canon/SKILL.md +15 -0
  119. package/presets/openwrite/skills/dialoguequality/SKILL.md +166 -0
  120. package/presets/openwrite/skills/dog-delivery-query/SKILL.md +40 -0
  121. package/presets/openwrite/skills/dog-import-query/SKILL.md +27 -0
  122. package/presets/openwrite/skills/dog-review-query/SKILL.md +40 -0
  123. package/presets/openwrite/skills/export-book/SKILL.md +14 -0
  124. package/presets/openwrite/skills/foreshadow/SKILL.md +14 -0
  125. package/presets/openwrite/skills/foreshadowing-system/SKILL.md +125 -0
  126. package/presets/openwrite/skills/learn/SKILL.md +24 -0
  127. package/presets/openwrite/skills/novel-creator/SKILL.md +88 -0
  128. package/presets/openwrite/skills/novel-creator/templates/beat_templates.yaml +179 -0
  129. package/presets/openwrite/skills/novel-creator/templates/example_usage.yaml +74 -0
  130. package/presets/openwrite/skills/novel-reviewer/SKILL.md +85 -0
  131. package/presets/openwrite/skills/oh-story-deslop/LICENSE +21 -0
  132. package/presets/openwrite/skills/oh-story-deslop/SKILL.md +36 -0
  133. package/presets/openwrite/skills/oh-story-deslop/agents/openai.yaml +4 -0
  134. package/presets/openwrite/skills/oh-story-deslop/references/method.md +22 -0
  135. package/presets/openwrite/skills/oh-story-long-analyze/LICENSE +21 -0
  136. package/presets/openwrite/skills/oh-story-long-analyze/SKILL.md +37 -0
  137. package/presets/openwrite/skills/oh-story-long-analyze/agents/openai.yaml +4 -0
  138. package/presets/openwrite/skills/oh-story-long-analyze/references/method.md +22 -0
  139. package/presets/openwrite/skills/oh-story-long-scan/LICENSE +21 -0
  140. package/presets/openwrite/skills/oh-story-long-scan/SKILL.md +36 -0
  141. package/presets/openwrite/skills/oh-story-long-scan/agents/openai.yaml +4 -0
  142. package/presets/openwrite/skills/oh-story-long-scan/references/method.md +21 -0
  143. package/presets/openwrite/skills/oh-story-long-write/LICENSE +21 -0
  144. package/presets/openwrite/skills/oh-story-long-write/SKILL.md +37 -0
  145. package/presets/openwrite/skills/oh-story-long-write/agents/openai.yaml +4 -0
  146. package/presets/openwrite/skills/oh-story-long-write/references/method.md +33 -0
  147. package/presets/openwrite/skills/oh-story-review/LICENSE +21 -0
  148. package/presets/openwrite/skills/oh-story-review/SKILL.md +36 -0
  149. package/presets/openwrite/skills/oh-story-review/agents/openai.yaml +4 -0
  150. package/presets/openwrite/skills/oh-story-review/references/method.md +25 -0
  151. package/presets/openwrite/skills/progress/SKILL.md +16 -0
  152. package/presets/openwrite/skills/review-chapter/SKILL.md +17 -0
  153. package/presets/openwrite/skills/revise-span/SKILL.md +16 -0
  154. package/presets/openwrite/skills/style-system/SKILL.md +135 -0
  155. package/presets/openwrite/skills/style-system/prompts/analyze_style.md +214 -0
  156. package/presets/openwrite/skills/style-system/prompts/batch_extract_style.md +66 -0
  157. package/presets/openwrite/skills/style-system/prompts/compose_style.md +83 -0
  158. package/presets/openwrite/skills/style-system/prompts/extract_style.md +84 -0
  159. package/presets/openwrite/skills/style-system/prompts/initialize_style.md +52 -0
  160. package/presets/openwrite/skills/style-system/templates/analysis_report.yaml +78 -0
  161. package/presets/openwrite/skills/style-system/templates/composed_style.md +96 -0
  162. package/presets/openwrite/skills/style-system/templates/extraction_report.yaml +80 -0
  163. package/presets/openwrite/skills/style-system/templates/fingerprint.yaml +61 -0
  164. package/presets/openwrite/skills/style-system/tools/README.md +33 -0
  165. package/presets/openwrite/skills/truth-validation/SKILL.md +119 -0
  166. package/presets/openwrite/skills/workflow-manager/SKILL.md +81 -0
  167. package/presets/openwrite/skills/world-query/SKILL.md +45 -0
  168. package/presets/openwrite/skills/write-next/SKILL.md +17 -0
  169. package/release/core-requirements.in +12 -0
  170. package/release/licenses/@deepseek-ai__cosmokit@1.8.3/LICENSE +21 -0
  171. package/release/licenses/@deepseek-ai__schemastery@3.18.2/LICENSE +21 -0
  172. package/release/licenses/@isaacs__fs-minipass@4.0.1/LICENSE +15 -0
  173. package/release/licenses/@standard-schema__spec@1.1.0/LICENSE +21 -0
  174. package/release/licenses/@types__d3-color@3.1.3/LICENSE +21 -0
  175. package/release/licenses/@types__d3-drag@3.0.7/LICENSE +21 -0
  176. package/release/licenses/@types__d3-interpolate@3.0.4/LICENSE +21 -0
  177. package/release/licenses/@types__d3-selection@3.0.11/LICENSE +21 -0
  178. package/release/licenses/@types__d3-transition@3.0.9/LICENSE +21 -0
  179. package/release/licenses/@types__d3-zoom@3.0.8/LICENSE +21 -0
  180. package/release/licenses/@xyflow__react@12.11.6/LICENSE +21 -0
  181. package/release/licenses/@xyflow__system@0.0.82/LICENSE +21 -0
  182. package/release/licenses/ajv-formats@3.0.1/LICENSE +21 -0
  183. package/release/licenses/ajv@8.20.0/LICENSE +22 -0
  184. package/release/licenses/chownr@3.0.0/LICENSE.md +63 -0
  185. package/release/licenses/classcat@5.0.5/LICENSE.md +7 -0
  186. package/release/licenses/commander@7.2.0/LICENSE +22 -0
  187. package/release/licenses/d3-array@3.2.4/LICENSE +13 -0
  188. package/release/licenses/d3-axis@3.0.0/LICENSE +13 -0
  189. package/release/licenses/d3-brush@3.0.0/LICENSE +13 -0
  190. package/release/licenses/d3-chord@3.0.1/LICENSE +13 -0
  191. package/release/licenses/d3-color@3.1.0/LICENSE +13 -0
  192. package/release/licenses/d3-contour@4.0.2/LICENSE +13 -0
  193. package/release/licenses/d3-delaunay@6.0.4/LICENSE +14 -0
  194. package/release/licenses/d3-dispatch@3.0.1/LICENSE +13 -0
  195. package/release/licenses/d3-drag@3.0.0/LICENSE +13 -0
  196. package/release/licenses/d3-dsv@3.0.1/LICENSE +13 -0
  197. package/release/licenses/d3-ease@3.0.1/LICENSE +28 -0
  198. package/release/licenses/d3-fetch@3.0.1/LICENSE +13 -0
  199. package/release/licenses/d3-force@3.0.0/LICENSE +13 -0
  200. package/release/licenses/d3-format@3.1.2/LICENSE +13 -0
  201. package/release/licenses/d3-geo@3.1.1/LICENSE +34 -0
  202. package/release/licenses/d3-hierarchy@3.1.2/LICENSE +13 -0
  203. package/release/licenses/d3-interpolate@3.0.1/LICENSE +13 -0
  204. package/release/licenses/d3-path@3.1.0/LICENSE +13 -0
  205. package/release/licenses/d3-polygon@3.0.1/LICENSE +13 -0
  206. package/release/licenses/d3-quadtree@3.0.1/LICENSE +13 -0
  207. package/release/licenses/d3-random@3.0.1/LICENSE +13 -0
  208. package/release/licenses/d3-scale-chromatic@3.1.0/LICENSE +28 -0
  209. package/release/licenses/d3-scale@4.0.2/LICENSE +13 -0
  210. package/release/licenses/d3-selection@3.0.0/LICENSE +13 -0
  211. package/release/licenses/d3-shape@3.2.0/LICENSE +13 -0
  212. package/release/licenses/d3-time-format@4.1.0/LICENSE +13 -0
  213. package/release/licenses/d3-time@3.1.0/LICENSE +13 -0
  214. package/release/licenses/d3-timer@3.0.1/LICENSE +13 -0
  215. package/release/licenses/d3-transition@3.0.1/LICENSE +13 -0
  216. package/release/licenses/d3-zoom@3.0.0/LICENSE +13 -0
  217. package/release/licenses/d3@7.9.0/LICENSE +13 -0
  218. package/release/licenses/delaunator@5.1.0/LICENSE +15 -0
  219. package/release/licenses/elkjs@0.12.0/LICENSE.md +263 -0
  220. package/release/licenses/fast-deep-equal@3.1.3/LICENSE +21 -0
  221. package/release/licenses/fast-uri@3.1.7/LICENSE +30 -0
  222. package/release/licenses/fflate@0.8.2/LICENSE +21 -0
  223. package/release/licenses/graceful-fs@4.2.11/LICENSE +15 -0
  224. package/release/licenses/iconv-lite@0.6.3/LICENSE +21 -0
  225. package/release/licenses/internmap@2.0.3/LICENSE +13 -0
  226. package/release/licenses/json-schema-traverse@1.0.0/LICENSE +21 -0
  227. package/release/licenses/lucide-react@0.468.0/LICENSE +15 -0
  228. package/release/licenses/minipass@7.1.3/LICENSE.md +55 -0
  229. package/release/licenses/minizlib@3.1.0/LICENSE +26 -0
  230. package/release/licenses/proper-lockfile@4.1.2/LICENSE +21 -0
  231. package/release/licenses/require-from-string@2.0.2/license +21 -0
  232. package/release/licenses/retry@0.12.0/License +21 -0
  233. package/release/licenses/robust-predicates@3.0.3/LICENSE +24 -0
  234. package/release/licenses/rw@1.3.3/LICENSE +26 -0
  235. package/release/licenses/safer-buffer@2.1.2/LICENSE +21 -0
  236. package/release/licenses/signal-exit@3.0.7/LICENSE.txt +16 -0
  237. package/release/licenses/tar@7.5.7/LICENSE.md +55 -0
  238. package/release/licenses/use-sync-external-store@1.6.0/LICENSE +21 -0
  239. package/release/licenses/yallist@5.0.0/LICENSE.md +63 -0
  240. package/release/licenses/zustand@4.5.7/LICENSE +21 -0
  241. package/release/openwrite-5.8.1-py3-none-any.whl +0 -0
  242. package/release/platform-overrides.in +5 -0
  243. package/release/python-constraints.in +96 -0
  244. package/release/requirements.lock +2927 -0
  245. package/release/runtime-manifest.json +98 -0
  246. package/release/sdk-compatibility.json +240 -0
  247. package/release/source.json +859 -0
  248. package/release/wheels/cryptography-50.0.1-cp312-abi3-macosx_13_0_x86_64.whl +0 -0
  249. package/release/wheels/licenses/LICENSE.txt +177 -0
  250. package/release/wheels/provenance.json +15 -0
  251. package/scripts/dog/delivery-stage.js +32 -0
  252. package/scripts/dog/import-chapter.js +20 -0
  253. package/scripts/dog/import-record.js +35 -0
  254. package/scripts/dog/review-dimension.js +41 -0
  255. package/scripts/dog/review-record.js +42 -0
  256. package/scripts/maintenance.mjs +179 -0
  257. package/scripts/prepare.mjs +21 -0
  258. package/vendor/dsh-dog/LICENSE +29 -0
  259. package/vendor/dsh-dog/README.md +114 -0
  260. package/vendor/dsh-dog/UPSTREAM.md +22 -0
  261. package/vendor/dsh-dog/cordis.patch.yml +14 -0
  262. package/vendor/dsh-dog/lib/client.js +2898 -0
  263. package/vendor/dsh-dog/lib/client.js.map +1 -0
  264. package/vendor/dsh-dog/lib/core-BiEBMLz3.d.ts +495 -0
  265. package/vendor/dsh-dog/lib/core-DdfMcrB4.js +2074 -0
  266. package/vendor/dsh-dog/lib/core.d.ts +2 -0
  267. package/vendor/dsh-dog/lib/core.js +2 -0
  268. package/vendor/dsh-dog/lib/index.d.ts +133 -0
  269. package/vendor/dsh-dog/lib/index.js +967 -0
  270. package/vendor/dsh-dog/package.json +97 -0
  271. package/vendor/dsh-dog/schemas/schema-0.2/graph.schema.json +139 -0
  272. package/vendor/dsh-dog/schemas/schema-0.2/report.schema.json +87 -0
  273. package/vendor/dsh-dog/schemas/schema-0.2/run.schema.json +259 -0
  274. package/vendor/dsh-dog/schemas/schema-0.2/runtime-event.schema.json +105 -0
  275. package/vendor/dsh-dog/schemas/schema-0.2/verification.schema.json +40 -0
@@ -0,0 +1,74 @@
1
+ # 创作流程与工作区
2
+
3
+ [返回首页](../README.md) · [安装指南](INSTALL.md)
4
+
5
+ ## Workspace 模型
6
+
7
+ dsh Workspace 是唯一的工作区身份:agent 工具调用按会话的不可变 `cwd`、浏览器
8
+ 面板按当前 session 绑定的 Workspace,都把请求路由到该 Workspace canonical root
9
+ 对应的独立 OpenWrite 应用实例(任务、评审、搜索、DAG、benchmark、DoG 产物全部
10
+ 按 root 隔离)。切换 dsh Workspace 即切换全部能力;OpenWrite 的 legacy 最近项目
11
+ 列表只用于直接 CLI(`openwrite studio --project ...`)兼容,不能改变 dsh 当前
12
+ 上下文。在 dsh 中新建作品的路径是:目录选择/创建 → `workspace.create` → 在该
13
+ 绝对路径初始化 OpenWrite(同时创建该作品目录的独立 Git 仓库)→ 连接 Workspace。
14
+ 也可以先按 [安装指南](INSTALL.md) 使用 `openwrite init --project` 初始化作品,
15
+ 再把该目录加入 dsh Workspace。契约细节见 [Workspace 契约](WORKSPACE_CONTEXT_CONTRACT.md)。
16
+
17
+ ## 工作方式
18
+
19
+ 作者入口是斜杠命令(与口语同义),接到已有 `novel_*`,不另建进度/伏笔/设定/记忆存储:
20
+ `/progress` 看进度、`/write-next` 写下一章、`/review-chapter` 审这一章、`/revise-span` 改这段、
21
+ `/foreshadow` 查伏笔、`/canon` 查设定、`/learn` 写法记忆、`/export-book` 导出。
22
+ 默认顺序:看进度 → 写下一章 → 审这一章 → 必要时改这段。宿主 `/export` 是会话日志,不是成稿。
23
+
24
+ 1. 在 **OpenWrite 创作** 会话里先收敛灵感、人物、世界观与大纲(所有写入经 OpenWrite
25
+ 修订门控,重要改动先出 diff 再确认),再在同一会话中写正文。
26
+ 正文和正典使用 `novel_document_change_plan`;大纲、资产、创作重点、伏笔和写作目标
27
+ 使用 `novel_structured_change_plan`。两者都由服务端保存不可变预览、在确认时重验源
28
+ revision,并返回只对已确认结果有效的安全撤销 token。
29
+ 正文区按 canonical reading order 切章和连续审读,计划未写章节保留在大纲中但不会被
30
+ 当作可读正文;搜索结果只有在 document ID 与完整 revision 都仍匹配时才开放改单行。
31
+ 2. 写正文时执行 `novel_context_preview` 预检上下文包 →
32
+ `novel_write_chapter` 写章 → `novel_review_chapter` 六域累加评审。
33
+ 每次评审同时生成 `data/novels/{id}/data/dog/reviews/{chapter}/dog-graph.json`;
34
+ 通过 `novel_task_create(type=chapter_review)` 启动的后台评审,也会在
35
+ `novel_task_get` 读到完成态时生成同样的图;
36
+ 图的顶层是上下文完整性、六个质量域、硬门禁和确定性聚合,六域下可展开原 37 项,
37
+ 查询 criterion、证据、问题、覆盖率和继承状态。DoG 只查询已生成的 artifact,
38
+ 不重新调用模型评审。
39
+ 写章、评审和 `novel_revision_*` 操作还会维护
40
+ `data/novels/{id}/data/dog/deliveries/{chapter}/dog-graph.json`:它把正文、
41
+ `writing → review → revision → application → rereview → closure` 六阶段串成章节交付总图;
42
+ 正文 SHA 改变后旧评审立即 stale,修订应用后必须复评通过才算交付。
43
+ DoG 的 `workspaceRoot` 需要指向 OpenWrite 项目根目录。
44
+ 资产不齐时 Agent 会回到规划阶段补齐,不需要切换会话。
45
+ 拆书导入则先执行 `conductor/smart_import.py`,对输出的
46
+ `data/novels/{id}/data/dog/imports/{IMPORT_ID}/dog-graph.json` 做 DoG 验收;
47
+ 验收通过后继续在同一 Agent 中建立大纲、角色、世界观、进度和正典事件,最后重新评审。
48
+ 3. 无人值守批量生产用 **conductor**(需 Studio 在跑):
49
+ `cd conductor && .venv/bin/python pipeline.py --chapters next --limit 3`——
50
+ 连续写章 → 六域评审 → 低质量分/低覆盖率/含 blocker 自动经修订闭环回炉;
51
+ `--review-only` / `--rework` / `--agent-guidance` 见模块 docstring 与 DESIGN.md §6。
52
+ 4. 在「资料 → 图谱」查看可缩放、筛选和展开的评审 DAG/交付 DAG;节点详情显示
53
+ 质量分、覆盖率、门禁、证据、模型、token 和耗时。
54
+ 5. 在「任务 → 模型测试」选择「章节写作」或「大纲设计」,再选择多个生成模型和独立评审模型。
55
+ 章节可用 `next` 或指定可测试的计划章节;大纲可接续已有规划,或指定起始章与设计章数,
56
+ 页面会显示包含首尾章的范围。任务自动匹配 DAG 和评审范围,结果可展开节点状态及证据;
57
+ 完整规则见 [模型测试说明](BENCHMARK_TASKS.md)。每个候选使用独立作品沙箱;章节的
58
+ 「真实写作框架」进入 OpenWrite 公共写章、状态结算、正文提交、Chapter Run V2 与正式评审流程;「裸写诊断」
59
+ 只用于排查模型原始输出。测试固定同一上下文 hash,结果隔离写入
60
+ `data/novels/{id}/data/benchmarks/`,不会改变全局路由或正式正文。结果页分别展示输入、
61
+ 输出、推理 token 和服务商实际报告的费用;明确 `$0` 与未知费用分开,`/ 1M tokens`
62
+ 仅表示本次调用的综合有效价。
63
+ 6. 在「创作」正文编辑器中,选中一段后可添加**作者选区批注**(不写入正文)。先保存当前草稿,或由工作台保存后再核对选区;重复句子不会静默绑到第一处。批注有一组固定颜色,旧记录没有颜色时显示琥珀。列表在「修订」中,可定位或标为已解决;找不到或出现多处匹配时显示「已脱离原文/需重新定位」。颜色只画在编辑器覆盖层,不会把 `span`/`style` 写进 Markdown。导出和字数仍不含这些批注。
64
+ 同一编辑器可用**插入标记**写入两种内部语法:`//**人物[维度]:旧 -> 新**` 与 `//**A~>B:关系**`。人物来自当前作品,同名或别名必须选明确身份。打开表单不会创建资产、改关系、写盘或调用模型。有效标记不计入可读字数,也不会进入默认导出;无效或代码围栏中的文本保持原样。
65
+ 7. 在「创作」中精修稿件并处理审稿/修订,在「资料」维护正典,在「任务 → 导入与导出」
66
+ 处理旧稿、项目迁移和成稿。作者旧稿先冻结源文件,再确认可编辑的分章预览;中断后从
67
+ 已完成阶段继续,发布前不会进入正式正文。导出先选择“完整备份”或“交付成品”:备份会
68
+ 显示接纳/评审警告但仍可下载,交付会把结构、元数据、正文事实和评审问题作为阻断项。
69
+ 完整作品档案列出纳入、排除、缺失文件和校验和;恢复前必须选择新路径,检查 ID/引用
70
+ 重写与冲突,旧任务只归档、不自动续跑。在「资料 → 大纲 → 原生场景结构」可先只读
71
+ 预览旧正文分场,再显式确认迁移;场景用稳定 ID 分别维护阅读顺序与故事时间、人物/地点/
72
+ 事件引用,并以 scene、源章和目标章 revision 保护元数据和跨章移动。过期结构可重新预览
73
+ 锚定,交付会阻断 stale/ambiguous,备份始终回退到完整正文。Studio 只作为头部溢出菜单
74
+ 中的高级维护出口。
@@ -0,0 +1,125 @@
1
+ # Workspace Context Wire Contract
2
+
3
+ > 状态:已实施(2026-08-31)
4
+ > 配套文档:`docs/WORKSPACE_INTEGRATION_GOAL.md`(目标与验收)、`GOAL.md`(验证日志)。
5
+ > 本文是 dsh-novel ↔ OpenWrite Studio 之间 Workspace context 的唯一契约定义。
6
+
7
+ ## 1. 身份模型
8
+
9
+ - **唯一 root identity** = dsh Workspace 的 canonical absolute path(`fs.realpath` 之后)。
10
+ - `workspace_id`(dsh `WorkspaceId`,UUID)只是 UI/会话引用,不能替代路径校验。
11
+ - 一个已初始化 dsh Workspace 对应一个 OpenWrite project root(含 `novel_config.yaml`)。
12
+ - `context_epoch`:per-root 单调递增整数。Studio 端每次 context 模式成功写(2xx POST/PUT,含 `project/init`)后 bump;bridge 端为每个 root 维护失效 revision。客户端用它丢弃过期状态,不用于授权。
13
+
14
+ ## 2. 三条入口的 context 来源
15
+
16
+ ### 2.1 Agent 工具调用(openwrite-bridge tools)
17
+
18
+ - 来源:`exec.agent?.session?.header?.cwd`(dsh `SessionHeader.cwd`,创建时 stamped、不可变)。
19
+ - `exec.agent` 缺失、session 缺失或 cwd 缺失 → **fail closed**,抛 `WORKSPACE_CONTEXT_MISSING`,绝不回退到插件启动 cwd 或固定项目。
20
+ - bridge 对 cwd 做 `realpath` 规范化后作为 `workspace_root`,连同 `session_id`、`callId`、`rootCallId` 和工具名一起放进请求 header(见 §3)。
21
+
22
+ ### 2.2 浏览器面板(studio-panel → bridge web proxy)
23
+
24
+ - 浏览器只发送 **workspace_id**(和可选 session_id),**从不发送路径**:
25
+ - `X-Dsh-Workspace-Id: <workspaceId>`(所有 `/studio-panel/api` 请求必需)
26
+ - `X-Dsh-Session-Id: <sessionId>`(可选,审计)
27
+ - SSE/轮询:`/studio-panel/events?workspace=<id>`、`/studio-panel/invalidation.json?workspace=<id>`
28
+ - proxy(host 进程内)用受信注册表解析:`ctx.workspaceRegistry.get(WorkspaceId(id))?.path`。
29
+ 浏览器无法借此注入任意路径——id→path 映射完全在 host 侧完成;伪造 id 最多选到另一个已注册 dsh Workspace。
30
+ - 解析失败 → 400 `WORKSPACE_UNKNOWN`;缺 header → 400 `WORKSPACE_CONTEXT_MISSING`;registry 服务不可用 → 503 `WORKSPACE_REGISTRY_UNAVAILABLE`(fail closed)。
31
+ - proxy 转发时改发 §3 的 OpenWrite context header。
32
+
33
+ ### 2.3 Studio HTTP(OpenWrite)
34
+
35
+ - 有 `X-OpenWrite-Workspace-Root` → context 模式,按 canonical root 路由到独立的 per-root `StudioApplication`。
36
+ - 无该 header → legacy 模式,路由到 launch app(`openwrite studio --project ...` 的启动项目),CLI 与 Studio 原生页面行为不变。
37
+
38
+ ## 3. OpenWrite context header(bridge/proxy → Studio)
39
+
40
+ ```text
41
+ X-OpenWrite-Workspace-Root: <canonical absolute path> # context 模式必需
42
+ X-OpenWrite-Workspace-Id: <dsh workspace id> # 可选,审计
43
+ X-OpenWrite-Session-Id: <dsh session id> # 可选,审计
44
+ X-OpenWrite-Context-Epoch: <int> # 可选,客户端观测 epoch,诊断
45
+ X-OpenWrite-Tool-Call-Id: <dsh call id> # Agent 工具调用时提供,审计关联
46
+ X-OpenWrite-Root-Call-Id: <dsh root call id> # Agent 工具调用时提供,调用树关联
47
+ X-OpenWrite-Tool-Name: <novel_* tool name> # Agent 工具调用时提供,审计关联
48
+ ```
49
+
50
+ 后三个字段不参与 Workspace 授权。OpenWrite 将它们与 Studio `request_id` 一并写入作品内脱敏
51
+ operation trace,用于把 dsh 工具调用关联到领域变更。
52
+
53
+ 校验规则(Studio `tools/workspace_manager.py`,全部 fail closed):
54
+
55
+ - expanduser 后非绝对 → 400 `WORKSPACE_ROOT_INVALID`(reason=`not_absolute`)
56
+ - 任何 segment 为 `..` → 400 `WORKSPACE_ROOT_INVALID`(reason=`traversal`)
57
+ - `os.path.realpath` 规范化(symlink 由此消解,canonical 结果即身份)
58
+ - 不存在 → `not_found`;非目录 → `not_directory`;不可读/不可进入 → `not_readable`
59
+ - framework root(`pyproject.toml` name=openwrite + `tools/studio.py`)→ 403 `WORKSPACE_FRAMEWORK_ROOT`
60
+ - 目标与另一个已激活 root 存在任一方向的嵌套关系 → 400 `WORKSPACE_ROOT_INVALID`(reason=`nested`);检查同时覆盖“新 root 在旧 root 内”和“旧 root 在新 root 内”。
61
+ - 无 `novel_config.yaml` 且路由不允许未初始化 → 428 `WORKSPACE_NOT_INITIALIZED`
62
+
63
+ ## 4. 错误码总表
64
+
65
+ | code | HTTP | 含义 |
66
+ |---|---|---|
67
+ | `WORKSPACE_CONTEXT_MISSING` | 400 | 需要 context 的入口缺 workspace root/id(bridge 工具无 session/cwd 同码) |
68
+ | `WORKSPACE_ROOT_INVALID` | 400 | 相对路径、`..`、不存在、非目录、不可读、嵌套(details.reason 区分) |
69
+ | `WORKSPACE_FRAMEWORK_ROOT` | 403 | root 是 OpenWrite 框架仓库 |
70
+ | `WORKSPACE_NOT_INITIALIZED` | 428 | root 未初始化(允许显式 onboarding 的路由除外) |
71
+ | `WORKSPACE_SWITCH_FORBIDDEN` | 409 | context 模式调用 `/api/project/open` 或 `/api/project/delete` |
72
+ | `WORKSPACE_CONTEXT_MISMATCH` | 409 | init 的 project_path ≠ context root;task 记录 root ≠ store root |
73
+ | `WORKSPACE_UNKNOWN` | 400 | proxy 侧 workspace_id 不在 dsh 注册表 |
74
+ | `WORKSPACE_REGISTRY_UNAVAILABLE` | 503 | proxy 侧 host workspaceRegistry 服务缺失 |
75
+
76
+ 错误 payload 沿用 Studio 现有 `{error, code, details, request_id}` 结构;proxy 侧用 `{error, code}`。
77
+
78
+ ## 5. 路由语义
79
+
80
+ ### context 模式
81
+
82
+ - 每个 canonical root 懒建并缓存一个独立 `StudioApplication`(独立 task runner、写锁、revision、服务缓存;用户级 model profile/settings 与 reference library 按设计共享)。即使 context root 等于 Studio 的 `launch_root`,也必须新建独立 context app;legacy default app 只能服务无 context 请求,二者不可别名。
83
+ - context app 不持有 `ProjectRegistry`(`None`),绝不写 legacy recent-project registry。
84
+ - `/api/project/open`、`/api/project/delete` → 409 `WORKSPACE_SWITCH_FORBIDDEN`。
85
+ - `/api/project/init`:允许未初始化 root;payload `project_path` 必须 canonicalize 到 context root(否则 409 `WORKSPACE_CONTEXT_MISMATCH`);相对路径直接 400。
86
+ - `/api/project/list`:只读 legacy registry 信息,不影响路由;面板不再使用。
87
+ - `/api/reading-order*`、`/api/chapters/{id}/work-brief` 与 `/api/scenes*`:全部从当前 context app 的 canonical root 重新投影;移动、场景迁移、元数据与跨章操作使用该 root 内的 exact revision/CAS,成功写入后只失效同 root 客户端。
88
+
89
+ ### legacy 模式(无 context header)
90
+
91
+ - 一切保持原行为:launch app 为默认,`/api/project/open` 可切换 default app 的 current project(CLI 单用户兼容路径);该 mutable default app 永不作为 context app 使用。
92
+
93
+ ## 6. 诊断接口
94
+
95
+ `GET /api/workspace/context`:
96
+
97
+ - context 模式:`{mode: "workspace", workspace_id, workspace_root, initialized, novel_id, context_epoch}`
98
+ - legacy 模式:`{mode: "legacy", workspace_root, initialized, novel_id}`
99
+ - 不返回凭据、正文或绝对路径以外的敏感信息。
100
+
101
+ ## 7. 后台 task 身份
102
+
103
+ - `TaskStore` 创建记录时自动持久化 `workspace_root`(取 store 自身 project_root)。
104
+ - context 模式创建的记录额外持久化 `workspace_id`、`session_id`、`context_epoch`(请求线程 thread-local 传入;执行线程不依赖它)。
105
+ - `recover_interrupted`:记录 root ≠ store root → 标记 failed(`WORKSPACE_CONTEXT_MISMATCH`),不可恢复重跑。
106
+ - get/cancel/retry:记录 root ≠ store root → 409 `WORKSPACE_CONTEXT_MISMATCH`。
107
+ - 轮询/取消/重试/结果物化全部发生在 per-root app 内,结构上不可能漂移。
108
+
109
+ ## 8. 失效传播(bridge → 浏览器)
110
+
111
+ - `NovelDomainService` 维护 per-root revision(`Map<root, {revision, lastMutation}>`)。
112
+ - SSE 连接按 `?workspace=<id>` 解析并绑定 root;`invalidate` 事件带 `{workspace_root, revision, resource, path}`,只发给匹配 root 的连接。
113
+ - `invalidation.json?workspace=<id>` 返回该 root 的快照。
114
+ - 浏览器 WorkbenchStore 按当前 context 过滤事件;context 切换时关闭旧流、重置 revision 基线。
115
+
116
+ ## 9. 前端 context 屏障
117
+
118
+ - WorkbenchStore 持有 `{workspaceId, root, generation}`;`setContext` 变化时:bump generation、清空章节/任务/DAG/图/当前章节/错误、abort 在途请求、关闭旧 SSE、重置 revision 基线、按新 workspace 重连并刷新。
119
+ - 迟到的旧 generation 响应一律丢弃。
120
+ - localStorage key 全部带 workspace 后缀:`dsh-novel.<key>.<workspaceId>`。
121
+ - 面板不再调用 `/api/project/list`;新建流程固定为:目录选择/创建 → `workspace.create` → context 模式 `/api/project/init`(绝对 canonical path,初始化时创建该作品目录的独立 Git 仓库)→ `connectWorkspace`/`sessions.open` → `setContext`。
122
+
123
+ ### Unicode 路径传输(0.2.0)
124
+
125
+ HTTP 请求头不能直接承载任意 Unicode 字符。bridge 对非 ASCII 路径使用 URI UTF-8 编码,并附加 `X-OpenWrite-Workspace-Root-Encoding: uri`;Core 只在该标记存在时解码一次,然后执行原有 realpath、Workspace、遍历与初始化目标检查。普通 ASCII 路径维持旧协议(包括字面百分号),浏览器仍只能提交宿主 Workspace ID。编码不赋予额外权限。
@@ -0,0 +1,278 @@
1
+ # dsh / OpenWrite Workspace Integration Goal
2
+
3
+ > 状态:实施中(2026-08-31)——wire contract 已定稿于
4
+ > `docs/WORKSPACE_CONTEXT_CONTRACT.md`,OpenWrite 多 root 管理、bridge 路由与
5
+ > 面板融合正在按本文阶段 B–E 落地,验证证据汇总进 `GOAL.md`。
6
+ > 用途:作为下一阶段实现的唯一工作区隔离规格,也可直接复制本文末尾的 Goal Prompt 给另一个 AI 执行。
7
+ > 范围:dsh-novel 与本地 OpenWrite Studio 的 Workspace 绑定、路由、前端状态和验收。
8
+ > 本文不授权删除正文、营销素材、benchmark 数据或任何凭据。
9
+
10
+ ## 1. 用户要解决的问题
11
+
12
+ 用户在 dsh 中选中一个 Workspace 后,OpenWrite 必须只服务这个 Workspace 的目录。切换 dsh Workspace 时,以下所有能力必须一起切换:
13
+
14
+ - 正文、章节、导入和写作流程
15
+ - 评审、修订、交付与评审 DAG
16
+ - 后台任务、Chapter Run、DoG 产物和 benchmark
17
+ - 搜索索引、研究资料、资产、伏笔和正典状态
18
+ - Studio-panel 的章节列表、任务列表、图、SSE/轮询缓存和当前选中章节
19
+
20
+ 当前截图中的“小说项目”下拉框不是 dsh 原生 Workspace 选择器,而是 OpenWrite 的全局最近项目列表。因此新建或切换 dsh Workspace 后,旧的 OpenWrite 项目仍会出现并继续被加载。这是上下文模型没有融合,不是单纯的 UI 文案问题。
21
+
22
+ ## 2. 已核实的当前实现
23
+
24
+ ### dsh-novel
25
+
26
+ - `scripts/dev.sh` 启动一个固定的 Studio:`openwrite studio --project "$PROJECT" --port 4567`,然后启动 `dsh web`。
27
+ - `packages/openwrite-bridge/src/index.ts` 默认 `baseUrl` 为 `http://127.0.0.1:4567`。
28
+ - `packages/openwrite-bridge/src/domain.ts` 在插件生命周期内只创建一个 `NovelDomainService` 和一个 `StudioClient`;代理、工具和失效事件都共享它。
29
+ - `packages/openwrite-bridge/src/tools.ts` 的工具调用使用同一 client,尚未把调用会话的 Workspace root 作为请求上下文。
30
+ - `packages/studio-panel/src/client/HeaderChrome.tsx` 调用 `/api/project/list`、`/api/project/open`、`/api/project/init`。它展示的是 OpenWrite registry,不是 dsh Workspace 列表;新建时发送相对的 `project_path`,可能在当前项目内生成嵌套目录。
31
+ - `packages/studio-panel/src/client/OperationsView.tsx` 仍提供同一套独立的项目切换/新建流程。
32
+ - `packages/studio-panel/src/client/api.ts` 的浏览器请求只经过 `/studio-panel/api`,没有明确的当前 session/workspace 身份。
33
+ - `packages/studio-panel/src/client/WorkbenchStore.ts`、`workbench-epochs.ts` 已有资源 epoch、SSE 和轮询降级,但 epoch 目前只表示单一 Studio 的资源变化;切换 root 时没有统一的 context epoch/清空屏障。
34
+ - dsh 依赖的原生 Workspace UI 已提供 `useWorkspaces`、`useSessions`、目录选择、Workspace 创建/连接和 session 绑定。Workspace 由 canonical path 和稳定 id 区分;Session header 含不可变 `cwd`。
35
+
36
+ ### OpenWrite
37
+
38
+ - `OpenWrite/tools/studio_application.py` 在进程内持有 `launch_root`、可变 `project_root`、项目级服务、锁和 `_task_runner`。
39
+ - `_activate_project()` 会重建服务并改变同一个 Application 的全局状态;`/api/project/open` 调用它。两个并发请求或两个浏览器 session 不能安全地共享这个 mutable singleton。
40
+ - 相对初始化路径以 `launch_root` 为基准解析;这正是当前 `project_path: novelId` 导致嵌套项目的根源。
41
+ - `/api/project/list` 来自 `~/.config/openwrite/recent_projects.yaml`,它是 OpenWrite 的 legacy 最近项目注册表,不是 dsh Workspace 身份来源。
42
+ - Studio 的 task runner、revision、research、benchmark、LightRAG、review store 等服务均以 `project_root` 构造;后台 task 若不固定 root,轮询或结果物化可能漂移。
43
+ - OpenWrite 已有 framework-root 检测和 onboarding 语义,框架仓库不可当作私人小说项目。
44
+
45
+ ### 当前本机状态(仅作上下文)
46
+
47
+ - dsh Web:`http://127.0.0.1:3080`。
48
+ - Studio:`http://127.0.0.1:4567`,当前启动时绑定 `/Users/jiaoziang/my_novel`。
49
+ - 正式项目目前保留 `/Users/jiaoziang/my_novel`;其他临时目录已移入回收站。
50
+ - `my_novel` 的 `data/benchmarks` 约占 497 MB;benchmark 工作区和正式正文必须继续分开。
51
+ - review-v2、共享 schema/codegen、DoG、epoch 和前端测试已有用户改动/提交,后续实现必须在其上增量工作。
52
+
53
+ ## 3. 目标契约(必须实现)
54
+
55
+ ### 3.1 单一身份
56
+
57
+ 1. dsh Workspace 的 canonical absolute path 是唯一 root identity;稳定 Workspace id 是 UI/会话引用,不能替代路径校验。
58
+ 2. 一个已初始化 dsh Workspace 与一个 OpenWrite project root 一一对应。项目根目录应包含 `novel_config.yaml`;未初始化目录只能进入显式 onboarding。
59
+ 3. `dsh-novel` 框架仓库、空 root、相对 root、路径穿越、无法 canonicalize 的 symlink 目标、没有权限的目录都不能静默成为小说项目。
60
+ 4. OpenWrite legacy recent-project registry 可以保留以兼容直接 CLI 使用,但不能决定 dsh 当前上下文,也不能作为浏览器的 Workspace 列表。
61
+
62
+ ### 3.2 请求上下文
63
+
64
+ 所有 Studio 请求必须有明确且可审计的 Workspace context:
65
+
66
+ ```text
67
+ { workspace_id, workspace_root, session_id?, context_epoch }
68
+ ```
69
+
70
+ - Bridge 工具:从实际调用会话的 `exec.agent.session.header.cwd` 派生 canonical root,并通过受控的 StudioClient 请求上下文发送;不能使用插件启动 cwd 或固定 fallback 覆盖它。
71
+ - 浏览器面板:使用 dsh 当前 session/workspace 的标准 props/hooks;代理请求必须能可靠识别当前 session,不接受用户任意输入的 root 作为授权依据。
72
+ - Studio:校验 root 的 canonical path、目录存在性、OpenWrite 配置/初始化状态、framework boundary 和授权映射;拒绝缺失/不一致 context,错误需带稳定 code。
73
+ - 不能通过全局 mutable “当前项目”字段在不同请求之间切换来实现路由。
74
+
75
+ ### 3.3 按 root 隔离的运行时
76
+
77
+ 优先实现一个进程内的 Workspace/Project Manager:
78
+
79
+ - `root -> 独立 StudioApplication/context`,每个 context 有自己的 task runner、locks、revision/invalidation channel、service instances 和缓存。
80
+ - 请求开始时选择 context;请求结束后不得改变其他 root 的状态。
81
+ - task 创建时持久化 canonical root、workspace id、context epoch;查询、取消、重试、结果物化和重启恢复均按该 identity 校验。
82
+ - SSE/轮询失效事件必须带 root/workspace identity;客户端只消费当前 context 的事件。
83
+ - 若多租户 Application 改造成本经评估不可接受,可采用每 Workspace 一个 Studio 进程,但必须有端口分配、进程生命周期、崩溃恢复、映射持久化、浏览器动态代理和清理机制。不能把单一固定 Studio + mutable project switch 作为最终方案。
84
+
85
+ ### 3.4 初始化与兼容
86
+
87
+ - 新建作品流程先用 dsh 原生目录 picker 选取/创建真实目录,再 `workspace.create({ path: canonicalPath })`,最后在该绝对路径执行 OpenWrite 初始化并连接 Workspace。
88
+ - 禁止默认将 `novel_id` 拼在当前小说目录后面;禁止相对 `project_path`。
89
+ - 当前 Workspace 是有效 OpenWrite 项目时,面板显示只读的当前 Workspace 状态;当前 Workspace 未初始化时显示 onboarding,不列出或自动恢复旧项目。
90
+ - 直接执行 `openwrite studio --project ...` 的 CLI 模式继续可用;legacy `/api/project/open`、`/api/project/list` 的兼容行为需经过明确边界设计,不得影响 dsh 路由。
91
+
92
+ ## 4. 推荐实现路径
93
+
94
+ ### 阶段 A:建立 context 规格与探针(先做)
95
+
96
+ 1. 阅读当前 dsh 版本的 Workspace、Session、web proxy、slot props 类型,确认:
97
+ - 工具调用中 `exec.agent.session.header.cwd` 的真实类型和生命周期;
98
+ - 浏览器当前 session/workspace 能从哪些标准 props/hooks 得到;
99
+ - HTTP 代理能否安全携带 session id,是否已有受信 envelope/header;
100
+ - Workspace 注册/创建/连接的官方动作名称和返回时序。
101
+ 2. 写出一份小型 context wire contract(建议 JSON/header + server-side lookup),明确缺失、非法、未知 Workspace、root 与 session 不匹配时的错误码。
102
+ 3. 增加只读诊断接口(例如 `/api/workspace/context`),返回 workspace id、canonical root、初始化状态、novel id、context epoch,不返回凭据和正文。
103
+
104
+ ### 阶段 B:OpenWrite 多 root 管理
105
+
106
+ 1. 在 `OpenWrite/tools/` 增加 Project/Workspace Manager,封装 canonicalize、framework-root 拒绝、初始化检查、按 root 创建/缓存 Application。
107
+ 2. 让 Studio HTTP handler 在每个请求解析并验证 context,再把请求分派给对应 Application;不要让 handler 直接调用共享 `server.app.project_root`。
108
+ 3. 把 background task 的 root identity 作为持久化契约;验证 task API、review/benchmark/DoG 结果和重启恢复。
109
+ 4. 将 invalidation/revision 从单一全局计数改为 per-context;必要时保留全局监控,但浏览器消费必须按 context 过滤。
110
+ 5. 保持 `/api/project/*` legacy API 的 CLI 兼容性:在无 dsh context 的直接 Studio 调用中仍以启动项目为默认,但有 dsh context 时必须拒绝跨 root open/switch。
111
+
112
+ ### 阶段 C:bridge context 路由
113
+
114
+ 1. 扩展 `StudioClient` 支持 request-scoped `WorkspaceContext`,所有 GET/POST/PUT/download 都携带 context。
115
+ 2. `NovelDomainService` 不再只有一个无上下文 client;采用 context-aware client factory 或按 root 的安全 client cache。
116
+ 3. 更新 `tools.ts` 每个工具入口,使用调用 agent session 的 cwd 派生 context;对无 agent/session 的调用明确失败。
117
+ 4. 更新 web proxy:从受信 dsh session/workspace 绑定解析 context,再转发到 Studio;不要相信浏览器任意 `workspace_root` 字段。
118
+ 5. 将 config、API proxy、SSE、invalidation snapshot 都变成当前 context 语义;切换 context 时旧 stream 必须关闭或标记过期。
119
+
120
+ ### 阶段 D:前端融合
121
+
122
+ 1. 删除/禁用 `HeaderChrome.tsx` 与 `OperationsView.tsx` 中基于 `/project/list` 的项目切换器和相对路径新建表单。
123
+ 2. 接入 dsh 原生 Workspace picker/list;显示当前 Workspace 名称及路径标识,必要时提供 onboarding 状态,而不是 OpenWrite recent 项目菜单。
124
+ 3. 创建作品走目录 picker -> workspace create -> OpenWrite init -> connect/select -> context epoch 更新的单一流程。
125
+ 4. `WorkbenchStore` 增加 context identity/epoch:context 变化时先清空章节、任务、DAG、图、当前章节和错误状态,再允许新 root 的请求提交;旧请求返回不得覆盖新 snapshot。
126
+ 5. 所有视图(Graph/DAG、Tasks、Benchmark、Models、Research、Library、Search、Review)按 context epoch 重挂载或取消旧请求;localStorage key 必须包含 workspace id/root hash,避免跨项目恢复章节。
127
+ 6. SSE 与轮询必须携带/绑定当前 context;断线降级不能回退到旧 root。
128
+
129
+ ### 阶段 E:安全、迁移与文档
130
+
131
+ 1. 对 canonical path 做边界测试:相对路径、`..`、绝对路径、symlink、framework root、文件路径、不可读目录、重复 basename。
132
+ 2. 明确旧 registry 的迁移策略:只用于 CLI 最近项目展示/兼容,不自动注入 dsh 当前 Workspace。
133
+ 3. 更新 `scripts/dev.sh` 和 README:说明 dsh Workspace 是主入口;固定 `--project` 只作为兼容/单项目启动模式;不要记录任何 API key。
134
+ 4. 为运行时诊断、日志、错误 payload 做脱敏;不得输出凭据、完整 Authorization、正文内容或临时 key。
135
+
136
+ ## 5. 文件级检查范围
137
+
138
+ ### dsh-novel(预期修改)
139
+
140
+ - `scripts/dev.sh`
141
+ - `packages/openwrite-bridge/src/index.ts`
142
+ - `packages/openwrite-bridge/src/client.ts`
143
+ - `packages/openwrite-bridge/src/domain.ts`
144
+ - `packages/openwrite-bridge/src/tools.ts`
145
+ - `packages/studio-panel/src/client/api.ts`
146
+ - `packages/studio-panel/src/client/HeaderChrome.tsx`
147
+ - `packages/studio-panel/src/client/OperationsView.tsx`
148
+ - `packages/studio-panel/src/client/WorkbenchStore.ts`
149
+ - `packages/studio-panel/src/client/workbench-epochs.ts`
150
+ - `packages/studio-panel/src/client/index.ts` 及必要的 slot/props 适配
151
+ - dsh bridge/panel tests、E2E fixtures、README/GOAL 状态记录
152
+
153
+ ### OpenWrite(预期修改)
154
+
155
+ - `tools/studio_application.py`
156
+ - `tools/studio.py` / `tools/studio_http.py`(以实际入口为准)
157
+ - 新增 `tools/workspace_manager.py` 或等价模块
158
+ - task runner/store、project registry、invalidation/revision 相关模块
159
+ - `tests/test_studio*.py`、任务/benchmark/DoG/HTTP 测试
160
+
161
+ 先阅读并适配现有用户改动,尤其是 review-v2、schema codegen、DoG、epoch 和前端 runner;不得用旧版本覆盖这些改动。`assets/marketing/` 不是本任务范围,禁止删除或回退。
162
+
163
+ ## 6. 测试矩阵
164
+
165
+ ### Python / OpenWrite
166
+
167
+ - 两个临时 root 各有同名章节:读取、写入、评审、搜索、资产、DAG、benchmark 结果完全分离。
168
+ - 两个 root 的 Application/service/task runner 独立;并发请求不会互换 `project_root`。
169
+ - 缺失/非法 context、root 与 workspace/session 不匹配、framework root、symlink escape 全部拒绝并返回稳定错误码。
170
+ - task 创建后重启/轮询/取消/重试/结果物化仍固定原 root。
171
+ - legacy project API 在无 dsh context 下保持兼容,在有 context 下不能跨 root 切换。
172
+ - 初始化只接受 canonical absolute target,不产生 `project/project_id` 嵌套。
173
+ - per-context revision/SSE 不串流;旧 context 事件不会污染新 context。
174
+
175
+ ### TypeScript / bridge
176
+
177
+ - 从 `exec.agent.session.header.cwd` 得到 canonical root;无 session 时拒绝而不是 fallback。
178
+ - 每种 StudioClient 请求均带 context;proxy 和 domain service 不共享可变 current project。
179
+ - 两个并发 session 的工具调用、任务查询和下载互不串数据。
180
+ - browser proxy 的 context 来源是受信 session/workspace 绑定,而非任意 URL/body root。
181
+
182
+ ### React / component
183
+
184
+ - Workspace 切换立即清空旧章节/任务/DAG,旧请求迟到不覆盖新状态。
185
+ - context epoch 使 GraphView、DAG、Tasks、Benchmark、Models、Research 等重新取数。
186
+ - localStorage、SSE、轮询、AbortController 均按 workspace/session 隔离。
187
+ - 面板不再调用 `/project/list` 作为主选择器;无效 Workspace 显示 onboarding。
188
+
189
+ ### Live E2E
190
+
191
+ 用两个临时真实目录 A/B:
192
+
193
+ 1. 注册为两个 dsh Workspace,并分别初始化 OpenWrite。
194
+ 2. 在 A/B 创建同名章节和可识别的不同内容。
195
+ 3. 切换 session/workspace,验证 `/api/workspace/context` root、章节、搜索、评审、DAG、任务和 benchmark 产物。
196
+ 4. 在 A 写作,在 B 评审;检查磁盘 hash 和任务 artifact 只落在对应目录。
197
+ 5. 切换后旧内容不短暂闪现;刷新后仍恢复当前 dsh Workspace。
198
+ 6. desktop `1440x1000` 与 mobile `390x844` 均无横向溢出,服务不可达时保留现有 probe-gated skip 语义。
199
+
200
+ ## 7. 验收标准
201
+
202
+ 只有同时满足以下条件才算完成:
203
+
204
+ - dsh Workspace 是唯一主上下文;OpenWrite recent registry 不再改变 dsh 当前项目。
205
+ - 任意时刻每个请求、任务、事件和 UI snapshot 都能回答“属于哪个 canonical root”。
206
+ - A/B 并发和切换测试通过,正文、评审、DAG、任务、搜索、benchmark 无交叉。
207
+ - framework root、未初始化 root、非法路径和无 context 均 fail closed。
208
+ - 面板没有独立的 OpenWrite 项目切换/相对路径新建入口;新建作品一定先创建/连接 dsh Workspace。
209
+ - Studio 重启、页面刷新、SSE 断线、旧请求迟到均不会恢复或显示错误项目。
210
+ - 直接 CLI `openwrite studio --project ...` 仍可用。
211
+ - OpenWrite 全量 pytest、dsh build、bridge smoke、contracts/DoG/epoch/components/E2E 门禁通过;新增测试证据写入 `GOAL.md`。
212
+ - review-v2 production gate 继续保持 `disabled_uncalibrated`;本任务不修改真实正式章节。
213
+
214
+ ## 8. 非目标与安全约束
215
+
216
+ - 不在本阶段启用 review-v2 production gate,也不代替 10–20 章人工校准。
217
+ - 不修改真实正式章节、用户研究资料或 `assets/marketing/`。
218
+ - 不读取、打印、复制、提交或删除临时 OpenRouter key;验收使用脱敏/假凭据。
219
+ - 不删除 benchmark 工作区数据;只确保它们按 root 隔离。
220
+ - 不回退用户已有未提交改动。
221
+ - 不以全局 singleton 保存 current project 解决并发问题。
222
+ - 不要求删除 OpenWrite legacy registry,除非先完成兼容性设计和迁移测试。
223
+ - 所有路径使用 canonical absolute path;所有错误 fail closed。
224
+
225
+ ## 9. 回滚与兼容策略
226
+
227
+ - 每个阶段使用独立、可回滚的提交;先落 context 诊断和测试,再切换默认路由。
228
+ - 保留单项目 CLI 启动路径作为 fallback,但 dsh 集成路径由 feature flag 或明确配置控制,不能隐式混用。
229
+ - 若多 root manager 尚未稳定,允许暂时只支持一个显式绑定 Workspace,并对第二个并发 root 返回明确的 `WORKSPACE_NOT_READY`,不能错误复用第一个 root。
230
+ - 数据迁移只新增 metadata/索引,不重写正文;旧任务若没有 root identity,应标记为不可安全恢复并要求用户重新运行。
231
+
232
+ ## 10. 交付格式
233
+
234
+ 实现 AI 必须最终报告:
235
+
236
+ 1. 实际采用的架构及放弃备选方案的原因。
237
+ 2. 修改文件和关键契约/API 字段。
238
+ 3. 每个隔离场景的测试命令与结果。
239
+ 4. live A/B E2E 的磁盘/hash 证据。
240
+ 5. 兼容性、迁移、残余风险和未完成项。
241
+ 6. `GOAL.md` 状态与验证日志更新。
242
+
243
+ 不得用“测试通过”替代 A/B root 隔离证据,也不得声称人工 review-v2 校准已完成。
244
+
245
+ ---
246
+
247
+ ## 可直接复制给另一个 AI 的 Goal Prompt
248
+
249
+ 你在 `/Users/jiaoziang/dsh-novel` 工作,同时可读取本地 `/Users/jiaoziang/OpenWrite`。请先完整阅读:
250
+
251
+ - `/Users/jiaoziang/dsh-novel/docs/WORKSPACE_INTEGRATION_GOAL.md`
252
+ - `/Users/jiaoziang/dsh-novel/GOAL.md`
253
+ - dsh Workspace/Session/slot/proxy 的实际类型与 README
254
+ - `packages/openwrite-bridge/src/{index,client,domain,tools}.ts`
255
+ - `packages/studio-panel/src/client/{api,HeaderChrome,OperationsView,WorkbenchStore,workbench-epochs}.ts(x)`
256
+ - `/Users/jiaoziang/OpenWrite/tools/studio_application.py` 及 Studio HTTP/task/project registry 测试
257
+
258
+ 目标:实现“dsh Workspace 是唯一工作区身份,OpenWrite 全部能力按该 Workspace canonical root 隔离并随切换而切换”。不能只修 UI 下拉框。
259
+
260
+ 执行顺序:
261
+
262
+ 1. 先做只读探索,确认 dsh 当前 session/workspace 身份如何进入工具调用和浏览器请求;写出 context wire contract、错误码和诊断接口设计。
263
+ 2. 为 OpenWrite 实现按 canonical root 的 Project/Workspace Manager 和独立 Application context。禁止用全局 mutable `current project`;task、SSE、revision、search、DAG、benchmark、DoG 都必须带 root identity。
264
+ 3. 让 bridge 工具从 `exec.agent.session.header.cwd` 派生 root,让浏览器代理从受信 dsh session/workspace 绑定派生 root;Studio 拒绝缺失、非法、未授权或不匹配 context。
265
+ 4. 删除面板基于 `/project/list` 的主切换器和相对路径新建流程,接入 dsh 原生 Workspace picker/list。新建必须是目录选择/创建 -> `workspace.create` -> 绝对路径 OpenWrite init -> connect/select -> context epoch。
266
+ 5. 给 WorkbenchStore、SSE、轮询、AbortController 和 localStorage 加 context identity/epoch 屏障;切换时清空旧数据,迟到请求不能覆盖新 root。
267
+ 6. 完成 Python、TypeScript、React 和 live Playwright A/B 两目录测试,证明同名章节、写作、评审、任务、DAG、搜索、benchmark 以及磁盘产物都不串库。
268
+ 7. 保持 CLI `openwrite studio --project ...` 兼容;review-v2 production gate 仍为 `disabled_uncalibrated`;不改正文、不碰 `assets/marketing/`、不处理或暴露任何真实 key;不要回退已有用户改动。
269
+
270
+ 实现要求:
271
+
272
+ - 所有路径 canonical absolute;framework root、相对路径、路径穿越、symlink escape、无效目录 fail closed。
273
+ - legacy recent project registry 只能作为 CLI 兼容信息,不能覆盖 dsh 当前 Workspace。
274
+ - 对未初始化 Workspace 显示 onboarding,不静默回退到旧项目。
275
+ - 后台 task 创建后固定 root,重启/轮询/取消/重试/结果物化不能漂移。
276
+ - 每一步先加测试和诊断,再切默认行为;保持提交小而可回滚。
277
+
278
+ 完成时必须报告实际架构、修改文件、context/API 契约、A/B 隔离证据、全部测试命令与结果、迁移/残余风险,并更新 `GOAL.md`。如果某项无法安全实现,明确返回阻塞原因和已验证证据,不要用固定 Studio 或旧项目 fallback 冒充完成。
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Audit reproduction of the current CreationView autosave defect.
4
+ *
5
+ * Run from any directory with Node 24:
6
+ * node /Users/jiaoziang/dsh-novel/docs/research/autosave-race-repro.mjs
7
+ *
8
+ * This deliberately asserts that the BUG is present: draft B is never sent,
9
+ * yet is marked saved when draft A's delayed PUT succeeds. Once production
10
+ * code is fixed, convert this into a regression test expecting B to remain
11
+ * dirty and eventually be submitted; do not retain these inverted assertions
12
+ * as a correctness gate.
13
+ *
14
+ * The actual save/updateDraft callbacks and JSON parsing helpers are extracted
15
+ * from CreationView.tsx. Only React bindings, refs, timers, state and HTTP are
16
+ * stubbed. No model, network, browser, service or real manuscript is accessed.
17
+ * This proves callback behavior, not full React/Vditor/browser integration.
18
+ */
19
+ import assert from 'node:assert/strict'
20
+ import { createHash } from 'node:crypto'
21
+ import { readFile } from 'node:fs/promises'
22
+ import { stripTypeScriptTypes } from 'node:module'
23
+ import { fileURLToPath } from 'node:url'
24
+ import { createContext, runInContext } from 'node:vm'
25
+
26
+ const sourceFile = fileURLToPath(new URL('../../packages/studio-panel/src/client/CreationView.tsx', import.meta.url))
27
+ const source = await readFile(sourceFile, 'utf8')
28
+ const start = source.indexOf(' const save = useCallback(')
29
+ const end = source.indexOf(' const chooseChapter = ', start)
30
+ const helpersStart = source.indexOf('function record(')
31
+ const helpersEnd = source.indexOf('function chapterId(', helpersStart)
32
+ assert.ok(start >= 0 && end > start && helpersStart >= 0 && helpersEnd > helpersStart,
33
+ 'CreationView source structure changed; review this audit extractor before running it again')
34
+ const callbacks = source.slice(start, end)
35
+ const helpers = source.slice(helpersStart, helpersEnd)
36
+ const code = stripTypeScriptTypes(
37
+ `${helpers}\n${callbacks}\nglobalThis.save = save; globalThis.updateDraft = updateDraft;`,
38
+ { mode: 'strip' },
39
+ )
40
+
41
+ const requests = []
42
+ const statuses = []
43
+ const invalidations = []
44
+ const timers = new Map()
45
+ let timerId = 0
46
+ let resolvePut
47
+ let clientDocument
48
+ let visibleDraft = 'A'
49
+ const draftRef = { current: 'A' }
50
+ const dirtyRef = { current: true }
51
+ const savedContentRef = { current: 'previous' }
52
+ const scope = {
53
+ document_: {
54
+ path: 'data/manuscript/ch_001.md', title: 'audit fixture',
55
+ content: 'previous', version: 'v1', revision: 'r1',
56
+ },
57
+ dirtyRef, draftRef, savedContentRef,
58
+ saveTimerRef: { current: null },
59
+ useCallback: callback => callback,
60
+ window: {
61
+ clearTimeout: id => { timers.delete(id) },
62
+ setTimeout: (callback, ms) => {
63
+ const id = ++timerId
64
+ timers.set(id, { callback, ms })
65
+ return id
66
+ },
67
+ },
68
+ workbenchStore: {
69
+ setEditorStatus: (...args) => { statuses.push(args) },
70
+ invalidate: resource => { invalidations.push(resource) },
71
+ },
72
+ putStudioApi: (path, body) => {
73
+ requests.push({ path, body: structuredClone(body) })
74
+ return new Promise(resolve => { resolvePut = resolve })
75
+ },
76
+ setDocument: value => { clientDocument = value },
77
+ setDraft: value => { visibleDraft = value },
78
+ StudioApiError: class StudioApiError extends Error {},
79
+ t: key => key,
80
+ }
81
+ createContext(scope)
82
+ runInContext(code, scope, { filename: 'CreationView-extracted-autosave.js' })
83
+
84
+ // 1. Submit A and leave the actual save callback suspended inside its PUT.
85
+ const pendingA = scope.save()
86
+ assert.equal(requests.length, 1)
87
+ assert.equal(requests[0].body.content, 'A')
88
+ assert.equal(typeof resolvePut, 'function')
89
+
90
+ // 2. Type B through the actual input callback while A is still pending.
91
+ scope.updateDraft('B')
92
+ assert.equal(dirtyRef.current, true)
93
+ assert.equal(timers.size, 1)
94
+ assert.equal([...timers.values()][0].ms, 1_200)
95
+
96
+ // 3. The server acknowledges only A. These assertions document the defect.
97
+ resolvePut({ ...scope.document_, content: 'A', version: 'v2', revision: 'r2' })
98
+ await pendingA
99
+ assert.equal(visibleDraft, 'B')
100
+ assert.equal(savedContentRef.current, 'B', 'BUG: B was incorrectly recorded as saved')
101
+ assert.equal(clientDocument.content, 'B', 'BUG: the client claims the server saved B')
102
+ assert.equal(dirtyRef.current, false, 'BUG: an unsent edit lost its dirty flag')
103
+ assert.equal(statuses.at(-1)[0], 'saved', 'BUG: the author sees an incorrect saved status')
104
+
105
+ // 4. Fire B's real scheduled callback, then explicitly request another save.
106
+ const [scheduledId, timer] = [...timers.entries()][0]
107
+ timers.delete(scheduledId)
108
+ timer.callback()
109
+ await Promise.resolve()
110
+ await scope.save()
111
+ assert.deepEqual(requests.map(request => request.body.content), ['A'],
112
+ 'BUG: B remains unsent even after its autosave timer and another save call')
113
+
114
+ console.log(JSON.stringify({
115
+ result: 'DEFECT REPRODUCED: B was never sent, but is marked saved and is not retried.',
116
+ sourceFile,
117
+ callbackLines: [source.slice(0, start).split('\n').length, source.slice(0, end).split('\n').length - 1],
118
+ callbackSha256: createHash('sha256').update(callbacks).digest('hex'),
119
+ node: process.version,
120
+ actualCallbacksExecuted: ['save', 'updateDraft'],
121
+ requestContents: requests.map(request => request.body.content),
122
+ serverResponseContent: 'A',
123
+ visibleDraft,
124
+ reportedSavedContent: savedContentRef.current,
125
+ clientDocumentContent: clientDocument.content,
126
+ dirty: dirtyRef.current,
127
+ finalStatus: statuses.at(-1)[0],
128
+ requestCountAfterActualTimerAndExplicitSave: requests.length,
129
+ statusSequence: statuses.map(status => status[0]),
130
+ invalidations,
131
+ proofScope: 'Deterministic execution of extracted source callbacks with stubs; no React/Vditor/browser interaction was tested.',
132
+ maintenanceNote: 'This is a defect reproduction, not a passing correctness gate. After fixing production code, convert it into a regression test expecting B to be preserved and submitted.',
133
+ }, null, 2))