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
@@ -25,6 +25,24 @@ afterEach(async () => {
25
25
  })
26
26
 
27
27
  describe('system-message', () => {
28
+ test('requires kimaki upload for Discord images, not markdown', () => {
29
+ const message = getOpencodeSystemMessage({
30
+ sessionId: 'ses_123',
31
+ })
32
+ expect(message).toContain('NEVER show images with markdown')
33
+ expect(message).toContain('Discord does not render local markdown images')
34
+ expect(message).toContain('ALWAYS upload them with `kimaki upload-to-discord`')
35
+ })
36
+
37
+ test('requires reading the report-bugs guide before filing kimaki issues', () => {
38
+ const message = getOpencodeSystemMessage({
39
+ sessionId: 'ses_123',
40
+ })
41
+ expect(message).toContain(
42
+ 'Never open a pull request on remorses/kimaki unless remorses asked for one in a comment on the issue',
43
+ )
44
+ })
45
+
28
46
  test('includes callout guidance for important content', () => {
29
47
  const message = getOpencodeSystemMessage({
30
48
  sessionId: 'ses_123',
@@ -39,6 +57,30 @@ describe('system-message', () => {
39
57
  expect(message).toContain('<callout accent="#f59e0b">')
40
58
  })
41
59
 
60
+ test('tells the model the sleep tool result is not a wake', () => {
61
+ const message = getOpencodeSystemMessage({
62
+ sessionId: 'ses_123',
63
+ })
64
+ expect(message).toContain('The tool result is not a wake')
65
+ expect(message).toContain('Woke after sleeping until')
66
+ expect(message).toContain(
67
+ 'If you still need to wake later after answering, call `kimaki_sleep` again',
68
+ )
69
+ expect(message).not.toContain('After wake, continue the wait reason')
70
+ })
71
+
72
+ test('tells the model to stay quiet between tool calls', () => {
73
+ const message = getOpencodeSystemMessage({
74
+ sessionId: 'ses_123',
75
+ })
76
+ expect(message).toContain('## Discord output')
77
+ expect(message).toContain('Be concise')
78
+ expect(message).toContain('Do not narrate between tool calls')
79
+ expect(message).toContain(
80
+ 'Do not output text until you are ready to give the user the final answer for this turn',
81
+ )
82
+ })
83
+
42
84
  test('requires interactive tools after all text, using exact tool names', () => {
43
85
  const message = getOpencodeSystemMessage({
44
86
  sessionId: 'ses_123',
@@ -48,6 +90,7 @@ describe('system-message', () => {
48
90
  expect(message).toContain('NEVER call `question` before your text')
49
91
  expect(message).toContain('`kimaki_action_buttons`')
50
92
  expect(message).toContain('`kimaki_file_upload`')
93
+ expect(message).toContain('`kimaki_sleep`')
51
94
  })
52
95
 
53
96
  test('persists and reads session system prompt for command path', async () => {
@@ -100,6 +143,34 @@ describe('system-message', () => {
100
143
  ).rejects.toMatchObject({ code: 'EISDIR' })
101
144
  })
102
145
 
146
+ test('includes all-projects session search example', () => {
147
+ const message = getOpencodeSystemMessage({
148
+ sessionId: 'ses_123',
149
+ channelId: 'chan_123',
150
+ })
151
+ expect(message).toContain('kimaki session search "auth timeout" --all')
152
+ expect(message).toContain('kimaki session search "auth timeout" --days 0')
153
+ expect(message).toContain(
154
+ 'Defaults to this project and the last 14 days. Use `--days 0` for all time. Use `--all` to search every locally registered project',
155
+ )
156
+ })
157
+
158
+ test('includes session title update guidance when scope or goal changed', () => {
159
+ const message = getOpencodeSystemMessage({
160
+ sessionId: 'ses_123',
161
+ threadId: 'thread_123',
162
+ })
163
+ expect(message).toContain('## updating the session title')
164
+ expect(message).toContain(
165
+ "kimaki session title 'Short title' --session ses_123",
166
+ )
167
+ expect(message).toContain(
168
+ 'When the session scope or goal changed from the first message, update the title:',
169
+ )
170
+ expect(message).toContain('thread-name="..."')
171
+ expect(message).toContain('Do not retitle every turn')
172
+ })
173
+
103
174
  test('includes parent session context when parentSessionId is set', () => {
104
175
  const message = getOpencodeSystemMessage({
105
176
  sessionId: 'ses_child',
@@ -116,6 +187,78 @@ describe('system-message', () => {
116
187
  expect(message).toContain('--parent-session ses_child')
117
188
  })
118
189
 
190
+ test('scheduled cron task section shows task id, cron and no-sleep rule', () => {
191
+ const message = getOpencodeSystemMessage({
192
+ sessionId: 'ses_task',
193
+ channelId: 'chan_123',
194
+ threadId: 'thread_123',
195
+ scheduledTask: {
196
+ taskId: 23,
197
+ scheduleKind: 'cron',
198
+ cronExpr: '0 6,14 * * *',
199
+ timezone: 'Europe/Rome',
200
+ },
201
+ })
202
+ expect(message).toContain('## scheduled task session')
203
+ expect(message).toContain('kimaki scheduled task #23')
204
+ expect(message).toContain('Schedule: cron `0 6,14 * * *` in Europe/Rome.')
205
+ expect(message).toContain('Do NOT use `kimaki_sleep` to wait for the next run')
206
+ expect(message).toContain('starts a fresh session automatically')
207
+ const section = message.slice(
208
+ message.indexOf('## scheduled task session'),
209
+ message.indexOf('## archiving the current thread'),
210
+ )
211
+ expect(section).not.toContain('archive')
212
+ })
213
+
214
+ test('cron task without timezone says UTC', () => {
215
+ const message = getOpencodeSystemMessage({
216
+ sessionId: 'ses_task_utc',
217
+ scheduledTask: { taskId: 7, scheduleKind: 'cron', cronExpr: '0 9 * * 1' },
218
+ })
219
+ expect(message).toContain('Schedule: cron `0 9 * * 1` in UTC.')
220
+ })
221
+
222
+ test('one-shot at task says it does not repeat and skips sleep', () => {
223
+ const message = getOpencodeSystemMessage({
224
+ sessionId: 'ses_oneshot',
225
+ scheduledTask: { scheduleKind: 'at' },
226
+ })
227
+ expect(message).toContain('## scheduled task session')
228
+ expect(message).toContain('a one-time kimaki scheduled task')
229
+ expect(message).toContain('This task runs once and does not repeat.')
230
+ expect(message).toContain('Do NOT use `kimaki_sleep` to wait for the next run')
231
+ expect(message).not.toContain('Schedule: cron')
232
+ })
233
+
234
+ test('no scheduled task section by default (normal sessions)', () => {
235
+ const message = getOpencodeSystemMessage({
236
+ sessionId: 'ses_normal',
237
+ channelId: 'chan_123',
238
+ threadId: 'thread_123',
239
+ })
240
+ expect(message).not.toContain('## scheduled task session')
241
+ })
242
+
243
+ test('lets agents omit --user for quiet scheduled work', () => {
244
+ const message = getOpencodeSystemMessage({
245
+ sessionId: 'ses_123',
246
+ channelId: 'chan_123',
247
+ threadId: 'thread_123',
248
+ })
249
+ expect(message).not.toContain(
250
+ 'ALWAYS pass `--user` when scheduling a task.',
251
+ )
252
+ expect(message).not.toContain('(always pass this)')
253
+ expect(message).toContain("kimaki task edit <id> --user ''")
254
+ expect(message).toContain(
255
+ "kimaki send --channel chan_123 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent <current_agent> --parent-session ses_123",
256
+ )
257
+ expect(message).not.toContain(
258
+ "kimaki send --channel chan_123 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent <current_agent> --parent-session ses_123 --user '<discord-user-id>'",
259
+ )
260
+ })
261
+
119
262
  test('omits parent session system block by default (btw/task/fork cache)', () => {
120
263
  // /btw, task subagents, and normal sessions must not get a parent block in
121
264
  // the system message. That block is opt-in via --parent-session only.
@@ -151,11 +294,20 @@ describe('system-message', () => {
151
294
  expect(message).toContain(
152
295
  'When pulling submodules and they jump to a new commit, commit that submodule pointer update right away before doing other work.',
153
296
  )
297
+ expect(message).toContain(
298
+ 'while kimaki session list --active --exclude ses_123; do sleep 5; done',
299
+ )
154
300
 
155
301
  expect(message).toMatchInlineSnapshot(`
156
302
  "
157
303
  The user is reading your messages from inside Discord, via kimaki.dev
158
304
 
305
+ ## Discord output
306
+
307
+ Be concise. Do not narrate between tool calls. Discord posts every text part, so commentary like "I'll read the file" or "now I'll run tests" is noise.
308
+ Do not output text until you are ready to give the user the final answer for this turn. Tool calls can run with no preceding text.
309
+ Exceptions: when a tool requires user-visible text first (\`question\`, \`kimaki_action_buttons\`, \`kimaki_file_upload\`, \`kimaki_sleep\`), write that required text, then call the tool.
310
+
159
311
  ## bash tool
160
312
 
161
313
  When calling the bash tool, always include these extra fields alongside \`command\`:
@@ -172,7 +324,7 @@ describe('system-message', () => {
172
324
  }
173
325
  \`\`\`
174
326
 
175
- \`description\` is shown to the user in Discord as a summary of the bash call.
327
+ \`description\` is shown in Discord when the bash command is longer than 50 characters.
176
328
  \`hasSideEffect\` distinguishes essential bash calls from read-only ones in low-verbosity mode.
177
329
 
178
330
  Your current OpenCode session ID is: ses_123
@@ -180,7 +332,7 @@ describe('system-message', () => {
180
332
  Your current Discord thread ID is: thread_123
181
333
  Your current Discord guild ID is: guild_123
182
334
 
183
- Per-turn Discord metadata like the current user and current agent is delivered in synthetic user message parts.
335
+ Per-turn Discord metadata like the current user, current agent, and Discord thread title is delivered in synthetic user message parts.
184
336
 
185
337
  ## permissions
186
338
 
@@ -203,6 +355,7 @@ describe('system-message', () => {
203
355
 
204
356
  ## debugging kimaki issues
205
357
 
358
+ ALWAYS read https://kimaki.dev/docs/guides/report-bugs first before submitting any issue to Kimaki. That page is the source of truth for exporting session jsonl, sharing evidence in a gist, and filing bugs. Never open a pull request on remorses/kimaki unless remorses asked for one in a comment on the issue.
206
359
  If there are internal kimaki issues (sessions not responding, bot errors, unexpected behavior), read the log file at \`<data-dir>/kimaki.log\`. This file contains detailed logs of all bot activity including session creation, event handling, errors, and API calls. The log file is reset every time the bot restarts, so it only contains logs from the current run.
207
360
 
208
361
  ## uploading files to discord
@@ -211,6 +364,8 @@ describe('system-message', () => {
211
364
 
212
365
  kimaki upload-to-discord --session ses_123 <file1> [file2] ...
213
366
 
367
+ NEVER show images with markdown like \`![alt](/tmp/file.png)\` or \`![alt](file://...)\`. Discord does not render local markdown images. ALWAYS upload them with \`kimaki upload-to-discord\` so they appear as real Discord attachments. Do this for every screenshot, generated image, and visual step the user should see.
368
+
214
369
  ## generating audio from text
215
370
 
216
371
  When the user asks you to generate audio of some text so they can listen instead of reading, use \`kimaki tts\` to create a speech file and \`kimaki upload-to-discord\` to send it to the thread. Only use this when the user explicitly asks for audio.
@@ -232,9 +387,18 @@ describe('system-message', () => {
232
387
  To ask the user to upload files from their device, use \`kimaki_file_upload\`. This shows a native file picker dialog in Discord. The files are downloaded to the project's \`uploads/\` directory and the tool returns the local file paths.
233
388
  You MUST call \`kimaki_file_upload\` LAST, after ALL text.
234
389
 
390
+ ## sleeping the session
391
+
392
+ Use \`kimaki_sleep\` to pause this session for hours or days, then continue when the time is reached. The sleep is stored in SQLite and survives bot restarts.
393
+ Pass either \`duration\` (\`30s\`, \`2h\`, \`1d\`) or \`until\` (UTC ISO ending with \`Z\`, example \`2026-08-20T09:00:00Z\`).
394
+ You MUST call \`kimaki_sleep\` LAST, after ALL text. Do not call more tools after it.
395
+ A new user message cancels the sleep. If you still need to wake later after answering, call \`kimaki_sleep\` again with \`until\` set to the original UTC time.
396
+ The tool result is not a wake. After it succeeds, write one short line that you are waiting, then stop. Do not continue the wait reason and do not pretend time has passed.
397
+ Wake is a later Discord message that starts with \`Woke after sleeping until\`. Only then continue the wait reason.
398
+
235
399
  ## archiving the current thread
236
400
 
237
- To archive the current Discord thread (hide it from sidebar) and stop the session, run:
401
+ To archive the current Discord thread (hide it from sidebar) without stopping the session, run:
238
402
 
239
403
  kimaki session archive thread_123 (or --session ses_123)
240
404
 
@@ -249,6 +413,17 @@ describe('system-message', () => {
249
413
  This stops the AI from processing but keeps the thread visible in Discord.
250
414
  Different from \`kimaki session archive\` which hides the thread.
251
415
 
416
+ ## updating the session title
417
+
418
+ When the session scope or goal changed from the first message, update the title:
419
+
420
+ kimaki session title 'Short title' --session ses_123
421
+
422
+ The current Discord thread title is in the per-turn \`<discord-user thread-name="..." />\` metadata.
423
+ This updates the OpenCode title. Discord follows automatically.
424
+ Do not retitle every turn. Discord rate-limits thread renames.
425
+ Keep titles short. Do not add emoji. Do not copy ⬦, btw:, or Fork: prefixes.
426
+
252
427
  ## discord user mentions
253
428
 
254
429
  Prefer Discord user IDs for mentions. Discord bots cannot ping by @name; use \`<@userId>\` in message text or pass the ID to \`--user\`.
@@ -349,13 +524,13 @@ describe('system-message', () => {
349
524
  Use \`--send-at\` to schedule a one-time or recurring task:
350
525
 
351
526
  kimaki send --channel chan_123 --prompt 'Reminder: review open PRs' --send-at '2026-03-01T09:00:00Z' --agent <current_agent> --parent-session ses_123 --user '<discord-user-id>'
352
- kimaki send --channel chan_123 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent <current_agent> --parent-session ses_123 --user '<discord-user-id>'
527
+ kimaki send --channel chan_123 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent <current_agent> --parent-session ses_123
353
528
 
354
529
  Use \`--pre-run '<command>'\` to check whether a scheduled task should start. Kimaki runs the command in the project directory. Exit code 0 starts the session and appends stdout to the prompt. Any other exit code skips that occurrence. Command output is written to the Kimaki log.
355
530
 
356
531
  Scheduled tasks do not overlap by default. Add \`--allow-concurrency\` only when concurrent sessions from the same task are safe.
357
532
 
358
- **ALWAYS pass \`--user\` when scheduling a task.** Discord only shows a thread in the left sidebar to its members. Without \`--user\`, kimaki does not ensure anyone is a member, so the task can fire completely unnoticed if the user never joined the thread or already left it. This applies to \`--channel\` and \`--thread\` scheduling alike.
533
+ Pass \`--user\` when the user should see or act on the thread. Omit \`--user\` for routine autonomous work (digests, monitors, housekeeping) so the thread does not appear in the user's Discord sidebar. Discord only shows a thread in the left sidebar to its members. Without \`--user\`, kimaki does not ensure anyone is a member. This applies to \`--channel\` and \`--thread\` scheduling alike.
359
534
 
360
535
  ALL scheduling is in UTC. Dates must be UTC ISO format ending with \`Z\`. Cron expressions also fire in UTC (e.g. \`0 9 * * 1\` means 9:00 UTC every Monday).
361
536
  When the user specifies a time without a timezone, ask them to confirm their timezone or the UTC equivalent. Never guess the user's timezone.
@@ -367,14 +542,14 @@ describe('system-message', () => {
367
542
  - \`--pre-run\` to start only when a project command exits with code 0
368
543
  - \`--allow-concurrency\` to permit overlapping runs from the same task
369
544
  - \`--parent-session\` to pass this session as parent of the scheduled child
370
- - \`--user\` to add a specific user to the scheduled thread (always pass this)
545
+ - \`--user\` to add a specific user to the scheduled thread. Omit this for quiet autonomous work
371
546
 
372
547
  \`--wait\` is incompatible with \`--send-at\` because scheduled tasks run in the future.
373
548
 
374
549
  Keep scheduled task prompts **short**. The prompt text becomes the first message in the Discord thread, so long prompts clutter the channel. Instead of inlining the full task description in \`--prompt\`, write a markdown file in the project's \`tasks/\` folder and reference it:
375
550
 
376
551
  \`\`\`bash
377
- kimaki send --channel chan_123 --prompt 'Read tasks/weekly-test-suite.md and follow instructions' --send-at '0 9 * * 1' --agent <current_agent> --parent-session ses_123 --user '<discord-user-id>'
552
+ kimaki send --channel chan_123 --prompt 'Read tasks/weekly-test-suite.md and follow instructions' --send-at '0 9 * * 1' --agent <current_agent> --parent-session ses_123
378
553
  \`\`\`
379
554
 
380
555
  The task file should contain all the detail: goal, constraints, expected output, completion criteria. Use this frontmatter format:
@@ -393,7 +568,7 @@ describe('system-message', () => {
393
568
  Notification strategy:
394
569
  - NEVER use \`@username\` (e.g. \`@Tommy\`) directly in task prompts. The prompt text becomes the first message in the thread, so a raw \`@\` mention triggers an actual Discord ping every time the task fires. Instead, wrap it in inline code like \`\\\`@Tommy\\\`\`, or use Discord user ID mentions like \`<@USER_ID>\` only in the body of the prompt where the agent will process it, not in the opening line.
395
570
  - If a task needs user attention, add "mention the user via Discord user ID when task requires user review" in the task md file.
396
- - With \`--user\`, the user is added to the thread and receives thread-level notifications.
571
+ - With \`--user\`, the user is added to the thread and receives thread-level notifications. Omit \`--user\` when the work should stay out of the sidebar.
397
572
  - If a scheduled task completes with no actionable result, archive the session: \`kimaki session archive thread_123 (or --session ses_123)\`
398
573
 
399
574
  Manage scheduled tasks with:
@@ -402,7 +577,7 @@ describe('system-message', () => {
402
577
  kimaki task edit <id> --prompt "new prompt" [--send-at "new schedule"] [--pre-run "command"] [--allow-concurrency true|false] [--user "<discord-user-id>"] [--model "provider/model"] [--agent "<agent>"]
403
578
  kimaki task delete <id>
404
579
 
405
- \`kimaki task list\` prints \`userId\`, \`agent\`, and \`model\` columns. A \`-\` in \`userId\` means nobody is added to the thread when that task fires, so the user may never see it. Fix it with \`kimaki task edit <id> --user '<discord-user-id>'\` instead of deleting and recreating the task. Change model or agent in place with \`--model\` / \`--agent\` (empty string clears the override). Do not read SQLite or recreate the task just to swap model.
580
+ \`kimaki task list\` prints \`userId\`, \`agent\`, and \`model\` columns. A \`-\` in \`userId\` means nobody is added to the thread when that task fires, so the user may never see it. Add a user with \`kimaki task edit <id> --user '<discord-user-id>'\`. Clear a stored user with \`kimaki task edit <id> --user ''\`. Change model or agent in place with \`--model\` / \`--agent\` (empty string clears the override). Do not read SQLite or recreate the task just to swap model.
406
581
 
407
582
  \`kimaki session list\` also shows if a session was started by a scheduled \`delay\` or \`cron\` task, including task ID when available.
408
583
 
@@ -480,15 +655,22 @@ describe('system-message', () => {
480
655
  kimaki session list
481
656
  kimaki session list --json # machine-readable output
482
657
  kimaki session list --project /path/to/project # specific project
658
+
659
+ # List only in-progress sessions. Exit status is 1 when none remain.
660
+ kimaki session list --active
483
661
  \`\`\`
484
662
 
485
- To search past sessions for this project (supports plain text or /regex/flags):
663
+ Titles prefixed with \`btw:\` are side sessions that answer a related user question in parallel. They are not duplicate sessions of the main task.
664
+
665
+ To search past sessions (supports plain text or /regex/flags). Defaults to this project and the last 14 days. Use \`--days 0\` for all time. Use \`--all\` to search every locally registered project:
486
666
 
487
667
  \`\`\`bash
488
668
  kimaki session search "auth timeout"
669
+ kimaki session search "auth timeout" --days 0
489
670
  kimaki session search "/error\\s+42/i"
490
671
  kimaki session search "rate limit" --project /path/to/project
491
672
  kimaki session search "/panic|crash/i" --channel <channel_id>
673
+ kimaki session search "auth timeout" --all
492
674
  \`\`\`
493
675
 
494
676
  To read a session's full conversation as markdown, pipe to a file and grep it to avoid wasting context.
@@ -500,6 +682,29 @@ describe('system-message', () => {
500
682
 
501
683
  Then use grep/read tools on the file to find what you need.
502
684
 
685
+ ### who edited a file
686
+
687
+ To find the session that last edited a file, run:
688
+
689
+ \`\`\`bash
690
+ kimaki session editors src/foo.ts
691
+ kimaki session editors src/foo.ts --json
692
+ \`\`\`
693
+
694
+ Output is newest first. Each row has:
695
+
696
+ - **session ID** (\`ses_xxx\`)
697
+ - **title** (Discord thread name, so you can tell what that session was doing)
698
+ - **time ago** (when that session last edited the file)
699
+
700
+ Use this before a commit when this session did not edit the file. Put the original session ID as the last line of the commit message:
701
+
702
+ \`\`\`
703
+ Session: ses_xxx
704
+ \`\`\`
705
+
706
+ If this session edited the file, use this session ID instead. If several files come from different sessions, split the commit by session. Do not attribute another session's edits to this one.
707
+
503
708
  ## cross-project commands
504
709
 
505
710
  When the user references another project by name, run \`kimaki project list\` to find its directory path and channel ID. Then read files, search code, or run commands directly in that directory. If the project is not listed, use \`kimaki project add /path/to/repo\` to register it and create a Discord channel for it. Do not add subfolders of an existing project — only add root project directories.
@@ -566,7 +771,7 @@ describe('system-message', () => {
566
771
 
567
772
  When the user asks you to wait for an existing session, run \`kimaki session wait <session_id>\` yourself via Bash, then continue from the printed session markdown. Do not tell the user to run the command.
568
773
 
569
- IMPORTANT: if you run \`kimaki send --wait\` or \`kimaki session wait <session_id>\` via the Bash tool, you must set the Bash tool \`timeout\` to **20 minutes or more** (example: \`timeout: 1_500_000\`). Otherwise the tool will terminate early (default is 2 minutes) and you won't see long sessions.
774
+ IMPORTANT: if you run \`kimaki send --wait\`, \`kimaki session wait <session_id>\`, or the active-session wait loop via the Bash tool, you must set the Bash tool \`timeout\` to **20 minutes or more** (example: \`timeout: 1_500_000\`). Otherwise the tool will terminate early (default is 2 minutes) and you won't see long sessions.
570
775
 
571
776
  If your Bash tool timeout triggers anyway, fall back to reading the session output from disk:
572
777
 
@@ -581,9 +786,14 @@ describe('system-message', () => {
581
786
 
582
787
  # Wait for a session that was already started elsewhere
583
788
  kimaki session wait <session_id>
789
+
790
+ # Wait until every other in-progress session in this project finishes
791
+ while kimaki session list --active --exclude ses_123; do sleep 5; done
584
792
  \`\`\`
585
793
 
586
- The command exits with the session markdown on stdout once the model finishes responding.
794
+ \`session list --active\` exits with status 0 while it finds active sessions and status 1 when none remain. Exclude the current session in a wait loop so the loop does not wait for itself.
795
+
796
+ \`session wait\` exits with the session markdown on stdout once the model finishes responding.
587
797
 
588
798
  Use \`--wait\` when you need to:
589
799
  - **Fix a bug in another project** before continuing here (e.g. fix a dependency, then resume)
@@ -670,7 +880,7 @@ describe('system-message', () => {
670
880
 
671
881
  ## running dev servers with tunnel access
672
882
 
673
- ALWAYS use \`kimaki tunnel\` when starting any dev server. NEVER run \`pnpm dev\`, \`npm run dev\`, or any dev server command without wrapping it in \`kimaki tunnel\`. Always invoke Kimaki directly as \`kimaki\`, never via \`npx\` or \`bunx\`. The user is on Discord, not at the terminal — localhost URLs are useless to them. They need a tunnel URL to access the site.
883
+ When starting a local dev server that the Discord user should open in a browser, prefer wrapping it in \`kimaki tunnel\` so they get a public URL. Localhost URLs are useless from Discord. Invoke Kimaki directly as \`kimaki\`, not via \`npx\` or \`bunx\`.
674
884
 
675
885
  Use \`bunx tuistory\` to run the tunnel + dev server combo in the background so it persists across commands. This is preferable to raw shell backgrounding because you can wait for real output, read logs, and interact with the running process.
676
886
 
@@ -848,7 +1058,7 @@ describe('system-message', () => {
848
1058
  You MUST call \`question\` LAST, after ALL text parts.
849
1059
  NEVER call \`question\` before your text. Discord will hide the message.
850
1060
 
851
- The same rule applies to \`kimaki_action_buttons\` and \`kimaki_file_upload\`.
1061
+ The same rule applies to \`kimaki_action_buttons\`, \`kimaki_file_upload\`, and \`kimaki_sleep\`.
852
1062
  You MUST call them LAST, after ALL text.
853
1063
 
854
1064
  ALWAYS use \`question\` when you ask the user a question. Do not write a numbered list in plain text.
@@ -878,6 +1088,7 @@ describe('system-message', () => {
878
1088
  userId: 'user_123',
879
1089
  sourceMessageId: 'msg_123',
880
1090
  sourceThreadId: 'thread_123',
1091
+ threadName: 'Fix auth bug',
881
1092
  repliedMessage: {
882
1093
  authorUsername: 'alice',
883
1094
  text: 'Original replied message',
@@ -891,7 +1102,7 @@ describe('system-message', () => {
891
1102
  },
892
1103
  }),
893
1104
  ).toMatchInlineSnapshot(`
894
- "<discord-user name="Tommy" user-id="user_123" message-id="msg_123" thread-id="thread_123" />
1105
+ "<discord-user name="Tommy" user-id="user_123" message-id="msg_123" thread-id="thread_123" thread-name="Fix auth bug" />
895
1106
 
896
1107
  This message was a reply to message
897
1108