@zwbigi/ink-xy 0.1.2 → 0.1.4

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 (598) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -2
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/server/app/_global-error.html +1 -1
  5. package/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/api/inkos/route.js.nft.json +1 -1
  21. package/.next/server/app/api/skills/install/route.js.nft.json +1 -1
  22. package/.next/server/app/api/skills/search/route.js.nft.json +1 -1
  23. package/.next/server/app/index.html +1 -1
  24. package/.next/server/app/index.rsc +1 -1
  25. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  26. package/.next/server/app/index.segments/_full.segment.rsc +1 -1
  27. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  28. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  29. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  30. package/.next/server/app-paths-manifest.json +2 -2
  31. package/.next/server/chunks/162.js +1 -1
  32. package/.next/server/middleware-build-manifest.js +1 -1
  33. package/.next/server/pages/404.html +1 -1
  34. package/.next/server/pages/500.html +1 -1
  35. package/.next/trace +4 -4
  36. package/.next/trace-build +1 -1
  37. package/bin/pi-web.js +4 -1
  38. package/inkos/.env.example +20 -0
  39. package/inkos/.node-version +1 -0
  40. package/inkos/.nvmrc +1 -0
  41. package/inkos/CHANGELOG.md +787 -0
  42. package/inkos/CONTRIBUTING.md +89 -0
  43. package/inkos/LICENSE +661 -0
  44. package/inkos/README.en.md +483 -0
  45. package/inkos/README.ja.md +461 -0
  46. package/inkos/README.md +272 -0
  47. package/inkos/assets/15qun.jpg +0 -0
  48. package/inkos/assets/41777702961_.pic.jpg +0 -0
  49. package/inkos/assets/inkos-short-demo-cover.png +0 -0
  50. package/inkos/assets/inkos-text.svg +40 -0
  51. package/inkos/assets/logo.svg +47 -0
  52. package/inkos/assets/screenshot-chapters.png +0 -0
  53. package/inkos/assets/screenshot-pipeline.png +0 -0
  54. package/inkos/assets/screenshot-state.png +0 -0
  55. package/inkos/assets/screenshot-terminal.png +0 -0
  56. package/inkos/assets/wechat-group-v8.jpg +0 -0
  57. package/inkos/package.json +42 -0
  58. package/inkos/packages/cli/package.json +74 -0
  59. package/inkos/packages/cli/src/__tests__/analytics.test.ts +154 -0
  60. package/inkos/packages/cli/src/__tests__/cli-integration.test.ts +1031 -0
  61. package/inkos/packages/cli/src/__tests__/daemon.test.ts +93 -0
  62. package/inkos/packages/cli/src/__tests__/doctor.test.ts +36 -0
  63. package/inkos/packages/cli/src/__tests__/interact-command.test.ts +142 -0
  64. package/inkos/packages/cli/src/__tests__/interaction-tools.test.ts +107 -0
  65. package/inkos/packages/cli/src/__tests__/llm-overrides.test.ts +25 -0
  66. package/inkos/packages/cli/src/__tests__/localization.test.ts +121 -0
  67. package/inkos/packages/cli/src/__tests__/progress-text.test.ts +92 -0
  68. package/inkos/packages/cli/src/__tests__/project-bootstrap.test.ts +71 -0
  69. package/inkos/packages/cli/src/__tests__/publish-package.test.ts +272 -0
  70. package/inkos/packages/cli/src/__tests__/revision-command.test.ts +82 -0
  71. package/inkos/packages/cli/src/__tests__/runtime-requirements.test.ts +89 -0
  72. package/inkos/packages/cli/src/__tests__/short-fiction-command.test.ts +48 -0
  73. package/inkos/packages/cli/src/__tests__/studio-runtime.test.ts +142 -0
  74. package/inkos/packages/cli/src/__tests__/studio.test.ts +87 -0
  75. package/inkos/packages/cli/src/__tests__/tui-activity-state.test.ts +20 -0
  76. package/inkos/packages/cli/src/__tests__/tui-agent-session.test.ts +213 -0
  77. package/inkos/packages/cli/src/__tests__/tui-chat-depth.test.ts +27 -0
  78. package/inkos/packages/cli/src/__tests__/tui-chat-draft.test.ts +44 -0
  79. package/inkos/packages/cli/src/__tests__/tui-command.test.ts +86 -0
  80. package/inkos/packages/cli/src/__tests__/tui-composer-caret.test.ts +46 -0
  81. package/inkos/packages/cli/src/__tests__/tui-composer-display.test.ts +40 -0
  82. package/inkos/packages/cli/src/__tests__/tui-dashboard.test.tsx +219 -0
  83. package/inkos/packages/cli/src/__tests__/tui-effects-i18n.test.ts +29 -0
  84. package/inkos/packages/cli/src/__tests__/tui-i18n.test.ts +22 -0
  85. package/inkos/packages/cli/src/__tests__/tui-input-chrome.test.ts +10 -0
  86. package/inkos/packages/cli/src/__tests__/tui-input-history.test.ts +40 -0
  87. package/inkos/packages/cli/src/__tests__/tui-layout.test.ts +55 -0
  88. package/inkos/packages/cli/src/__tests__/tui-local-commands.test.ts +47 -0
  89. package/inkos/packages/cli/src/__tests__/tui-session-store.test.ts +81 -0
  90. package/inkos/packages/cli/src/__tests__/tui-setup-i18n.test.ts +31 -0
  91. package/inkos/packages/cli/src/__tests__/tui-slash-autocomplete.test.ts +33 -0
  92. package/inkos/packages/cli/src/commands/agent.ts +65 -0
  93. package/inkos/packages/cli/src/commands/analytics.ts +77 -0
  94. package/inkos/packages/cli/src/commands/audit.ts +52 -0
  95. package/inkos/packages/cli/src/commands/book.ts +260 -0
  96. package/inkos/packages/cli/src/commands/compose.ts +50 -0
  97. package/inkos/packages/cli/src/commands/config.ts +328 -0
  98. package/inkos/packages/cli/src/commands/consolidate.ts +50 -0
  99. package/inkos/packages/cli/src/commands/daemon.ts +121 -0
  100. package/inkos/packages/cli/src/commands/detect.ts +125 -0
  101. package/inkos/packages/cli/src/commands/doctor.ts +391 -0
  102. package/inkos/packages/cli/src/commands/draft.ts +43 -0
  103. package/inkos/packages/cli/src/commands/eval.ts +217 -0
  104. package/inkos/packages/cli/src/commands/export.ts +45 -0
  105. package/inkos/packages/cli/src/commands/fanfic.ts +183 -0
  106. package/inkos/packages/cli/src/commands/genre.ts +160 -0
  107. package/inkos/packages/cli/src/commands/import.ts +158 -0
  108. package/inkos/packages/cli/src/commands/init.ts +47 -0
  109. package/inkos/packages/cli/src/commands/interact.ts +109 -0
  110. package/inkos/packages/cli/src/commands/plan.ts +54 -0
  111. package/inkos/packages/cli/src/commands/radar.ts +60 -0
  112. package/inkos/packages/cli/src/commands/review.ts +253 -0
  113. package/inkos/packages/cli/src/commands/revise.ts +58 -0
  114. package/inkos/packages/cli/src/commands/short-fiction.ts +294 -0
  115. package/inkos/packages/cli/src/commands/status.ts +138 -0
  116. package/inkos/packages/cli/src/commands/studio.ts +194 -0
  117. package/inkos/packages/cli/src/commands/style.ts +99 -0
  118. package/inkos/packages/cli/src/commands/tui.ts +18 -0
  119. package/inkos/packages/cli/src/commands/update.ts +45 -0
  120. package/inkos/packages/cli/src/commands/write.ts +324 -0
  121. package/inkos/packages/cli/src/index.ts +5 -0
  122. package/inkos/packages/cli/src/interaction/tools.ts +49 -0
  123. package/inkos/packages/cli/src/localization.ts +215 -0
  124. package/inkos/packages/cli/src/program.ts +106 -0
  125. package/inkos/packages/cli/src/progress-text.ts +85 -0
  126. package/inkos/packages/cli/src/project-bootstrap.ts +175 -0
  127. package/inkos/packages/cli/src/runtime-requirements.ts +135 -0
  128. package/inkos/packages/cli/src/tui/__tests__/markdown.test.ts +64 -0
  129. package/inkos/packages/cli/src/tui/activity-state.ts +41 -0
  130. package/inkos/packages/cli/src/tui/agent-input.ts +264 -0
  131. package/inkos/packages/cli/src/tui/ansi.ts +72 -0
  132. package/inkos/packages/cli/src/tui/app.ts +130 -0
  133. package/inkos/packages/cli/src/tui/chat-depth.ts +22 -0
  134. package/inkos/packages/cli/src/tui/chat-draft.ts +42 -0
  135. package/inkos/packages/cli/src/tui/composer-caret.ts +22 -0
  136. package/inkos/packages/cli/src/tui/composer-display.ts +26 -0
  137. package/inkos/packages/cli/src/tui/dashboard-model.ts +164 -0
  138. package/inkos/packages/cli/src/tui/dashboard.tsx +544 -0
  139. package/inkos/packages/cli/src/tui/effects.ts +542 -0
  140. package/inkos/packages/cli/src/tui/i18n.ts +278 -0
  141. package/inkos/packages/cli/src/tui/input-history.ts +69 -0
  142. package/inkos/packages/cli/src/tui/local-commands.ts +55 -0
  143. package/inkos/packages/cli/src/tui/markdown.ts +64 -0
  144. package/inkos/packages/cli/src/tui/session-store.ts +6 -0
  145. package/inkos/packages/cli/src/tui/setup.ts +397 -0
  146. package/inkos/packages/cli/src/tui/slash-autocomplete.ts +62 -0
  147. package/inkos/packages/cli/src/tui/theme.ts +17 -0
  148. package/inkos/packages/cli/src/utils.ts +222 -0
  149. package/inkos/packages/cli/tsconfig.json +9 -0
  150. package/inkos/packages/core/genres/cozy.md +43 -0
  151. package/inkos/packages/core/genres/cultivation.md +42 -0
  152. package/inkos/packages/core/genres/dungeon-core.md +40 -0
  153. package/inkos/packages/core/genres/horror.md +51 -0
  154. package/inkos/packages/core/genres/isekai.md +43 -0
  155. package/inkos/packages/core/genres/litrpg.md +43 -0
  156. package/inkos/packages/core/genres/other.md +24 -0
  157. package/inkos/packages/core/genres/progression.md +41 -0
  158. package/inkos/packages/core/genres/romantasy.md +45 -0
  159. package/inkos/packages/core/genres/sci-fi.md +42 -0
  160. package/inkos/packages/core/genres/system-apocalypse.md +40 -0
  161. package/inkos/packages/core/genres/tower-climber.md +41 -0
  162. package/inkos/packages/core/genres/urban.md +53 -0
  163. package/inkos/packages/core/genres/xianxia.md +46 -0
  164. package/inkos/packages/core/genres/xuanhuan.md +64 -0
  165. package/inkos/packages/core/package.json +61 -0
  166. package/inkos/packages/core/src/__tests__/agent-max-tokens-policy.test.ts +29 -0
  167. package/inkos/packages/core/src/__tests__/agent-session.test.ts +866 -0
  168. package/inkos/packages/core/src/__tests__/agent-system-prompt.test.ts +167 -0
  169. package/inkos/packages/core/src/__tests__/agent-tools-params.test.ts +197 -0
  170. package/inkos/packages/core/src/__tests__/agent-tools.test.ts +421 -0
  171. package/inkos/packages/core/src/__tests__/ai-tells.test.ts +90 -0
  172. package/inkos/packages/core/src/__tests__/architect-phase5-consolidated.test.ts +445 -0
  173. package/inkos/packages/core/src/__tests__/architect-phase5.test.ts +455 -0
  174. package/inkos/packages/core/src/__tests__/architect-phase7.test.ts +210 -0
  175. package/inkos/packages/core/src/__tests__/architect.test.ts +859 -0
  176. package/inkos/packages/core/src/__tests__/audit-parse.test.ts +78 -0
  177. package/inkos/packages/core/src/__tests__/book-id.test.ts +26 -0
  178. package/inkos/packages/core/src/__tests__/book-session-store.test.ts +447 -0
  179. package/inkos/packages/core/src/__tests__/book-session.test.ts +113 -0
  180. package/inkos/packages/core/src/__tests__/chapter-analyzer.test.ts +574 -0
  181. package/inkos/packages/core/src/__tests__/chapter-memo-parser.test.ts +247 -0
  182. package/inkos/packages/core/src/__tests__/chapter-persistence.test.ts +198 -0
  183. package/inkos/packages/core/src/__tests__/chapter-review-cycle.test.ts +294 -0
  184. package/inkos/packages/core/src/__tests__/chapter-splitter.test.ts +156 -0
  185. package/inkos/packages/core/src/__tests__/chapter-state-recovery.test.ts +235 -0
  186. package/inkos/packages/core/src/__tests__/chapter-truth-validation.test.ts +253 -0
  187. package/inkos/packages/core/src/__tests__/composer.test.ts +627 -0
  188. package/inkos/packages/core/src/__tests__/config-loader.test.ts +325 -0
  189. package/inkos/packages/core/src/__tests__/config-migration.test.ts +102 -0
  190. package/inkos/packages/core/src/__tests__/consolidator.test.ts +32 -0
  191. package/inkos/packages/core/src/__tests__/context-filter.test.ts +60 -0
  192. package/inkos/packages/core/src/__tests__/context-transform.test.ts +108 -0
  193. package/inkos/packages/core/src/__tests__/continuity.test.ts +391 -0
  194. package/inkos/packages/core/src/__tests__/detection-insights.test.ts +59 -0
  195. package/inkos/packages/core/src/__tests__/detector.test.ts +86 -0
  196. package/inkos/packages/core/src/__tests__/draft-directive-parser.test.ts +386 -0
  197. package/inkos/packages/core/src/__tests__/edit-controller.test.ts +190 -0
  198. package/inkos/packages/core/src/__tests__/effective-llm-config.test.ts +486 -0
  199. package/inkos/packages/core/src/__tests__/fanfic-dimensions.test.ts +58 -0
  200. package/inkos/packages/core/src/__tests__/fanfic-models.test.ts +69 -0
  201. package/inkos/packages/core/src/__tests__/governed-working-set.test.ts +155 -0
  202. package/inkos/packages/core/src/__tests__/hook-arbiter.test.ts +124 -0
  203. package/inkos/packages/core/src/__tests__/hook-governance.test.ts +228 -0
  204. package/inkos/packages/core/src/__tests__/hook-health.test.ts +166 -0
  205. package/inkos/packages/core/src/__tests__/hook-ledger-validator.test.ts +236 -0
  206. package/inkos/packages/core/src/__tests__/hook-promotion.test.ts +192 -0
  207. package/inkos/packages/core/src/__tests__/hook-stale-detection.test.ts +136 -0
  208. package/inkos/packages/core/src/__tests__/index-notify-lazy.test.ts +20 -0
  209. package/inkos/packages/core/src/__tests__/interaction-chat-tokens.test.ts +170 -0
  210. package/inkos/packages/core/src/__tests__/interaction-models.test.ts +155 -0
  211. package/inkos/packages/core/src/__tests__/interaction-nl-router.test.ts +223 -0
  212. package/inkos/packages/core/src/__tests__/interaction-runtime.test.ts +633 -0
  213. package/inkos/packages/core/src/__tests__/interaction-tools.test.ts +343 -0
  214. package/inkos/packages/core/src/__tests__/length-metrics.test.ts +82 -0
  215. package/inkos/packages/core/src/__tests__/length-normalizer.test.ts +331 -0
  216. package/inkos/packages/core/src/__tests__/list-models.test.ts +109 -0
  217. package/inkos/packages/core/src/__tests__/llm-env.test.ts +31 -0
  218. package/inkos/packages/core/src/__tests__/logger.test.ts +175 -0
  219. package/inkos/packages/core/src/__tests__/long-span-fatigue.test.ts +160 -0
  220. package/inkos/packages/core/src/__tests__/memory-retrieval.test.ts +1303 -0
  221. package/inkos/packages/core/src/__tests__/models.test.ts +918 -0
  222. package/inkos/packages/core/src/__tests__/outline-paths.test.ts +97 -0
  223. package/inkos/packages/core/src/__tests__/path-safety.test.ts +22 -0
  224. package/inkos/packages/core/src/__tests__/persisted-governed-plan.test.ts +134 -0
  225. package/inkos/packages/core/src/__tests__/phase5-cleanup.test.ts +393 -0
  226. package/inkos/packages/core/src/__tests__/phase5-hotfix.test.ts +288 -0
  227. package/inkos/packages/core/src/__tests__/phase7-hotfix.test.ts +614 -0
  228. package/inkos/packages/core/src/__tests__/pipeline-agent.test.ts +354 -0
  229. package/inkos/packages/core/src/__tests__/pipeline-runner-memory-sync.test.ts +317 -0
  230. package/inkos/packages/core/src/__tests__/pipeline-runner.test.ts +5200 -0
  231. package/inkos/packages/core/src/__tests__/planner-context.test.ts +137 -0
  232. package/inkos/packages/core/src/__tests__/planner-prompts-ratio.test.ts +11 -0
  233. package/inkos/packages/core/src/__tests__/planner-prompts.test.ts +171 -0
  234. package/inkos/packages/core/src/__tests__/planner.test.ts +362 -0
  235. package/inkos/packages/core/src/__tests__/planning-materials.test.ts +90 -0
  236. package/inkos/packages/core/src/__tests__/polisher.test.ts +189 -0
  237. package/inkos/packages/core/src/__tests__/post-write-validator.test.ts +291 -0
  238. package/inkos/packages/core/src/__tests__/probe.test.ts +77 -0
  239. package/inkos/packages/core/src/__tests__/project-interaction.test.ts +241 -0
  240. package/inkos/packages/core/src/__tests__/provider.test.ts +953 -0
  241. package/inkos/packages/core/src/__tests__/providers-group.test.ts +34 -0
  242. package/inkos/packages/core/src/__tests__/providers-lookup.test.ts +81 -0
  243. package/inkos/packages/core/src/__tests__/providers-schema.test.ts +158 -0
  244. package/inkos/packages/core/src/__tests__/proxy-fetch.test.ts +75 -0
  245. package/inkos/packages/core/src/__tests__/revise-foundation.test.ts +514 -0
  246. package/inkos/packages/core/src/__tests__/reviser.test.ts +859 -0
  247. package/inkos/packages/core/src/__tests__/runtime-state-store.test.ts +388 -0
  248. package/inkos/packages/core/src/__tests__/scheduler.test.ts +123 -0
  249. package/inkos/packages/core/src/__tests__/secrets-migration.test.ts +71 -0
  250. package/inkos/packages/core/src/__tests__/secrets.test.ts +95 -0
  251. package/inkos/packages/core/src/__tests__/sensitive-words.test.ts +88 -0
  252. package/inkos/packages/core/src/__tests__/service-presets-regression.test.ts +73 -0
  253. package/inkos/packages/core/src/__tests__/service-resolver-regression.test.ts +75 -0
  254. package/inkos/packages/core/src/__tests__/service-resolver.test.ts +228 -0
  255. package/inkos/packages/core/src/__tests__/session-transcript-restore.test.ts +1311 -0
  256. package/inkos/packages/core/src/__tests__/session-transcript.test.ts +195 -0
  257. package/inkos/packages/core/src/__tests__/settler-delta-parser.test.ts +133 -0
  258. package/inkos/packages/core/src/__tests__/short-fiction-public.test.ts +241 -0
  259. package/inkos/packages/core/src/__tests__/spot-fix-patches.test.ts +104 -0
  260. package/inkos/packages/core/src/__tests__/state-manager.test.ts +1298 -0
  261. package/inkos/packages/core/src/__tests__/state-projections.test.ts +130 -0
  262. package/inkos/packages/core/src/__tests__/state-reducer.test.ts +372 -0
  263. package/inkos/packages/core/src/__tests__/state-validator-agent.test.ts +165 -0
  264. package/inkos/packages/core/src/__tests__/state-validator.test.ts +122 -0
  265. package/inkos/packages/core/src/__tests__/style-analyzer.test.ts +61 -0
  266. package/inkos/packages/core/src/__tests__/temperature-constraints.test.ts +57 -0
  267. package/inkos/packages/core/src/__tests__/v13-hotfix-round4.test.ts +343 -0
  268. package/inkos/packages/core/src/__tests__/verify-service.test.ts +77 -0
  269. package/inkos/packages/core/src/__tests__/webhook.test.ts +91 -0
  270. package/inkos/packages/core/src/__tests__/writer-parser.test.ts +348 -0
  271. package/inkos/packages/core/src/__tests__/writer-prompts.test.ts +269 -0
  272. package/inkos/packages/core/src/__tests__/writer.test.ts +1360 -0
  273. package/inkos/packages/core/src/agent/agent-session.ts +737 -0
  274. package/inkos/packages/core/src/agent/agent-system-prompt.ts +199 -0
  275. package/inkos/packages/core/src/agent/agent-tools.ts +835 -0
  276. package/inkos/packages/core/src/agent/context-transform.ts +85 -0
  277. package/inkos/packages/core/src/agent/index.ts +14 -0
  278. package/inkos/packages/core/src/agents/ai-tells.ts +161 -0
  279. package/inkos/packages/core/src/agents/architect.ts +1291 -0
  280. package/inkos/packages/core/src/agents/base.ts +100 -0
  281. package/inkos/packages/core/src/agents/chapter-analyzer.ts +634 -0
  282. package/inkos/packages/core/src/agents/composer.ts +469 -0
  283. package/inkos/packages/core/src/agents/consolidator.ts +218 -0
  284. package/inkos/packages/core/src/agents/continuity.ts +824 -0
  285. package/inkos/packages/core/src/agents/detection-insights.ts +72 -0
  286. package/inkos/packages/core/src/agents/detector.ts +224 -0
  287. package/inkos/packages/core/src/agents/en-prompt-sections.ts +129 -0
  288. package/inkos/packages/core/src/agents/fanfic-canon-importer.ts +146 -0
  289. package/inkos/packages/core/src/agents/fanfic-dimensions.ts +87 -0
  290. package/inkos/packages/core/src/agents/fanfic-prompt-sections.ts +109 -0
  291. package/inkos/packages/core/src/agents/foundation-reviewer.ts +204 -0
  292. package/inkos/packages/core/src/agents/length-normalizer.ts +218 -0
  293. package/inkos/packages/core/src/agents/observer-prompts.ts +127 -0
  294. package/inkos/packages/core/src/agents/planner-context.ts +297 -0
  295. package/inkos/packages/core/src/agents/planner-prompts.ts +404 -0
  296. package/inkos/packages/core/src/agents/planner.ts +783 -0
  297. package/inkos/packages/core/src/agents/polisher.ts +153 -0
  298. package/inkos/packages/core/src/agents/post-write-validator.ts +873 -0
  299. package/inkos/packages/core/src/agents/radar-source.ts +123 -0
  300. package/inkos/packages/core/src/agents/radar.ts +120 -0
  301. package/inkos/packages/core/src/agents/reviser.ts +701 -0
  302. package/inkos/packages/core/src/agents/rules-reader.ts +155 -0
  303. package/inkos/packages/core/src/agents/sensitive-words.ts +142 -0
  304. package/inkos/packages/core/src/agents/settler-delta-parser.ts +53 -0
  305. package/inkos/packages/core/src/agents/settler-parser.ts +38 -0
  306. package/inkos/packages/core/src/agents/settler-prompts.ts +230 -0
  307. package/inkos/packages/core/src/agents/short-fiction.ts +429 -0
  308. package/inkos/packages/core/src/agents/state-validator.ts +322 -0
  309. package/inkos/packages/core/src/agents/style-analyzer.ts +93 -0
  310. package/inkos/packages/core/src/agents/writer-parser.ts +178 -0
  311. package/inkos/packages/core/src/agents/writer-prompts.ts +899 -0
  312. package/inkos/packages/core/src/agents/writer.ts +1450 -0
  313. package/inkos/packages/core/src/index.ts +392 -0
  314. package/inkos/packages/core/src/interaction/book-session-store.ts +226 -0
  315. package/inkos/packages/core/src/interaction/draft-directive-parser.ts +266 -0
  316. package/inkos/packages/core/src/interaction/edit-controller.ts +270 -0
  317. package/inkos/packages/core/src/interaction/events.ts +41 -0
  318. package/inkos/packages/core/src/interaction/export-artifact.ts +151 -0
  319. package/inkos/packages/core/src/interaction/intents.ts +63 -0
  320. package/inkos/packages/core/src/interaction/modes.ts +13 -0
  321. package/inkos/packages/core/src/interaction/nl-router.ts +258 -0
  322. package/inkos/packages/core/src/interaction/project-control.ts +150 -0
  323. package/inkos/packages/core/src/interaction/project-session-store.ts +81 -0
  324. package/inkos/packages/core/src/interaction/project-tools.ts +704 -0
  325. package/inkos/packages/core/src/interaction/request-router.ts +5 -0
  326. package/inkos/packages/core/src/interaction/runtime.ts +1167 -0
  327. package/inkos/packages/core/src/interaction/session-transcript-legacy.ts +113 -0
  328. package/inkos/packages/core/src/interaction/session-transcript-restore.ts +607 -0
  329. package/inkos/packages/core/src/interaction/session-transcript-schema.ts +76 -0
  330. package/inkos/packages/core/src/interaction/session-transcript.ts +189 -0
  331. package/inkos/packages/core/src/interaction/session.ts +226 -0
  332. package/inkos/packages/core/src/interaction/truth-authority.ts +45 -0
  333. package/inkos/packages/core/src/llm/config-migration.ts +58 -0
  334. package/inkos/packages/core/src/llm/cover-providers.ts +45 -0
  335. package/inkos/packages/core/src/llm/provider.ts +1331 -0
  336. package/inkos/packages/core/src/llm/providers/endpoints/ai360.ts +42 -0
  337. package/inkos/packages/core/src/llm/providers/endpoints/anthropic.ts +82 -0
  338. package/inkos/packages/core/src/llm/providers/endpoints/astronCodingPlan.ts +30 -0
  339. package/inkos/packages/core/src/llm/providers/endpoints/baichuan.ts +28 -0
  340. package/inkos/packages/core/src/llm/providers/endpoints/bailian.ts +65 -0
  341. package/inkos/packages/core/src/llm/providers/endpoints/bailianCodingPlan.ts +30 -0
  342. package/inkos/packages/core/src/llm/providers/endpoints/custom.ts +22 -0
  343. package/inkos/packages/core/src/llm/providers/endpoints/deepseek.ts +35 -0
  344. package/inkos/packages/core/src/llm/providers/endpoints/giteeai.ts +41 -0
  345. package/inkos/packages/core/src/llm/providers/endpoints/githubCopilot.ts +43 -0
  346. package/inkos/packages/core/src/llm/providers/endpoints/glmCodingPlan.ts +28 -0
  347. package/inkos/packages/core/src/llm/providers/endpoints/google.ts +51 -0
  348. package/inkos/packages/core/src/llm/providers/endpoints/hunyuan.ts +42 -0
  349. package/inkos/packages/core/src/llm/providers/endpoints/infiniai.ts +72 -0
  350. package/inkos/packages/core/src/llm/providers/endpoints/internlm.ts +28 -0
  351. package/inkos/packages/core/src/llm/providers/endpoints/kimiCode.ts +23 -0
  352. package/inkos/packages/core/src/llm/providers/endpoints/kimiCodingPlan.ts +24 -0
  353. package/inkos/packages/core/src/llm/providers/endpoints/kkaiapi.ts +56 -0
  354. package/inkos/packages/core/src/llm/providers/endpoints/longcat.ts +25 -0
  355. package/inkos/packages/core/src/llm/providers/endpoints/minimax.ts +39 -0
  356. package/inkos/packages/core/src/llm/providers/endpoints/minimaxCodingPlan.ts +28 -0
  357. package/inkos/packages/core/src/llm/providers/endpoints/mistral.ts +40 -0
  358. package/inkos/packages/core/src/llm/providers/endpoints/modelscope.ts +30 -0
  359. package/inkos/packages/core/src/llm/providers/endpoints/moonshot.ts +39 -0
  360. package/inkos/packages/core/src/llm/providers/endpoints/newapi.ts +21 -0
  361. package/inkos/packages/core/src/llm/providers/endpoints/ollama.ts +73 -0
  362. package/inkos/packages/core/src/llm/providers/endpoints/openai.ts +77 -0
  363. package/inkos/packages/core/src/llm/providers/endpoints/opencodeCodingPlan.ts +30 -0
  364. package/inkos/packages/core/src/llm/providers/endpoints/openrouter.ts +87 -0
  365. package/inkos/packages/core/src/llm/providers/endpoints/ppio.ts +86 -0
  366. package/inkos/packages/core/src/llm/providers/endpoints/qiniu.ts +32 -0
  367. package/inkos/packages/core/src/llm/providers/endpoints/sensenova.ts +45 -0
  368. package/inkos/packages/core/src/llm/providers/endpoints/siliconcloud.ts +126 -0
  369. package/inkos/packages/core/src/llm/providers/endpoints/spark.ts +33 -0
  370. package/inkos/packages/core/src/llm/providers/endpoints/stepfun.ts +35 -0
  371. package/inkos/packages/core/src/llm/providers/endpoints/tencentcloud.ts +25 -0
  372. package/inkos/packages/core/src/llm/providers/endpoints/volcengine.ts +52 -0
  373. package/inkos/packages/core/src/llm/providers/endpoints/volcengineCodingPlan.ts +42 -0
  374. package/inkos/packages/core/src/llm/providers/endpoints/wenxin.ts +106 -0
  375. package/inkos/packages/core/src/llm/providers/endpoints/xai.ts +34 -0
  376. package/inkos/packages/core/src/llm/providers/endpoints/xiaomimimo.ts +26 -0
  377. package/inkos/packages/core/src/llm/providers/endpoints/zeroone.ts +34 -0
  378. package/inkos/packages/core/src/llm/providers/endpoints/zhipu.ts +61 -0
  379. package/inkos/packages/core/src/llm/providers/index.ts +71 -0
  380. package/inkos/packages/core/src/llm/providers/lookup.ts +70 -0
  381. package/inkos/packages/core/src/llm/providers/probe.ts +35 -0
  382. package/inkos/packages/core/src/llm/providers/types.ts +89 -0
  383. package/inkos/packages/core/src/llm/providers/verify.ts +104 -0
  384. package/inkos/packages/core/src/llm/secrets.ts +77 -0
  385. package/inkos/packages/core/src/llm/service-presets.ts +215 -0
  386. package/inkos/packages/core/src/llm/service-resolver.ts +91 -0
  387. package/inkos/packages/core/src/models/book-rules.ts +126 -0
  388. package/inkos/packages/core/src/models/book.ts +70 -0
  389. package/inkos/packages/core/src/models/chapter.ts +42 -0
  390. package/inkos/packages/core/src/models/detection.ts +25 -0
  391. package/inkos/packages/core/src/models/genre-profile.ts +36 -0
  392. package/inkos/packages/core/src/models/input-governance.ts +99 -0
  393. package/inkos/packages/core/src/models/length-governance.ts +46 -0
  394. package/inkos/packages/core/src/models/project.ts +161 -0
  395. package/inkos/packages/core/src/models/runtime-state.ts +144 -0
  396. package/inkos/packages/core/src/models/state.ts +52 -0
  397. package/inkos/packages/core/src/models/style-profile.ts +15 -0
  398. package/inkos/packages/core/src/notify/dispatcher.ts +96 -0
  399. package/inkos/packages/core/src/notify/feishu.ts +34 -0
  400. package/inkos/packages/core/src/notify/telegram.ts +25 -0
  401. package/inkos/packages/core/src/notify/webhook.ts +58 -0
  402. package/inkos/packages/core/src/notify/wechat-work.ts +22 -0
  403. package/inkos/packages/core/src/pipeline/agent.ts +691 -0
  404. package/inkos/packages/core/src/pipeline/chapter-persistence.ts +79 -0
  405. package/inkos/packages/core/src/pipeline/chapter-review-cycle.ts +324 -0
  406. package/inkos/packages/core/src/pipeline/chapter-state-recovery.ts +236 -0
  407. package/inkos/packages/core/src/pipeline/chapter-truth-validation.ts +145 -0
  408. package/inkos/packages/core/src/pipeline/detection-runner.ts +164 -0
  409. package/inkos/packages/core/src/pipeline/persisted-governed-plan.ts +216 -0
  410. package/inkos/packages/core/src/pipeline/runner.ts +3438 -0
  411. package/inkos/packages/core/src/pipeline/scheduler.ts +411 -0
  412. package/inkos/packages/core/src/pipeline/short-fiction-runner.ts +801 -0
  413. package/inkos/packages/core/src/prompts/index.ts +1 -0
  414. package/inkos/packages/core/src/prompts/short-fiction.ts +273 -0
  415. package/inkos/packages/core/src/state/manager.ts +560 -0
  416. package/inkos/packages/core/src/state/memory-db.ts +359 -0
  417. package/inkos/packages/core/src/state/runtime-state-store.ts +164 -0
  418. package/inkos/packages/core/src/state/state-bootstrap.ts +657 -0
  419. package/inkos/packages/core/src/state/state-projections.ts +255 -0
  420. package/inkos/packages/core/src/state/state-reducer.ts +260 -0
  421. package/inkos/packages/core/src/state/state-validator.ts +117 -0
  422. package/inkos/packages/core/src/utils/analytics.ts +92 -0
  423. package/inkos/packages/core/src/utils/book-id.ts +31 -0
  424. package/inkos/packages/core/src/utils/cadence-policy.ts +46 -0
  425. package/inkos/packages/core/src/utils/chapter-cadence.ts +211 -0
  426. package/inkos/packages/core/src/utils/chapter-memo-parser.ts +157 -0
  427. package/inkos/packages/core/src/utils/chapter-splitter.ts +80 -0
  428. package/inkos/packages/core/src/utils/config-loader.ts +29 -0
  429. package/inkos/packages/core/src/utils/context-assembly.ts +98 -0
  430. package/inkos/packages/core/src/utils/context-filter.ts +190 -0
  431. package/inkos/packages/core/src/utils/effective-llm-config.ts +529 -0
  432. package/inkos/packages/core/src/utils/governed-context.ts +101 -0
  433. package/inkos/packages/core/src/utils/governed-working-set.ts +395 -0
  434. package/inkos/packages/core/src/utils/hook-arbiter.ts +332 -0
  435. package/inkos/packages/core/src/utils/hook-governance.ts +199 -0
  436. package/inkos/packages/core/src/utils/hook-health.ts +189 -0
  437. package/inkos/packages/core/src/utils/hook-ledger-validator.ts +277 -0
  438. package/inkos/packages/core/src/utils/hook-lifecycle.ts +224 -0
  439. package/inkos/packages/core/src/utils/hook-policy.ts +115 -0
  440. package/inkos/packages/core/src/utils/hook-promotion.ts +313 -0
  441. package/inkos/packages/core/src/utils/hook-stale-detection.ts +168 -0
  442. package/inkos/packages/core/src/utils/length-metrics.ts +123 -0
  443. package/inkos/packages/core/src/utils/llm-endpoint-auth.ts +40 -0
  444. package/inkos/packages/core/src/utils/llm-env.ts +74 -0
  445. package/inkos/packages/core/src/utils/logger.ts +123 -0
  446. package/inkos/packages/core/src/utils/long-span-fatigue.ts +545 -0
  447. package/inkos/packages/core/src/utils/memory-retrieval.ts +527 -0
  448. package/inkos/packages/core/src/utils/narrative-control.ts +177 -0
  449. package/inkos/packages/core/src/utils/outline-paths.ts +275 -0
  450. package/inkos/packages/core/src/utils/path-safety.ts +11 -0
  451. package/inkos/packages/core/src/utils/planning-materials.ts +185 -0
  452. package/inkos/packages/core/src/utils/pov-filter.ts +149 -0
  453. package/inkos/packages/core/src/utils/proxy-fetch.ts +44 -0
  454. package/inkos/packages/core/src/utils/runtime-writer.ts +41 -0
  455. package/inkos/packages/core/src/utils/spot-fix-patches.ts +189 -0
  456. package/inkos/packages/core/src/utils/story-markdown.ts +346 -0
  457. package/inkos/packages/core/src/utils/web-search.ts +82 -0
  458. package/inkos/packages/core/src/utils/writing-methodology.ts +164 -0
  459. package/inkos/packages/core/tsconfig.json +8 -0
  460. package/inkos/packages/core/vitest.config.ts +7 -0
  461. package/inkos/packages/studio/components.json +25 -0
  462. package/inkos/packages/studio/index.html +13 -0
  463. package/inkos/packages/studio/package.json +72 -0
  464. package/inkos/packages/studio/postcss.config.js +3 -0
  465. package/inkos/packages/studio/src/App.test.ts +25 -0
  466. package/inkos/packages/studio/src/App.tsx +280 -0
  467. package/inkos/packages/studio/src/api/__tests__/normalize-base-url.test.ts +40 -0
  468. package/inkos/packages/studio/src/api/book-create.test.ts +104 -0
  469. package/inkos/packages/studio/src/api/book-create.ts +94 -0
  470. package/inkos/packages/studio/src/api/errors.ts +17 -0
  471. package/inkos/packages/studio/src/api/index.ts +30 -0
  472. package/inkos/packages/studio/src/api/lib/run-store.ts +177 -0
  473. package/inkos/packages/studio/src/api/lib/sse.ts +50 -0
  474. package/inkos/packages/studio/src/api/phase5-hotfix.test.ts +335 -0
  475. package/inkos/packages/studio/src/api/safety.ts +6 -0
  476. package/inkos/packages/studio/src/api/server.test.ts +3162 -0
  477. package/inkos/packages/studio/src/api/server.ts +3666 -0
  478. package/inkos/packages/studio/src/api/v13-hotfix-round4.test.ts +226 -0
  479. package/inkos/packages/studio/src/app-state.test.ts +8 -0
  480. package/inkos/packages/studio/src/app-state.ts +1 -0
  481. package/inkos/packages/studio/src/components/ConfirmDialog.tsx +95 -0
  482. package/inkos/packages/studio/src/components/ServiceConfigSourceCard.tsx +139 -0
  483. package/inkos/packages/studio/src/components/ServiceQuickLinks.tsx +65 -0
  484. package/inkos/packages/studio/src/components/Sidebar.tsx +652 -0
  485. package/inkos/packages/studio/src/components/ai-elements/code-block.tsx +562 -0
  486. package/inkos/packages/studio/src/components/ai-elements/confirmation.tsx +174 -0
  487. package/inkos/packages/studio/src/components/ai-elements/message.tsx +360 -0
  488. package/inkos/packages/studio/src/components/ai-elements/prompt-input.tsx +1457 -0
  489. package/inkos/packages/studio/src/components/ai-elements/reasoning.tsx +226 -0
  490. package/inkos/packages/studio/src/components/ai-elements/shimmer.tsx +77 -0
  491. package/inkos/packages/studio/src/components/ai-elements/tool.tsx +173 -0
  492. package/inkos/packages/studio/src/components/chat/BookSidebar.tsx +291 -0
  493. package/inkos/packages/studio/src/components/chat/ChatMessage.tsx +39 -0
  494. package/inkos/packages/studio/src/components/chat/QuickActions.tsx +73 -0
  495. package/inkos/packages/studio/src/components/chat/ToolExecutionSteps.tsx +320 -0
  496. package/inkos/packages/studio/src/components/chat/__tests__/ToolExecutionSteps.test.ts +114 -0
  497. package/inkos/packages/studio/src/components/chat-utils.ts +56 -0
  498. package/inkos/packages/studio/src/components/chatbar-state.test.ts +69 -0
  499. package/inkos/packages/studio/src/components/sidebar/ChaptersSection.tsx +66 -0
  500. package/inkos/packages/studio/src/components/sidebar/CharacterSection.tsx +129 -0
  501. package/inkos/packages/studio/src/components/sidebar/FoundationSection.tsx +61 -0
  502. package/inkos/packages/studio/src/components/sidebar/ProgressSection.tsx +124 -0
  503. package/inkos/packages/studio/src/components/sidebar/SidebarCard.tsx +30 -0
  504. package/inkos/packages/studio/src/components/sidebar/SummarySection.tsx +89 -0
  505. package/inkos/packages/studio/src/components/ui/alert.tsx +76 -0
  506. package/inkos/packages/studio/src/components/ui/badge.tsx +52 -0
  507. package/inkos/packages/studio/src/components/ui/button-group.tsx +87 -0
  508. package/inkos/packages/studio/src/components/ui/button.tsx +58 -0
  509. package/inkos/packages/studio/src/components/ui/collapsible.tsx +19 -0
  510. package/inkos/packages/studio/src/components/ui/command.tsx +194 -0
  511. package/inkos/packages/studio/src/components/ui/dialog.tsx +158 -0
  512. package/inkos/packages/studio/src/components/ui/dropdown-menu.tsx +266 -0
  513. package/inkos/packages/studio/src/components/ui/hover-card.tsx +51 -0
  514. package/inkos/packages/studio/src/components/ui/input-group.tsx +158 -0
  515. package/inkos/packages/studio/src/components/ui/input.tsx +20 -0
  516. package/inkos/packages/studio/src/components/ui/select.tsx +199 -0
  517. package/inkos/packages/studio/src/components/ui/separator.tsx +23 -0
  518. package/inkos/packages/studio/src/components/ui/spinner.tsx +10 -0
  519. package/inkos/packages/studio/src/components/ui/textarea.tsx +18 -0
  520. package/inkos/packages/studio/src/components/ui/tooltip.tsx +66 -0
  521. package/inkos/packages/studio/src/constants/service-groups.ts +29 -0
  522. package/inkos/packages/studio/src/hooks/use-api.test.ts +93 -0
  523. package/inkos/packages/studio/src/hooks/use-api.ts +189 -0
  524. package/inkos/packages/studio/src/hooks/use-book-activity.test.ts +129 -0
  525. package/inkos/packages/studio/src/hooks/use-book-activity.ts +180 -0
  526. package/inkos/packages/studio/src/hooks/use-colors.ts +27 -0
  527. package/inkos/packages/studio/src/hooks/use-hash-route.test.ts +101 -0
  528. package/inkos/packages/studio/src/hooks/use-hash-route.ts +89 -0
  529. package/inkos/packages/studio/src/hooks/use-i18n.ts +289 -0
  530. package/inkos/packages/studio/src/hooks/use-session-events.ts +64 -0
  531. package/inkos/packages/studio/src/hooks/use-sse.test.ts +52 -0
  532. package/inkos/packages/studio/src/hooks/use-sse.ts +92 -0
  533. package/inkos/packages/studio/src/hooks/use-theme.test.ts +31 -0
  534. package/inkos/packages/studio/src/hooks/use-theme.ts +75 -0
  535. package/inkos/packages/studio/src/index.css +323 -0
  536. package/inkos/packages/studio/src/lib/error-copy.test.ts +34 -0
  537. package/inkos/packages/studio/src/lib/error-copy.ts +37 -0
  538. package/inkos/packages/studio/src/lib/utils.ts +6 -0
  539. package/inkos/packages/studio/src/main.tsx +10 -0
  540. package/inkos/packages/studio/src/pages/Analytics.tsx +80 -0
  541. package/inkos/packages/studio/src/pages/BookCreate.tsx +895 -0
  542. package/inkos/packages/studio/src/pages/BookDetail.tsx +652 -0
  543. package/inkos/packages/studio/src/pages/ChapterReader.tsx +266 -0
  544. package/inkos/packages/studio/src/pages/ChatPage.tsx +521 -0
  545. package/inkos/packages/studio/src/pages/DaemonControl.tsx +116 -0
  546. package/inkos/packages/studio/src/pages/Dashboard.tsx +379 -0
  547. package/inkos/packages/studio/src/pages/DoctorView.tsx +82 -0
  548. package/inkos/packages/studio/src/pages/GenreManager.tsx +464 -0
  549. package/inkos/packages/studio/src/pages/ImportManager.tsx +216 -0
  550. package/inkos/packages/studio/src/pages/LanguageSelector.tsx +74 -0
  551. package/inkos/packages/studio/src/pages/LogViewer.tsx +82 -0
  552. package/inkos/packages/studio/src/pages/RadarView.tsx +157 -0
  553. package/inkos/packages/studio/src/pages/ServiceDetailPage.tsx +393 -0
  554. package/inkos/packages/studio/src/pages/ServiceListPage.tsx +463 -0
  555. package/inkos/packages/studio/src/pages/StyleManager.tsx +225 -0
  556. package/inkos/packages/studio/src/pages/TruthFiles.tsx +194 -0
  557. package/inkos/packages/studio/src/pages/chat-page-state.test.ts +206 -0
  558. package/inkos/packages/studio/src/pages/chat-page-state.ts +112 -0
  559. package/inkos/packages/studio/src/pages/page-state.test.ts +258 -0
  560. package/inkos/packages/studio/src/pages/service-detail-state.test.ts +294 -0
  561. package/inkos/packages/studio/src/pages/service-detail-state.ts +234 -0
  562. package/inkos/packages/studio/src/pages/style-manager-state.test.ts +22 -0
  563. package/inkos/packages/studio/src/pages/truth-files-state.test.ts +61 -0
  564. package/inkos/packages/studio/src/shared/contracts.ts +143 -0
  565. package/inkos/packages/studio/src/store/chat/__tests__/message-parts.test.ts +172 -0
  566. package/inkos/packages/studio/src/store/chat/index.ts +3 -0
  567. package/inkos/packages/studio/src/store/chat/initialState.ts +8 -0
  568. package/inkos/packages/studio/src/store/chat/message-policy.test.ts +16 -0
  569. package/inkos/packages/studio/src/store/chat/message-policy.ts +5 -0
  570. package/inkos/packages/studio/src/store/chat/parts-builder.ts +187 -0
  571. package/inkos/packages/studio/src/store/chat/selectors.ts +13 -0
  572. package/inkos/packages/studio/src/store/chat/slices/create/action.ts +10 -0
  573. package/inkos/packages/studio/src/store/chat/slices/create/initialState.ts +9 -0
  574. package/inkos/packages/studio/src/store/chat/slices/message/action.ts +417 -0
  575. package/inkos/packages/studio/src/store/chat/slices/message/initialState.ts +10 -0
  576. package/inkos/packages/studio/src/store/chat/slices/message/runtime.test.ts +21 -0
  577. package/inkos/packages/studio/src/store/chat/slices/message/runtime.ts +233 -0
  578. package/inkos/packages/studio/src/store/chat/slices/message/stream-events.ts +272 -0
  579. package/inkos/packages/studio/src/store/chat/store.ts +11 -0
  580. package/inkos/packages/studio/src/store/chat/types.ts +169 -0
  581. package/inkos/packages/studio/src/store/service/index.ts +2 -0
  582. package/inkos/packages/studio/src/store/service/store.ts +123 -0
  583. package/inkos/packages/studio/src/store/service/types.ts +50 -0
  584. package/inkos/packages/studio/tsconfig.json +24 -0
  585. package/inkos/packages/studio/tsconfig.server.json +11 -0
  586. package/inkos/packages/studio/vite.config.ts +34 -0
  587. package/inkos/packages/studio/vitest.config.ts +14 -0
  588. package/inkos/pnpm-lock.yaml +9569 -0
  589. package/inkos/pnpm-workspace.yaml +2 -0
  590. package/inkos/scripts/prepare-package-for-publish.mjs +135 -0
  591. package/inkos/scripts/restore-package-json.mjs +31 -0
  592. package/inkos/scripts/set-package-versions.mjs +74 -0
  593. package/inkos/scripts/verify-no-workspace-protocol.mjs +140 -0
  594. package/inkos/skills/SKILL.md +654 -0
  595. package/inkos/tsconfig.json +19 -0
  596. package/package.json +4 -3
  597. /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_buildManifest.js +0 -0
  598. /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_ssgManifest.js +0 -0
