@actalk/inkos-studio 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/dist/api/book-create.js +1 -1
  2. package/dist/api/book-create.js.map +1 -1
  3. package/dist/api/index.js +1 -1
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/server.d.ts.map +1 -1
  6. package/dist/api/server.js +748 -87
  7. package/dist/api/server.js.map +1 -1
  8. package/dist/assets/_baseUniq-C-g5htb0.js +1 -0
  9. package/dist/assets/abap-BdImnpbu.js +1 -0
  10. package/dist/assets/actionscript-3-CoDkCxhg.js +1 -0
  11. package/dist/assets/ada-bCR0ucgS.js +1 -0
  12. package/dist/assets/andromeeda-C4gqWexZ.js +1 -0
  13. package/dist/assets/angular-html-CU67Zn6k.js +1 -0
  14. package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
  15. package/dist/assets/apache-Pmp26Uib.js +1 -0
  16. package/dist/assets/apex-D8_7TLub.js +1 -0
  17. package/dist/assets/apl-dKokRX4l.js +1 -0
  18. package/dist/assets/applescript-Co6uUVPk.js +1 -0
  19. package/dist/assets/ara-BRHolxvo.js +1 -0
  20. package/dist/assets/arc-B13N8XoZ.js +1 -0
  21. package/dist/assets/architectureDiagram-Q4EWVU46-COxJdWXS.js +36 -0
  22. package/dist/assets/asciidoc-Ve4PFQV2.js +1 -0
  23. package/dist/assets/asm-D_Q5rh1f.js +1 -0
  24. package/dist/assets/astro-CbQHKStN.js +1 -0
  25. package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  26. package/dist/assets/awk-DMzUqQB5.js +1 -0
  27. package/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
  28. package/dist/assets/ayu-light-BA47KaF1.js +1 -0
  29. package/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
  30. package/dist/assets/ballerina-BFfxhgS-.js +1 -0
  31. package/dist/assets/bat-BkioyH1T.js +1 -0
  32. package/dist/assets/beancount-k_qm7-4y.js +1 -0
  33. package/dist/assets/berry-uYugtg8r.js +1 -0
  34. package/dist/assets/bibtex-CHM0blh-.js +1 -0
  35. package/dist/assets/bicep-Bmn6On1c.js +1 -0
  36. package/dist/assets/bird2-DPOp833l.js +1 -0
  37. package/dist/assets/blade-D4QpJJKB.js +1 -0
  38. package/dist/assets/blockDiagram-DXYQGD6D-DS_ZpHJ5.js +132 -0
  39. package/dist/assets/bsl-BO_Y6i37.js +1 -0
  40. package/dist/assets/c-BIGW1oBm.js +1 -0
  41. package/dist/assets/c3-eo99z4R2.js +1 -0
  42. package/dist/assets/c4Diagram-AHTNJAMY-BxSij0o0.js +10 -0
  43. package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
  44. package/dist/assets/cairo-KRGpt6FW.js +1 -0
  45. package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  46. package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  47. package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  48. package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  49. package/dist/assets/channel-C2r273Z1.js +1 -0
  50. package/dist/assets/chunk-4BX2VUAB-DFB21W8n.js +1 -0
  51. package/dist/assets/chunk-4TB4RGXK-BnJ77uxv.js +206 -0
  52. package/dist/assets/chunk-55IACEB6-CQ_glRcB.js +1 -0
  53. package/dist/assets/chunk-EDXVE4YY-Dyd-Q8PI.js +1 -0
  54. package/dist/assets/chunk-FMBD7UC4-BDiYToLm.js +15 -0
  55. package/dist/assets/chunk-OYMX7WX6-DEzfeklY.js +231 -0
  56. package/dist/assets/chunk-QZHKN3VN-CJPYUr7I.js +1 -0
  57. package/dist/assets/chunk-YZCP3GAM-CBxzODC9.js +1 -0
  58. package/dist/assets/clarity-D53aC0YG.js +1 -0
  59. package/dist/assets/classDiagram-6PBFFD2Q-CSUo3NoK.js +1 -0
  60. package/dist/assets/classDiagram-v2-HSJHXN6E-CSUo3NoK.js +1 -0
  61. package/dist/assets/clojure-P80f7IUj.js +1 -0
  62. package/dist/assets/clone-DJaLNYqk.js +1 -0
  63. package/dist/assets/cmake-D1j8_8rp.js +1 -0
  64. package/dist/assets/cobol-nwyudZeR.js +1 -0
  65. package/dist/assets/codeowners-Bp6g37R7.js +1 -0
  66. package/dist/assets/codeql-DsOJ9woJ.js +1 -0
  67. package/dist/assets/coffee-Ch7k5sss.js +1 -0
  68. package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
  69. package/dist/assets/coq-DkFqJrB1.js +1 -0
  70. package/dist/assets/cose-bilkent-S5V4N54A-BDcwl962.js +1 -0
  71. package/dist/assets/cpp-CofmeUqb.js +1 -0
  72. package/dist/assets/crystal-tKQVLTB8.js +1 -0
  73. package/dist/assets/csharp-COcwbKMJ.js +1 -0
  74. package/dist/assets/css-DPfMkruS.js +1 -0
  75. package/dist/assets/csv-fuZLfV_i.js +1 -0
  76. package/dist/assets/cue-D82EKSYY.js +1 -0
  77. package/dist/assets/cypher-COkxafJQ.js +1 -0
  78. package/dist/assets/cytoscape.esm-DxGcaOPV.js +331 -0
  79. package/dist/assets/d-85-TOEBH.js +1 -0
  80. package/dist/assets/dagre-KV5264BT-q3iXqwp5.js +4 -0
  81. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  82. package/dist/assets/dart-CF10PKvl.js +1 -0
  83. package/dist/assets/dax-CEL-wOlO.js +1 -0
  84. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  85. package/dist/assets/desktop-BmXAJ9_W.js +1 -0
  86. package/dist/assets/diagram-5BDNPKRD-CSyQp4XD.js +10 -0
  87. package/dist/assets/diagram-G4DWMVQ6-CyO2u0j-.js +24 -0
  88. package/dist/assets/diagram-MMDJMWI5-0Rum0AK-.js +43 -0
  89. package/dist/assets/diagram-TYMM5635-CpFFDrQG.js +24 -0
  90. package/dist/assets/diff-D97Zzqfu.js +1 -0
  91. package/dist/assets/docker-BcOcwvcX.js +1 -0
  92. package/dist/assets/dotenv-Da5cRb03.js +1 -0
  93. package/dist/assets/dracula-BzJJZx-M.js +1 -0
  94. package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  95. package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
  96. package/dist/assets/edge-BkV0erSs.js +1 -0
  97. package/dist/assets/elixir-CDX3lj18.js +1 -0
  98. package/dist/assets/elm-DbKCFpqz.js +1 -0
  99. package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
  100. package/dist/assets/erDiagram-SMLLAGMA-DgPF_t0E.js +85 -0
  101. package/dist/assets/erb-B12qg9BL.js +1 -0
  102. package/dist/assets/erlang-DsQrWhSR.js +1 -0
  103. package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  104. package/dist/assets/everforest-light-C8M2exoo.js +1 -0
  105. package/dist/assets/fennel-BYunw83y.js +1 -0
  106. package/dist/assets/fish-BvzEVeQv.js +1 -0
  107. package/dist/assets/flowDiagram-DWJPFMVM-D1sVXEs4.js +162 -0
  108. package/dist/assets/fluent-C4IJs8-o.js +1 -0
  109. package/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  110. package/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
  111. package/dist/assets/fsharp-CXgrBDvD.js +1 -0
  112. package/dist/assets/ganttDiagram-T4ZO3ILL-Cah2BN0H.js +292 -0
  113. package/dist/assets/gdresource-BOOCDP_w.js +1 -0
  114. package/dist/assets/gdscript-C5YyOfLZ.js +1 -0
  115. package/dist/assets/gdshader-DkwncUOv.js +1 -0
  116. package/dist/assets/genie-D0YGMca9.js +1 -0
  117. package/dist/assets/gherkin-DyxjwDmM.js +1 -0
  118. package/dist/assets/git-commit-F4YmCXRG.js +1 -0
  119. package/dist/assets/git-rebase-r7XF79zn.js +1 -0
  120. package/dist/assets/gitGraphDiagram-UUTBAWPF-Bb32ArKL.js +106 -0
  121. package/dist/assets/github-dark-DHJKELXO.js +1 -0
  122. package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  123. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  124. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  125. package/dist/assets/github-light-DAi9KRSo.js +1 -0
  126. package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  127. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  128. package/dist/assets/gleam-BspZqrRM.js +1 -0
  129. package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
  130. package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
  131. package/dist/assets/glsl-DplSGwfg.js +1 -0
  132. package/dist/assets/gn-n2N0HUVH.js +1 -0
  133. package/dist/assets/gnuplot-DdkO51Og.js +1 -0
  134. package/dist/assets/go-CxLEBnE3.js +1 -0
  135. package/dist/assets/graph-DPYgoB8M.js +1 -0
  136. package/dist/assets/graphql-ChdNCCLP.js +1 -0
  137. package/dist/assets/groovy-gcz8RCvz.js +1 -0
  138. package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  139. package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  140. package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  141. package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  142. package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  143. package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  144. package/dist/assets/hack-CaT9iCJl.js +1 -0
  145. package/dist/assets/haml-B8DHNrY2.js +1 -0
  146. package/dist/assets/handlebars-BL8al0AC.js +1 -0
  147. package/dist/assets/haskell-Df6bDoY_.js +1 -0
  148. package/dist/assets/haxe-CzTSHFRz.js +1 -0
  149. package/dist/assets/hcl-BWvSN4gD.js +1 -0
  150. package/dist/assets/highlighted-body-OFNGDK62-C5OR7F5H.js +1 -0
  151. package/dist/assets/hjson-D5-asLiD.js +1 -0
  152. package/dist/assets/hlsl-D3lLCCz7.js +1 -0
  153. package/dist/assets/horizon-BUw7H-hv.js +1 -0
  154. package/dist/assets/horizon-bright-Cn-bp-IR.js +1 -0
  155. package/dist/assets/houston-DnULxvSX.js +1 -0
  156. package/dist/assets/html-GMplVEZG.js +1 -0
  157. package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
  158. package/dist/assets/http-jrhK8wxY.js +1 -0
  159. package/dist/assets/hurl-irOxFIW8.js +1 -0
  160. package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
  161. package/dist/assets/hy-DFXneXwc.js +1 -0
  162. package/dist/assets/imba-DGztddWO.js +1 -0
  163. package/dist/assets/index-CdSd4xAM.js +1267 -0
  164. package/dist/assets/index-hHRX6lZN.css +1 -0
  165. package/dist/assets/infoDiagram-42DDH7IO-BRPlXDRX.js +2 -0
  166. package/dist/assets/ini-BEwlwnbL.js +1 -0
  167. package/dist/assets/init-Gi6I4Gst.js +1 -0
  168. package/dist/assets/ishikawaDiagram-UXIWVN3A-DIMNZ5LJ.js +70 -0
  169. package/dist/assets/java-CylS5w8V.js +1 -0
  170. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  171. package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
  172. package/dist/assets/jison-wvAkD_A8.js +1 -0
  173. package/dist/assets/journeyDiagram-VCZTEJTY-CQ9Buybm.js +139 -0
  174. package/dist/assets/json-Cp-IABpG.js +1 -0
  175. package/dist/assets/json5-C9tS-k6U.js +1 -0
  176. package/dist/assets/jsonc-Des-eS-w.js +1 -0
  177. package/dist/assets/jsonl-DcaNXYhu.js +1 -0
  178. package/dist/assets/jsonnet-DFQXde-d.js +1 -0
  179. package/dist/assets/jssm-C2t-YnRu.js +1 -0
  180. package/dist/assets/jsx-g9-lgVsj.js +1 -0
  181. package/dist/assets/julia-CxzCAyBv.js +1 -0
  182. package/dist/assets/just-Cw27pwNe.js +1 -0
  183. package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  184. package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  185. package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
  186. package/dist/assets/kanban-definition-6JOO6SKY-CsZuyfP0.js +89 -0
  187. package/dist/assets/kdl-DV7GczEv.js +1 -0
  188. package/dist/assets/kotlin-BdnUsdx6.js +1 -0
  189. package/dist/assets/kusto-DZf3V79B.js +1 -0
  190. package/dist/assets/laserwave-DUszq2jm.js +1 -0
  191. package/dist/assets/latex-CWtU0Tv5.js +1 -0
  192. package/dist/assets/layout-wiYzOkdn.js +1 -0
  193. package/dist/assets/lean-BZvkOJ9d.js +1 -0
  194. package/dist/assets/less-B1dDrJ26.js +1 -0
  195. package/dist/assets/light-plus-B7mTdjB0.js +1 -0
  196. package/dist/assets/linear-D2SNljoC.js +1 -0
  197. package/dist/assets/liquid-DYVedYrR.js +1 -0
  198. package/dist/assets/llvm-DjAJT7YJ.js +1 -0
  199. package/dist/assets/log-2UxHyX5q.js +1 -0
  200. package/dist/assets/logo-BtOb2qkB.js +1 -0
  201. package/dist/assets/lua-BaeVxFsk.js +1 -0
  202. package/dist/assets/luau-C-HG3fhB.js +1 -0
  203. package/dist/assets/make-CHLpvVh8.js +1 -0
  204. package/dist/assets/markdown-Cvjx9yec.js +1 -0
  205. package/dist/assets/marko-CnJfTvn9.js +1 -0
  206. package/dist/assets/material-theme-D5KoaKCx.js +1 -0
  207. package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  208. package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  209. package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  210. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  211. package/dist/assets/matlab-D7o27uSR.js +1 -0
  212. package/dist/assets/mdc-BMNejdWA.js +1 -0
  213. package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  214. package/dist/assets/mermaid-mWjccvbQ.js +1 -0
  215. package/dist/assets/min-NQHw0HnA.js +1 -0
  216. package/dist/assets/min-dark-CafNBF8u.js +1 -0
  217. package/dist/assets/min-light-CTRr51gU.js +1 -0
  218. package/dist/assets/mindmap-definition-QFDTVHPH-CLFneoyC.js +96 -0
  219. package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
  220. package/dist/assets/mojo-rZm6bMo-.js +1 -0
  221. package/dist/assets/monokai-D4h5O-jR.js +1 -0
  222. package/dist/assets/moonbit-_H4v1dQx.js +1 -0
  223. package/dist/assets/move-IF9eRakj.js +1 -0
  224. package/dist/assets/narrat-DRg8JJMk.js +1 -0
  225. package/dist/assets/nextflow-Zz6hmt5N.js +1 -0
  226. package/dist/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  227. package/dist/assets/nginx-BpAMiNFr.js +1 -0
  228. package/dist/assets/night-owl-C39BiMTA.js +1 -0
  229. package/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
  230. package/dist/assets/nim-CVrawwO9.js +1 -0
  231. package/dist/assets/nix-CwoSXNpI.js +1 -0
  232. package/dist/assets/nord-Ddv68eIx.js +1 -0
  233. package/dist/assets/nushell-Cz2AlsmD.js +1 -0
  234. package/dist/assets/objective-c-DXmwc3jG.js +1 -0
  235. package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
  236. package/dist/assets/ocaml-C0hk2d4L.js +1 -0
  237. package/dist/assets/odin-BBf5iR-q.js +1 -0
  238. package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  239. package/dist/assets/one-light-C3Wv6jpd.js +1 -0
  240. package/dist/assets/openscad-C4EeE6gA.js +1 -0
  241. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  242. package/dist/assets/pascal-D93ZcfNL.js +1 -0
  243. package/dist/assets/perl-C0TMdlhV.js +1 -0
  244. package/dist/assets/php-Dhbhpdrm.js +1 -0
  245. package/dist/assets/pieDiagram-DEJITSTG-CGetk0td.js +30 -0
  246. package/dist/assets/pkl-u5AG7uiY.js +1 -0
  247. package/dist/assets/plastic-3e1v2bzS.js +1 -0
  248. package/dist/assets/plsql-ChMvpjG-.js +1 -0
  249. package/dist/assets/po-BTJTHyun.js +1 -0
  250. package/dist/assets/poimandres-CS3Unz2-.js +1 -0
  251. package/dist/assets/polar-C0HS_06l.js +1 -0
  252. package/dist/assets/postcss-CXtECtnM.js +1 -0
  253. package/dist/assets/powerquery-CEu0bR-o.js +1 -0
  254. package/dist/assets/powershell-Dpen1YoG.js +1 -0
  255. package/dist/assets/prisma-Dd19v3D-.js +1 -0
  256. package/dist/assets/prolog-CbFg5uaA.js +1 -0
  257. package/dist/assets/proto-C7zT0LnQ.js +1 -0
  258. package/dist/assets/pug-CGlum2m_.js +1 -0
  259. package/dist/assets/puppet-BMWR74SV.js +1 -0
  260. package/dist/assets/purescript-CklMAg4u.js +1 -0
  261. package/dist/assets/python-B6aJPvgy.js +1 -0
  262. package/dist/assets/qml-3beO22l8.js +1 -0
  263. package/dist/assets/qmldir-C8lEn-DE.js +1 -0
  264. package/dist/assets/qss-IeuSbFQv.js +1 -0
  265. package/dist/assets/quadrantDiagram-34T5L4WZ-DGvGQ55s.js +7 -0
  266. package/dist/assets/r-Dspwwk_N.js +1 -0
  267. package/dist/assets/racket-BqYA7rlc.js +1 -0
  268. package/dist/assets/raku-DXvB9xmW.js +1 -0
  269. package/dist/assets/razor-Uh8Bk_45.js +1 -0
  270. package/dist/assets/red-bN70gL4F.js +1 -0
  271. package/dist/assets/reg-C-SQnVFl.js +1 -0
  272. package/dist/assets/regexp-CDVJQ6XC.js +1 -0
  273. package/dist/assets/rel-C3B-1QV4.js +1 -0
  274. package/dist/assets/requirementDiagram-MS252O5E-BtWYKfId.js +84 -0
  275. package/dist/assets/riscv-BM1_JUlF.js +1 -0
  276. package/dist/assets/ron-D8l8udqQ.js +1 -0
  277. package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  278. package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  279. package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
  280. package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
  281. package/dist/assets/rst-BrH8l1NY.js +1 -0
  282. package/dist/assets/ruby-Dw2BHqvy.js +1 -0
  283. package/dist/assets/rust-B1yitclQ.js +1 -0
  284. package/dist/assets/sankeyDiagram-XADWPNL6-B7dHCFEb.js +10 -0
  285. package/dist/assets/sas-cz2c8ADy.js +1 -0
  286. package/dist/assets/sass-Cj5Yp3dK.js +1 -0
  287. package/dist/assets/scala-C151Ov-r.js +1 -0
  288. package/dist/assets/scheme-C98Dy4si.js +1 -0
  289. package/dist/assets/scss-OYdSNvt2.js +1 -0
  290. package/dist/assets/sdbl-DVxCFoDh.js +1 -0
  291. package/dist/assets/sequenceDiagram-FGHM5R23-SCghmIaN.js +157 -0
  292. package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
  293. package/dist/assets/shellscript-Yzrsuije.js +1 -0
  294. package/dist/assets/shellsession-BADoaaVG.js +1 -0
  295. package/dist/assets/slack-dark-BthQWCQV.js +1 -0
  296. package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  297. package/dist/assets/smalltalk-BERRCDM3.js +1 -0
  298. package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  299. package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
  300. package/dist/assets/solarized-light-L9t79GZl.js +1 -0
  301. package/dist/assets/solidity-rGO070M0.js +1 -0
  302. package/dist/assets/soy-Brmx7dQM.js +1 -0
  303. package/dist/assets/sparql-rVzFXLq3.js +1 -0
  304. package/dist/assets/splunk-BtCnVYZw.js +1 -0
  305. package/dist/assets/sql-BLtJtn59.js +1 -0
  306. package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
  307. package/dist/assets/stata-BH5u7GGu.js +1 -0
  308. package/dist/assets/stateDiagram-FHFEXIEX-Da-qWGV4.js +1 -0
  309. package/dist/assets/stateDiagram-v2-QKLJ7IA2-EC5r6wTi.js +1 -0
  310. package/dist/assets/stylus-BEDo0Tqx.js +1 -0
  311. package/dist/assets/surrealql-Bq5Q-fJD.js +1 -0
  312. package/dist/assets/svelte-C_ipcX3V.js +1 -0
  313. package/dist/assets/swift-D82vCrfD.js +1 -0
  314. package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  315. package/dist/assets/system-verilog-CnnmHF94.js +1 -0
  316. package/dist/assets/systemd-4A_iFExJ.js +1 -0
  317. package/dist/assets/talonscript-CkByrt1z.js +1 -0
  318. package/dist/assets/tasl-QIJgUcNo.js +1 -0
  319. package/dist/assets/tcl-dwOrl1Do.js +1 -0
  320. package/dist/assets/templ-P3uqSqPl.js +1 -0
  321. package/dist/assets/terraform-BETggiCN.js +1 -0
  322. package/dist/assets/tex-idrVyKtj.js +1 -0
  323. package/dist/assets/timeline-definition-GMOUNBTQ-DXzhFcp9.js +120 -0
  324. package/dist/assets/tokyo-night-hegEt444.js +1 -0
  325. package/dist/assets/toml-vGWfd6FD.js +1 -0
  326. package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
  327. package/dist/assets/tsv-B_m7g4N7.js +1 -0
  328. package/dist/assets/tsx-COt5Ahok.js +1 -0
  329. package/dist/assets/turtle-BsS91CYL.js +1 -0
  330. package/dist/assets/twig-DNn4PbVi.js +1 -0
  331. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  332. package/dist/assets/typespec-BGHnOYBU.js +1 -0
  333. package/dist/assets/typst-DHCkPAjA.js +1 -0
  334. package/dist/assets/v-BcVCzyr7.js +1 -0
  335. package/dist/assets/vala-CsfeWuGM.js +1 -0
  336. package/dist/assets/vb-D17OF-Vu.js +1 -0
  337. package/dist/assets/vennDiagram-DHZGUBPP-CHwAxJ-d.js +34 -0
  338. package/dist/assets/verilog-BQ8w6xss.js +1 -0
  339. package/dist/assets/vesper-DU1UobuO.js +1 -0
  340. package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
  341. package/dist/assets/viml-CJc9bBzg.js +1 -0
  342. package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  343. package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  344. package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  345. package/dist/assets/vue-DN_0RTcg.js +1 -0
  346. package/dist/assets/vue-html-AaS7Mt5G.js +1 -0
  347. package/dist/assets/vue-vine-CQOfvN7w.js +1 -0
  348. package/dist/assets/vyper-CDx5xZoG.js +1 -0
  349. package/dist/assets/wardley-RL74JXVD-DOtmpHBm.js +162 -0
  350. package/dist/assets/wardleyDiagram-NUSXRM2D-DqzDdtmB.js +20 -0
  351. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  352. package/dist/assets/wasm-MzD3tlZU.js +1 -0
  353. package/dist/assets/wenyan-BV7otONQ.js +1 -0
  354. package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
  355. package/dist/assets/wikitext-BhOHFoWU.js +1 -0
  356. package/dist/assets/wit-5i3qLPDT.js +1 -0
  357. package/dist/assets/wolfram-lXgVvXCa.js +1 -0
  358. package/dist/assets/xml-sdJ4AIDG.js +1 -0
  359. package/dist/assets/xsl-CtQFsRM5.js +1 -0
  360. package/dist/assets/xychartDiagram-5P7HB3ND-B5UU8au-.js +7 -0
  361. package/dist/assets/yaml-Buea-lGh.js +1 -0
  362. package/dist/assets/zenscript-DVFEvuxE.js +1 -0
  363. package/dist/assets/zig-VOosw3JB.js +1 -0
  364. package/dist/index.html +2 -2
  365. package/package.json +22 -4
  366. package/dist/assets/index-CrV75dEH.css +0 -1
  367. package/dist/assets/index-tV8pf10O.js +0 -395
