@xiaobuyu/nodesign 0.0.1

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 (372) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +140 -0
  3. package/bin/nodesign.js +176 -0
  4. package/package.json +104 -0
  5. package/server/api/README.md +45 -0
  6. package/server/api/_guard.js +75 -0
  7. package/server/api/admin.js +187 -0
  8. package/server/api/assets/docx-page.js +103 -0
  9. package/server/api/assets/helpers.js +22 -0
  10. package/server/api/assets/notes.js +144 -0
  11. package/server/api/assets.js +896 -0
  12. package/server/api/board.js +57 -0
  13. package/server/api/browse.js +138 -0
  14. package/server/api/canvas.js +444 -0
  15. package/server/api/chatai.js +324 -0
  16. package/server/api/exports/build-standalone.js +980 -0
  17. package/server/api/exports/cards.js +118 -0
  18. package/server/api/exports/docx-pdf.js +46 -0
  19. package/server/api/exports/export-page.js +142 -0
  20. package/server/api/exports/handoff.js +147 -0
  21. package/server/api/exports.js +690 -0
  22. package/server/api/instruction.js +75 -0
  23. package/server/api/local.js +115 -0
  24. package/server/api/me.js +116 -0
  25. package/server/api/memory.js +185 -0
  26. package/server/api/pending-changes.js +366 -0
  27. package/server/api/plugins.js +164 -0
  28. package/server/api/projects.js +220 -0
  29. package/server/api/publish.js +64 -0
  30. package/server/api/recent.js +73 -0
  31. package/server/api/sessions.js +589 -0
  32. package/server/api/skills.js +77 -0
  33. package/server/api/standalone-fit.js +168 -0
  34. package/server/api/turn-compose.js +180 -0
  35. package/server/api/turn-inflight.js +44 -0
  36. package/server/api/turn.js +669 -0
  37. package/server/auth/README.md +17 -0
  38. package/server/auth/middleware.js +138 -0
  39. package/server/auth/origin-guard.js +71 -0
  40. package/server/auth/session.js +108 -0
  41. package/server/auth/tier.js +113 -0
  42. package/server/auth/users-store.js +348 -0
  43. package/server/edit/README.md +29 -0
  44. package/server/engine/README.md +113 -0
  45. package/server/engine/agent/agent-shared.js +550 -0
  46. package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -0
  47. package/server/engine/agent/auto-mode-rules.js +78 -0
  48. package/server/engine/agent/context.js +418 -0
  49. package/server/engine/agent/events.js +464 -0
  50. package/server/engine/agent/hooks/canvas-validate.js +199 -0
  51. package/server/engine/agent/hooks/failure.js +120 -0
  52. package/server/engine/agent/hooks/file-events.js +75 -0
  53. package/server/engine/agent/hooks/lifecycle.js +233 -0
  54. package/server/engine/agent/hooks/post-canvas-focus.js +80 -0
  55. package/server/engine/agent/hooks/post-guidance.js +206 -0
  56. package/server/engine/agent/hooks/post-subagent-report.js +104 -0
  57. package/server/engine/agent/hooks/post-trim.js +58 -0
  58. package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -0
  59. package/server/engine/agent/hooks/pre-defaults.js +84 -0
  60. package/server/engine/agent/hooks/pre-injectors.js +277 -0
  61. package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -0
  62. package/server/engine/agent/hooks/pre-starter-files.js +61 -0
  63. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -0
  64. package/server/engine/agent/hooks/site-validate.js +94 -0
  65. package/server/engine/agent/hooks/tool-prompts.js +31 -0
  66. package/server/engine/agent/hooks/turn-state-memory.js +56 -0
  67. package/server/engine/agent/hooks/user-prompt-submit.js +215 -0
  68. package/server/engine/agent/hooks.js +354 -0
  69. package/server/engine/agent/init-contract.js +69 -0
  70. package/server/engine/agent/isolation.js +159 -0
  71. package/server/engine/agent/model-context.js +359 -0
  72. package/server/engine/agent/model-table.js +292 -0
  73. package/server/engine/agent/plugin-loader.js +266 -0
  74. package/server/engine/agent/prompts/nodesign-prelude.md +336 -0
  75. package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -0
  76. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -0
  77. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -0
  78. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -0
  79. package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -0
  80. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -0
  81. package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -0
  82. package/server/engine/agent/prompts/tools/site-reference.md +106 -0
  83. package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -0
  84. package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -0
  85. package/server/engine/agent/session-loop.js +1024 -0
  86. package/server/engine/agent/session-model.js +164 -0
  87. package/server/engine/agent/skill.js +204 -0
  88. package/server/engine/agent/system-prompts.js +86 -0
  89. package/server/engine/agent/task-events.js +78 -0
  90. package/server/engine/agents/ds-extractor.md +132 -0
  91. package/server/engine/agents/explorer.md +190 -0
  92. package/server/engine/agents/index.js +230 -0
  93. package/server/engine/agents/schemas/design-system.json +156 -0
  94. package/server/engine/agents/schemas/tweak-schema.json +113 -0
  95. package/server/engine/agents/tweak-proposer.md +127 -0
  96. package/server/engine/agents/vision-checker.md +254 -0
  97. package/server/engine/browse/capture.js +422 -0
  98. package/server/engine/browse/card.js +137 -0
  99. package/server/engine/browse/handover.js +59 -0
  100. package/server/engine/browse/page-digest.js +119 -0
  101. package/server/engine/browse/refs.js +196 -0
  102. package/server/engine/browse/registry.js +307 -0
  103. package/server/engine/browse/screencast.js +170 -0
  104. package/server/engine/browse/state.js +121 -0
  105. package/server/engine/chatai/chat-log.js +79 -0
  106. package/server/engine/chatai/index.js +193 -0
  107. package/server/engine/chatai/openai-compat.js +152 -0
  108. package/server/engine/chatai/orchestrate.js +309 -0
  109. package/server/engine/chatai/perform.js +40 -0
  110. package/server/engine/chatai/summarize.js +79 -0
  111. package/server/engine/mcp/capability-gate.js +52 -0
  112. package/server/engine/mcp/index.js +339 -0
  113. package/server/engine/mcp/param-sanitizer.js +142 -0
  114. package/server/engine/mcp/tools/arrange-on-board.js +124 -0
  115. package/server/engine/mcp/tools/artifact-session.js +278 -0
  116. package/server/engine/mcp/tools/browse-computer.js +421 -0
  117. package/server/engine/mcp/tools/browse-find-batch.js +183 -0
  118. package/server/engine/mcp/tools/browse-screenshot.js +157 -0
  119. package/server/engine/mcp/tools/browse.js +480 -0
  120. package/server/engine/mcp/tools/build-docx.js +101 -0
  121. package/server/engine/mcp/tools/clear-pending-changes.js +99 -0
  122. package/server/engine/mcp/tools/create-on-board.js +155 -0
  123. package/server/engine/mcp/tools/crystallize-skill.js +168 -0
  124. package/server/engine/mcp/tools/deliver-files.js +0 -0
  125. package/server/engine/mcp/tools/explain-style.js +237 -0
  126. package/server/engine/mcp/tools/export-handoff.js +133 -0
  127. package/server/engine/mcp/tools/expose-tweaks.js +149 -0
  128. package/server/engine/mcp/tools/generate-image.js +842 -0
  129. package/server/engine/mcp/tools/get-computed-styles.js +164 -0
  130. package/server/engine/mcp/tools/get-pending-changes.js +205 -0
  131. package/server/engine/mcp/tools/h3box-ssh.js +84 -0
  132. package/server/engine/mcp/tools/helpers/acquire-page.js +82 -0
  133. package/server/engine/mcp/tools/helpers/motion-lab.js +554 -0
  134. package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -0
  135. package/server/engine/mcp/tools/helpers/perception-page.js +215 -0
  136. package/server/engine/mcp/tools/helpers/reference-download.js +103 -0
  137. package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -0
  138. package/server/engine/mcp/tools/helpers/rembg.js +273 -0
  139. package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -0
  140. package/server/engine/mcp/tools/highlight.js +68 -0
  141. package/server/engine/mcp/tools/list-pages.js +222 -0
  142. package/server/engine/mcp/tools/lookup-tags.js +59 -0
  143. package/server/engine/mcp/tools/navigate-to-page.js +60 -0
  144. package/server/engine/mcp/tools/organize-board.js +64 -0
  145. package/server/engine/mcp/tools/paint-still.js +434 -0
  146. package/server/engine/mcp/tools/pin-to-board.js +130 -0
  147. package/server/engine/mcp/tools/preview-deck.js +130 -0
  148. package/server/engine/mcp/tools/profile-scroll.js +305 -0
  149. package/server/engine/mcp/tools/publish-site.js +102 -0
  150. package/server/engine/mcp/tools/query-elements.js +195 -0
  151. package/server/engine/mcp/tools/read-board.js +103 -0
  152. package/server/engine/mcp/tools/read-document.js +93 -0
  153. package/server/engine/mcp/tools/read-page.js +264 -0
  154. package/server/engine/mcp/tools/read-tavern-json.js +143 -0
  155. package/server/engine/mcp/tools/record-decision.js +140 -0
  156. package/server/engine/mcp/tools/relate-on-board.js +110 -0
  157. package/server/engine/mcp/tools/remove-background.js +391 -0
  158. package/server/engine/mcp/tools/report-issue.js +115 -0
  159. package/server/engine/mcp/tools/roll-film.js +266 -0
  160. package/server/engine/mcp/tools/screenshot-docx.js +138 -0
  161. package/server/engine/mcp/tools/screenshot-url.js +195 -0
  162. package/server/engine/mcp/tools/screenshot.js +583 -0
  163. package/server/engine/mcp/tools/tier-gate.js +96 -0
  164. package/server/engine/mcp/tools/trace-motion.js +215 -0
  165. package/server/engine/mcp/tools/web-search.js +548 -0
  166. package/server/engine/motion/inventory.js +349 -0
  167. package/server/engine/perception/session.js +235 -0
  168. package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -0
  169. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -0
  170. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -0
  171. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -0
  172. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -0
  173. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -0
  174. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -0
  175. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -0
  176. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -0
  177. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -0
  178. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -0
  179. package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -0
  180. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -0
  181. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -0
  182. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -0
  183. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -0
  184. package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -0
  185. package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -0
  186. package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -0
  187. package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -0
  188. package/server/engine/runs/active-runs.js +698 -0
  189. package/server/engine/runs/live-turn.js +252 -0
  190. package/server/engine/runs/store.js +364 -0
  191. package/server/engine/runs/turn-relay.js +217 -0
  192. package/server/engine/runtime/workspace.js +164 -0
  193. package/server/index.js +235 -0
  194. package/server/lib/artifact-file-path.js +80 -0
  195. package/server/lib/artifact-target.js +284 -0
  196. package/server/lib/asset-refs.js +239 -0
  197. package/server/lib/async-queue.js +104 -0
  198. package/server/lib/auto-relations.js +130 -0
  199. package/server/lib/binding-types.js +60 -0
  200. package/server/lib/board-kind-sizes.js +62 -0
  201. package/server/lib/board-relations.js +133 -0
  202. package/server/lib/browse-proxy.js +180 -0
  203. package/server/lib/canvas-id.js +36 -0
  204. package/server/lib/cover.js +227 -0
  205. package/server/lib/danbooru-tags.js +291 -0
  206. package/server/lib/doc-extract.js +156 -0
  207. package/server/lib/docx/build-from-source.js +260 -0
  208. package/server/lib/docx/build.js +490 -0
  209. package/server/lib/docx/dump-styles.js +287 -0
  210. package/server/lib/docx/fonts/nodesign-cjk.conf +164 -0
  211. package/server/lib/docx/merge-runs.js +129 -0
  212. package/server/lib/docx/numbering.js +218 -0
  213. package/server/lib/docx/order.js +152 -0
  214. package/server/lib/docx/rawzip.js +172 -0
  215. package/server/lib/docx/render.js +92 -0
  216. package/server/lib/docx/text-lint.js +225 -0
  217. package/server/lib/docx/tokens.js +335 -0
  218. package/server/lib/docx/units.js +29 -0
  219. package/server/lib/docx/xml.js +291 -0
  220. package/server/lib/docx-pages.js +187 -0
  221. package/server/lib/export-collect.js +289 -0
  222. package/server/lib/export-package.js +210 -0
  223. package/server/lib/html-srcset.js +145 -0
  224. package/server/lib/image-variant.js +460 -0
  225. package/server/lib/ingress/forward-openai-chat.js +305 -0
  226. package/server/lib/ingress/openai-chat.js +491 -0
  227. package/server/lib/ingress/session-notice.js +66 -0
  228. package/server/lib/ingress/session-routes.js +68 -0
  229. package/server/lib/ingress/slot-probe.js +130 -0
  230. package/server/lib/ingress/upstream-billing.js +52 -0
  231. package/server/lib/ingress/upstream-fail-streak.js +72 -0
  232. package/server/lib/ingress/upstream-truncation.js +48 -0
  233. package/server/lib/issues-store.js +182 -0
  234. package/server/lib/kinds/deck.js +72 -0
  235. package/server/lib/kinds/docx.js +283 -0
  236. package/server/lib/kinds/file-kinds.js +113 -0
  237. package/server/lib/kinds/index.js +202 -0
  238. package/server/lib/kinds/site.js +161 -0
  239. package/server/lib/model-ingress.js +543 -0
  240. package/server/lib/moderation.js +255 -0
  241. package/server/lib/notice-store.js +103 -0
  242. package/server/lib/plugin-install.js +104 -0
  243. package/server/lib/plugin-validator.js +721 -0
  244. package/server/lib/publish-store.js +115 -0
  245. package/server/lib/quick-summary.js +52 -0
  246. package/server/lib/quota.js +270 -0
  247. package/server/lib/rate-window.js +29 -0
  248. package/server/lib/reference-assets.js +92 -0
  249. package/server/lib/region-shot.js +135 -0
  250. package/server/lib/safe-path.js +73 -0
  251. package/server/lib/sdk-session.js +33 -0
  252. package/server/lib/showcase-store.js +92 -0
  253. package/server/lib/site-publish.js +465 -0
  254. package/server/lib/ssrf-guard.js +432 -0
  255. package/server/lib/task-scan.js +158 -0
  256. package/server/lib/tavern-json.js +154 -0
  257. package/server/lib/video-variant.js +237 -0
  258. package/server/lib/workspace-path.js +33 -0
  259. package/server/ops/fix-sdk-musl.mjs +40 -0
  260. package/server/ops/install-macos-fonts.sh +114 -0
  261. package/server/ops/macos-fonts.conf +336 -0
  262. package/server/ops/sandbox-shim/bwrap +57 -0
  263. package/server/projects/assets-summary.js +119 -0
  264. package/server/projects/auto-name.js +53 -0
  265. package/server/projects/board-store.js +640 -0
  266. package/server/projects/move-entry.js +103 -0
  267. package/server/projects/store.js +270 -0
  268. package/server/projects/ui-config.js +54 -0
  269. package/server/projects/workspace-templates.js +53 -0
  270. package/server/projects/workspace.js +1025 -0
  271. package/server/runtime/capabilities.js +153 -0
  272. package/server/runtime/local-config.js +177 -0
  273. package/server/runtime/local-env.js +88 -0
  274. package/server/runtime/platform.js +342 -0
  275. package/server/runtime/profile.js +72 -0
  276. package/server/services/rembg-launcher.js +313 -0
  277. package/server/services/rembg-service.py +334 -0
  278. package/server/shared/README.md +25 -0
  279. package/server/shared/deck.js +54 -0
  280. package/server/shared/time.js +49 -0
  281. package/server/style-pipeline/README.md +53 -0
  282. package/server/ws/broker.js +36 -0
  283. package/server/ws/browse-channel.js +177 -0
  284. package/server/ws/index.js +386 -0
  285. package/web/README.md +89 -0
  286. package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +11 -0
  287. package/web/dist/assets/DeckWindow-4G0xX7P3.js +16 -0
  288. package/web/dist/assets/DocxWindow-BXQsoPkh.js +16 -0
  289. package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  290. package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  291. package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  292. package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  293. package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  294. package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  295. package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  296. package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  297. package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  298. package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  299. package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  300. package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  301. package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  302. package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  303. package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  304. package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  305. package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  306. package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  307. package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  308. package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  309. package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  310. package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  311. package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  312. package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  313. package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  314. package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  315. package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  316. package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  317. package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  318. package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  319. package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  320. package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  321. package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  322. package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  323. package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  324. package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  325. package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  326. package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  327. package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  328. package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  329. package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  330. package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  331. package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  332. package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  333. package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  334. package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  335. package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  336. package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  337. package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  338. package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  339. package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  340. package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  341. package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  342. package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  343. package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  344. package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  345. package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  346. package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  347. package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  348. package/web/dist/assets/SiteWindow-D6eS-2gM.js +18 -0
  349. package/web/dist/assets/again-D8tfgKKO.webp +0 -0
  350. package/web/dist/assets/caveat-nd-CP6HlsNg.woff2 +0 -0
  351. package/web/dist/assets/clock-xJhvfYpS.webp +0 -0
  352. package/web/dist/assets/film-cut-BBKMwH7q.webp +0 -0
  353. package/web/dist/assets/film-still-BZ_DL9Tt.webp +0 -0
  354. package/web/dist/assets/index-C0KUf6Wd.css +1 -0
  355. package/web/dist/assets/index-Cbl3ATvp.js +2125 -0
  356. package/web/dist/assets/ink-desk-BRbxeDsR.webp +0 -0
  357. package/web/dist/assets/ink-night-Bz4BT3XH.webp +0 -0
  358. package/web/dist/assets/ink-portrait-C0DM4V8Q.webp +0 -0
  359. package/web/dist/assets/longcang-regular-C0uvTuKa.woff2 +0 -0
  360. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  361. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  362. package/web/dist/assets/pending-edit-apply-DTjo2geW.js +31 -0
  363. package/web/dist/assets/question-8H4HSqxh.webp +0 -0
  364. package/web/dist/assets/reject-BaPJqElj.webp +0 -0
  365. package/web/dist/assets/rotate-cw-LMT3x1Zj.js +11 -0
  366. package/web/dist/assets/rp-portrait-CbrGpew2.webp +0 -0
  367. package/web/dist/assets/rp-street-C4jVKjxx.webp +0 -0
  368. package/web/dist/assets/square-dashed-mouse-pointer-CBoAkfhi.js +11 -0
  369. package/web/dist/assets/tangle-DIxHVNuT.webp +0 -0
  370. package/web/dist/assets/thisone-BylZKglV.webp +0 -0
  371. package/web/dist/assets/thumb-DO1wfK1I.webp +0 -0
  372. package/web/dist/index.html +18 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * agent/isolation.js —— 会话隔离配置的单一来源(2026-08-15)
