kimaki 0.31.0 → 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 (989) hide show
  1. package/dist/agent-ui.d.ts +33 -0
  2. package/dist/agent-ui.e2e.test.d.ts +1 -0
  3. package/dist/agent-ui.e2e.test.js +140 -0
  4. package/dist/agent-ui.e2e.test.js.map +1 -0
  5. package/dist/agent-ui.js +199 -0
  6. package/dist/agent-ui.js.map +1 -0
  7. package/dist/analytics.d.ts +42 -0
  8. package/dist/analytics.e2e.test.d.ts +1 -0
  9. package/dist/analytics.e2e.test.js +112 -0
  10. package/dist/analytics.e2e.test.js.map +1 -0
  11. package/dist/analytics.js +162 -223
  12. package/dist/analytics.js.map +1 -0
  13. package/dist/analytics.test.d.ts +1 -0
  14. package/dist/analytics.test.js +121 -157
  15. package/dist/analytics.test.js.map +1 -0
  16. package/dist/autostart.d.ts +7 -0
  17. package/dist/autostart.js +75 -0
  18. package/dist/autostart.js.map +1 -0
  19. package/dist/bot.d.ts +90 -0
  20. package/dist/bot.js +120 -0
  21. package/dist/bot.js.map +1 -0
  22. package/dist/btw.e2e.test.d.ts +1 -0
  23. package/dist/btw.e2e.test.js +102 -0
  24. package/dist/btw.e2e.test.js.map +1 -0
  25. package/dist/catalog-refresh.e2e.test.d.ts +1 -0
  26. package/dist/catalog-refresh.e2e.test.js +90 -0
  27. package/dist/catalog-refresh.e2e.test.js.map +1 -0
  28. package/dist/cli/bot.d.ts +5 -0
  29. package/dist/cli/bot.js +427 -0
  30. package/dist/cli/bot.js.map +1 -0
  31. package/dist/cli/discord.d.ts +3 -0
  32. package/dist/cli/discord.js +118 -0
  33. package/dist/cli/discord.js.map +1 -0
  34. package/dist/cli/project.d.ts +5 -0
  35. package/dist/cli/project.js +143 -0
  36. package/dist/cli/project.js.map +1 -0
  37. package/dist/cli/schedule.d.ts +3 -0
  38. package/dist/cli/schedule.js +156 -0
  39. package/dist/cli/schedule.js.map +1 -0
  40. package/dist/cli/session.d.ts +4 -0
  41. package/dist/cli/session.js +281 -0
  42. package/dist/cli/session.js.map +1 -0
  43. package/dist/cli/shared.d.ts +55 -0
  44. package/dist/cli/shared.js +138 -0
  45. package/dist/cli/shared.js.map +1 -0
  46. package/dist/cli-discord.e2e.test.d.ts +1 -0
  47. package/dist/cli-discord.e2e.test.js +64 -0
  48. package/dist/cli-discord.e2e.test.js.map +1 -0
  49. package/dist/cli-lock.e2e.test.d.ts +1 -0
  50. package/dist/cli-lock.e2e.test.js +520 -0
  51. package/dist/cli-lock.e2e.test.js.map +1 -0
  52. package/dist/cli-read.e2e.test.d.ts +1 -0
  53. package/dist/cli-read.e2e.test.js +81 -0
  54. package/dist/cli-read.e2e.test.js.map +1 -0
  55. package/dist/cli-sessions.e2e.test.d.ts +1 -0
  56. package/dist/cli-sessions.e2e.test.js +116 -0
  57. package/dist/cli-sessions.e2e.test.js.map +1 -0
  58. package/dist/cli.d.ts +2 -0
  59. package/dist/cli.js +34 -263
  60. package/dist/cli.js.map +1 -0
  61. package/dist/commands/login-commands.d.ts +59 -0
  62. package/dist/commands/login-commands.js +341 -0
  63. package/dist/commands/login-commands.js.map +1 -0
  64. package/dist/commands/preference-commands.d.ts +44 -0
  65. package/dist/commands/preference-commands.js +551 -0
  66. package/dist/commands/preference-commands.js.map +1 -0
  67. package/dist/commands/session-commands.d.ts +8 -0
  68. package/dist/commands/session-commands.js +267 -0
  69. package/dist/commands/session-commands.js.map +1 -0
  70. package/dist/commands/thread-commands.d.ts +16 -0
  71. package/dist/commands/thread-commands.js +285 -0
  72. package/dist/commands/thread-commands.js.map +1 -0
  73. package/dist/commands/worktree-commands.d.ts +41 -0
  74. package/dist/commands/worktree-commands.js +279 -0
  75. package/dist/commands/worktree-commands.js.map +1 -0
  76. package/dist/commands.e2e.test.d.ts +1 -0
  77. package/dist/commands.e2e.test.js +255 -0
  78. package/dist/commands.e2e.test.js.map +1 -0
  79. package/dist/credentials.d.ts +84 -0
  80. package/dist/credentials.js +320 -0
  81. package/dist/credentials.js.map +1 -0
  82. package/dist/db.d.ts +1532 -0
  83. package/dist/db.js +57 -245
  84. package/dist/db.js.map +1 -0
  85. package/dist/db.test.d.ts +1 -0
  86. package/dist/db.test.js +351 -682
  87. package/dist/db.test.js.map +1 -0
  88. package/dist/duration.d.ts +2 -0
  89. package/dist/duration.js +18 -0
  90. package/dist/duration.js.map +1 -0
  91. package/dist/effects.d.ts +15 -0
  92. package/dist/effects.js +291 -0
  93. package/dist/effects.js.map +1 -0
  94. package/dist/errors.d.ts +38 -0
  95. package/dist/errors.js +40 -175
  96. package/dist/errors.js.map +1 -0
  97. package/dist/event-loop.d.ts +40 -0
  98. package/dist/event-loop.js +414 -0
  99. package/dist/event-loop.js.map +1 -0
  100. package/dist/file-edit-log.d.ts +41 -0
  101. package/dist/file-edit-log.js +122 -164
  102. package/dist/file-edit-log.js.map +1 -0
  103. package/dist/file-edit-log.test.d.ts +1 -0
  104. package/dist/file-edit-log.test.js +68 -244
  105. package/dist/file-edit-log.test.js.map +1 -0
  106. package/dist/format-parts.d.ts +137 -0
  107. package/dist/format-parts.js +300 -0
  108. package/dist/format-parts.js.map +1 -0
  109. package/dist/format-parts.test.d.ts +1 -0
  110. package/dist/format-parts.test.js +144 -0
  111. package/dist/format-parts.test.js.map +1 -0
  112. package/dist/gateway-replay.e2e.test.d.ts +1 -0
  113. package/dist/gateway-replay.e2e.test.js +95 -0
  114. package/dist/gateway-replay.e2e.test.js.map +1 -0
  115. package/dist/harness.e2e.test.d.ts +1 -0
  116. package/dist/harness.e2e.test.js +107 -0
  117. package/dist/harness.e2e.test.js.map +1 -0
  118. package/dist/hello-thread.e2e.test.d.ts +1 -0
  119. package/dist/hello-thread.e2e.test.js +194 -0
  120. package/dist/hello-thread.e2e.test.js.map +1 -0
  121. package/dist/ingress.d.ts +7 -0
  122. package/dist/ingress.js +337 -0
  123. package/dist/ingress.js.map +1 -0
  124. package/dist/interaction-context.d.ts +45 -0
  125. package/dist/interaction-context.js +77 -0
  126. package/dist/interaction-context.js.map +1 -0
  127. package/dist/interrupt.e2e.test.d.ts +1 -0
  128. package/dist/interrupt.e2e.test.js +124 -0
  129. package/dist/interrupt.e2e.test.js.map +1 -0
  130. package/dist/legacy.e2e.test.d.ts +1 -0
  131. package/dist/legacy.e2e.test.js +85 -0
  132. package/dist/legacy.e2e.test.js.map +1 -0
  133. package/dist/live-call.d.ts +51 -0
  134. package/dist/live-call.js +207 -0
  135. package/dist/live-call.js.map +1 -0
  136. package/dist/lock-routes.d.ts +812 -0
  137. package/dist/lock-routes.js +635 -0
  138. package/dist/lock-routes.js.map +1 -0
  139. package/dist/lock-server.d.ts +36 -0
  140. package/dist/lock-server.js +223 -0
  141. package/dist/lock-server.js.map +1 -0
  142. package/dist/logger.d.ts +13 -0
  143. package/dist/logger.js +112 -162
  144. package/dist/logger.js.map +1 -0
  145. package/dist/login.e2e.test.d.ts +1 -0
  146. package/dist/login.e2e.test.js +83 -0
  147. package/dist/login.e2e.test.js.map +1 -0
  148. package/dist/main.d.ts +32 -0
  149. package/dist/main.js +190 -0
  150. package/dist/main.js.map +1 -0
  151. package/dist/markdown/components.d.ts +10 -0
  152. package/dist/markdown/components.js +77 -0
  153. package/dist/markdown/components.js.map +1 -0
  154. package/dist/markdown/markdown.test.d.ts +1 -0
  155. package/dist/markdown/markdown.test.js +393 -0
  156. package/dist/markdown/markdown.test.js.map +1 -0
  157. package/dist/markdown/render-markdown.d.ts +30 -0
  158. package/dist/markdown/render-markdown.js +265 -0
  159. package/dist/markdown/render-markdown.js.map +1 -0
  160. package/dist/message-tool.e2e.test.d.ts +1 -0
  161. package/dist/message-tool.e2e.test.js +39 -0
  162. package/dist/message-tool.e2e.test.js.map +1 -0
  163. package/dist/migrations.d.ts +8 -0
  164. package/dist/migrations.js +187 -0
  165. package/dist/migrations.js.map +1 -0
  166. package/dist/onboarding-cli.e2e.test.d.ts +1 -0
  167. package/dist/onboarding-cli.e2e.test.js +178 -0
  168. package/dist/onboarding-cli.e2e.test.js.map +1 -0
  169. package/dist/onboarding.d.ts +36 -0
  170. package/dist/onboarding.e2e.test.d.ts +1 -0
  171. package/dist/onboarding.e2e.test.js +148 -0
  172. package/dist/onboarding.e2e.test.js.map +1 -0
  173. package/dist/onboarding.js +261 -0
  174. package/dist/onboarding.js.map +1 -0
  175. package/dist/opencode-server.d.ts +54 -0
  176. package/dist/opencode-server.js +413 -0
  177. package/dist/opencode-server.js.map +1 -0
  178. package/dist/opencode-server.test.d.ts +1 -0
  179. package/dist/opencode-server.test.js +21 -0
  180. package/dist/opencode-server.test.js.map +1 -0
  181. package/dist/permissions.d.ts +31 -0
  182. package/dist/permissions.e2e.test.d.ts +1 -0
  183. package/dist/permissions.e2e.test.js +166 -0
  184. package/dist/permissions.e2e.test.js.map +1 -0
  185. package/dist/permissions.js +95 -0
  186. package/dist/permissions.js.map +1 -0
  187. package/dist/plugin/anthropic/index.d.ts +13 -0
  188. package/dist/plugin/anthropic/index.js +201 -0
  189. package/dist/plugin/anthropic/index.js.map +1 -0
  190. package/dist/plugin/anthropic/index.test.d.ts +1 -0
  191. package/dist/plugin/anthropic/index.test.js +39 -0
  192. package/dist/plugin/anthropic/index.test.js.map +1 -0
  193. package/dist/plugin/index.d.ts +3 -0
  194. package/dist/plugin/index.js +106 -0
  195. package/dist/plugin/index.js.map +1 -0
  196. package/dist/plugin/session-id/index.d.ts +3 -0
  197. package/dist/plugin/session-id/index.js +18 -0
  198. package/dist/plugin/session-id/index.js.map +1 -0
  199. package/dist/plugin.e2e.test.d.ts +1 -0
  200. package/dist/plugin.e2e.test.js +87 -0
  201. package/dist/plugin.e2e.test.js.map +1 -0
  202. package/dist/preference-commands.e2e.test.d.ts +1 -0
  203. package/dist/preference-commands.e2e.test.js +555 -0
  204. package/dist/preference-commands.e2e.test.js.map +1 -0
  205. package/dist/probe-live.tmp.d.ts +1 -0
  206. package/dist/probe-live.tmp.js +82 -0
  207. package/dist/probe-live.tmp.js.map +1 -0
  208. package/dist/project.d.ts +58 -0
  209. package/dist/project.js +169 -0
  210. package/dist/project.js.map +1 -0
  211. package/dist/prompt.d.ts +112 -0
  212. package/dist/prompt.js +578 -0
  213. package/dist/prompt.js.map +1 -0
  214. package/dist/questions.d.ts +59 -0
  215. package/dist/questions.e2e.test.d.ts +1 -0
  216. package/dist/questions.e2e.test.js +290 -0
  217. package/dist/questions.e2e.test.js.map +1 -0
  218. package/dist/questions.js +231 -0
  219. package/dist/questions.js.map +1 -0
  220. package/dist/queue.d.ts +45 -0
  221. package/dist/queue.e2e.test.d.ts +1 -0
  222. package/dist/queue.e2e.test.js +220 -0
  223. package/dist/queue.e2e.test.js.map +1 -0
  224. package/dist/queue.js +132 -0
  225. package/dist/queue.js.map +1 -0
  226. package/dist/remote-send.d.ts +32 -0
  227. package/dist/remote-send.e2e.test.d.ts +1 -0
  228. package/dist/remote-send.e2e.test.js +89 -0
  229. package/dist/remote-send.e2e.test.js.map +1 -0
  230. package/dist/remote-send.js +105 -0
  231. package/dist/remote-send.js.map +1 -0
  232. package/dist/renderer.e2e.test.d.ts +1 -0
  233. package/dist/renderer.e2e.test.js +357 -0
  234. package/dist/renderer.e2e.test.js.map +1 -0
  235. package/dist/restart.e2e.test.d.ts +1 -0
  236. package/dist/restart.e2e.test.js +68 -0
  237. package/dist/restart.e2e.test.js.map +1 -0
  238. package/dist/routes.d.ts +24 -0
  239. package/dist/routes.js +42 -0
  240. package/dist/routes.js.map +1 -0
  241. package/dist/routes.test.d.ts +1 -0
  242. package/dist/routes.test.js +79 -0
  243. package/dist/routes.test.js.map +1 -0
  244. package/dist/routines.e2e.test.d.ts +1 -0
  245. package/dist/routines.e2e.test.js +188 -0
  246. package/dist/routines.e2e.test.js.map +1 -0
  247. package/dist/scheduler.d.ts +116 -0
  248. package/dist/scheduler.e2e.test.d.ts +1 -0
  249. package/dist/scheduler.e2e.test.js +163 -0
  250. package/dist/scheduler.e2e.test.js.map +1 -0
  251. package/dist/scheduler.js +602 -0
  252. package/dist/scheduler.js.map +1 -0
  253. package/dist/scheduler.test.d.ts +1 -0
  254. package/dist/scheduler.test.js +180 -0
  255. package/dist/scheduler.test.js.map +1 -0
  256. package/dist/schema-sql.d.ts +1 -0
  257. package/dist/schema-sql.js +3 -0
  258. package/dist/schema-sql.js.map +1 -0
  259. package/dist/schema.d.ts +2968 -0
  260. package/dist/schema.js +95 -252
  261. package/dist/schema.js.map +1 -0
  262. package/dist/session-commands.e2e.test.d.ts +1 -0
  263. package/dist/session-commands.e2e.test.js +285 -0
  264. package/dist/session-commands.e2e.test.js.map +1 -0
  265. package/dist/session-events.d.ts +55 -0
  266. package/dist/session-events.js +190 -0
  267. package/dist/session-events.js.map +1 -0
  268. package/dist/session-ops.e2e.test.d.ts +1 -0
  269. package/dist/session-ops.e2e.test.js +114 -0
  270. package/dist/session-ops.e2e.test.js.map +1 -0
  271. package/dist/sessions.d.ts +121 -0
  272. package/dist/sessions.js +662 -0
  273. package/dist/sessions.js.map +1 -0
  274. package/dist/shell.e2e.test.d.ts +1 -0
  275. package/dist/shell.e2e.test.js +147 -0
  276. package/dist/shell.e2e.test.js.map +1 -0
  277. package/dist/slash-commands.d.ts +62 -0
  278. package/dist/slash-commands.js +475 -0
  279. package/dist/slash-commands.js.map +1 -0
  280. package/dist/slash-commands.test.d.ts +1 -0
  281. package/dist/slash-commands.test.js +87 -0
  282. package/dist/slash-commands.test.js.map +1 -0
  283. package/dist/sleep.e2e.test.d.ts +1 -0
  284. package/dist/sleep.e2e.test.js +115 -487
  285. package/dist/sleep.e2e.test.js.map +1 -0
  286. package/dist/sleeps.d.ts +37 -0
  287. package/dist/sleeps.js +220 -0
  288. package/dist/sleeps.js.map +1 -0
  289. package/dist/store.d.ts +10 -0
  290. package/dist/store.js +8 -28
  291. package/dist/store.js.map +1 -0
  292. package/dist/system-prompt.d.ts +65 -0
  293. package/dist/system-prompt.js +875 -0
  294. package/dist/system-prompt.js.map +1 -0
  295. package/dist/tasks.e2e.test.d.ts +1 -0
  296. package/dist/tasks.e2e.test.js +173 -0
  297. package/dist/tasks.e2e.test.js.map +1 -0
  298. package/dist/test/auth-plugin/index.d.ts +3 -0
  299. package/dist/test/auth-plugin/index.js +18 -0
  300. package/dist/test/auth-plugin/index.js.map +1 -0
  301. package/dist/test/fake-mcp-server.d.ts +1 -0
  302. package/dist/test/fake-mcp-server.js +45 -0
  303. package/dist/test/fake-mcp-server.js.map +1 -0
  304. package/dist/test/harness.d.ts +246 -0
  305. package/dist/test/harness.js +704 -0
  306. package/dist/test/harness.js.map +1 -0
  307. package/dist/test/replay.d.ts +14 -0
  308. package/dist/test/replay.js +75 -0
  309. package/dist/test/replay.js.map +1 -0
  310. package/dist/thread-reducer.d.ts +172 -0
  311. package/dist/thread-reducer.fixtures.test.d.ts +1 -0
  312. package/dist/thread-reducer.fixtures.test.js +360 -0
  313. package/dist/thread-reducer.fixtures.test.js.map +1 -0
  314. package/dist/thread-reducer.js +497 -0
  315. package/dist/thread-reducer.js.map +1 -0
  316. package/dist/thread-reducer.test.d.ts +1 -0
  317. package/dist/thread-reducer.test.js +369 -0
  318. package/dist/thread-reducer.test.js.map +1 -0
  319. package/dist/tts.test.d.ts +1 -0
  320. package/dist/tts.test.js +147 -0
  321. package/dist/tts.test.js.map +1 -0
  322. package/dist/voice-agent.d.ts +33 -0
  323. package/dist/voice-agent.js +159 -0
  324. package/dist/voice-agent.js.map +1 -0
  325. package/dist/voice-attachments.e2e.test.d.ts +1 -0
  326. package/dist/voice-attachments.e2e.test.js +253 -0
  327. package/dist/voice-attachments.e2e.test.js.map +1 -0
  328. package/dist/voice-call.d.ts +59 -0
  329. package/dist/voice-call.e2e.test.d.ts +1 -0
  330. package/dist/voice-call.e2e.test.js +259 -0
  331. package/dist/voice-call.e2e.test.js.map +1 -0
  332. package/dist/voice-call.js +760 -0
  333. package/dist/voice-call.js.map +1 -0
  334. package/dist/voice-openai.e2e.test.d.ts +1 -0
  335. package/dist/voice-openai.e2e.test.js +161 -0
  336. package/dist/voice-openai.e2e.test.js.map +1 -0
  337. package/dist/voice.d.ts +116 -0
  338. package/dist/voice.js +619 -717
  339. package/dist/voice.js.map +1 -0
  340. package/dist/working-directory.e2e.test.d.ts +1 -0
  341. package/dist/working-directory.e2e.test.js +231 -0
  342. package/dist/working-directory.e2e.test.js.map +1 -0
  343. package/dist/worktrees.d.ts +54 -0
  344. package/dist/worktrees.js +247 -709
  345. package/dist/worktrees.js.map +1 -0
  346. package/dist/worktrees.test.d.ts +1 -0
  347. package/dist/worktrees.test.js +97 -952
  348. package/dist/worktrees.test.js.map +1 -0
  349. package/package.json +38 -74
  350. package/src/agent-ui.e2e.test.ts +134 -0
  351. package/src/agent-ui.ts +216 -0
  352. package/src/analytics.e2e.test.ts +138 -0
  353. package/src/analytics.test.ts +122 -182
  354. package/src/analytics.ts +206 -258
  355. package/src/autostart.ts +78 -0
  356. package/src/bot.ts +194 -0
  357. package/src/btw.e2e.test.ts +130 -0
  358. package/src/catalog-refresh.e2e.test.ts +112 -0
  359. package/src/cli/bot.ts +419 -0
  360. package/src/cli/discord.ts +116 -0
  361. package/src/cli/project.ts +147 -0
  362. package/src/cli/schedule.ts +151 -0
  363. package/src/cli/session.ts +285 -0
  364. package/src/cli/shared.ts +139 -0
  365. package/src/cli-discord.e2e.test.ts +62 -0
  366. package/src/cli-lock.e2e.test.ts +530 -0
  367. package/src/cli-read.e2e.test.ts +78 -0
  368. package/src/cli-sessions.e2e.test.ts +122 -0
  369. package/src/cli.ts +35 -418
  370. package/src/commands/login-commands.ts +360 -0
  371. package/src/commands/preference-commands.ts +586 -0
  372. package/src/commands/session-commands.ts +289 -0
  373. package/src/commands/thread-commands.ts +324 -0
  374. package/src/commands/worktree-commands.ts +288 -0
  375. package/src/commands.e2e.test.ts +290 -0
  376. package/src/credentials.ts +425 -0
  377. package/src/db.test.ts +344 -759
  378. package/src/db.ts +66 -284
  379. package/src/duration.ts +18 -0
  380. package/src/effects.ts +303 -0
  381. package/src/errors.ts +39 -228
  382. package/src/event-loop.ts +462 -0
  383. package/src/file-edit-log.test.ts +68 -272
  384. package/src/file-edit-log.ts +108 -231
  385. package/src/fixtures/v1-schema.sql +241 -0
  386. package/src/fixtures/voice.ogg +0 -0
  387. package/src/format-parts.test.ts +150 -0
  388. package/src/format-parts.ts +405 -0
  389. package/src/gateway-replay.e2e.test.ts +116 -0
  390. package/src/harness.e2e.test.ts +124 -0
  391. package/src/hello-thread.e2e.test.ts +223 -0
  392. package/src/ingress.ts +334 -0
  393. package/src/interaction-context.ts +131 -0
  394. package/src/interrupt.e2e.test.ts +151 -0
  395. package/src/legacy.e2e.test.ts +105 -0
  396. package/src/live-call.ts +249 -0
  397. package/src/lock-routes.ts +625 -0
  398. package/src/lock-server.ts +233 -0
  399. package/src/logger.ts +120 -207
  400. package/src/login.e2e.test.ts +86 -0
  401. package/src/main.ts +230 -0
  402. package/src/markdown/components.ts +102 -0
  403. package/src/markdown/markdown.test.ts +414 -0
  404. package/src/markdown/render-markdown.ts +288 -0
  405. package/src/migrations.ts +198 -0
  406. package/src/onboarding-cli.e2e.test.ts +215 -0
  407. package/src/onboarding.e2e.test.ts +171 -0
  408. package/src/onboarding.ts +297 -0
  409. package/src/opencode-server.test.ts +24 -0
  410. package/src/opencode-server.ts +461 -0
  411. package/src/permissions.e2e.test.ts +191 -0
  412. package/src/permissions.ts +121 -0
  413. package/src/plugin/anthropic/index.test.ts +39 -0
  414. package/src/plugin/anthropic/index.ts +213 -0
  415. package/src/plugin/index.ts +93 -0
  416. package/src/plugin/session-id/index.ts +19 -0
  417. package/src/plugin.e2e.test.ts +112 -0
  418. package/src/preference-commands.e2e.test.ts +597 -0
  419. package/src/project.ts +216 -0
  420. package/src/prompt.ts +664 -0
  421. package/src/questions.e2e.test.ts +328 -0
  422. package/src/questions.ts +322 -0
  423. package/src/queue.e2e.test.ts +253 -0
  424. package/src/queue.ts +158 -0
  425. package/src/remote-send.e2e.test.ts +91 -0
  426. package/src/remote-send.ts +110 -0
  427. package/src/renderer.e2e.test.ts +390 -0
  428. package/src/restart.e2e.test.ts +90 -0
  429. package/src/routes.test.ts +81 -0
  430. package/src/routes.ts +48 -0
  431. package/src/routines.e2e.test.ts +210 -0
  432. package/src/scheduler.e2e.test.ts +182 -0
  433. package/src/scheduler.test.ts +183 -0
  434. package/src/scheduler.ts +648 -0
  435. package/src/schema-sql.ts +3 -0
  436. package/src/schema.ts +95 -271
  437. package/src/session-commands.e2e.test.ts +326 -0
  438. package/src/session-events.ts +218 -0
  439. package/src/session-ops.e2e.test.ts +141 -0
  440. package/src/sessions.ts +803 -0
  441. package/src/shell.e2e.test.ts +173 -0
  442. package/src/slash-commands.test.ts +89 -0
  443. package/src/slash-commands.ts +539 -0
  444. package/src/sleep.e2e.test.ts +135 -552
  445. package/src/sleeps.ts +230 -0
  446. package/src/store.ts +19 -217
  447. package/src/system-prompt.ts +944 -0
  448. package/src/test/auth-plugin/index.ts +17 -0
  449. package/src/test/fake-mcp-server.ts +49 -0
  450. package/src/test/harness.ts +957 -0
  451. package/src/test/replay.ts +90 -0
  452. package/src/thread-reducer.fixtures.test.ts +374 -0
  453. package/src/thread-reducer.test.ts +380 -0
  454. package/src/thread-reducer.ts +671 -0
  455. package/src/tts.test.ts +150 -0
  456. package/src/voice-agent.ts +196 -0
  457. package/src/voice-attachments.e2e.test.ts +298 -0
  458. package/src/voice-call.e2e.test.ts +293 -0
  459. package/src/voice-call.ts +764 -0
  460. package/src/voice-openai.e2e.test.ts +191 -0
  461. package/src/voice.ts +668 -988
  462. package/src/working-directory.e2e.test.ts +226 -0
  463. package/src/worktrees.test.ts +89 -1115
  464. package/src/worktrees.ts +195 -957
  465. package/bin.js +0 -2
  466. package/dist/agent-model.e2e.test.js +0 -1223
  467. package/dist/ai-tool-to-genai.js +0 -233
  468. package/dist/ai-tool-to-genai.test.js +0 -267
  469. package/dist/ai-tool.js +0 -6
  470. package/dist/anthropic-account-identity.js +0 -70
  471. package/dist/anthropic-account-identity.test.js +0 -38
  472. package/dist/anthropic-auth-plugin.js +0 -958
  473. package/dist/anthropic-auth-plugin.test.js +0 -115
  474. package/dist/anthropic-auth-state.js +0 -186
  475. package/dist/anthropic-auth-state.test.js +0 -203
  476. package/dist/bash-tool-schema-plugin.js +0 -50
  477. package/dist/bash-tool-schema-plugin.test.js +0 -68
  478. package/dist/bin.js +0 -151
  479. package/dist/bot-lifecycle.test.js +0 -206
  480. package/dist/btw-prefix-detection.js +0 -24
  481. package/dist/btw-prefix-detection.test.js +0 -130
  482. package/dist/cache-drift-plugin.js +0 -183
  483. package/dist/cache-rewrite.js +0 -39
  484. package/dist/cache-rewrite.test.js +0 -41
  485. package/dist/channel-management.js +0 -347
  486. package/dist/cli-commands/bot.js +0 -254
  487. package/dist/cli-commands/maintenance.js +0 -171
  488. package/dist/cli-commands/misc.js +0 -153
  489. package/dist/cli-commands/project.js +0 -510
  490. package/dist/cli-commands/send.js +0 -659
  491. package/dist/cli-commands/session.js +0 -889
  492. package/dist/cli-commands/task.js +0 -277
  493. package/dist/cli-commands/thread.js +0 -63
  494. package/dist/cli-commands/user.js +0 -145
  495. package/dist/cli-parsing.test.js +0 -226
  496. package/dist/cli-runner.js +0 -1591
  497. package/dist/cli-runner.test.js +0 -119
  498. package/dist/cli-send-thread.e2e.test.js +0 -470
  499. package/dist/cloudflare-transcription.js +0 -56
  500. package/dist/commands/abort.js +0 -84
  501. package/dist/commands/action-buttons.js +0 -248
  502. package/dist/commands/add-project.js +0 -111
  503. package/dist/commands/agent.js +0 -658
  504. package/dist/commands/ask-question.js +0 -371
  505. package/dist/commands/ask-question.test.js +0 -150
  506. package/dist/commands/btw.js +0 -275
  507. package/dist/commands/compact.js +0 -117
  508. package/dist/commands/context-usage.js +0 -194
  509. package/dist/commands/context-usage.test.js +0 -49
  510. package/dist/commands/create-new-project.js +0 -138
  511. package/dist/commands/diff.js +0 -63
  512. package/dist/commands/file-upload.js +0 -280
  513. package/dist/commands/fork-subagent.js +0 -175
  514. package/dist/commands/fork.js +0 -291
  515. package/dist/commands/gemini-apikey.js +0 -123
  516. package/dist/commands/last-sessions.js +0 -129
  517. package/dist/commands/login.js +0 -893
  518. package/dist/commands/mcp.js +0 -239
  519. package/dist/commands/memory-snapshot.js +0 -24
  520. package/dist/commands/mention-mode.js +0 -42
  521. package/dist/commands/merge-worktree.js +0 -205
  522. package/dist/commands/model-variant.js +0 -390
  523. package/dist/commands/model.js +0 -1012
  524. package/dist/commands/multioauth.js +0 -425
  525. package/dist/commands/new-worktree.js +0 -625
  526. package/dist/commands/paginated-select.js +0 -57
  527. package/dist/commands/permissions.js +0 -327
  528. package/dist/commands/queue.js +0 -347
  529. package/dist/commands/queue.test.js +0 -15
  530. package/dist/commands/remove-project.js +0 -111
  531. package/dist/commands/restart-opencode-server.js +0 -133
  532. package/dist/commands/resume.js +0 -148
  533. package/dist/commands/run-command.js +0 -79
  534. package/dist/commands/screenshare.js +0 -307
  535. package/dist/commands/screenshare.test.js +0 -20
  536. package/dist/commands/session-id.js +0 -78
  537. package/dist/commands/session.js +0 -248
  538. package/dist/commands/share.js +0 -80
  539. package/dist/commands/tasks.js +0 -296
  540. package/dist/commands/types.js +0 -2
  541. package/dist/commands/undo-redo.js +0 -313
  542. package/dist/commands/unset-model.js +0 -116
  543. package/dist/commands/upgrade.js +0 -41
  544. package/dist/commands/user-command.js +0 -196
  545. package/dist/commands/verbosity.js +0 -124
  546. package/dist/commands/vscode.js +0 -295
  547. package/dist/commands/worktrees.js +0 -548
  548. package/dist/condense-memory.js +0 -33
  549. package/dist/config.js +0 -126
  550. package/dist/context-awareness-plugin.js +0 -357
  551. package/dist/context-awareness-plugin.test.js +0 -126
  552. package/dist/cpu-profiler.js +0 -211
  553. package/dist/cpu-profiler.test.js +0 -160
  554. package/dist/critique-utils.js +0 -95
  555. package/dist/database.js +0 -1074
  556. package/dist/debounce-timeout.js +0 -28
  557. package/dist/debounced-process-flush.js +0 -77
  558. package/dist/default-channel-provisioning.test.js +0 -149
  559. package/dist/discord-bot.js +0 -1432
  560. package/dist/discord-command-registration.js +0 -519
  561. package/dist/discord-command-registration.test.js +0 -17
  562. package/dist/discord-urls.js +0 -81
  563. package/dist/discord-utils.js +0 -789
  564. package/dist/discord-utils.test.js +0 -400
  565. package/dist/escape-backticks.test.js +0 -429
  566. package/dist/event-stream-real-capture.e2e.test.js +0 -533
  567. package/dist/eventsource-parser.test.js +0 -327
  568. package/dist/exec-async.js +0 -38
  569. package/dist/external-directory-permissions.e2e.test.js +0 -86
  570. package/dist/external-directory-project-deny.e2e.test.js +0 -70
  571. package/dist/external-opencode-sync.js +0 -620
  572. package/dist/external-opencode-sync.test.js +0 -206
  573. package/dist/extract-sdk-error-message.test.js +0 -13
  574. package/dist/format-tables.js +0 -728
  575. package/dist/format-tables.test.js +0 -915
  576. package/dist/forum-sync/config.js +0 -79
  577. package/dist/forum-sync/discord-operations.js +0 -157
  578. package/dist/forum-sync/index.js +0 -5
  579. package/dist/forum-sync/markdown.js +0 -110
  580. package/dist/forum-sync/sync-to-discord.js +0 -417
  581. package/dist/forum-sync/sync-to-files.js +0 -190
  582. package/dist/forum-sync/types.js +0 -53
  583. package/dist/forum-sync/watchers.js +0 -322
  584. package/dist/gateway-proxy-reconnect.e2e.test.js +0 -414
  585. package/dist/gateway-proxy.e2e.test.js +0 -488
  586. package/dist/genai-worker-wrapper.js +0 -111
  587. package/dist/genai-worker.js +0 -309
  588. package/dist/genai.js +0 -232
  589. package/dist/git-worktree-core.js +0 -461
  590. package/dist/guild-categories.test.js +0 -225
  591. package/dist/heap-monitor.js +0 -122
  592. package/dist/hrana-server.js +0 -322
  593. package/dist/hrana-server.test.js +0 -99
  594. package/dist/html-actions.js +0 -123
  595. package/dist/html-actions.test.js +0 -70
  596. package/dist/html-components.js +0 -115
  597. package/dist/html-components.test.js +0 -34
  598. package/dist/image-optimizer-plugin.js +0 -153
  599. package/dist/image-utils.js +0 -112
  600. package/dist/interaction-handler.js +0 -578
  601. package/dist/ipc-polling.js +0 -245
  602. package/dist/ipc-tools-plugin.js +0 -251
  603. package/dist/kimaki-digital-twin.e2e.test.js +0 -161
  604. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +0 -158
  605. package/dist/kimaki-opencode-plugin.js +0 -33
  606. package/dist/kimaki-opencode-plugin.test.js +0 -126
  607. package/dist/kimaki-workspace-adaptor.js +0 -118
  608. package/dist/limit-heading-depth.js +0 -25
  609. package/dist/limit-heading-depth.test.js +0 -105
  610. package/dist/list-channel-threads.js +0 -163
  611. package/dist/list-channel-threads.test.js +0 -80
  612. package/dist/markdown.js +0 -455
  613. package/dist/markdown.test.js +0 -411
  614. package/dist/memory-overview-plugin.js +0 -123
  615. package/dist/message-formatting.js +0 -788
  616. package/dist/message-formatting.test.js +0 -1132
  617. package/dist/message-preprocessing.js +0 -419
  618. package/dist/oauth-rotation-shared.js +0 -253
  619. package/dist/onboarding-tutorial.js +0 -163
  620. package/dist/onboarding-welcome.js +0 -37
  621. package/dist/openai-auth-plugin.js +0 -125
  622. package/dist/openai-auth-state.js +0 -237
  623. package/dist/openai-realtime.js +0 -224
  624. package/dist/opencode-command-detection.js +0 -65
  625. package/dist/opencode-command-detection.test.js +0 -240
  626. package/dist/opencode-command.js +0 -181
  627. package/dist/opencode-command.test.js +0 -218
  628. package/dist/opencode-interrupt-plugin.js +0 -253
  629. package/dist/opencode-interrupt-plugin.test.js +0 -293
  630. package/dist/opencode.js +0 -1217
  631. package/dist/parse-permission-rules.test.js +0 -117
  632. package/dist/patch-text-parser.js +0 -97
  633. package/dist/plugin-logger.js +0 -105
  634. package/dist/plugin-opencode-client.js +0 -54
  635. package/dist/privacy-sanitizer.js +0 -105
  636. package/dist/project-add.test.js +0 -62
  637. package/dist/question-abort-clears-dropdown.e2e.test.js +0 -89
  638. package/dist/question-abort-resume.e2e.test.js +0 -100
  639. package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +0 -150
  640. package/dist/question-text-before-dropdown.e2e.test.js +0 -52
  641. package/dist/queue-advanced-abort.e2e.test.js +0 -348
  642. package/dist/queue-advanced-action-buttons.e2e.test.js +0 -209
  643. package/dist/queue-advanced-e2e-setup.js +0 -1043
  644. package/dist/queue-advanced-footer.e2e.test.js +0 -286
  645. package/dist/queue-advanced-model-switch.e2e.test.js +0 -297
  646. package/dist/queue-advanced-permissions-typing.e2e.test.js +0 -189
  647. package/dist/queue-advanced-question.e2e.test.js +0 -266
  648. package/dist/queue-advanced-typing-interrupt.e2e.test.js +0 -113
  649. package/dist/queue-advanced-typing.e2e.test.js +0 -151
  650. package/dist/queue-delete-message.e2e.test.js +0 -216
  651. package/dist/queue-drain-after-interactive-ui.e2e.test.js +0 -120
  652. package/dist/queue-interrupt-drain.e2e.test.js +0 -256
  653. package/dist/queue-question-select-drain.e2e.test.js +0 -162
  654. package/dist/runtime-idle-sweeper.js +0 -52
  655. package/dist/runtime-lifecycle.e2e.test.js +0 -570
  656. package/dist/sentry.js +0 -23
  657. package/dist/session-handler/agent-utils.js +0 -65
  658. package/dist/session-handler/event-stream-state.js +0 -1225
  659. package/dist/session-handler/event-stream-state.test.js +0 -3160
  660. package/dist/session-handler/global-event-listener.js +0 -212
  661. package/dist/session-handler/model-utils.js +0 -311
  662. package/dist/session-handler/model-utils.test.js +0 -445
  663. package/dist/session-handler/opencode-session-event-log.js +0 -121
  664. package/dist/session-handler/thread-ingress.test.js +0 -41
  665. package/dist/session-handler/thread-runtime-state.js +0 -209
  666. package/dist/session-handler/thread-session-runtime-summary.test.js +0 -121
  667. package/dist/session-handler/thread-session-runtime.js +0 -4320
  668. package/dist/session-handler.js +0 -9
  669. package/dist/session-search.js +0 -219
  670. package/dist/session-search.test.js +0 -287
  671. package/dist/session-sleep.test.js +0 -224
  672. package/dist/session-title-rename.test.js +0 -128
  673. package/dist/skill-filter.js +0 -43
  674. package/dist/skill-filter.test.js +0 -86
  675. package/dist/startup-service.js +0 -153
  676. package/dist/startup-time.e2e.test.js +0 -297
  677. package/dist/system-message.js +0 -806
  678. package/dist/system-message.test.js +0 -917
  679. package/dist/task-id-plugin.js +0 -16
  680. package/dist/task-runner.js +0 -554
  681. package/dist/task-schedule.js +0 -298
  682. package/dist/task-schedule.test.js +0 -366
  683. package/dist/test-utils.js +0 -385
  684. package/dist/thinking-utils.js +0 -45
  685. package/dist/thinking-utils.test.js +0 -35
  686. package/dist/thread-message-queue.e2e.test.js +0 -1227
  687. package/dist/tools.js +0 -357
  688. package/dist/undo-redo.e2e.test.js +0 -171
  689. package/dist/unnest-code-blocks.js +0 -146
  690. package/dist/unnest-code-blocks.test.js +0 -673
  691. package/dist/upgrade.js +0 -114
  692. package/dist/utils.js +0 -148
  693. package/dist/voice-attachment.js +0 -56
  694. package/dist/voice-handler.js +0 -727
  695. package/dist/voice-message.e2e.test.js +0 -1254
  696. package/dist/voice-routing-input.e2e.test.js +0 -195
  697. package/dist/voice-routing-question.e2e.test.js +0 -182
  698. package/dist/voice.test.js +0 -500
  699. package/dist/wait-session.js +0 -193
  700. package/dist/websockify.js +0 -69
  701. package/dist/worker-types.js +0 -4
  702. package/dist/worktree-clone-isolation.e2e.test.js +0 -188
  703. package/dist/worktree-lifecycle.e2e.test.js +0 -735
  704. package/dist/worktree-utils.js +0 -3
  705. package/dist/xai-auth-plugin.js +0 -238
  706. package/dist/xai-auth-state.js +0 -234
  707. package/dist/xai-auth-state.test.js +0 -183
  708. package/dist/xml.js +0 -92
  709. package/dist/xml.test.js +0 -32
  710. package/skills/bump-holocron-consumers/SKILL.md +0 -137
  711. package/skills/critique/SKILL.md +0 -116
  712. package/skills/egaki/SKILL.md +0 -86
  713. package/skills/errore/SKILL.md +0 -501
  714. package/skills/goke/SKILL.md +0 -695
  715. package/skills/holocron/SKILL.md +0 -942
  716. package/skills/lintcn/SKILL.md +0 -873
  717. package/skills/new-skill/SKILL.md +0 -339
  718. package/skills/npm-package/SKILL.md +0 -692
  719. package/skills/opensrc/SKILL.md +0 -102
  720. package/skills/playwriter/SKILL.md +0 -35
  721. package/skills/profano/SKILL.md +0 -33
  722. package/skills/readme.md +0 -20
  723. package/skills/sigillo/SKILL.md +0 -370
  724. package/skills/spiceflow/SKILL.md +0 -80
  725. package/skills/strada/SKILL.md +0 -104
  726. package/skills/termcast/SKILL.md +0 -133
  727. package/skills/tuistory/SKILL.md +0 -237
  728. package/skills/usecomputer/SKILL.md +0 -264
  729. package/skills/zele/SKILL.md +0 -81
  730. package/src/agent-model.e2e.test.ts +0 -1557
  731. package/src/ai-tool-to-genai.test.ts +0 -296
  732. package/src/ai-tool-to-genai.ts +0 -283
  733. package/src/ai-tool.ts +0 -39
  734. package/src/anthropic-account-identity.test.ts +0 -52
  735. package/src/anthropic-account-identity.ts +0 -96
  736. package/src/anthropic-auth-plugin.test.ts +0 -135
  737. package/src/anthropic-auth-plugin.ts +0 -1180
  738. package/src/anthropic-auth-state.test.ts +0 -259
  739. package/src/anthropic-auth-state.ts +0 -272
  740. package/src/bash-tool-schema-plugin.test.ts +0 -84
  741. package/src/bash-tool-schema-plugin.ts +0 -77
  742. package/src/bin.ts +0 -173
  743. package/src/bot-lifecycle.test.ts +0 -252
  744. package/src/btw-prefix-detection.test.ts +0 -142
  745. package/src/btw-prefix-detection.ts +0 -30
  746. package/src/cache-drift-plugin.ts +0 -249
  747. package/src/cache-rewrite.test.ts +0 -49
  748. package/src/cache-rewrite.ts +0 -96
  749. package/src/channel-management.ts +0 -494
  750. package/src/cli-commands/bot.ts +0 -374
  751. package/src/cli-commands/maintenance.ts +0 -237
  752. package/src/cli-commands/misc.ts +0 -208
  753. package/src/cli-commands/project.ts +0 -680
  754. package/src/cli-commands/send.ts +0 -885
  755. package/src/cli-commands/session.ts +0 -1181
  756. package/src/cli-commands/task.ts +0 -358
  757. package/src/cli-commands/thread.ts +0 -86
  758. package/src/cli-commands/user.ts +0 -195
  759. package/src/cli-parsing.test.ts +0 -305
  760. package/src/cli-runner.test.ts +0 -166
  761. package/src/cli-runner.ts +0 -2157
  762. package/src/cli-send-thread.e2e.test.ts +0 -609
  763. package/src/cloudflare-transcription.ts +0 -76
  764. package/src/commands/abort.ts +0 -113
  765. package/src/commands/action-buttons.ts +0 -371
  766. package/src/commands/add-project.ts +0 -146
  767. package/src/commands/agent.ts +0 -911
  768. package/src/commands/ask-question.test.ts +0 -177
  769. package/src/commands/ask-question.ts +0 -532
  770. package/src/commands/btw.ts +0 -356
  771. package/src/commands/compact.ts +0 -154
  772. package/src/commands/context-usage.test.ts +0 -57
  773. package/src/commands/context-usage.ts +0 -267
  774. package/src/commands/create-new-project.ts +0 -203
  775. package/src/commands/diff.ts +0 -91
  776. package/src/commands/file-upload.ts +0 -398
  777. package/src/commands/fork-subagent.ts +0 -262
  778. package/src/commands/fork.ts +0 -421
  779. package/src/commands/gemini-apikey.ts +0 -205
  780. package/src/commands/last-sessions.ts +0 -176
  781. package/src/commands/login.ts +0 -1182
  782. package/src/commands/mcp.ts +0 -307
  783. package/src/commands/memory-snapshot.ts +0 -30
  784. package/src/commands/mention-mode.ts +0 -66
  785. package/src/commands/merge-worktree.ts +0 -282
  786. package/src/commands/model-variant.ts +0 -522
  787. package/src/commands/model.ts +0 -1377
  788. package/src/commands/multioauth.ts +0 -508
  789. package/src/commands/new-worktree.ts +0 -835
  790. package/src/commands/paginated-select.ts +0 -81
  791. package/src/commands/permissions.ts +0 -458
  792. package/src/commands/queue.test.ts +0 -19
  793. package/src/commands/queue.ts +0 -471
  794. package/src/commands/remove-project.ts +0 -152
  795. package/src/commands/restart-opencode-server.ts +0 -173
  796. package/src/commands/resume.ts +0 -229
  797. package/src/commands/run-command.ts +0 -123
  798. package/src/commands/screenshare.test.ts +0 -30
  799. package/src/commands/screenshare.ts +0 -388
  800. package/src/commands/session-id.ts +0 -109
  801. package/src/commands/session.ts +0 -319
  802. package/src/commands/share.ts +0 -106
  803. package/src/commands/tasks.ts +0 -399
  804. package/src/commands/types.ts +0 -25
  805. package/src/commands/undo-redo.ts +0 -394
  806. package/src/commands/unset-model.ts +0 -152
  807. package/src/commands/upgrade.ts +0 -51
  808. package/src/commands/user-command.ts +0 -260
  809. package/src/commands/verbosity.ts +0 -172
  810. package/src/commands/vscode.ts +0 -378
  811. package/src/commands/worktrees.ts +0 -762
  812. package/src/condense-memory.ts +0 -36
  813. package/src/config.ts +0 -147
  814. package/src/context-awareness-plugin.test.ts +0 -144
  815. package/src/context-awareness-plugin.ts +0 -508
  816. package/src/cpu-profiler.test.ts +0 -183
  817. package/src/cpu-profiler.ts +0 -293
  818. package/src/critique-utils.ts +0 -139
  819. package/src/database.ts +0 -1484
  820. package/src/debounce-timeout.ts +0 -43
  821. package/src/debounced-process-flush.ts +0 -104
  822. package/src/default-channel-provisioning.test.ts +0 -156
  823. package/src/discord-bot.ts +0 -1852
  824. package/src/discord-command-registration.test.ts +0 -20
  825. package/src/discord-command-registration.ts +0 -704
  826. package/src/discord-urls.ts +0 -88
  827. package/src/discord-utils.test.ts +0 -470
  828. package/src/discord-utils.ts +0 -1079
  829. package/src/escape-backticks.test.ts +0 -469
  830. package/src/event-stream-real-capture.e2e.test.ts +0 -692
  831. package/src/eventsource-parser.test.ts +0 -351
  832. package/src/exec-async.ts +0 -44
  833. package/src/external-directory-permissions.e2e.test.ts +0 -103
  834. package/src/external-directory-project-deny.e2e.test.ts +0 -84
  835. package/src/external-opencode-sync.test.ts +0 -248
  836. package/src/external-opencode-sync.ts +0 -867
  837. package/src/extract-sdk-error-message.test.ts +0 -18
  838. package/src/format-tables.test.ts +0 -988
  839. package/src/format-tables.ts +0 -1031
  840. package/src/forum-sync/config.ts +0 -92
  841. package/src/forum-sync/discord-operations.ts +0 -245
  842. package/src/forum-sync/index.ts +0 -9
  843. package/src/forum-sync/markdown.ts +0 -172
  844. package/src/forum-sync/sync-to-discord.ts +0 -595
  845. package/src/forum-sync/sync-to-files.ts +0 -294
  846. package/src/forum-sync/types.ts +0 -175
  847. package/src/forum-sync/watchers.ts +0 -474
  848. package/src/gateway-proxy-reconnect.e2e.test.ts +0 -553
  849. package/src/gateway-proxy.e2e.test.ts +0 -648
  850. package/src/genai-worker-wrapper.ts +0 -164
  851. package/src/genai-worker.ts +0 -385
  852. package/src/genai.ts +0 -318
  853. package/src/git-worktree-core.ts +0 -616
  854. package/src/guild-categories.test.ts +0 -260
  855. package/src/heap-monitor.ts +0 -152
  856. package/src/hrana-server.test.ts +0 -130
  857. package/src/hrana-server.ts +0 -399
  858. package/src/html-actions.test.ts +0 -87
  859. package/src/html-actions.ts +0 -174
  860. package/src/html-components.test.ts +0 -38
  861. package/src/html-components.ts +0 -177
  862. package/src/image-optimizer-plugin.ts +0 -194
  863. package/src/image-utils.ts +0 -150
  864. package/src/interaction-handler.ts +0 -775
  865. package/src/ipc-polling.ts +0 -325
  866. package/src/ipc-tools-plugin.ts +0 -306
  867. package/src/kimaki-digital-twin.e2e.test.ts +0 -199
  868. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +0 -208
  869. package/src/kimaki-opencode-plugin.test.ts +0 -142
  870. package/src/kimaki-opencode-plugin.ts +0 -34
  871. package/src/kimaki-workspace-adaptor.ts +0 -144
  872. package/src/limit-heading-depth.test.ts +0 -116
  873. package/src/limit-heading-depth.ts +0 -26
  874. package/src/list-channel-threads.test.ts +0 -90
  875. package/src/list-channel-threads.ts +0 -252
  876. package/src/markdown.test.ts +0 -498
  877. package/src/markdown.ts +0 -577
  878. package/src/memory-overview-plugin.ts +0 -159
  879. package/src/message-formatting.test.ts +0 -1401
  880. package/src/message-formatting.ts +0 -999
  881. package/src/message-preprocessing.ts +0 -557
  882. package/src/oauth-rotation-shared.ts +0 -332
  883. package/src/onboarding-tutorial.ts +0 -167
  884. package/src/onboarding-welcome.ts +0 -49
  885. package/src/openai-auth-plugin.ts +0 -158
  886. package/src/openai-auth-state.ts +0 -297
  887. package/src/openai-realtime.ts +0 -358
  888. package/src/opencode-command-detection.test.ts +0 -307
  889. package/src/opencode-command-detection.ts +0 -76
  890. package/src/opencode-command.test.ts +0 -290
  891. package/src/opencode-command.ts +0 -251
  892. package/src/opencode-interrupt-plugin.test.ts +0 -419
  893. package/src/opencode-interrupt-plugin.ts +0 -306
  894. package/src/opencode.ts +0 -1590
  895. package/src/parse-permission-rules.test.ts +0 -127
  896. package/src/patch-text-parser.ts +0 -107
  897. package/src/plugin-logger.ts +0 -139
  898. package/src/plugin-opencode-client.ts +0 -71
  899. package/src/privacy-sanitizer.ts +0 -142
  900. package/src/project-add.test.ts +0 -80
  901. package/src/question-abort-clears-dropdown.e2e.test.ts +0 -109
  902. package/src/question-abort-resume.e2e.test.ts +0 -121
  903. package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +0 -172
  904. package/src/question-text-before-dropdown.e2e.test.ts +0 -67
  905. package/src/queue-advanced-abort.e2e.test.ts +0 -449
  906. package/src/queue-advanced-action-buttons.e2e.test.ts +0 -271
  907. package/src/queue-advanced-e2e-setup.ts +0 -1164
  908. package/src/queue-advanced-footer.e2e.test.ts +0 -357
  909. package/src/queue-advanced-model-switch.e2e.test.ts +0 -382
  910. package/src/queue-advanced-permissions-typing.e2e.test.ts +0 -256
  911. package/src/queue-advanced-question.e2e.test.ts +0 -322
  912. package/src/queue-advanced-typing-interrupt.e2e.test.ts +0 -147
  913. package/src/queue-advanced-typing.e2e.test.ts +0 -199
  914. package/src/queue-delete-message.e2e.test.ts +0 -262
  915. package/src/queue-drain-after-interactive-ui.e2e.test.ts +0 -152
  916. package/src/queue-interrupt-drain.e2e.test.ts +0 -299
  917. package/src/queue-question-select-drain.e2e.test.ts +0 -209
  918. package/src/runtime-idle-sweeper.ts +0 -76
  919. package/src/runtime-lifecycle.e2e.test.ts +0 -714
  920. package/src/schema.sql +0 -251
  921. package/src/sentry.ts +0 -26
  922. package/src/session-handler/agent-utils.ts +0 -104
  923. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +0 -45
  924. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +0 -40
  925. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +0 -23
  926. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +0 -22
  927. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +0 -277
  928. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +0 -46
  929. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +0 -21
  930. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +0 -56
  931. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +0 -44
  932. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +0 -29
  933. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +0 -29
  934. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +0 -50
  935. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +0 -59
  936. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +0 -52
  937. package/src/session-handler/event-stream-state.test.ts +0 -3431
  938. package/src/session-handler/event-stream-state.ts +0 -1792
  939. package/src/session-handler/global-event-listener.ts +0 -258
  940. package/src/session-handler/model-utils.test.ts +0 -537
  941. package/src/session-handler/model-utils.ts +0 -489
  942. package/src/session-handler/opencode-session-event-log.ts +0 -159
  943. package/src/session-handler/thread-ingress.test.ts +0 -54
  944. package/src/session-handler/thread-runtime-state.ts +0 -358
  945. package/src/session-handler/thread-session-runtime-summary.test.ts +0 -169
  946. package/src/session-handler/thread-session-runtime.ts +0 -5696
  947. package/src/session-handler.ts +0 -15
  948. package/src/session-search.test.ts +0 -331
  949. package/src/session-search.ts +0 -382
  950. package/src/session-sleep.test.ts +0 -294
  951. package/src/session-title-rename.test.ts +0 -186
  952. package/src/skill-filter.test.ts +0 -110
  953. package/src/skill-filter.ts +0 -63
  954. package/src/startup-service.ts +0 -200
  955. package/src/startup-time.e2e.test.ts +0 -377
  956. package/src/system-message.test.ts +0 -1013
  957. package/src/system-message.ts +0 -1043
  958. package/src/task-id-plugin.ts +0 -17
  959. package/src/task-runner.ts +0 -768
  960. package/src/task-schedule.test.ts +0 -424
  961. package/src/task-schedule.ts +0 -470
  962. package/src/test-utils.ts +0 -549
  963. package/src/thinking-utils.test.ts +0 -46
  964. package/src/thinking-utils.ts +0 -82
  965. package/src/thread-message-queue.e2e.test.ts +0 -1468
  966. package/src/tools.ts +0 -430
  967. package/src/undici.d.ts +0 -12
  968. package/src/undo-redo.e2e.test.ts +0 -219
  969. package/src/unnest-code-blocks.test.ts +0 -713
  970. package/src/unnest-code-blocks.ts +0 -185
  971. package/src/upgrade.ts +0 -127
  972. package/src/utils.ts +0 -216
  973. package/src/voice-attachment.ts +0 -79
  974. package/src/voice-handler.ts +0 -1000
  975. package/src/voice-message.e2e.test.ts +0 -1513
  976. package/src/voice-routing-input.e2e.test.ts +0 -203
  977. package/src/voice-routing-question.e2e.test.ts +0 -188
  978. package/src/voice.test.ts +0 -565
  979. package/src/wait-session.ts +0 -291
  980. package/src/websockify.ts +0 -101
  981. package/src/worker-types.ts +0 -64
  982. package/src/worktree-clone-isolation.e2e.test.ts +0 -213
  983. package/src/worktree-lifecycle.e2e.test.ts +0 -900
  984. package/src/worktree-utils.ts +0 -4
  985. package/src/xai-auth-plugin.ts +0 -303
  986. package/src/xai-auth-state.test.ts +0 -224
  987. package/src/xai-auth-state.ts +0 -294
  988. package/src/xml.test.ts +0 -38
  989. package/src/xml.ts +0 -121
