saeeol 1.3.0 → 1.4.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 (545) hide show
  1. package/AGENTS.md +72 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/assets/saeeol.ico +0 -0
  5. package/bin/saeeol.cjs +3 -1
  6. package/bunfig.toml +7 -0
  7. package/database.db +0 -0
  8. package/drizzle.config.ts +10 -0
  9. package/git +0 -0
  10. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  11. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  12. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  13. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  14. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  15. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  16. package/migration/20260225215848_workspace/migration.sql +7 -0
  17. package/migration/20260225215848_workspace/snapshot.json +959 -0
  18. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  19. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  20. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  21. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  22. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  23. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  24. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  25. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  26. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  27. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  28. package/migration/20260323234822_events/migration.sql +13 -0
  29. package/migration/20260323234822_events/snapshot.json +1271 -0
  30. package/migration/20260410174513_workspace-name/migration.sql +16 -0
  31. package/migration/20260410174513_workspace-name/snapshot.json +1271 -0
  32. package/migration/20260413175956_chief_energizer/migration.sql +13 -0
  33. package/migration/20260413175956_chief_energizer/snapshot.json +1399 -0
  34. package/migration/20260423070820_add_icon_url_override/migration.sql +2 -0
  35. package/migration/20260423070820_add_icon_url_override/snapshot.json +1409 -0
  36. package/migration/20260428004200_add_session_path/migration.sql +1 -0
  37. package/migration/20260428004200_add_session_path/snapshot.json +1419 -0
  38. package/npm/bin/saeeol +42 -0
  39. package/npm/package.json +39 -0
  40. package/npm/postinstall.js +162 -0
  41. package/package.json +201 -207
  42. package/parsers-config.ts +289 -0
  43. package/script/build.ts +393 -0
  44. package/script/check-migrations.ts +16 -0
  45. package/script/fix-node-pty.ts +34 -0
  46. package/script/generate.ts +23 -0
  47. package/script/postinstall.mjs +189 -0
  48. package/script/publish.ts +200 -0
  49. package/script/run-workspace-server +106 -0
  50. package/script/schema.ts +63 -0
  51. package/script/test-runner.ts +420 -0
  52. package/script/time.ts +6 -0
  53. package/script/trace-imports.ts +153 -0
  54. package/script/upgrade-opentui.ts +64 -0
  55. package/scripts/diff-sdk-types.sh +52 -0
  56. package/specs/effect/facades.md +221 -0
  57. package/specs/effect/http-api.md +401 -0
  58. package/specs/effect/instance-context.md +309 -0
  59. package/specs/effect/loose-ends.md +34 -0
  60. package/specs/effect/migration.md +299 -0
  61. package/specs/effect/routes.md +64 -0
  62. package/specs/effect/schema.md +399 -0
  63. package/specs/effect/server-package.md +668 -0
  64. package/specs/effect/tools.md +90 -0
  65. package/specs/tui-plugins.md +433 -0
  66. package/specs/v2/api.ts +67 -0
  67. package/specs/v2/keymappings.md +10 -0
  68. package/specs/v2/message-shape.md +136 -0
  69. package/src/acp/agent-message.ts +1 -1
  70. package/src/acp/agent-utils.ts +1 -1
  71. package/src/boxes/ansi.ts +17 -0
  72. package/src/boxes/atomic-write.ts +35 -0
  73. package/src/boxes/b64.ts +58 -0
  74. package/src/boxes/bash-security.ts +129 -0
  75. package/src/boxes/bom.ts +18 -0
  76. package/src/boxes/cancel.ts +16 -0
  77. package/src/boxes/chop.ts +12 -0
  78. package/src/boxes/clamp.ts +3 -0
  79. package/src/boxes/compact.ts +9 -0
  80. package/src/boxes/cost-tracker.ts +116 -0
  81. package/src/boxes/dataurl.ts +29 -0
  82. package/src/boxes/delay.ts +27 -0
  83. package/src/boxes/diff-apply.ts +53 -0
  84. package/src/boxes/disposable.ts +13 -0
  85. package/src/boxes/err.ts +34 -0
  86. package/src/boxes/human.ts +47 -0
  87. package/src/boxes/iife.ts +9 -0
  88. package/src/boxes/latch.ts +8 -0
  89. package/src/boxes/memory.ts +198 -0
  90. package/src/boxes/net.ts +16 -0
  91. package/src/boxes/plural.ts +4 -0
  92. package/src/boxes/puny.ts +21 -0
  93. package/src/boxes/retry.ts +49 -0
  94. package/src/boxes/rwlock.ts +41 -0
  95. package/src/boxes/schedule.ts +71 -0
  96. package/src/boxes/scope.ts +21 -0
  97. package/src/boxes/tokens.ts +9 -0
  98. package/src/boxes/ttl-cache.ts +63 -0
  99. package/src/boxes/typed-event.ts +51 -0
  100. package/src/boxes/uid.ts +50 -0
  101. package/src/boxes/wave6.test.ts +296 -0
  102. package/src/boxes/wildcard.ts +58 -0
  103. package/src/bus/global.ts +1 -1
  104. package/src/cli/cmd/github-run-api.ts +2 -2
  105. package/src/cli/cmd/run-events.ts +2 -2
  106. package/src/cli/cmd/tui/component/logo.tsx +1 -1
  107. package/src/cli/cmd/tui/component/prompt/use-prompt-memos.ts +2 -2
  108. package/src/cli/cmd/tui/context/app/editor-zed.ts +1 -1
  109. package/src/cli/cmd/tui/context/app/editor.ts +1 -1
  110. package/src/cli/cmd/tui/context/app/theme.tsx +1 -1
  111. package/src/cli/cmd/tui/preflight.ts +138 -0
  112. package/src/cli/cmd/tui/thread.ts +20 -0
  113. package/src/cli/cmd/tui/util/revert-diff.ts +1 -1
  114. package/src/overlay/cli/cmd/roll-call-call.ts +1 -1
  115. package/src/overlay/cost-tracker/format.ts +1 -1
  116. package/src/overlay/cost-tracker/index.ts +4 -4
  117. package/src/overlay/cost-tracker/state.ts +2 -2
  118. package/src/overlay/cost-tracker/types.ts +2 -2
  119. package/src/overlay/memory/age.ts +1 -1
  120. package/src/overlay/memory/index.ts +4 -4
  121. package/src/overlay/memory/paths.ts +2 -2
  122. package/src/overlay/memory/scan.ts +1 -1
  123. package/src/overlay/memory/types.ts +2 -2
  124. package/src/overlay/tool/bash-security.ts +3 -3
  125. package/src/overlay/util/url.ts +1 -1
  126. package/src/plugin/codex-auth.ts +1 -1
  127. package/src/provider/model-cache.ts +2 -2
  128. package/src/provider/provider-resolve.ts +3 -3
  129. package/src/provider/transform-message.ts +1 -1
  130. package/src/server/routes/game.ts +284 -0
  131. package/src/server/server.ts +2 -0
  132. package/src/session/core/compaction/compaction-helpers.ts +1 -1
  133. package/src/session/core/compaction/compaction.ts +1 -1
  134. package/src/session/core/session.ts +2 -0
  135. package/src/sessions/ingest-queue.ts +2 -2
  136. package/src/sessions/remote-ws.ts +1 -1
  137. package/src/tool/workflow/question.ts +1 -1
  138. package/src/util/abort.ts +1 -1
  139. package/src/util/bom.ts +2 -2
  140. package/src/util/color.ts +1 -1
  141. package/src/util/data-url.ts +1 -1
  142. package/src/util/defer.ts +1 -1
  143. package/src/util/error.ts +2 -2
  144. package/src/util/filesystem.ts +2 -2
  145. package/src/util/format.ts +1 -1
  146. package/src/util/iife.ts +1 -1
  147. package/src/util/local-context.ts +1 -1
  148. package/src/util/locale.ts +2 -2
  149. package/src/util/lock.ts +1 -1
  150. package/src/util/network.ts +1 -1
  151. package/src/util/signal.ts +1 -1
  152. package/src/util/token.ts +1 -1
  153. package/src/util/wildcard.ts +1 -1
  154. package/sst-env.d.ts +10 -0
  155. package/test/AGENTS.md +133 -0
  156. package/test/account/repo.test.ts +352 -0
  157. package/test/account/service.test.ts +456 -0
  158. package/test/acp/agent-interface.test.ts +51 -0
  159. package/test/acp/event-subscription.test.ts +725 -0
  160. package/test/agent/agent.test.ts +890 -0
  161. package/test/auth/auth.test.ts +86 -0
  162. package/test/bun/registry.test.ts +75 -0
  163. package/test/bus/bus-effect.test.ts +161 -0
  164. package/test/bus/bus-integration.test.ts +87 -0
  165. package/test/bus/bus.test.ts +219 -0
  166. package/test/cli/account.test.ts +26 -0
  167. package/test/cli/auto-mode.test.ts +75 -0
  168. package/test/cli/bin-saeeol.test.ts +8 -0
  169. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  170. package/test/cli/cmd/tui/prompt-traits.test.ts +38 -0
  171. package/test/cli/cmd/tui/sync.test.tsx +159 -0
  172. package/test/cli/error.test.ts +18 -0
  173. package/test/cli/github-action.test.ts +198 -0
  174. package/test/cli/github-remote.test.ts +85 -0
  175. package/test/cli/import.test.ts +97 -0
  176. package/test/cli/install-artifact.test.ts +72 -0
  177. package/test/cli/plugin-auth-picker.test.ts +120 -0
  178. package/test/cli/pr.test.ts +59 -0
  179. package/test/cli/tui/editor-context-zed.test.ts +356 -0
  180. package/test/cli/tui/editor-context.test.tsx +228 -0
  181. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  182. package/test/cli/tui/markdown.test.ts +161 -0
  183. package/test/cli/tui/plugin-add.test.ts +111 -0
  184. package/test/cli/tui/plugin-install.test.ts +87 -0
  185. package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
  186. package/test/cli/tui/plugin-loader-entrypoint.test.ts +484 -0
  187. package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
  188. package/test/cli/tui/plugin-loader.test.ts +816 -0
  189. package/test/cli/tui/plugin-toggle.test.ts +157 -0
  190. package/test/cli/tui/revert-diff.test.ts +35 -0
  191. package/test/cli/tui/slot-replace.test.tsx +47 -0
  192. package/test/cli/tui/theme-store.test.ts +54 -0
  193. package/test/cli/tui/thread.test.ts +28 -0
  194. package/test/cli/tui/transcript.test.ts +426 -0
  195. package/test/cli/tui/usage.test.ts +60 -0
  196. package/test/cli/tui/use-event.test.tsx +175 -0
  197. package/test/config/agent-color.test.ts +67 -0
  198. package/test/config/config.test.ts +2544 -0
  199. package/test/config/fixtures/empty-frontmatter.md +4 -0
  200. package/test/config/fixtures/frontmatter.md +28 -0
  201. package/test/config/fixtures/markdown-header.md +11 -0
  202. package/test/config/fixtures/no-frontmatter.md +1 -0
  203. package/test/config/fixtures/weird-model-id.md +13 -0
  204. package/test/config/lsp.test.ts +87 -0
  205. package/test/config/markdown.test.ts +228 -0
  206. package/test/config/plugin.test.ts +0 -0
  207. package/test/config/tui.test.ts +624 -0
  208. package/test/control-plane/adapters.test.ts +71 -0
  209. package/test/control-plane/workspace.test.ts +1526 -0
  210. package/test/effect/app-runtime-logger.test.ts +98 -0
  211. package/test/effect/config-service.test.ts +65 -0
  212. package/test/effect/instance-state.test.ts +394 -0
  213. package/test/effect/run-service.test.ts +89 -0
  214. package/test/effect/runner.test.ts +523 -0
  215. package/test/fake/provider.ts +82 -0
  216. package/test/file/fsmonitor.test.ts +68 -0
  217. package/test/file/ignore.test.ts +10 -0
  218. package/test/file/index.test.ts +954 -0
  219. package/test/file/path-traversal.test.ts +205 -0
  220. package/test/file/ripgrep.test.ts +226 -0
  221. package/test/file/watcher.test.ts +249 -0
  222. package/test/filesystem/filesystem.test.ts +319 -0
  223. package/test/fixture/db.ts +11 -0
  224. package/test/fixture/fixture.test.ts +26 -0
  225. package/test/fixture/fixture.ts +175 -0
  226. package/test/fixture/flock-worker.ts +72 -0
  227. package/test/fixture/log-init-worker.ts +62 -0
  228. package/test/fixture/lsp/fake-lsp-server.js +249 -0
  229. package/test/fixture/plug-worker.ts +93 -0
  230. package/test/fixture/plugin-meta-worker.ts +19 -0
  231. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  232. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  233. package/test/fixture/skills/index.json +6 -0
  234. package/test/fixture/tui-plugin.ts +323 -0
  235. package/test/fixture/tui-runtime.ts +31 -0
  236. package/test/format/format.test.ts +272 -0
  237. package/test/git/git.test.ts +128 -0
  238. package/test/ide/ide.test.ts +82 -0
  239. package/test/installation/installation.test.ts +168 -0
  240. package/test/keybind.test.ts +421 -0
  241. package/test/lib/effect.ts +53 -0
  242. package/test/lib/filesystem.ts +10 -0
  243. package/test/lib/llm-server.ts +778 -0
  244. package/test/lib/websocket.ts +46 -0
  245. package/test/lsp/client.test.ts +482 -0
  246. package/test/lsp/index.test.ts +160 -0
  247. package/test/lsp/launch.test.ts +22 -0
  248. package/test/lsp/lifecycle.test.ts +184 -0
  249. package/test/ltm/ltm.test.ts +230 -0
  250. package/test/mcp/headers.test.ts +178 -0
  251. package/test/mcp/lifecycle.test.ts +787 -0
  252. package/test/mcp/oauth-auto-connect.test.ts +311 -0
  253. package/test/mcp/oauth-browser.test.ts +276 -0
  254. package/test/mcp/oauth-callback.test.ts +34 -0
  255. package/test/memory/abort-leak-webfetch.ts +49 -0
  256. package/test/memory/abort-leak.test.ts +128 -0
  257. package/test/patch/patch.test.ts +348 -0
  258. package/test/permission/arity.test.ts +33 -0
  259. package/test/permission/next.test.ts +1227 -0
  260. package/test/permission/next.toConfig.test.ts +110 -0
  261. package/test/permission-task.test.ts +326 -0
  262. package/test/plugin/auth-override.test.ts +79 -0
  263. package/test/plugin/cloudflare.test.ts +68 -0
  264. package/test/plugin/codex.test.ts +123 -0
  265. package/test/plugin/github-copilot-models.test.ts +261 -0
  266. package/test/plugin/install-concurrency.test.ts +140 -0
  267. package/test/plugin/install.test.ts +570 -0
  268. package/test/plugin/loader-shared.test.ts +1169 -0
  269. package/test/plugin/meta.test.ts +137 -0
  270. package/test/plugin/plugin-contract.test.ts +291 -0
  271. package/test/plugin/shared.test.ts +88 -0
  272. package/test/plugin/trigger.test.ts +102 -0
  273. package/test/plugin/workspace-adapter.test.ts +109 -0
  274. package/test/preload.ts +77 -0
  275. package/test/project/instance.test.ts +276 -0
  276. package/test/project/migrate-global.test.ts +152 -0
  277. package/test/project/project.test.ts +600 -0
  278. package/test/project/vcs.test.ts +286 -0
  279. package/test/project/worktree-remove.test.ts +126 -0
  280. package/test/project/worktree.test.ts +223 -0
  281. package/test/provider/amazon-bedrock.test.ts +462 -0
  282. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  283. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  284. package/test/provider/gitlab-duo.test.ts +413 -0
  285. package/test/provider/local.test.ts +208 -0
  286. package/test/provider/models.test.ts +261 -0
  287. package/test/provider/provider-category.test.ts +190 -0
  288. package/test/provider/provider.test.ts +2758 -0
  289. package/test/provider/transform.test.ts +3681 -0
  290. package/test/pty/pty-output-isolation.test.ts +147 -0
  291. package/test/pty/pty-session.test.ts +102 -0
  292. package/test/pty/pty-shell.test.ts +104 -0
  293. package/test/question/question.test.ts +490 -0
  294. package/test/saeeol/agent-global-config-dirs.test.ts +24 -0
  295. package/test/saeeol/agent-manager-tool.test.ts +71 -0
  296. package/test/saeeol/agent-permission-overrides.test.ts +75 -0
  297. package/test/saeeol/agent-skill-permissions.test.ts +37 -0
  298. package/test/saeeol/ask-agent-permissions.test.ts +303 -0
  299. package/test/saeeol/bash-hierarchy.test.ts +64 -0
  300. package/test/saeeol/bash-permission-metadata.test.ts +66 -0
  301. package/test/saeeol/bash-security-extended.test.ts +243 -0
  302. package/test/saeeol/bedrock-claude-empty-content.test.ts +138 -0
  303. package/test/saeeol/boxes-integration.test.ts +415 -0
  304. package/test/saeeol/builtin-skills.test.ts +75 -0
  305. package/test/saeeol/cleanup.ts +28 -0
  306. package/test/saeeol/cli/dev-setup.test.ts +74 -0
  307. package/test/saeeol/cli/roll-call.test.ts +161 -0
  308. package/test/saeeol/cli-run-auto-helper.test.ts +58 -0
  309. package/test/saeeol/codex-auth-refresh.test.ts +124 -0
  310. package/test/saeeol/commit-message/generate.test.ts +188 -0
  311. package/test/saeeol/commit-message/git-context.test.ts +303 -0
  312. package/test/saeeol/commit-message-windows.test.ts +38 -0
  313. package/test/saeeol/compaction-payload-recovery.test.ts +406 -0
  314. package/test/saeeol/compaction-preservation-audit.test.ts +122 -0
  315. package/test/saeeol/compaction-skip-guard.test.ts +224 -0
  316. package/test/saeeol/compaction-smart-select.test.ts +100 -0
  317. package/test/saeeol/config/config.test.ts +166 -0
  318. package/test/saeeol/config/indexing-default-plugin.test.ts +82 -0
  319. package/test/saeeol/config/opentelemetry-default.test.ts +29 -0
  320. package/test/saeeol/config-gitignore.test.ts +70 -0
  321. package/test/saeeol/config-injector.test.ts +305 -0
  322. package/test/saeeol/config-resilience.test.ts +234 -0
  323. package/test/saeeol/config-validation.test.ts +183 -0
  324. package/test/saeeol/cost-propagation.test.ts +94 -0
  325. package/test/saeeol/cost-tracker-extended.test.ts +141 -0
  326. package/test/saeeol/cost-tracker.test.ts +64 -0
  327. package/test/saeeol/custom-provider-delete.test.ts +149 -0
  328. package/test/saeeol/diff-full.test.ts +226 -0
  329. package/test/saeeol/edit-permission-filediff.test.ts +223 -0
  330. package/test/saeeol/encoding.test.ts +364 -0
  331. package/test/saeeol/enhance-prompt.test.ts +61 -0
  332. package/test/saeeol/ensure-plan-dir.test.ts +32 -0
  333. package/test/saeeol/errors.test.ts +144 -0
  334. package/test/saeeol/external-directory-boundary.test.ts +96 -0
  335. package/test/saeeol/gateway-headers.test.ts +88 -0
  336. package/test/saeeol/help.test.ts +191 -0
  337. package/test/saeeol/ignore-migrator.test.ts +308 -0
  338. package/test/saeeol/indexing-auth.test.ts +45 -0
  339. package/test/saeeol/indexing-feature.test.ts +44 -0
  340. package/test/saeeol/indexing-label.test.ts +70 -0
  341. package/test/saeeol/indexing-startup.test.ts +381 -0
  342. package/test/saeeol/indexing-worktree.test.ts +73 -0
  343. package/test/saeeol/instruction.test.ts +136 -0
  344. package/test/saeeol/lancedb-runtime.test.ts +116 -0
  345. package/test/saeeol/loader-auth.test.ts +168 -0
  346. package/test/saeeol/local-model.test.ts +621 -0
  347. package/test/saeeol/logo.test.ts +31 -0
  348. package/test/saeeol/lsp-typescript-lightweight.test.ts +89 -0
  349. package/test/saeeol/mcp-branding.test.ts +33 -0
  350. package/test/saeeol/mcp-docker-rm.test.ts +32 -0
  351. package/test/saeeol/mcp-migrator.test.ts +736 -0
  352. package/test/saeeol/mcp-oauth-callback.test.ts +33 -0
  353. package/test/saeeol/memory-io.test.ts +198 -0
  354. package/test/saeeol/memory-paths.test.ts +87 -0
  355. package/test/saeeol/memory-security.test.ts +166 -0
  356. package/test/saeeol/model-cache-org.test.ts +164 -0
  357. package/test/saeeol/model-info-panel-utils.test.ts +52 -0
  358. package/test/saeeol/model-info-panel.types.test.ts +7 -0
  359. package/test/saeeol/models-401-fallback.test.ts +52 -0
  360. package/test/saeeol/modes-migrator.test.ts +320 -0
  361. package/test/saeeol/nvidia-headers.test.ts +74 -0
  362. package/test/saeeol/patch-jsonc.test.ts +73 -0
  363. package/test/saeeol/patch.test.ts +172 -0
  364. package/test/saeeol/paths.test.ts +265 -0
  365. package/test/saeeol/permission/config-paths.test.ts +174 -0
  366. package/test/saeeol/permission/env-read.test.ts +149 -0
  367. package/test/saeeol/permission/external-directory-allow.test.ts +327 -0
  368. package/test/saeeol/permission/next.always-rules.test.ts +882 -0
  369. package/test/saeeol/permission/next.reply-http.test.ts +205 -0
  370. package/test/saeeol/permission/next.reply-routing.test.ts +184 -0
  371. package/test/saeeol/plan-exit-detection.test.ts +494 -0
  372. package/test/saeeol/plan-followup.test.ts +1376 -0
  373. package/test/saeeol/project-config-update.test.ts +120 -0
  374. package/test/saeeol/project-id.test.ts +455 -0
  375. package/test/saeeol/provider-cost.test.ts +171 -0
  376. package/test/saeeol/provider-list-failed-state.test.ts +100 -0
  377. package/test/saeeol/question-dismiss-all.test.ts +174 -0
  378. package/test/saeeol/read-directory.test.ts +116 -0
  379. package/test/saeeol/rules-migrator.test.ts +257 -0
  380. package/test/saeeol/run-auto.test.ts +176 -0
  381. package/test/saeeol/run-network.test.ts +224 -0
  382. package/test/saeeol/semantic-search.test.ts +186 -0
  383. package/test/saeeol/server/permission-allow-everything.test.ts +125 -0
  384. package/test/saeeol/session/instruction-substitution.test.ts +72 -0
  385. package/test/saeeol/session/platform-attribution.test.ts +118 -0
  386. package/test/saeeol/session/session.test.ts +105 -0
  387. package/test/saeeol/session-compaction-cap.test.ts +399 -0
  388. package/test/saeeol/session-compaction-chunks.test.ts +501 -0
  389. package/test/saeeol/session-compaction-safety.test.ts +481 -0
  390. package/test/saeeol/session-fork-remap.test.ts +251 -0
  391. package/test/saeeol/session-import-service.test.ts +114 -0
  392. package/test/saeeol/session-list.test.ts +47 -0
  393. package/test/saeeol/session-message-metadata.test.ts +128 -0
  394. package/test/saeeol/session-overflow.test.ts +78 -0
  395. package/test/saeeol/session-processor-empty-tool-calls.test.ts +571 -0
  396. package/test/saeeol/session-processor-network-offline.test.ts +204 -0
  397. package/test/saeeol/session-processor-retry-limit.test.ts +238 -0
  398. package/test/saeeol/session-processor-review-telemetry.test.ts +82 -0
  399. package/test/saeeol/session-prompt-compaction-safety.test.ts +517 -0
  400. package/test/saeeol/session-prompt-queue.test.ts +815 -0
  401. package/test/saeeol/sessions/inflight-cache.test.ts +157 -0
  402. package/test/saeeol/sessions/ingest-queue.test.ts +402 -0
  403. package/test/saeeol/sessions/remote-protocol.test.ts +258 -0
  404. package/test/saeeol/sessions/remote-sender.test.ts +1036 -0
  405. package/test/saeeol/sessions/remote-ws.test.ts +367 -0
  406. package/test/saeeol/sessions/sessions-enable-remote.test.disable +181 -0
  407. package/test/saeeol/slot-prop-reactivity.test.ts +142 -0
  408. package/test/saeeol/snapshot-cache.test.ts +84 -0
  409. package/test/saeeol/snapshot-freeze-repro.test.ts +100 -0
  410. package/test/saeeol/snapshot-track-timeout.test.ts +519 -0
  411. package/test/saeeol/stats-subagent-cost.test.ts +123 -0
  412. package/test/saeeol/suggestion/auto-dismiss.test.ts +65 -0
  413. package/test/saeeol/suggestion/suggestion.test.ts +145 -0
  414. package/test/saeeol/suggestion/tool.test.ts +298 -0
  415. package/test/saeeol/summary-file-diff.test.ts +28 -0
  416. package/test/saeeol/system-prompt.test.ts +142 -0
  417. package/test/saeeol/task-nesting.test.ts +193 -0
  418. package/test/saeeol/telemetry/feedback.test.ts +8 -0
  419. package/test/saeeol/todo-view.test.ts +57 -0
  420. package/test/saeeol/tool-encoding.test.ts +455 -0
  421. package/test/saeeol/tool-registry-indexing-import-failure.test.ts +49 -0
  422. package/test/saeeol/tool-registry-indexing.test.ts +236 -0
  423. package/test/saeeol/tool-registry-semantic-import-failure.test.ts +55 -0
  424. package/test/saeeol/tool-task-model.test.ts +352 -0
  425. package/test/saeeol/transform-opus-4.7.test.ts +89 -0
  426. package/test/saeeol/tui-diff.test.ts +91 -0
  427. package/test/saeeol/tui-sync.test.ts +80 -0
  428. package/test/saeeol/util/url.test.ts +141 -0
  429. package/test/saeeol/workflows-migrator.test.ts +261 -0
  430. package/test/saeeol/worktree-diff-summary.test.ts +64 -0
  431. package/test/saeeol/worktree-diff.test.ts +223 -0
  432. package/test/saeeol/worktree-remove-lock.test.ts +82 -0
  433. package/test/server/AGENTS.md +15 -0
  434. package/test/server/contract.test.ts +357 -0
  435. package/test/server/experimental-session-list.test.ts +157 -0
  436. package/test/server/global-session-list.test.ts +155 -0
  437. package/test/server/httpapi-authorization.test.ts +103 -0
  438. package/test/server/httpapi-bridge.test.ts +440 -0
  439. package/test/server/httpapi-config.test.ts +67 -0
  440. package/test/server/httpapi-cors.test.ts +89 -0
  441. package/test/server/httpapi-event.test.ts +57 -0
  442. package/test/server/httpapi-experimental.test.ts +219 -0
  443. package/test/server/httpapi-file.test.ts +79 -0
  444. package/test/server/httpapi-instance-context.test.ts +237 -0
  445. package/test/server/httpapi-instance.legacy.test.ts +140 -0
  446. package/test/server/httpapi-instance.test.ts +83 -0
  447. package/test/server/httpapi-json-parity.test.ts +263 -0
  448. package/test/server/httpapi-mcp-oauth.test.ts +76 -0
  449. package/test/server/httpapi-mcp.test.ts +189 -0
  450. package/test/server/httpapi-provider.test.ts +153 -0
  451. package/test/server/httpapi-pty-websocket.test.ts +16 -0
  452. package/test/server/httpapi-pty.test.ts +175 -0
  453. package/test/server/httpapi-raw-route-auth.test.ts +89 -0
  454. package/test/server/httpapi-sdk.test.ts +681 -0
  455. package/test/server/httpapi-session.test.ts +464 -0
  456. package/test/server/httpapi-sync.test.ts +130 -0
  457. package/test/server/httpapi-tui.test.ts +121 -0
  458. package/test/server/httpapi-workspace-routing.test.ts +471 -0
  459. package/test/server/httpapi-workspace.test.ts +427 -0
  460. package/test/server/lib/conformance.ts +88 -0
  461. package/test/server/lib/stateful.ts +112 -0
  462. package/test/server/project-init-git.test.ts +113 -0
  463. package/test/server/proxy-util.test.ts +113 -0
  464. package/test/server/session-actions.test.ts +49 -0
  465. package/test/server/session-list.test.ts +238 -0
  466. package/test/server/session-messages.test.ts +167 -0
  467. package/test/server/session-select.test.ts +100 -0
  468. package/test/server/trace-attributes.test.ts +76 -0
  469. package/test/server/workspace-proxy.test.ts +165 -0
  470. package/test/server/workspace-routing.test.ts +85 -0
  471. package/test/session/compaction.test.ts +2420 -0
  472. package/test/session/instruction.test.ts +247 -0
  473. package/test/session/llm.test.ts +1273 -0
  474. package/test/session/message-v2.test.ts +1291 -0
  475. package/test/session/messages-pagination.test.ts +1173 -0
  476. package/test/session/network.test.ts +249 -0
  477. package/test/session/processor-effect.test.ts +847 -0
  478. package/test/session/prompt.test.ts +2131 -0
  479. package/test/session/retry.test.ts +340 -0
  480. package/test/session/revert-compact.test.ts +639 -0
  481. package/test/session/schema-decoding.test.ts +311 -0
  482. package/test/session/session-entry-stepper.test.ts +917 -0
  483. package/test/session/session-schema.test.ts +76 -0
  484. package/test/session/snapshot-tool-race.test.ts +257 -0
  485. package/test/session/structured-output-integration.test.ts +265 -0
  486. package/test/session/structured-output.test.ts +381 -0
  487. package/test/session/system.test.ts +73 -0
  488. package/test/share/share-next.test.ts +333 -0
  489. package/test/shell/shell.test.ts +99 -0
  490. package/test/skill/discovery.test.ts +116 -0
  491. package/test/skill/skill.test.ts +393 -0
  492. package/test/smoke/.tui-debug-output.txt +1 -0
  493. package/test/smoke/.tui-debug-plain.txt +1 -0
  494. package/test/smoke/.tui-walkthrough-report.txt +122 -0
  495. package/test/smoke/smoke-tui-pty.test.ts +123 -0
  496. package/test/smoke/smoke-tui.mjs +83 -0
  497. package/test/smoke/tui-walkthrough.test.ts +520 -0
  498. package/test/snapshot/snapshot.test.ts +1531 -0
  499. package/test/storage/db.test.ts +23 -0
  500. package/test/storage/json-migration.test.ts +832 -0
  501. package/test/storage/storage.test.ts +293 -0
  502. package/test/suggestion/suggestion.test.ts +1 -0
  503. package/test/sync/index.test.ts +256 -0
  504. package/test/tool/__snapshots__/parameters.test.ts.snap +500 -0
  505. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  506. package/test/tool/apply_patch.test.ts +614 -0
  507. package/test/tool/bash.test.ts +1225 -0
  508. package/test/tool/diagnostics-filter.test.ts +55 -0
  509. package/test/tool/edit.test.ts +754 -0
  510. package/test/tool/external-directory.test.ts +169 -0
  511. package/test/tool/fixtures/large-image.png +0 -0
  512. package/test/tool/fixtures/models-api.json +65179 -0
  513. package/test/tool/glob.test.ts +107 -0
  514. package/test/tool/grep.test.ts +114 -0
  515. package/test/tool/lsp.test.ts +187 -0
  516. package/test/tool/parameters.test.ts +243 -0
  517. package/test/tool/question.test.ts +129 -0
  518. package/test/tool/read.test.ts +500 -0
  519. package/test/tool/recall.test.ts +151 -0
  520. package/test/tool/registry.test.ts +203 -0
  521. package/test/tool/skill.test.ts +135 -0
  522. package/test/tool/suggest.test.ts +1 -0
  523. package/test/tool/task.test.ts +612 -0
  524. package/test/tool/tool-define.test.ts +99 -0
  525. package/test/tool/truncation.test.ts +260 -0
  526. package/test/tool/webfetch.test.ts +103 -0
  527. package/test/tool/write.test.ts +291 -0
  528. package/test/util/data-url.test.ts +14 -0
  529. package/test/util/effect-zod.test.ts +754 -0
  530. package/test/util/error.test.ts +38 -0
  531. package/test/util/filesystem.test.ts +656 -0
  532. package/test/util/format.test.ts +59 -0
  533. package/test/util/glob.test.ts +164 -0
  534. package/test/util/iife.test.ts +36 -0
  535. package/test/util/lazy.test.ts +50 -0
  536. package/test/util/lock.test.ts +72 -0
  537. package/test/util/log.test.ts +86 -0
  538. package/test/util/module.test.ts +59 -0
  539. package/test/util/process.test.ts +128 -0
  540. package/test/util/timeout.test.ts +21 -0
  541. package/test/util/which.test.ts +100 -0
  542. package/test/util/wildcard.test.ts +90 -0
  543. package/test/workspace/workspace-restore.test.ts +296 -0
  544. package/src/provider/models-snapshot.d.ts +0 -2
  545. package/src/provider/models-snapshot.js +0 -3