@@ -0,0 +1,737 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Agent } from "@mariozechner/pi-agent-core";
3
+ import type { AgentEvent, AgentMessage } from "@mariozechner/pi-agent-core";
4
+ import { streamSimple, getModel, getEnvApiKey } from "@mariozechner/pi-ai";
5
+ import type { Model, Api, AssistantMessage, Message, ToolResultMessage, UserMessage } from "@mariozechner/pi-ai";
6
+ import type { PipelineRunner } from "../pipeline/runner.js";
7
+ import { buildAgentSystemPrompt } from "./agent-system-prompt.js";
8
+ import {
9
+ createPatchChapterTextTool,
10
+ createRenameEntityTool,
11
+ createSubAgentTool,
12
+ createReadTool,
13
+ createGrepTool,
14
+ createLsTool,
15
+ createWriteTruthFileTool,
16
+ createShortFictionRunTool,
17
+ createGenerateCoverTool,
18
+ } from "./agent-tools.js";
19
+ import { createBookContextTransform } from "./context-transform.js";
20
+ import {
21
+ appendTranscriptEvents,
22
+ readTranscriptEvents,
23
+ } from "../interaction/session-transcript.js";
24
+ import {
25
+ TOOL_RESULT_BRIDGE_TEXT,
26
+ adaptRestoredAgentMessagesForModel,
27
+ restoreAgentMessagesFromTranscript,
28
+ } from "../interaction/session-transcript-restore.js";
29
+ import type { TranscriptEvent, TranscriptRole } from "../interaction/session-transcript-schema.js";
30
+ import { assertSafeBookId } from "../utils/book-id.js";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Types
34
+ // ---------------------------------------------------------------------------
35
+
36
+ export interface AgentSessionConfig {
37
+ /** Unique session identifier (typically the BookSession id). */
38
+ sessionId: string;
39
+ /** Book ID, or null if in "new book" mode. */
40
+ bookId: string | null;
41
+ /** Language for the system prompt. */
42
+ language: string;
43
+ /** PipelineRunner for sub-agent tool delegation. */
44
+ pipeline: PipelineRunner;
45
+ /** Project root directory (books/ lives under this). */
46
+ projectRoot: string;
47
+ /** pi-ai Model to use, or provider+modelId to resolve via getModel. */
48
+ model: Model<Api> | { provider: string; modelId: string };
49
+ /** Optional API key. When omitted, falls back to env-based key lookup. */
50
+ apiKey?: string;
51
+ /** Allow the read tool to read absolute paths outside projectRoot/books. Defaults to false; set INKOS_AGENT_ALLOW_SYSTEM_READ=1 to enable. */
52
+ allowSystemFileRead?: boolean;
53
+ /** Optional listener for streaming events (for SSE forwarding). */
54
+ onEvent?: (event: AgentEvent) => void;
55
+ }
56
+
57
+ export interface AgentSessionResult {
58
+ /** Extracted text from the final assistant message. */
59
+ responseText: string;
60
+ /** Full raw Agent conversation history. */
61
+ messages: AgentMessage[];
62
+ /** Upstream model error surfaced by pi-agent-core, if the final assistant turn failed. */
63
+ errorMessage?: string;
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Cache
68
+ // ---------------------------------------------------------------------------
69
+
70
+ interface CachedAgent {
71
+ agent: Agent;
72
+ sessionId: string;
73
+ projectRoot: string;
74
+ bookId: string | null;
75
+ language: string;
76
+ modelIdentity: string;
77
+ apiKey: string | undefined;
78
+ allowSystemFileRead: boolean;
79
+ lastCommittedSeq: number;
80
+ lastActive: number;
81
+ }
82
+
83
+ const agentCache = new Map<string, CachedAgent>();
84
+ const agentSessionQueues = new Map<string, Promise<void>>();
85
+
86
+ /** TTL for cached agents: 5 minutes. */
87
+ const CACHE_TTL_MS = 5 * 60 * 1000;
88
+
89
+ /** Cleanup interval handle (lazy-started). */
90
+ let cleanupTimer: ReturnType<typeof setInterval> | null = null;
91
+
92
+ function ensureCleanupTimer(): void {
93
+ if (cleanupTimer) return;
94
+ cleanupTimer = setInterval(() => {
95
+ const now = Date.now();
96
+ for (const [id, entry] of agentCache) {
97
+ if (now - entry.lastActive > CACHE_TTL_MS) {
98
+ agentCache.delete(id);
99
+ }
100
+ }
101
+ // Stop the timer when nothing left to watch.
102
+ if (agentCache.size === 0 && cleanupTimer) {
103
+ clearInterval(cleanupTimer);
104
+ cleanupTimer = null;
105
+ }
106
+ }, 60_000); // run every 60 s
107
+ // Allow the process to exit even if this timer is alive.
108
+ if (cleanupTimer && typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
109
+ cleanupTimer.unref();
110
+ }
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Helpers
115
+ // ---------------------------------------------------------------------------
116
+
117
+ function resolveModel(spec: AgentSessionConfig["model"]): Model<Api> {
118
+ if (!spec) {
119
+ throw new Error("Model is required but was undefined. Check LLM configuration.");
120
+ }
121
+ if (typeof spec === "object" && "id" in spec && "api" in spec) {
122
+ // Already a Model object.
123
+ return spec as Model<Api>;
124
+ }
125
+ const { provider, modelId } = spec as { provider: string; modelId: string };
126
+ if (!provider || !modelId) {
127
+ throw new Error(`Invalid model spec: provider=${provider}, modelId=${modelId}`);
128
+ }
129
+ return getModel(provider as any, modelId as any);
130
+ }
131
+
132
+ function envFlagEnabled(value: string | undefined, defaultValue: boolean): boolean {
133
+ if (value === undefined) return defaultValue;
134
+ if (value === "1" || value.toLowerCase() === "true") return true;
135
+ if (value === "0" || value.toLowerCase() === "false") return false;
136
+ return defaultValue;
137
+ }
138
+
139
+ function agentModelIdentity(model: Model<Api>): string {
140
+ return [
141
+ model.api,
142
+ model.provider,
143
+ model.baseUrl ?? "",
144
+ model.id,
145
+ ].join("::");
146
+ }
147
+
148
+ function sessionQueueKey(projectRoot: string, sessionId: string): string {
149
+ return `${projectRoot}\0${sessionId}`;
150
+ }
151
+
152
+ function agentCacheKey(projectRoot: string, sessionId: string): string {
153
+ return sessionQueueKey(projectRoot, sessionId);
154
+ }
155
+
156
+ async function runInAgentSessionQueue<T>(
157
+ projectRoot: string,
158
+ sessionId: string,
159
+ task: () => Promise<T>,
160
+ ): Promise<T> {
161
+ const key = sessionQueueKey(projectRoot, sessionId);
162
+ const previous = agentSessionQueues.get(key) ?? Promise.resolve();
163
+ let release!: () => void;
164
+ const gate = new Promise<void>((resolve) => {
165
+ release = resolve;
166
+ });
167
+ const queued = previous.catch(() => undefined).then(() => gate);
168
+ agentSessionQueues.set(key, queued);
169
+
170
+ await previous.catch(() => undefined);
171
+ try {
172
+ return await task();
173
+ } finally {
174
+ release();
175
+ if (agentSessionQueues.get(key) === queued) {
176
+ agentSessionQueues.delete(key);
177
+ }
178
+ }
179
+ }
180
+
181
+ async function latestCommittedSeq(projectRoot: string, sessionId: string): Promise<number> {
182
+ const events = await readTranscriptEvents(projectRoot, sessionId);
183
+ return events
184
+ .filter((event) => event.type === "request_committed")
185
+ .reduce((max, event) => Math.max(max, event.seq), 0);
186
+ }
187
+
188
+ function transcriptRoleForMessage(message: AgentMessage): TranscriptRole | null {
189
+ if (!message || typeof message !== "object" || !("role" in message)) return null;
190
+ const role = (message as { role?: unknown }).role;
191
+ return role === "user" || role === "assistant" || role === "toolResult" || role === "system"
192
+ ? role
193
+ : null;
194
+ }
195
+
196
+ function firstToolCallId(message: AgentMessage): string | undefined {
197
+ if (!message || typeof message !== "object" || !("content" in message)) return undefined;
198
+ const content = (message as { content?: unknown }).content;
199
+ if (!Array.isArray(content)) return undefined;
200
+ const block = content.find(
201
+ (item): item is { type: "toolCall"; id: string } =>
202
+ !!item &&
203
+ typeof item === "object" &&
204
+ (item as { type?: unknown }).type === "toolCall" &&
205
+ typeof (item as { id?: unknown }).id === "string",
206
+ );
207
+ return block?.id;
208
+ }
209
+
210
+ function toolCallIdForMessage(message: AgentMessage): string | undefined {
211
+ if (!message || typeof message !== "object") return undefined;
212
+ if ((message as { role?: unknown }).role === "toolResult") {
213
+ const toolCallId = (message as { toolCallId?: unknown }).toolCallId;
214
+ return typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : undefined;
215
+ }
216
+ return firstToolCallId(message);
217
+ }
218
+
219
+ function messageTimestamp(message: AgentMessage): number {
220
+ if (message && typeof message === "object") {
221
+ const timestamp = (message as { timestamp?: unknown }).timestamp;
222
+ if (typeof timestamp === "number" && Number.isFinite(timestamp) && timestamp >= 0) {
223
+ return Math.floor(timestamp);
224
+ }
225
+ }
226
+ return Date.now();
227
+ }
228
+
229
+ async function ensureSessionCreatedEvent(
230
+ projectRoot: string,
231
+ sessionId: string,
232
+ bookId: string | null,
233
+ ): Promise<void> {
234
+ await appendTranscriptEvents(projectRoot, sessionId, ({ events, nextSeq }) => {
235
+ if (events.some((event) => event.type === "session_created")) return [];
236
+
237
+ const now = Date.now();
238
+ return [{
239
+ type: "session_created",
240
+ version: 1,
241
+ sessionId,
242
+ seq: nextSeq,
243
+ timestamp: now,
244
+ bookId,
245
+ title: null,
246
+ createdAt: now,
247
+ updatedAt: now,
248
+ }];
249
+ });
250
+ }
251
+
252
+ async function appendAgentTranscriptEvent(
253
+ projectRoot: string,
254
+ sessionId: string,
255
+ buildEvent: (seq: number) => TranscriptEvent,
256
+ ): Promise<TranscriptEvent> {
257
+ const events = await appendTranscriptEvents(projectRoot, sessionId, ({ nextSeq }) => [
258
+ buildEvent(nextSeq),
259
+ ]);
260
+ const event = events[0];
261
+ if (!event) throw new Error(`Failed to append transcript event for session "${sessionId}"`);
262
+ return event;
263
+ }
264
+
265
+ /**
266
+ * Extract readable text from an AssistantMessage's content array.
267
+ * Filters out tool-call blocks; concatenates text blocks.
268
+ */
269
+ function extractTextFromAssistant(msg: AssistantMessage): string {
270
+ return msg.content
271
+ .filter((c): c is { type: "text"; text: string } => c.type === "text")
272
+ .map((c) => c.text)
273
+ .join("");
274
+ }
275
+
276
+ function lastAssistantMessage(messages: AgentMessage[]): AssistantMessage | undefined {
277
+ for (let i = messages.length - 1; i >= 0; i--) {
278
+ const msg = messages[i];
279
+ if (msg && typeof msg === "object" && "role" in msg && (msg as { role?: unknown }).role === "assistant") {
280
+ return msg as AssistantMessage;
281
+ }
282
+ }
283
+ return undefined;
284
+ }
285
+
286
+ function assistantErrorMessage(message: AssistantMessage | undefined): string | undefined {
287
+ return message &&
288
+ (message.stopReason === "error" || message.stopReason === "aborted") &&
289
+ message.errorMessage
290
+ ? message.errorMessage
291
+ : undefined;
292
+ }
293
+
294
+ function convertAgentMessagesForModel(messages: AgentMessage[], model: Model<Api>): Message[] {
295
+ const llmMessages = messages.filter((message): message is Message => {
296
+ if (!message || typeof message !== "object" || !("role" in message)) return false;
297
+ return message.role === "user" || message.role === "assistant" || message.role === "toolResult";
298
+ });
299
+
300
+ const candidate = model as { api?: unknown; baseUrl?: unknown };
301
+ const isGoogleOpenAICompatible = (
302
+ candidate.api === "openai-completions" &&
303
+ typeof candidate.baseUrl === "string" &&
304
+ candidate.baseUrl.includes("generativelanguage.googleapis.com")
305
+ );
306
+ if (!isGoogleOpenAICompatible) return llmMessages;
307
+
308
+ const converted: Message[] = [];
309
+ const pushToolResultsAsUser = (toolResults: ToolResultMessage[]) => {
310
+ const lines = toolResults.flatMap((result) => {
311
+ const content = result.content
312
+ .map((block) => block.type === "text" ? block.text : "[image]")
313
+ .filter(Boolean)
314
+ .join("\n")
315
+ .trim() || "(empty tool result)";
316
+ return [`- ${result.toolName} (${result.toolCallId}):`, content];
317
+ });
318
+ converted.push({
319
+ role: "user",
320
+ content: [
321
+ "[Tool results]",
322
+ ...lines,
323
+ "Use these tool results to answer the active user request. If a tool failed, explain the failure and choose the next useful action.",
324
+ ].join("\n"),
325
+ timestamp: toolResults.reduce(
326
+ (max, result) => Math.max(max, messageTimestamp(result as AgentMessage)),
327
+ 0,
328
+ ) || Date.now(),
329
+ });
330
+ };
331
+
332
+ for (let i = 0; i < llmMessages.length; i++) {
333
+ const message = llmMessages[i];
334
+
335
+ if (message.role === "assistant") {
336
+ const textContent = message.content.filter(
337
+ (block): block is { type: "text"; text: string } =>
338
+ block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0,
339
+ );
340
+ if (
341
+ textContent.length === 1 &&
342
+ message.content.length === 1 &&
343
+ textContent[0].text.trim() === TOOL_RESULT_BRIDGE_TEXT
344
+ ) {
345
+ continue;
346
+ }
347
+
348
+ const toolCallIds = new Set<string>();
349
+ for (const block of message.content) {
350
+ if (block.type === "toolCall" && typeof block.id === "string" && block.id.length > 0) {
351
+ toolCallIds.add(block.id);
352
+ }
353
+ }
354
+ if (toolCallIds.size === 0) {
355
+ converted.push(message);
356
+ continue;
357
+ }
358
+
359
+ if (textContent.length > 0) {
360
+ converted.push({ ...message, content: textContent });
361
+ }
362
+
363
+ const toolResults: ToolResultMessage[] = [];
364
+ let nextIndex = i + 1;
365
+ while (nextIndex < llmMessages.length) {
366
+ const next = llmMessages[nextIndex];
367
+ if (next.role !== "toolResult" || !toolCallIds.has(next.toolCallId)) break;
368
+ toolResults.push(next);
369
+ nextIndex += 1;
370
+ }
371
+
372
+ if (toolResults.length > 0) {
373
+ pushToolResultsAsUser(toolResults);
374
+ i = nextIndex - 1;
375
+ }
376
+ continue;
377
+ }
378
+
379
+ if (message.role === "toolResult") {
380
+ pushToolResultsAsUser([message]);
381
+ continue;
382
+ }
383
+
384
+ converted.push(message);
385
+ }
386
+
387
+ return converted;
388
+ }
389
+
390
+ /**
391
+ * Extract thinking/reasoning text from an AssistantMessage's content array.
392
+ */
393
+ function extractThinkingFromAssistant(msg: AssistantMessage): string {
394
+ return msg.content
395
+ .filter((c: any) => c.type === "thinking")
396
+ .map((c: any) => c.thinking ?? "")
397
+ .join("");
398
+ }
399
+
400
+ /**
401
+ * Convert plain `{ role, content }` messages (from BookSession disk storage)
402
+ * back into pi-agent AgentMessage format so they can be loaded into an Agent.
403
+ */
404
+ function plainToAgentMessages(
405
+ plain: Array<{ role: string; content: string }>,
406
+ ): AgentMessage[] {
407
+ return plain.map((m) => {
408
+ const ts = Date.now();
409
+ if (m.role === "user") {
410
+ return { role: "user", content: m.content, timestamp: ts } satisfies UserMessage;
411
+ }
412
+ // For stored assistant messages we only have the text.
413
+ // Re-wrap as a minimal AssistantMessage with a single TextContent.
414
+ return {
415
+ role: "assistant",
416
+ content: [{ type: "text", text: m.content }],
417
+ api: "anthropic-messages",
418
+ provider: "anthropic",
419
+ model: "unknown",
420
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
421
+ stopReason: "stop",
422
+ timestamp: ts,
423
+ } satisfies AssistantMessage;
424
+ });
425
+ }
426
+
427
+ /**
428
+ * Flatten the Agent's in-memory messages to plain `{ role, content }` pairs
429
+ * suitable for BookSession persistence.
430
+ */
431
+ function agentMessagesToPlain(
432
+ messages: AgentMessage[],
433
+ ): Array<{ role: string; content: string; thinking?: string }> {
434
+ const out: Array<{ role: string; content: string; thinking?: string }> = [];
435
+ for (const msg of messages) {
436
+ if (!msg || typeof msg !== "object" || !("role" in msg)) continue;
437
+
438
+ const m = msg as { role: string; [k: string]: any };
439
+
440
+ if (m.role === "user") {
441
+ const content = typeof m.content === "string"
442
+ ? m.content
443
+ : Array.isArray(m.content)
444
+ ? m.content
445
+ .filter((c: any) => c.type === "text")
446
+ .map((c: any) => c.text)
447
+ .join("")
448
+ : "";
449
+ if (content) out.push({ role: "user", content });
450
+ } else if (m.role === "assistant") {
451
+ const text = extractTextFromAssistant(m as AssistantMessage);
452
+ const thinking = extractThinkingFromAssistant(m as AssistantMessage);
453
+ if (text || thinking) {
454
+ const entry: { role: string; content: string; thinking?: string } = { role: "assistant", content: text };
455
+ if (thinking) entry.thinking = thinking;
456
+ out.push(entry);
457
+ }
458
+ }
459
+ // ToolResult messages are internal; skip them for persistence.
460
+ }
461
+ return out;
462
+ }
463
+
464
+ // ---------------------------------------------------------------------------
465
+ // Main entry point
466
+ // ---------------------------------------------------------------------------
467
+
468
+ function createAgentToolsForMode(params: {
469
+ readonly pipeline: PipelineRunner;
470
+ readonly bookId: string | null;
471
+ readonly projectRoot: string;
472
+ readonly allowSystemFileRead: boolean;
473
+ }) {
474
+ const subAgentTool = createSubAgentTool(params.pipeline, params.bookId, params.projectRoot);
475
+ const shortFictionTool = createShortFictionRunTool(params.pipeline, params.projectRoot);
476
+ const generateCoverTool = createGenerateCoverTool(params.projectRoot);
477
+ if (!params.bookId) {
478
+ return [subAgentTool, shortFictionTool, generateCoverTool];
479
+ }
480
+
481
+ return [
482
+ subAgentTool,
483
+ shortFictionTool,
484
+ generateCoverTool,
485
+ createReadTool(params.projectRoot, { allowSystemPaths: params.allowSystemFileRead }),
486
+ createWriteTruthFileTool(params.pipeline, params.projectRoot, params.bookId),
487
+ createRenameEntityTool(params.pipeline, params.projectRoot, params.bookId),
488
+ createPatchChapterTextTool(params.pipeline, params.projectRoot, params.bookId),
489
+ createGrepTool(params.projectRoot),
490
+ createLsTool(params.projectRoot),
491
+ ];
492
+ }
493
+
494
+ /**
495
+ * Run a single conversation turn within a cached Agent session.
496
+ *
497
+ * If the session already exists in the cache, reuses the Agent (with its full
498
+ * in-memory message history including tool calls). Otherwise creates a new
499
+ * Agent, optionally restoring messages from `initialMessages`.
500
+ */
501
+ export async function runAgentSession(
502
+ config: AgentSessionConfig,
503
+ userMessage: string,
504
+ initialMessages?: Array<{ role: string; content: string }>,
505
+ ): Promise<AgentSessionResult> {
506
+ return runInAgentSessionQueue(config.projectRoot, config.sessionId, () =>
507
+ runAgentSessionUnlocked(config, userMessage, initialMessages)
508
+ );
509
+ }
510
+
511
+ async function runAgentSessionUnlocked(
512
+ config: AgentSessionConfig,
513
+ userMessage: string,
514
+ initialMessages?: Array<{ role: string; content: string }>,
515
+ ): Promise<AgentSessionResult> {
516
+ const { sessionId, language, pipeline, projectRoot, onEvent } = config;
517
+ // Normalize at the entry point so downstream comparisons, closures, and
518
+ // fs paths never see `undefined`. The type is already `string | null`, but
519
+ // some callers may bypass the type system (e.g. `activeBookId ?? null` gets
520
+ // skipped) and we don't want that to (a) throw in path.join or (b) trigger
521
+ // a spurious cache eviction because `null !== undefined`.
522
+ const bookId: string | null = config.bookId ? assertSafeBookId(config.bookId) : null;
523
+ const model = resolveModel(config.model);
524
+ const requestedModelIdentity = agentModelIdentity(model);
525
+ const allowSystemFileRead = config.allowSystemFileRead ?? envFlagEnabled(process.env.INKOS_AGENT_ALLOW_SYSTEM_READ, false);
526
+ const cacheKey = agentCacheKey(projectRoot, sessionId);
527
+
528
+ // ----- Resolve or create Agent -----
529
+ let cached = agentCache.get(cacheKey);
530
+ let currentCommittedSeq: number | undefined;
531
+
532
+ if (cached) {
533
+ currentCommittedSeq = await latestCommittedSeq(projectRoot, sessionId);
534
+ // Evict and rebuild if model protocol identity OR bookId changed. Both are
535
+ // captured into the Agent at construction time (model via initialState,
536
+ // bookId via closures in systemPrompt / tools / transformContext), so a
537
+ // mismatch means the cached Agent would keep using stale context.
538
+ const modelChanged = cached.modelIdentity !== requestedModelIdentity;
539
+ const projectRootChanged = cached.projectRoot !== projectRoot;
540
+ const bookChanged = cached.bookId !== bookId;
541
+ const languageChanged = cached.language !== language;
542
+ const apiKeyChanged = cached.apiKey !== config.apiKey;
543
+ const readPermissionChanged = cached.allowSystemFileRead !== allowSystemFileRead;
544
+ const transcriptChanged = cached.lastCommittedSeq !== currentCommittedSeq;
545
+
546
+ if (
547
+ modelChanged ||
548
+ projectRootChanged ||
549
+ bookChanged ||
550
+ languageChanged ||
551
+ apiKeyChanged ||
552
+ readPermissionChanged ||
553
+ transcriptChanged
554
+ ) {
555
+ agentCache.delete(cacheKey);
556
+ cached = undefined;
557
+ }
558
+ }
559
+
560
+ if (!cached) {
561
+ const restoredMessages = adaptRestoredAgentMessagesForModel(
562
+ await restoreAgentMessagesFromTranscript(projectRoot, sessionId),
563
+ model,
564
+ );
565
+ const initialAgentMessages = restoredMessages.length > 0
566
+ ? restoredMessages
567
+ : initialMessages && initialMessages.length > 0
568
+ ? plainToAgentMessages(initialMessages)
569
+ : [];
570
+ const agent = new Agent({
571
+ initialState: {
572
+ model,
573
+ systemPrompt: buildAgentSystemPrompt(bookId, language),
574
+ tools: createAgentToolsForMode({ pipeline, bookId, projectRoot, allowSystemFileRead }),
575
+ messages: initialAgentMessages,
576
+ },
577
+ transformContext: createBookContextTransform(bookId, projectRoot),
578
+ convertToLlm: (messages) => convertAgentMessagesForModel(messages, model),
579
+ streamFn: streamSimple,
580
+ getApiKey: (provider: string) => {
581
+ if (config.apiKey) return config.apiKey;
582
+ return getEnvApiKey(provider);
583
+ },
584
+ });
585
+
586
+ cached = {
587
+ agent,
588
+ sessionId,
589
+ projectRoot,
590
+ bookId,
591
+ language,
592
+ modelIdentity: requestedModelIdentity,
593
+ apiKey: config.apiKey,
594
+ allowSystemFileRead,
595
+ lastCommittedSeq: currentCommittedSeq ?? await latestCommittedSeq(projectRoot, sessionId),
596
+ lastActive: Date.now(),
597
+ };
598
+ agentCache.set(cacheKey, cached);
599
+ ensureCleanupTimer();
600
+ }
601
+
602
+ cached.lastActive = Date.now();
603
+ const { agent } = cached;
604
+
605
+ // ----- Prepare transcript persistence -----
606
+ const requestId = randomUUID();
607
+ await ensureSessionCreatedEvent(projectRoot, sessionId, bookId);
608
+ await appendAgentTranscriptEvent(projectRoot, sessionId, (seq) => ({
609
+ type: "request_started",
610
+ version: 1,
611
+ sessionId,
612
+ requestId,
613
+ seq,
614
+ timestamp: Date.now(),
615
+ input: userMessage,
616
+ }));
617
+
618
+ let parentUuid: string | null = null;
619
+ let piTurnIndex = 0;
620
+ let lastAssistantUuid: string | null = null;
621
+
622
+ const persistAgentEvent = async (event: AgentEvent): Promise<void> => {
623
+ if (event.type === "turn_start") {
624
+ piTurnIndex += 1;
625
+ return;
626
+ }
627
+ if (event.type !== "message_end") return;
628
+
629
+ const role = transcriptRoleForMessage(event.message);
630
+ if (!role) return;
631
+
632
+ const uuid = randomUUID();
633
+ const isToolResult = role === "toolResult";
634
+ const toolCallId = toolCallIdForMessage(event.message);
635
+ await appendAgentTranscriptEvent(projectRoot, sessionId, (seq) => ({
636
+ type: "message",
637
+ version: 1,
638
+ sessionId,
639
+ requestId,
640
+ uuid,
641
+ parentUuid: isToolResult && lastAssistantUuid ? lastAssistantUuid : parentUuid,
642
+ seq,
643
+ role,
644
+ timestamp: messageTimestamp(event.message),
645
+ piTurnIndex,
646
+ ...(toolCallId ? { toolCallId } : {}),
647
+ ...(isToolResult && lastAssistantUuid
648
+ ? { sourceToolAssistantUuid: lastAssistantUuid }
649
+ : {}),
650
+ message: event.message,
651
+ }));
652
+
653
+ if (role === "assistant") lastAssistantUuid = uuid;
654
+ parentUuid = uuid;
655
+ };
656
+
657
+ // ----- Subscribe to events (transcript persistence + SSE forwarding) -----
658
+ const unsubscribe = agent.subscribe(async (event: AgentEvent) => {
659
+ await persistAgentEvent(event);
660
+ onEvent?.(event);
661
+ });
662
+
663
+ // ----- Execute the turn -----
664
+ let finalAssistant: AssistantMessage | undefined;
665
+ let errorMessage: string | undefined;
666
+
667
+ try {
668
+ await agent.prompt(userMessage);
669
+
670
+ finalAssistant = lastAssistantMessage(agent.state.messages);
671
+ errorMessage = assistantErrorMessage(finalAssistant);
672
+ if (errorMessage) {
673
+ const failedError = errorMessage;
674
+ await appendAgentTranscriptEvent(projectRoot, sessionId, (seq) => ({
675
+ type: "request_failed",
676
+ version: 1,
677
+ sessionId,
678
+ requestId,
679
+ seq,
680
+ timestamp: Date.now(),
681
+ error: failedError,
682
+ }));
683
+ agentCache.delete(cacheKey);
684
+ } else {
685
+ const committed = await appendAgentTranscriptEvent(projectRoot, sessionId, (seq) => ({
686
+ type: "request_committed",
687
+ version: 1,
688
+ sessionId,
689
+ requestId,
690
+ seq,
691
+ timestamp: Date.now(),
692
+ }));
693
+ cached.lastCommittedSeq = committed.seq;
694
+ }
695
+ } catch (error) {
696
+ await appendAgentTranscriptEvent(projectRoot, sessionId, (seq) => ({
697
+ type: "request_failed",
698
+ version: 1,
699
+ sessionId,
700
+ requestId,
701
+ seq,
702
+ timestamp: Date.now(),
703
+ error: error instanceof Error ? error.message : String(error),
704
+ }));
705
+ agentCache.delete(cacheKey);
706
+ throw error;
707
+ } finally {
708
+ unsubscribe();
709
+ }
710
+
711
+ // ----- Extract result -----
712
+ const allMessages = agent.state.messages;
713
+ finalAssistant ??= lastAssistantMessage(allMessages);
714
+ const responseText = finalAssistant ? extractTextFromAssistant(finalAssistant) : "";
715
+ errorMessage ??= assistantErrorMessage(finalAssistant);
716
+
717
+ return {
718
+ responseText,
719
+ messages: allMessages.slice(),
720
+ ...(errorMessage ? { errorMessage } : {}),
721
+ };
722
+ }
723
+
724
+ // ---------------------------------------------------------------------------
725
+ // Cache management
726
+ // ---------------------------------------------------------------------------
727
+
728
+ /** Manually evict a cached Agent session. */
729
+ export function evictAgentCache(sessionId: string): boolean {
730
+ let deleted = agentCache.delete(sessionId);
731
+ for (const [key, entry] of agentCache) {
732
+ if (entry.sessionId !== sessionId) continue;
733
+ agentCache.delete(key);
734
+ deleted = true;
735
+ }
736
+ return deleted;
737
+ }