saeeol 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/package.json +14 -14
  2. package/script/build.ts +1 -1
  3. package/src/addons/addon-analytics.ts +1 -1
  4. package/src/cli/cmd/tui/routes/session/suggest.tsx +1 -1
  5. package/src/cli/logo.ts +1 -1
  6. package/src/cli/ui.ts +1 -1
  7. package/src/config/config-loader.ts +1 -1
  8. package/src/config/config-schema.ts +1 -1
  9. package/src/config/config.ts +1 -1
  10. package/src/config/markdown.ts +1 -1
  11. package/src/index.ts +2 -2
  12. package/src/lsp/lsp.ts +1 -1
  13. package/src/lsp/server-web.ts +1 -1
  14. package/src/mcp/oauth-callback.ts +1 -1
  15. package/src/{saeeol → overlay}/encoding.ts +1 -1
  16. package/src/{saeeol → overlay}/text-stream.ts +1 -1
  17. package/src/{saeeol → overlay}/tool/encoded-io.ts +1 -1
  18. package/src/patch/patch-apply.ts +1 -1
  19. package/src/patch/patch-parse.ts +1 -1
  20. package/src/session/compaction-helpers.ts +1 -169
  21. package/src/session/compaction.ts +1 -712
  22. package/src/session/core/compaction/compaction-helpers.ts +169 -0
  23. package/src/session/core/compaction/compaction.ts +712 -0
  24. package/src/session/core/compaction/overflow.ts +28 -0
  25. package/src/session/core/instruction.ts +234 -0
  26. package/src/session/core/llm.ts +504 -0
  27. package/src/session/core/network.ts +392 -0
  28. package/src/session/core/processor.ts +731 -0
  29. package/src/session/core/projectors.ts +139 -0
  30. package/src/session/core/resolve-tools.ts +241 -0
  31. package/src/session/core/retry.ts +149 -0
  32. package/src/session/core/revert.ts +173 -0
  33. package/src/session/core/run-state.ts +110 -0
  34. package/src/session/core/schema.ts +35 -0
  35. package/src/session/core/session-types.ts +160 -0
  36. package/src/session/core/session.sql.ts +124 -0
  37. package/src/session/core/session.ts +948 -0
  38. package/src/session/core/shell-exec.ts +205 -0
  39. package/src/session/core/status.ts +100 -0
  40. package/src/session/core/subtask.ts +268 -0
  41. package/src/session/core/summary.ts +173 -0
  42. package/src/session/core/system.ts +114 -0
  43. package/src/session/core/todo.ts +86 -0
  44. package/src/session/core/user-part.ts +293 -0
  45. package/src/session/instruction.ts +1 -234
  46. package/src/session/llm.ts +1 -504
  47. package/src/session/message/message-errors.ts +83 -0
  48. package/src/session/message/message-parts.ts +89 -0
  49. package/src/session/message/message-query.ts +107 -0
  50. package/src/session/message/message-transform.ts +156 -0
  51. package/src/session/message/message-types.ts +68 -0
  52. package/src/session/message/message-v2.ts +73 -0
  53. package/src/session/message/message.ts +192 -0
  54. package/src/session/message-errors.ts +1 -83
  55. package/src/session/message-parts.ts +1 -89
  56. package/src/session/message-query.ts +1 -107
  57. package/src/session/message-transform.ts +1 -156
  58. package/src/session/message-types.ts +1 -68
  59. package/src/session/message-v2.ts +1 -73
  60. package/src/session/message.ts +1 -192
  61. package/src/session/network.ts +1 -392
  62. package/src/session/overflow.ts +1 -28
  63. package/src/session/processor.ts +1 -731
  64. package/src/session/projectors.ts +2 -139
  65. package/src/session/prompt/prompt-command.ts +93 -0
  66. package/src/session/prompt/prompt-loop.ts +299 -0
  67. package/src/session/prompt/prompt-model.ts +44 -0
  68. package/src/session/prompt/prompt-reminders.ts +120 -0
  69. package/src/session/prompt/prompt-resolve.ts +42 -0
  70. package/src/session/prompt/prompt-schemas.ts +128 -0
  71. package/src/session/prompt/prompt-title.ts +55 -0
  72. package/src/session/prompt/prompt-types.ts +47 -0
  73. package/src/session/prompt/prompt-user-msg.ts +80 -0
  74. package/src/session/prompt/prompt.ts +211 -0
  75. package/src/session/prompt-command.ts +1 -93
  76. package/src/session/prompt-loop.ts +1 -299
  77. package/src/session/prompt-model.ts +1 -44
  78. package/src/session/prompt-reminders.ts +1 -120
  79. package/src/session/prompt-resolve.ts +1 -42
  80. package/src/session/prompt-schemas.ts +1 -128
  81. package/src/session/prompt-title.ts +1 -55
  82. package/src/session/prompt-types.ts +1 -47
  83. package/src/session/prompt-user-msg.ts +1 -80
  84. package/src/session/prompt.ts +1 -211
  85. package/src/session/resolve-tools.ts +1 -241
  86. package/src/session/retry.ts +1 -149
  87. package/src/session/revert.ts +1 -173
  88. package/src/session/run-state.ts +1 -110
  89. package/src/session/schema.ts +1 -35
  90. package/src/session/session-types.ts +1 -160
  91. package/src/session/session.sql.ts +1 -124
  92. package/src/session/session.ts +1 -948
  93. package/src/session/shell-exec.ts +1 -205
  94. package/src/session/status.ts +1 -100
  95. package/src/session/subtask.ts +1 -268
  96. package/src/session/summary.ts +1 -173
  97. package/src/session/system.ts +1 -114
  98. package/src/session/todo.ts +1 -86
  99. package/src/session/user-part.ts +1 -293
  100. package/src/skill/index.ts +1 -1
  101. package/src/suggestion/index.ts +1 -1
  102. package/src/tool/apply_patch.ts +2 -2
  103. package/src/tool/edit.ts +2 -2
  104. package/src/tool/read.ts +2 -2
  105. package/src/tool/recall.ts +1 -1
  106. package/src/tool/registry.ts +2 -2
  107. package/src/tool/suggest.ts +1 -1
  108. package/src/tool/task.ts +3 -3
  109. package/src/tool/todo.ts +1 -1
  110. package/src/tool/write.ts +2 -2
  111. package/test/lsp/index.test.ts +1 -1
  112. package/test/saeeol/agent-manager-tool.test.ts +1 -1
  113. package/test/saeeol/ask-agent-permissions.test.ts +1 -1
  114. package/test/saeeol/bash-hierarchy.test.ts +1 -1
  115. package/test/saeeol/builtin-skills.test.ts +1 -1
  116. package/test/saeeol/cli/dev-setup.test.ts +1 -1
  117. package/test/saeeol/cli/roll-call.test.ts +1 -1
  118. package/test/saeeol/cli-run-auto-helper.test.ts +1 -1
  119. package/test/saeeol/codex-auth-refresh.test.ts +1 -1
  120. package/test/saeeol/commit-message/generate.test.ts +1 -1
  121. package/test/saeeol/commit-message/git-context.test.ts +1 -1
  122. package/test/saeeol/commit-message-windows.test.ts +1 -1
  123. package/test/saeeol/compaction-payload-recovery.test.ts +1 -1
  124. package/test/saeeol/config/config.test.ts +1 -1
  125. package/test/saeeol/config-injector.test.ts +1 -1
  126. package/test/saeeol/config-validation.test.ts +1 -1
  127. package/test/saeeol/cost-propagation.test.ts +1 -1
  128. package/test/saeeol/custom-provider-delete.test.ts +1 -1
  129. package/test/saeeol/diff-full.test.ts +1 -1
  130. package/test/saeeol/encoding.test.ts +1 -1
  131. package/test/saeeol/enhance-prompt.test.ts +1 -1
  132. package/test/saeeol/ensure-plan-dir.test.ts +1 -1
  133. package/test/saeeol/errors.test.ts +1 -1
  134. package/test/saeeol/help.test.ts +3 -3
  135. package/test/saeeol/ignore-migrator.test.ts +1 -1
  136. package/test/saeeol/indexing-auth.test.ts +1 -1
  137. package/test/saeeol/indexing-feature.test.ts +1 -1
  138. package/test/saeeol/indexing-label.test.ts +1 -1
  139. package/test/saeeol/indexing-startup.test.ts +1 -1
  140. package/test/saeeol/indexing-worktree.test.ts +1 -1
  141. package/test/saeeol/lancedb-runtime.test.ts +9 -9
  142. package/test/saeeol/logo.test.ts +1 -1
  143. package/test/saeeol/lsp-typescript-lightweight.test.ts +2 -2
  144. package/test/saeeol/mcp-migrator.test.ts +1 -1
  145. package/test/saeeol/model-info-panel-utils.test.ts +1 -1
  146. package/test/saeeol/modes-migrator.test.ts +1 -1
  147. package/test/saeeol/paths.test.ts +1 -1
  148. package/test/saeeol/permission/config-paths.test.ts +2 -2
  149. package/test/saeeol/permission/external-directory-allow.test.ts +1 -1
  150. package/test/saeeol/plan-exit-detection.test.ts +1 -1
  151. package/test/saeeol/plan-followup.test.ts +2 -2
  152. package/test/saeeol/project-id.test.ts +1 -1
  153. package/test/saeeol/question-dismiss-all.test.ts +1 -1
  154. package/test/saeeol/rules-migrator.test.ts +1 -1
  155. package/test/saeeol/semantic-search.test.ts +2 -2
  156. package/test/saeeol/session/platform-attribution.test.ts +1 -1
  157. package/test/saeeol/session-compaction-cap.test.ts +2 -2
  158. package/test/saeeol/session-compaction-chunks.test.ts +1 -1
  159. package/test/saeeol/session-compaction-safety.test.ts +1 -1
  160. package/test/saeeol/session-import-service.test.ts +1 -1
  161. package/test/saeeol/session-processor-empty-tool-calls.test.ts +1 -1
  162. package/test/saeeol/session-processor-review-telemetry.test.ts +1 -1
  163. package/test/saeeol/session-prompt-queue.test.ts +1 -1
  164. package/test/saeeol/sessions/remote-sender.test.ts +1 -1
  165. package/test/saeeol/snapshot-track-timeout.test.ts +1 -1
  166. package/test/saeeol/suggestion/auto-dismiss.test.ts +2 -2
  167. package/test/saeeol/suggestion/suggestion.test.ts +1 -1
  168. package/test/saeeol/suggestion/tool.test.ts +2 -2
  169. package/test/saeeol/system-prompt.test.ts +1 -1
  170. package/test/saeeol/todo-view.test.ts +1 -1
  171. package/test/saeeol/tool-registry-indexing.test.ts +3 -3
  172. package/test/saeeol/tui-diff.test.ts +1 -1
  173. package/test/saeeol/tui-sync.test.ts +1 -1
  174. package/test/saeeol/util/url.test.ts +1 -1
  175. package/test/saeeol/workflows-migrator.test.ts +1 -1
  176. package/test/saeeol/worktree-diff-summary.test.ts +1 -1
  177. package/tsconfig.json +1 -1
  178. /package/src/{saeeol → overlay}/agent/agent-remove.ts +0 -0
  179. /package/src/{saeeol → overlay}/agent/index.ts +0 -0
  180. /package/src/{saeeol → overlay}/agent/patch-agents.ts +0 -0
  181. /package/src/{saeeol → overlay}/agent/permissions.ts +0 -0
  182. /package/src/{saeeol → overlay}/agent-manager/event.ts +0 -0
  183. /package/src/{saeeol → overlay}/bash-hierarchy.ts +0 -0
  184. /package/src/{saeeol → overlay}/bell.ts +0 -0
  185. /package/src/{saeeol → overlay}/bootstrap.ts +0 -0
  186. /package/src/{saeeol → overlay}/claw/autocomplete-popup.tsx +0 -0
  187. /package/src/{saeeol → overlay}/claw/autocomplete-ref.ts +0 -0
  188. /package/src/{saeeol → overlay}/claw/autocomplete-types.ts +0 -0
  189. /package/src/{saeeol → overlay}/claw/autocomplete.tsx +0 -0
  190. /package/src/{saeeol → overlay}/claw/chat-client.ts +0 -0
  191. /package/src/{saeeol → overlay}/claw/chat.tsx +0 -0
  192. /package/src/{saeeol → overlay}/claw/client-events.ts +0 -0
  193. /package/src/{saeeol → overlay}/claw/client-helpers.ts +0 -0
  194. /package/src/{saeeol → overlay}/claw/client.ts +0 -0
  195. /package/src/{saeeol → overlay}/claw/dialog-conversation-list.tsx +0 -0
  196. /package/src/{saeeol → overlay}/claw/event-service-client-core.ts +0 -0
  197. /package/src/{saeeol → overlay}/claw/event-service-client-impl.ts +0 -0
  198. /package/src/{saeeol → overlay}/claw/event-service-client.ts +0 -0
  199. /package/src/{saeeol → overlay}/claw/hooks.ts +0 -0
  200. /package/src/{saeeol → overlay}/claw/sidebar.tsx +0 -0
  201. /package/src/{saeeol → overlay}/claw/types.ts +0 -0
  202. /package/src/{saeeol → overlay}/claw/view.tsx +0 -0
  203. /package/src/{saeeol → overlay}/cli/cmd/roll-call-call.ts +0 -0
  204. /package/src/{saeeol → overlay}/cli/cmd/roll-call-format.ts +0 -0
  205. /package/src/{saeeol → overlay}/cli/cmd/roll-call.ts +0 -0
  206. /package/src/{saeeol → overlay}/cli/cmd/tui/app.tsx +0 -0
  207. /package/src/{saeeol → overlay}/cli/cmd/tui/component/dialog-provider.tsx +0 -0
  208. /package/src/{saeeol → overlay}/cli/cmd/tui/feedback.ts +0 -0
  209. /package/src/{saeeol → overlay}/cli/cmd/tui/util/terminal.ts +0 -0
  210. /package/src/{saeeol → overlay}/cli/dev-setup-utils.ts +0 -0
  211. /package/src/{saeeol → overlay}/cli/dev-setup.ts +0 -0
  212. /package/src/{saeeol → overlay}/cli/heap-snapshot.ts +0 -0
  213. /package/src/{saeeol → overlay}/cli/logo.ts +0 -0
  214. /package/src/{saeeol → overlay}/cli/run-auto.ts +0 -0
  215. /package/src/{saeeol → overlay}/cli/saeeol-logo.ts +0 -0
  216. /package/src/{saeeol → overlay}/cloud-session.ts +0 -0
  217. /package/src/{saeeol → overlay}/commands-index.ts +0 -0
  218. /package/src/{saeeol → overlay}/commands.ts +0 -0
  219. /package/src/{saeeol → overlay}/commands.tsx +0 -0
  220. /package/src/{saeeol → overlay}/commit-message/generate.ts +0 -0
  221. /package/src/{saeeol → overlay}/commit-message/git-context.ts +0 -0
  222. /package/src/{saeeol → overlay}/commit-message/index.ts +0 -0
  223. /package/src/{saeeol → overlay}/commit-message/types.ts +0 -0
  224. /package/src/{saeeol → overlay}/components/dialog-auto-method.tsx +0 -0
  225. /package/src/{saeeol → overlay}/components/dialog-claw-setup.tsx +0 -0
  226. /package/src/{saeeol → overlay}/components/dialog-claw-upgrade.tsx +0 -0
  227. /package/src/{saeeol → overlay}/components/dialog-indexing-helpers.ts +0 -0
  228. /package/src/{saeeol → overlay}/components/dialog-indexing.tsx +0 -0
  229. /package/src/{saeeol → overlay}/components/dialog-notifications.tsx +0 -0
  230. /package/src/{saeeol → overlay}/components/dialog-organization.tsx +0 -0
  231. /package/src/{saeeol → overlay}/components/dialog-profile.tsx +0 -0
  232. /package/src/{saeeol → overlay}/components/dialog-provider-settings.tsx +0 -0
  233. /package/src/{saeeol → overlay}/components/dialog-team-select.tsx +0 -0
  234. /package/src/{saeeol → overlay}/components/dialog-tuning.tsx +0 -0
  235. /package/src/{saeeol → overlay}/components/dialog-vector-store.tsx +0 -0
  236. /package/src/{saeeol → overlay}/components/error-display.tsx +0 -0
  237. /package/src/{saeeol → overlay}/components/model-info-panel-utils.ts +0 -0
  238. /package/src/{saeeol → overlay}/components/model-info-panel.tsx +0 -0
  239. /package/src/{saeeol → overlay}/components/news.tsx +0 -0
  240. /package/src/{saeeol → overlay}/components/notification-banner.tsx +0 -0
  241. /package/src/{saeeol → overlay}/components/session-indexing.tsx +0 -0
  242. /package/src/{saeeol → overlay}/components/tips.tsx +0 -0
  243. /package/src/{saeeol → overlay}/config/config-core.ts +0 -0
  244. /package/src/{saeeol → overlay}/config/config-helpers.ts +0 -0
  245. /package/src/{saeeol → overlay}/config/config-legacy.ts +0 -0
  246. /package/src/{saeeol → overlay}/config/config.ts +0 -0
  247. /package/src/{saeeol → overlay}/config/default-plugins.ts +0 -0
  248. /package/src/{saeeol → overlay}/config/markdown.ts +0 -0
  249. /package/src/{saeeol → overlay}/config-injector.ts +0 -0
  250. /package/src/{saeeol → overlay}/config-validation.ts +0 -0
  251. /package/src/{saeeol → overlay}/const.ts +0 -0
  252. /package/src/{saeeol → overlay}/cost-tracker/format.ts +0 -0
  253. /package/src/{saeeol → overlay}/cost-tracker/index.ts +0 -0
  254. /package/src/{saeeol → overlay}/cost-tracker/state.ts +0 -0
  255. /package/src/{saeeol → overlay}/cost-tracker/types.ts +0 -0
  256. /package/src/{saeeol → overlay}/docs/migration.md +0 -0
  257. /package/src/{saeeol → overlay}/docs/rules-migration.md +0 -0
  258. /package/src/{saeeol → overlay}/editor-context.ts +0 -0
  259. /package/src/{saeeol → overlay}/enhance-prompt.ts +0 -0
  260. /package/src/{saeeol → overlay}/errors.ts +0 -0
  261. /package/src/{saeeol → overlay}/generate-cli-docs.ts +0 -0
  262. /package/src/{saeeol → overlay}/help-command.ts +0 -0
  263. /package/src/{saeeol → overlay}/help.ts +0 -0
  264. /package/src/{saeeol → overlay}/i18n/index.ts +0 -0
  265. /package/src/{saeeol → overlay}/ignore-migrator.ts +0 -0
  266. /package/src/{saeeol → overlay}/index.ts +0 -0
  267. /package/src/{saeeol → overlay}/indexing-auth.ts +0 -0
  268. /package/src/{saeeol → overlay}/indexing-feature.ts +0 -0
  269. /package/src/{saeeol → overlay}/indexing-helpers.ts +0 -0
  270. /package/src/{saeeol → overlay}/indexing-label.ts +0 -0
  271. /package/src/{saeeol → overlay}/indexing-state.ts +0 -0
  272. /package/src/{saeeol → overlay}/indexing-types.ts +0 -0
  273. /package/src/{saeeol → overlay}/indexing.ts +0 -0
  274. /package/src/{saeeol → overlay}/lancedb.ts +0 -0
  275. /package/src/{saeeol → overlay}/mcp-migrator.ts +0 -0
  276. /package/src/{saeeol → overlay}/mcp-oauth-callback.ts +0 -0
  277. /package/src/{saeeol → overlay}/memory/age.ts +0 -0
  278. /package/src/{saeeol → overlay}/memory/index.ts +0 -0
  279. /package/src/{saeeol → overlay}/memory/paths.ts +0 -0
  280. /package/src/{saeeol → overlay}/memory/scan.ts +0 -0
  281. /package/src/{saeeol → overlay}/memory/types.ts +0 -0
  282. /package/src/{saeeol → overlay}/model-match.ts +0 -0
  283. /package/src/{saeeol → overlay}/modes-migrator.ts +0 -0
  284. /package/src/{saeeol → overlay}/paths.ts +0 -0
  285. /package/src/{saeeol → overlay}/permission/config-paths.ts +0 -0
  286. /package/src/{saeeol → overlay}/permission/drain.ts +0 -0
  287. /package/src/{saeeol → overlay}/permission/external-directory.ts +0 -0
  288. /package/src/{saeeol → overlay}/permission/read.ts +0 -0
  289. /package/src/{saeeol → overlay}/permission/routes.ts +0 -0
  290. /package/src/{saeeol → overlay}/permission/rule.ts +0 -0
  291. /package/src/{saeeol → overlay}/plan-followup-handover.ts +0 -0
  292. /package/src/{saeeol → overlay}/plan-followup-runtime.ts +0 -0
  293. /package/src/{saeeol → overlay}/plan-followup-session.ts +0 -0
  294. /package/src/{saeeol → overlay}/plan-followup.ts +0 -0
  295. /package/src/{saeeol → overlay}/plugins/home-footer.tsx +0 -0
  296. /package/src/{saeeol → overlay}/plugins/home-news.tsx +0 -0
  297. /package/src/{saeeol → overlay}/plugins/home-onboarding.tsx +0 -0
  298. /package/src/{saeeol → overlay}/plugins/sidebar-footer.tsx +0 -0
  299. /package/src/{saeeol → overlay}/plugins/sidebar-pr.tsx +0 -0
  300. /package/src/{saeeol → overlay}/plugins/sidebar-usage.tsx +0 -0
  301. /package/src/{saeeol → overlay}/project-id.ts +0 -0
  302. /package/src/{saeeol → overlay}/provider/codex-refresh.ts +0 -0
  303. /package/src/{saeeol → overlay}/provider/provider.ts +0 -0
  304. /package/src/{saeeol → overlay}/provider-options.ts +0 -0
  305. /package/src/{saeeol → overlay}/question/index.ts +0 -0
  306. /package/src/{saeeol → overlay}/remote-tui.tsx +0 -0
  307. /package/src/{saeeol → overlay}/review/command.ts +0 -0
  308. /package/src/{saeeol → overlay}/review/diff.ts +0 -0
  309. /package/src/{saeeol → overlay}/review/prompt.ts +0 -0
  310. /package/src/{saeeol → overlay}/review/review.ts +0 -0
  311. /package/src/{saeeol → overlay}/review/types.ts +0 -0
  312. /package/src/{saeeol → overlay}/review/worktree-diff.ts +0 -0
  313. /package/src/{saeeol → overlay}/rules-migrator.ts +0 -0
  314. /package/src/{saeeol → overlay}/server/instance.ts +0 -0
  315. /package/src/{saeeol → overlay}/server/router.ts +0 -0
  316. /package/src/{saeeol → overlay}/server/routes/commit-message.ts +0 -0
  317. /package/src/{saeeol → overlay}/server/routes/indexing.ts +0 -0
  318. /package/src/{saeeol → overlay}/server/server.ts +0 -0
  319. /package/src/{saeeol → overlay}/session/compaction-chunks-core.ts +0 -0
  320. /package/src/{saeeol → overlay}/session/compaction-chunks-utils.ts +0 -0
  321. /package/src/{saeeol → overlay}/session/compaction-chunks.ts +0 -0
  322. /package/src/{saeeol → overlay}/session/compaction-payload-recovery.ts +0 -0
  323. /package/src/{saeeol → overlay}/session/cost-propagation.ts +0 -0
  324. /package/src/{saeeol → overlay}/session/digest-storage.ts +0 -0
  325. /package/src/{saeeol → overlay}/session/fork.ts +0 -0
  326. /package/src/{saeeol → overlay}/session/index.ts +0 -0
  327. /package/src/{saeeol → overlay}/session/instruction.ts +0 -0
  328. /package/src/{saeeol → overlay}/session/kg-compaction.ts +0 -0
  329. /package/src/{saeeol → overlay}/session/llm.ts +0 -0
  330. /package/src/{saeeol → overlay}/session/overflow.ts +0 -0
  331. /package/src/{saeeol → overlay}/session/platform.ts +0 -0
  332. /package/src/{saeeol → overlay}/session/processor.ts +0 -0
  333. /package/src/{saeeol → overlay}/session/prompt-context.ts +0 -0
  334. /package/src/{saeeol → overlay}/session/prompt-plan.ts +0 -0
  335. /package/src/{saeeol → overlay}/session/prompt-queue.ts +0 -0
  336. /package/src/{saeeol → overlay}/session/prompt.ts +0 -0
  337. /package/src/{saeeol → overlay}/session/queries.ts +0 -0
  338. /package/src/{saeeol → overlay}/session/tui-sync.ts +0 -0
  339. /package/src/{saeeol → overlay}/session-import/routes.ts +0 -0
  340. /package/src/{saeeol → overlay}/session-import/service.ts +0 -0
  341. /package/src/{saeeol → overlay}/session-import/types.ts +0 -0
  342. /package/src/{saeeol → overlay}/skills/builtin.ts +0 -0
  343. /package/src/{saeeol → overlay}/skills/config.md +0 -0
  344. /package/src/{saeeol → overlay}/snapshot/diff-full.ts +0 -0
  345. /package/src/{saeeol → overlay}/snapshot/index.ts +0 -0
  346. /package/src/{saeeol → overlay}/snapshot/track-hooks.ts +0 -0
  347. /package/src/{saeeol → overlay}/snapshot/track-types.ts +0 -0
  348. /package/src/{saeeol → overlay}/snapshot/track.ts +0 -0
  349. /package/src/{saeeol → overlay}/soul.txt +0 -0
  350. /package/src/{saeeol → overlay}/suggestion/index.ts +0 -0
  351. /package/src/{saeeol → overlay}/suggestion/routes.ts +0 -0
  352. /package/src/{saeeol → overlay}/suggestion/tool.ts +0 -0
  353. /package/src/{saeeol → overlay}/suggestion/tool.txt +0 -0
  354. /package/src/{saeeol → overlay}/suggestion/tui/bar.tsx +0 -0
  355. /package/src/{saeeol → overlay}/suggestion/tui/prompt.tsx +0 -0
  356. /package/src/{saeeol → overlay}/suggestion/tui/render.tsx +0 -0
  357. /package/src/{saeeol → overlay}/suggestion/tui/sync.ts +0 -0
  358. /package/src/{saeeol → overlay}/system-prompt.ts +0 -0
  359. /package/src/{saeeol → overlay}/todo-view.ts +0 -0
  360. /package/src/{saeeol → overlay}/tool/agent-manager.ts +0 -0
  361. /package/src/{saeeol → overlay}/tool/agent-manager.txt +0 -0
  362. /package/src/{saeeol → overlay}/tool/bash-security-patterns.ts +0 -0
  363. /package/src/{saeeol → overlay}/tool/bash-security.ts +0 -0
  364. /package/src/{saeeol → overlay}/tool/question.ts +0 -0
  365. /package/src/{saeeol → overlay}/tool/registry.ts +0 -0
  366. /package/src/{saeeol → overlay}/tool/semantic-search.ts +0 -0
  367. /package/src/{saeeol → overlay}/tool/semantic-search.txt +0 -0
  368. /package/src/{saeeol → overlay}/tool/task.ts +0 -0
  369. /package/src/{saeeol → overlay}/ts-check.ts +0 -0
  370. /package/src/{saeeol → overlay}/ts-client.ts +0 -0
  371. /package/src/{saeeol → overlay}/tui/diff.ts +0 -0
  372. /package/src/{saeeol → overlay}/util/url.ts +0 -0
  373. /package/src/{saeeol → overlay}/worker/index.ts +0 -0
  374. /package/src/{saeeol → overlay}/worker/pool.ts +0 -0
  375. /package/src/{saeeol → overlay}/worker/store.ts +0 -0
  376. /package/src/{saeeol → overlay}/worker/types.ts +0 -0
  377. /package/src/{saeeol → overlay}/workflows-migrator.ts +0 -0
  378. /package/src/{saeeol → overlay}/worktree-cleanup.ts +0 -0
  379. /package/src/{saeeol → overlay}/worktree-family.ts +0 -0