@@ -26,6 +26,7 @@ import {
26
26
  } from "@saeeol/core/util/saeeol-process"
27
27
  import { validateSession } from "./validate-session"
28
28
  import { spawn } from "node:child_process"
29
+ import { preflight, writeCrashLog } from "./preflight"
29
30
 
30
31
  declare global {
31
32
  const SAEEOL_WORKER_PATH: string
@@ -168,6 +169,24 @@ export const TuiThreadCommand = cmd({
168
169
  // chdir so the thread and worker share the same directory key.
169
170
  const next = resolveThreadDirectory(args.project)
170
171
  const file = await target()
172
+
173
+ // Preflight checks before spawning Worker
174
+ const pf = preflight()
175
+ if (pf.length > 0) {
176
+ for (const f of pf) {
177
+ if (f.severity === "error") {
178
+ UI.error(`[preflight] ${f.message}`)
179
+ } else {
180
+ UI.println(`[preflight] ⚠ ${f.message}`)
181
+ }
182
+ }
183
+ const errors = pf.filter((f) => f.severity === "error")
184
+ if (errors.length > 0) {
185
+ UI.error(`TUI cannot start — fix ${errors.length} error(s) above`)
186
+ process.exitCode = 1
187
+ return
188
+ }
189
+ }
171
190
  try {
172
191
  process.chdir(next)
173
192
  } catch {
@@ -196,6 +215,7 @@ export const TuiThreadCommand = cmd({
196
215
  const client = Rpc.client<typeof rpc>(worker)
197
216
  const error = (e: unknown) => {
198
217
  Log.Default.error("process error", { error: errorMessage(e) })
218
+ writeCrashLog(e)
199
219
  }
200
220
  const reload = () => {
201
221
  client.call("reload", undefined).catch((err) => {
@@ -1,4 +1,4 @@
1
- import { parseUnifiedDiff, type DiffHunk } from "@saeeol/boxes/diff-apply"
1
+ import { parseUnifiedDiff, type DiffHunk } from "@/boxes/diff-apply"
2
2
 
3
3
  interface ParsedFile {
4
4
  filename: string
@@ -1,4 +1,4 @@
1
- import { random as randomId } from "@saeeol/boxes/uid"
1
+ import { random as randomId } from "@/boxes/uid"
2
2
  import { Provider } from "../../../provider/provider"
3
3
  import { ProviderTransform } from "../../../provider/transform"
4
4
  import { generateText } from "ai"
@@ -1 +1 @@
1
- export { formatCost, formatTotalCost } from "@saeeol/boxes/cost-tracker"
1
+ export { formatCost, formatTotalCost } from "@/boxes/cost-tracker"
@@ -1,12 +1,12 @@
1
- /**
1
+ /**
2
2
  * Cost tracker — re-exported from @saeeol/boxes
3
3
  * Original 4-file module (types + state + format + index) → single atom
4
4
  */
5
- export { formatCost, formatTotalCost } from "@saeeol/boxes/cost-tracker"
6
- export type { TokenUsage, ModelUsage } from "@saeeol/boxes/cost-tracker"
5
+ export { formatCost, formatTotalCost } from "@/boxes/cost-tracker"
6
+ export type { TokenUsage, ModelUsage } from "@/boxes/cost-tracker"
7
7
 
8
8
  // Namespace wrapper for backward compatibility
9
- import * as CT from "@saeeol/boxes/cost-tracker"
9
+ import * as CT from "@/boxes/cost-tracker"
10
10
  export const State = {
11
11
  reset: CT.reset,
12
12
  getTotalCost: CT.getTotalCost,
@@ -1,5 +1,5 @@
1
- export {
1
+ export {
2
2
  getTotalCost, getDuration, getAPIDuration, getLines,
3
3
  getModelUsage, getUsageForModel, addSessionCost, addLines,
4
4
  addAPIDuration, addToolDuration, reset,
5
- } from "@saeeol/boxes/cost-tracker"
5
+ } from "@/boxes/cost-tracker"
@@ -1,9 +1,9 @@
1
- export type { TokenUsage, ModelUsage } from "@saeeol/boxes/cost-tracker"
1
+ export type { TokenUsage, ModelUsage } from "@/boxes/cost-tracker"
2
2
  export interface CostState {
3
3
  totalCostUSD: number
4
4
  totalAPIDuration: number
5
5
  totalToolDuration: number
6
6
  totalLinesAdded: number
7
7
  totalLinesRemoved: number
8
- modelUsage: Record<string, import("@saeeol/boxes/cost-tracker").ModelUsage>
8
+ modelUsage: Record<string, import("@/boxes/cost-tracker").ModelUsage>
9
9
  }
@@ -1 +1 @@
1
- export { memoryAgeDays, memoryAge, memoryFreshnessText } from "@saeeol/boxes/memory"
1
+ export { memoryAgeDays, memoryAge, memoryFreshnessText } from "@/boxes/memory"
@@ -1,4 +1,4 @@
1
- /**
1
+ /**
2
2
  * Memory system — re-exported from @saeeol/boxes
3
3
  * Original 5-file module (types + age + paths + scan + index) → single atom
4
4
  */
@@ -20,11 +20,11 @@ export {
20
20
  ensureDir,
21
21
  buildPrompt,
22
22
  scan,
23
- } from "@saeeol/boxes/memory"
24
- export type { MemoryType, MemoryHeader } from "@saeeol/boxes/memory"
23
+ } from "@/boxes/memory"
24
+ export type { MemoryType, MemoryHeader } from "@/boxes/memory"
25
25
 
26
26
  // Namespace wrapper for backward compatibility
27
- import * as Mem from "@saeeol/boxes/memory"
27
+ import * as Mem from "@/boxes/memory"
28
28
  export const Memory = {
29
29
  scan: Mem.scan,
30
30
  buildPrompt: Mem.buildPrompt,
@@ -1,4 +1,4 @@
1
- export {
1
+ export {
2
2
  getMemoryBaseDir, getMemoryDir, getMemoryEntrypoint, isMemoryPath,
3
3
  MAX_ENTRYPOINT_LINES, MAX_ENTRYPOINT_BYTES,
4
- } from "@saeeol/boxes/memory"
4
+ } from "@/boxes/memory"
@@ -1 +1 @@
1
- export { scanMemoryFiles, formatMemoryManifest } from "@saeeol/boxes/memory"
1
+ export { scanMemoryFiles, formatMemoryManifest } from "@/boxes/memory"
@@ -1,2 +1,2 @@
1
- export { MEMORY_TYPES, parseMemoryType } from "@saeeol/boxes/memory"
2
- export type { MemoryType } from "@saeeol/boxes/memory"
1
+ export { MEMORY_TYPES, parseMemoryType } from "@/boxes/memory"
2
+ export type { MemoryType } from "@/boxes/memory"
@@ -1,6 +1,6 @@
1
- /**
1
+ /**
2
2
  * Bash security validator — re-exported from @saeeol/boxes
3
3
  * Original 2-file module (patterns + validator) → single atom
4
4
  */
5
- export { validate, isCommandSafe, getSecurityReport } from "@saeeol/boxes/bash-security"
6
- export type { SecurityResult } from "@saeeol/boxes/bash-security"
5
+ export { validate, isCommandSafe, getSecurityReport } from "@/boxes/bash-security"
6
+ export type { SecurityResult } from "@/boxes/bash-security"
@@ -1 +1 @@
1
- export { norm as normalizeUrls } from "@saeeol/boxes/puny"
1
+ export { norm as normalizeUrls } from "@/boxes/puny"
@@ -1,4 +1,4 @@
1
- import { b64EncBytes } from "@saeeol/boxes/b64"
1
+ import { b64EncBytes } from "@/boxes/b64"
2
2
  import * as Log from "@saeeol/core/util/log"
3
3
  import { InstallationVersion } from "@saeeol/core/installation/version"
4
4
  import { setTimeout as sleep } from "node:timers/promises"
@@ -1,5 +1,5 @@
1
- import { fetchSaeeolModels, type SaeeolModelsResult } from "@saeeol/gateway"
2
- import { TTLCache } from "@saeeol/boxes/ttl-cache"
1
+ import { fetchSaeeolModels, type SaeeolModelsResult } from "@saeeol/gateway"
2
+ import { TTLCache } from "@/boxes/ttl-cache"
3
3
  import { Config } from "../config/config"
4
4
  import { Auth } from "../auth"
5
5
  import * as Log from "@saeeol/core/util/log"
@@ -188,7 +188,7 @@ export async function resolveSDK(model: Model, s: State, envs: Record<string, st
188
188
  void Bus.publish(ProviderInstallEvent.Started, {
189
189
  providerID: model.providerID,
190
190
  pkg: model.api.npm,
191
- })
191
+ }).catch(() => {})
192
192
  try {
193
193
  const item = await Npm.add(model.api.npm)
194
194
  if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
@@ -196,13 +196,13 @@ export async function resolveSDK(model: Model, s: State, envs: Record<string, st
196
196
  void Bus.publish(ProviderInstallEvent.Completed, {
197
197
  providerID: model.providerID,
198
198
  pkg: model.api.npm,
199
- })
199
+ }).catch(() => {})
200
200
  } catch (installErr) {
201
201
  void Bus.publish(ProviderInstallEvent.Failed, {
202
202
  providerID: model.providerID,
203
203
  pkg: model.api.npm,
204
204
  error: installErr instanceof Error ? installErr.message : String(installErr),
205
- })
205
+ }).catch(() => {})
206
206
  throw installErr
207
207
  }
208
208
  } else {
@@ -1,4 +1,4 @@
1
- import { parse as parseDataUrl } from "@saeeol/boxes/dataurl"
1
+ import { parse as parseDataUrl } from "@/boxes/dataurl"
2
2
  import type { ModelMessage } from "ai"
3
3
  import { mergeDeep, unique } from "remeda"
4
4
  import type * as Provider from "./provider"
@@ -0,0 +1,284 @@
1
+ import { Hono } from "hono"
2
+ import { z } from "zod"
3
+ import { generateText } from "ai"
4
+ import { createOpenAI } from "@ai-sdk/openai"
5
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
6
+ import * as Log from "@saeeol/core/util/log"
7
+ import { lazy } from "@/util/lazy"
8
+
9
+ const log = Log.create({ service: "game-event" })
10
+
11
+ // ── Request schemas ──────────────────────────────────────────────
12
+
13
+ const GameContextSchema = z.object({
14
+ character: z.object({ id: z.string(), name: z.string() }).optional(),
15
+ stats: z.record(z.number()).optional(),
16
+ flags: z.record(z.any()).optional(),
17
+ items: z.record(z.number()).optional(),
18
+ route: z.string().optional(),
19
+ act: z.string().optional(),
20
+ choice_history: z.array(z.string()).optional(),
21
+ bosses_defeated: z.array(z.string()).optional(),
22
+ })
23
+
24
+ const EventSchema = z.object({
25
+ id: z.string().optional(),
26
+ lines: z.array(z.object({ speaker: z.string(), text: z.string() }).passthrough()).optional(),
27
+ choices: z.array(z.any()).optional(),
28
+ nextSceneId: z.string().optional(),
29
+ }).passthrough()
30
+
31
+ const BaseRequestSchema = z.object({
32
+ action: z.enum(["augment", "generate", "reaction"]),
33
+ context: GameContextSchema.optional(),
34
+ })
35
+
36
+ const AugmentRequestSchema = BaseRequestSchema.extend({
37
+ action: z.literal("augment"),
38
+ event: EventSchema,
39
+ })
40
+
41
+ const GenerateRequestSchema = BaseRequestSchema.extend({
42
+ action: z.literal("generate"),
43
+ event_id: z.string(),
44
+ prompt_hint: z.record(z.any()).optional(),
45
+ })
46
+
47
+ const ReactionRequestSchema = BaseRequestSchema.extend({
48
+ action: z.literal("reaction"),
49
+ choice_text: z.string(),
50
+ event: EventSchema,
51
+ })
52
+
53
+ const RequestSchema = z.discriminatedUnion("action", [
54
+ AugmentRequestSchema,
55
+ GenerateRequestSchema,
56
+ ReactionRequestSchema,
57
+ ])
58
+
59
+ // ── Prompt builders ──────────────────────────────────────────────
60
+
61
+ function buildSystemPrompt(hint: Record<string, any> = {}): string {
62
+ const tone = hint.tone ?? "몰입감 있고 생생한 묘사"
63
+ const themes = (hint.themes ?? []).join(", ")
64
+ const escalation = hint.escalation ?? ""
65
+
66
+ return `너는 에로틱 판타지 던전 크롤러 게임의 AI 내레이터다.
67
+
68
+ ## 역할
69
+ - 플레이어의 행동과 상황에 맞는 생생한 묘사를 생성한다
70
+ - 캐릭터의 성격과 현재 상태(HP, 음란도, 개발도, 소지 아이템, 플래그)를 반영한다
71
+ - 항상 한국어로 응답한다
72
+
73
+ ## 분위기
74
+ - 톤: ${tone}
75
+ - 테마: ${themes}
76
+ ${escalation ? `- 에스컬레이션: ${escalation}` : ""}
77
+
78
+ ## 응답 형식
79
+ 항상 다음 JSON 스키마로 응답한다. 다른 텍스트는 절대 포함하지 않는다:
80
+ {
81
+ "lines": [
82
+ { "speaker": "나레이션", "text": "묘사 텍스트", "ai": true }
83
+ ]
84
+ }
85
+
86
+ ## 규칙
87
+ - lines는 1~3개
88
+ - 각 line의 text는 1~3문장
89
+ - 캐릭터 이름은 context에서 가져와서 자연스럽게 사용
90
+ - 현재 루트와 act의 분위기를 반영
91
+ - 플래그가 있으면 이전 경험을 참조
92
+ - 절대 JSON 이외의 텍스트를 출력하지 않는다`
93
+ }
94
+
95
+ function buildAugmentPrompt(event: any, context: any): string {
96
+ const charName = context?.character?.name ?? "모험자"
97
+ const charId = context?.character?.id ?? ""
98
+ const route = context?.route ?? ""
99
+ const flags = context?.flags ?? {}
100
+ const stats = context?.stats ?? {}
101
+ const originalLines = (event?.lines ?? []).map((l: any) => `${l.speaker}: ${l.text}`).join("\n")
102
+
103
+ return `이 이벤트에 분위기를 더하는 AI 묘사를 생성하라.
104
+
105
+ ## 현재 상태
106
+ - 캐릭터: ${charName} (${charId})
107
+ - 루트: ${route}
108
+ - HP: ${stats.hp ?? "?"}, 음란도: ${stats.lewdness ?? "?"}, 개발도: ${stats.corruption ?? "?"}
109
+ - 플래그: ${JSON.stringify(flags)}
110
+
111
+ ## 원본 이벤트 대사
112
+ ${originalLines}
113
+
114
+ ## 요청
115
+ 원본 대사 앞에 들어갈 1~2줄의 분위기 묘사를 생성하라. 캐릭터의 현재 상태와 플래그를 반영하라.`
116
+ }
117
+
118
+ function buildGeneratePrompt(eventId: string, context: any, hint: Record<string, any> = {}): string {
119
+ const charName = context?.character?.name ?? "모험자"
120
+ const route = context?.route ?? ""
121
+ const act = context?.act ?? ""
122
+ const stats = context?.stats ?? {}
123
+ const flags = context?.flags ?? {}
124
+ const items = context?.items ?? {}
125
+ const charHints = hint.character_hints ?? {}
126
+ const charId = context?.character?.id ?? ""
127
+ const charHint = charHints[charId] ?? ""
128
+
129
+ return `완전히 새로운 게임 이벤트를 생성하라.
130
+
131
+ ## 이벤트 ID: ${eventId}
132
+ ## Act: ${act}, Route: ${route}
133
+
134
+ ## 캐릭터
135
+ - 이름: ${charName} (${charId})
136
+ - 성향: ${charHint}
137
+ - HP: ${stats.hp ?? "?"}, 음란도: ${stats.lewdness ?? "?"}, 개발도: ${stats.corruption ?? "?"}
138
+
139
+ ## 소지품
140
+ ${JSON.stringify(items)}
141
+
142
+ ## 경험한 일들 (플래그)
143
+ ${JSON.stringify(flags)}
144
+
145
+ ## 요청
146
+ 다음 JSON 스키마로 완전한 이벤트를 생성하라:
147
+ {
148
+ "id": "${eventId}",
149
+ "act": "${act}",
150
+ "lines": [
151
+ { "speaker": "나레이션", "text": "...", "ai": true }
152
+ ],
153
+ "choices": [
154
+ { "text": "선택지 텍스트", "nextSceneId": "다음_이벤트_ID", "conditions": [], "effects": [] }
155
+ ],
156
+ "nextSceneId": "기본_다음_이벤트_ID"
157
+ }
158
+
159
+ 2~4줄의 대사와 2~3개의 선택지를 만들어라.`
160
+ }
161
+
162
+ function buildReactionPrompt(choiceText: string, event: any, context: any): string {
163
+ const charName = context?.character?.name ?? "모험자"
164
+ const stats = context?.stats ?? {}
165
+
166
+ return `플레이어가 선택을 한 직후의 반응 묘사를 생성하라.
167
+
168
+ ## 선택한 것: "${choiceText}"
169
+ ## 캐릭터: ${charName}
170
+ - HP: ${stats.hp ?? "?"}, 음란도: ${stats.lewdness ?? "?"}
171
+
172
+ ## 요청
173
+ 선택 직후의 짧은 반응 묘사 1~2줄을 생성하라. 캐릭터의 감정과 상황 변화를 반영하라.
174
+
175
+ JSON 형식:
176
+ {
177
+ "lines": [
178
+ { "speaker": "나레이션", "text": "...", "ai": true }
179
+ ]
180
+ }`
181
+ }
182
+
183
+ // ── LLM call ─────────────────────────────────────────────────────
184
+
185
+ function createProvider() {
186
+ // Use OPENAI_API_KEY or OPENAI_BASE_URL from env
187
+ const apiKey = process.env.OPENAI_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? ""
188
+ const baseURL = process.env.OPENAI_BASE_URL
189
+
190
+ if (baseURL) {
191
+ return createOpenAICompatible({ name: "custom", baseURL, apiKey })
192
+ }
193
+
194
+ return createOpenAI({ apiKey })
195
+ }
196
+
197
+ async function callLLM(systemPrompt: string, userPrompt: string): Promise<any> {
198
+ const provider = createProvider()
199
+ const modelId = process.env.GAME_LLM_MODEL ?? "gpt-4o-mini"
200
+ const model = provider(modelId)
201
+
202
+ const result = await generateText({
203
+ model,
204
+ system: systemPrompt,
205
+ prompt: userPrompt,
206
+ maxTokens: 1024,
207
+ temperature: 0.8,
208
+ })
209
+
210
+ // Extract JSON from response (handle markdown code blocks)
211
+ let text = result.text.trim()
212
+ const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/)
213
+ if (jsonMatch) {
214
+ text = jsonMatch[1].trim()
215
+ }
216
+
217
+ return JSON.parse(text)
218
+ }
219
+
220
+ // ── Routes ───────────────────────────────────────────────────────
221
+
222
+ export const GameEventRoutes = lazy(() =>
223
+ new Hono()
224
+ .post(
225
+ "/event",
226
+ async (c) => {
227
+ const body = await c.req.json()
228
+ const parsed = RequestSchema.safeParse(body)
229
+
230
+ if (!parsed.success) {
231
+ return c.json({ error: "Invalid request", details: parsed.error.flatten() }, 400)
232
+ }
233
+
234
+ const req = parsed.data
235
+ const context = req.context ?? {}
236
+
237
+ try {
238
+ switch (req.action) {
239
+ case "augment": {
240
+ const hint: Record<string, any> = {}
241
+ const systemPrompt = buildSystemPrompt(hint)
242
+ const userPrompt = buildAugmentPrompt(req.event, context)
243
+
244
+ const result = await callLLM(systemPrompt, userPrompt)
245
+
246
+ // Merge AI lines into original event
247
+ const aiLines: any[] = result.lines ?? []
248
+ const originalLines: any[] = req.event?.lines ?? []
249
+ const merged = {
250
+ ...req.event,
251
+ lines: [...aiLines, ...originalLines],
252
+ }
253
+
254
+ log.info("augment event", { id: req.event?.id, aiLines: aiLines.length })
255
+ return c.json(merged)
256
+ }
257
+
258
+ case "generate": {
259
+ const hint = req.prompt_hint ?? {}
260
+ const systemPrompt = buildSystemPrompt(hint)
261
+ const userPrompt = buildGeneratePrompt(req.event_id, context, hint)
262
+
263
+ const result = await callLLM(systemPrompt, userPrompt)
264
+ log.info("generate event", { id: req.event_id })
265
+ return c.json(result)
266
+ }
267
+
268
+ case "reaction": {
269
+ const hint: Record<string, any> = {}
270
+ const systemPrompt = buildSystemPrompt(hint)
271
+ const userPrompt = buildReactionPrompt(req.choice_text, req.event, context)
272
+
273
+ const result = await callLLM(systemPrompt, userPrompt)
274
+ log.info("reaction", { choice: req.choice_text })
275
+ return c.json(result)
276
+ }
277
+ }
278
+ } catch (err: any) {
279
+ log.error("game event failed", { error: err.message })
280
+ return c.json({ error: err.message }, 500)
281
+ }
282
+ },
283
+ ),
284
+ )
@@ -13,6 +13,7 @@ import { InstanceRoutes } from "./routes/instance"
13
13
  import { ControlPlaneRoutes } from "./routes/control"
14
14
  import { UIRoutes } from "./routes/ui"
15
15
  import { GlobalRoutes } from "./routes/global"
16
+ import { GameEventRoutes } from "./routes/game"
16
17
  import { WorkspaceRouterMiddleware } from "./workspace"
17
18
  import { InstanceMiddleware } from "./routes/instance/middleware"
18
19
  import { WorkspaceRoutes } from "./routes/control/workspace"
@@ -106,6 +107,7 @@ function createHono(opts: CorsOptions, selection: ServerBackend.Selection = Serv
106
107
  .use(CompressionMiddleware)
107
108
  .use(CorsMiddleware(opts))
108
109
  .route("/global", GlobalRoutes())
110
+ .route("/api", GameEventRoutes())
109
111
 
110
112
  const runtime = adapter.create(app)
111
113
 
@@ -1,4 +1,4 @@
1
- import { clamp } from "@saeeol/boxes/clamp"
1
+ import { clamp } from "@/boxes/clamp"
2
2
  import { BusEvent } from "@/bus/bus-event"
3
3
  import { SessionID, MessageID } from "../schema"
4
4
  import { Provider } from "@/provider/provider"
@@ -1,4 +1,4 @@
1
- import { clamp } from "@saeeol/boxes/clamp"
1
+ import { clamp } from "@/boxes/clamp"
2
2
  import { BusEvent } from "@/bus/bus-event"
3
3
  import { Bus } from "@/bus"
4
4
  import * as Session from "../session"
@@ -317,7 +317,9 @@ export const getUsage = (input: {
317
317
  input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
318
318
  // google-vertex-anthropic returns metadata under "vertex" key
319
319
  input.metadata?.["vertex"]?.["cacheCreationInputTokens"] ??
320
+ // @ts-expect-error
320
321
  input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
322
+ // @ts-expect-error
321
323
  input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
322
324
  0,
323
325
  ),
@@ -1,7 +1,7 @@
1
- import { ulid } from "ulid"
1
+ import { ulid } from "ulid"
2
2
  import type * as SDK from "@saeeol/sdk/v2"
3
3
  import type { SaeeolSession } from "@/saeeol/session"
4
- import { expDelay } from "@saeeol/boxes/schedule"
4
+ import { expDelay } from "@/boxes/schedule"
5
5
 
6
6
  export namespace IngestQueue {
7
7
  export type Client = {
@@ -1,4 +1,4 @@
1
- import { random as randomId } from "@saeeol/boxes/uid"
1
+ import { random as randomId } from "@/boxes/uid"
2
2
  import { RemoteProtocol } from "@/sessions/remote-protocol"
3
3
 
4
4
  export namespace RemoteWS {
@@ -1,5 +1,5 @@
1
1
  import { Effect, Schema } from "effect"
2
- import { plural } from "@saeeol/boxes/plural"
2
+ import { plural } from "@/boxes/plural"
3
3
  import * as Tool from "../core/tool"
4
4
  import { Question } from "../../question"
5
5
  import DESCRIPTION from "./question.txt"
package/src/util/abort.ts CHANGED
@@ -1 +1 @@
1
- export { cancelAfter as abortAfter, cancelAny as abortAfterAny } from "@saeeol/boxes/cancel"
1
+ export { cancelAfter as abortAfter, cancelAny as abortAfterAny } from "@/boxes/cancel"
package/src/util/bom.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { split, join } from "@saeeol/boxes/bom"
1
+ export { split, join } from "@/boxes/bom"
2
2
  import { Effect } from "effect"
3
3
  import { AppFileSystem } from "@saeeol/core/filesystem"
4
- import { split, join } from "@saeeol/boxes/bom"
4
+ import { split, join } from "@/boxes/bom"
5
5
 
6
6
  export const readFile = Effect.fn("Bom.readFile")(function* (fs: AppFileSystem.Interface, filePath: string) {
7
7
  return split(new TextDecoder("utf-8", { ignoreBOM: true }).decode(yield* fs.readFile(filePath)))
package/src/util/color.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { valid as isValidHex, rgb as hexToRgb, bold as hexToAnsiBold } from "@saeeol/boxes/ansi"
1
+ export { valid as isValidHex, rgb as hexToRgb, bold as hexToAnsiBold } from "@/boxes/ansi"
2
2
 
3
3
  export * as Color from "./color"
@@ -1 +1 @@
1
- export { decode as decodeDataUrl } from "@saeeol/boxes/dataurl"
1
+ export { decode as decodeDataUrl } from "@/boxes/dataurl"
package/src/util/defer.ts CHANGED
@@ -1 +1 @@
1
- export { disposable as defer } from "@saeeol/boxes/disposable"
1
+ export { disposable as defer } from "@/boxes/disposable"
package/src/util/error.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { msg as errorMessage, fmt as errorFormat } from "@saeeol/boxes/err"
2
- import { msg as errorMessage, fmt as errorFormat } from "@saeeol/boxes/err"
1
+ export { msg as errorMessage, fmt as errorFormat } from "@/boxes/err"
2
+ import { msg as errorMessage, fmt as errorFormat } from "@/boxes/err"
3
3
  import { isRecord } from "./record"
4
4
 
5
5
  export function errorData(error: unknown) {
@@ -1,5 +1,5 @@
1
- import { chmod, mkdir, readFile, stat as statFile, writeFile } from "fs/promises"
2
- import { atomicWrite } from "@saeeol/boxes/atomic-write"
1
+ import { chmod, mkdir, readFile, stat as statFile, writeFile } from "fs/promises"
2
+ import { atomicWrite } from "@/boxes/atomic-write"
3
3
  import { createWriteStream, existsSync, statSync } from "fs"
4
4
  import { realpathSync } from "fs"
5
5
  import { dirname, isAbsolute, join, relative, resolve as pathResolve, sep, win32 } from "path"
@@ -1 +1 @@
1
- export { secsDuration as formatDuration } from "@saeeol/boxes/human"
1
+ export { secsDuration as formatDuration } from "@/boxes/human"
package/src/util/iife.ts CHANGED
@@ -1 +1 @@
1
- export { iife } from "@saeeol/boxes/iife"
1
+ export { iife } from "@/boxes/iife"
@@ -1,3 +1,3 @@
1
- export { scope as create, NotFound } from "@saeeol/boxes/scope"
1
+ export { scope as create, NotFound } from "@/boxes/scope"
2
2
 
3
3
  export * as LocalContext from "./local-context"
@@ -1,5 +1,5 @@
1
- import { time, datetime } from "@saeeol/boxes/human"
2
- export { title as titlecase, time, datetime, compact as number, duration, truncate, truncateMid as truncateMiddle, plural as pluralize } from "@saeeol/boxes/human"
1
+ import { time, datetime } from "@/boxes/human"
2
+ export { title as titlecase, time, datetime, compact as number, duration, truncate, truncateMid as truncateMiddle, plural as pluralize } from "@/boxes/human"
3
3
 
4
4
  export function todayTimeOrDateTime(input: number): string {
5
5
  const date = new Date(input)
package/src/util/lock.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { read, write } from "@saeeol/boxes/rwlock"
1
+ export { read, write } from "@/boxes/rwlock"
2
2
 
3
3
  export * as Lock from "./lock"
@@ -1 +1 @@
1
- export { online, proxied } from "@saeeol/boxes/net"
1
+ export { online, proxied } from "@/boxes/net"