@@ -2,12 +2,68 @@ import { Hono } from "hono";
2
2
  import { cors } from "hono/cors";
3
3
  import { streamSSE } from "hono/streaming";
4
4
  import { serve } from "@hono/node-server";
5
- import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionInput, processProjectInteractionRequest, resolveSessionActiveBook, } from "@actalk/inkos-core";
6
- import { access, readFile, readdir } from "node:fs/promises";
5
+ import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, findOrCreateBookSession, listBookSessions, loadBookSession, persistBookSession, appendBookSessionMessage, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceModel, loadSecrets, saveSecrets, getServiceApiKey, listModelsForService, chatCompletion, GLOBAL_ENV_PATH, } from "@actalk/inkos-core";
6
+ import { access, readFile, readdir, writeFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
8
  import { isSafeBookId } from "./safety.js";
9
9
  import { ApiError } from "./errors.js";
10
10
  import { buildStudioBookConfig } from "./book-create.js";
11
+ // -- Pipeline stage definitions per agent type --
12
+ const PIPELINE_STAGES = {
13
+ writer: [
14
+ "准备章节输入", "撰写章节草稿", "落盘最终章节",
15
+ "生成最终真相文件", "校验真相文件变更", "同步记忆索引",
16
+ "更新章节索引与快照",
17
+ ],
18
+ architect: [
19
+ "生成基础设定", "保存书籍配置", "写入基础设定文件",
20
+ "初始化控制文档", "创建初始快照",
21
+ ],
22
+ reviser: [
23
+ "加载修订上下文", "修订章节", "落盘修订结果",
24
+ "更新索引与快照",
25
+ ],
26
+ auditor: ["审计章节"],
27
+ };
28
+ const AGENT_LABELS = {
29
+ architect: "建书", writer: "写作", auditor: "审计",
30
+ reviser: "修订", exporter: "导出",
31
+ };
32
+ const TOOL_LABELS = {
33
+ read: "读取文件", edit: "编辑文件", grep: "搜索", ls: "列目录",
34
+ };
35
+ function resolveToolLabel(tool, agent) {
36
+ if (tool === "sub_agent" && agent)
37
+ return AGENT_LABELS[agent] ?? agent;
38
+ return TOOL_LABELS[tool] ?? tool;
39
+ }
40
+ function summarizeResult(result) {
41
+ if (typeof result === "string")
42
+ return result.slice(0, 200);
43
+ if (result && typeof result === "object") {
44
+ const r = result;
45
+ if (typeof r.content === "string")
46
+ return r.content.slice(0, 200);
47
+ if (typeof r.text === "string")
48
+ return r.text.slice(0, 200);
49
+ }
50
+ return String(result).slice(0, 200);
51
+ }
52
+ function extractToolError(result) {
53
+ if (typeof result === "string")
54
+ return result.slice(0, 500);
55
+ if (result && typeof result === "object") {
56
+ const r = result;
57
+ if (typeof r.content === "string")
58
+ return r.content.slice(0, 500);
59
+ if (r.content && Array.isArray(r.content)) {
60
+ const textPart = r.content.find((c) => c.type === "text");
61
+ if (textPart)
62
+ return textPart.text?.slice(0, 500) ?? "";
63
+ }
64
+ }
65
+ return String(result).slice(0, 500);
66
+ }
11
67
  const subscribers = new Set();
12
68
  const bookCreateStatus = new Map();
13
69
  function broadcast(event, data) {
@@ -15,6 +71,154 @@ function broadcast(event, data) {
15
71
  handler(event, data);
16
72
  }
17
73
  }
74
+ function isCustomServiceId(serviceId) {
75
+ return serviceId === "custom" || serviceId.startsWith("custom:");
76
+ }
77
+ function serviceConfigKey(entry) {
78
+ return entry.service === "custom" ? `custom:${entry.name ?? "Custom"}` : entry.service;
79
+ }
80
+ function normalizeServiceEntry(serviceId, value) {
81
+ if (serviceId.startsWith("custom:")) {
82
+ return {
83
+ service: "custom",
84
+ name: decodeURIComponent(serviceId.slice("custom:".length)),
85
+ ...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl: value.baseUrl } : {}),
86
+ ...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
87
+ ...(typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {}),
88
+ ...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
89
+ ...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
90
+ };
91
+ }
92
+ if (serviceId === "custom") {
93
+ return {
94
+ service: "custom",
95
+ ...(typeof value.name === "string" && value.name.length > 0 ? { name: value.name } : {}),
96
+ ...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl: value.baseUrl } : {}),
97
+ ...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
98
+ ...(typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {}),
99
+ ...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
100
+ ...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
101
+ };
102
+ }
103
+ return {
104
+ service: serviceId,
105
+ ...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
106
+ ...(typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {}),
107
+ ...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
108
+ ...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
109
+ };
110
+ }
111
+ function normalizeConfigSource(value) {
112
+ return value === "studio" ? "studio" : "env";
113
+ }
114
+ function normalizeServiceConfig(raw) {
115
+ if (Array.isArray(raw)) {
116
+ return raw
117
+ .filter((entry) => Boolean(entry) && typeof entry === "object")
118
+ .map((entry) => ({
119
+ service: typeof entry.service === "string" && entry.service.length > 0 ? entry.service : "custom",
120
+ ...(typeof entry.name === "string" && entry.name.length > 0 ? { name: entry.name } : {}),
121
+ ...(typeof entry.baseUrl === "string" && entry.baseUrl.length > 0 ? { baseUrl: entry.baseUrl } : {}),
122
+ ...(typeof entry.temperature === "number" ? { temperature: entry.temperature } : {}),
123
+ ...(typeof entry.maxTokens === "number" ? { maxTokens: entry.maxTokens } : {}),
124
+ ...(entry.apiFormat === "chat" || entry.apiFormat === "responses" ? { apiFormat: entry.apiFormat } : {}),
125
+ ...(typeof entry.stream === "boolean" ? { stream: entry.stream } : {}),
126
+ }));
127
+ }
128
+ if (raw && typeof raw === "object") {
129
+ return Object.entries(raw)
130
+ .filter(([, value]) => value && typeof value === "object")
131
+ .map(([serviceId, value]) => normalizeServiceEntry(serviceId, value));
132
+ }
133
+ return [];
134
+ }
135
+ function mergeServiceConfig(existing, updates) {
136
+ const merged = new Map(existing.map((entry) => [serviceConfigKey(entry), entry]));
137
+ for (const update of updates) {
138
+ merged.set(serviceConfigKey(update), update);
139
+ }
140
+ return [...merged.values()];
141
+ }
142
+ async function loadRawConfig(root) {
143
+ const configPath = join(root, "inkos.json");
144
+ const raw = await readFile(configPath, "utf-8");
145
+ return JSON.parse(raw);
146
+ }
147
+ async function saveRawConfig(root, config) {
148
+ await writeFile(join(root, "inkos.json"), JSON.stringify(config, null, 2), "utf-8");
149
+ }
150
+ async function readEnvConfigSummary(path) {
151
+ try {
152
+ const raw = await readFile(path, "utf-8");
153
+ const values = new Map();
154
+ for (const line of raw.split(/\r?\n/)) {
155
+ const trimmed = line.trim();
156
+ if (!trimmed || trimmed.startsWith("#"))
157
+ continue;
158
+ const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
159
+ if (!match)
160
+ continue;
161
+ const [, key, value] = match;
162
+ values.set(key, value.trim());
163
+ }
164
+ const provider = values.get("INKOS_LLM_PROVIDER") ?? null;
165
+ const baseUrl = values.get("INKOS_LLM_BASE_URL") ?? null;
166
+ const model = values.get("INKOS_LLM_MODEL") ?? null;
167
+ const apiKey = values.get("INKOS_LLM_API_KEY") ?? "";
168
+ const detected = Boolean(provider || baseUrl || model || apiKey);
169
+ return {
170
+ detected,
171
+ provider,
172
+ baseUrl,
173
+ model,
174
+ hasApiKey: apiKey.length > 0,
175
+ };
176
+ }
177
+ catch {
178
+ return {
179
+ detected: false,
180
+ provider: null,
181
+ baseUrl: null,
182
+ model: null,
183
+ hasApiKey: false,
184
+ };
185
+ }
186
+ }
187
+ async function readEnvConfigStatus(root) {
188
+ const project = await readEnvConfigSummary(join(root, ".env"));
189
+ const global = await readEnvConfigSummary(GLOBAL_ENV_PATH);
190
+ return {
191
+ project,
192
+ global,
193
+ effectiveSource: project.detected ? "project" : global.detected ? "global" : null,
194
+ };
195
+ }
196
+ async function resolveConfiguredServiceBaseUrl(root, serviceId, inlineBaseUrl) {
197
+ if (inlineBaseUrl?.trim())
198
+ return inlineBaseUrl.trim();
199
+ if (!isCustomServiceId(serviceId)) {
200
+ return resolveServicePreset(serviceId)?.baseUrl;
201
+ }
202
+ try {
203
+ const config = await loadRawConfig(root);
204
+ const services = normalizeServiceConfig(config.llm?.services);
205
+ const matched = services.find((entry) => serviceConfigKey(entry) === serviceId);
206
+ return matched?.baseUrl;
207
+ }
208
+ catch {
209
+ return undefined;
210
+ }
211
+ }
212
+ async function resolveConfiguredServiceEntry(root, serviceId) {
213
+ try {
214
+ const config = await loadRawConfig(root);
215
+ const services = normalizeServiceConfig(config.llm?.services);
216
+ return services.find((entry) => serviceConfigKey(entry) === serviceId);
217
+ }
218
+ catch {
219
+ return undefined;
220
+ }
221
+ }
18
222
  // --- Server factory ---