3
+ *
4
+ * 一件事被拆成两道闸,因为 SDK 的工具分两拨跑:
5
+ *
6
+ * ① sandbox → **只管 Bash**。bwrap 起独立 mount/net namespace:写只落工作区、
7
+ * 凭据读不到、env 里的 key 被 unset、AF_UNIX 与回环被切断、
8
+ * 外网走代理。开关在 runtime/platform.js(NODESIGN_SANDBOX=on)。
9
+ * ② settings.permissions.deny → 管 **Read / Grep / Glob / Write / Edit**。
10
+ * 这些是 SDK 进程内工具,根本不进 bwrap,沙盒对它们零作用。
11
+ *
12
+ * 缺一半等于没关。第三块(跨项目边界)在 hooks/pre-workspace-scope-guard.js ——
13
+ * deny 规则写不出"除了自己这个项目"(deny 压过 allow,项目又是动态新建的)。
14
+ *
15
+ * 08-15 开这套时真跑出来的四件事,改之前先读:
16
+ * 1. `dangerouslyDisableSandbox` 默认允许,agent 撞到偶发失败会自己拿它关沙盒
17
+ * —— 必须 allowUnsandboxedCommands:false 焊死。
18
+ * 2. deny 规则的路径必须是**双斜杠**绝对形式,单斜杠静默失效。
19
+ * 3. npm 默认缓存在 ~/.npm,沙盒里 HOME 不可写 → `npm i` EROFS,构建道整条断。
20
+ * 4. 目录级 denyRead 拦得住读文件,拦不住 `ls` 看文件名(接受:名字不是秘密)。
21
+ */
22
+
23
+ import path from 'node:path';
24
+ import os from 'node:os';
25
+ import fs from 'node:fs/promises';
26
+ import { platform } from '../../runtime/platform.js';
27
+ import { autoModeSettings } from './auto-mode-rules.js';
28
+
29
+ /**
30
+ * 会话要用的缓存/临时目录一次备齐(2026-08-19 从 session-loop 下沉)。
31
+ *
32
+ * - npmCacheDir:共用 npm 缓存(数据根下;内容寻址 + 完整性校验,跨会话共用
33
+ * 不构成投毒面)。沙盒里 HOME 不可写,不给它 `npm i` 直接 EROFS。
34
+ * - agentTmpDir:沙盒里 Bash 的 $TMPDIR。SDK 按 CLAUDE_CODE_TMPDIR 派生沙盒
35
+ * tmp(没设就 os.tmpdir()),而派生出来的 /tmp/claude 在 bwrap 里只读 ——
36
+ * agent 装个 py7zr 连撞四堵墙、venv 静默失败(iss_msz25m5p_v5so)。
37
+ * ⚠️ 必须短:SDK 二进制里有条 warn —— 这个路径超 ~30 字节时 AF_UNIX socket
38
+ * 放不下,子进程 $TMPDIR **静默回退**默认值(= 白配)。所以挂 /tmp/nd 下、
39
+ * 按项目分目录;沙盒里遮兄弟目录见 buildIsolationOptions。
40
+ * - pip 缓存指进 agentTmpDir/pip,不然每条 pip 命令先吐一段
41
+ * ~/.cache/pip 不可写的 WARNING 噪音。
42
+ *
43
+ * 目录必须先存在 —— bwrap 绑一个不存在的路径会起不来;建不出来也不拦会话。
44
+ * envPatch 直接摊进 sdkEnv。
45
+ */
46
+ export async function prepareAgentDirs({ dataRoot, projectId, sessionId }) {
47
+ const npmCacheDir = path.join(dataRoot, '.npm-cache');
48
+ const agentTmpRoot = path.join(os.tmpdir(), 'nd');
49
+ const agentTmpDir = path.join(agentTmpRoot, projectId || String(sessionId || 'anon').slice(0, 12));
50
+ for (const d of [npmCacheDir, path.join(agentTmpDir, 'pip')]) {
51
+ try { await fs.mkdir(d, { recursive: true }); } catch { /* 起不来也不该拦会话 */ }
52
+ }
53
+ return {
54
+ npmCacheDir,
55
+ agentTmpRoot,
56
+ agentTmpDir,
57
+ envPatch: {
58
+ npm_config_cache: npmCacheDir,
59
+ CLAUDE_CODE_TMPDIR: agentTmpDir,
60
+ PIP_CACHE_DIR: path.join(agentTmpDir, 'pip'),
61
+ },
62
+ };
63
+ }
64
+
65
+ /**
66
+ * bwrap 垫片的 env(PATH 前插一个目录,里面的 `bwrap` 是我们的包装脚本)
67
+ *
68
+ * 治的是 `apply-seccomp: unshare(CLONE_NEWUSER): Invalid argument` 这个偶发
69
+ * —— 内核级竞态,机器越闲越容易撞,实测每十几次 Bash 调用炸一次,而且会把
70
+ * agent 带进错误推断(有一次它干脆自己拿 `dangerouslyDisableSandbox` 绕过去)。
71
+ * 原理与安全不变量写在 server/ops/sandbox-shim/bwrap 里。
72
+ *
73
+ * 📮 上游 issue:https://github.com/anthropics/claude-code/issues/86928
74
+ * ⏳ **临时设施**:垫片认的是 SDK 内部的命令前缀,**SDK 一升级前缀变了就静默
75
+ * 失效**(原样透传 = 悄悄退回那个偶发,不报错)。升级后查
76
+ * `<数据根>/.sandbox-shim.log` 里 `rewrote=1` 的计数还在不在涨;
77
+ * 上游修好之后连这个函数带 ops/sandbox-shim/ 一起删掉。
78
+ * 关掉:`NODESIGN_SANDBOX_SHIM=off`。
79
+ */
80
+ export function sandboxShimEnv({ baseEnv = process.env, dataRoot } = {}) {
81
+ if (!platform.sandboxEnabled) return {};
82
+ if (process.env.NODESIGN_SANDBOX_SHIM === 'off') return {};
83
+ const dir = path.join(platform.repoRoot, 'server', 'ops', 'sandbox-shim');
84
+ return {
85
+ PATH: `${dir}${path.delimiter}${baseEnv.PATH || ''}`,
86
+ // 改写次数记一笔:SDK 升级后 pattern 变了会静默退回现状,靠这个计数发现
87
+ ...(dataRoot ? { NODESIGN_SHIM_LOG: path.join(dataRoot, '.sandbox-shim.log') } : {}),
88
+ };
89
+ }
90
+
91
+ /**
92
+ * @param {object} o
93
+ * @param {string} o.cwdRoot 会话工作区(= sharedRoot,扁平化之后同一个目录)
94
+ * @param {string} o.sharedRoot 项目共享根
95
+ * @param {string} o.npmCacheDir 共用 npm 缓存(在数据根下,必须可写可读)
96
+ * @param {string} [o.agentTmpRoot] 所有项目沙盒 tmp 的根(/tmp/nd)—— 整体遮读
97
+ * @param {string} [o.agentTmpDir] 本项目的沙盒 tmp(CLAUDE_CODE_TMPDIR 指它)——
98
+ * 开可写可读天窗。两个一起传或都不传。
99
+ * @param {string} o.dataRoot PROJECTS_DATA_ROOT
100
+ * @param {object} o.env 传给 SDK 的 env(凭据抹除按它的键名算)
101
+ * @returns {{ sandbox: object, settings: object }} 直接摊进 query options
102
+ */
103
+ export function buildIsolationOptions({ cwdRoot, sharedRoot, npmCacheDir, agentTmpRoot, agentTmpDir, dataRoot, env }) {
104
+ return {
105
+ sandbox: {
106
+ enabled: platform.sandboxEnabled,
107
+ failIfUnavailable: false,
108
+ // ⭐ 逃生门焊死:SDK 给 Bash 留了 `dangerouslyDisableSandbox` 参数,**默认允许**。
109
+ // 08-15 开沙盒当天就被 agent 自发用上了 —— 它撞到 apply-seccomp 偶发失败,
110
+ // 第三次自己带上这个参数关掉沙盒,然后读到了隔壁项目的文件(它老实汇报了,
111
+ // 所以我们看见了)。一个能被工具参数关掉的沙盒等于没有沙盒。
112
+ allowUnsandboxedCommands: false,
113
+ network: {
114
+ allowLocalBinding: false,
115
+ // 全域允许:这层留着不是为了管出口,是为了 Linux 上顺带切断 unix socket
116
+ // —— pm2 的 rpc.sock 就在家目录里,通了的话 `pm2 start` 能起一个沙盒外的
117
+ // 进程(完整逃逸)。只读挂载拦不住 socket 连接,实测过。
118
+ allowedDomains: ['*'],
119
+ },
120
+ filesystem: {
121
+ allowWrite: [
122
+ cwdRoot,
123
+ ...(sharedRoot ? [
124
+ path.join(sharedRoot, '.claude', 'agent-memory'),
125
+ path.join(sharedRoot, 'assets'),
126
+ ] : []),
127
+ npmCacheDir,
128
+ // 本项目的沙盒 tmp(CLAUDE_CODE_TMPDIR / $TMPDIR / pip 缓存都指这里)。
129
+ // 没有它 Bash 的 tmp 只读:venv 静默失败、pip --user EROFS、npm 装不上
130
+ ...(agentTmpDir ? [agentTmpDir] : []),
131
+ ],
132
+ denyWrite: ['/etc', '/usr', '/bin', '/sbin', '/private/etc'],
133
+ // 数据根整个盖住、再用 allowRead 给自己的工作区开天窗。
134
+ // ⚠️ 口径要准(08-18 上生产时实测纠正):`ls 数据根` **看得见别的项目的
135
+ // 目录名**(沙盒里实测列出了另一个 pid),拦住的是**进去读**——
136
+ // `cat <别人的>/board.json` 与 `ls <别人的>/` 都空。
137
+ // 原注释写的"连 ls 数据根都只看得见自己那一个条目"过强,是错的;
138
+ // platform.js 那边的说法才对:「目录级 denyRead 拦得住 cat,拦不住 ls
139
+ // 看文件名 —— 文件名不是秘密,接受」。
140
+ // agentTmpRoot(/tmp/nd)同 dataRoot 一个待遇:根整个遮住、自己的子目录
141
+ // 开天窗 —— 不遮的话 /tmp/nd/<别的项目>/ 就是跨项目读通道
142
+ denyRead: [...platform.credentialBlacklist(), dataRoot, ...(agentTmpRoot ? [agentTmpRoot] : [])],
143
+ allowRead: [cwdRoot, npmCacheDir, ...(agentTmpDir ? [agentTmpDir] : [])],
144
+ },
145
+ credentials: {
146
+ // filesystem 那层拦得住 `cat .env`,拦不住 `env` —— 服务端 process.env
147
+ // 原样继承给了 Bash 子进程,key 就明晃晃躺着。按名字在沙盒内 unset。
148
+ envVars: platform.secretEnvVarNames(env).map(name => ({ name, mode: 'deny' })),
149
+ },
150
+ },
151
+ settings: {
152
+ permissions: { deny: platform.protectedPathRules({ dataRoot }) },
153
+ // auto 模式的分类器规则(只在开了 auto 时注入,省得白占 settings)。
154
+ // ⚠️ 按节替换不是追加 —— 为什么只覆盖 environment / hard_deny 两节,
155
+ // 见 auto-mode-rules.js 文件头。
156
+ ...(platform.autoModeEnabled ? { autoMode: autoModeSettings() } : {}),
157
+ },
158
+ };
159
+ }
@@ -0,0 +1,359 @@
1
+ /**
2
+ * server/engine/agent/model-context.js — 模型的单一真相源(2026-08-19 重建)。
3
+ *
4
+ * 历史形态是三张平行表(APP_TO_SDK_MODEL / APP_MODEL_REAL_WINDOW / SELECTABLE_MODELS),
5
+ * 文件头自己就写着"写错一个字,两处都只会静默降级"。现在收成一张 MODELS 表 +
6
+ * 一张 UPSTREAMS 表,旧的每个导出都从表派生,加载时做一致性断言(撞车当场炸,
7
+ * 不静默)。
8
+ *
9
+ * ## 两条通路
10
+ *
11
+ * - **订阅**(没有 api 字段的行):模型真名 SDK 认识,session-loop 不注入任何
12
+ * ANTHROPIC_* env,binary 走 ~/.claude 的 OAuth。今天生产的全部流量。
13
+ * - **API**(有 api 字段的行):请求经 server/lib/model-ingress.js(进程内
14
+ * Anthropic 范式通用入口)打到上游。SDK 视角看到的是 sdkAlias(让它把
15
+ * context window 算对),入口在出口把 alias 还原成 wireModel、按上游换钥匙、
16
+ * 按行开怪癖修补(tool_result 图片提升等)。
17
+ *
18
+ * ## SDK spoofing 为什么存在(Kimi 时代的发现,机制不变)
19
+ *
20
+ * SDK binary 内部 model registry 不识别非 Claude 名 → rawMaxTokens fallback
21
+ * 200k → auto-compact 在 ~180k 触发,浪费上游真实容量。喂 SDK 一个它认识的
22
+ * 1M alias,autoCompactWindow=230400 真生效。SDK 序列化请求时会剥 `[1m]`
23
+ * 后缀,所以入口的反查表要同时认带后缀和不带后缀两种形态。
24
+ *
25
+ * ## 记账
26
+ *
27
+ * SDK 的 costUSD 按 alias 的 Claude 价目表算,API 模型全是虚价(Kimi 时代按
28
+ * Opus 价虚高 30×)。repriceUsageDeltas 把 usage key 还原成 appModel、按行内
29
+ * prices 重算 costUsd。行没填 prices = 沿用 SDK 虚价(接真流量前必须填价)。
30
+ *
31
+ * ⚠️ 硬约束:一个 sdkAlias 不能被两个 API 模型共用 —— 反查靠它,撞了整条
32
+ * 路由和记账都错。模块加载断言兜底。
33
+ */
34
+
35
+ import { can, localGenApproved, DENIAL } from '../../auth/tier.js';
36
+ import { platform } from '../../runtime/platform.js';
37
+ import { UPSTREAMS_BUILTIN, MODELS_BUILTIN, BRANDS } from './model-table.js';
38
+ import { loadLocalConfig } from '../../runtime/local-config.js';
39
+
40
+ export { BRANDS };
41
+
42
+ // ── 内置表 + 用户插槽合并(08-22)──
43
+ // 外部行(runtime/local-config.js)全部共用这一个 sdkAlias:表内唯一空着的 1M 订阅名。全表反查分不出它们,
44
+ // 靠 ingress/session-routes.js 会话优先路由(一个会话只认自己那行)。所以外部行**不进 WIRE_LOOKUP 的 alias 键**,
45
+ // 只按 id 可查;没注册会话的请求用这个 alias 发过来一律 502(探针要带会话前缀)。
46
+ export const EXTERNAL_SDK_ALIAS = 'claude-sonnet-4-6[1m]';
47
+ const external = loadLocalConfig();
48
+ export const UPSTREAMS = Object.freeze({ ...UPSTREAMS_BUILTIN, ...external.upstreams });
49
+ /** 配置条目 → 表行(字段名一一对应,见 local-config.js 文件头) */
50
+ function toExternalRow(m) {
51
+ const { id, label, desc, brand, window, uncensored, upstream, wireModel, fastModel, ...api } = m;
52
+ return Object.freeze({
53
+ id, window, brand, external: true, ...(uncensored ? { uncensored: true } : {}),
54
+ select: Object.freeze({ label, desc }),
55
+ api: Object.freeze({ upstream, wireModel, sdkAlias: EXTERNAL_SDK_ALIAS, fastModel: fastModel || id, ...api }),
56
+ });
57
+ }
58
+ const MODELS = Object.freeze([...MODELS_BUILTIN, ...external.models.map(toExternalRow)]);
59
+ /** 外部插槽被整条丢掉的原因(启动日志一份、GET /api/local/config 一份,同一个数组) */
60
+ export const MODEL_CONFIG_ERRORS = external.errors;
61
+
62
+ // ── 派生索引(模块加载时构建 + 断言)──
63
+ // 分级:内置行的错照旧当场炸(那是代码错);外部行的错丢行 + 记进 MODEL_CONFIG_ERRORS(那是用户配置错,别拉下整站)
64
+
65
+ const BY_ID = new Map();
66
+ /** wire 名(appModel / sdkAlias / alias 剥 [1m] 后缀形态)→ 行。入口反查用 */
67
+ const WIRE_LOOKUP = new Map();
68
+
69
+ function checkRow(row) {
70
+ if (!BRANDS.includes(row.brand)) throw new Error(`[model-context] ${row.id} 的 brand 必须是 BRANDS 之一:${row.brand}`);
71
+ if (!row.api) return;
72
+ if (!UPSTREAMS[row.api.upstream]) throw new Error(`[model-context] ${row.id} 指向不存在的 upstream: ${row.api.upstream}`);
73
+ // alias 必须是本表里的订阅 Claude 名 —— SDK 才认识、窗口才查得到
74
+ if (!row.api.sdkAlias || !BY_ID.has(row.api.sdkAlias) || BY_ID.get(row.api.sdkAlias).api) throw new Error(`[model-context] ${row.id} 的 sdkAlias 必须是表内订阅模型名:${row.api.sdkAlias}`);
75
+ const fast = BY_ID.get(row.api.fastModel);
76
+ if (!fast || !fast.api) throw new Error(`[model-context] ${row.id} 的 fastModel 必须是表内 API 模型:${row.api.fastModel}`);
77
+ }
78
+ for (const row of MODELS) {
79
+ if (BY_ID.has(row.id)) throw new Error(`[model-context] 模型 id 重复:${row.id}`);
80
+ BY_ID.set(row.id, row);
81
+ }
82
+ for (const row of MODELS) {
83
+ try { checkRow(row); } catch (err) {
84
+ if (!row.external) throw err;
85
+ BY_ID.delete(row.id); MODEL_CONFIG_ERRORS.push({ where: `models (${row.id})`, message: err.message }); continue;
86
+ }
87
+ if (!row.api) continue;
88
+ const keys = row.external ? [row.id] : [row.id, row.api.sdkAlias, row.api.sdkAlias.replace(/\[1m\]$/i, '')];
89
+ for (const k of keys) {
90
+ const prev = WIRE_LOOKUP.get(k);
91
+ if (prev && prev !== row) throw new Error(`[model-context] wire 名撞车:'${k}' 同时属于 ${prev.id} 和 ${row.id}(一个 sdkAlias 不能共用)`);
92
+ WIRE_LOOKUP.set(k, row);
93
+ }
94
+ }
95
+ if (MODEL_CONFIG_ERRORS.length) {
96
+ console.warn(`[model-context] 本地插槽配置有 ${MODEL_CONFIG_ERRORS.length} 处问题(对应条目已跳过)${external.path ? `:${external.path}` : ''}`);
97
+ for (const e of MODEL_CONFIG_ERRORS) console.warn(` - ${e.where}: ${e.message}`);
98
+ }
99
+
100
+ /** 当前进程里真正生效的外部行 id(配置页据此判「已生效 / 要重启」) */
101
+ export function externalModelIds() {
102
+ return [...BY_ID.values()].filter((r) => r.external).map((r) => r.id);
103
+ }
104
+
105
+ /** 一行在入口会以哪些 body.model 名出现(id / sdkAlias / 剥 [1m] 的 alias)。session-routes 会话优先匹配用;不认识的 id → [] */
106
+ export function wireNamesOf(appModel) {
107
+ const row = appModel ? BY_ID.get(appModel) : null;
108
+ if (!row) return [];
109
+ return row.api ? [row.id, row.api.sdkAlias, row.api.sdkAlias.replace(/\[1m\]$/i, '')] : [row.id];
110
+ }
111
+
112
+ // ── 旧导出(签名不变,全部改为查表)──
113
+
114
+ /**
115
+ * picker 的**全量**清单(含带闸门的行)。⚠️ 对外接口一律用
116
+ * `selectableModelsFor(user)`,直接用这个等于把闸门拆了。保留导出是因为它是
117
+ * 「表里哪些行可选」的唯一真相,闸门只是在它上面过滤。
118
+ */
119
+ export const SELECTABLE_MODELS = Object.freeze(
120
+ MODELS.filter((m) => m.select).map((m) => Object.freeze({ id: m.id, brand: m.brand, ...m.select })),
121
+ );
122
+
123
+ /** 这个 appModel 出自谁家(BRANDS 之一)。不认识的 id → null,调用方自己决定兜底,别猜。 */
124
+ export function brandOfModel(appModel) {
125
+ return BY_ID.get(appModel)?.brand || null;
126
+ }
127
+
128
+ /**
129
+ * 按用户过滤可选模型。两种闸不同语义(08-21):
130
+ * - `gate: 'localGen'`:**看不见**。只对 admin / 已批准本地产线的账号露出(同 roll_film 那套批准制)
131
+ * - `gate: 'subscription'`:**看得见选不了**。订阅 Claude 行对没有订阅资格的账号
132
+ * (auth/tier.js can(user,'subscription')=false:basic 档/公开注册号)仍在清单里,但带 `locked: true`;
133
+ * 用户拍板「选择器依旧在,无配额账户无法请求,并且弹框提示」—— 让人知道有更强的档、
134
+ * 怎么拿到(邀请码),而不是当它不存在
135
+ *
136
+ * ⚠️ 三处消费方必须都走它/allowedModelsFor:GET /model 的清单、PUT /model 的校验、
137
+ * turn.js 的模型校验。少一处就是一个绕过闸门的后门 —— 2026-08-19 的独立评审正是在
138
+ * turn.js 抓到过这种漏校验。校验用 allowedModelsFor(不含 locked),清单用本函数。
139
+ */
140
+ export const SUBSCRIPTION_LOCK_REASON = DENIAL.subscription;
141
+
142
+ export function hasSubscriptionAccess(user) { // 订阅 Claude 资格 = 档位能力(auth/tier.js);薄封装只为调用点读着顺
143
+ return can(user, 'subscription');
144
+ }
145
+
146
+ const upstreamKeyPresent = (row) => { if (!row.api) return !!platform.claudeAuthPresent(); const up = UPSTREAMS[row.api.upstream]; return !up || up.authStyle === 'none' || !!up.key || !!(up.keyEnv && process.env[up.keyEnv]); }; // 无 api = 内置 Claude 行:本地版看本机凭据
147
+ export function selectableModelsFor(user) {
148
+ const approved = localGenApproved(user); // 档位 + 逐人批准,同 paint_still / roll_film / 演出端点一把尺
149
+ const subscribed = hasSubscriptionAccess(user);
150
+ const out = [];
151
+ for (const m of SELECTABLE_MODELS) {
152
+ if (platform.isLocal && !upstreamKeyPresent(BY_ID.get(m.id))) continue; // 本地版藏没配钥匙的行(含没登录/没 key 时的内置 Claude 行);hosted 不过滤(缺钥匙让请求 502 fail-loud)
153
+ if (m.gate === 'localGen') { if (approved) out.push(m); continue; }
154
+ if (m.gate === 'subscription' && !subscribed) { out.push({ ...m, locked: true, lockReason: SUBSCRIPTION_LOCK_REASON }); continue; }
155
+ out.push(m);
156
+ }
157
+ return out;
158
+ }
159
+
160
+ /** 真能请求的(不含 locked)。PUT /model 与 turn.js 校验用这份 */
161
+ export function allowedModelsFor(user) {
162
+ return selectableModelsFor(user).filter((m) => !m.locked);
163
+ }
164
+
165
+ /** 这个模型对这个用户是「看得见选不了」吗(在清单里且 locked)。turn 拒绝时据此回 403 而不是 400 */
166
+ export function isModelLockedFor(user, appModel) {
167
+ return selectableModelsFor(user).some((m) => m.id === appModel && m.locked);
168
+ }
169
+
170
+ /**
171
+ * 这个用户没选过时用哪个:表里标 `default: true` 的行(08-21 = ox-alpha),它对该用户
172
+ * 不可选时退到第一个可选的。前端 picker 与新会话的兜底都问这条,不再各自硬编码。
173
+ */
174
+ export function defaultModelFor(user) {
175
+ const allowed = allowedModelsFor(user);
176
+ return (allowed.find((m) => m.default) || allowed[0])?.id || null;
177
+ }
178
+
179
+ /**
180
+ * 会话中途从 openai-chat 行(Ox)切到别的通路要拦(08-21 fable 评审 P3):转换层合成的 thinking 块
181
+ * 没有 signature,CLI 会把它们原样回传给 Anthropic → 400 invalid signature。返回拒绝理由或 null。
182
+ */
183
+ export function crossLaneSwitchReason(fromModel, toModel) {
184
+ if (!fromModel || !toModel || fromModel === toModel) return null;
185
+ const from = resolveWireModel(fromModel);
186
+ const to = resolveWireModel(toModel);
187
+ if (from?.protocol === 'openai-chat' && to?.protocol !== 'openai-chat') {
188
+ return '这个会话是在 Ox(免费)上开的,它的思考记录换到 Claude 会被拒收。想用 Claude 请新开一个会话';
189
+ }
190
+ return null;
191
+ }
192
+
193
+ /** 免费行(API 行且四价全 0):金额配额对它无意义,turn.js 改走按轮次的免费闸 */
194
+ export function modelIsFree(appModel) {
195
+ const p = BY_ID.get(appModel)?.api?.prices;
196
+ return !!p && ['input', 'output', 'cacheRead', 'cacheWrite'].every((k) => Number(p[k]) === 0);
197
+ }
198
+
199
+ /**
200
+ * 这个模型是不是跑在无审查权重上(表里的 `uncensored` 位)。
201
+ *
202
+ * 唯一消费方是 prelude 渲染:为 true 的行不注入「底线」那一节(见
203
+ * agent-shared.renderPrelude)。查表,未知名字一律 false —— 拼错一个字
204
+ * 只该退回**更严**的那一档,绝不能因为查不到就当成无审查。
205
+ */
206
+ export function isUncensoredModel(appModel) {
207
+ if (!appModel) return false;
208
+ return BY_ID.get(appModel)?.uncensored === true;
209
+ }
210
+
211
+ /** 决定 sdkOptions.model 喂什么。API 行给 alias;订阅/未知原样返回(让 SDK 自己 fallback) */
212
+ export function resolveSdkSpoofModel(appModel) {
213
+ if (!appModel) return appModel;
214
+ const row = BY_ID.get(appModel);
215
+ return row?.api ? row.api.sdkAlias : appModel;
216
+ }
217
+
218
+ /** 真实 context window。查表;未命中按 pattern fallback;都不匹配返 null */
219
+ export function resolveModelContextWindow(appModel) {
220
+ if (!appModel) return null;
221
+ const row = BY_ID.get(appModel);
222
+ if (row) return row.window;
223
+ if (/^kimi[-_]/i.test(appModel)) return 256_000;
224
+ if (/\[1m\]$/i.test(appModel)) return 1_000_000;
225
+ return null;
226
+ }
227
+
228
+ /**
229
+ * 按 model 选 thinking config(喂 sdkOptions.thinking)。
230
+ *
231
+ * API 行统一走 enabled+budget(older-model 路径)——真正的出口形态由
232
+ * model-ingress 按行内 thinking 字段决定('strip' 会把字段整个删掉),
233
+ * 这里给 SDK 的值只影响 SDK 内部行为,不到线上。
234
+ *
235
+ * 订阅行沿用原 regex 逻辑:
236
+ * - adaptive 一族:Opus 4.6+ / Sonnet 5+ / Fable / Mythos。
237
+ * ⚠️ Sonnet 5 起 budgetTokens 已被 API 移除(enabled+budget 会 400)。
238
+ * - display 必须显式 'summarized':默认 'omitted' 时 thinking 块是空文本,
239
+ * 前端思考期完全静默(2026-07-23 "失联"问题主因)。
240
+ */
241
+ export function pickThinkingConfig(model) {
242
+ const row = model ? BY_ID.get(model) : null;
243
+ if (row?.api) return { type: 'enabled', budgetTokens: 8192 };
244
+ if (model && /^claude-(?:opus-(?:4-[6789]|[5-9])|sonnet-[5-9]|fable|mythos)/.test(model)) {
245
+ return { type: 'adaptive', display: 'summarized' };
246
+ }
247
+ return { type: 'enabled', budgetTokens: 8192 };
248
+ }
249
+
250
+ // ── 新导出:路由 ──
251
+
252
+ /**
253
+ * 会话模型 → 通路描述。session-loop 据此决定 env 注入。
254
+ *
255
+ * `window` 要喂给 `CLAUDE_CODE_AUTO_COMPACT_WINDOW`:2026-08-19 盒上实测,
256
+ * SDK 的压缩窗口 = **min(该 env, 别名的 rawMaxTokens)**(getContextUsage 的
257
+ * autocompactSource 会从 model-default/auto 变成 env)。两个都得对:
258
+ * - 只靠别名:200k 名白扔容量,1M 名会一路涨到远超上游 n_ctx 然后炸
259
+ * - 只靠 env:会被别名的 rawMaxTokens 钳住(200k 别名 + env 262144 = 200000)
260
+ * 所以 sdkAlias 一律选 1M 档打底,真实值由这个 env 钉死。
261
+ *
262
+ * @returns {{ mode: 'subscription' } | {
263
+ * mode: 'api', appModel: string, sdkAlias: string, fastModel: string,
264
+ * window: number, upstreamId: string, upstream: object,
265
+ * }}
266
+ */
267
+ export function resolveModelRoute(appModel) {
268
+ const row = appModel ? BY_ID.get(appModel) : null;
269
+ if (!row?.api) return { mode: 'subscription' };
270
+ return {
271
+ mode: 'api',
272
+ appModel: row.id,
273
+ sdkAlias: row.api.sdkAlias,
274
+ fastModel: row.api.fastModel,
275
+ window: row.window,
276
+ upstreamId: row.api.upstream,
277
+ upstream: UPSTREAMS[row.api.upstream],
278
+ };
279
+ }
280
+
281
+ /**
282
+ * 入口反查:请求 body.model(可能是 appModel、sdkAlias 或剥了 [1m] 的 alias)
283
+ * → 该发往哪里、怎么修。查不到返回 null(入口 fail-loud 502,不静默转发)。
284
+ */
285
+ export function resolveWireModel(bodyModel) {
286
+ const row = typeof bodyModel === 'string' ? WIRE_LOOKUP.get(bodyModel) : null;
287
+ if (!row) return null;
288
+ return {
289
+ appModel: row.id,
290
+ wireModel: row.api.wireModel,
291
+ upstreamId: row.api.upstream,
292
+ upstream: UPSTREAMS[row.api.upstream],
293
+ thinking: row.api.thinking || 'strip',
294
+ liftImages: !!row.api.liftImages,
295
+ protocol: UPSTREAMS[row.api.upstream]?.protocol || 'anthropic',
296
+ reasoningEffort: row.api.reasoningEffort || null,
297
+ // helper 请求(标题生成 / auto 分类器 / 摘要 —— 凡 body.model 不是会话主行的)用的档位:
298
+ // 行内可写 helperReasoningEffort 显式指定,没写就 'low'(Ox 实测 low=0 reasoning token)。
299
+ // 主 agent 想多少是主行的事,helper 一句话的活不该跟着 high/max 想几分钟
300
+ helperReasoningEffort: row.api.helperReasoningEffort || (row.api.reasoningEffort ? 'low' : null),
301
+ maxOutput: row.api.maxOutput || null,
302
+ // 「只想了没说」的就地重发额度(lib/ingress/forward-openai-chat.js)。**按行配**而不是全局:
303
+ // 这是某个模型的体质问题不是协议的 —— 08-21 深夜实测当天 4 次全在 Ox 两个主行上(它吐第一个字前
304
+ // 要想很久,深想档一发约 45 秒,Zen 掐流的窗口就长),所以那两行放宽到 6 次 / 360 秒,别的行走
305
+ // 全局默认(env / 2 次 / 120 秒);helper 不放宽(一句话的活,重发只是白占上游)。
306
+ // ⭐天花板由 CLI 定:流式请求走 SDK 客户端 timeout 600 秒,而预算是**开新一发之前**查的 →
307
+ // 预算 + 单发最长挂起(实测 185 秒)必须 < 600 秒(配了断言,见 upstream-truncation.test.js)。
308
+ emptyRetries: Number.isFinite(row.api.emptyRetries) ? row.api.emptyRetries : null,
309
+ retryBudgetMs: Number.isFinite(row.api.retryBudgetMs) ? row.api.retryBudgetMs : null,
310
+ };
311
+ }
312
+
313
+ /**
314
+ * usage 差分 reprice:key 从 SDK alias 还原成 appModel,按行内 prices 重算
315
+ * costUsd。多个 key 归并到同一 appModel 时逐字段相加。context.js 的
316
+ * absorbResult 在差分之后调这一步。
317
+ *
318
+ * ⚠️ 必须带 sessionAppModel 且只对 API 会话生效:SDK 报的 usage key 是 alias,
319
+ * 而 alias 同时也是真实存在的订阅 Claude 名(sonnet-4-6[1m] 既是 Gemini 的
320
+ * spoof 也是一个真模型)——不看会话通路就 remap,订阅会话跑 sonnet-4-6 会被
321
+ * 错记成 Gemini 的账。订阅会话原样返回,一个字段都不动。
322
+ *
323
+ * @param {Record<string, {inputTokens, outputTokens, cacheReadTokens, cacheCreateTokens, costUsd}>} deltas
324
+ * @param {string} sessionAppModel 本会话的 appModel(AgentContext.appModel)
325
+ * @returns 同构对象(API 会话新建;订阅会话原样返回入参)
326
+ */
327
+ export function repriceUsageDeltas(deltas, sessionAppModel) {
328
+ if (!deltas || typeof deltas !== 'object') return deltas;
329
+ const sessionRow = sessionAppModel ? BY_ID.get(sessionAppModel) : null;
330
+ if (!sessionRow?.api) return deltas;
331
+ // API 会话的所有请求必经 ingress:表内 key 按表归;不在表里的 key(SDK 内部
332
+ // helper 用 config 默认 Claude 名发的请求)必然被 ingress 的会话 fast 兜底
333
+ // 承接 —— 归到 fastModel 头上是精确归因,不是猜测。
334
+ const fastRow = BY_ID.get(sessionRow.api.fastModel);
335
+ const out = {};
336
+ for (const [key, d] of Object.entries(deltas)) {
337
+ const row = WIRE_LOOKUP.get(key) || fastRow;
338
+ const appKey = row ? row.id : key;
339
+ const p = row?.api?.prices;
340
+ const repriced = p ? {
341
+ ...d,
342
+ costUsd: (
343
+ d.inputTokens * p.input
344
+ + d.outputTokens * p.output
345
+ + d.cacheReadTokens * (p.cacheRead || 0)
346
+ + d.cacheCreateTokens * (p.cacheWrite || 0)
347
+ ) / 1e6,
348
+ } : { ...d };
349
+ const prev = out[appKey];
350
+ out[appKey] = prev ? {
351
+ inputTokens: prev.inputTokens + repriced.inputTokens,
352
+ outputTokens: prev.outputTokens + repriced.outputTokens,
353
+ cacheReadTokens: prev.cacheReadTokens + repriced.cacheReadTokens,
354
+ cacheCreateTokens: prev.cacheCreateTokens + repriced.cacheCreateTokens,
355
+ costUsd: prev.costUsd + repriced.costUsd,
356
+ } : repriced;
357
+ }
358
+ return out;
359
+ }