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,496 @@
1
+ import { Service } from '@deepseek-ai/cordis';
2
+ import { StudioClient, workspaceRootHeaders } from './client.js';
3
+ import { workspaceContextFromExec } from './tools.js';
4
+ export const CONFIG_ROUTE = '/studio-panel/config.json';
5
+ export const API_PROXY_ROUTE = '/studio-panel/api';
6
+ export const EVENTS_ROUTE = '/studio-panel/events';
7
+ export const INVALIDATION_ROUTE = '/studio-panel/invalidation.json';
8
+ const PROXY_TIMEOUT_MS = 60_000;
9
+ /** Cap for the best-effort upstream context_epoch read; never blocks a snapshot longer. */
10
+ const CONTEXT_EPOCH_TIMEOUT_MS = 5_000;
11
+ const WRITABLE_PATHS = new Set([
12
+ 'assets',
13
+ 'assets/update',
14
+ 'assets/package/import',
15
+ 'outline/edit',
16
+ 'rolling-plans',
17
+ 'narrative-forecasts',
18
+ 'sync',
19
+ 'import/preview',
20
+ 'import',
21
+ 'manuscript-imports/prepare',
22
+ 'manuscript-imports/structure',
23
+ 'manuscript-imports/confirm',
24
+ 'manuscript-imports/run',
25
+ 'manuscript-imports/discard',
26
+ 'project-archives/create',
27
+ 'project-archives/restore/preview',
28
+ 'project-archives/restore',
29
+ 'reading-order/move',
30
+ 'scenes/migration/apply',
31
+ 'scenes/migration/rollback',
32
+ 'scenes/metadata',
33
+ 'scenes/move',
34
+ 'tasks',
35
+ 'benchmarks',
36
+ 'document',
37
+ 'document/change-plan',
38
+ 'structured/change-plan',
39
+ 'manuscript-editing',
40
+ 'manuscript/acceptance/baseline',
41
+ 'manuscript/acceptance/external',
42
+ 'manuscript/acceptance/reconcile',
43
+ 'manuscript/acceptance/ack',
44
+ 'project/init',
45
+ 'model',
46
+ 'model/test',
47
+ 'model/embedding/test',
48
+ 'model/embedding',
49
+ 'model/embedding/select',
50
+ 'model/embedding/delete',
51
+ 'model/profiles',
52
+ 'model/profiles/delete',
53
+ 'model/routes',
54
+ 'chapter/delete-batch',
55
+ 'research/settings',
56
+ ]);
57
+ const WRITABLE_PATH_RE = /^(?:tasks\/[A-Za-z0-9_-]+\/(?:cancel|retry|confirm)|revisions\/(?:selection|from-review|rev_[A-Za-z0-9_-]+\/(?:apply|reject|regenerate)))$/;
58
+ /** Mutation/SSE state bucket key for context-less (legacy) mutations. */
59
+ const LEGACY_ROOT_KEY = '';
60
+ function sendJson(res, status, payload) {
61
+ const body = Buffer.from(JSON.stringify(payload));
62
+ res.writeHead(status, {
63
+ 'content-type': 'application/json',
64
+ 'cache-control': 'no-cache',
65
+ 'content-length': String(body.length),
66
+ });
67
+ res.end(body);
68
+ }
69
+ async function readBody(req) {
70
+ const iterator = req[Symbol.asyncIterator];
71
+ if (iterator === undefined)
72
+ return Buffer.alloc(0);
73
+ const chunks = [];
74
+ for await (const chunk of iterator.call(req)) {
75
+ chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
76
+ }
77
+ return Buffer.concat(chunks);
78
+ }
79
+ function resourceForPath(path) {
80
+ if (path.startsWith('/api/manuscript-imports'))
81
+ return 'tasks';
82
+ if (path.startsWith('/api/project-archives'))
83
+ return 'workspace';
84
+ if (path.startsWith('/api/document') || path.startsWith('/api/write') || path.startsWith('/api/manuscript/acceptance'))
85
+ return 'manuscript';
86
+ if (path.startsWith('/api/outline'))
87
+ return 'outline';
88
+ if (path.startsWith('/api/scenes'))
89
+ return 'outline';
90
+ if (path.startsWith('/api/assets'))
91
+ return 'assets';
92
+ if (path.startsWith('/api/tasks') || path.startsWith('/api/review'))
93
+ return 'tasks';
94
+ if (path.startsWith('/api/benchmarks'))
95
+ return 'benchmark';
96
+ if (path.startsWith('/api/model'))
97
+ return 'models';
98
+ if (path.startsWith('/api/research'))
99
+ return 'research';
100
+ if (path.startsWith('/api/revisions'))
101
+ return 'revisions';
102
+ return 'workspace';
103
+ }
104
+ function compactTaskList(bytes, pathPart, method) {
105
+ if (method !== 'GET' || pathPart !== 'tasks')
106
+ return bytes;
107
+ try {
108
+ const envelope = JSON.parse(bytes.toString('utf8'));
109
+ const data = envelope['data'];
110
+ if (data === undefined || !Array.isArray(data['tasks']))
111
+ return bytes;
112
+ const tasks = data['tasks'].map((raw) => {
113
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
114
+ return raw;
115
+ const task = raw;
116
+ const result = task['result'] !== null && typeof task['result'] === 'object' && !Array.isArray(task['result'])
117
+ ? task['result']
118
+ : null;
119
+ const reviewV2 = result?.['review_v2'] !== null && typeof result?.['review_v2'] === 'object' && !Array.isArray(result?.['review_v2'])
120
+ ? result['review_v2']
121
+ : null;
122
+ const reviewResult = task['type'] === 'chapter_review' && result !== null
123
+ ? {
124
+ score: result['score'],
125
+ passed: result['passed'],
126
+ issues: result['issues'],
127
+ summary: result['summary'],
128
+ review_v2: reviewV2 === null ? undefined : {
129
+ schema_version: reviewV2['schema_version'],
130
+ execution_status: reviewV2['execution_status'],
131
+ quality_score: reviewV2['quality_score'],
132
+ coverage: reviewV2['coverage'],
133
+ gate_status: reviewV2['gate_status'],
134
+ delivery_status: reviewV2['delivery_status'],
135
+ production_gate_status: reviewV2['production_gate_status'],
136
+ freshness_status: reviewV2['freshness_status'],
137
+ source_revision: reviewV2['source_revision'],
138
+ current_source_revision: reviewV2['current_source_revision'],
139
+ },
140
+ issue_details: Array.isArray(result['issue_details'])
141
+ ? result['issue_details'].map((rawIssue) => {
142
+ const issue = rawIssue !== null && typeof rawIssue === 'object' && !Array.isArray(rawIssue)
143
+ ? rawIssue
144
+ : {};
145
+ return {
146
+ severity: issue['severity'],
147
+ review_severity: issue['review_severity'],
148
+ revision_priority: issue['revision_priority'],
149
+ legacy_severity: issue['legacy_severity'],
150
+ dimension: issue['dimension'],
151
+ category: issue['category'],
152
+ summary: issue['summary'] ?? issue['description'],
153
+ };
154
+ })
155
+ : [],
156
+ }
157
+ : null;
158
+ const benchmarkResult = task['type'] === 'model_benchmark' && result !== null
159
+ ? {
160
+ run_id: result['run_id'], status: result['status'], artifact_path: result['artifact_path'],
161
+ context_hash: result['context_hash'], summary: result['summary'],
162
+ }
163
+ : null;
164
+ return {
165
+ task_id: task['task_id'],
166
+ type: task['type'],
167
+ status: task['status'],
168
+ phase: task['phase'],
169
+ phase_index: task['phase_index'],
170
+ progress: task['progress'],
171
+ chapter_id: task['chapter_id'],
172
+ input_summary: task['input_summary'],
173
+ error: task['error'],
174
+ retryable: task['retryable'],
175
+ attempt: task['attempt'],
176
+ created_at: task['created_at'],
177
+ updated_at: task['updated_at'],
178
+ started_at: task['started_at'],
179
+ completed_at: task['completed_at'],
180
+ result_ref: task['result_ref'],
181
+ schema_version: task['schema_version'],
182
+ result: reviewResult ?? benchmarkResult,
183
+ };
184
+ });
185
+ return Buffer.from(JSON.stringify({ ...envelope, data: { ...data, tasks } }));
186
+ }
187
+ catch {
188
+ return bytes;
189
+ }
190
+ }
191
+ /** Read a single-valued request header (node lowercases header names). */
192
+ function headerValue(req, name) {
193
+ const value = req.headers?.[name];
194
+ return typeof value === 'string' && value !== '' ? value : undefined;
195
+ }
196
+ /** Read the `?workspace=` query parameter of a route URL. */
197
+ function workspaceParam(req) {
198
+ const value = new URL(req.url ?? '/', 'http://localhost').searchParams.get('workspace');
199
+ return value === null || value === '' ? undefined : value;
200
+ }
201
+ /** Single host-side boundary shared by agent tools, browser proxy and live invalidation. */
202
+ export class NovelDomainService extends Service {
203
+ toolOptions;
204
+ client;
205
+ authorizeRequest;
206
+ connect;
207
+ baseUrl;
208
+ resolveWorkspace;
209
+ /** Per-root invalidation state, keyed by canonical workspace root ('' = legacy). */
210
+ roots = new Map();
211
+ /** Open SSE connections bound to the root key they subscribed to. */
212
+ streams = new Map();
213
+ constructor(ctx, options) {
214
+ super(ctx, 'novelDomain');
215
+ this.toolOptions = { timeoutMs: options.timeoutMs, ...(options.outputDir ? { outputDir: options.outputDir } : {}) };
216
+ this.connect = options.connect;
217
+ this.authorizeRequest = options.authorizeRequest;
218
+ this.baseUrl = options.baseUrl.replace(/\/+$/, '');
219
+ this.resolveWorkspace = options.resolveWorkspace;
220
+ this.client = new StudioClient({
221
+ connect: options.connect,
222
+ baseUrl: this.baseUrl,
223
+ timeoutMs: options.timeoutMs,
224
+ onMutation: (path, context) => this.notifyMutation(path, context),
225
+ });
226
+ }
227
+ /**
228
+ * Factory for the tool surface: every tool execution derives its own
229
+ * request-scoped client from the dsh session context (fail closed).
230
+ */
231
+ clientFactory() {
232
+ return exec => this.client.scoped(workspaceContextFromExec(exec));
233
+ }
234
+ /** Record a mutation under its root and broadcast only to that root's SSE connections. */
235
+ notifyMutation(path, context) {
236
+ const root = context?.workspaceRoot ?? LEGACY_ROOT_KEY;
237
+ const state = this.roots.get(root) ?? { revision: 0, lastMutation: { revision: 0, resource: 'workspace', path: '' } };
238
+ state.revision += 1;
239
+ state.lastMutation = { revision: state.revision, resource: resourceForPath(path), path };
240
+ this.roots.set(root, state);
241
+ const event = JSON.stringify({ workspace_root: root, ...state.lastMutation });
242
+ for (const [stream, key] of this.streams) {
243
+ if (key === root)
244
+ stream.write(`event: invalidate\ndata: ${event}\n\n`);
245
+ }
246
+ }
247
+ /** Snapshot of one root's invalidation state (zero revision when never mutated). */
248
+ snapshotFor(root) {
249
+ const state = this.roots.get(root);
250
+ if (state === undefined)
251
+ return { revision: 0, resource: null, path: null, workspace_root: root };
252
+ return { workspace_root: root, ...state.lastMutation };
253
+ }
254
+ /**
255
+ * Best-effort read of the upstream per-root context epoch (the only signal
256
+ * that background task transitions bump). ANY failure — network, non-2xx,
257
+ * missing/invalid field — degrades to null so the caller omits the field;
258
+ * this helper never throws and never breaks a snapshot.
259
+ */
260
+ async fetchContextEpoch(root, id, signal) {
261
+ try {
262
+ const timeout = AbortSignal.timeout(CONTEXT_EPOCH_TIMEOUT_MS);
263
+ const payload = await this.client
264
+ .scoped({ workspaceRoot: root, workspaceId: id })
265
+ .getJson('/api/workspace/context', {}, signal === undefined ? timeout : AbortSignal.any([timeout, signal]));
266
+ const epoch = payload?.['context_epoch'];
267
+ return typeof epoch === 'number' && Number.isFinite(epoch) ? epoch : null;
268
+ }
269
+ catch {
270
+ return null;
271
+ }
272
+ }
273
+ /**
274
+ * Resolve a browser-supplied workspace id through the host registry, failing
275
+ * closed per the contract: missing id → 400 WORKSPACE_CONTEXT_MISSING, no
276
+ * registry → 503 WORKSPACE_REGISTRY_UNAVAILABLE, unknown id → 400 WORKSPACE_UNKNOWN.
277
+ */
278
+ resolveBrowserWorkspace(id) {
279
+ if (id === undefined) {
280
+ return { rejection: { status: 400, code: 'WORKSPACE_CONTEXT_MISSING', message: 'missing workspace id (x-dsh-workspace-id header or ?workspace= param)' } };
281
+ }
282
+ if (this.resolveWorkspace === undefined) {
283
+ return { rejection: { status: 503, code: 'WORKSPACE_REGISTRY_UNAVAILABLE', message: 'host workspaceRegistry service is not available' } };
284
+ }
285
+ const root = this.resolveWorkspace(id);
286
+ if (root === null) {
287
+ return { rejection: { status: 503, code: 'WORKSPACE_REGISTRY_UNAVAILABLE', message: 'host workspaceRegistry service is not available' } };
288
+ }
289
+ if (root === undefined) {
290
+ return { rejection: { status: 400, code: 'WORKSPACE_UNKNOWN', message: `workspace id is not registered: ${id}` } };
291
+ }
292
+ return { root, id };
293
+ }
294
+ static sendRejection(res, rejection) {
295
+ sendJson(res, rejection.status, { error: rejection.message, code: rejection.code });
296
+ }
297
+ registerWebRoutes(ctx) {
298
+ const register = route => ctx.webServer.register({ ...route, handler: (req, res) => {
299
+ const rejection = this.authorizeRequest?.(req.headers);
300
+ if (rejection !== undefined) {
301
+ sendJson(res, rejection, { error: 'dsh browser authentication required' });
302
+ return;
303
+ }
304
+ return route.handler(req, res);
305
+ } });
306
+ // These responses belong to this webServer injection scope. Removing a
307
+ // route does not close requests already handled by it; end them explicitly
308
+ // so EventSource reconnects to the newly mounted plugin after a reload.
309
+ const responses = new Set();
310
+ const lifetime = new AbortController();
311
+ let disposed = false;
312
+ ctx.effect(() => () => {
313
+ disposed = true;
314
+ lifetime.abort();
315
+ for (const response of responses) {
316
+ this.streams.delete(response);
317
+ try {
318
+ response.end();
319
+ }
320
+ catch { /* already disconnected */ }
321
+ }
322
+ responses.clear();
323
+ }, 'novel-domain: close live invalidation responses');
324
+ ctx.effect(() => register({
325
+ kind: 'exact',
326
+ path: CONFIG_ROUTE,
327
+ handler: (req, res) => {
328
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
329
+ res.writeHead(405);
330
+ res.end();
331
+ return;
332
+ }
333
+ res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-cache' });
334
+ res.end(JSON.stringify({ studioUrl: this.baseUrl }));
335
+ },
336
+ }), 'novel-domain: config route');
337
+ ctx.effect(() => register({
338
+ kind: 'prefix',
339
+ path: API_PROXY_ROUTE,
340
+ handler: this.createProxyHandler(lifetime.signal, responses),
341
+ }), 'novel-domain: API proxy');
342
+ ctx.effect(() => register({
343
+ kind: 'exact',
344
+ path: INVALIDATION_ROUTE,
345
+ handler: async (req, res) => {
346
+ if (disposed) {
347
+ sendJson(res, 503, { error: 'novel-domain is reloading', code: 'NOVEL_DOMAIN_DISPOSED' });
348
+ return;
349
+ }
350
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
351
+ res.writeHead(405);
352
+ res.end();
353
+ return;
354
+ }
355
+ const resolved = this.resolveBrowserWorkspace(workspaceParam(req));
356
+ if ('rejection' in resolved) {
357
+ NovelDomainService.sendRejection(res, resolved.rejection);
358
+ return;
359
+ }
360
+ const snapshot = this.snapshotFor(resolved.root);
361
+ // Merge the upstream context epoch (omitted when unreadable): this is
362
+ // how polling clients observe background task transitions that never
363
+ // touch the bridge-local revision.
364
+ responses.add(res);
365
+ const contextEpoch = await this.fetchContextEpoch(resolved.root, resolved.id, lifetime.signal);
366
+ responses.delete(res);
367
+ if (disposed)
368
+ return;
369
+ sendJson(res, 200, contextEpoch === null ? snapshot : { ...snapshot, context_epoch: contextEpoch });
370
+ },
371
+ }), 'novel-domain: invalidation snapshot');
372
+ ctx.effect(() => register({
373
+ kind: 'exact',
374
+ path: EVENTS_ROUTE,
375
+ handler: async (req, res) => {
376
+ if (disposed) {
377
+ sendJson(res, 503, { error: 'novel-domain is reloading', code: 'NOVEL_DOMAIN_DISPOSED' });
378
+ return;
379
+ }
380
+ if (req.method !== 'GET') {
381
+ res.writeHead(405);
382
+ res.end();
383
+ return;
384
+ }
385
+ const resolved = this.resolveBrowserWorkspace(workspaceParam(req));
386
+ if ('rejection' in resolved) {
387
+ NovelDomainService.sendRejection(res, resolved.rejection);
388
+ return;
389
+ }
390
+ res.writeHead(200, {
391
+ 'content-type': 'text/event-stream',
392
+ 'cache-control': 'no-cache',
393
+ connection: 'keep-alive',
394
+ });
395
+ responses.add(res);
396
+ this.streams.set(res, resolved.root);
397
+ const dispose = () => { this.streams.delete(res); responses.delete(res); };
398
+ res.on?.('close', dispose);
399
+ res.on?.('error', dispose);
400
+ // The ready event carries the upstream context epoch when it is
401
+ // readable; the stream waits for the fetch to settle (capped at
402
+ // CONTEXT_EPOCH_TIMEOUT_MS), never longer. A client gone by then
403
+ // simply never gets the ready event — polling carries epochs anyway.
404
+ const revision = this.roots.get(resolved.root)?.revision ?? 0;
405
+ const contextEpoch = await this.fetchContextEpoch(resolved.root, resolved.id, lifetime.signal);
406
+ if (disposed || !this.streams.has(res))
407
+ return;
408
+ const ready = { workspace_root: resolved.root, revision };
409
+ if (contextEpoch !== null)
410
+ ready['context_epoch'] = contextEpoch;
411
+ res.write(`event: ready\ndata: ${JSON.stringify(ready)}\n\n`);
412
+ },
413
+ }), 'novel-domain: invalidation stream');
414
+ }
415
+ createProxyHandler(lifetime, responses) {
416
+ return async (req, res) => {
417
+ if (lifetime.aborted) {
418
+ sendJson(res, 503, { error: 'OpenWrite 已停止' });
419
+ return;
420
+ }
421
+ const sub = (req.url ?? '').slice(API_PROXY_ROUTE.length);
422
+ if (sub === '' || !sub.startsWith('/')) {
423
+ sendJson(res, 404, { error: 'novel-domain proxy: missing API path' });
424
+ return;
425
+ }
426
+ // Every browser request carries only a workspace_id; the trusted host
427
+ // registry resolves it to the canonical root (contract §2.2).
428
+ const resolved = this.resolveBrowserWorkspace(headerValue(req, 'x-dsh-workspace-id'));
429
+ if ('rejection' in resolved) {
430
+ NovelDomainService.sendRejection(res, resolved.rejection);
431
+ return;
432
+ }
433
+ const method = req.method ?? 'GET';
434
+ const pathPart = sub.split('?')[0]?.slice(1) ?? '';
435
+ const isWrite = method === 'POST' || method === 'PUT';
436
+ if (!isWrite && method !== 'GET' && method !== 'HEAD') {
437
+ sendJson(res, 405, { error: 'novel-domain proxy allows only GET/HEAD and allowlisted writes' });
438
+ return;
439
+ }
440
+ if (isWrite && !WRITABLE_PATHS.has(pathPart) && !WRITABLE_PATH_RE.test(pathPart)) {
441
+ sendJson(res, 405, { error: `novel-domain proxy: write path "${pathPart}" is not allowlisted` });
442
+ return;
443
+ }
444
+ responses.add(res);
445
+ try {
446
+ const headers = {
447
+ accept: 'application/json',
448
+ ...workspaceRootHeaders(resolved.root, true),
449
+ 'x-openwrite-workspace-id': resolved.id,
450
+ };
451
+ const sessionId = headerValue(req, 'x-dsh-session-id');
452
+ if (sessionId !== undefined)
453
+ headers['x-openwrite-session-id'] = sessionId;
454
+ let body;
455
+ if (isWrite) {
456
+ body = await readBody(req);
457
+ headers['content-type'] = 'application/json';
458
+ headers['x-openwrite-studio'] = '1';
459
+ }
460
+ const connection = await this.connect?.();
461
+ if (connection?.token)
462
+ headers['authorization'] = `Bearer ${connection.token}`;
463
+ const upstream = await fetch(new URL(`/api${sub}`, connection?.baseUrl ?? this.baseUrl), {
464
+ method,
465
+ headers,
466
+ ...(body !== undefined ? { body: new Uint8Array(body) } : {}),
467
+ signal: AbortSignal.any([lifetime, AbortSignal.timeout(PROXY_TIMEOUT_MS)]),
468
+ });
469
+ if (lifetime.aborted)
470
+ return;
471
+ const upstreamBytes = Buffer.from(await upstream.arrayBuffer());
472
+ const bytes = compactTaskList(upstreamBytes, pathPart, method);
473
+ const responseHeaders = {
474
+ 'content-type': upstream.headers.get('content-type') ?? 'application/json',
475
+ 'cache-control': 'no-cache',
476
+ 'content-length': String(bytes.length),
477
+ };
478
+ const disposition = upstream.headers.get('content-disposition');
479
+ if (disposition !== null)
480
+ responseHeaders['content-disposition'] = disposition;
481
+ res.writeHead(upstream.status, responseHeaders);
482
+ res.end(bytes);
483
+ if (isWrite && upstream.ok) {
484
+ this.notifyMutation(`/api/${pathPart}`, { workspaceRoot: resolved.root, workspaceId: resolved.id });
485
+ }
486
+ }
487
+ catch (error) {
488
+ if (!lifetime.aborted)
489
+ sendJson(res, 502, { error: '写作环境暂不可用,请打开环境与诊断重试' });
490
+ }
491
+ finally {
492
+ responses.delete(res);
493
+ }
494
+ };
495
+ }
496
+ }
@@ -0,0 +1,17 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import Schema from '@deepseek-ai/schemastery';
3
+ export declare const name = "@dsh-novel/openwrite-bridge";
4
+ export declare const inject: string[];
5
+ export interface Config {
6
+ mode?: 'managed' | 'external';
7
+ runtimeRoot?: string;
8
+ artifacts?: string;
9
+ /** Base URL of the local OpenWrite Studio server. */
10
+ baseUrl: string;
11
+ /** Per-tool cooperative timeout in ms; chapter writing/review can take minutes. */
12
+ timeoutMs: number;
13
+ /** Directory `novel_export` saves exported files into. */
14
+ outputDir: string;
15
+ }
16
+ export declare const Config: Schema<Config>;
17
+ export declare function apply(ctx: Context, config: Config): void;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * `@dsh-novel/openwrite-bridge`: a dsh plugin registering `novel_*` tools that
3
+ * call the local OpenWrite Studio HTTP action surface. dsh owns agent
4
+ * orchestration; OpenWrite owns the novel domain logic; this package is a thin
5
+ * bridge over the HTTP contract only.
6
+ */
7
+ import { tmpdir, homedir } from 'node:os';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { ManagedRuntime } from './managed-runtime.js';
10
+ import { join } from 'node:path';
11
+ import Schema from '@deepseek-ai/schemastery';
12
+ import { NovelDomainService } from './domain.js';
13
+ export const name = '@dsh-novel/openwrite-bridge';
14
+ export const inject = [];
15
+ export const Config = Schema.object({
16
+ mode: Schema.union(['managed', 'external']),
17
+ runtimeRoot: Schema.string(),
18
+ artifacts: Schema.string(),
19
+ baseUrl: Schema.string().default('http://127.0.0.1:4567'),
20
+ timeoutMs: Schema.number().default(600000),
21
+ outputDir: Schema.string().default(join(tmpdir(), 'openwrite-exports')),
22
+ });
23
+ export function apply(ctx, config) {
24
+ // The web host process mounts ctx.workspaceRegistry. Resolve it lazily on
25
+ // every call: plugin start order must not strand the lookup, an absent
26
+ // registry (null) fails closed in the domain, and ctx.inject(['webServer',
27
+ // ...]) would silently never fire if we inject-required a second service.
28
+ const resolveWorkspace = (workspaceId) => {
29
+ const registry = ctx.get('workspaceRegistry');
30
+ if (registry === undefined)
31
+ return null;
32
+ return registry.get(workspaceId)?.path;
33
+ };
34
+ // A supplied legacy baseUrl remains external; the standard bundle selects managed explicitly.
35
+ const runtime = config.mode === 'managed' ? new ManagedRuntime(config.runtimeRoot ?? join(process.env['DSH_HOME']?.trim() || join(homedir(), '.dsh'), 'openwrite'), config.artifacts ?? fileURLToPath(new URL('../../../release/', import.meta.url))) : undefined;
36
+ const authorizeRequest = (headers) => {
37
+ const connection = ctx.get('connection');
38
+ return connection ? connection.requestRejection({ headers }) : 503;
39
+ };
40
+ const domain = new NovelDomainService(ctx, { authorizeRequest, baseUrl: config.baseUrl, timeoutMs: config.timeoutMs, outputDir: config.outputDir, resolveWorkspace,
41
+ ...(runtime ? { connect: () => runtime.ensure() } : {}),
42
+ });
43
+ ctx.effect(() => () => runtime?.dispose(), 'openwrite: owned runtime cleanup');
44
+ ctx.inject(['webServer'], webCtx => webCtx.effect(() => webCtx.webServer.register({
45
+ kind: 'exact', path: '/studio-panel/runtime', handler: async (req, res) => {
46
+ const respond = (code, data) => { res.writeHead(code, { 'content-type': 'application/json', 'cache-control': 'no-store' }); res.end(JSON.stringify(data)); };
47
+ const rejection = authorizeRequest(req.headers);
48
+ if (rejection !== undefined) {
49
+ respond(rejection, { error: 'dsh browser authentication required' });
50
+ return;
51
+ }
52
+ if (req.method === 'GET') {
53
+ respond(200, runtime?.status() ?? { phase: 'ready', message: '使用外部写作环境' });
54
+ return;
55
+ }
56
+ if (req.method !== 'POST' || req.headers['x-openwrite-studio'] !== '1') {
57
+ respond(405, { error: '操作不允许' });
58
+ return;
59
+ }
60
+ const chunks = [];
61
+ let size = 0;
62
+ for await (const chunk of req) {
63
+ size += chunk.length;
64
+ if (size > 1024) {
65
+ respond(413, { error: '请求过大' });
66
+ return;
67
+ }
68
+ ;
69
+ chunks.push(Buffer.from(chunk));
70
+ }
71
+ try {
72
+ const action = JSON.parse(Buffer.concat(chunks).toString()).action;
73
+ if (action === 'cancel')
74
+ await runtime?.cancel();
75
+ else if (action === 'prepare' || action === 'retry')
76
+ void runtime?.ensure().catch(() => { });
77
+ else {
78
+ respond(400, { error: '未知操作' });
79
+ return;
80
+ }
81
+ respond(202, runtime?.status() ?? { phase: 'ready' });
82
+ }
83
+ catch {
84
+ respond(400, { error: '请求无效' });
85
+ }
86
+ },
87
+ }), 'openwrite: runtime controls'));
88
+ ctx.inject(['webServer'], webCtx => domain.registerWebRoutes(webCtx));
89
+ }