@@ -1,139 +1,2 @@
1
- import { NotFoundError } from "@/storage/storage"
2
- import { eq } from "drizzle-orm"
3
- import { and } from "drizzle-orm"
4
- import { SyncEvent } from "@/sync"
5
- import * as Session from "./session"
6
- import { MessageV2 } from "./message-v2"
7
- import { SessionTable, MessageTable, PartTable } from "./session.sql"
8
- import * as Log from "@saeeol/core/util/log"
9
-
10
- const log = Log.create({ service: "session.projector" })
11
-
12
- function foreign(err: unknown) {
13
- if (typeof err !== "object" || err === null) return false
14
- if ("code" in err && err.code === "SQLITE_CONSTRAINT_FOREIGNKEY") return true
15
- return "message" in err && typeof err.message === "string" && err.message.includes("FOREIGN KEY constraint failed")
16
- }
17
-
18
- export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> | null } : T
19
-
20
- function grab<T extends object, K1 extends keyof T, X>(
21
- obj: T,
22
- field1: K1,
23
- cb?: (val: NonNullable<T[K1]>) => X,
24
- ): X | undefined {
25
- if (obj == undefined || !(field1 in obj)) return undefined
26
-
27
- const val = obj[field1]
28
- if (val && typeof val === "object" && cb) {
29
- return cb(val)
30
- }
31
- if (val === undefined) {
32
- throw new Error(
33
- "Session update failure: pass `null` to clear a field instead of `undefined`: " + JSON.stringify(obj),
34
- )
35
- }
36
- return val as X | undefined
37
- }
38
-
39
- export function toPartialRow(info: DeepPartial<Session.Info>) {
40
- const obj = {
41
- id: grab(info, "id"),
42
- project_id: grab(info, "projectID"),
43
- workspace_id: grab(info, "workspaceID"),
44
- parent_id: grab(info, "parentID"),
45
- slug: grab(info, "slug"),
46
- directory: grab(info, "directory"),
47
- path: grab(info, "path"),
48
- title: grab(info, "title"),
49
- version: grab(info, "version"),
50
- share_url: grab(info, "share", (v) => grab(v, "url")),
51
- summary_additions: grab(info, "summary", (v) => grab(v, "additions")),
52
- summary_deletions: grab(info, "summary", (v) => grab(v, "deletions")),
53
- summary_files: grab(info, "summary", (v) => grab(v, "files")),
54
- summary_diffs: grab(info, "summary", (v) => grab(v, "diffs")),
55
- revert: grab(info, "revert"),
56
- permission: grab(info, "permission"),
57
- time_created: grab(info, "time", (v) => grab(v, "created")),
58
- time_updated: grab(info, "time", (v) => grab(v, "updated")),
59
- time_compacting: grab(info, "time", (v) => grab(v, "compacting")),
60
- time_archived: grab(info, "time", (v) => grab(v, "archived")),
61
- }
62
-
63
- return Object.fromEntries(Object.entries(obj).filter(([_, val]) => val !== undefined))
64
- }
65
-
66
- export default [
67
- SyncEvent.project(Session.Event.Created, (db, data) => {
68
- db.insert(SessionTable)
69
- .values(Session.toRow(data.info as Session.Info))
70
- .run()
71
- }),
72
-
73
- SyncEvent.project(Session.Event.Updated, (db, data) => {
74
- const info = data.info
75
- const row = db
76
- .update(SessionTable)
77
- .set(toPartialRow(info as Session.Patch))
78
- .where(eq(SessionTable.id, data.sessionID))
79
- .returning()
80
- .get()
81
- if (!row) throw new NotFoundError({ message: `Session not found: ${data.sessionID}` })
82
- }),
83
-
84
- SyncEvent.project(Session.Event.Deleted, (db, data) => {
85
- db.delete(SessionTable).where(eq(SessionTable.id, data.sessionID)).run()
86
- }),
87
-
88
- SyncEvent.project(MessageV2.Event.Updated, (db, data) => {
89
- const time_created = data.info.time.created
90
- const { id, sessionID, ...rest } = data.info
91
-
92
- try {
93
- db.insert(MessageTable)
94
- .values({
95
- id,
96
- session_id: sessionID,
97
- time_created,
98
- data: rest,
99
- })
100
- .onConflictDoUpdate({ target: MessageTable.id, set: { data: rest } })
101
- .run()
102
- } catch (err) {
103
- if (!foreign(err)) throw err
104
- log.warn("ignored late message update", { messageID: id, sessionID })
105
- }
106
- }),
107
-
108
- SyncEvent.project(MessageV2.Event.Removed, (db, data) => {
109
- db.delete(MessageTable)
110
- .where(and(eq(MessageTable.id, data.messageID), eq(MessageTable.session_id, data.sessionID)))
111
- .run()
112
- }),
113
-
114
- SyncEvent.project(MessageV2.Event.PartRemoved, (db, data) => {
115
- db.delete(PartTable)
116
- .where(and(eq(PartTable.id, data.partID), eq(PartTable.session_id, data.sessionID)))
117
- .run()
118
- }),
119
-
120
- SyncEvent.project(MessageV2.Event.PartUpdated, (db, data) => {
121
- const { id, messageID, sessionID, ...rest } = data.part
122
-
123
- try {
124
- db.insert(PartTable)
125
- .values({
126
- id,
127
- message_id: messageID,
128
- session_id: sessionID,
129
- time_created: data.time,
130
- data: rest,
131
- })
132
- .onConflictDoUpdate({ target: PartTable.id, set: { data: rest } })
133
- .run()
134
- } catch (err) {
135
- if (!foreign(err)) throw err
136
- log.warn("ignored late part update", { partID: id, messageID, sessionID })
137
- }
138
- }),
139
- ]
1
+ export * from "./core/projectors"
2
+ export { default } from "./core/projectors"
@@ -0,0 +1,93 @@
1
+ import { Effect } from "effect"
2
+ import { NamedError } from "@saeeol/core/util/error"
3
+ import * as EffectLogger from "@saeeol/core/effect/logger"
4
+ import { ConfigMarkdown } from "@/config/markdown"
5
+ import { Shell } from "@/shell/shell"
6
+ import { Provider } from "@/provider/provider"
7
+ import { Process } from "@/util/process"
8
+ import { SessionID } from "../core/schema"
9
+ import * as Session from "../core/session"
10
+ import { resolvePromptParts } from "./prompt-resolve"
11
+ import { getModel, lastModel } from "./prompt-model"
12
+ import type { CommandInput } from "./prompt-schemas"
13
+ import type { PromptDeps } from "./prompt-types"
14
+
15
+ const elog = EffectLogger.create({ service: "session.prompt" })
16
+
17
+ export const commandHandler = (deps: PromptDeps, markReviewTelemetry?: (parts: any, cmd: string) => void) => {
18
+ const resolveParts = resolvePromptParts(deps)
19
+
20
+ return Effect.fn("SessionPrompt.command")(function* (input: CommandInput) {
21
+ yield* elog.info("command", { sessionID: input.sessionID, command: input.command, agent: input.agent })
22
+ const cmd = yield* deps.commands.get(input.command)
23
+ if (!cmd) {
24
+ const available = (yield* deps.commands.list()).map((c: any) => c.name)
25
+ const hint = available.length ? ` Available commands: ${available.join(", ")}` : ""
26
+ const error = new NamedError.Unknown({ message: `Command not found: "${input.command}".${hint}` })
27
+ yield* deps.bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
28
+ throw error
29
+ }
30
+ const argsRegex = /\S+|"[^"]*"|'[^']*'/g
31
+ const quoteTrimRegex = /^["']|["']$/g
32
+ const placeholderRegex = /\$(\d+)/g
33
+ const agentName = cmd.agent ?? input.agent ?? (yield* deps.agents.defaultAgent())
34
+ const raw = input.arguments.match(argsRegex) ?? []
35
+ const args = raw.map((arg: string) => arg.replace(quoteTrimRegex, ""))
36
+ const templateCommand = yield* Effect.promise(async () => cmd.template)
37
+ const placeholders = templateCommand.match(placeholderRegex) ?? []
38
+ let last = 0
39
+ for (const item of placeholders) { const value = Number(item.slice(1)); if (value > last) last = value }
40
+ const withArgs = templateCommand.replaceAll(placeholderRegex, (_: string, index: string) => {
41
+ const position = Number(index)
42
+ const argIndex = position - 1
43
+ if (argIndex >= args.length) return ""
44
+ if (position === last) return args.slice(argIndex).join(" ")
45
+ return args[argIndex]
46
+ })
47
+ const usesArgumentsPlaceholder = templateCommand.includes("$ARGUMENTS")
48
+ let template = withArgs.replaceAll("$ARGUMENTS", input.arguments)
49
+ if (placeholders.length === 0 && !usesArgumentsPlaceholder && input.arguments.trim()) {
50
+ template = template + "\n\n" + input.arguments
51
+ }
52
+ const bashRegex = /```bash\n([\s\S]*?)```/g
53
+ const shellMatches = ConfigMarkdown.shell(template)
54
+ if (shellMatches.length > 0) {
55
+ const cfg = yield* deps.config.get()
56
+ const sh = Shell.preferred(cfg.shell)
57
+ const results = yield* Effect.promise(() =>
58
+ Promise.all(shellMatches.map(async (match: RegExpExecArray) => (await Process.text([match[1]], { shell: sh, nothrow: true })).text)),
59
+ )
60
+ let index = 0
61
+ template = template.replace(bashRegex, () => results[index++])
62
+ }
63
+ template = template.trim()
64
+ const taskModel = yield* Effect.gen(function* () {
65
+ if (cmd.model) return Provider.parseModel(cmd.model)
66
+ if (cmd.agent) { const cmdAgent = yield* deps.agents.get(cmd.agent); if (cmdAgent?.model) return cmdAgent.model }
67
+ if (input.model) return Provider.parseModel(input.model)
68
+ return yield* lastModel(deps)(input.sessionID)
69
+ })
70
+ yield* getModel(deps)(taskModel.providerID, taskModel.modelID, input.sessionID)
71
+ const agent = yield* deps.agents.get(agentName)
72
+ if (!agent) {
73
+ const available = (yield* deps.agents.list()).filter((a: any) => !a.hidden).map((a: any) => a.name)
74
+ const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
75
+ const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
76
+ yield* deps.bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
77
+ throw error
78
+ }
79
+ const templateParts = yield* resolveParts(template)
80
+ markReviewTelemetry?.(templateParts, input.command)
81
+ const isSubtask = (agent.mode === "subagent" && cmd.subtask !== false) || cmd.subtask === true
82
+ const parts = isSubtask
83
+ ? [{ type: "subtask" as const, agent: agent.name, description: cmd.description ?? "",
84
+ command: input.command, model: { providerID: taskModel.providerID, modelID: taskModel.modelID },
85
+ prompt: (templateParts.find((y: any) => y.type === "text") as any)?.text ?? "" }]
86
+ : [...templateParts, ...(input.parts ?? [])]
87
+ const userAgent = isSubtask ? (input.agent ?? (yield* deps.agents.defaultAgent())) : agentName
88
+ const userModel = isSubtask ? input.model ? Provider.parseModel(input.model) : yield* lastModel(deps)(input.sessionID) : taskModel
89
+ yield* deps.plugin.trigger("command.execute.before",
90
+ { command: input.command, sessionID: input.sessionID, arguments: input.arguments }, { parts })
91
+ return { sessionID: input.sessionID, messageID: input.messageID, model: userModel, agent: userAgent, parts, variant: input.variant, command: input.command, arguments: input.arguments }
92
+ })
93
+ }
@@ -0,0 +1,299 @@
1
+ import { Effect, Latch, Option } from "effect"
2
+ import * as EffectLogger from "@saeeol/core/effect/logger"
3
+ import * as Log from "@saeeol/core/util/log"
4
+ import { SessionID, MessageID } from "../core/schema"
5
+ import { MessageV2 } from "../message/message-v2"
6
+ import * as Session from "../core/session"
7
+ import { SaeeolSession } from "@/saeeol/session"
8
+ import { SaeeolSessionPrompt } from "@/saeeol/session/prompt"
9
+ import { SaeeolSessionPromptQueue } from "@/saeeol/session/prompt-queue"
10
+ import { SaeeolSessionProcessor } from "@/saeeol/session/processor"
11
+ import { InstanceState } from "@/effect/instance-state"
12
+ import { EffectBridge } from "@/effect/bridge"
13
+ import { type TaskPromptOps } from "@/tool/task"
14
+ import { NamedError } from "@saeeol/core/util/error"
15
+ import MAX_STEPS from "./max-steps.txt"
16
+ import { type PromptInput, type ShellInput, LoopInput, STRUCTURED_OUTPUT_SYSTEM_PROMPT, REQUEST_PRUNE_BYTES, createStructuredOutputTool } from "./prompt-schemas"
17
+ import type { PromptDeps } from "./prompt-types"
18
+ const elog = EffectLogger.create({ service: "session.prompt" })
19
+ const log = Log.create({ service: "session.prompt" })
20
+
21
+ export interface LoopHelpers {
22
+ getModel: (...args: any[]) => any
23
+ lastModel: (...args: any[]) => any
24
+ lastAssistant: (...args: any[]) => any
25
+ createUserMessage: (...args: any[]) => any
26
+ ensureTitle: (...args: any[]) => any
27
+ insertReminders: (...args: any[]) => any
28
+ resolvePromptParts: (...args: any[]) => any
29
+ commandHandler: (...args: any[]) => any
30
+ prompt: (...args: any[]) => any
31
+ cancel: (...args: any[]) => any
32
+ }
33
+
34
+ export function createRunLoop(deps: PromptDeps, helpers: LoopHelpers) {
35
+ const closeReasons = new Map<string, any>()
36
+ const resolveTools = (input: any) => deps.resolveToolsSvc.resolve(input)
37
+ const runner = Effect.fn("SessionPrompt.runner")(function* () { return yield* EffectBridge.make() })
38
+ const ops = Effect.fn("SessionPrompt.ops")(function* () {
39
+ const run = yield* runner()
40
+ return {
41
+ cancel: (id: SessionID) => run.fork(helpers.cancel(id)),
42
+ resolvePromptParts: (t: string) => helpers.resolvePromptParts(t),
43
+ prompt: (input: PromptInput) => helpers.prompt(input),
44
+ } satisfies TaskPromptOps
45
+ })
46
+ const handleSubtask = (input: any) => Effect.gen(function* () {
47
+ const ops_ = yield* ops()
48
+ return yield* deps.subtaskSvc.handle({ ...input, getModel: helpers.getModel, promptOps: ops_ })
49
+ })
50
+ const shellImpl = (input: ShellInput, ready?: Latch.Latch) =>
51
+ deps.shellSvc.exec(input, helpers.getModel, helpers.lastModel as any, ready)
52
+ const runLoop = Effect.fn("SessionPrompt.run")(function* (sessionID: SessionID) {
53
+ const envCache: SaeeolSessionPrompt.EnvCache = {}
54
+ closeReasons.delete(sessionID)
55
+ let compactionAttempts = 0
56
+ const ctx = yield* InstanceState.context
57
+ const slog = elog.with({ sessionID })
58
+ let structured: unknown | undefined
59
+ let step = 0
60
+ const session = yield* deps.sessions.get(sessionID)
61
+ while (true) {
62
+ yield* deps.status.set(sessionID, { type: "busy" })
63
+ yield* slog.info("loop", { step })
64
+ let msgs: MessageV2.WithParts[] = yield* MessageV2.filterCompactedEffect(sessionID)
65
+ msgs = SaeeolSessionPromptQueue.scope(sessionID, msgs)
66
+ msgs = SaeeolSessionPrompt.trimBeforeLastSummary(msgs)
67
+ let lastUser: MessageV2.User | undefined, lastAssistant: MessageV2.Assistant | undefined, lastFinished: MessageV2.Assistant | undefined
68
+ let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = []
69
+ for (let i = msgs.length - 1; i >= 0; i--) {
70
+ const msg = msgs[i]
71
+ if (!lastUser && msg.info.role === "user") lastUser = msg.info
72
+ if (!lastAssistant && msg.info.role === "assistant") lastAssistant = msg.info
73
+ if (!lastFinished && msg.info.role === "assistant" && msg.info.finish) lastFinished = msg.info
74
+ if (lastUser && lastFinished) break
75
+ const task = msg.parts.filter((part) => part.type === "compaction" || part.type === "subtask")
76
+ if (task && !lastFinished) tasks.push(...task)
77
+ }
78
+ if (!lastUser) throw new Error("No user message found in stream.")
79
+ const telemetry = SaeeolSessionProcessor.extractReviewTelemetry(
80
+ msgs.findLast((m) => m.info.role === "user" && m.info.id === lastUser.id)?.parts ?? [],
81
+ )
82
+ const lastAssistantMsg = msgs.findLast(
83
+ (msg) => msg.info.role === "assistant" && msg.info.id === lastAssistant?.id,
84
+ )
85
+ const hasToolCalls = lastAssistantMsg?.parts.some((part) => part.type === "tool" && !part.metadata?.providerExecuted) ?? false
86
+ if (lastAssistant?.finish && hasToolCalls && lastAssistant.parentID === lastUser.id &&
87
+ lastUser.id < lastAssistant.id &&
88
+ SaeeolSessionPrompt.shouldAskPlanFollowup({ messages: msgs, abort: AbortSignal.any([]) })
89
+ ) {
90
+ const action = yield* Effect.promise((signal) =>
91
+ SaeeolSessionPrompt.askPlanFollowup({ sessionID, messages: msgs, abort: signal }),
92
+ )
93
+ if (action === "continue") continue
94
+ yield* slog.info("exiting loop")
95
+ break
96
+ }
97
+ if (lastAssistant?.finish && !["tool-calls"].includes(lastAssistant.finish) &&
98
+ !hasToolCalls && lastAssistant.parentID === lastUser.id && lastUser.id < lastAssistant.id
99
+ ) {
100
+ const action = yield* Effect.promise((signal) =>
101
+ SaeeolSessionPrompt.askPlanFollowup({ sessionID, messages: msgs, abort: signal }),
102
+ )
103
+ if (action === "continue") continue
104
+ yield* slog.info("exiting loop")
105
+ break
106
+ }
107
+ step++
108
+ if (step === 1)
109
+ yield* helpers.ensureTitle({
110
+ session, modelID: lastUser.model.modelID, providerID: lastUser.model.providerID, history: msgs,
111
+ }).pipe(Effect.ignore, Effect.forkIn(deps.scope))
112
+ const model = yield* helpers.getModel(lastUser.model.providerID, lastUser.model.modelID, sessionID)
113
+ const task = tasks.pop()
114
+ if (task?.type === "subtask") {
115
+ yield* handleSubtask({ task, model, lastUser, sessionID, session, msgs })
116
+ continue
117
+ }
118
+ if (task?.type === "compaction") {
119
+ const result = yield* deps.compaction.process({
120
+ messages: msgs, parentID: lastUser.id, sessionID, auto: task.auto, overflow: task.overflow,
121
+ })
122
+ if (result === "stop") { closeReasons.set(sessionID, "error"); break }
123
+ continue
124
+ }
125
+ if (lastFinished && lastFinished.summary !== true &&
126
+ (yield* deps.compaction.isOverflow({ tokens: lastFinished.tokens, model }))
127
+ ) {
128
+ const guard = SaeeolSessionPrompt.guardCompactionAttempt({
129
+ sessionID, attempts: compactionAttempts, closeReasons, message: lastFinished,
130
+ })
131
+ if (guard.exhausted) {
132
+ yield* deps.sessions.updateMessage(lastFinished)
133
+ yield* deps.bus.publish(Session.Event.Error, { sessionID, error: guard.error })
134
+ break
135
+ }
136
+ compactionAttempts++
137
+ yield* deps.compaction.create({ sessionID, agent: lastUser.agent, model: lastUser.model, auto: true })
138
+ continue
139
+ }
140
+ const agent = yield* deps.agents.get(lastUser.agent)
141
+ if (!agent) {
142
+ const available = (yield* deps.agents.list()).filter((a: any) => !a.hidden).map((a: any) => a.name)
143
+ const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
144
+ const error = new NamedError.Unknown({ message: `Agent not found: "${lastUser.agent}".${hint}` })
145
+ yield* deps.bus.publish(Session.Event.Error, { sessionID, error: error.toObject() })
146
+ throw error
147
+ }
148
+ const maxSteps = agent.steps ?? Infinity
149
+ const isLastStep = step >= maxSteps
150
+ msgs = yield* helpers.insertReminders({ messages: msgs, agent, session })
151
+ const msg: MessageV2.Assistant = {
152
+ id: MessageID.ascending(), parentID: lastUser.id, role: "assistant", mode: agent.name,
153
+ agent: agent.name, variant: lastUser.model.variant, path: { cwd: ctx.directory, root: ctx.worktree },
154
+ cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
155
+ modelID: model.id, providerID: model.providerID, time: { created: Date.now() }, sessionID,
156
+ }
157
+ yield* deps.sessions.updateMessage(msg)
158
+ const handle = yield* deps.processor.create({ assistantMessage: msg, sessionID, model, telemetry })
159
+ const outcome: "break" | "continue" = yield* Effect.gen(function* () {
160
+ const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
161
+ const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false
162
+ const tools = yield* resolveTools({
163
+ agent, session, model, tools: lastUser.tools, processor: handle,
164
+ bypassAgentCheck, messages: msgs,
165
+ })
166
+ if (lastUser.format?.type === "json_schema") {
167
+ tools["StructuredOutput"] = createStructuredOutputTool({
168
+ schema: lastUser.format.schema,
169
+ onSuccess(output: unknown) { structured = output },
170
+ })
171
+ }
172
+ if (step === 1)
173
+ yield* deps.summary.summarize({ sessionID, messageID: lastUser.id }).pipe(Effect.ignore, Effect.forkIn(deps.scope))
174
+ if (step > 1 && lastFinished) {
175
+ for (const m of msgs) {
176
+ if (m.info.role !== "user" || m.info.id <= lastFinished.id) continue
177
+ for (const p of m.parts) {
178
+ if (p.type !== "text" || p.ignored || p.synthetic || !p.text.trim()) continue
179
+ p.text = ["<system-reminder>", "The user sent the following message:", p.text, "",
180
+ "Please address this message and continue with your tasks.", "</system-reminder>"].join("\n")
181
+ }
182
+ }
183
+ }
184
+ yield* deps.plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
185
+ SaeeolSessionPrompt.injectEditorContext({ msgs, lastUser, sessionID, cache: envCache })
186
+ msgs = SaeeolSessionPrompt.maybeStripHistoricalMedia(msgs)
187
+ const allResult: any = yield* Effect.all([
188
+ deps.sys.skills(agent), deps.sys.environment(model, lastUser.editorContext),
189
+ deps.instruction.system().pipe(Effect.orDie),
190
+ ])
191
+ const skills: any = allResult[0], env: any[] = allResult[1], instructions: any[] = allResult[2]
192
+ let modelMsgs = yield* MessageV2.toModelMessagesEffect(msgs, model)
193
+ const size = Buffer.byteLength(JSON.stringify(modelMsgs))
194
+ if (size > REQUEST_PRUNE_BYTES) {
195
+ yield* deps.compaction.prune({ sessionID, reason: "payload-limit" })
196
+ msgs = yield* MessageV2.filterCompactedEffect(sessionID)
197
+ msgs = SaeeolSessionPromptQueue.scope(sessionID, msgs)
198
+ msgs = SaeeolSessionPrompt.trimBeforeLastSummary(msgs)
199
+ yield* deps.plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
200
+ SaeeolSessionPrompt.injectEditorContext({ msgs, lastUser, sessionID, cache: envCache })
201
+ msgs = SaeeolSessionPrompt.maybeStripHistoricalMedia(msgs)
202
+ modelMsgs = yield* MessageV2.toModelMessagesEffect(msgs, model)
203
+ const nextSize = Buffer.byteLength(JSON.stringify(modelMsgs))
204
+ if (nextSize > REQUEST_PRUNE_BYTES) log.warn("payload still large after pruning", { size: nextSize })
205
+ }
206
+ const system = [...env, ...instructions, ...(skills ? [skills] : [])]
207
+ const format = lastUser.format ?? { type: "text" as const }
208
+ if (format.type === "json_schema") system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
209
+ const result = yield* handle.process({
210
+ user: lastUser, agent, permission: SaeeolSessionPrompt.guardPermissions({ agent, session }),
211
+ sessionID, parentSessionID: session.parentID, system,
212
+ messages: [...modelMsgs, ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS }] : [])],
213
+ tools, model, toolChoice: format.type === "json_schema" ? "required" : undefined,
214
+ })
215
+ if (structured !== undefined) {
216
+ handle.message.structured = structured
217
+ handle.message.finish = handle.message.finish ?? "stop"
218
+ yield* deps.sessions.updateMessage(handle.message)
219
+ return "break" as const
220
+ }
221
+ const finished = handle.message.finish && !["tool-calls", "unknown"].includes(handle.message.finish)
222
+ if (finished && !handle.message.error) {
223
+ if (format.type === "json_schema") {
224
+ handle.message.error = new MessageV2.StructuredOutputError({
225
+ message: "Model did not produce structured output", retries: 0,
226
+ }).toObject()
227
+ yield* deps.sessions.updateMessage(handle.message)
228
+ return "break" as const
229
+ }
230
+ if (handle.message.finish === "error") {
231
+ SaeeolSessionProcessor.providerFinishError(handle.message)
232
+ yield* deps.sessions.updateMessage(handle.message)
233
+ closeReasons.set(sessionID, "error")
234
+ return "break" as const
235
+ }
236
+ }
237
+ if (result === "stop") {
238
+ if (handle.message.error) closeReasons.set(sessionID, "error")
239
+ return "break" as const
240
+ }
241
+ if (result === "compact") {
242
+ const guard = SaeeolSessionPrompt.guardCompactionAttempt({
243
+ sessionID, attempts: compactionAttempts, closeReasons, message: handle.message,
244
+ })
245
+ if (guard.exhausted) {
246
+ yield* deps.sessions.updateMessage(handle.message)
247
+ yield* deps.bus.publish(Session.Event.Error, { sessionID, error: guard.error })
248
+ return "break" as const
249
+ }
250
+ compactionAttempts++
251
+ yield* deps.compaction.create({
252
+ sessionID, agent: lastUser.agent, model: lastUser.model, auto: true,
253
+ overflow: !handle.message.finish,
254
+ })
255
+ }
256
+ if (SaeeolSessionPromptQueue.hasFollowup(sessionID)) {
257
+ closeReasons.set(sessionID, "interrupted")
258
+ return "break" as const
259
+ }
260
+ if (result !== "compact" && !handle.message.finish) {
261
+ handle.message.finish = "unknown"
262
+ yield* deps.sessions.updateMessage(handle.message)
263
+ }
264
+ return "continue" as const
265
+ }).pipe(Effect.ensuring(deps.instruction.clear(handle.message.id)))
266
+ if (outcome === "break") break
267
+ continue
268
+ }
269
+ yield* deps.compaction.prune({ sessionID, reason: "normal" }).pipe(Effect.ignore, Effect.forkIn(deps.scope))
270
+ return yield* helpers.lastAssistant(sessionID)
271
+ })
272
+ const loop = Effect.fn("SessionPrompt.loop")(function* (input: LoopInput) {
273
+ yield* SaeeolSessionPrompt.recoverDanglingAssistant({
274
+ sessionID: input.sessionID, status: deps.status, sessions: deps.sessions,
275
+ })
276
+ yield* SaeeolSessionPrompt.recoverProviderFinishError({
277
+ sessionID: input.sessionID, status: deps.status, sessions: deps.sessions,
278
+ })
279
+ yield* deps.bus.publish(SaeeolSession.Event.TurnOpen, { sessionID: input.sessionID })
280
+ return yield* Effect.onExit(
281
+ deps.state.ensureRunning(input.sessionID, helpers.lastAssistant(input.sessionID), runLoop(input.sessionID)),
282
+ Effect.fnUntraced(function* (exit) {
283
+ yield* deps.bus.publish(SaeeolSession.Event.TurnClose, {
284
+ sessionID: input.sessionID,
285
+ reason: SaeeolSessionPrompt.resolveCloseReason({
286
+ sessionID: input.sessionID, closeReasons, exit,
287
+ }),
288
+ })
289
+ }),
290
+ )
291
+ })
292
+ const shell = Effect.fn("SessionPrompt.shell")(function* (input: ShellInput) {
293
+ const ready = yield* Latch.make()
294
+ return yield* deps.state.startShell(
295
+ input.sessionID, helpers.lastAssistant(input.sessionID), shellImpl(input, ready), ready,
296
+ )
297
+ })
298
+ return { runLoop, loop, shell, closeReasons }
299
+ }
@@ -0,0 +1,44 @@
1
+ import { Effect, Exit, Cause, Option, Types } from "effect"
2
+ import { SessionID, MessageID, PartID } from "../core/schema"
3
+ import { MessageV2 } from "../message/message-v2"
4
+ import { NamedError } from "@saeeol/core/util/error"
5
+ import * as Log from "@saeeol/core/util/log"
6
+ import type { PromptInput } from "./prompt-schemas"
7
+ import type { ModelID, ProviderID } from "../../provider/schema"
8
+ import type { PromptDeps } from "./prompt-types"
9
+
10
+ const log = Log.create({ service: "session.prompt" })
11
+
12
+ export const getModel = (deps: PromptDeps) =>
13
+ Effect.fn("SessionPrompt.getModel")(function* (providerID: ProviderID, modelID: ModelID, sessionID: SessionID) {
14
+ const exit = yield* deps.provider.getModel(providerID, modelID).pipe(Effect.exit)
15
+ if (Exit.isSuccess(exit)) return exit.value
16
+ const err = Cause.squash(exit.cause)
17
+ if (deps.provider.ModelNotFoundError?.isInstance?.(err) ?? (err as any).constructor?.name === "ModelNotFoundError") {
18
+ const hint = (err as any).data?.suggestions?.length ? ` Did you mean: ${(err as any).data.suggestions.join(", ")}?` : ""
19
+ yield* deps.bus.publish(deps.sessions.constructor?.Event?.Error ?? { type: "session.error" }, {
20
+ sessionID,
21
+ error: new NamedError.Unknown({ message: `Model not found: ${providerID}/${modelID}.${hint}` }).toObject(),
22
+ })
23
+ }
24
+ return yield* Effect.failCause(exit.cause)
25
+ })
26
+
27
+ export const lastModel = (deps: PromptDeps) =>
28
+ Effect.fnUntraced(function* (sessionID: SessionID) {
29
+ const match = yield* deps.sessions.findMessage(sessionID, (m: any) => m.info.role === "user" && !!m.info.model) as any
30
+ if (Option.isSome(match) && (match.value as any).info.role === "user") return (match.value as any).info.model
31
+ return yield* deps.provider.defaultModel()
32
+ })
33
+
34
+ export const lastAssistant = (deps: PromptDeps) =>
35
+ Effect.fnUntraced(function* (sessionID: SessionID) {
36
+ for (let attempt = 0; attempt < 10; attempt++) {
37
+ const match = yield* deps.sessions.findMessage(sessionID, (m: any) => m.info.role !== "user")
38
+ if (Option.isSome(match)) return match.value
39
+ const msgs = yield* deps.sessions.messages({ sessionID, limit: 1 })
40
+ if (msgs.length > 0) return msgs[0]
41
+ yield* Effect.sleep("50 millis")
42
+ }
43
+ throw new Error("Impossible")
44
+ })