bonecode 1.0.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 (605) hide show
  1. package/ARCHITECTURE.md +183 -0
  2. package/README.md +71 -0
  3. package/bin/bonecode +62 -0
  4. package/bone/migrations/rag_vectors.sql +258 -0
  5. package/bone/output/agent/.dockerignore +7 -0
  6. package/bone/output/agent/.env.example +36 -0
  7. package/bone/output/agent/.github/workflows/ci.yaml +58 -0
  8. package/bone/output/agent/AgentDomain.bone.map +350 -0
  9. package/bone/output/agent/AgentDomain.postman_collection.json +958 -0
  10. package/bone/output/agent/Dockerfile +22 -0
  11. package/bone/output/agent/README.md +47 -0
  12. package/bone/output/agent/admin/index.html +740 -0
  13. package/bone/output/agent/docker-compose.yaml +22 -0
  14. package/bone/output/agent/k8s/deployment.yaml +75 -0
  15. package/bone/output/agent/migrations/agent.sql +36 -0
  16. package/bone/output/agent/migrations/agent_instance.sql +36 -0
  17. package/bone/output/agent/migrations/audit_log.sql +18 -0
  18. package/bone/output/agent/migrations/build_step.sql +34 -0
  19. package/bone/output/agent/migrations/event_outbox.sql +31 -0
  20. package/bone/output/agent/migrations/plan.sql +30 -0
  21. package/bone/output/agent/migrations/task.sql +30 -0
  22. package/bone/output/agent/migrations/tool_call.sql +33 -0
  23. package/bone/output/agent/openapi.yaml +1116 -0
  24. package/bone/output/agent/package.json +36 -0
  25. package/bone/output/agent/schema.graphql +233 -0
  26. package/bone/output/agent/sdk/client.ts +231 -0
  27. package/bone/output/agent/src/algorithms.ts +2 -0
  28. package/bone/output/agent/src/audit.ts +44 -0
  29. package/bone/output/agent/src/auth.ts +57 -0
  30. package/bone/output/agent/src/cron.ts +12 -0
  31. package/bone/output/agent/src/db.ts +32 -0
  32. package/bone/output/agent/src/debug.ts +66 -0
  33. package/bone/output/agent/src/events.ts +243 -0
  34. package/bone/output/agent/src/extensions.ts +54 -0
  35. package/bone/output/agent/src/failure_rules.ts +323 -0
  36. package/bone/output/agent/src/flows.ts +168 -0
  37. package/bone/output/agent/src/health.ts +43 -0
  38. package/bone/output/agent/src/index.ts +100 -0
  39. package/bone/output/agent/src/logger.ts +66 -0
  40. package/bone/output/agent/src/metrics.ts +75 -0
  41. package/bone/output/agent/src/migrate.ts +352 -0
  42. package/bone/output/agent/src/migration_diff.ts +108 -0
  43. package/bone/output/agent/src/notify.ts +125 -0
  44. package/bone/output/agent/src/routes/agent_instance.ts +234 -0
  45. package/bone/output/agent/src/routes/build_step.ts +105 -0
  46. package/bone/output/agent/src/routes/plan.ts +91 -0
  47. package/bone/output/agent/src/routes/task.ts +105 -0
  48. package/bone/output/agent/src/routes/tool_call.ts +166 -0
  49. package/bone/output/agent/src/schemas.ts +384 -0
  50. package/bone/output/agent/src/state_machines/agent_instance.ts +24 -0
  51. package/bone/output/agent/src/state_machines/build_step.ts +22 -0
  52. package/bone/output/agent/src/state_machines/plan.ts +22 -0
  53. package/bone/output/agent/src/state_machines/task.ts +22 -0
  54. package/bone/output/agent/src/state_machines/tool_call.ts +22 -0
  55. package/bone/output/agent/src/tests.ts +362 -0
  56. package/bone/output/agent/src/websocket.ts +201 -0
  57. package/bone/output/agent/tsconfig.json +25 -0
  58. package/bone/output/rag/.dockerignore +7 -0
  59. package/bone/output/rag/.env.example +36 -0
  60. package/bone/output/rag/.github/workflows/ci.yaml +58 -0
  61. package/bone/output/rag/Dockerfile +22 -0
  62. package/bone/output/rag/RAGDomain.bone.map +287 -0
  63. package/bone/output/rag/RAGDomain.postman_collection.json +923 -0
  64. package/bone/output/rag/README.md +47 -0
  65. package/bone/output/rag/admin/index.html +818 -0
  66. package/bone/output/rag/docker-compose.yaml +22 -0
  67. package/bone/output/rag/k8s/deployment.yaml +75 -0
  68. package/bone/output/rag/migrations/audit_log.sql +18 -0
  69. package/bone/output/rag/migrations/code_chunk.sql +34 -0
  70. package/bone/output/rag/migrations/code_file.sql +33 -0
  71. package/bone/output/rag/migrations/event_outbox.sql +31 -0
  72. package/bone/output/rag/migrations/indexing_job.sql +33 -0
  73. package/bone/output/rag/migrations/knowledge_base.sql +35 -0
  74. package/bone/output/rag/migrations/memory_entry.sql +34 -0
  75. package/bone/output/rag/openapi.yaml +1097 -0
  76. package/bone/output/rag/package.json +36 -0
  77. package/bone/output/rag/schema.graphql +245 -0
  78. package/bone/output/rag/sdk/client.ts +234 -0
  79. package/bone/output/rag/src/algorithms.ts +2 -0
  80. package/bone/output/rag/src/audit.ts +37 -0
  81. package/bone/output/rag/src/auth.ts +57 -0
  82. package/bone/output/rag/src/cron.ts +12 -0
  83. package/bone/output/rag/src/db.ts +32 -0
  84. package/bone/output/rag/src/debug.ts +66 -0
  85. package/bone/output/rag/src/events.ts +243 -0
  86. package/bone/output/rag/src/extensions.ts +350 -0
  87. package/bone/output/rag/src/failure_rules.ts +315 -0
  88. package/bone/output/rag/src/flows.ts +239 -0
  89. package/bone/output/rag/src/health.ts +43 -0
  90. package/bone/output/rag/src/index.ts +95 -0
  91. package/bone/output/rag/src/logger.ts +66 -0
  92. package/bone/output/rag/src/metrics.ts +75 -0
  93. package/bone/output/rag/src/migrate.ts +364 -0
  94. package/bone/output/rag/src/migration_diff.ts +108 -0
  95. package/bone/output/rag/src/notify.ts +99 -0
  96. package/bone/output/rag/src/routes/code_chunk.ts +75 -0
  97. package/bone/output/rag/src/routes/code_file.ts +101 -0
  98. package/bone/output/rag/src/routes/indexing_job.ts +87 -0
  99. package/bone/output/rag/src/routes/knowledge_base.ts +230 -0
  100. package/bone/output/rag/src/routes/memory_entry.ts +87 -0
  101. package/bone/output/rag/src/schemas.ts +394 -0
  102. package/bone/output/rag/src/state_machines/code_file.ts +23 -0
  103. package/bone/output/rag/src/state_machines/indexing_job.ts +22 -0
  104. package/bone/output/rag/src/state_machines/knowledge_base.ts +23 -0
  105. package/bone/output/rag/src/state_machines/memory_entry.ts +20 -0
  106. package/bone/output/rag/src/tests.ts +340 -0
  107. package/bone/output/rag/tsconfig.json +25 -0
  108. package/bone/output/session/.dockerignore +7 -0
  109. package/bone/output/session/.env.example +36 -0
  110. package/bone/output/session/.github/workflows/ci.yaml +58 -0
  111. package/bone/output/session/Dockerfile +22 -0
  112. package/bone/output/session/README.md +47 -0
  113. package/bone/output/session/SessionDomain.bone.map +350 -0
  114. package/bone/output/session/SessionDomain.postman_collection.json +958 -0
  115. package/bone/output/session/admin/index.html +667 -0
  116. package/bone/output/session/docker-compose.yaml +22 -0
  117. package/bone/output/session/k8s/deployment.yaml +75 -0
  118. package/bone/output/session/migrations/audit_log.sql +18 -0
  119. package/bone/output/session/migrations/event_outbox.sql +31 -0
  120. package/bone/output/session/migrations/message.sql +31 -0
  121. package/bone/output/session/migrations/part.sql +28 -0
  122. package/bone/output/session/migrations/permission.sql +28 -0
  123. package/bone/output/session/migrations/project.sql +28 -0
  124. package/bone/output/session/migrations/session.sql +38 -0
  125. package/bone/output/session/openapi.yaml +1101 -0
  126. package/bone/output/session/package.json +36 -0
  127. package/bone/output/session/schema.graphql +222 -0
  128. package/bone/output/session/sdk/client.ts +225 -0
  129. package/bone/output/session/src/algorithms.ts +2 -0
  130. package/bone/output/session/src/audit.ts +44 -0
  131. package/bone/output/session/src/auth.ts +57 -0
  132. package/bone/output/session/src/cron.ts +12 -0
  133. package/bone/output/session/src/db.ts +32 -0
  134. package/bone/output/session/src/debug.ts +66 -0
  135. package/bone/output/session/src/events.ts +270 -0
  136. package/bone/output/session/src/extensions.ts +215 -0
  137. package/bone/output/session/src/failure_rules.ts +284 -0
  138. package/bone/output/session/src/flows.ts +168 -0
  139. package/bone/output/session/src/health.ts +43 -0
  140. package/bone/output/session/src/index.ts +100 -0
  141. package/bone/output/session/src/logger.ts +66 -0
  142. package/bone/output/session/src/metrics.ts +75 -0
  143. package/bone/output/session/src/migrate.ts +332 -0
  144. package/bone/output/session/src/migration_diff.ts +108 -0
  145. package/bone/output/session/src/notify.ts +112 -0
  146. package/bone/output/session/src/routes/message.ts +93 -0
  147. package/bone/output/session/src/routes/part.ts +79 -0
  148. package/bone/output/session/src/routes/permission.ts +79 -0
  149. package/bone/output/session/src/routes/project.ts +79 -0
  150. package/bone/output/session/src/routes/session.ts +294 -0
  151. package/bone/output/session/src/schemas.ts +357 -0
  152. package/bone/output/session/src/state_machines/session.ts +23 -0
  153. package/bone/output/session/src/tests.ts +326 -0
  154. package/bone/output/session/src/websocket.ts +201 -0
  155. package/bone/output/session/tsconfig.json +25 -0
  156. package/bone/output/workspace/.dockerignore +7 -0
  157. package/bone/output/workspace/.env.example +36 -0
  158. package/bone/output/workspace/.github/workflows/ci.yaml +58 -0
  159. package/bone/output/workspace/Dockerfile +22 -0
  160. package/bone/output/workspace/README.md +45 -0
  161. package/bone/output/workspace/WorkspaceDomain.bone.map +189 -0
  162. package/bone/output/workspace/WorkspaceDomain.postman_collection.json +621 -0
  163. package/bone/output/workspace/admin/index.html +485 -0
  164. package/bone/output/workspace/docker-compose.yaml +22 -0
  165. package/bone/output/workspace/k8s/deployment.yaml +75 -0
  166. package/bone/output/workspace/migrations/audit_log.sql +18 -0
  167. package/bone/output/workspace/migrations/codebase.sql +34 -0
  168. package/bone/output/workspace/migrations/event_outbox.sql +31 -0
  169. package/bone/output/workspace/migrations/snapshot.sql +32 -0
  170. package/bone/output/workspace/migrations/workspace.sql +33 -0
  171. package/bone/output/workspace/openapi.yaml +721 -0
  172. package/bone/output/workspace/package.json +36 -0
  173. package/bone/output/workspace/schema.graphql +153 -0
  174. package/bone/output/workspace/sdk/client.ts +155 -0
  175. package/bone/output/workspace/src/algorithms.ts +2 -0
  176. package/bone/output/workspace/src/audit.ts +37 -0
  177. package/bone/output/workspace/src/auth.ts +57 -0
  178. package/bone/output/workspace/src/cron.ts +12 -0
  179. package/bone/output/workspace/src/db.ts +32 -0
  180. package/bone/output/workspace/src/debug.ts +66 -0
  181. package/bone/output/workspace/src/events.ts +243 -0
  182. package/bone/output/workspace/src/extensions.ts +44 -0
  183. package/bone/output/workspace/src/failure_rules.ts +153 -0
  184. package/bone/output/workspace/src/health.ts +43 -0
  185. package/bone/output/workspace/src/index.ts +89 -0
  186. package/bone/output/workspace/src/logger.ts +66 -0
  187. package/bone/output/workspace/src/metrics.ts +75 -0
  188. package/bone/output/workspace/src/migrate.ts +220 -0
  189. package/bone/output/workspace/src/migration_diff.ts +108 -0
  190. package/bone/output/workspace/src/notify.ts +73 -0
  191. package/bone/output/workspace/src/routes/codebase.ts +87 -0
  192. package/bone/output/workspace/src/routes/snapshot.ts +127 -0
  193. package/bone/output/workspace/src/routes/workspace.ts +190 -0
  194. package/bone/output/workspace/src/schemas.ts +231 -0
  195. package/bone/output/workspace/src/state_machines/codebase.ts +21 -0
  196. package/bone/output/workspace/src/state_machines/snapshot.ts +20 -0
  197. package/bone/output/workspace/src/state_machines/workspace.ts +21 -0
  198. package/bone/output/workspace/src/tests.ts +249 -0
  199. package/bone/output/workspace/tsconfig.json +25 -0
  200. package/compat/opencode_adapter.ts +410 -0
  201. package/package.json +69 -0
  202. package/scripts/check_benchmark_session.js +34 -0
  203. package/scripts/check_finish_event.js +24 -0
  204. package/scripts/check_parts.js +15 -0
  205. package/scripts/compile.js +79 -0
  206. package/scripts/copy_opencode.ps1 +53 -0
  207. package/scripts/create_functions.sql +129 -0
  208. package/scripts/migrate.js +85 -0
  209. package/scripts/migrate_from_opencode.ts +218 -0
  210. package/scripts/test_agent_loop.js +101 -0
  211. package/scripts/test_api.ps1 +116 -0
  212. package/scripts/test_context_builder.js +136 -0
  213. package/scripts/test_context_builder.ts +97 -0
  214. package/scripts/test_rag.js +189 -0
  215. package/scripts/test_stream_events.js +36 -0
  216. package/scripts/test_websocket_and_saga.js +216 -0
  217. package/src/cli.ts +475 -0
  218. package/src/config.ts +162 -0
  219. package/src/context_builder.ts +598 -0
  220. package/src/engine/account/account.sql.ts +39 -0
  221. package/src/engine/account/account.ts +456 -0
  222. package/src/engine/account/repo.ts +166 -0
  223. package/src/engine/account/schema.ts +99 -0
  224. package/src/engine/account/url.ts +8 -0
  225. package/src/engine/acp/README.md +174 -0
  226. package/src/engine/acp/agent.ts +1968 -0
  227. package/src/engine/acp/runtime.ts +22 -0
  228. package/src/engine/acp/session.ts +122 -0
  229. package/src/engine/acp/types.ts +24 -0
  230. package/src/engine/agent/agent.ts +463 -0
  231. package/src/engine/agent/generate.txt +75 -0
  232. package/src/engine/agent/prompt/compaction.txt +9 -0
  233. package/src/engine/agent/prompt/explore.txt +18 -0
  234. package/src/engine/agent/prompt/scout.txt +36 -0
  235. package/src/engine/agent/prompt/summary.txt +11 -0
  236. package/src/engine/agent/prompt/title.txt +44 -0
  237. package/src/engine/agent/subagent-permissions.ts +34 -0
  238. package/src/engine/auth/index.ts +96 -0
  239. package/src/engine/background/background/job.ts +200 -0
  240. package/src/engine/background/job.ts +200 -0
  241. package/src/engine/bus/bus-event.ts +45 -0
  242. package/src/engine/bus/global.ts +22 -0
  243. package/src/engine/bus/index.ts +203 -0
  244. package/src/engine/command/command/index.ts +181 -0
  245. package/src/engine/command/command/template/initialize.txt +66 -0
  246. package/src/engine/command/command/template/review.txt +101 -0
  247. package/src/engine/command/index.ts +181 -0
  248. package/src/engine/command/template/initialize.txt +66 -0
  249. package/src/engine/command/template/review.txt +101 -0
  250. package/src/engine/config/agent.ts +172 -0
  251. package/src/engine/config/attachment.ts +25 -0
  252. package/src/engine/config/command.ts +62 -0
  253. package/src/engine/config/config.ts +833 -0
  254. package/src/engine/config/console-state.ts +14 -0
  255. package/src/engine/config/entry-name.ts +16 -0
  256. package/src/engine/config/error.ts +23 -0
  257. package/src/engine/config/formatter.ts +13 -0
  258. package/src/engine/config/layout.ts +6 -0
  259. package/src/engine/config/lsp.ts +43 -0
  260. package/src/engine/config/managed.ts +71 -0
  261. package/src/engine/config/markdown.ts +96 -0
  262. package/src/engine/config/mcp.ts +56 -0
  263. package/src/engine/config/model-id.ts +5 -0
  264. package/src/engine/config/parse.ts +79 -0
  265. package/src/engine/config/paths.ts +45 -0
  266. package/src/engine/config/permission.ts +58 -0
  267. package/src/engine/config/plugin.ts +84 -0
  268. package/src/engine/config/provider.ts +111 -0
  269. package/src/engine/config/reference.ts +23 -0
  270. package/src/engine/config/server.ts +19 -0
  271. package/src/engine/config/skills.ts +14 -0
  272. package/src/engine/config/variable.ts +90 -0
  273. package/src/engine/control-plane/adapters/index.ts +41 -0
  274. package/src/engine/control-plane/adapters/worktree.ts +96 -0
  275. package/src/engine/control-plane/dev/README.md +19 -0
  276. package/src/engine/control-plane/dev/debug-workspace-plugin.ts +73 -0
  277. package/src/engine/control-plane/schema.ts +14 -0
  278. package/src/engine/control-plane/types.ts +59 -0
  279. package/src/engine/control-plane/util.ts +39 -0
  280. package/src/engine/control-plane/workspace-adapter-runtime.ts +51 -0
  281. package/src/engine/control-plane/workspace-context.ts +26 -0
  282. package/src/engine/control-plane/workspace.sql.ts +20 -0
  283. package/src/engine/control-plane/workspace.ts +1072 -0
  284. package/src/engine/data-migration.ts +161 -0
  285. package/src/engine/effect/app-runtime.ts +143 -0
  286. package/src/engine/effect/bootstrap-runtime.ts +29 -0
  287. package/src/engine/effect/bridge.ts +84 -0
  288. package/src/engine/effect/config-service.ts +67 -0
  289. package/src/engine/effect/instance-ref.ts +11 -0
  290. package/src/engine/effect/instance-registry.ts +12 -0
  291. package/src/engine/effect/instance-state.ts +72 -0
  292. package/src/engine/effect/promise.ts +17 -0
  293. package/src/engine/effect/run-service.ts +47 -0
  294. package/src/engine/effect/runner.ts +217 -0
  295. package/src/engine/effect/runtime-flags.ts +74 -0
  296. package/src/engine/effect/service-use.ts +38 -0
  297. package/src/engine/env/index.ts +37 -0
  298. package/src/engine/event-v2-bridge.ts +89 -0
  299. package/src/engine/file/file/ignore.ts +81 -0
  300. package/src/engine/file/file/index.ts +651 -0
  301. package/src/engine/file/file/protected.ts +59 -0
  302. package/src/engine/file/file/ripgrep.ts +481 -0
  303. package/src/engine/file/file/watcher.ts +167 -0
  304. package/src/engine/file/ignore.ts +81 -0
  305. package/src/engine/file/index.ts +651 -0
  306. package/src/engine/file/protected.ts +59 -0
  307. package/src/engine/file/ripgrep.ts +481 -0
  308. package/src/engine/file/watcher.ts +167 -0
  309. package/src/engine/format/format/formatter.ts +404 -0
  310. package/src/engine/format/format/index.ts +209 -0
  311. package/src/engine/format/formatter.ts +404 -0
  312. package/src/engine/format/index.ts +209 -0
  313. package/src/engine/git/git/index.ts +347 -0
  314. package/src/engine/git/index.ts +347 -0
  315. package/src/engine/id/id.ts +80 -0
  316. package/src/engine/ide/index.ts +70 -0
  317. package/src/engine/image/image/image.ts +176 -0
  318. package/src/engine/image/image.ts +176 -0
  319. package/src/engine/index.ts +251 -0
  320. package/src/engine/installation/index.ts +327 -0
  321. package/src/engine/lsp/client.ts +707 -0
  322. package/src/engine/lsp/diagnostic.ts +29 -0
  323. package/src/engine/lsp/language.ts +121 -0
  324. package/src/engine/lsp/launch.ts +21 -0
  325. package/src/engine/lsp/lsp/client.ts +707 -0
  326. package/src/engine/lsp/lsp/diagnostic.ts +29 -0
  327. package/src/engine/lsp/lsp/language.ts +121 -0
  328. package/src/engine/lsp/lsp/launch.ts +21 -0
  329. package/src/engine/lsp/lsp/lsp.ts +507 -0
  330. package/src/engine/lsp/lsp/server.ts +2064 -0
  331. package/src/engine/lsp/lsp.ts +507 -0
  332. package/src/engine/lsp/server.ts +2064 -0
  333. package/src/engine/mcp/auth.ts +146 -0
  334. package/src/engine/mcp/index.ts +958 -0
  335. package/src/engine/mcp/mcp/auth.ts +146 -0
  336. package/src/engine/mcp/mcp/index.ts +958 -0
  337. package/src/engine/mcp/mcp/oauth-callback.ts +232 -0
  338. package/src/engine/mcp/mcp/oauth-provider.ts +214 -0
  339. package/src/engine/mcp/oauth-callback.ts +232 -0
  340. package/src/engine/mcp/oauth-provider.ts +214 -0
  341. package/src/engine/node.ts +6 -0
  342. package/src/engine/patch/index.ts +689 -0
  343. package/src/engine/patch/patch/index.ts +689 -0
  344. package/src/engine/permission/arity.ts +163 -0
  345. package/src/engine/permission/evaluate.ts +15 -0
  346. package/src/engine/permission/index.ts +306 -0
  347. package/src/engine/permission/permission/arity.ts +163 -0
  348. package/src/engine/permission/permission/evaluate.ts +15 -0
  349. package/src/engine/permission/permission/index.ts +306 -0
  350. package/src/engine/permission/permission/schema.ts +13 -0
  351. package/src/engine/permission/schema.ts +13 -0
  352. package/src/engine/plugin/azure.ts +26 -0
  353. package/src/engine/plugin/cloudflare.ts +76 -0
  354. package/src/engine/plugin/codex.ts +622 -0
  355. package/src/engine/plugin/digitalocean.ts +411 -0
  356. package/src/engine/plugin/github-copilot/copilot.ts +394 -0
  357. package/src/engine/plugin/github-copilot/models.ts +196 -0
  358. package/src/engine/plugin/index.ts +295 -0
  359. package/src/engine/plugin/install.ts +439 -0
  360. package/src/engine/plugin/loader.ts +216 -0
  361. package/src/engine/plugin/meta.ts +188 -0
  362. package/src/engine/plugin/shared.ts +323 -0
  363. package/src/engine/project/bootstrap-service.ts +9 -0
  364. package/src/engine/project/bootstrap.ts +75 -0
  365. package/src/engine/project/instance-context.ts +24 -0
  366. package/src/engine/project/instance-layer.ts +11 -0
  367. package/src/engine/project/instance-runtime.ts +16 -0
  368. package/src/engine/project/instance-store.ts +193 -0
  369. package/src/engine/project/project.sql.ts +17 -0
  370. package/src/engine/project/project.ts +537 -0
  371. package/src/engine/project/schema.ts +13 -0
  372. package/src/engine/project/vcs.ts +405 -0
  373. package/src/engine/provider/auth.ts +225 -0
  374. package/src/engine/provider/error.ts +204 -0
  375. package/src/engine/provider/model-status.ts +8 -0
  376. package/src/engine/provider/provider.ts +1843 -0
  377. package/src/engine/provider/schema.ts +30 -0
  378. package/src/engine/provider/sdk/copilot/AGENTS.md +1 -0
  379. package/src/engine/provider/transform.ts +1376 -0
  380. package/src/engine/pty/index.ts +365 -0
  381. package/src/engine/pty/input.ts +24 -0
  382. package/src/engine/pty/pty/index.ts +365 -0
  383. package/src/engine/pty/pty/input.ts +24 -0
  384. package/src/engine/pty/pty/pty.bun.ts +26 -0
  385. package/src/engine/pty/pty/pty.node.ts +27 -0
  386. package/src/engine/pty/pty/pty.ts +25 -0
  387. package/src/engine/pty/pty/schema.ts +14 -0
  388. package/src/engine/pty/pty/ticket.ts +68 -0
  389. package/src/engine/pty/pty.bun.ts +26 -0
  390. package/src/engine/pty/pty.node.ts +27 -0
  391. package/src/engine/pty/pty.ts +25 -0
  392. package/src/engine/pty/schema.ts +14 -0
  393. package/src/engine/pty/ticket.ts +68 -0
  394. package/src/engine/question/index.ts +213 -0
  395. package/src/engine/question/question/index.ts +213 -0
  396. package/src/engine/question/question/schema.ts +10 -0
  397. package/src/engine/question/schema.ts +10 -0
  398. package/src/engine/reference/reference/reference.ts +241 -0
  399. package/src/engine/reference/reference/repository-cache.ts +147 -0
  400. package/src/engine/reference/reference.ts +241 -0
  401. package/src/engine/reference/repository-cache.ts +147 -0
  402. package/src/engine/session/compaction.ts +651 -0
  403. package/src/engine/session/compaction_logic.ts +120 -0
  404. package/src/engine/session/instruction.ts +238 -0
  405. package/src/engine/session/instruction_loader.ts +54 -0
  406. package/src/engine/session/llm.ts +459 -0
  407. package/src/engine/session/message-error.ts +14 -0
  408. package/src/engine/session/message-v2.ts +1202 -0
  409. package/src/engine/session/message.ts +146 -0
  410. package/src/engine/session/overflow.ts +32 -0
  411. package/src/engine/session/overflow_check.ts +46 -0
  412. package/src/engine/session/processor.ts +823 -0
  413. package/src/engine/session/prompt/anthropic.txt +105 -0
  414. package/src/engine/session/prompt/beast.txt +147 -0
  415. package/src/engine/session/prompt/build-switch.txt +5 -0
  416. package/src/engine/session/prompt/codex.txt +79 -0
  417. package/src/engine/session/prompt/copilot-gpt-5.txt +143 -0
  418. package/src/engine/session/prompt/default.txt +105 -0
  419. package/src/engine/session/prompt/gemini.txt +155 -0
  420. package/src/engine/session/prompt/gpt.txt +107 -0
  421. package/src/engine/session/prompt/kimi.txt +95 -0
  422. package/src/engine/session/prompt/max-steps.txt +16 -0
  423. package/src/engine/session/prompt/plan-reminder-anthropic.txt +67 -0
  424. package/src/engine/session/prompt/plan.txt +26 -0
  425. package/src/engine/session/prompt/trinity.txt +97 -0
  426. package/src/engine/session/prompt.ts +671 -0
  427. package/src/engine/session/provider_transform.ts +187 -0
  428. package/src/engine/session/retry.ts +200 -0
  429. package/src/engine/session/retry_logic.ts +65 -0
  430. package/src/engine/session/revert.ts +162 -0
  431. package/src/engine/session/run-state.ts +153 -0
  432. package/src/engine/session/schema.ts +26 -0
  433. package/src/engine/session/session.sql.ts +137 -0
  434. package/src/engine/session/session.ts +1011 -0
  435. package/src/engine/session/status.ts +94 -0
  436. package/src/engine/session/summary.ts +164 -0
  437. package/src/engine/session/system.ts +84 -0
  438. package/src/engine/session/system_prompt.ts +65 -0
  439. package/src/engine/session/todo.ts +81 -0
  440. package/src/engine/session/tool_registry.ts +162 -0
  441. package/src/engine/share/session.ts +61 -0
  442. package/src/engine/share/share-next.ts +376 -0
  443. package/src/engine/share/share.sql.ts +13 -0
  444. package/src/engine/shell/shell/shell.ts +215 -0
  445. package/src/engine/shell/shell.ts +215 -0
  446. package/src/engine/skill/discovery.ts +116 -0
  447. package/src/engine/skill/index.ts +336 -0
  448. package/src/engine/skill/prompt/customize-opencode.md +377 -0
  449. package/src/engine/skill/skill/discovery.ts +116 -0
  450. package/src/engine/skill/skill/index.ts +336 -0
  451. package/src/engine/skill/skill/prompt/customize-opencode.md +377 -0
  452. package/src/engine/snapshot/index.ts +762 -0
  453. package/src/engine/snapshot/snapshot/index.ts +762 -0
  454. package/src/engine/sync/README.md +179 -0
  455. package/src/engine/sync/event.sql.ts +17 -0
  456. package/src/engine/sync/index.ts +410 -0
  457. package/src/engine/sync/schema.ts +11 -0
  458. package/src/engine/temporary.ts +33 -0
  459. package/src/engine/tool/apply_patch.ts +313 -0
  460. package/src/engine/tool/apply_patch.txt +33 -0
  461. package/src/engine/tool/edit.ts +711 -0
  462. package/src/engine/tool/edit.txt +10 -0
  463. package/src/engine/tool/external-directory.ts +49 -0
  464. package/src/engine/tool/glob.ts +103 -0
  465. package/src/engine/tool/glob.txt +6 -0
  466. package/src/engine/tool/grep.ts +156 -0
  467. package/src/engine/tool/grep.txt +8 -0
  468. package/src/engine/tool/invalid.ts +21 -0
  469. package/src/engine/tool/json-schema.ts +164 -0
  470. package/src/engine/tool/lsp.ts +113 -0
  471. package/src/engine/tool/lsp.txt +24 -0
  472. package/src/engine/tool/mcp-websearch.ts +96 -0
  473. package/src/engine/tool/plan-enter.txt +14 -0
  474. package/src/engine/tool/plan-exit.txt +13 -0
  475. package/src/engine/tool/plan.ts +78 -0
  476. package/src/engine/tool/question.ts +44 -0
  477. package/src/engine/tool/question.txt +10 -0
  478. package/src/engine/tool/read.ts +337 -0
  479. package/src/engine/tool/read.txt +14 -0
  480. package/src/engine/tool/registry.ts +472 -0
  481. package/src/engine/tool/repo_clone.ts +80 -0
  482. package/src/engine/tool/repo_clone.txt +5 -0
  483. package/src/engine/tool/repo_overview.ts +279 -0
  484. package/src/engine/tool/repo_overview.txt +4 -0
  485. package/src/engine/tool/schema.ts +14 -0
  486. package/src/engine/tool/shell/id.ts +19 -0
  487. package/src/engine/tool/shell/prompt.ts +295 -0
  488. package/src/engine/tool/shell/shell.txt +77 -0
  489. package/src/engine/tool/shell.ts +647 -0
  490. package/src/engine/tool/skill.ts +75 -0
  491. package/src/engine/tool/skill.txt +5 -0
  492. package/src/engine/tool/task.ts +337 -0
  493. package/src/engine/tool/task.txt +58 -0
  494. package/src/engine/tool/task_status.ts +179 -0
  495. package/src/engine/tool/task_status.txt +13 -0
  496. package/src/engine/tool/todo.ts +57 -0
  497. package/src/engine/tool/todowrite.txt +167 -0
  498. package/src/engine/tool/tool/apply_patch.ts +313 -0
  499. package/src/engine/tool/tool/apply_patch.txt +33 -0
  500. package/src/engine/tool/tool/edit.ts +711 -0
  501. package/src/engine/tool/tool/edit.txt +10 -0
  502. package/src/engine/tool/tool/external-directory.ts +49 -0
  503. package/src/engine/tool/tool/glob.ts +103 -0
  504. package/src/engine/tool/tool/glob.txt +6 -0
  505. package/src/engine/tool/tool/grep.ts +156 -0
  506. package/src/engine/tool/tool/grep.txt +8 -0
  507. package/src/engine/tool/tool/invalid.ts +21 -0
  508. package/src/engine/tool/tool/json-schema.ts +164 -0
  509. package/src/engine/tool/tool/lsp.ts +113 -0
  510. package/src/engine/tool/tool/lsp.txt +24 -0
  511. package/src/engine/tool/tool/mcp-websearch.ts +96 -0
  512. package/src/engine/tool/tool/plan-enter.txt +14 -0
  513. package/src/engine/tool/tool/plan-exit.txt +13 -0
  514. package/src/engine/tool/tool/plan.ts +78 -0
  515. package/src/engine/tool/tool/question.ts +44 -0
  516. package/src/engine/tool/tool/question.txt +10 -0
  517. package/src/engine/tool/tool/read.ts +337 -0
  518. package/src/engine/tool/tool/read.txt +14 -0
  519. package/src/engine/tool/tool/registry.ts +472 -0
  520. package/src/engine/tool/tool/repo_clone.ts +80 -0
  521. package/src/engine/tool/tool/repo_clone.txt +5 -0
  522. package/src/engine/tool/tool/repo_overview.ts +279 -0
  523. package/src/engine/tool/tool/repo_overview.txt +4 -0
  524. package/src/engine/tool/tool/schema.ts +14 -0
  525. package/src/engine/tool/tool/shell/id.ts +19 -0
  526. package/src/engine/tool/tool/shell/prompt.ts +295 -0
  527. package/src/engine/tool/tool/shell/shell.txt +77 -0
  528. package/src/engine/tool/tool/shell.ts +647 -0
  529. package/src/engine/tool/tool/skill.ts +75 -0
  530. package/src/engine/tool/tool/skill.txt +5 -0
  531. package/src/engine/tool/tool/task.ts +337 -0
  532. package/src/engine/tool/tool/task.txt +58 -0
  533. package/src/engine/tool/tool/task_status.ts +179 -0
  534. package/src/engine/tool/tool/task_status.txt +13 -0
  535. package/src/engine/tool/tool/todo.ts +57 -0
  536. package/src/engine/tool/tool/todowrite.txt +167 -0
  537. package/src/engine/tool/tool/tool.ts +164 -0
  538. package/src/engine/tool/tool/truncate.ts +160 -0
  539. package/src/engine/tool/tool/truncation-dir.ts +4 -0
  540. package/src/engine/tool/tool/webfetch.ts +192 -0
  541. package/src/engine/tool/tool/webfetch.txt +13 -0
  542. package/src/engine/tool/tool/websearch.ts +143 -0
  543. package/src/engine/tool/tool/websearch.txt +14 -0
  544. package/src/engine/tool/tool/write.ts +104 -0
  545. package/src/engine/tool/tool/write.txt +8 -0
  546. package/src/engine/tool/tool.ts +164 -0
  547. package/src/engine/tool/truncate.ts +160 -0
  548. package/src/engine/tool/truncation-dir.ts +4 -0
  549. package/src/engine/tool/webfetch.ts +192 -0
  550. package/src/engine/tool/webfetch.txt +13 -0
  551. package/src/engine/tool/websearch.ts +143 -0
  552. package/src/engine/tool/websearch.txt +14 -0
  553. package/src/engine/tool/write.ts +104 -0
  554. package/src/engine/tool/write.txt +8 -0
  555. package/src/engine/util/archive.ts +17 -0
  556. package/src/engine/util/bom.ts +31 -0
  557. package/src/engine/util/data-url.ts +9 -0
  558. package/src/engine/util/defer.ts +10 -0
  559. package/src/engine/util/effect-http-client.ts +11 -0
  560. package/src/engine/util/error.ts +88 -0
  561. package/src/engine/util/filesystem.ts +252 -0
  562. package/src/engine/util/format.ts +20 -0
  563. package/src/engine/util/iife.ts +3 -0
  564. package/src/engine/util/lazy.ts +20 -0
  565. package/src/engine/util/local-context.ts +25 -0
  566. package/src/engine/util/locale.ts +86 -0
  567. package/src/engine/util/media.ts +26 -0
  568. package/src/engine/util/process.ts +176 -0
  569. package/src/engine/util/queue.ts +32 -0
  570. package/src/engine/util/record.ts +3 -0
  571. package/src/engine/util/repository.ts +158 -0
  572. package/src/engine/util/rpc.ts +66 -0
  573. package/src/engine/util/signal.ts +12 -0
  574. package/src/engine/util/timeout.ts +13 -0
  575. package/src/engine/util/token.ts +7 -0
  576. package/src/engine/util/util/archive.ts +17 -0
  577. package/src/engine/util/util/bom.ts +31 -0
  578. package/src/engine/util/util/data-url.ts +9 -0
  579. package/src/engine/util/util/defer.ts +10 -0
  580. package/src/engine/util/util/effect-http-client.ts +11 -0
  581. package/src/engine/util/util/error.ts +88 -0
  582. package/src/engine/util/util/filesystem.ts +252 -0
  583. package/src/engine/util/util/format.ts +20 -0
  584. package/src/engine/util/util/iife.ts +3 -0
  585. package/src/engine/util/util/lazy.ts +20 -0
  586. package/src/engine/util/util/local-context.ts +25 -0
  587. package/src/engine/util/util/locale.ts +86 -0
  588. package/src/engine/util/util/media.ts +26 -0
  589. package/src/engine/util/util/process.ts +176 -0
  590. package/src/engine/util/util/queue.ts +32 -0
  591. package/src/engine/util/util/record.ts +3 -0
  592. package/src/engine/util/util/repository.ts +158 -0
  593. package/src/engine/util/util/rpc.ts +66 -0
  594. package/src/engine/util/util/signal.ts +12 -0
  595. package/src/engine/util/util/timeout.ts +13 -0
  596. package/src/engine/util/util/token.ts +7 -0
  597. package/src/engine/util/util/which.ts +14 -0
  598. package/src/engine/util/util/wildcard.ts +59 -0
  599. package/src/engine/util/which.ts +14 -0
  600. package/src/engine/util/wildcard.ts +59 -0
  601. package/src/engine/worktree/index.ts +621 -0
  602. package/src/rag_worker.ts +519 -0
  603. package/src/server.ts +201 -0
  604. package/src/tui.ts +637 -0
  605. package/tsconfig.json +24 -0
