kimaki 0.26.0 → 0.28.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 (302) hide show
  1. package/dist/agent-model.e2e.test.js +250 -164
  2. package/dist/anthropic-account-identity.js +9 -1
  3. package/dist/anthropic-auth-plugin.js +10 -13
  4. package/dist/anthropic-auth-plugin.test.js +17 -0
  5. package/dist/anthropic-auth-state.js +1 -0
  6. package/dist/bash-tool-schema-plugin.js +32 -0
  7. package/dist/bash-tool-schema-plugin.test.js +49 -0
  8. package/dist/bin.js +73 -19
  9. package/dist/cli-commands/maintenance.js +46 -7
  10. package/dist/cli-commands/project.js +11 -27
  11. package/dist/cli-commands/send.js +30 -1
  12. package/dist/cli-commands/session.js +322 -90
  13. package/dist/cli-commands/task.js +34 -15
  14. package/dist/cli-parsing.test.js +83 -4
  15. package/dist/cli-runner.js +44 -8
  16. package/dist/cli-runner.test.js +27 -0
  17. package/dist/cli-send-thread.e2e.test.js +9 -9
  18. package/dist/cli.js +32 -0
  19. package/dist/commands/abort.js +5 -1
  20. package/dist/commands/action-buttons.js +2 -1
  21. package/dist/commands/add-project.js +4 -7
  22. package/dist/commands/agent.js +164 -32
  23. package/dist/commands/ask-question.js +23 -33
  24. package/dist/commands/ask-question.test.js +43 -1
  25. package/dist/commands/btw.js +28 -6
  26. package/dist/commands/file-upload.js +2 -1
  27. package/dist/commands/fork-subagent.js +2 -4
  28. package/dist/commands/fork.js +4 -4
  29. package/dist/commands/last-sessions.js +17 -10
  30. package/dist/commands/login.js +6 -0
  31. package/dist/commands/merge-worktree.js +47 -14
  32. package/dist/commands/model-variant.js +8 -1
  33. package/dist/commands/model.js +16 -9
  34. package/dist/commands/multioauth.js +1 -0
  35. package/dist/commands/new-worktree.js +170 -102
  36. package/dist/commands/queue.js +3 -2
  37. package/dist/commands/resume.js +4 -4
  38. package/dist/commands/session.js +78 -63
  39. package/dist/commands/user-command.js +6 -8
  40. package/dist/commands/worktrees.js +5 -13
  41. package/dist/config.js +16 -1
  42. package/dist/cpu-profiler.js +211 -0
  43. package/dist/cpu-profiler.test.js +160 -0
  44. package/dist/database.js +152 -3
  45. package/dist/db.js +33 -0
  46. package/dist/db.test.js +267 -1
  47. package/dist/default-channel-provisioning.test.js +148 -0
  48. package/dist/discord-bot.js +517 -488
  49. package/dist/discord-command-registration.js +25 -10
  50. package/dist/discord-command-registration.test.js +17 -0
  51. package/dist/discord-utils.js +97 -5
  52. package/dist/discord-utils.test.js +151 -1
  53. package/dist/errors.js +10 -0
  54. package/dist/exec-async.js +17 -5
  55. package/dist/external-directory-permissions.e2e.test.js +11 -7
  56. package/dist/external-directory-project-deny.e2e.test.js +5 -3
  57. package/dist/external-opencode-sync.js +94 -17
  58. package/dist/external-opencode-sync.test.js +206 -0
  59. package/dist/extract-sdk-error-message.test.js +13 -0
  60. package/dist/file-edit-log.js +197 -0
  61. package/dist/file-edit-log.test.js +258 -0
  62. package/dist/format-tables.js +52 -0
  63. package/dist/format-tables.test.js +93 -0
  64. package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
  65. package/dist/gateway-proxy.e2e.test.js +18 -18
  66. package/dist/git-worktree-core.js +182 -17
  67. package/dist/interaction-handler.js +416 -398
  68. package/dist/ipc-tools-plugin.js +68 -3
  69. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
  70. package/dist/kimaki-opencode-plugin.js +9 -3
  71. package/dist/kimaki-workspace-adaptor.js +33 -10
  72. package/dist/logger.js +11 -1
  73. package/dist/markdown.js +3 -1
  74. package/dist/markdown.test.js +11 -3
  75. package/dist/message-formatting.js +181 -51
  76. package/dist/message-formatting.test.js +529 -22
  77. package/dist/message-preprocessing.js +105 -1
  78. package/dist/oauth-rotation-shared.js +10 -0
  79. package/dist/openai-auth-plugin.js +1 -0
  80. package/dist/openai-auth-state.js +1 -0
  81. package/dist/opencode-command.js +28 -0
  82. package/dist/opencode-command.test.js +109 -1
  83. package/dist/opencode-interrupt-plugin.js +4 -0
  84. package/dist/opencode-interrupt-plugin.test.js +24 -0
  85. package/dist/opencode.js +82 -15
  86. package/dist/project-add.test.js +62 -0
  87. package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
  88. package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
  89. package/dist/question-text-before-dropdown.e2e.test.js +52 -0
  90. package/dist/queue-advanced-abort.e2e.test.js +8 -10
  91. package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
  92. package/dist/queue-advanced-e2e-setup.js +78 -5
  93. package/dist/queue-advanced-footer.e2e.test.js +60 -269
  94. package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
  95. package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
  96. package/dist/queue-advanced-question.e2e.test.js +32 -27
  97. package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
  98. package/dist/queue-advanced-typing.e2e.test.js +7 -9
  99. package/dist/queue-delete-message.e2e.test.js +3 -3
  100. package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
  101. package/dist/queue-interrupt-drain.e2e.test.js +122 -12
  102. package/dist/queue-question-select-drain.e2e.test.js +34 -132
  103. package/dist/runtime-lifecycle.e2e.test.js +21 -51
  104. package/dist/schema.js +35 -1
  105. package/dist/session-handler/event-stream-state.js +391 -50
  106. package/dist/session-handler/event-stream-state.test.js +736 -8
  107. package/dist/session-handler/model-utils.js +172 -5
  108. package/dist/session-handler/model-utils.test.js +445 -0
  109. package/dist/session-handler/opencode-session-event-log.js +30 -1
  110. package/dist/session-handler/thread-ingress.test.js +41 -0
  111. package/dist/session-handler/thread-runtime-state.js +2 -0
  112. package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
  113. package/dist/session-handler/thread-session-runtime.js +574 -299
  114. package/dist/session-search.js +123 -4
  115. package/dist/session-search.test.js +248 -1
  116. package/dist/session-sleep.test.js +224 -0
  117. package/dist/session-title-rename.test.js +37 -39
  118. package/dist/sleep.e2e.test.js +494 -0
  119. package/dist/startup-time.e2e.test.js +2 -1
  120. package/dist/store.js +3 -0
  121. package/dist/system-message.js +102 -16
  122. package/dist/system-message.test.js +192 -15
  123. package/dist/task-runner.js +96 -5
  124. package/dist/task-schedule.js +87 -1
  125. package/dist/task-schedule.test.js +149 -1
  126. package/dist/test-utils.js +17 -9
  127. package/dist/thinking-utils.js +10 -0
  128. package/dist/thinking-utils.test.js +35 -0
  129. package/dist/thread-message-queue.e2e.test.js +65 -213
  130. package/dist/undo-redo.e2e.test.js +17 -9
  131. package/dist/voice-attachment.js +23 -1
  132. package/dist/voice-handler.js +21 -4
  133. package/dist/voice-message.e2e.test.js +144 -30
  134. package/dist/voice-routing-input.e2e.test.js +195 -0
  135. package/dist/voice-routing-question.e2e.test.js +125 -0
  136. package/dist/voice.js +28 -8
  137. package/dist/voice.test.js +96 -30
  138. package/dist/worktree-clone-isolation.e2e.test.js +188 -0
  139. package/dist/worktree-lifecycle.e2e.test.js +55 -36
  140. package/dist/worktrees.js +226 -443
  141. package/dist/worktrees.test.js +435 -58
  142. package/dist/xai-auth-plugin.js +1 -0
  143. package/dist/xai-auth-state.js +1 -0
  144. package/package.json +8 -6
  145. package/skills/bump-holocron-consumers/SKILL.md +137 -0
  146. package/skills/critique/SKILL.md +4 -4
  147. package/skills/errore/SKILL.md +60 -218
  148. package/skills/goke/SKILL.md +150 -10
  149. package/skills/holocron/SKILL.md +154 -17
  150. package/skills/npm-package/SKILL.md +55 -35
  151. package/skills/profano/SKILL.md +17 -2
  152. package/skills/spiceflow/SKILL.md +39 -3
  153. package/skills/strada/SKILL.md +39 -0
  154. package/skills/zele/SKILL.md +19 -0
  155. package/src/agent-model.e2e.test.ts +287 -186
  156. package/src/anthropic-account-identity.ts +20 -1
  157. package/src/anthropic-auth-plugin.test.ts +19 -0
  158. package/src/anthropic-auth-plugin.ts +12 -16
  159. package/src/anthropic-auth-state.ts +1 -0
  160. package/src/bash-tool-schema-plugin.test.ts +55 -0
  161. package/src/bash-tool-schema-plugin.ts +46 -0
  162. package/src/bin.ts +78 -26
  163. package/src/cli-commands/maintenance.ts +59 -13
  164. package/src/cli-commands/project.ts +14 -35
  165. package/src/cli-commands/send.ts +31 -1
  166. package/src/cli-commands/session.ts +374 -114
  167. package/src/cli-commands/task.ts +34 -15
  168. package/src/cli-parsing.test.ts +103 -4
  169. package/src/cli-runner.test.ts +49 -0
  170. package/src/cli-runner.ts +44 -7
  171. package/src/cli-send-thread.e2e.test.ts +9 -11
  172. package/src/cli.ts +53 -0
  173. package/src/commands/abort.ts +6 -1
  174. package/src/commands/action-buttons.ts +2 -1
  175. package/src/commands/add-project.ts +4 -8
  176. package/src/commands/agent.ts +205 -29
  177. package/src/commands/ask-question.test.ts +48 -1
  178. package/src/commands/ask-question.ts +23 -33
  179. package/src/commands/btw.ts +40 -5
  180. package/src/commands/file-upload.ts +2 -1
  181. package/src/commands/fork-subagent.ts +2 -3
  182. package/src/commands/fork.ts +4 -3
  183. package/src/commands/last-sessions.ts +10 -4
  184. package/src/commands/login.ts +6 -0
  185. package/src/commands/merge-worktree.ts +54 -16
  186. package/src/commands/model-variant.ts +9 -1
  187. package/src/commands/model.ts +20 -9
  188. package/src/commands/multioauth.ts +1 -0
  189. package/src/commands/new-worktree.ts +138 -55
  190. package/src/commands/queue.ts +3 -2
  191. package/src/commands/resume.ts +4 -3
  192. package/src/commands/session.ts +94 -70
  193. package/src/commands/user-command.ts +6 -6
  194. package/src/commands/worktrees.ts +5 -12
  195. package/src/config.ts +18 -1
  196. package/src/cpu-profiler.test.ts +183 -0
  197. package/src/cpu-profiler.ts +293 -0
  198. package/src/database.ts +235 -3
  199. package/src/db.test.ts +293 -0
  200. package/src/db.ts +38 -0
  201. package/src/default-channel-provisioning.test.ts +155 -0
  202. package/src/discord-bot.ts +78 -36
  203. package/src/discord-command-registration.test.ts +20 -0
  204. package/src/discord-command-registration.ts +42 -12
  205. package/src/discord-utils.test.ts +176 -0
  206. package/src/discord-utils.ts +152 -7
  207. package/src/errors.ts +12 -0
  208. package/src/exec-async.ts +20 -11
  209. package/src/external-directory-permissions.e2e.test.ts +11 -7
  210. package/src/external-directory-project-deny.e2e.test.ts +5 -3
  211. package/src/external-opencode-sync.test.ts +248 -0
  212. package/src/external-opencode-sync.ts +122 -19
  213. package/src/extract-sdk-error-message.test.ts +18 -0
  214. package/src/file-edit-log.test.ts +287 -0
  215. package/src/file-edit-log.ts +283 -0
  216. package/src/format-tables.test.ts +101 -0
  217. package/src/format-tables.ts +75 -0
  218. package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
  219. package/src/gateway-proxy.e2e.test.ts +18 -17
  220. package/src/git-worktree-core.ts +247 -20
  221. package/src/interaction-handler.ts +36 -13
  222. package/src/ipc-tools-plugin.ts +82 -5
  223. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
  224. package/src/kimaki-opencode-plugin.ts +9 -3
  225. package/src/kimaki-workspace-adaptor.ts +40 -11
  226. package/src/logger.ts +14 -4
  227. package/src/markdown.test.ts +13 -3
  228. package/src/markdown.ts +4 -1
  229. package/src/message-formatting.test.ts +672 -25
  230. package/src/message-formatting.ts +242 -53
  231. package/src/message-preprocessing.ts +116 -1
  232. package/src/oauth-rotation-shared.ts +10 -0
  233. package/src/openai-auth-plugin.ts +1 -0
  234. package/src/openai-auth-state.ts +1 -0
  235. package/src/opencode-command.test.ts +140 -0
  236. package/src/opencode-command.ts +38 -0
  237. package/src/opencode-interrupt-plugin.test.ts +32 -0
  238. package/src/opencode-interrupt-plugin.ts +3 -0
  239. package/src/opencode.ts +115 -15
  240. package/src/project-add.test.ts +80 -0
  241. package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
  242. package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
  243. package/src/question-text-before-dropdown.e2e.test.ts +67 -0
  244. package/src/queue-advanced-abort.e2e.test.ts +8 -9
  245. package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
  246. package/src/queue-advanced-e2e-setup.ts +83 -4
  247. package/src/queue-advanced-footer.e2e.test.ts +67 -315
  248. package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
  249. package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
  250. package/src/queue-advanced-question.e2e.test.ts +36 -30
  251. package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
  252. package/src/queue-advanced-typing.e2e.test.ts +8 -8
  253. package/src/queue-delete-message.e2e.test.ts +3 -3
  254. package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
  255. package/src/queue-interrupt-drain.e2e.test.ts +133 -12
  256. package/src/queue-question-select-drain.e2e.test.ts +36 -161
  257. package/src/runtime-lifecycle.e2e.test.ts +21 -54
  258. package/src/schema.sql +15 -2
  259. package/src/schema.ts +37 -1
  260. package/src/session-handler/event-stream-state.test.ts +790 -7
  261. package/src/session-handler/event-stream-state.ts +568 -53
  262. package/src/session-handler/model-utils.test.ts +537 -0
  263. package/src/session-handler/model-utils.ts +281 -5
  264. package/src/session-handler/opencode-session-event-log.ts +31 -1
  265. package/src/session-handler/thread-ingress.test.ts +54 -0
  266. package/src/session-handler/thread-runtime-state.ts +3 -0
  267. package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
  268. package/src/session-handler/thread-session-runtime.ts +712 -358
  269. package/src/session-search.test.ts +281 -0
  270. package/src/session-search.ts +241 -7
  271. package/src/session-sleep.test.ts +294 -0
  272. package/src/session-title-rename.test.ts +54 -48
  273. package/src/sleep.e2e.test.ts +565 -0
  274. package/src/startup-time.e2e.test.ts +5 -1
  275. package/src/store.ts +22 -0
  276. package/src/system-message.test.ts +226 -15
  277. package/src/system-message.ts +130 -16
  278. package/src/task-runner.ts +138 -3
  279. package/src/task-schedule.test.ts +170 -0
  280. package/src/task-schedule.ts +141 -1
  281. package/src/test-utils.ts +21 -10
  282. package/src/thinking-utils.test.ts +46 -0
  283. package/src/thinking-utils.ts +21 -0
  284. package/src/thread-message-queue.e2e.test.ts +65 -245
  285. package/src/undo-redo.e2e.test.ts +17 -9
  286. package/src/voice-attachment.ts +29 -1
  287. package/src/voice-handler.ts +23 -4
  288. package/src/voice-message.e2e.test.ts +149 -29
  289. package/src/voice-routing-input.e2e.test.ts +203 -0
  290. package/src/voice-routing-question.e2e.test.ts +131 -0
  291. package/src/voice.test.ts +107 -29
  292. package/src/voice.ts +34 -6
  293. package/src/worktree-clone-isolation.e2e.test.ts +213 -0
  294. package/src/worktree-lifecycle.e2e.test.ts +55 -35
  295. package/src/worktrees.test.ts +505 -64
  296. package/src/worktrees.ts +257 -603
  297. package/src/xai-auth-plugin.ts +1 -0
  298. package/src/xai-auth-state.ts +1 -0
  299. package/dist/message-finish-field.e2e.test.js +0 -169
  300. package/dist/subagent-rate-limit-plugin.js +0 -184
  301. package/src/message-finish-field.e2e.test.ts +0 -196
  302. package/src/subagent-rate-limit-plugin.ts +0 -228