19
223
  export function createStudioServer(initialConfig, root) {
20
224
  const app = new Hono();
@@ -29,14 +233,14 @@ export function createStudioServer(initialConfig, root) {
29
233
  return c.json({ error: { code: "INTERNAL_ERROR", message: "Unexpected server error." } }, 500);
30
234
  });
31
235
  // BookId validation middleware — blocks path traversal on all book routes
32
- app.use("/api/books/:id/*", async (c, next) => {
236
+ app.use("/api/v1/books/:id/*", async (c, next) => {
33
237
  const bookId = c.req.param("id");
34
238
  if (!isSafeBookId(bookId)) {
35
239
  throw new ApiError(400, "INVALID_BOOK_ID", `Invalid book ID: "${bookId}"`);
36
240
  }
37
241
  await next();
38
242
  });
39
- app.use("/api/books/:id", async (c, next) => {
243
+ app.use("/api/v1/books/:id", async (c, next) => {
40
244
  const bookId = c.req.param("id");
41
245
  if (!isSafeBookId(bookId)) {
42
246
  throw new ApiError(400, "INVALID_BOOK_ID", `Invalid book ID: "${bookId}"`);
@@ -49,36 +253,47 @@ export function createStudioServer(initialConfig, root) {
49
253
  broadcast("log", { level: entry.level, tag: entry.tag, message: entry.message });
50
254
  },
51
255
  };
256
+ // Logger sink that prints to server terminal
257
+ const consoleSink = {
258
+ write(entry) {
259
+ const prefix = `[${entry.tag}]`;
260
+ if (entry.level === "warn")
261
+ console.warn(prefix, entry.message);
262
+ else if (entry.level === "error")
263
+ console.error(prefix, entry.message);
264
+ else
265
+ console.log(prefix, entry.message);
266
+ },
267
+ };
52
268
  async function loadCurrentProjectConfig(options) {
53
269
  const freshConfig = await loadProjectConfig(root, options);
54
270
  cachedConfig = freshConfig;
55
271
  return freshConfig;
56
272
  }
57
273
  async function buildPipelineConfig(overrides) {
58
- const currentConfig = await loadCurrentProjectConfig();
59
- const logger = createLogger({ tag: "studio", sinks: [sseSink] });
274
+ const currentConfig = overrides?.currentConfig ?? await loadCurrentProjectConfig();
275
+ const logger = createLogger({ tag: "studio", sinks: [sseSink, consoleSink] });
60
276
  return {
61
- client: createLLMClient(currentConfig.llm),
62
- model: currentConfig.llm.model,
277
+ client: overrides?.client ?? createLLMClient(currentConfig.llm),
278
+ model: overrides?.model ?? currentConfig.llm.model,
63
279
  projectRoot: root,
64
280
  defaultLLMConfig: currentConfig.llm,
65
281
  modelOverrides: currentConfig.modelOverrides,
66
282
  notifyChannels: currentConfig.notify,
67
283
  logger,
68
284
  onStreamProgress: (progress) => {
69
- if (progress.status === "streaming") {
70
- broadcast("llm:progress", {
71
- elapsedMs: progress.elapsedMs,
72
- totalChars: progress.totalChars,
73
- chineseChars: progress.chineseChars,
74
- });
75
- }
285
+ broadcast("llm:progress", {
286
+ status: progress.status,
287
+ elapsedMs: progress.elapsedMs,
288
+ totalChars: progress.totalChars,
289
+ chineseChars: progress.chineseChars,
290
+ });
76
291
  },
77
292
  externalContext: overrides?.externalContext,
78
293
  };
79
294
  }
80
295
  // --- Books ---
81
- app.get("/api/books", async (c) => {
296
+ app.get("/api/v1/books", async (c) => {
82
297
  const bookIds = await state.listBooks();
83
298
  const books = await Promise.all(bookIds.map(async (id) => {
84
299
  const book = await state.loadBookConfig(id);
@@ -87,7 +302,7 @@ export function createStudioServer(initialConfig, root) {
87
302
  }));
88
303
  return c.json({ books });
89
304
  });
90
- app.get("/api/books/:id", async (c) => {
305
+ app.get("/api/v1/books/:id", async (c) => {
91
306
  const id = c.req.param("id");
92
307
  try {
93
308
  const book = await state.loadBookConfig(id);
@@ -100,7 +315,7 @@ export function createStudioServer(initialConfig, root) {
100
315
  }
101
316
  });
102
317
  // --- Genres ---
103
- app.get("/api/genres", async (c) => {
318
+ app.get("/api/v1/genres", async (c) => {
104
319
  const { listAvailableGenres, readGenreProfile } = await import("@actalk/inkos-core");
105
320
  const rawGenres = await listAvailableGenres(root);
106
321
  const genres = await Promise.all(rawGenres.map(async (g) => {
@@ -115,7 +330,7 @@ export function createStudioServer(initialConfig, root) {
115
330
  return c.json({ genres });
116
331
  });
117
332
  // --- Book Create ---
118
- app.post("/api/books/create", async (c) => {
333
+ app.post("/api/v1/books/create", async (c) => {
119
334
  const body = await c.req.json();
120
335
  const now = new Date().toISOString();
121
336
  const bookConfig = buildStudioBookConfig(body, now);
@@ -156,7 +371,7 @@ export function createStudioServer(initialConfig, root) {
156
371
  });
157
372
  return c.json({ status: "creating", bookId });
158
373
  });
159
- app.get("/api/books/:id/create-status", async (c) => {
374
+ app.get("/api/v1/books/:id/create-status", async (c) => {
160
375
  const id = c.req.param("id");
161
376
  const status = bookCreateStatus.get(id);
162
377
  if (!status) {
@@ -165,7 +380,7 @@ export function createStudioServer(initialConfig, root) {
165
380
  return c.json(status);
166
381
  });
167
382
  // --- Chapters ---
168
- app.get("/api/books/:id/chapters/:num", async (c) => {
383
+ app.get("/api/v1/books/:id/chapters/:num", async (c) => {
169
384
  const id = c.req.param("id");
170
385
  const num = parseInt(c.req.param("num"), 10);
171
386
  const bookDir = state.bookDir(id);
@@ -184,7 +399,7 @@ export function createStudioServer(initialConfig, root) {
184
399
  }
185
400
  });
186
401
  // --- Chapter Save ---
187
- app.put("/api/books/:id/chapters/:num", async (c) => {
402
+ app.put("/api/v1/books/:id/chapters/:num", async (c) => {
188
403
  const id = c.req.param("id");
189
404
  const num = parseInt(c.req.param("num"), 10);
190
405
  const bookDir = state.bookDir(id);
@@ -206,12 +421,13 @@ export function createStudioServer(initialConfig, root) {
206
421
  });
207
422
  // --- Truth files ---
208
423
  const TRUTH_FILES = [
424
+ "author_intent.md", "current_focus.md",
209
425
  "story_bible.md", "volume_outline.md", "current_state.md",
210
426
  "particle_ledger.md", "pending_hooks.md", "chapter_summaries.md",
211
427
  "subplot_board.md", "emotional_arcs.md", "character_matrix.md",
212
428
  "style_guide.md", "parent_canon.md", "fanfic_canon.md", "book_rules.md",
213
429
  ];
214
- app.get("/api/books/:id/truth/:file", async (c) => {
430
+ app.get("/api/v1/books/:id/truth/:file", async (c) => {
215
431
  const id = c.req.param("id");
216
432
  const file = c.req.param("file");
217
433
  if (!TRUTH_FILES.includes(file)) {
@@ -227,7 +443,7 @@ export function createStudioServer(initialConfig, root) {
227
443
  }
228
444
  });
229
445
  // --- Analytics ---
230
- app.get("/api/books/:id/analytics", async (c) => {
446
+ app.get("/api/v1/books/:id/analytics", async (c) => {
231
447
  const id = c.req.param("id");
232
448
  try {
233
449
  const chapters = await state.loadChapterIndex(id);
@@ -238,7 +454,7 @@ export function createStudioServer(initialConfig, root) {
238
454
  }
239
455
  });
240
456
  // --- Actions ---
241
- app.post("/api/books/:id/write-next", async (c) => {
457
+ app.post("/api/v1/books/:id/write-next", async (c) => {
242
458
  const id = c.req.param("id");
243
459
  const body = await c.req.json().catch(() => ({ wordCount: undefined }));
244
460
  broadcast("write:start", { bookId: id });
@@ -251,7 +467,7 @@ export function createStudioServer(initialConfig, root) {
251
467
  });
252
468
  return c.json({ status: "writing", bookId: id });
253
469
  });
254
- app.post("/api/books/:id/draft", async (c) => {
470
+ app.post("/api/v1/books/:id/draft", async (c) => {
255
471
  const id = c.req.param("id");
256
472
  const body = await c.req.json().catch(() => ({ wordCount: undefined, context: undefined }));
257
473
  broadcast("draft:start", { bookId: id });
@@ -263,7 +479,7 @@ export function createStudioServer(initialConfig, root) {
263
479
  });
264
480
  return c.json({ status: "drafting", bookId: id });
265
481
  });
266
- app.post("/api/books/:id/chapters/:num/approve", async (c) => {
482
+ app.post("/api/v1/books/:id/chapters/:num/approve", async (c) => {
267
483
  const id = c.req.param("id");
268
484
  const num = parseInt(c.req.param("num"), 10);
269
485
  try {
@@ -276,7 +492,7 @@ export function createStudioServer(initialConfig, root) {
276
492
  return c.json({ error: String(e) }, 500);
277
493
  }
278
494
  });
279
- app.post("/api/books/:id/chapters/:num/reject", async (c) => {
495
+ app.post("/api/v1/books/:id/chapters/:num/reject", async (c) => {
280
496
  const id = c.req.param("id");
281
497
  const num = parseInt(c.req.param("num"), 10);
282
498
  try {
@@ -300,7 +516,7 @@ export function createStudioServer(initialConfig, root) {
300
516
  }
301
517
  });
302
518
  // --- SSE ---
303
- app.get("/api/events", (c) => {
519
+ app.get("/api/v1/events", (c) => {
304
520
  return streamSSE(c, async (stream) => {
305
521
  const handler = (event, data) => {
306
522
  stream.writeSSE({ event, data: JSON.stringify(data) });
@@ -318,8 +534,184 @@ export function createStudioServer(initialConfig, root) {
318
534
  await new Promise(() => { });
319
535
  });
320
536
  });
537
+ // --- Model discovery ---
538
+ app.get("/api/v1/services", async (c) => {
539
+ const secrets = await loadSecrets(root);
540
+ const SERVICE_KEYS = [
541
+ "openai", "anthropic", "deepseek", "moonshot", "minimax",
542
+ "bailian", "zhipu", "siliconflow", "ppio", "openrouter", "ollama",
543
+ ];
544
+ // Fast: only check connection status from secrets, no external API calls
545
+ const services = SERVICE_KEYS.map((key) => {
546
+ const preset = resolveServicePreset(key);
547
+ return {
548
+ service: key,
549
+ label: preset?.label ?? key,
550
+ connected: Boolean(secrets.services[key]?.apiKey),
551
+ };
552
+ });
553
+ // Add custom services from inkos.json
554
+ try {
555
+ const config = await loadRawConfig(root);
556
+ for (const svc of normalizeServiceConfig(config.llm?.services)) {
557
+ if (svc.service === "custom") {
558
+ const secretKey = `custom:${svc.name}`;
559
+ services.push({
560
+ service: secretKey,
561
+ label: svc.name ?? "Custom",
562
+ connected: Boolean(secrets.services[secretKey]?.apiKey),
563
+ });
564
+ }
565
+ }
566
+ }
567
+ catch { /* no config file */ }
568
+ return c.json({ services });
569
+ });
570
+ app.get("/api/v1/services/config", async (c) => {
571
+ const config = await loadRawConfig(root);
572
+ const llm = config.llm ?? {};
573
+ const services = normalizeServiceConfig(llm.services);
574
+ const envConfig = await readEnvConfigStatus(root);
575
+ return c.json({
576
+ services,
577
+ defaultModel: llm.defaultModel ?? null,
578
+ configSource: normalizeConfigSource(llm.configSource),
579
+ envConfig,
580
+ });
581
+ });
582
+ app.put("/api/v1/services/config", async (c) => {
583
+ const body = await c.req.json();
584
+ const config = await loadRawConfig(root);
585
+ config.llm = config.llm ?? {};
586
+ const llm = config.llm;
587
+ if (body.services !== undefined) {
588
+ const existingServices = normalizeServiceConfig(llm.services);
589
+ const incomingServices = normalizeServiceConfig(body.services);
590
+ llm.services = mergeServiceConfig(existingServices, incomingServices);
591
+ }
592
+ if (body.defaultModel !== undefined) {
593
+ llm.defaultModel = body.defaultModel;
594
+ }
595
+ if (body.configSource !== undefined) {
596
+ llm.configSource = normalizeConfigSource(body.configSource);
597
+ }
598
+ await saveRawConfig(root, config);
599
+ return c.json({ ok: true });
600
+ });
601
+ app.post("/api/v1/services/:service/test", async (c) => {
602
+ const service = c.req.param("service");
603
+ const { apiKey, baseUrl, apiFormat, stream } = await c.req.json();
604
+ if (!apiKey?.trim()) {
605
+ return c.json({ ok: false, error: "API Key 不能为空" }, 400);
606
+ }
607
+ const resolvedBaseUrl = await resolveConfiguredServiceBaseUrl(root, service, baseUrl);
608
+ if (!resolvedBaseUrl) {
609
+ return c.json({ ok: false, error: `未知服务商: ${service}` }, 400);
610
+ }
611
+ // Call the real /models API — no fallback
612
+ const modelsUrl = resolvedBaseUrl.replace(/\/$/, "") + "/models";
613
+ try {
614
+ const res = await fetch(modelsUrl, {
615
+ headers: { Authorization: `Bearer ${apiKey.trim()}` },
616
+ signal: AbortSignal.timeout(10_000),
617
+ });
618
+ if (!res.ok) {
619
+ const body = await res.text().catch(() => "");
620
+ if (res.status === 401 || res.status === 403) {
621
+ return c.json({ ok: false, error: "API Key 无效,请检查后重试" }, 400);
622
+ }
623
+ return c.json({ ok: false, error: `服务商返回 ${res.status}: ${body.slice(0, 200)}` }, 400);
624
+ }
625
+ const json = await res.json();
626
+ const models = (json.data ?? []).map((m) => ({ id: m.id, name: m.id }));
627
+ if (models.length === 0) {
628
+ return c.json({ ok: false, error: "连接成功但未返回可用模型" }, 400);
629
+ }
630
+ const rawConfig = await loadRawConfig(root).catch(() => ({}));
631
+ const preferredModel = typeof rawConfig.llm?.defaultModel === "string"
632
+ ? String(rawConfig.llm.defaultModel)
633
+ : undefined;
634
+ const sampleModel = models.find((m) => m.id === preferredModel)?.id
635
+ ?? models.find((m) => m.id === "gpt-5.4")?.id
636
+ ?? models[0]?.id;
637
+ if (sampleModel) {
638
+ const client = createLLMClient({
639
+ provider: service === "anthropic" ? "anthropic" : "openai",
640
+ service: isCustomServiceId(service) ? "custom" : service,
641
+ configSource: "studio",
642
+ baseUrl: resolvedBaseUrl,
643
+ apiKey: apiKey.trim(),
644
+ model: sampleModel,
645
+ temperature: 0.7,
646
+ maxTokens: 64,
647
+ thinkingBudget: 0,
648
+ apiFormat: apiFormat ?? "chat",
649
+ stream: stream ?? true,
650
+ });
651
+ try {
652
+ await chatCompletion(client, sampleModel, [{ role: "user", content: "ping" }], { maxTokens: 5 });
653
+ }
654
+ catch (error) {
655
+ const message = error instanceof Error ? error.message : String(error);
656
+ return c.json({ ok: false, error: `模型列表可读,但生成测试失败:${message}` }, 400);
657
+ }
658
+ }
659
+ return c.json({ ok: true, modelCount: models.length, models: models.slice(0, 50) });
660
+ }
661
+ catch (err) {
662
+ if (err?.name === "TimeoutError" || err?.name === "AbortError") {
663
+ return c.json({ ok: false, error: `连接超时:无法访问 ${modelsUrl}` }, 400);
664
+ }
665
+ return c.json({ ok: false, error: `连接失败: ${err?.message ?? String(err)}` }, 400);
666
+ }
667
+ });
668
+ app.put("/api/v1/services/:service/secret", async (c) => {
669
+ const service = c.req.param("service");
670
+ const { apiKey } = await c.req.json();
671
+ const secrets = await loadSecrets(root);
672
+ if (apiKey?.trim()) {
673
+ secrets.services[service] = { apiKey: apiKey.trim() };
674
+ }
675
+ else {
676
+ delete secrets.services[service];
677
+ }
678
+ await saveSecrets(root, secrets);
679
+ return c.json({ ok: true });
680
+ });
681
+ app.get("/api/v1/services/:service/secret", async (c) => {
682
+ const service = c.req.param("service");
683
+ const secrets = await loadSecrets(root);
684
+ return c.json({
685
+ apiKey: secrets.services[service]?.apiKey ?? "",
686
+ });
687
+ });
688
+ app.get("/api/v1/services/:service/models", async (c) => {
689
+ const service = c.req.param("service");
690
+ const apiKey = c.req.query("apiKey") || await getServiceApiKey(root, service);
691
+ // No key = no models (don't fallback to built-in list)
692
+ if (!apiKey)
693
+ return c.json({ models: [] });
694
+ const resolvedBaseUrl = await resolveConfiguredServiceBaseUrl(root, service);
695
+ if (!resolvedBaseUrl)
696
+ return c.json({ models: [] });
697
+ // Call real API only
698
+ let models = [];
699
+ try {
700
+ const modelsUrl = resolvedBaseUrl.replace(/\/$/, "") + "/models";
701
+ const res = await fetch(modelsUrl, {
702
+ headers: { Authorization: `Bearer ${apiKey}` },
703
+ signal: AbortSignal.timeout(10_000),
704
+ });
705
+ if (res.ok) {
706
+ const json = await res.json();
707
+ models = (json.data ?? []).map((m) => ({ id: m.id, name: m.id }));
708
+ }
709
+ }
710
+ catch { /* timeout or network error — return empty */ }
711
+ return c.json({ models });
712
+ });
321
713
  // --- Project info ---
322
- app.get("/api/project", async (c) => {
714
+ app.get("/api/v1/project", async (c) => {
323
715
  const currentConfig = await loadCurrentProjectConfig({ requireApiKey: false });
324
716
  // Check if language was explicitly set in inkos.json (not just the schema default)
325
717
  const raw = JSON.parse(await readFile(join(root, "inkos.json"), "utf-8"));
@@ -337,7 +729,7 @@ export function createStudioServer(initialConfig, root) {
337
729
  });
338
730
  });
339
731
  // --- Config editing ---
340
- app.put("/api/project", async (c) => {
732
+ app.put("/api/v1/project", async (c) => {
341
733
  const updates = await c.req.json();
342
734
  const configPath = join(root, "inkos.json");
343
735
  try {
@@ -365,7 +757,7 @@ export function createStudioServer(initialConfig, root) {
365
757
  }
366
758
  });
367
759
  // --- Truth files browser ---
368
- app.get("/api/books/:id/truth", async (c) => {
760
+ app.get("/api/v1/books/:id/truth", async (c) => {
369
761
  const id = c.req.param("id");
370
762
  const bookDir = state.bookDir(id);
371
763
  const storyDir = join(bookDir, "story");
@@ -384,12 +776,12 @@ export function createStudioServer(initialConfig, root) {
384
776
  });
385
777
  // --- Daemon control ---
386
778
  let schedulerInstance = null;
387
- app.get("/api/daemon", (c) => {
779
+ app.get("/api/v1/daemon", (c) => {
388
780
  return c.json({
389
781
  running: schedulerInstance?.isRunning ?? false,
390
782
  });
391
783
  });
392
- app.post("/api/daemon/start", async (c) => {
784
+ app.post("/api/v1/daemon/start", async (c) => {
393
785
  if (schedulerInstance?.isRunning) {
394
786
  return c.json({ error: "Daemon already running" }, 400);
395
787
  }
@@ -429,7 +821,7 @@ export function createStudioServer(initialConfig, root) {
429
821
  return c.json({ error: String(e) }, 500);
430
822
  }
431
823
  });
432
- app.post("/api/daemon/stop", (c) => {
824
+ app.post("/api/v1/daemon/stop", (c) => {
433
825
  if (!schedulerInstance?.isRunning) {
434
826
  return c.json({ error: "Daemon not running" }, 400);
435
827
  }
@@ -439,7 +831,7 @@ export function createStudioServer(initialConfig, root) {
439
831
  return c.json({ ok: true, running: false });
440
832
  });
441
833
  // --- Logs ---
442
- app.get("/api/logs", async (c) => {
834
+ app.get("/api/v1/logs", async (c) => {
443
835
  const logPath = join(root, "inkos.log");
444
836
  try {
445
837
  const content = await readFile(logPath, "utf-8");
@@ -459,7 +851,7 @@ export function createStudioServer(initialConfig, root) {
459
851
  }
460
852
  });
461
853
  // --- Agent chat ---
462
- app.get("/api/interaction/session", async (c) => {
854
+ app.get("/api/v1/interaction/session", async (c) => {
463
855
  const session = await loadProjectSession(root);
464
856
  const activeBookId = await resolveSessionActiveBook(root, session);
465
857
  return c.json({
@@ -469,38 +861,307 @@ export function createStudioServer(initialConfig, root) {
469
861
  activeBookId,
470
862
  });
471
863
  });
472
- app.post("/api/agent", async (c) => {
473
- const { instruction, activeBookId } = await c.req.json();
864
+ // -- Per-book session endpoints --
865
+ app.get("/api/v1/sessions", async (c) => {
866
+ const bookId = c.req.query("bookId");
867
+ const sessions = await listBookSessions(root, bookId === undefined ? null : bookId === "null" ? null : bookId);
868
+ return c.json({ sessions: sessions.map((s) => ({
869
+ sessionId: s.sessionId,
870
+ bookId: s.bookId,
871
+ messageCount: s.messages.length,
872
+ createdAt: s.createdAt,
873
+ updatedAt: s.updatedAt,
874
+ })) });
875
+ });
876
+ app.get("/api/v1/sessions/:sessionId", async (c) => {
877
+ const session = await loadBookSession(root, c.req.param("sessionId"));
878
+ if (!session)
879
+ return c.json({ error: "Session not found" }, 404);
880
+ return c.json({ session });
881
+ });
882
+ app.post("/api/v1/sessions", async (c) => {
883
+ const body = await c.req.json().catch(() => ({}));
884
+ const bookId = body.bookId ?? null;
885
+ const session = await findOrCreateBookSession(root, bookId);
886
+ return c.json({ session });
887
+ });
888
+ app.post("/api/v1/agent", async (c) => {
889
+ const { instruction, activeBookId, sessionId: reqSessionId, model: reqModel, service: reqService } = await c.req.json();
890
+ const sessionId = reqSessionId;
474
891
  if (!instruction?.trim()) {
475
892
  return c.json({ error: "No instruction provided" }, 400);
476
893
  }
477
894
  broadcast("agent:start", { instruction, activeBookId });
478
895
  try {
479
- const pipeline = new PipelineRunner(await buildPipelineConfig());
480
- const tools = createInteractionToolsFromDeps(pipeline, state);
481
- const result = await processProjectInteractionInput({
896
+ // Load config + create LLM client (pipeline created after model resolution)
897
+ const config = await loadCurrentProjectConfig({ requireApiKey: false });
898
+ const client = createLLMClient(config.llm);
899
+ // Resolve or create BookSession for history
900
+ let bookSession;
901
+ if (sessionId) {
902
+ bookSession =
903
+ (await loadBookSession(root, sessionId)) ??
904
+ (await findOrCreateBookSession(root, activeBookId ?? null));
905
+ }
906
+ else {
907
+ bookSession = await findOrCreateBookSession(root, activeBookId ?? null);
908
+ }
909
+ // Build initial message context from persisted session
910
+ const initialMessages = bookSession.messages
911
+ .filter((m) => m.role === "user" || m.role === "assistant")
912
+ .map((m) => ({ role: m.role, content: m.content }));
913
+ // Resolve model — multi-service resolution
914
+ let resolvedModel;
915
+ let resolvedApiKey;
916
+ if (reqService && reqModel) {
917
+ // 1. Frontend explicitly selected a service+model — fail loudly if no key
918
+ try {
919
+ const configuredEntry = await resolveConfiguredServiceEntry(root, reqService);
920
+ const resolved = await resolveServiceModel(reqService, reqModel, root, await resolveConfiguredServiceBaseUrl(root, reqService), configuredEntry?.apiFormat);
921
+ resolvedModel = resolved.model;
922
+ resolvedApiKey = resolved.apiKey;
923
+ }
924
+ catch (e) {
925
+ const msg = e?.message ?? String(e);
926
+ if (/API key/i.test(msg)) {
927
+ return c.json({
928
+ error: `请先为 ${reqService} 配置 API Key`,
929
+ response: `请先在模型配置中为 ${reqService} 填写 API Key,然后再试。`,
930
+ }, 400);
931
+ }
932
+ throw e;
933
+ }
934
+ }
935
+ if (!resolvedModel) {
936
+ // 2. Try defaultModel from new config format
937
+ const rawConfig = config.llm;
938
+ const defaultModel = rawConfig.defaultModel;
939
+ const servicesArr = normalizeServiceConfig(rawConfig.services);
940
+ const firstService = servicesArr[0];
941
+ if (firstService?.service && defaultModel) {
942
+ try {
943
+ const resolved = await resolveServiceModel(serviceConfigKey(firstService), defaultModel, root, firstService.baseUrl, firstService.apiFormat);
944
+ resolvedModel = resolved.model;
945
+ resolvedApiKey = resolved.apiKey;
946
+ }
947
+ catch { /* fall through */ }
948
+ }
949
+ }
950
+ if (!resolvedModel) {
951
+ // 3. Try first connected service from secrets
952
+ const secrets = await loadSecrets(root);
953
+ for (const [svcName, svcData] of Object.entries(secrets.services)) {
954
+ if (svcData?.apiKey) {
955
+ try {
956
+ const models = await listModelsForService(svcName, svcData.apiKey);
957
+ if (models.length > 0) {
958
+ const configuredEntry = await resolveConfiguredServiceEntry(root, svcName);
959
+ const resolved = await resolveServiceModel(svcName, models[0].id, root, await resolveConfiguredServiceBaseUrl(root, svcName), configuredEntry?.apiFormat);
960
+ resolvedModel = resolved.model;
961
+ resolvedApiKey = resolved.apiKey;
962
+ break;
963
+ }
964
+ }
965
+ catch { /* try next */ }
966
+ }
967
+ }
968
+ }
969
+ if (!resolvedModel) {
970
+ // 4. Legacy fallback: use createLLMClient
971
+ resolvedModel = client._piModel
972
+ ? client._piModel
973
+ : { provider: config.llm.provider ?? "anthropic", modelId: config.llm.model };
974
+ resolvedApiKey = client._apiKey;
975
+ }
976
+ const model = resolvedModel;
977
+ const agentApiKey = resolvedApiKey;
978
+ const configuredEntry = reqService ? await resolveConfiguredServiceEntry(root, reqService) : undefined;
979
+ // Create pipeline with resolved model (so sub_agent tools use the frontend-selected model)
980
+ // Don't spread config.llm — its baseUrl/provider belong to the old service.
981
+ // Let createLLMClient resolve baseUrl from the service preset.
982
+ const pipelineClient = (reqService && reqModel && resolvedApiKey)
983
+ ? createLLMClient({
984
+ ...config.llm,
985
+ service: configuredEntry?.service ?? reqService,
986
+ model: reqModel,
987
+ apiKey: resolvedApiKey,
988
+ ...(configuredEntry?.apiFormat ? { apiFormat: configuredEntry.apiFormat } : {}),
989
+ ...(configuredEntry?.stream !== undefined ? { stream: configuredEntry.stream } : {}),
990
+ baseUrl: configuredEntry?.baseUrl ?? "",
991
+ })
992
+ : client;
993
+ const pipeline = new PipelineRunner(await buildPipelineConfig({
994
+ client: pipelineClient,
995
+ model: reqModel ?? config.llm.model,
996
+ currentConfig: config,
997
+ }));
998
+ // Run pi-agent session
999
+ const collectedToolExecs = [];
1000
+ const result = await runAgentSession({
1001
+ model,
1002
+ apiKey: agentApiKey,
1003
+ pipeline,
482
1004
  projectRoot: root,
483
- input: instruction,
484
- tools,
485
- activeBookId,
1005
+ bookId: activeBookId ?? null,
1006
+ sessionId: bookSession.sessionId,
1007
+ language: config.language ?? "zh",
1008
+ onEvent: (event) => {
1009
+ if (event.type === "message_update") {
1010
+ const ame = event.assistantMessageEvent;
1011
+ if (ame.type === "text_delta") {
1012
+ broadcast("draft:delta", { text: ame.delta });
1013
+ }
1014
+ else if (ame.type === "thinking_delta") {
1015
+ broadcast("thinking:delta", { text: ame.delta });
1016
+ }
1017
+ else if (ame.type === "thinking_start") {
1018
+ broadcast("thinking:start", {});
1019
+ }
1020
+ else if (ame.type === "thinking_end") {
1021
+ broadcast("thinking:end", {});
1022
+ }
1023
+ }
1024
+ if (event.type === "tool_execution_start") {
1025
+ const args = event.args;
1026
+ const agent = event.toolName === "sub_agent" ? args?.agent : undefined;
1027
+ const stages = agent ? (PIPELINE_STAGES[agent] ?? []) : [];
1028
+ collectedToolExecs.push({
1029
+ id: event.toolCallId,
1030
+ tool: event.toolName,
1031
+ agent,
1032
+ label: resolveToolLabel(event.toolName, agent),
1033
+ status: "running",
1034
+ args,
1035
+ stages: stages.length > 0
1036
+ ? stages.map(l => ({ label: l, status: "pending" }))
1037
+ : undefined,
1038
+ startedAt: Date.now(),
1039
+ });
1040
+ broadcast("tool:start", {
1041
+ id: event.toolCallId,
1042
+ tool: event.toolName,
1043
+ args,
1044
+ stages,
1045
+ });
1046
+ }
1047
+ if (event.type === "tool_execution_update") {
1048
+ broadcast("tool:update", { tool: event.toolName, partialResult: event.partialResult });
1049
+ }
1050
+ if (event.type === "tool_execution_end") {
1051
+ const exec = collectedToolExecs.find(t => t.id === event.toolCallId);
1052
+ if (exec) {
1053
+ exec.status = event.isError ? "error" : "completed";
1054
+ exec.completedAt = Date.now();
1055
+ exec.stages = exec.stages?.map(s => ({ ...s, status: "completed" }));
1056
+ if (event.isError)
1057
+ exec.error = extractToolError(event.result);
1058
+ else
1059
+ exec.result = summarizeResult(event.result);
1060
+ }
1061
+ broadcast("tool:end", {
1062
+ id: event.toolCallId,
1063
+ tool: event.toolName,
1064
+ result: event.result,
1065
+ isError: event.isError,
1066
+ });
1067
+ }
1068
+ },
1069
+ }, instruction, initialMessages);
1070
+ // Persist user + assistant messages to BookSession
1071
+ bookSession = appendBookSessionMessage(bookSession, {
1072
+ role: "user",
1073
+ content: instruction,
1074
+ timestamp: Date.now(),
1075
+ });
1076
+ if (result.responseText) {
1077
+ const lastAssistant = result.messages?.filter((m) => m.role === "assistant").pop();
1078
+ const thinking = lastAssistant?.thinking;
1079
+ bookSession = appendBookSessionMessage(bookSession, {
1080
+ role: "assistant",
1081
+ content: result.responseText,
1082
+ ...(thinking ? { thinking } : {}),
1083
+ ...(collectedToolExecs.length > 0 ? { toolExecutions: collectedToolExecs } : {}),
1084
+ timestamp: Date.now() + 1,
1085
+ });
1086
+ }
1087
+ if (!result.responseText) {
1088
+ try {
1089
+ const fallbackClient = createLLMClient({
1090
+ ...config.llm,
1091
+ service: configuredEntry?.service ?? reqService ?? config.llm.service,
1092
+ model: reqModel ?? config.llm.model,
1093
+ apiKey: agentApiKey ?? config.llm.apiKey,
1094
+ baseUrl: configuredEntry?.baseUrl ?? config.llm.baseUrl,
1095
+ ...(configuredEntry?.apiFormat ? { apiFormat: configuredEntry.apiFormat } : {}),
1096
+ ...(configuredEntry?.stream !== undefined ? { stream: configuredEntry.stream } : {}),
1097
+ });
1098
+ const fallback = await chatCompletion(fallbackClient, reqModel ?? config.llm.model, [
1099
+ { role: "system", content: buildAgentSystemPrompt(activeBookId ?? null, config.language ?? "zh") },
1100
+ { role: "user", content: instruction },
1101
+ ], { maxTokens: 256 });
1102
+ if (fallback.content?.trim()) {
1103
+ bookSession = appendBookSessionMessage(bookSession, {
1104
+ role: "assistant",
1105
+ content: fallback.content,
1106
+ timestamp: Date.now() + 1,
1107
+ });
1108
+ await persistBookSession(root, bookSession);
1109
+ return c.json({
1110
+ response: fallback.content,
1111
+ session: { sessionId: bookSession.sessionId },
1112
+ });
1113
+ }
1114
+ }
1115
+ catch {
1116
+ // fall through to probe-based diagnosis below
1117
+ }
1118
+ try {
1119
+ const probeClient = createLLMClient({
1120
+ ...config.llm,
1121
+ service: configuredEntry?.service ?? reqService ?? config.llm.service,
1122
+ model: reqModel ?? config.llm.model,
1123
+ apiKey: agentApiKey ?? config.llm.apiKey,
1124
+ baseUrl: configuredEntry?.baseUrl ?? config.llm.baseUrl,
1125
+ ...(configuredEntry?.apiFormat ? { apiFormat: configuredEntry.apiFormat } : {}),
1126
+ ...(configuredEntry?.stream !== undefined ? { stream: configuredEntry.stream } : {}),
1127
+ });
1128
+ await chatCompletion(probeClient, reqModel ?? config.llm.model, [{ role: "user", content: "ping" }], { maxTokens: 5 });
1129
+ }
1130
+ catch (probeError) {
1131
+ const probeMessage = probeError instanceof Error ? probeError.message : String(probeError);
1132
+ return c.json({
1133
+ error: { code: "AGENT_EMPTY_RESPONSE", message: probeMessage },
1134
+ response: probeMessage,
1135
+ }, 502);
1136
+ }
1137
+ const emptyMessage = "模型未返回文本内容。请检查协议类型(chat/responses)、流式开关或上游服务兼容性。";
1138
+ return c.json({
1139
+ error: { code: "AGENT_EMPTY_RESPONSE", message: emptyMessage },
1140
+ response: emptyMessage,
1141
+ }, 502);
1142
+ }
1143
+ await persistBookSession(root, bookSession);
1144
+ broadcast("agent:complete", { instruction, activeBookId });
1145
+ return c.json({
1146
+ response: result.responseText,
1147
+ session: { sessionId: bookSession.sessionId },
486
1148
  });
487
- const response = result.responseText ?? "Acknowledged.";
488
- broadcast("agent:complete", { instruction, activeBookId, response });
489
- return c.json({ response, session: result.session, request: result.request });
490
1149
  }
491
1150
  catch (e) {
492
1151
  const msg = e instanceof Error ? e.message : String(e);
493
1152
  broadcast("agent:error", { instruction, activeBookId, error: msg });
494
- return c.json({
495
- error: {
496
- code: "INTERACTION_ERROR",
497
- message: msg,
498
- },
499
- }, 500);
1153
+ // Agent busy — return 429 with user-friendly message
1154
+ if (/already processing|prompt.*queue/i.test(msg)) {
1155
+ return c.json({
1156
+ error: { code: "AGENT_BUSY", message: "正在处理中,请等待当前操作完成" },
1157
+ response: "正在处理中,请等待当前操作完成后再发送。",
1158
+ }, 429);
1159
+ }
1160
+ return c.json({ error: { code: "AGENT_ERROR", message: msg } }, 500);
500
1161
  }
501
1162
  });
502
1163
  // --- Language setup ---
503
- app.post("/api/project/language", async (c) => {
1164
+ app.post("/api/v1/project/language", async (c) => {
504
1165
  const { language } = await c.req.json();
505
1166
  const configPath = join(root, "inkos.json");
506
1167
  try {
@@ -516,7 +1177,7 @@ export function createStudioServer(initialConfig, root) {
516
1177
  }
517
1178
  });
518
1179
  // --- Audit ---
519
- app.post("/api/books/:id/audit/:chapter", async (c) => {
1180
+ app.post("/api/v1/books/:id/audit/:chapter", async (c) => {
520
1181
  const id = c.req.param("id");
521
1182
  const chapterNum = parseInt(c.req.param("chapter"), 10);
522
1183
  const bookDir = state.bookDir(id);
@@ -548,7 +1209,7 @@ export function createStudioServer(initialConfig, root) {
548
1209
  }
549
1210
  });
550
1211
  // --- Revise ---
551
- app.post("/api/books/:id/revise/:chapter", async (c) => {
1212
+ app.post("/api/v1/books/:id/revise/:chapter", async (c) => {
552
1213
  const id = c.req.param("id");
553
1214
  const chapterNum = parseInt(c.req.param("chapter"), 10);
554
1215
  const bookDir = state.bookDir(id);
@@ -578,7 +1239,7 @@ export function createStudioServer(initialConfig, root) {
578
1239
  }
579
1240
  });
580
1241
  // --- Export ---
581
- app.get("/api/books/:id/export", async (c) => {
1242
+ app.get("/api/v1/books/:id/export", async (c) => {
582
1243
  const id = c.req.param("id");
583
1244
  const format = (c.req.query("format") ?? "txt");
584
1245
  const approvedOnly = c.req.query("approvedOnly") === "true";
@@ -634,7 +1295,7 @@ export function createStudioServer(initialConfig, root) {
634
1295
  }
635
1296
  });
636
1297
  // --- Export to file (save to project dir) ---
637
- app.post("/api/books/:id/export-save", async (c) => {
1298
+ app.post("/api/v1/books/:id/export-save", async (c) => {
638
1299
  const id = c.req.param("id");
639
1300
  const { format, approvedOnly } = await c.req.json().catch(() => ({ format: "txt", approvedOnly: false }));
640
1301
  const fmt = format ?? "txt";
@@ -667,7 +1328,7 @@ export function createStudioServer(initialConfig, root) {
667
1328
  }
668
1329
  });
669
1330
  // --- Genre detail + copy ---
670
- app.get("/api/genres/:id", async (c) => {
1331
+ app.get("/api/v1/genres/:id", async (c) => {
671
1332
  const genreId = c.req.param("id");
672
1333
  try {
673
1334
  const { readGenreProfile } = await import("@actalk/inkos-core");
@@ -678,7 +1339,7 @@ export function createStudioServer(initialConfig, root) {
678
1339
  return c.json({ error: String(e) }, 404);
679
1340
  }
680
1341
  });
681
- app.post("/api/genres/:id/copy", async (c) => {
1342
+ app.post("/api/v1/genres/:id/copy", async (c) => {
682
1343
  const genreId = c.req.param("id");
683
1344
  if (/[/\\\0]/.test(genreId) || genreId.includes("..")) {
684
1345
  throw new ApiError(400, "INVALID_GENRE_ID", `Invalid genre ID: "${genreId}"`);
@@ -697,11 +1358,11 @@ export function createStudioServer(initialConfig, root) {
697
1358
  }
698
1359
  });
699
1360
  // --- Model overrides ---
700
- app.get("/api/project/model-overrides", async (c) => {
1361
+ app.get("/api/v1/project/model-overrides", async (c) => {
701
1362
  const raw = JSON.parse(await readFile(join(root, "inkos.json"), "utf-8"));
702
1363
  return c.json({ overrides: raw.modelOverrides ?? {} });
703
1364
  });
704
- app.put("/api/project/model-overrides", async (c) => {
1365
+ app.put("/api/v1/project/model-overrides", async (c) => {
705
1366
  const { overrides } = await c.req.json();
706
1367
  const configPath = join(root, "inkos.json");
707
1368
  const raw = JSON.parse(await readFile(configPath, "utf-8"));
@@ -711,11 +1372,11 @@ export function createStudioServer(initialConfig, root) {
711
1372
  return c.json({ ok: true });
712
1373
  });
713
1374
  // --- Notify channels ---
714
- app.get("/api/project/notify", async (c) => {
1375
+ app.get("/api/v1/project/notify", async (c) => {
715
1376
  const raw = JSON.parse(await readFile(join(root, "inkos.json"), "utf-8"));
716
1377
  return c.json({ channels: raw.notify ?? [] });
717
1378
  });
718
- app.put("/api/project/notify", async (c) => {
1379
+ app.put("/api/v1/project/notify", async (c) => {
719
1380
  const { channels } = await c.req.json();
720
1381
  const configPath = join(root, "inkos.json");
721
1382
  const raw = JSON.parse(await readFile(configPath, "utf-8"));
@@ -725,7 +1386,7 @@ export function createStudioServer(initialConfig, root) {
725
1386
  return c.json({ ok: true });
726
1387
  });
727
1388
  // --- AIGC Detection ---
728
- app.post("/api/books/:id/detect/:chapter", async (c) => {
1389
+ app.post("/api/v1/books/:id/detect/:chapter", async (c) => {
729
1390
  const id = c.req.param("id");
730
1391
  const chapterNum = parseInt(c.req.param("chapter"), 10);
731
1392
  const bookDir = state.bookDir(id);
@@ -746,7 +1407,7 @@ export function createStudioServer(initialConfig, root) {
746
1407
  }
747
1408
  });
748
1409
  // --- Truth file edit ---
749
- app.put("/api/books/:id/truth/:file", async (c) => {
1410
+ app.put("/api/v1/books/:id/truth/:file", async (c) => {
750
1411
  const id = c.req.param("id");
751
1412
  const file = c.req.param("file");
752
1413
  if (!TRUTH_FILES.includes(file)) {
@@ -763,7 +1424,7 @@ export function createStudioServer(initialConfig, root) {
763
1424
  // NEW ENDPOINTS — CLI parity
764
1425
  // =============================================
765
1426
  // --- Book Delete ---
766
- app.delete("/api/books/:id", async (c) => {
1427
+ app.delete("/api/v1/books/:id", async (c) => {
767
1428
  const id = c.req.param("id");
768
1429
  const bookDir = state.bookDir(id);
769
1430
  try {
@@ -777,7 +1438,7 @@ export function createStudioServer(initialConfig, root) {
777
1438
  }
778
1439
  });
779
1440
  // --- Book Update ---
780
- app.put("/api/books/:id", async (c) => {
1441
+ app.put("/api/v1/books/:id", async (c) => {
781
1442
  const id = c.req.param("id");
782
1443
  const updates = await c.req.json();
783
1444
  try {
@@ -798,7 +1459,7 @@ export function createStudioServer(initialConfig, root) {
798
1459
  }
799
1460
  });
800
1461
  // --- Write Rewrite (specific chapter) ---
801
- app.post("/api/books/:id/rewrite/:chapter", async (c) => {
1462
+ app.post("/api/v1/books/:id/rewrite/:chapter", async (c) => {
802
1463
  const id = c.req.param("id");
803
1464
  const chapterNum = parseInt(c.req.param("chapter"), 10);
804
1465
  const body = await c.req
@@ -819,7 +1480,7 @@ export function createStudioServer(initialConfig, root) {
819
1480
  return c.json({ error: String(e) }, 500);
820
1481
  }
821
1482
  });
822
- app.post("/api/books/:id/resync/:chapter", async (c) => {
1483
+ app.post("/api/v1/books/:id/resync/:chapter", async (c) => {
823
1484
  const id = c.req.param("id");
824
1485
  const chapterNum = parseInt(c.req.param("chapter"), 10);
825
1486
  const body = await c.req
@@ -837,7 +1498,7 @@ export function createStudioServer(initialConfig, root) {
837
1498
  }
838
1499
  });
839
1500
  // --- Detect All chapters ---
840
- app.post("/api/books/:id/detect-all", async (c) => {
1501
+ app.post("/api/v1/books/:id/detect-all", async (c) => {
841
1502
  const id = c.req.param("id");
842
1503
  const bookDir = state.bookDir(id);
843
1504
  try {
@@ -858,7 +1519,7 @@ export function createStudioServer(initialConfig, root) {
858
1519
  }
859
1520
  });
860
1521
  // --- Detect Stats ---
861
- app.get("/api/books/:id/detect/stats", async (c) => {
1522
+ app.get("/api/v1/books/:id/detect/stats", async (c) => {
862
1523
  const id = c.req.param("id");
863
1524
  try {
864
1525
  const { loadDetectionHistory, analyzeDetectionInsights } = await import("@actalk/inkos-core");
@@ -872,7 +1533,7 @@ export function createStudioServer(initialConfig, root) {
872
1533
  }
873
1534
  });
874
1535
  // --- Genre Create ---
875
- app.post("/api/genres/create", async (c) => {
1536
+ app.post("/api/v1/genres/create", async (c) => {
876
1537
  const body = await c.req.json();
877
1538
  if (!body.id || !body.name) {
878
1539
  return c.json({ error: "id and name are required" }, 400);
@@ -904,7 +1565,7 @@ export function createStudioServer(initialConfig, root) {
904
1565
  return c.json({ ok: true, id: body.id });
905
1566
  });
906
1567
  // --- Genre Edit ---
907
- app.put("/api/genres/:id", async (c) => {
1568
+ app.put("/api/v1/genres/:id", async (c) => {
908
1569
  const genreId = c.req.param("id");
909
1570
  if (/[/\\\0]/.test(genreId) || genreId.includes("..")) {
910
1571
  throw new ApiError(400, "INVALID_GENRE_ID", `Invalid genre ID: "${genreId}"`);
@@ -935,7 +1596,7 @@ export function createStudioServer(initialConfig, root) {
935
1596
  return c.json({ ok: true, id: genreId });
936
1597
  });
937
1598
  // --- Genre Delete (project-level only) ---
938
- app.delete("/api/genres/:id", async (c) => {
1599
+ app.delete("/api/v1/genres/:id", async (c) => {
939
1600
  const genreId = c.req.param("id");
940
1601
  if (/[/\\\0]/.test(genreId) || genreId.includes("..")) {
941
1602
  throw new ApiError(400, "INVALID_GENRE_ID", `Invalid genre ID: "${genreId}"`);
@@ -951,7 +1612,7 @@ export function createStudioServer(initialConfig, root) {
951
1612
  }
952
1613
  });
953
1614
  // --- Style Analyze ---
954
- app.post("/api/style/analyze", async (c) => {
1615
+ app.post("/api/v1/style/analyze", async (c) => {
955
1616
  const { text, sourceName } = await c.req.json();
956
1617
  if (!text?.trim())
957
1618
  return c.json({ error: "text is required" }, 400);
@@ -965,7 +1626,7 @@ export function createStudioServer(initialConfig, root) {
965
1626
  }
966
1627
  });
967
1628
  // --- Style Import to Book ---
968
- app.post("/api/books/:id/style/import", async (c) => {
1629
+ app.post("/api/v1/books/:id/style/import", async (c) => {
969
1630
  const id = c.req.param("id");
970
1631
  const { text, sourceName } = await c.req.json();
971
1632
  broadcast("style:start", { bookId: id });
@@ -981,7 +1642,7 @@ export function createStudioServer(initialConfig, root) {
981
1642
  }
982
1643
  });
983
1644
  // --- Import Chapters ---
984
- app.post("/api/books/:id/import/chapters", async (c) => {
1645
+ app.post("/api/v1/books/:id/import/chapters", async (c) => {
985
1646
  const id = c.req.param("id");
986
1647
  const { text, splitRegex } = await c.req.json();
987
1648
  if (!text?.trim())
@@ -1001,7 +1662,7 @@ export function createStudioServer(initialConfig, root) {
1001
1662
  }
1002
1663
  });
1003
1664
  // --- Import Canon ---
1004
- app.post("/api/books/:id/import/canon", async (c) => {
1665
+ app.post("/api/v1/books/:id/import/canon", async (c) => {
1005
1666
  const id = c.req.param("id");
1006
1667
  const { fromBookId } = await c.req.json();
1007
1668
  if (!fromBookId)
@@ -1019,7 +1680,7 @@ export function createStudioServer(initialConfig, root) {
1019
1680
  }
1020
1681
  });
1021
1682
  // --- Fanfic Init ---
1022
- app.post("/api/fanfic/init", async (c) => {
1683
+ app.post("/api/v1/fanfic/init", async (c) => {
1023
1684
  const body = await c.req.json();
1024
1685
  if (!body.title || !body.sourceText) {
1025
1686
  return c.json({ error: "title and sourceText are required" }, 400);
@@ -1052,7 +1713,7 @@ export function createStudioServer(initialConfig, root) {
1052
1713
  }
1053
1714
  });
1054
1715
  // --- Fanfic Show (read canon) ---
1055
- app.get("/api/books/:id/fanfic", async (c) => {
1716
+ app.get("/api/v1/books/:id/fanfic", async (c) => {
1056
1717
  const id = c.req.param("id");
1057
1718
  const bookDir = state.bookDir(id);
1058
1719
  try {
@@ -1064,7 +1725,7 @@ export function createStudioServer(initialConfig, root) {
1064
1725
  }
1065
1726
  });
1066
1727
  // --- Fanfic Refresh ---
1067
- app.post("/api/books/:id/fanfic/refresh", async (c) => {
1728
+ app.post("/api/v1/books/:id/fanfic/refresh", async (c) => {
1068
1729
  const id = c.req.param("id");
1069
1730
  const { sourceText, sourceName } = await c.req.json();
1070
1731
  if (!sourceText?.trim())
@@ -1083,7 +1744,7 @@ export function createStudioServer(initialConfig, root) {
1083
1744
  }
1084
1745
  });
1085
1746
  // --- Radar Scan ---
1086
- app.post("/api/radar/scan", async (c) => {
1747
+ app.post("/api/v1/radar/scan", async (c) => {
1087
1748
  broadcast("radar:start", {});
1088
1749
  try {
1089
1750
  const pipeline = new PipelineRunner(await buildPipelineConfig());
@@ -1097,7 +1758,7 @@ export function createStudioServer(initialConfig, root) {
1097
1758
  }
1098
1759
  });
1099
1760
  // --- Doctor (environment health check) ---
1100
- app.get("/api/doctor", async (c) => {
1761
+ app.get("/api/v1/doctor", async (c) => {
1101
1762
  const { existsSync } = await import("node:fs");
1102
1763
  const { GLOBAL_ENV_PATH } = await import("@actalk/inkos-core");
1103
1764
  const checks = {
@@ -1127,7 +1788,7 @@ export function createStudioServer(initialConfig, root) {
1127
1788
  }
1128
1789
  // --- Standalone runner ---
1129
1790
  export async function startStudioServer(root, port = 4567, options) {
1130
- const config = await loadProjectConfig(root);
1791
+ const config = await loadProjectConfig(root, { requireApiKey: false });
1131
1792
  const app = createStudioServer(config, root);
1132
1793
  // Serve frontend static files — single process for API + frontend
1133
1794
  if (options?.staticDir) {
@@ -1161,7 +1822,7 @@ export async function startStudioServer(root, port = 4567, options) {
1161
1822
  if (existsSync(indexPath)) {
1162
1823
  const indexHtml = await readFileFs(indexPath, "utf-8");
1163
1824
  app.get("*", (c) => {
1164
- if (c.req.path.startsWith("/api/"))
1825
+ if (c.req.path.startsWith("/api/v1/"))
1165
1826
  return c.notFound();
1166
1827
  return c.html(indexHtml);
1167
1828
  });