@@ -0,0 +1,671 @@
1
+ /**
2
+ * BoneCode Agent Loop — improved fork of OpenCode's session/prompt.ts
3
+ *
4
+ * What's the same as OpenCode:
5
+ * - Multi-turn loop until finish_reason === "stop"
6
+ * - Context compaction (prune → summarize → replay)
7
+ * - Retry with exponential backoff + retry-after headers
8
+ * - Doom-loop detection (3 identical consecutive tool calls)
9
+ * - Permission system (suspend/resume via Deferred)
10
+ * - Provider message transforms (Anthropic, Mistral, DeepSeek, etc.)
11
+ * - Per-provider system prompts
12
+ * - Skills injection
13
+ * - Instruction file discovery (AGENTS.md, CLAUDE.md)
14
+ * - Tool registry (all OpenCode tools)
15
+ * - Sub-agent task delegation
16
+ *
17
+ * What's improved with BoneScript:
18
+ * - All session/message/part persistence goes through generated capability
19
+ * endpoints (state machine enforced, events emitted automatically)
20
+ * - RAG context injected from pgvector knowledge base before each LLM call
21
+ * - Part deltas broadcast to WebSocket part_stream channel in real time
22
+ * - Agent state tracked in agent_instances table (cost, tokens, state machine)
23
+ * - Tool calls recorded in tool_calls table with permission decisions
24
+ * - Durable event bus for long-running tasks
25
+ */
26
+
27
+ import { streamText, type Tool } from "ai";
28
+ import { createAnthropic } from "@ai-sdk/anthropic";
29
+ import { createOpenAI } from "@ai-sdk/openai";
30
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
31
+ import { v4 as uuid } from "uuid";
32
+ import { pool } from "../../../bone/output/session/src/db";
33
+ import { eventBus } from "../../../bone/output/session/src/events";
34
+ import { logger } from "../../../bone/output/session/src/logger";
35
+ import { counter } from "../../../bone/output/session/src/metrics";
36
+ import { broadcastToChannel } from "../../../bone/output/session/src/websocket";
37
+ import { execute_tool_calls } from "../../../bone/output/session/src/extensions";
38
+ import * as ProviderTransform from "./provider_transform";
39
+ import { delay as retryDelay, retryable } from "./retry_logic";
40
+ import { isOverflow } from "./overflow_check";
41
+ import { buildCompactionSummary } from "./compaction_logic";
42
+ import { getSystemPrompt } from "./system_prompt";
43
+ import { loadInstructionFiles } from "./instruction_loader";
44
+ import { buildToolRegistry } from "./tool_registry";
45
+
46
+ // ─── Types ────────────────────────────────────────────────────────────────────
47
+
48
+ export interface PromptInput {
49
+ session_id: string;
50
+ message_id: string;
51
+ content: string;
52
+ model_id: string;
53
+ provider_id: string;
54
+ agent_name?: string;
55
+ /** Optional: force a specific variant (e.g. "high" for extended thinking) */
56
+ variant?: string;
57
+ }
58
+
59
+ export interface LoopResult {
60
+ ok: boolean;
61
+ finish_reason: string;
62
+ total_tokens_in: number;
63
+ total_tokens_out: number;
64
+ total_cost: number;
65
+ compacted: boolean;
66
+ error?: string;
67
+ }
68
+
69
+ const DOOM_LOOP_THRESHOLD = 3;
70
+ const MAX_TURNS = 20; // safety cap — most tasks complete in 1-3 turns
71
+
72
+ // ─── Main Agent Loop ──────────────────────────────────────────────────────────
73
+
74
+ export async function runAgentLoop(input: PromptInput): Promise<LoopResult> {
75
+ const { session_id, message_id, model_id, provider_id, agent_name = "build" } = input;
76
+
77
+ // Load session + project context
78
+ const sessionRow = await pool.query(
79
+ `SELECT s.*, p.worktree, p.vcs FROM sessions s JOIN projects p ON p.id = s.project_id WHERE s.id = $1`,
80
+ [session_id]
81
+ );
82
+ if (!sessionRow.rows[0]) throw new Error(`Session not found: ${session_id}`);
83
+ const session = sessionRow.rows[0];
84
+
85
+ // Create or reuse agent instance
86
+ const agentId = uuid();
87
+ await pool.query(
88
+ `INSERT INTO agent_instances (id, session_id, model_id, provider_id, context_window_used, context_window_max, total_cost_usd, total_tokens_in, total_tokens_out, config, state)
89
+ VALUES ($1, $2, $3, $4, 0, 200000, 0, 0, 0, $5, 'running')`,
90
+ [agentId, session_id, model_id, provider_id, JSON.stringify({ agent_name })]
91
+ );
92
+
93
+ // Transition session to busy
94
+ await pool.query(`UPDATE sessions SET state = 'busy', updated_at = NOW() WHERE id = $1`, [session_id]);
95
+ await eventBus.publish("SessionStateChanged", { session_id, from_state: session.state, to_state: "busy", changed_at: new Date().toISOString() }, "AgentLoop");
96
+
97
+ const stats = { tokens_in: 0, tokens_out: 0, cost: 0, compacted: false };
98
+ let turn = 0;
99
+ let lastFinishReason = "unknown";
100
+
101
+ try {
102
+ // ── Main multi-turn loop ──────────────────────────────────────────────────
103
+ while (turn < MAX_TURNS) {
104
+ turn++;
105
+ if (turn === MAX_TURNS) {
106
+ logger.error("agent_loop_max_turns", { event: "max_turns", metadata: { session_id, max: MAX_TURNS } });
107
+ break;
108
+ }
109
+
110
+ // Load full message history
111
+ const messages = await loadMessageHistory(session_id);
112
+
113
+ // Check if we need compaction before calling the LLM
114
+ const lastAssistant = messages.filter(m => m.role === "assistant").slice(-1)[0];
115
+ // Estimate tokens from text length when model doesn't return counts (local models)
116
+ const estimatedTokens = lastAssistant?.tokens_input
117
+ ? { input: lastAssistant.tokens_input, output: lastAssistant.tokens_output || 0, total: 0, cache: { read: 0, write: 0 } }
118
+ : (() => {
119
+ // Rough estimate: 1 token ≈ 4 chars
120
+ const totalChars = messages.reduce((sum, m) => {
121
+ const parts = m.parts || [];
122
+ return sum + parts.reduce((s: number, p: any) => s + (p.data?.text?.length || 0), 0);
123
+ }, 0);
124
+ return { input: Math.floor(totalChars / 4), output: 0, total: Math.floor(totalChars / 4), cache: { read: 0, write: 0 } };
125
+ })();
126
+ if (isOverflow(estimatedTokens, model_id)) {
127
+ logger.info("context_overflow_detected", { event: "compaction", metadata: { session_id, turn } });
128
+ const compacted = await runCompaction(session_id, agentId, model_id, provider_id, messages);
129
+ if (!compacted) break; // unrecoverable overflow
130
+ stats.compacted = true;
131
+ continue; // restart loop with compacted history
132
+ }
133
+
134
+ // Build AI SDK messages
135
+ const aiMessages = await buildAIMessages(session_id, messages, model_id);
136
+
137
+ // Inject RAG context into system prompt
138
+ const lastUserText = getLastUserText(messages);
139
+ const systemPrompt = await buildSystemPromptWithRAG(session_id, session.worktree, model_id, provider_id, agent_name, lastUserText);
140
+
141
+ // Load tools — pass empty for models that don't support function calling
142
+ const allTools = await buildToolRegistry(session_id, agentId, session.worktree);
143
+ // Check if model supports tools (some local models don't)
144
+ const tools = supportsTools(model_id) ? allTools : {};
145
+
146
+ // Get language model
147
+ const languageModel = getLanguageModel(provider_id, model_id);
148
+
149
+ // Stream the LLM response
150
+ const assistantMsgId = uuid();
151
+ await pool.query(
152
+ `INSERT INTO messages (id, session_id, role, model_id, provider_id) VALUES ($1, $2, 'assistant', $3, $4)`,
153
+ [assistantMsgId, session_id, model_id, provider_id]
154
+ );
155
+ await eventBus.publish("MessageAdded", { session_id, message_id: assistantMsgId, role: "assistant", added_at: new Date().toISOString() }, "AgentLoop");
156
+
157
+ const result = await streamWithRetry({
158
+ session_id, agentId, assistantMsgId,
159
+ languageModel, aiMessages, systemPrompt, tools,
160
+ model_id, provider_id, stats,
161
+ });
162
+
163
+ lastFinishReason = result.finish_reason;
164
+
165
+ // Update agent token counters
166
+ await pool.query(
167
+ `UPDATE agent_instances SET total_tokens_in = total_tokens_in + $2, total_tokens_out = total_tokens_out + $3, total_cost_usd = total_cost_usd + $4, updated_at = NOW() WHERE id = $1`,
168
+ [agentId, result.tokens_in, result.tokens_out, result.cost]
169
+ );
170
+
171
+ // Check termination conditions:
172
+ // 1. "stop" with no tool calls = model finished naturally
173
+ // 2. "length" = hit token limit — don't loop, the response is complete
174
+ // 3. "content-filter" = blocked — stop
175
+ // 4. "tool-calls" with no actual tool calls = model confused — stop
176
+ const terminalReasons = new Set(["stop", "length", "content-filter", "end-turn"]);
177
+ if (terminalReasons.has(result.finish_reason) && !result.has_tool_calls) {
178
+ break;
179
+ }
180
+
181
+ // If there were tool calls, they were executed during streaming — loop continues
182
+ // If finish reason is unexpected and no tool calls, stop to avoid infinite loop
183
+ if (!result.has_tool_calls) {
184
+ break;
185
+ }
186
+ }
187
+
188
+ // Transition agent to done
189
+ await pool.query(`UPDATE agent_instances SET state = 'done', updated_at = NOW() WHERE id = $1`, [agentId]);
190
+ await pool.query(`UPDATE sessions SET state = 'active', updated_at = NOW() WHERE id = $1`, [session_id]);
191
+ await eventBus.publish("AgentCompleted", {
192
+ agent_id: agentId, session_id,
193
+ total_cost_usd: stats.cost, total_tokens_in: stats.tokens_in, total_tokens_out: stats.tokens_out,
194
+ completed_at: new Date().toISOString(),
195
+ }, "AgentLoop");
196
+
197
+ counter("agent.loop.completed", { provider: provider_id, model: model_id, turns: String(turn) });
198
+ return { ok: true, finish_reason: lastFinishReason, total_tokens_in: stats.tokens_in, total_tokens_out: stats.tokens_out, total_cost: stats.cost, compacted: stats.compacted };
199
+
200
+ } catch (e: any) {
201
+ await pool.query(`UPDATE agent_instances SET state = 'failed', updated_at = NOW() WHERE id = $1`, [agentId]);
202
+ await pool.query(`UPDATE sessions SET state = 'active', updated_at = NOW() WHERE id = $1`, [session_id]);
203
+ await eventBus.publish("AgentFailed", { agent_id: agentId, session_id, error: e.message, failed_at: new Date().toISOString() }, "AgentLoop");
204
+ logger.error("agent_loop_failed", { event: "loop_error", metadata: { session_id, error: e.message } });
205
+ return { ok: false, finish_reason: "error", total_tokens_in: stats.tokens_in, total_tokens_out: stats.tokens_out, total_cost: stats.cost, compacted: stats.compacted, error: e.message };
206
+ }
207
+ }
208
+
209
+ // ─── Stream with Retry ────────────────────────────────────────────────────────
210
+
211
+ async function streamWithRetry(ctx: {
212
+ session_id: string; agentId: string; assistantMsgId: string;
213
+ languageModel: any; aiMessages: any[]; systemPrompt: string;
214
+ tools: Record<string, Tool>; model_id: string; provider_id: string;
215
+ stats: { tokens_in: number; tokens_out: number; cost: number };
216
+ }): Promise<{ finish_reason: string; tokens_in: number; tokens_out: number; cost: number; has_tool_calls: boolean }> {
217
+ let attempt = 0;
218
+ const MAX_RETRIES = 5;
219
+ let currentCtx = { ...ctx };
220
+
221
+ while (true) {
222
+ try {
223
+ return await streamOnce(currentCtx);
224
+ } catch (e: any) {
225
+ // On Bad Request with tools, retry without tools
226
+ if (e.message?.includes("Bad Request") && Object.keys(currentCtx.tools).length > 0 && attempt === 0) {
227
+ console.log("[AgentLoop] Bad Request with tools — retrying without tools");
228
+ currentCtx = { ...currentCtx, tools: {} };
229
+ attempt++;
230
+ continue;
231
+ }
232
+
233
+ const retryInfo = retryable(e, ctx.provider_id);
234
+ if (!retryInfo || attempt >= MAX_RETRIES) throw e;
235
+
236
+ const waitMs = retryDelay(attempt, e);
237
+ logger.info("llm_retry", { event: "retry", metadata: { attempt, wait_ms: waitMs, reason: retryInfo.message } });
238
+ broadcastToChannel("session_events", {
239
+ type: "session.retry", session_id: ctx.session_id, attempt, message: retryInfo.message, next: Date.now() + waitMs,
240
+ });
241
+ await new Promise(r => setTimeout(r, waitMs));
242
+ attempt++;
243
+ }
244
+ }
245
+ }
246
+
247
+ // ─── Single Stream Pass ───────────────────────────────────────────────────────
248
+
249
+ async function streamOnce(ctx: {
250
+ session_id: string; agentId: string; assistantMsgId: string;
251
+ languageModel: any; aiMessages: any[]; systemPrompt: string;
252
+ tools: Record<string, Tool>; model_id: string; provider_id: string;
253
+ stats: { tokens_in: number; tokens_out: number; cost: number };
254
+ }): Promise<{ finish_reason: string; tokens_in: number; tokens_out: number; cost: number; has_tool_calls: boolean }> {
255
+ const { session_id, assistantMsgId, languageModel, aiMessages, systemPrompt, tools, model_id, provider_id } = ctx;
256
+
257
+ const transformedMessages = ProviderTransform.message(
258
+ aiMessages,
259
+ { id: model_id, providerID: provider_id, api: { npm: providerToNpm(provider_id), id: model_id } } as any,
260
+ {}
261
+ ) as any[];
262
+
263
+ let currentTextPartId: string | null = null;
264
+ let currentTextContent = "";
265
+ let hasToolCalls = false;
266
+ let finishReason = "unknown";
267
+ const toolCallsThisTurn: Array<{ id: string; name: string; input: any }> = [];
268
+
269
+ const result = streamText({
270
+ model: languageModel,
271
+ system: systemPrompt,
272
+ messages: aiMessages as any[],
273
+ tools,
274
+ maxRetries: 0,
275
+ temperature: ProviderTransform.temperature({ id: model_id, providerID: provider_id } as any) ?? 0.7,
276
+ topP: ProviderTransform.topP({ id: model_id, providerID: provider_id } as any),
277
+ topK: ProviderTransform.topK({ id: model_id, providerID: provider_id } as any),
278
+ // Use a high maxTokens so the model can complete long responses.
279
+ // The local model's context window is the real limit.
280
+ maxTokens: parseInt(process.env.MAX_TOKENS || "8192"),
281
+ experimental_repairToolCall: async (failed: any) => {
282
+ const lower = failed.toolCall.toolName.toLowerCase();
283
+ if (lower !== failed.toolCall.toolName && tools[lower]) {
284
+ return { ...failed.toolCall, toolName: lower };
285
+ }
286
+ return { ...failed.toolCall, args: JSON.stringify({ tool: failed.toolCall.toolName, error: failed.error?.message }), toolName: "invalid" };
287
+ },
288
+ });
289
+
290
+ // Process the stream
291
+ for await (const event of result.fullStream) {
292
+ switch (event.type) {
293
+ case "text-delta": {
294
+ const text = (event as any).textDelta || (event as any).text || "";
295
+ if (!text) break;
296
+
297
+ if (!currentTextPartId) {
298
+ // Start a new text part on first delta
299
+ currentTextPartId = uuid();
300
+ currentTextContent = "";
301
+ await pool.query(
302
+ `INSERT INTO parts (id, message_id, session_id, part_type, data, order_index) VALUES ($1, $2, $3, 'text', $4, 0)`,
303
+ [currentTextPartId, assistantMsgId, session_id, JSON.stringify({ text: "" })]
304
+ );
305
+ }
306
+
307
+ currentTextContent += text;
308
+ // Broadcast delta to WebSocket part_stream for live streaming
309
+ broadcastToChannel("part_stream", {
310
+ type: "part.delta",
311
+ session_id,
312
+ message_id: assistantMsgId,
313
+ part_id: currentTextPartId,
314
+ delta: { type: "text", text },
315
+ });
316
+ break;
317
+ }
318
+
319
+ case "tool-call": {
320
+ // Flush any pending text part first
321
+ if (currentTextPartId) {
322
+ await pool.query(
323
+ `UPDATE parts SET data = $2, updated_at = NOW() WHERE id = $1`,
324
+ [currentTextPartId, JSON.stringify({ text: currentTextContent })]
325
+ );
326
+ await eventBus.publish("PartUpdated", {
327
+ session_id, message_id: assistantMsgId, part_id: currentTextPartId,
328
+ part_type: "text", updated_at: new Date().toISOString(),
329
+ }, "AgentLoop");
330
+ currentTextPartId = null;
331
+ currentTextContent = "";
332
+ }
333
+
334
+ hasToolCalls = true;
335
+ const toolArgs = (event as any).args || (event as any).input || {};
336
+ toolCallsThisTurn.push({ id: event.toolCallId, name: event.toolName, input: toolArgs });
337
+
338
+ // Record tool call in DB
339
+ const toolCallDbId = uuid();
340
+ await pool.query(
341
+ `INSERT INTO tool_calls (id, session_id, agent_id, tool_name, tool_input, state) VALUES ($1, $2, $3, $4, $5, 'running')`,
342
+ [toolCallDbId, session_id, ctx.agentId, event.toolName, JSON.stringify(toolArgs)]
343
+ );
344
+
345
+ // Record as part
346
+ const toolPartId = uuid();
347
+ await pool.query(
348
+ `INSERT INTO parts (id, message_id, session_id, part_type, data, order_index) VALUES ($1, $2, $3, 'tool_invocation', $4, 0)`,
349
+ [toolPartId, assistantMsgId, session_id, JSON.stringify({ tool_call_id: event.toolCallId, tool_name: event.toolName, args: toolArgs, state: "running" })]
350
+ );
351
+
352
+ await eventBus.publish("ToolCallRequested", {
353
+ tool_call_id: toolCallDbId, session_id, agent_id: ctx.agentId,
354
+ tool_name: event.toolName, tool_input: toolArgs, requested_at: new Date().toISOString(),
355
+ }, "AgentLoop");
356
+
357
+ // Doom-loop detection: 3 identical consecutive tool calls
358
+ const recentSame = toolCallsThisTurn.filter(
359
+ tc => tc.name === event.toolName && JSON.stringify(tc.input) === JSON.stringify(toolArgs)
360
+ );
361
+ if (recentSame.length >= DOOM_LOOP_THRESHOLD) {
362
+ logger.error("doom_loop_detected", { event: "doom_loop", metadata: { tool: event.toolName, session_id } });
363
+ throw new Error(`Doom loop detected: tool '${event.toolName}' called ${DOOM_LOOP_THRESHOLD} times with identical input`);
364
+ }
365
+ break;
366
+ }
367
+
368
+ case "step-finish": {
369
+ // Flush pending text part
370
+ if (currentTextPartId) {
371
+ await pool.query(
372
+ `UPDATE parts SET data = $2, updated_at = NOW() WHERE id = $1`,
373
+ [currentTextPartId, JSON.stringify({ text: currentTextContent })]
374
+ );
375
+ await eventBus.publish("PartUpdated", {
376
+ session_id, message_id: assistantMsgId, part_id: currentTextPartId,
377
+ part_type: "text", updated_at: new Date().toISOString(),
378
+ }, "AgentLoop");
379
+ currentTextPartId = null;
380
+ currentTextContent = "";
381
+ }
382
+
383
+ const usage = (event as any).usage;
384
+ const tokensIn = usage?.promptTokens || 0;
385
+ const tokensOut = usage?.completionTokens || 0;
386
+ ctx.stats.tokens_in += tokensIn;
387
+ ctx.stats.tokens_out += tokensOut;
388
+
389
+ await pool.query(
390
+ `UPDATE messages SET tokens_input = $2, tokens_output = $3, updated_at = NOW() WHERE id = $1`,
391
+ [assistantMsgId, tokensIn, tokensOut]
392
+ );
393
+ break;
394
+ }
395
+
396
+ case "finish": {
397
+ finishReason = (event as any).finishReason || "stop";
398
+ break;
399
+ }
400
+
401
+ case "error": {
402
+ throw (event as any).error;
403
+ }
404
+ }
405
+ }
406
+
407
+ // Final flush — ensure any pending text part is saved
408
+ if (currentTextPartId && currentTextContent) {
409
+ await pool.query(
410
+ `UPDATE parts SET data = $2, updated_at = NOW() WHERE id = $1`,
411
+ [currentTextPartId, JSON.stringify({ text: currentTextContent })]
412
+ );
413
+ await eventBus.publish("PartUpdated", {
414
+ session_id, message_id: assistantMsgId, part_id: currentTextPartId,
415
+ part_type: "text", updated_at: new Date().toISOString(),
416
+ }, "AgentLoop");
417
+ currentTextPartId = null;
418
+ currentTextContent = "";
419
+ }
420
+
421
+ // Collect final usage
422
+ let tokensIn = 0, tokensOut = 0;
423
+ try {
424
+ const finalUsage = await result.usage;
425
+ tokensIn = finalUsage?.promptTokens || ctx.stats.tokens_in;
426
+ tokensOut = finalUsage?.completionTokens || ctx.stats.tokens_out;
427
+ } catch {
428
+ tokensIn = ctx.stats.tokens_in;
429
+ tokensOut = ctx.stats.tokens_out;
430
+ }
431
+
432
+ // If we still have no text part, try getting the full text from the result
433
+ if (!currentTextPartId) {
434
+ try {
435
+ const fullText = await result.text;
436
+ if (fullText && fullText.trim()) {
437
+ const fallbackPartId = uuid();
438
+ await pool.query(
439
+ `INSERT INTO parts (id, message_id, session_id, part_type, data, order_index) VALUES ($1, $2, $3, 'text', $4, 0)`,
440
+ [fallbackPartId, assistantMsgId, session_id, JSON.stringify({ text: fullText })]
441
+ );
442
+ await eventBus.publish("PartUpdated", {
443
+ session_id, message_id: assistantMsgId, part_id: fallbackPartId,
444
+ part_type: "text", updated_at: new Date().toISOString(),
445
+ }, "AgentLoop");
446
+ }
447
+ } catch { /* non-fatal */ }
448
+ }
449
+
450
+ return {
451
+ finish_reason: finishReason,
452
+ tokens_in: tokensIn,
453
+ tokens_out: tokensOut,
454
+ cost: 0,
455
+ has_tool_calls: hasToolCalls,
456
+ };
457
+ }
458
+
459
+ // ─── Context Compaction ───────────────────────────────────────────────────────
460
+
461
+ async function runCompaction(
462
+ session_id: string,
463
+ agentId: string,
464
+ model_id: string,
465
+ provider_id: string,
466
+ messages: any[]
467
+ ): Promise<boolean> {
468
+ try {
469
+ // Transition session to compacting
470
+ await pool.query(`UPDATE sessions SET state = 'compacting', updated_at = NOW() WHERE id = $1`, [session_id]);
471
+ await eventBus.publish("SessionStateChanged", { session_id, from_state: "busy", to_state: "compacting", changed_at: new Date().toISOString() }, "AgentLoop");
472
+
473
+ // Build summary using the compaction logic (ported from OpenCode)
474
+ const summary = await buildCompactionSummary({
475
+ messages,
476
+ model_id,
477
+ provider_id,
478
+ session_id,
479
+ });
480
+
481
+ if (!summary) return false;
482
+
483
+ // Store summary as a special assistant message
484
+ const summaryMsgId = uuid();
485
+ await pool.query(
486
+ `INSERT INTO messages (id, session_id, role, model_id, provider_id) VALUES ($1, $2, 'assistant', $3, $4)`,
487
+ [summaryMsgId, session_id, model_id, provider_id]
488
+ );
489
+ const summaryPartId = uuid();
490
+ await pool.query(
491
+ `INSERT INTO parts (id, message_id, session_id, part_type, data, order_index) VALUES ($1, $2, $3, 'text', $4, 0)`,
492
+ [summaryPartId, summaryMsgId, session_id, JSON.stringify({ text: summary, is_compaction_summary: true })]
493
+ );
494
+
495
+ // Transition back to busy
496
+ await pool.query(`UPDATE sessions SET state = 'busy', updated_at = NOW() WHERE id = $1`, [session_id]);
497
+ await eventBus.publish("SessionCompacted", {
498
+ session_id, tokens_before: 0, tokens_after: 0, compacted_at: new Date().toISOString(),
499
+ }, "AgentLoop");
500
+
501
+ broadcastToChannel("session_events", { type: "session.compacted", session_id });
502
+ return true;
503
+ } catch (e: any) {
504
+ logger.error("compaction_failed", { event: "compaction_error", metadata: { session_id, error: e.message } });
505
+ await pool.query(`UPDATE sessions SET state = 'busy', updated_at = NOW() WHERE id = $1`, [session_id]);
506
+ return false;
507
+ }
508
+ }
509
+
510
+ // ─── Message History Builder ──────────────────────────────────────────────────
511
+
512
+ async function loadMessageHistory(session_id: string): Promise<any[]> {
513
+ const result = await pool.query(
514
+ `SELECT m.id, m.role, m.model_id, m.provider_id, m.tokens_input, m.tokens_output,
515
+ json_agg(p.* ORDER BY p.order_index, p.created_at) FILTER (WHERE p.id IS NOT NULL) AS parts
516
+ FROM messages m
517
+ LEFT JOIN parts p ON p.message_id = m.id
518
+ WHERE m.session_id = $1
519
+ GROUP BY m.id ORDER BY m.created_at ASC`,
520
+ [session_id]
521
+ );
522
+ return result.rows;
523
+ }
524
+
525
+ async function buildAIMessages(session_id: string, messages: any[], model_id: string): Promise<any[]> {
526
+ const aiMessages: any[] = [];
527
+
528
+ for (const msg of messages) {
529
+ const parts = msg.parts || [];
530
+
531
+ if (msg.role === "user") {
532
+ const content: any[] = [];
533
+ for (const p of parts) {
534
+ if (p.part_type === "text" && p.data?.text) {
535
+ content.push({ type: "text", text: p.data.text });
536
+ } else if (p.part_type === "file" && p.data?.url) {
537
+ content.push({ type: "file", url: p.data.url, mediaType: p.data.mime || "text/plain" });
538
+ }
539
+ }
540
+ if (content.length > 0) aiMessages.push({ role: "user", content });
541
+
542
+ } else if (msg.role === "assistant") {
543
+ const content: any[] = [];
544
+ for (const p of parts) {
545
+ if (p.part_type === "text" && p.data?.text) {
546
+ // Skip compaction summaries — they're injected as system context
547
+ if (p.data.is_compaction_summary) continue;
548
+ content.push({ type: "text", text: p.data.text });
549
+ } else if (p.part_type === "tool_invocation" && p.data?.tool_call_id) {
550
+ content.push({ type: "tool-call", toolCallId: p.data.tool_call_id, toolName: p.data.tool_name, args: p.data.args || {} });
551
+ }
552
+ }
553
+ if (content.length > 0) aiMessages.push({ role: "assistant", content });
554
+
555
+ } else if (msg.role === "tool") {
556
+ const content: any[] = [];
557
+ for (const p of parts) {
558
+ if (p.part_type === "tool_result" && p.data?.tool_call_id) {
559
+ content.push({ type: "tool-result", toolCallId: p.data.tool_call_id, toolName: p.data.tool_name, result: p.data.result });
560
+ }
561
+ }
562
+ if (content.length > 0) aiMessages.push({ role: "tool", content });
563
+ }
564
+ }
565
+
566
+ return aiMessages;
567
+ }
568
+
569
+ function getLastUserText(messages: any[]): string {
570
+ const lastUser = messages.filter(m => m.role === "user").slice(-1)[0];
571
+ if (!lastUser) return "";
572
+ return (lastUser.parts || [])
573
+ .filter((p: any) => p.part_type === "text")
574
+ .map((p: any) => p.data?.text || "")
575
+ .join(" ");
576
+ }
577
+
578
+ // ─── System Prompt with RAG ───────────────────────────────────────────────────
579
+
580
+ async function buildSystemPromptWithRAG(
581
+ session_id: string,
582
+ worktree: string,
583
+ model_id: string,
584
+ provider_id: string,
585
+ agent_name: string,
586
+ userText: string
587
+ ): Promise<string> {
588
+ // Base system prompt (provider-specific, from OpenCode)
589
+ const base = getSystemPrompt(model_id, provider_id, agent_name);
590
+
591
+ // Environment context
592
+ const envContext = [
593
+ `Working directory: ${worktree}`,
594
+ `Platform: ${process.platform}`,
595
+ `Today's date: ${new Date().toDateString()}`,
596
+ ].join("\n");
597
+
598
+ // Instruction files (AGENTS.md, CLAUDE.md, etc.)
599
+ const instructions = await loadInstructionFiles(worktree).catch(() => "");
600
+
601
+ // Codebase context — no embedding needed
602
+ // Uses FTS, git recency, session history, import graph, instruction files
603
+ let codebaseContext = "";
604
+ if (userText && worktree) {
605
+ try {
606
+ const { buildContext, formatContextForPrompt } = await import("../../context_builder");
607
+ const sessionRow = await pool.query(
608
+ `SELECT project_id FROM sessions WHERE id = $1`,
609
+ [session_id]
610
+ );
611
+ const project_id = sessionRow.rows[0]?.project_id || "";
612
+
613
+ const ctxResult = await buildContext({
614
+ session_id,
615
+ project_id,
616
+ worktree,
617
+ query: userText,
618
+ max_chunks: 10,
619
+ max_chars: 10_000,
620
+ });
621
+
622
+ codebaseContext = formatContextForPrompt(ctxResult, worktree);
623
+ if (ctxResult.summary !== "no relevant context found") {
624
+ logger.info("context_injected", { event: "context", metadata: { summary: ctxResult.summary } });
625
+ }
626
+ } catch (e: any) {
627
+ logger.error("context_build_failed", { event: "context", metadata: { error: e.message } });
628
+ }
629
+ }
630
+
631
+ return [base, envContext, instructions, codebaseContext].filter(Boolean).join("\n\n");
632
+ }
633
+
634
+ // ─── Language Model Factory ───────────────────────────────────────────────────
635
+
636
+ function getLanguageModel(provider_id: string, model_id: string): any {
637
+ const apiKey = process.env[`${provider_id.toUpperCase()}_API_KEY`] || process.env.OPENAI_API_KEY || "not-needed";
638
+ const baseUrl = process.env[`${provider_id.toUpperCase()}_BASE_URL`] || process.env.OPENAI_BASE_URL;
639
+
640
+ switch (provider_id.toLowerCase()) {
641
+ case "anthropic":
642
+ return createAnthropic({ apiKey, baseURL: baseUrl })(model_id);
643
+ case "google":
644
+ return createGoogleGenerativeAI({ apiKey })(model_id);
645
+ case "openai":
646
+ case "openai_compatible":
647
+ default:
648
+ return createOpenAI({ apiKey, baseURL: baseUrl })(model_id);
649
+ }
650
+ }
651
+
652
+ function providerToNpm(provider_id: string): string {
653
+ const map: Record<string, string> = {
654
+ anthropic: "@ai-sdk/anthropic",
655
+ openai: "@ai-sdk/openai",
656
+ google: "@ai-sdk/google",
657
+ openai_compatible: "@ai-sdk/openai-compatible",
658
+ };
659
+ return map[provider_id.toLowerCase()] || "@ai-sdk/openai-compatible";
660
+ }
661
+
662
+ function supportsTools(model_id: string): boolean {
663
+ const id = model_id.toLowerCase();
664
+ // Known models that support function calling
665
+ if (id.includes("gpt-") || id.includes("claude") || id.includes("gemini")) return true;
666
+ // Qwen3 and other local models — check env override
667
+ if (process.env.MODEL_SUPPORTS_TOOLS === "true") return true;
668
+ if (process.env.MODEL_SUPPORTS_TOOLS === "false") return false;
669
+ // Default: try with tools, fall back gracefully on error
670
+ return true;
671
+ }