@@ -19,7 +19,7 @@ import { getRuntime } from './session-handler/thread-session-runtime.js';
19
19
  import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, setChannelVerbosity, } from './database.js';
20
20
  import { startHranaServer, stopHranaServer } from './hrana-server.js';
21
21
  import { initializeOpencodeForDirectory, restartOpencodeServer, stopOpencodeServer, } from './opencode.js';
22
- import { chooseLockPort, cleanupTestSessions, initTestGitRepo, waitForBotMessageContaining, waitForBotReplyAfterUserMessage, } from './test-utils.js';
22
+ import { chooseLockPort, cleanupTestSessions, initTestGitRepo, isFooterMessage, waitForBotMessageContaining, waitForBotReplyAfterUserMessage, } from './test-utils.js';
23
23
  const TEST_USER_ID = '200000000000000888';
24
24
  const TEXT_CHANNEL_ID = '200000000000000889';
25
25
  function createRunDirectories() {
@@ -202,10 +202,10 @@ describe('runtime lifecycle', () => {
202
202
  fs.rmSync(directories.dataDir, { recursive: true, force: true });
203
203
  }
204
204
  }, 5_000);
205
- test('three sequential completions reuse same runtime and listener', async () => {
206
- // Sends A, waits for full completion (footer), sends B, waits for
207
- // footer, sends C, waits for footer. Proves the listener stays alive
208
- // across full run cycles without any interrupt/queue involvement.
205
+ test('two sequential completions reuse same runtime and listener', async () => {
206
+ // Sends A, waits for full completion (footer), then sends B and waits
207
+ // for its footer. Proves the listener stays alive across full run cycles
208
+ // without any interrupt/queue involvement.
209
209
  // This is the "calm" path — no abort, no queue, just sequential use.
210
210
  // 1. Send first message → thread created, session established
211
211
  await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
@@ -251,47 +251,20 @@ describe('runtime lifecycle', () => {
251
251
  });
252
252
  // Same runtime instance — listener was not recreated
253
253
  const runtimeAfterB = getRuntime(thread.id);
254
- expect(runtimeAfterB).toBe(runtimeAfterA);
255
- // 3. Send C after B fully completed
256
- await th.user(TEST_USER_ID).sendMessage({
257
- content: 'Reply with exactly: seq-gamma',
258
- });
259
- await waitForBotReplyAfterUserMessage({
260
- discord,
261
- threadId: thread.id,
262
- userId: TEST_USER_ID,
263
- userMessageIncludes: 'seq-gamma',
264
- timeout: 4_000,
265
- });
266
- await waitForBotMessageContaining({
267
- discord,
268
- threadId: thread.id,
269
- userId: TEST_USER_ID,
270
- text: '*project',
271
- afterUserMessageIncludes: 'seq-gamma',
272
- timeout: 4_000,
273
- });
274
- // Still the same runtime — three full cycles, one runtime, one listener
275
- const runtimeAfterC = getRuntime(thread.id);
276
254
  expect(await th.text()).toMatchInlineSnapshot(`
277
255
  "--- from: user (lifecycle-tester)
278
256
  Reply with exactly: seq-alpha
279
257
  --- from: assistant (TestBot)
280
- *using deterministic-provider/deterministic-v2*
281
- ⬥ ok
282
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
258
+ > *using deterministic-provider/deterministic-v2*
259
+ > ok
260
+ > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>
283
261
  --- from: user (lifecycle-tester)
284
262
  Reply with exactly: seq-beta
285
263
  --- from: assistant (TestBot)
286
- ⬥ ok
287
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
288
- --- from: user (lifecycle-tester)
289
- Reply with exactly: seq-gamma
290
- --- from: assistant (TestBot)
291
- ⬥ ok
292
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
264
+ > ok
265
+ > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>"
293
266
  `);
294
- expect(runtimeAfterC).toBe(runtimeAfterA);
267
+ expect(runtimeAfterB).toBe(runtimeAfterA);
295
268
  }, 15_000);
296
269
  test('footer includes context percentage and model id', async () => {
297
270
  const prompt = 'Reply with exactly: footer-check';
@@ -313,10 +286,7 @@ describe('runtime lifecycle', () => {
313
286
  });
314
287
  const messages = await discord.thread(thread.id).getMessages();
315
288
  const footerMessage = messages.find((message) => {
316
- if (message.author.id !== discord.botUserId) {
317
- return false;
318
- }
319
- if (!message.content.startsWith('*')) {
289
+ if (!isFooterMessage({ message, botUserId: discord.botUserId })) {
320
290
  return false;
321
291
  }
322
292
  return message.content.includes('deterministic-v2') && message.content.includes('%');
@@ -325,9 +295,9 @@ describe('runtime lifecycle', () => {
325
295
  "--- from: user (lifecycle-tester)
326
296
  Reply with exactly: footer-check
327
297
  --- from: assistant (TestBot)
328
- *using deterministic-provider/deterministic-v2*
329
- ⬥ ok
330
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
298
+ > *using deterministic-provider/deterministic-v2*
299
+ > ok
300
+ > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>"
331
301
  `);
332
302
  expect(footerMessage).toBeDefined();
333
303
  if (!footerMessage) {
@@ -383,14 +353,14 @@ describe('runtime lifecycle', () => {
383
353
  "--- from: user (lifecycle-tester)
384
354
  Reply with exactly: reconnect-alpha
385
355
  --- from: assistant (TestBot)
386
- *using deterministic-provider/deterministic-v2*
387
- ⬥ ok
388
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
356
+ > *using deterministic-provider/deterministic-v2*
357
+ > ok
358
+ > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>
389
359
  --- from: user (lifecycle-tester)
390
360
  Reply with exactly: reconnect-beta
391
361
  --- from: assistant (TestBot)
392
- ⬥ ok
393
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
362
+ > ok
363
+ > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>"
394
364
  `);
395
365
  const runtimeAfterRestart = getRuntime(thread.id);
396
366
  expect(runtimeAfterRestart).toBe(runtimeBeforeRestart);
@@ -420,7 +390,7 @@ describe('runtime lifecycle', () => {
420
390
  "--- from: user (lifecycle-tester)
421
391
  Reply with exactly: footer-high-usage
422
392
  --- from: assistant (TestBot)
423
- *using deterministic-provider/deterministic-v2*"
393
+ > *using deterministic-provider/deterministic-v2*"
424
394
  `);
425
395
  const threadText = await discord.thread(thread.id).text();
426
396
  expect(threadText).not.toContain('⬦ context usage');
package/dist/schema.js CHANGED
@@ -27,6 +27,10 @@ export const thread_sessions = sqliteCore.sqliteTable('thread_sessions', {
27
27
  // Survives bot restarts so child multi-turn system prompts keep the parent ID.
28
28
  parent_session_id: sqliteCore.text('parent_session_id'),
29
29
  created_at: datetime('created_at').default(orm.sql `CURRENT_TIMESTAMP`),
30
+ // Bumped on every (re)binding of a session to this thread. /resume can map one
31
+ // session to several threads, so session_id -> thread_id needs a tiebreaker.
32
+ // Without it the reverse lookup is arbitrary and tools can target a dead thread.
33
+ updated_at: datetime('updated_at').default(orm.sql `CURRENT_TIMESTAMP`).$onUpdate(() => new Date()),
30
34
  });
31
35
  export const session_events = sqliteCore.sqliteTable('session_events', {
32
36
  id: sqliteCore.integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }).notNull(),
@@ -42,7 +46,7 @@ export const session_events = sqliteCore.sqliteTable('session_events', {
42
46
  export const part_messages = sqliteCore.sqliteTable('part_messages', {
43
47
  part_id: sqliteCore.text('part_id').primaryKey().notNull(),
44
48
  message_id: sqliteCore.text('message_id').notNull(),
45
- thread_id: sqliteCore.text('thread_id').notNull().references(() => thread_sessions.thread_id, { onUpdate: 'cascade' }),
49
+ thread_id: sqliteCore.text('thread_id').notNull().references(() => thread_sessions.thread_id, { onDelete: 'cascade', onUpdate: 'cascade' }),
46
50
  created_at: datetime('created_at').default(orm.sql `CURRENT_TIMESTAMP`),
47
51
  });
48
52
  export const bot_tokens = sqliteCore.sqliteTable('bot_tokens', {
@@ -198,6 +202,35 @@ export const forum_sync_configs = sqliteCore.sqliteTable('forum_sync_configs', {
198
202
  }, (table) => [
199
203
  sqliteCore.uniqueIndex('forum_sync_configs_app_id_forum_channel_id_key').on(table.app_id, table.forum_channel_id),
200
204
  ]);
205
+ // Durable "wake this session later" rows for the kimaki_sleep tool.
206
+ //
207
+ // Delivery is at-least-once, and ingress makes it at-most-once-per-turn:
208
+ //
209
+ // planned ──► consumed cancelled / failed are terminal
210
+ // ▲ │
211
+ // └───┘ retried until ingress consumes it
212
+ //
213
+ // A row stays `planned` until ingress turns the wake into a turn, so ANY loss
214
+ // (crash before posting, crash after posting, missed gateway event) is covered
215
+ // by the same retry. `delivery_id` is the Discord message nonce, so a retry
216
+ // returns the existing message instead of waking the session twice.
217
+ //
218
+ // There is no thread_id: the owning thread is resolved from session_id at wake
219
+ // time, so a session rebound by /resume wakes in its current thread.
220
+ export const session_sleeps = sqliteCore.sqliteTable('session_sleeps', {
221
+ session_id: sqliteCore.text('session_id').primaryKey().notNull(),
222
+ wake_at: datetime('wake_at').notNull(),
223
+ reason: sqliteCore.text('reason'),
224
+ status: sqliteCore.text('status', { enum: ['planned', 'consumed', 'cancelled', 'failed'] }).notNull().default('planned'),
225
+ // Regenerated per sleep occurrence. Doubles as the Discord nonce and as a
226
+ // generation guard so a stale in-flight wake cannot mutate a newer sleep.
227
+ delivery_id: sqliteCore.text('delivery_id').notNull().$defaultFn(() => crypto.randomUUID()),
228
+ attempts: sqliteCore.integer('attempts', { mode: 'number' }).notNull().default(0),
229
+ last_attempt_at: datetime('last_attempt_at'),
230
+ created_at: datetime('created_at').default(orm.sql `CURRENT_TIMESTAMP`),
231
+ }, (table) => [
232
+ sqliteCore.index('session_sleeps_status_wake_at_idx').on(table.status, table.wake_at),
233
+ ]);
201
234
  export const ipc_requests = sqliteCore.sqliteTable('ipc_requests', {
202
235
  id: sqliteCore.text('id').primaryKey().notNull().$defaultFn(() => crypto.randomUUID()),
203
236
  type: sqliteCore.text('type', { enum: ['file_upload', 'action_buttons'] }).notNull(),
@@ -232,6 +265,7 @@ export const relations = defineRelations({
232
265
  scheduled_task_runs,
233
266
  session_start_sources,
234
267
  forum_sync_configs,
268
+ session_sleeps,
235
269
  ipc_requests,
236
270
  }, (r) => ({
237
271
  thread_sessions: {