package/src/opencode.ts DELETED
@@ -1,1590 +0,0 @@
1
- // OpenCode single-server process manager.
2
- //
3
- // Architecture: ONE opencode serve process shared by all project directories.
4
- // Each SDK client uses the x-opencode-directory header to scope requests to a
5
- // specific project. The server lazily creates and caches an Instance per unique
6
- // directory path internally.
7
- //
8
- // Permission layering — READ THIS BEFORE ADDING A PERMISSION RULE.
9
- //
10
- // opencode evaluates permissions with findLast() over a flattened list, so the
11
- // last matching rule wins. The order is:
12
- //
13
- // opencode built-in defaults
14
- // ▼
15
- // merged config files ── kimaki's generated config, THEN the user's
16
- // ▼ project opencode.json (deep-merged on top)
17
- // config.agent.<name>.permission
18
- // ▼
19
- // session.permission ── buildSessionPermissions(), always wins
20
- //
21
- // Directory ALLOW rules therefore belong in the generated server config, never
22
- // in session rules or an agent block: a project opencode.json must still be
23
- // able to `deny` or `ask` for specific folders. Anything placed in
24
- // session.permission silently overrides the user.
25
- //
26
- // external_directory is `{ '*': 'allow' }` by default. opencode's own default
27
- // is `ask`, which meant the agent had to interrupt the user for ordinary reads
28
- // outside the project, and an unanswered prompt was auto-rejected on TTL. Users
29
- // who want stricter behaviour add `deny`/`ask` rules to their own
30
- // opencode.json, or start kimaki with --restrict-directories.
31
- //
32
- // session.permission carries exactly one thing: the worktree original-checkout
33
- // deny, which must beat user config on purpose.
34
- //
35
- // Uses errore for type-safe error handling.
36
-
37
- import { spawn, execFileSync, type ChildProcess } from 'node:child_process'
38
- import fs from 'node:fs'
39
- import http from 'node:http'
40
- import net from 'node:net'
41
- import os from 'node:os'
42
- import path from 'node:path'
43
- import readline from 'node:readline'
44
- import { createRequire } from 'node:module'
45
- import { fileURLToPath, pathToFileURL } from 'node:url'
46
-
47
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
48
- import {
49
- createOpencodeClient,
50
- type OpencodeClient,
51
- type Config as SdkConfig,
52
- type PermissionRuleset,
53
- } from '@opencode-ai/sdk/v2'
54
-
55
- import {
56
- restartGlobalEventListener,
57
- waitForGlobalEventListener,
58
- } from './session-handler/global-event-listener.js'
59
- import {
60
- getDataDir,
61
- getLockPort,
62
- getRestrictExternalDirectories,
63
- getOpencodeHostname,
64
- getOpencodePort,
65
- } from './config.js'
66
- import { store } from './store.js'
67
- import { getHranaUrl } from './hrana-server.js'
68
-
69
- export function resolveSubrouterPluginSpec({ isDev }: { isDev: boolean }) {
70
- const require = createRequire(import.meta.url)
71
- const entry = require.resolve('@subrouter/opencode')
72
- if (isDev) return pathToFileURL(entry).href
73
-
74
- const packageJsonPath = require.resolve('@subrouter/opencode/package.json')
75
- const version = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).version
76
- if (typeof version !== 'string' || !version) {
77
- throw new Error(`Missing @subrouter/opencode version in ${packageJsonPath}`)
78
- }
79
- return `@subrouter/opencode@${version}`
80
- }
81
-
82
- // SDK Config type is simplified; opencode accepts nested permission objects with path patterns
83
- type PermissionAction = 'ask' | 'allow' | 'deny'
84
- type PermissionRule = PermissionAction | Record<string, PermissionAction>
85
- type Config = Omit<SdkConfig, 'permission'> & {
86
- permission?: {
87
- edit?: PermissionRule
88
- bash?: PermissionRule
89
- external_directory?: PermissionRule
90
- webfetch?: PermissionRule
91
- [key: string]: PermissionRule | undefined
92
- }
93
- }
94
- import * as errore from 'errore'
95
- import { createLogger, LogPrefix } from './logger.js'
96
- import { notifyError } from './sentry.js'
97
- import {
98
- DirectoryNotAccessibleError,
99
- ServerStartError,
100
- ServerNotReadyError,
101
- FetchError,
102
- OpencodeIncompatibleVersionError,
103
- type OpenCodeErrors,
104
- } from './errors.js'
105
- import {
106
- ensureKimakiCommandShim,
107
- getIncompatibleOpencodeVersionError,
108
- getPathEnvKey,
109
- getSpawnCommandAndArgs,
110
- prependPathEntry,
111
- selectResolvedCommand,
112
- } from './opencode-command.js'
113
- import { execAsync } from './exec-async.js'
114
- import { computeSkillPermission } from './skill-filter.js'
115
-
116
- const opencodeLogger = createLogger(LogPrefix.OPENCODE)
117
-
118
- /**
119
- * Build Basic auth headers from OPENCODE_SERVER_PASSWORD env var.
120
- * Returns empty object when no password is set.
121
- */
122
- export function getOpencodeServerAuthHeaders(): Record<string, string> {
123
- const serverPassword = process.env.OPENCODE_SERVER_PASSWORD
124
- if (!serverPassword) return {}
125
- const username = process.env.OPENCODE_SERVER_USERNAME || 'opencode'
126
- const encoded = Buffer.from(`${username}:${serverPassword}`).toString('base64')
127
- return { Authorization: `Basic ${encoded}` }
128
- }
129
-
130
- const LOOPBACK_HOSTNAMES = new Set(['127.0.0.1', 'localhost', '::1'])
131
-
132
- export function publicOpencodeBindRequiresPassword({
133
- hostname,
134
- }: {
135
- hostname: string | null | undefined
136
- }): boolean {
137
- if (!hostname) return false
138
- return !LOOPBACK_HOSTNAMES.has(hostname)
139
- }
140
-
141
- // Always pass --hostname so opencode.json server.hostname / mdns cannot bind 0.0.0.0.
142
- const DEFAULT_OPENCODE_HOSTNAME = '127.0.0.1'
143
-
144
- export function buildOpencodeServeArgs({
145
- port,
146
- hostname,
147
- }: {
148
- port: number
149
- hostname?: string | null
150
- }): string[] {
151
- return [
152
- 'serve',
153
- '--port',
154
- port.toString(),
155
- '--hostname',
156
- hostname || DEFAULT_OPENCODE_HOSTNAME,
157
- '--print-logs',
158
- '--log-level',
159
- 'WARN',
160
- ]
161
- }
162
-
163
- // Tracks directories that have been initialized, to avoid repeated log spam
164
- // from the external sync polling loop.
165
- const initializedDirectories = new Set<string>()
166
-
167
- const STARTUP_STDERR_TAIL_LIMIT = 30
168
- const STARTUP_STDERR_LINE_MAX_LENGTH = 120
169
- const STARTUP_ERROR_REASON_MAX_LENGTH = 1500
170
- const ANSI_ESCAPE_REGEX =
171
- /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g
172
-
173
- export async function requestHealthcheck({
174
- url,
175
- timeoutMs = 2000,
176
- }: {
177
- url: string
178
- timeoutMs?: number
179
- }): Promise<{ status: number; body: string }> {
180
- return new Promise((resolve, reject) => {
181
- let settled = false
182
- let timeout: NodeJS.Timeout | null = null
183
- const settle = (
184
- handler: () => void,
185
- ) => {
186
- if (settled) return
187
- settled = true
188
- if (timeout) clearTimeout(timeout)
189
- handler()
190
- }
191
-
192
- const req = http.request(
193
- url,
194
- {
195
- method: 'GET',
196
- headers: {
197
- connection: 'close',
198
- ...getOpencodeServerAuthHeaders(),
199
- },
200
- },
201
- (res) => {
202
- const chunks: Buffer[] = []
203
- res.on('data', (chunk) => {
204
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
205
- })
206
- res.on('end', () => {
207
- settle(() => {
208
- resolve({
209
- status: res.statusCode || 0,
210
- body: Buffer.concat(chunks).toString('utf-8'),
211
- })
212
- })
213
- })
214
- res.on('error', (error) => {
215
- settle(() => reject(error))
216
- })
217
- },
218
- )
219
- req.on('error', (error) => {
220
- settle(() => reject(error))
221
- })
222
- timeout = setTimeout(() => {
223
- settle(() => {
224
- req.destroy()
225
- reject(new Error(`Health check request timed out after ${timeoutMs}ms`))
226
- })
227
- }, timeoutMs)
228
- req.end()
229
- })
230
- }
231
-
232
- function truncateWithEllipsis({
233
- value,
234
- maxLength,
235
- }: {
236
- value: string
237
- maxLength: number
238
- }): string {
239
- if (maxLength <= 3) {
240
- return value.slice(0, maxLength)
241
- }
242
- if (value.length <= maxLength) {
243
- return value
244
- }
245
- return `${value.slice(0, maxLength - 3)}...`
246
- }
247
-
248
- function stripAnsiCodes(value: string): string {
249
- return value.replaceAll(ANSI_ESCAPE_REGEX, '')
250
- }
251
-
252
- function sanitizeOutputLine(line: string): string {
253
- return stripAnsiCodes(line).trim()
254
- }
255
-
256
- function sanitizeForCodeFence(line: string): string {
257
- return line.replaceAll('```', '`\u200b``')
258
- }
259
-
260
- function pushStartupStderrTail({
261
- stderrTail,
262
- line,
263
- }: {
264
- stderrTail: string[]
265
- line: string
266
- }): void {
267
- const sanitizedLine = sanitizeOutputLine(line)
268
- if (sanitizedLine.length === 0) {
269
- return
270
- }
271
-
272
- const truncatedLine = truncateWithEllipsis({
273
- value: sanitizeForCodeFence(sanitizedLine),
274
- maxLength: STARTUP_STDERR_LINE_MAX_LENGTH,
275
- })
276
-
277
- stderrTail.push(truncatedLine)
278
- if (stderrTail.length > STARTUP_STDERR_TAIL_LIMIT) {
279
- stderrTail.splice(0, stderrTail.length - STARTUP_STDERR_TAIL_LIMIT)
280
- }
281
- }
282
-
283
- function subscribeToProcessLogStream({
284
- stream,
285
- onLine,
286
- }: {
287
- stream: NodeJS.ReadableStream | null | undefined
288
- onLine: (line: string) => void
289
- }): readline.Interface | null {
290
- if (!stream) {
291
- return null
292
- }
293
-
294
- const logReader = readline.createInterface({
295
- input: stream,
296
- crlfDelay: Infinity,
297
- })
298
-
299
- logReader.on('line', (line) => {
300
- const sanitizedLine = sanitizeOutputLine(line)
301
- if (sanitizedLine.length === 0) {
302
- return
303
- }
304
- onLine(sanitizedLine)
305
- })
306
-
307
- return logReader
308
- }
309
-
310
- function buildStartupTimeoutReason({
311
- maxAttempts,
312
- stderrTail,
313
- }: {
314
- maxAttempts: number
315
- stderrTail: string[]
316
- }): string {
317
- const timeoutSeconds = Math.round((maxAttempts * 100) / 1000)
318
- const baseReason = `Server did not start after ${timeoutSeconds} seconds`
319
- if (stderrTail.length === 0) {
320
- return baseReason
321
- }
322
-
323
- const formatReason = ({
324
- lines,
325
- omitted,
326
- }: {
327
- lines: string[]
328
- omitted: number
329
- }): string => {
330
- const omittedLine =
331
- omitted > 0
332
- ? `[... ${omitted} older stderr lines omitted to fit Discord ...]\n`
333
- : ''
334
- const stderrCodeBlock = `${omittedLine}${lines.join('\n')}`
335
- return `${baseReason}\nLast opencode stderr lines:\n\`\`\`text\n${stderrCodeBlock}\n\`\`\``
336
- }
337
-
338
- let lines = [...stderrTail]
339
- let omitted = 0
340
- let formattedReason = formatReason({ lines, omitted })
341
-
342
- while (
343
- formattedReason.length > STARTUP_ERROR_REASON_MAX_LENGTH &&
344
- lines.length > 0
345
- ) {
346
- lines = lines.slice(1)
347
- omitted += 1
348
- formattedReason = formatReason({ lines, omitted })
349
- }
350
-
351
- return truncateWithEllipsis({
352
- value: formattedReason,
353
- maxLength: STARTUP_ERROR_REASON_MAX_LENGTH,
354
- })
355
- }
356
-
357
- // ── Single server state ──────────────────────────────────────────
358
- // One opencode serve process, shared by all project directories.
359
- // Clients are created per-directory with the x-opencode-directory header.
360
-
361
- type SingleServer = {
362
- process: ChildProcess | null
363
- port: number
364
- baseUrl: string
365
- /** True when this server was discovered from the bot's hrana endpoint,
366
- * not spawned by this process. We must not kill it on cleanup. */
367
- discovered?: boolean
368
- }
369
-
370
- type ServerLifecycleEvent =
371
- | { type: 'started'; port: number }
372
- | { type: 'stopped' }
373
-
374
- let singleServer: SingleServer | null = null
375
- let serverRetryCount = 0
376
- const serverLifecycleListeners = new Set<(event: ServerLifecycleEvent) => void>()
377
- let processCleanupHandlersRegistered = false
378
- let startingServerProcess: ChildProcess | null = null
379
- const clientCache = new Map<string, OpencodeClient>()
380
-
381
- function notifyServerLifecycle(event: ServerLifecycleEvent): void {
382
- for (const listener of serverLifecycleListeners) {
383
- listener(event)
384
- }
385
- }
386
-
387
- export function subscribeOpencodeServerLifecycle(
388
- listener: (event: ServerLifecycleEvent) => void,
389
- ): () => void {
390
- serverLifecycleListeners.add(listener)
391
- return () => {
392
- serverLifecycleListeners.delete(listener)
393
- }
394
- }
395
-
396
- function killSingleServerProcessNow({
397
- reason,
398
- }: {
399
- reason: string
400
- }): void {
401
- if (!singleServer) {
402
- return
403
- }
404
-
405
- // Never kill a server we didn't spawn (discovered from another process)
406
- if (singleServer.discovered || !singleServer.process) {
407
- return
408
- }
409
-
410
- const serverProcess = singleServer.process
411
- const pid = serverProcess.pid
412
- if (!pid || serverProcess.killed) {
413
- return
414
- }
415
-
416
- const killResult = errore.try(
417
- () => {
418
- serverProcess.kill('SIGTERM')
419
- },
420
- (error) => {
421
- return new Error('Failed to send SIGTERM to opencode server', {
422
- cause: error,
423
- })
424
- },
425
- )
426
-
427
- if (killResult instanceof Error) {
428
- opencodeLogger.warn(
429
- `[cleanup:${reason}] ${killResult.message} (pid: ${pid}, port: ${singleServer.port})`,
430
- )
431
- return
432
- }
433
-
434
- opencodeLogger.log(
435
- `[cleanup:${reason}] Sent SIGTERM to opencode server (pid: ${pid}, port: ${singleServer.port})`,
436
- )
437
- }
438
-
439
- function killStartingServerProcessNow({
440
- reason,
441
- }: {
442
- reason: string
443
- }): void {
444
- const serverProcess = startingServerProcess
445
- if (!serverProcess) {
446
- return
447
- }
448
-
449
- const pid = serverProcess.pid
450
- if (!pid || serverProcess.killed) {
451
- return
452
- }
453
-
454
- const killResult = errore.try(
455
- () => {
456
- serverProcess.kill('SIGTERM')
457
- },
458
- (error) => {
459
- return new Error('Failed to send SIGTERM to starting opencode server', {
460
- cause: error,
461
- })
462
- },
463
- )
464
-
465
- if (killResult instanceof Error) {
466
- opencodeLogger.warn(
467
- `[cleanup:${reason}] ${killResult.message} (pid: ${pid})`,
468
- )
469
- return
470
- }
471
-
472
- opencodeLogger.log(
473
- `[cleanup:${reason}] Sent SIGTERM to starting opencode server (pid: ${pid})`,
474
- )
475
- }
476
-
477
- function ensureProcessCleanupHandlersRegistered(): void {
478
- if (processCleanupHandlersRegistered) {
479
- return
480
- }
481
- processCleanupHandlersRegistered = true
482
-
483
- opencodeLogger.log('Registering process cleanup handlers for opencode server')
484
-
485
- process.on('exit', () => {
486
- killSingleServerProcessNow({ reason: 'process-exit' })
487
- killStartingServerProcessNow({ reason: 'process-exit' })
488
- })
489
-
490
- // Fallback for CLI subcommands without their own signal handling. Any signal
491
- // listener disables Node's default exit, so if we are the only listener we
492
- // must exit ourselves. Otherwise the process ignores Ctrl+C and, in the bot,
493
- // keeps holding the hrana lock port. If another owner exists (the bot
494
- // lifecycle handlers, a subcommand), it decides when to exit.
495
- for (const signal of ['SIGINT', 'SIGTERM'] as const) {
496
- process.on(signal, () => {
497
- killSingleServerProcessNow({ reason: signal.toLowerCase() })
498
- killStartingServerProcessNow({ reason: signal.toLowerCase() })
499
- if (process.listenerCount(signal) > 1) {
500
- return
501
- }
502
- process.exit(128 + os.constants.signals[signal])
503
- })
504
- }
505
- }
506
-
507
- // ── Resolve opencode binary ──────────────────────────────────────
508
- // Resolve the full path to the opencode binary so we can spawn without
509
- // shell: true. Using shell: true creates an intermediate sh process — when
510
- // cleanup sends SIGTERM it only kills the shell, leaving the actual opencode
511
- // process orphaned (reparented to PID 1). Resolving the path upfront lets
512
- // us spawn the binary directly and SIGTERM reaches the right process.
513
- //
514
- // Resolution order:
515
- // 1. OPENCODE_PATH env var (explicit user override)
516
- // 2. `which opencode` / `where opencode` (system PATH)
517
- // 3. Fall back to bare "opencode" (spawn will fail with a clear error)
518
- //
519
- // OpenCode must be installed globally before running kimaki. The bot startup
520
- // checks for it via ensureCommandAvailable and prompts to install if missing.
521
-
522
- let resolvedOpencodeCommand: string | null = null
523
-
524
- export function resolveOpencodeCommand(): string {
525
- if (resolvedOpencodeCommand) {
526
- return resolvedOpencodeCommand
527
- }
528
-
529
- const envPath = process.env.OPENCODE_PATH
530
- if (envPath) {
531
- const resolvedFromEnv = selectResolvedCommand({
532
- output: envPath,
533
- isWindows: process.platform === 'win32',
534
- })
535
- if (resolvedFromEnv) {
536
- resolvedOpencodeCommand = resolvedFromEnv
537
- return resolvedFromEnv
538
- }
539
- }
540
-
541
- const isWindows = process.platform === 'win32'
542
- const whichCmd = isWindows ? 'where' : 'which'
543
- const result = errore.try(
544
- () => {
545
- const commandOutput = execFileSync(whichCmd, ['opencode'], {
546
- encoding: 'utf8',
547
- timeout: 5000,
548
- })
549
- const resolved = selectResolvedCommand({
550
- output: commandOutput,
551
- isWindows,
552
- })
553
- if (resolved) {
554
- return resolved
555
- }
556
- throw new Error('opencode not found in PATH')
557
- },
558
- () => new Error('opencode not found in PATH'),
559
- )
560
-
561
- if (result instanceof Error) {
562
- // Fall back to bare command name — spawn will fail with a clear error
563
- // if it can't find the binary.
564
- opencodeLogger.warn('Could not resolve opencode path via which, falling back to "opencode"')
565
- return 'opencode'
566
- }
567
-
568
- resolvedOpencodeCommand = result
569
- opencodeLogger.log(`Resolved opencode binary: ${result}`)
570
- return result
571
- }
572
-
573
- export async function assertCompatibleOpencodeVersion({
574
- resolvedCommand = resolveOpencodeCommand(),
575
- }: {
576
- resolvedCommand?: string
577
- } = {}): Promise<OpencodeIncompatibleVersionError | true> {
578
- const { command, args } = getSpawnCommandAndArgs({
579
- resolvedCommand,
580
- baseArgs: ['--version'],
581
- })
582
- const result = await execAsync(
583
- { command, args },
584
- { timeout: 5000, encoding: 'utf8' },
585
- ).catch((cause) => {
586
- return new Error('Failed to read OpenCode version', { cause })
587
- })
588
- if (result instanceof Error) {
589
- opencodeLogger.warn(result.message)
590
- return true
591
- }
592
- const incompatible = getIncompatibleOpencodeVersionError(
593
- `${result.stdout}\n${result.stderr}`,
594
- )
595
- if (incompatible) return incompatible
596
- return true
597
- }
598
- async function getOpenPort(): Promise<number> {
599
- return new Promise((resolve, reject) => {
600
- const server = net.createServer()
601
- server.listen(0, () => {
602
- const address = server.address()
603
- if (address && typeof address === 'object') {
604
- const port = address.port
605
- server.close(() => {
606
- resolve(port)
607
- })
608
- } else {
609
- reject(new Error('Failed to get port'))
610
- }
611
- })
612
- server.on('error', reject)
613
- })
614
- }
615
-
616
- async function waitForServer({
617
- port,
618
- directory,
619
- maxAttempts = 300,
620
- startupStderrTail,
621
- }: {
622
- port: number
623
- directory?: string
624
- maxAttempts?: number
625
- startupStderrTail: string[]
626
- }): Promise<ServerStartError | true> {
627
- const endpoint = new URL(`http://127.0.0.1:${port}/api/health`)
628
- if (directory) {
629
- endpoint.searchParams.set('directory', directory)
630
- }
631
- for (let i = 0; i < maxAttempts; i++) {
632
- const response = await requestHealthcheck({ url: endpoint.toString() })
633
- .catch((e) => new FetchError({ url: endpoint.toString(), cause: e }))
634
- if (response instanceof Error) {
635
- // Connection refused or other transient errors - continue polling.
636
- // Use 100ms interval instead of 1s so we detect readiness faster.
637
- // Critical for scale-to-zero cold starts where every ms matters.
638
- await new Promise((resolve) => setTimeout(resolve, 100))
639
- continue
640
- }
641
- if (response.status < 500) {
642
- return true
643
- }
644
- const body = response.body
645
- // Fatal errors that won't resolve with retrying
646
- if (body.includes('BunInstallFailedError')) {
647
- return new ServerStartError({ port, reason: body.slice(0, 200) })
648
- }
649
- await new Promise((resolve) => setTimeout(resolve, 100))
650
- }
651
- return new ServerStartError({
652
- port,
653
- reason: buildStartupTimeoutReason({
654
- maxAttempts,
655
- stderrTail: startupStderrTail,
656
- }),
657
- })
658
- }
659
-
660
- // ── Single server lifecycle ──────────────────────────────────────
661
- // The server is started lazily on first initializeOpencodeForDirectory() call.
662
- // It uses permissive defaults (edit: allow, bash: allow, webfetch: allow, and
663
- // external_directory: '*' allow unless --restrict-directories is set).
664
-
665
- // In-flight promise to prevent concurrent startups from racing
666
- let startingServer: Promise<
667
- ServerStartError | OpencodeIncompatibleVersionError | SingleServer
668
- > | null = null
669
- let preferredStartupDirectory: string | null = null
670
-
671
- function ensureOpencodeHomeDirectories({
672
- directories,
673
- }: {
674
- directories: Record<string, string>
675
- }) {
676
- Object.values(directories).map((directory) => {
677
- fs.mkdirSync(directory, { recursive: true })
678
- })
679
- }
680
-
681
- /**
682
- * Try to discover an OpenCode server already running in the bot process.
683
- * Queries the hrana server on the lock port for the OpenCode server port,
684
- * then verifies the server is healthy. Returns null if no server found.
685
- */
686
- async function discoverExistingServer(): Promise<SingleServer | null> {
687
- const lockPort = getLockPort()
688
- try {
689
- const portResponse = await requestHealthcheck({
690
- url: `http://127.0.0.1:${lockPort}/kimaki/opencode-port`,
691
- timeoutMs: 2000,
692
- })
693
- if (portResponse.status !== 200) {
694
- return null
695
- }
696
- const parsed = JSON.parse(portResponse.body)
697
- const port = parsed?.port
698
- if (typeof port !== 'number') {
699
- return null
700
- }
701
-
702
- // Verify the OpenCode server is actually healthy
703
- const healthResponse = await requestHealthcheck({
704
- url: `http://127.0.0.1:${port}/api/health`,
705
- timeoutMs: 2000,
706
- })
707
- if (healthResponse.status >= 500) {
708
- return null
709
- }
710
-
711
- opencodeLogger.log(
712
- `Discovered existing OpenCode server on port ${port} via hrana lock port ${lockPort}`,
713
- )
714
- return {
715
- process: null,
716
- port,
717
- baseUrl: `http://127.0.0.1:${port}`,
718
- discovered: true,
719
- }
720
- } catch {
721
- // Connection refused or other network error — no bot running
722
- return null
723
- }
724
- }
725
-
726
- async function ensureSingleServer({
727
- directory,
728
- }: {
729
- directory?: string
730
- } = {}): Promise<ServerStartError | OpencodeIncompatibleVersionError | SingleServer> {
731
- const startupDirectory = directory || preferredStartupDirectory || undefined
732
- if (singleServer && !singleServer.process?.killed) {
733
- return singleServer
734
- }
735
-
736
- // Deduplicate concurrent startup attempts (covers both discovery and spawn)
737
- if (startingServer) {
738
- return startingServer
739
- }
740
-
741
- // Wrap discovery + spawn in a single shared promise so concurrent callers
742
- // don't each run discoverExistingServer() and then each spawn a server.
743
- startingServer = (async () => {
744
- // Try to discover an already-running server from the bot process via
745
- // the hrana server's /kimaki/opencode-port endpoint. This lets CLI
746
- // subcommands (kimaki session list, archive, wait, etc.) reuse the
747
- // bot's OpenCode server instead of spawning a redundant one.
748
- const discovered = await discoverExistingServer()
749
- if (discovered) {
750
- singleServer = discovered
751
- return discovered
752
- }
753
-
754
- const compatibility = await assertCompatibleOpencodeVersion()
755
- if (compatibility instanceof Error) return compatibility
756
-
757
- return startSingleServer({ directory: startupDirectory })
758
- })()
759
-
760
- try {
761
- return await startingServer
762
- } finally {
763
- startingServer = null
764
- }
765
- }
766
-
767
- async function startSingleServer({
768
- directory,
769
- }: {
770
- directory?: string
771
- } = {}): Promise<ServerStartError | SingleServer> {
772
- ensureProcessCleanupHandlersRegistered()
773
-
774
- const configuredPort = getOpencodePort()
775
- const port = configuredPort ?? (await getOpenPort())
776
- const hostname = getOpencodeHostname() ?? DEFAULT_OPENCODE_HOSTNAME
777
-
778
- if (
779
- publicOpencodeBindRequiresPassword({ hostname }) &&
780
- !process.env.OPENCODE_SERVER_PASSWORD
781
- ) {
782
- return new ServerStartError({
783
- port,
784
- reason: `OPENCODE_SERVER_PASSWORD is required when --opencode-hostname is ${hostname}`,
785
- })
786
- }
787
-
788
- const serveArgs = buildOpencodeServeArgs({ port, hostname })
789
-
790
- const {
791
- command: spawnCommand,
792
- args: spawnArgs,
793
- windowsVerbatimArguments,
794
- } = getSpawnCommandAndArgs({
795
- resolvedCommand: resolveOpencodeCommand(),
796
- baseArgs: serveArgs,
797
- })
798
-
799
- // Server config uses permissive defaults. By default every external directory
800
- // is allowed: opencode's own 'ask' default produced constant permission
801
- // prompts for ordinary reads, and users who want protection can add their own
802
- // `deny`/`ask` rules in opencode.json (project config is loaded after this
803
- // file, so it wins).
804
- // With --restrict-directories the old behaviour comes back: only a small set
805
- // of known-safe paths is pre-allowed and everything else falls through to the
806
- // user's opencode.json default (which is 'ask' unless they changed it).
807
- const externalDirectoryPermissions = buildServerExternalDirectoryPermissions()
808
- const kimakiShimDirectory = ensureKimakiCommandShim({
809
- dataDir: getDataDir(),
810
- execPath: process.execPath,
811
- execArgv: process.execArgv,
812
- entryScript: process.argv[1] || fileURLToPath(new URL('../bin.js', import.meta.url)),
813
- })
814
- const pathEnvKey = getPathEnvKey(process.env)
815
- const pathEnv = kimakiShimDirectory instanceof Error
816
- ? process.env[pathEnvKey]
817
- : prependPathEntry({
818
- entry: kimakiShimDirectory,
819
- existingPath: process.env[pathEnvKey],
820
- })
821
- if (kimakiShimDirectory instanceof Error) {
822
- opencodeLogger.warn(kimakiShimDirectory.message)
823
- }
824
- const gatewayToken = store.getState().gatewayToken
825
- const vitestOpencodeEnv = (() => {
826
- if (process.env.KIMAKI_VITEST !== '1') {
827
- return {}
828
- }
829
- const root = path.join(getDataDir(), 'opencode-vitest-home')
830
- const directories = {
831
- OPENCODE_TEST_HOME: root,
832
- OPENCODE_CONFIG_DIR: path.join(root, '.opencode-kimaki'),
833
- XDG_CONFIG_HOME: path.join(root, '.config'),
834
- XDG_DATA_HOME: path.join(root, '.local', 'share'),
835
- XDG_CACHE_HOME: path.join(root, '.cache'),
836
- XDG_STATE_HOME: path.join(root, '.local', 'state'),
837
- }
838
- // OpenCode writes state/config files into these XDG locations during boot.
839
- // In CI, a fresh temp data dir means the parent folders may not exist yet,
840
- // and some writes fail closed with NotFound before OpenCode has a chance to
841
- // create them lazily. Pre-create the directories so startup-time tests do
842
- // not flap based on process scheduling.
843
- ensureOpencodeHomeDirectories({ directories })
844
- return directories
845
- })()
846
-
847
- // Write config to a file instead of passing via OPENCODE_CONFIG_CONTENT env var.
848
- // OPENCODE_CONFIG (file path) is loaded before project config in opencode's
849
- // priority chain, so project-level opencode.json can override kimaki defaults.
850
- // OPENCODE_CONFIG_CONTENT was loaded last and overrode user project configs,
851
- // causing issue #90 (project permissions not being respected).
852
- const isDev = import.meta.url.endsWith('.ts') || import.meta.url.endsWith('.tsx')
853
- // Skill whitelist/blacklist from --enable-skill / --disable-skill CLI flags.
854
- // Applied as opencode permission.skill rules so every agent inherits the
855
- // filter via Permission.merge(defaults, agentRules, user).
856
- const skillPermission = computeSkillPermission({
857
- enabledSkills: store.getState().enabledSkills,
858
- disabledSkills: store.getState().disabledSkills,
859
- })
860
- const opencodeConfig = {
861
- $schema: 'https://opencode.ai/config.json',
862
- lsp: false,
863
- formatter: false,
864
- plugin: [
865
- new URL(
866
- isDev ? './kimaki-opencode-plugin.ts' : './kimaki-opencode-plugin.js',
867
- import.meta.url,
868
- ).href,
869
- // npm identity lets opencode dedupe a user-installed copy by package
870
- // name. Development still loads this workspace's built package directly.
871
- resolveSubrouterPluginSpec({ isDev }),
872
- ],
873
- permission: {
874
- edit: 'allow',
875
- bash: 'allow',
876
- external_directory: externalDirectoryPermissions,
877
- webfetch: 'allow',
878
- ...(skillPermission && { skill: skillPermission }),
879
- },
880
- agent: {
881
- explore: {
882
- permission: {
883
- '*': 'deny',
884
- grep: 'allow',
885
- glob: 'allow',
886
- list: 'allow',
887
- read: {
888
- '*': 'allow',
889
- '*.env': 'deny',
890
- '*.env.*': 'deny',
891
- '*.env.example': 'allow',
892
- },
893
- webfetch: 'allow',
894
- websearch: 'allow',
895
- codesearch: 'allow',
896
- // No external_directory here on purpose. opencode composes agents as
897
- // merge(defaults, agentSpecific, userConfig) and then appends
898
- // config.agent.<name>.permission LAST, so anything set here would beat
899
- // the user's own top-level opencode.json rules. The top-level
900
- // permission block above already covers this agent.
901
- },
902
- },
903
- },
904
- // When a permission prompt times out and is auto-rejected, the model sees
905
- // the rejection as a tool error and continues working (tries alternatives
906
- // or explains it couldn't proceed) instead of the session going dead.
907
- experimental: {
908
- continue_loop_on_deny: true,
909
- },
910
- provider: {
911
- xai: {
912
- models: {
913
- 'grok-composer-2.5-fast': {
914
- name: 'Grok Composer 2.5 Fast',
915
- attachment: true,
916
- tool_call: true,
917
- limit: {
918
- context: 256000,
919
- output: 256000,
920
- },
921
- cost: {
922
- input: 0.50,
923
- output: 2.50,
924
- cache_read: 0.20,
925
- },
926
- },
927
- },
928
- },
929
- },
930
- skills: {
931
- paths: [path.resolve(__dirname, '..', 'skills')],
932
- },
933
- } satisfies Config
934
- const opencodeConfigPath = path.join(getDataDir(), 'opencode-config.json')
935
- const opencodeConfigJson = JSON.stringify(opencodeConfig, null, 2)
936
- const existingContent = (() => {
937
- try {
938
- return fs.readFileSync(opencodeConfigPath, 'utf-8')
939
- } catch {
940
- return ''
941
- }
942
- })()
943
- if (existingContent !== opencodeConfigJson) {
944
- fs.writeFileSync(opencodeConfigPath, opencodeConfigJson)
945
- }
946
-
947
- const serverProcess = spawn(
948
- spawnCommand,
949
- spawnArgs,
950
- {
951
- stdio: 'pipe',
952
- detached: false,
953
- windowsVerbatimArguments,
954
- // No project-specific cwd — the server handles all directories via
955
- // x-opencode-directory header. Use home dir as a neutral working dir.
956
- cwd: os.homedir(),
957
- env: {
958
- ...process.env,
959
- OPENCODE_CONFIG: opencodeConfigPath,
960
- OPENCODE_PORT: port.toString(),
961
- KIMAKI: '1',
962
- // The browser is not on this machine, so no localhost callback fires.
963
- SUBROUTER_MANUAL_OAUTH: '1',
964
- OPENCODE_EXPERIMENTAL_WORKSPACES: 'true',
965
- OPENCODE_ENABLE_EXA: '1',
966
- KIMAKI_DATA_DIR: getDataDir(),
967
- KIMAKI_LOCK_PORT: getLockPort().toString(),
968
- KIMAKI_PARENT_LOCK_PORT: getLockPort().toString(),
969
- ...(gatewayToken && { KIMAKI_DB_AUTH_TOKEN: gatewayToken }),
970
- // Guard: prevents agents from running `kimaki` root command inside
971
- // an OpenCode session, which would steal the lock port and break the bot.
972
- KIMAKI_OPENCODE_PROCESS: '1',
973
- ...(getHranaUrl() && { KIMAKI_DB_URL: getHranaUrl()! }),
974
- ...(process.env.KIMAKI_SENTRY_DSN && {
975
- KIMAKI_SENTRY_DSN: process.env.KIMAKI_SENTRY_DSN,
976
- }),
977
- ...vitestOpencodeEnv,
978
- ...(pathEnv && { [pathEnvKey]: pathEnv }),
979
- },
980
- },
981
- )
982
-
983
- startingServerProcess = serverProcess
984
-
985
- // Buffer logs until we know if server started successfully.
986
- const logBuffer: string[] = []
987
- const startupStderrTail: string[] = []
988
- let serverReady = false
989
-
990
- logBuffer.push(
991
- `Spawned opencode ${serveArgs.join(' ')} (pid: ${serverProcess.pid})`,
992
- )
993
-
994
- const stdoutReader = subscribeToProcessLogStream({
995
- stream: serverProcess.stdout,
996
- onLine: (line) => {
997
- if (!serverReady) {
998
- logBuffer.push(`[stdout] ${line}`)
999
- return
1000
- }
1001
- opencodeLogger.log(line)
1002
- },
1003
- })
1004
-
1005
- const stderrReader = subscribeToProcessLogStream({
1006
- stream: serverProcess.stderr,
1007
- onLine: (line) => {
1008
- if (!serverReady) {
1009
- logBuffer.push(`[stderr] ${line}`)
1010
- pushStartupStderrTail({ stderrTail: startupStderrTail, line })
1011
- return
1012
- }
1013
- opencodeLogger.error(line)
1014
- },
1015
- })
1016
-
1017
- serverProcess.on('error', (error) => {
1018
- logBuffer.push(`Failed to start server on port ${port}: ${error}`)
1019
- })
1020
-
1021
- serverProcess.on('exit', (code, signal) => {
1022
- stdoutReader?.close()
1023
- stderrReader?.close()
1024
-
1025
- if (startingServerProcess === serverProcess) {
1026
- startingServerProcess = null
1027
- }
1028
-
1029
- opencodeLogger.log(
1030
- `Opencode server exited with code: ${code}, signal: ${signal}`,
1031
- )
1032
- singleServer = null
1033
- clientCache.clear()
1034
- notifyServerLifecycle({ type: 'stopped' })
1035
-
1036
- // Intentional kills should not trigger auto-restart:
1037
- // - SIGTERM from our cleanup/restart code
1038
- // - SIGINT propagated from Ctrl+C (parent process group signal)
1039
- // - any exit during bot shutdown (shuttingDown flag)
1040
- // Only unexpected crashes (non-zero exit without signal) get retried.
1041
- if (signal === 'SIGTERM' || signal === 'SIGINT' || global.shuttingDown) {
1042
- serverRetryCount = 0
1043
- return
1044
- }
1045
- if (code !== 0) {
1046
- if (serverRetryCount < 5) {
1047
- serverRetryCount += 1
1048
- opencodeLogger.log(
1049
- `Restarting server (attempt ${serverRetryCount}/5)`,
1050
- )
1051
- void ensureSingleServer().then(
1052
- (result) => {
1053
- if (result instanceof Error) {
1054
- opencodeLogger.error(`Failed to restart opencode server:`, result)
1055
- void notifyError(result, `OpenCode server restart failed`)
1056
- }
1057
- },
1058
- )
1059
- } else {
1060
- const crashError = new Error(
1061
- `Server crashed too many times (5), not restarting`,
1062
- )
1063
- opencodeLogger.error(crashError.message)
1064
- void notifyError(crashError, `OpenCode server crash loop exhausted`)
1065
- }
1066
- } else {
1067
- serverRetryCount = 0
1068
- }
1069
- })
1070
-
1071
- const waitResult = await waitForServer({
1072
- port,
1073
- directory,
1074
- startupStderrTail,
1075
- })
1076
- if (waitResult instanceof Error) {
1077
- killStartingServerProcessNow({ reason: 'startup-failed' })
1078
- if (startingServerProcess === serverProcess) {
1079
- startingServerProcess = null
1080
- }
1081
-
1082
- // Dump buffered logs on failure
1083
- opencodeLogger.error(`Server failed to start:`)
1084
- for (const line of logBuffer) {
1085
- opencodeLogger.error(` ${line}`)
1086
- }
1087
- return waitResult
1088
- }
1089
- serverReady = true
1090
- // stopOpencodeServer() may have run while we waited (bot shutdown). Never
1091
- // publish a server that nobody will stop.
1092
- if (global.shuttingDown || serverProcess.killed) {
1093
- killStartingServerProcessNow({ reason: 'stopped-during-startup' })
1094
- if (startingServerProcess === serverProcess) {
1095
- startingServerProcess = null
1096
- }
1097
- return new ServerStartError({ port, reason: 'stopped during startup' })
1098
- }
1099
- opencodeLogger.log(`Server ready on port ${port}`)
1100
-
1101
- // Always dump startup logs so plugin loading errors and other startup output
1102
- // are visible in kimaki.log.
1103
- for (const line of logBuffer) {
1104
- opencodeLogger.log(line)
1105
- }
1106
-
1107
- const server: SingleServer = {
1108
- process: serverProcess,
1109
- port,
1110
- baseUrl: `http://127.0.0.1:${port}`,
1111
- }
1112
- if (startingServerProcess === serverProcess) {
1113
- startingServerProcess = null
1114
- }
1115
- singleServer = server
1116
- notifyServerLifecycle({ type: 'started', port })
1117
- return server
1118
- }
1119
-
1120
- function getOrCreateClient({
1121
- baseUrl,
1122
- directory,
1123
- }: {
1124
- baseUrl: string
1125
- directory: string
1126
- }): OpencodeClient {
1127
- const cached = clientCache.get(directory)
1128
- if (cached) {
1129
- return cached
1130
- }
1131
-
1132
- const fetchWithTimeout = (request: Request) =>
1133
- fetch(request, {
1134
- // @ts-ignore
1135
- timeout: false,
1136
- })
1137
-
1138
- const client = createOpencodeClient({
1139
- baseUrl,
1140
- directory,
1141
- fetch: fetchWithTimeout as typeof fetch,
1142
- headers: getOpencodeServerAuthHeaders(),
1143
- })
1144
- clientCache.set(directory, client)
1145
- return client
1146
- }
1147
-
1148
- // ── Public API ───────────────────────────────────────────────────
1149
- // Same signatures as before so callers don't need to change.
1150
-
1151
- /**
1152
- * Initialize OpenCode server for a directory.
1153
- * Starts the single shared server if not running, then returns a client
1154
- * factory scoped to the given directory via x-opencode-directory header.
1155
- *
1156
- * @param directory - The project directory to scope requests to
1157
- * @param options.originalRepoDirectory - For worktrees: the original repo directory
1158
- * (no longer used for server-level permissions — use buildSessionPermissions
1159
- * at session.create() time instead)
1160
- */
1161
- export async function initializeOpencodeForDirectory(
1162
- directory: string,
1163
- _options?: { originalRepoDirectory?: string; channelId?: string },
1164
- ): Promise<OpenCodeErrors | (() => OpencodeClient)> {
1165
- // Verify directory exists and is accessible
1166
- const accessCheck = errore.tryFn({
1167
- try: () => {
1168
- fs.accessSync(directory, fs.constants.R_OK | fs.constants.X_OK)
1169
- },
1170
- catch: () => new DirectoryNotAccessibleError({ directory }),
1171
- })
1172
- if (accessCheck instanceof Error) return accessCheck
1173
-
1174
- preferredStartupDirectory = directory
1175
-
1176
- const server = await ensureSingleServer({ directory })
1177
- if (server instanceof Error) return server
1178
-
1179
- if (!initializedDirectories.has(directory)) {
1180
- initializedDirectories.add(directory)
1181
- }
1182
-
1183
- return () => {
1184
- if (!singleServer) {
1185
- throw new ServerNotReadyError({ directory })
1186
- }
1187
- return getOrCreateClient({
1188
- baseUrl: singleServer.baseUrl,
1189
- directory,
1190
- })
1191
- }
1192
- }
1193
-
1194
- /**
1195
- * Known-safe paths that never need an external_directory prompt, used only when
1196
- * --restrict-directories is active. Without the flag every path is allowed and
1197
- * this list is irrelevant.
1198
- */
1199
- function knownSafeExternalDirectories(): string[] {
1200
- const tmpdir = os.tmpdir().replaceAll('\\', '/')
1201
- const homeDirectory = ({ relativePath }: { relativePath: string }) => {
1202
- return path.resolve(os.homedir(), relativePath.replaceAll('\\', '/'))
1203
- }
1204
- return [
1205
- '/tmp',
1206
- '/private/tmp',
1207
- tmpdir,
1208
- // The agent can read the global AGENTS.md and opencode config; the path is
1209
- // visible in the system prompt so models routinely try to open it.
1210
- homeDirectory({ relativePath: '.config/opencode' }),
1211
- // The Anthropic plugin rewrites the name in the system prompt, so some
1212
- // models try this misspelled path instead.
1213
- homeDirectory({ relativePath: '.config/openc0de' }),
1214
- // Cached opensrc checkouts.
1215
- homeDirectory({ relativePath: '.opensrc' }),
1216
- // Kimaki data dir (logs, db, etc).
1217
- homeDirectory({ relativePath: '.kimaki' }),
1218
- // Prior opencode tool outputs.
1219
- homeDirectory({ relativePath: '.local/share/opencode/tool-output' }),
1220
- // Language toolchain caches, so builds can inspect downloaded modules.
1221
- homeDirectory({ relativePath: '.cache/zig' }),
1222
- homeDirectory({ relativePath: '.cargo' }),
1223
- homeDirectory({ relativePath: '.cache/go-build' }),
1224
- homeDirectory({ relativePath: 'go/pkg' }),
1225
- ]
1226
- }
1227
-
1228
- /**
1229
- * Build the server-level `permission.external_directory` value.
1230
- *
1231
- * Default: `{ '*': 'allow' }` — no prompt for any directory.
1232
- * With --restrict-directories: an allow-list of known-safe paths only. There is
1233
- * deliberately no catch-all '*': 'ask' entry so opencode's own 'ask' default
1234
- * still applies to everything else.
1235
- *
1236
- * Always an object, never the plain string 'allow'. opencode deep-merges config
1237
- * files (remeda mergeDeep) and this file is loaded before the project's
1238
- * opencode.json, so object keys from the project merge on top of these and win
1239
- * via findLast(). A plain string would instead be replaced wholesale by the
1240
- * project object, dropping allow-all for every unmatched path.
1241
- */
1242
- function buildServerExternalDirectoryPermissions(): Record<
1243
- string,
1244
- 'ask' | 'allow' | 'deny'
1245
- > {
1246
- if (!getRestrictExternalDirectories()) {
1247
- return { [ALL_EXTERNAL_DIRECTORIES_PATTERN]: 'allow' }
1248
- }
1249
-
1250
- const permissions: Record<string, 'ask' | 'allow' | 'deny'> = {}
1251
- for (const directory of knownSafeExternalDirectories()) {
1252
- permissions[directory] = 'allow'
1253
- permissions[`${directory}/*`] = 'allow'
1254
- }
1255
- return permissions
1256
- }
1257
-
1258
- /**
1259
- * Build the per-session permission ruleset passed to session.create/update.
1260
- *
1261
- * Keep this list minimal. Session rules are the LAST ruleset opencode
1262
- * evaluates — `Permission.merge(agent.permission, session.permission)` in
1263
- * session/tools.ts, then `findLast()` in permission/index.ts — so every rule
1264
- * here silently overrides the user's own opencode.json. Only rules that must
1265
- * beat user config belong here.
1266
- *
1267
- * In particular, directory *allow* rules must NOT go here. They live in the
1268
- * server config so a project opencode.json can still deny or ask for specific
1269
- * folders. Putting an `external_directory: '*' allow` rule here would make
1270
- * every user `deny` rule a no-op.
1271
- *
1272
- * The session's own working directory never needs a rule either: opencode skips
1273
- * the external_directory gate entirely for paths inside the active instance
1274
- * (`containsPath` in tool/external-directory.ts).
1275
- *
1276
- * That leaves one rule: worktree isolation. Once a thread moves to a managed
1277
- * worktree, deny the original checkout so the agent stops editing the main repo.
1278
- */
1279
- export function buildSessionPermissions({
1280
- directory,
1281
- originalRepoDirectory,
1282
- }: {
1283
- directory: string
1284
- originalRepoDirectory?: string
1285
- }): PermissionRuleset {
1286
- // Normalize path separators for cross-platform compatibility (Windows uses backslashes)
1287
- const normalizedDirectory = directory.replaceAll('\\', '/')
1288
- const originalRepo = originalRepoDirectory?.replaceAll('\\', '/')
1289
-
1290
- if (!originalRepo || originalRepo === normalizedDirectory) {
1291
- return []
1292
- }
1293
-
1294
- return buildExternalDirectoryPermissionRules({
1295
- resolvedPattern: originalRepo,
1296
- action: 'deny',
1297
- })
1298
- }
1299
-
1300
- const ALL_EXTERNAL_DIRECTORIES_PATTERN = '*'
1301
-
1302
- function buildExternalDirectoryPermissionRules({
1303
- resolvedPattern,
1304
- action,
1305
- }: {
1306
- resolvedPattern: string
1307
- action: 'allow' | 'deny' | 'ask'
1308
- }): PermissionRuleset {
1309
- if (resolvedPattern === ALL_EXTERNAL_DIRECTORIES_PATTERN) {
1310
- return [
1311
- {
1312
- permission: 'external_directory',
1313
- pattern: ALL_EXTERNAL_DIRECTORIES_PATTERN,
1314
- action,
1315
- },
1316
- ]
1317
- }
1318
-
1319
- return [
1320
- {
1321
- permission: 'external_directory',
1322
- pattern: resolvedPattern,
1323
- action,
1324
- },
1325
- {
1326
- permission: 'external_directory',
1327
- pattern: `${resolvedPattern}/*`,
1328
- action,
1329
- },
1330
- ]
1331
- }
1332
-
1333
- /**
1334
- * Parse raw permission strings into PermissionRuleset entries.
1335
- *
1336
- * Accepted formats:
1337
- * "tool:action" → { permission: tool, pattern: "*", action }
1338
- * "tool:pattern:action" → { permission: tool, pattern, action }
1339
- *
1340
- * The action must be one of "allow", "deny", "ask" (case-insensitive).
1341
- * Parts are trimmed to tolerate whitespace from YAML deserialization.
1342
- * Invalid entries are silently skipped (bad user input shouldn't crash the bot).
1343
- * If `raw` is not an array, returns empty (defensive against malformed YAML markers).
1344
- */
1345
- export function parsePermissionRules(raw: unknown): PermissionRuleset {
1346
- if (!Array.isArray(raw)) {
1347
- return []
1348
- }
1349
- const validActions = new Set(['allow', 'deny', 'ask'])
1350
- return raw.flatMap((entry) => {
1351
- if (typeof entry !== 'string') {
1352
- return []
1353
- }
1354
- const parts = entry.split(':').map((s) => {
1355
- return s.trim()
1356
- })
1357
- if (parts.length === 2) {
1358
- const [permission, rawAction] = parts
1359
- const action = rawAction!.toLowerCase()
1360
- if (!permission || !validActions.has(action)) {
1361
- return []
1362
- }
1363
- return [{ permission, pattern: '*', action: action as 'allow' | 'deny' | 'ask' }]
1364
- }
1365
- if (parts.length >= 3) {
1366
- // Last segment is the action, first segment is the permission,
1367
- // everything in between is the pattern (may contain colons in theory,
1368
- // but unlikely for tool patterns).
1369
- const permission = parts[0]!
1370
- const rawAction = parts[parts.length - 1]!
1371
- const action = rawAction.toLowerCase()
1372
- const pattern = parts.slice(1, -1).join(':')
1373
- if (!permission || !pattern || !validActions.has(action)) {
1374
- return []
1375
- }
1376
- return [{ permission, pattern, action: action as 'allow' | 'deny' | 'ask' }]
1377
- }
1378
- return []
1379
- })
1380
- }
1381
-
1382
- // ── Injection guard per-session config ───────────────────────────
1383
- // Per-session injection guard patterns are written as JSON files to
1384
- // <dataDir>/injection-guard/<sessionId>.json. The injection guard plugin
1385
- // (running inside the opencode server process) reads KIMAKI_DATA_DIR env
1386
- // var to find these files in tool.execute.after.
1387
- // This avoids needing env vars (which are per-process, not per-session).
1388
-
1389
- function getInjectionGuardDir(): string {
1390
- return path.join(getDataDir(), 'injection-guard')
1391
- }
1392
-
1393
- /**
1394
- * Write per-session injection guard config so the plugin picks it up.
1395
- * Only call this if injectionGuardPatterns is non-empty.
1396
- */
1397
- export function writeInjectionGuardConfig({
1398
- sessionId,
1399
- scanPatterns,
1400
- }: {
1401
- sessionId: string
1402
- scanPatterns: string[]
1403
- }): void {
1404
- if (scanPatterns.length === 0) {
1405
- return
1406
- }
1407
- try {
1408
- const dir = getInjectionGuardDir()
1409
- fs.mkdirSync(dir, { recursive: true })
1410
- fs.writeFileSync(
1411
- path.join(dir, `${sessionId}.json`),
1412
- JSON.stringify({ scanPatterns }),
1413
- )
1414
- } catch {
1415
- // Best effort -- don't crash the bot if data dir write fails
1416
- }
1417
- }
1418
-
1419
- /**
1420
- * Remove per-session injection guard config file.
1421
- */
1422
- export function removeInjectionGuardConfig({ sessionId }: { sessionId: string }): void {
1423
- try {
1424
- fs.unlinkSync(path.join(getInjectionGuardDir(), `${sessionId}.json`))
1425
- } catch {
1426
- // File may already be gone
1427
- }
1428
- }
1429
-
1430
- /**
1431
- * Read per-session injection guard config. Used by the kimaki plugin
1432
- * inside the opencode server process.
1433
- */
1434
- export function readInjectionGuardConfig({ sessionId }: { sessionId: string }): { scanPatterns: string[] } | null {
1435
- try {
1436
- const raw = fs.readFileSync(
1437
- path.join(getInjectionGuardDir(), `${sessionId}.json`),
1438
- 'utf-8',
1439
- )
1440
- return JSON.parse(raw) as { scanPatterns: string[] }
1441
- } catch {
1442
- return null
1443
- }
1444
- }
1445
-
1446
- // ── Public helpers ───────────────────────────────────────────────
1447
- // These helpers expose the single shared server and directory-scoped clients.
1448
-
1449
- export function getOpencodeServerPort(_directory?: string): number | null {
1450
- return singleServer?.port ?? null
1451
- }
1452
-
1453
- export function getOpencodeServerBaseUrl(): string | null {
1454
- return singleServer?.baseUrl ?? null
1455
- }
1456
-
1457
- export function getOpencodeClient(directory: string): OpencodeClient | null {
1458
- if (!singleServer) {
1459
- return null
1460
- }
1461
- return getOrCreateClient({
1462
- baseUrl: singleServer.baseUrl,
1463
- directory,
1464
- })
1465
- }
1466
-
1467
- // Structural union of the OpenCode v2 SDK error response shapes. The concrete
1468
- // type of `result.error` varies per route, so we describe the fields each shape
1469
- // may carry instead of importing every per-route error union:
1470
- // - NotFoundError / BadRequestError: { name, data: { message } }
1471
- // - InvalidRequestError: { _tag, message }
1472
- // - EffectHttpApiErrorBadRequest: { _tag: "BadRequest" } (no message)
1473
- // - some routes also surface { errors: [...] }
1474
- export type SdkErrorResponse = {
1475
- data?: { message?: string; ref?: string } | null
1476
- message?: string
1477
- errors?: unknown[]
1478
- _tag?: string
1479
- name?: string
1480
- }
1481
-
1482
- /**
1483
- * Extract a human-readable message from an OpenCode SDK error response.
1484
- * Probes each known shape and falls back to a generic message.
1485
- */
1486
- export function extractSdkErrorMessage(error: SdkErrorResponse | null | undefined): string {
1487
- if (!error) {
1488
- return 'Unknown OpenCode API error'
1489
- }
1490
-
1491
- if (error.data?.message) {
1492
- const name = error.name ? `${error.name}: ` : ''
1493
- const ref = error.data.ref ? ` (${error.data.ref})` : ''
1494
- return `${name}${error.data.message}${ref}`
1495
- }
1496
-
1497
- if (error.message) {
1498
- return error.message
1499
- }
1500
-
1501
- if (error.errors && error.errors.length > 0) {
1502
- return JSON.stringify(error.errors)
1503
- }
1504
-
1505
- if (error._tag) {
1506
- return error._tag
1507
- }
1508
-
1509
- if (error.name) {
1510
- return error.name
1511
- }
1512
-
1513
- return 'Unknown OpenCode API error'
1514
- }
1515
-
1516
- /**
1517
- * Stop the single opencode server.
1518
- * Used for process teardown, tests, and explicit restarts.
1519
- */
1520
- export async function stopOpencodeServer(): Promise<boolean> {
1521
- // A server still booting lives only in startingServerProcess.
1522
- killStartingServerProcessNow({ reason: 'stop-opencode-server' })
1523
- startingServerProcess = null
1524
-
1525
- if (!singleServer) {
1526
- return false
1527
- }
1528
-
1529
- const server = singleServer
1530
-
1531
- // For discovered servers (from another process), just clear local state
1532
- // without killing the process we don't own.
1533
- if (server.discovered || !server.process) {
1534
- singleServer = null
1535
- clientCache.clear()
1536
- serverRetryCount = 0
1537
- return true
1538
- }
1539
-
1540
- opencodeLogger.log(
1541
- `Stopping opencode server (pid: ${server.process.pid}, port: ${server.port})`,
1542
- )
1543
- if (!server.process.killed) {
1544
- const killResult = errore.try(
1545
- () => {
1546
- server.process!.kill('SIGTERM')
1547
- },
1548
- (error) => {
1549
- return new Error('Failed to send SIGTERM to opencode server', {
1550
- cause: error,
1551
- })
1552
- },
1553
- )
1554
- if (killResult instanceof Error) {
1555
- opencodeLogger.warn(killResult.message)
1556
- }
1557
- }
1558
-
1559
- singleServer = null
1560
- clientCache.clear()
1561
- serverRetryCount = 0
1562
- // Don't dispose the global listener here — it will reconnect when
1563
- // the server restarts. Only abort the current SSE connection so it
1564
- // doesn't hang on a dead server.
1565
- restartGlobalEventListener()
1566
- await new Promise((resolve) => {
1567
- setTimeout(resolve, 1000)
1568
- })
1569
- return true
1570
- }
1571
-
1572
- /**
1573
- * Restart the single opencode server.
1574
- * Kills the existing process and starts a new one.
1575
- * Used for resolving opencode state issues, refreshing auth, plugins, etc.
1576
- */
1577
- export async function restartOpencodeServer(): Promise<OpenCodeErrors | true> {
1578
- if (singleServer) {
1579
- await stopOpencodeServer()
1580
- }
1581
-
1582
- // Reset retry count for the fresh start
1583
- serverRetryCount = 0
1584
-
1585
- const result = await ensureSingleServer()
1586
- if (result instanceof Error) return result
1587
- restartGlobalEventListener()
1588
- await waitForGlobalEventListener()
1589
- return true
1590
- }