kimaki 0.28.0 → 0.30.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 (179) hide show
  1. package/dist/agent-model.e2e.test.js +33 -33
  2. package/dist/anthropic-account-identity.js +1 -1
  3. package/dist/anthropic-auth-plugin.js +5 -5
  4. package/dist/anthropic-auth-plugin.test.js +30 -2
  5. package/dist/anthropic-auth-state.js +1 -1
  6. package/dist/bash-tool-schema-plugin.js +18 -0
  7. package/dist/bash-tool-schema-plugin.test.js +20 -1
  8. package/dist/bin.js +7 -3
  9. package/dist/bot-lifecycle.test.js +206 -0
  10. package/dist/cache-drift-plugin.js +25 -18
  11. package/dist/cache-rewrite.js +39 -0
  12. package/dist/cache-rewrite.test.js +41 -0
  13. package/dist/channel-management.js +113 -51
  14. package/dist/cli-commands/misc.js +4 -1
  15. package/dist/cli-commands/project.js +7 -4
  16. package/dist/cli-commands/session.js +118 -36
  17. package/dist/cli-commands/thread.js +63 -0
  18. package/dist/cli-parsing.test.js +3 -3
  19. package/dist/cli-runner.js +11 -1
  20. package/dist/cli-send-thread.e2e.test.js +2 -2
  21. package/dist/cli.js +6 -4
  22. package/dist/commands/ask-question.js +72 -23
  23. package/dist/commands/queue.js +84 -36
  24. package/dist/commands/queue.test.js +15 -0
  25. package/dist/commands/restart-opencode-server.js +7 -1
  26. package/dist/commands/session-id.js +5 -5
  27. package/dist/database.js +149 -6
  28. package/dist/db.js +34 -0
  29. package/dist/db.test.js +199 -1
  30. package/dist/default-channel-provisioning.test.js +2 -1
  31. package/dist/discord-bot.js +220 -146
  32. package/dist/discord-command-registration.js +1 -1
  33. package/dist/external-directory-permissions.e2e.test.js +3 -3
  34. package/dist/external-directory-project-deny.e2e.test.js +1 -1
  35. package/dist/forum-sync/watchers.js +15 -0
  36. package/dist/gateway-proxy.e2e.test.js +9 -9
  37. package/dist/guild-categories.test.js +225 -0
  38. package/dist/hrana-server.js +68 -14
  39. package/dist/interaction-handler.js +5 -1
  40. package/dist/kimaki-opencode-plugin.js +2 -0
  41. package/dist/kimaki-opencode-plugin.test.js +28 -0
  42. package/dist/list-channel-threads.js +163 -0
  43. package/dist/list-channel-threads.test.js +80 -0
  44. package/dist/markdown.js +164 -102
  45. package/dist/markdown.test.js +88 -20
  46. package/dist/message-formatting.js +29 -18
  47. package/dist/message-formatting.test.js +111 -3
  48. package/dist/opencode.js +27 -12
  49. package/dist/question-abort-clears-dropdown.e2e.test.js +1 -1
  50. package/dist/question-abort-resume.e2e.test.js +100 -0
  51. package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +1 -1
  52. package/dist/question-text-before-dropdown.e2e.test.js +1 -1
  53. package/dist/queue-advanced-abort.e2e.test.js +3 -3
  54. package/dist/queue-advanced-action-buttons.e2e.test.js +5 -5
  55. package/dist/queue-advanced-e2e-setup.js +63 -0
  56. package/dist/queue-advanced-footer.e2e.test.js +18 -18
  57. package/dist/queue-advanced-model-switch.e2e.test.js +3 -4
  58. package/dist/queue-advanced-permissions-typing.e2e.test.js +2 -2
  59. package/dist/queue-advanced-typing-interrupt.e2e.test.js +3 -3
  60. package/dist/queue-delete-message.e2e.test.js +4 -4
  61. package/dist/queue-drain-after-interactive-ui.e2e.test.js +3 -3
  62. package/dist/queue-interrupt-drain.e2e.test.js +10 -10
  63. package/dist/queue-question-select-drain.e2e.test.js +5 -5
  64. package/dist/runtime-lifecycle.e2e.test.js +95 -11
  65. package/dist/schema.js +21 -0
  66. package/dist/session-handler/event-stream-state.js +240 -6
  67. package/dist/session-handler/event-stream-state.test.js +1116 -130
  68. package/dist/session-handler/thread-runtime-state.js +4 -0
  69. package/dist/session-handler/thread-session-runtime.js +499 -89
  70. package/dist/sleep.e2e.test.js +7 -7
  71. package/dist/store.js +1 -1
  72. package/dist/system-message.js +64 -23
  73. package/dist/system-message.test.js +69 -23
  74. package/dist/task-id-plugin.js +16 -0
  75. package/dist/task-runner.js +7 -0
  76. package/dist/task-schedule.test.js +1 -1
  77. package/dist/test-utils.js +5 -3
  78. package/dist/thread-message-queue.e2e.test.js +30 -31
  79. package/dist/undo-redo.e2e.test.js +3 -3
  80. package/dist/voice-message.e2e.test.js +64 -26
  81. package/dist/voice-routing-input.e2e.test.js +4 -4
  82. package/dist/voice-routing-question.e2e.test.js +4 -4
  83. package/dist/voice.js +107 -21
  84. package/dist/voice.test.js +52 -1
  85. package/dist/wait-session.js +24 -2
  86. package/dist/worktree-lifecycle.e2e.test.js +8 -8
  87. package/package.json +7 -7
  88. package/skills/egaki/SKILL.md +14 -0
  89. package/skills/holocron/SKILL.md +125 -42
  90. package/skills/opensrc/SKILL.md +20 -3
  91. package/skills/spiceflow/SKILL.md +53 -172
  92. package/skills/strada/SKILL.md +61 -232
  93. package/src/agent-model.e2e.test.ts +33 -33
  94. package/src/anthropic-account-identity.ts +1 -1
  95. package/src/anthropic-auth-plugin.test.ts +37 -2
  96. package/src/anthropic-auth-plugin.ts +9 -7
  97. package/src/anthropic-auth-state.ts +1 -0
  98. package/src/bash-tool-schema-plugin.test.ts +29 -0
  99. package/src/bash-tool-schema-plugin.ts +31 -0
  100. package/src/bin.ts +7 -3
  101. package/src/bot-lifecycle.test.ts +252 -0
  102. package/src/cache-drift-plugin.ts +34 -24
  103. package/src/cache-rewrite.test.ts +49 -0
  104. package/src/cache-rewrite.ts +96 -0
  105. package/src/channel-management.ts +144 -65
  106. package/src/cli-commands/misc.ts +4 -1
  107. package/src/cli-commands/project.ts +9 -4
  108. package/src/cli-commands/session.ts +167 -38
  109. package/src/cli-commands/thread.ts +86 -0
  110. package/src/cli-parsing.test.ts +3 -3
  111. package/src/cli-runner.ts +11 -0
  112. package/src/cli-send-thread.e2e.test.ts +2 -2
  113. package/src/cli.ts +8 -6
  114. package/src/commands/ask-question.ts +88 -25
  115. package/src/commands/queue.test.ts +19 -0
  116. package/src/commands/queue.ts +111 -45
  117. package/src/commands/restart-opencode-server.ts +12 -1
  118. package/src/commands/session-id.ts +5 -5
  119. package/src/database.ts +219 -6
  120. package/src/db.test.ts +236 -0
  121. package/src/db.ts +38 -0
  122. package/src/default-channel-provisioning.test.ts +2 -1
  123. package/src/discord-bot.ts +272 -185
  124. package/src/discord-command-registration.ts +3 -1
  125. package/src/external-directory-permissions.e2e.test.ts +3 -3
  126. package/src/external-directory-project-deny.e2e.test.ts +1 -1
  127. package/src/forum-sync/watchers.ts +20 -0
  128. package/src/gateway-proxy.e2e.test.ts +9 -9
  129. package/src/guild-categories.test.ts +260 -0
  130. package/src/hrana-server.ts +96 -16
  131. package/src/interaction-handler.ts +7 -0
  132. package/src/kimaki-opencode-plugin.test.ts +34 -0
  133. package/src/kimaki-opencode-plugin.ts +2 -0
  134. package/src/list-channel-threads.test.ts +90 -0
  135. package/src/list-channel-threads.ts +252 -0
  136. package/src/markdown.test.ts +108 -22
  137. package/src/markdown.ts +216 -103
  138. package/src/message-formatting.test.ts +129 -2
  139. package/src/message-formatting.ts +28 -14
  140. package/src/opencode.ts +28 -13
  141. package/src/question-abort-clears-dropdown.e2e.test.ts +1 -1
  142. package/src/question-abort-resume.e2e.test.ts +121 -0
  143. package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +1 -1
  144. package/src/question-text-before-dropdown.e2e.test.ts +1 -1
  145. package/src/queue-advanced-abort.e2e.test.ts +3 -3
  146. package/src/queue-advanced-action-buttons.e2e.test.ts +5 -5
  147. package/src/queue-advanced-e2e-setup.ts +65 -0
  148. package/src/queue-advanced-footer.e2e.test.ts +18 -18
  149. package/src/queue-advanced-model-switch.e2e.test.ts +3 -4
  150. package/src/queue-advanced-permissions-typing.e2e.test.ts +2 -2
  151. package/src/queue-advanced-typing-interrupt.e2e.test.ts +3 -3
  152. package/src/queue-delete-message.e2e.test.ts +4 -4
  153. package/src/queue-drain-after-interactive-ui.e2e.test.ts +3 -3
  154. package/src/queue-interrupt-drain.e2e.test.ts +10 -10
  155. package/src/queue-question-select-drain.e2e.test.ts +5 -5
  156. package/src/runtime-lifecycle.e2e.test.ts +104 -10
  157. package/src/schema.sql +16 -0
  158. package/src/schema.ts +23 -0
  159. package/src/session-handler/event-stream-state.test.ts +1184 -133
  160. package/src/session-handler/event-stream-state.ts +322 -8
  161. package/src/session-handler/thread-runtime-state.ts +5 -0
  162. package/src/session-handler/thread-session-runtime.ts +597 -95
  163. package/src/sleep.e2e.test.ts +7 -7
  164. package/src/store.ts +2 -2
  165. package/src/system-message.test.ts +70 -23
  166. package/src/system-message.ts +67 -22
  167. package/src/task-id-plugin.ts +17 -0
  168. package/src/task-runner.ts +8 -0
  169. package/src/task-schedule.test.ts +1 -1
  170. package/src/test-utils.ts +5 -3
  171. package/src/thread-message-queue.e2e.test.ts +30 -31
  172. package/src/undo-redo.e2e.test.ts +3 -3
  173. package/src/voice-message.e2e.test.ts +70 -26
  174. package/src/voice-routing-input.e2e.test.ts +4 -4
  175. package/src/voice-routing-question.e2e.test.ts +4 -4
  176. package/src/voice.test.ts +56 -0
  177. package/src/voice.ts +135 -25
  178. package/src/wait-session.ts +25 -2
  179. package/src/worktree-lifecycle.e2e.test.ts +8 -8
@@ -344,19 +344,19 @@ describe('kimaki_sleep', () => {
344
344
  "--- from: user (sleep-tester)
345
345
  SLEEP_TOOL_MARKER wait for the deploy
346
346
  --- from: assistant (TestBot)
347
- > *using deterministic-provider/deterministic-v2*
347
+ -# *using deterministic-provider/deterministic-v2*
348
348
  > waiting for the deploy
349
349
 
350
350
  ┣ kimaki_sleep until 2030-01-01T09:00:00Z _waiting for the deploy_
351
351
 
352
352
  > sleep-started
353
- > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
354
- ⬦ Woke after sleeping until 2030-01-01 09:00 UTC
353
+ -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
354
+ -# Woke after sleeping until 2030-01-01 09:00 UTC
355
355
  Reason: waiting for the deploy
356
356
  Continue the work you were waiting for.
357
357
  [embed]
358
358
  > sleep-wake-done
359
- > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
359
+ -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
360
360
  `)
361
361
 
362
362
  // The wake must continue the SAME OpenCode session, not start a new one:
@@ -465,16 +465,16 @@ describe('kimaki_sleep', () => {
465
465
  "--- from: user (sleep-tester)
466
466
  SLEEP_CANCEL_MARKER wait for something
467
467
  --- from: assistant (TestBot)
468
- > *using deterministic-provider/deterministic-v2*
468
+ -# *using deterministic-provider/deterministic-v2*
469
469
  ┣ kimaki_sleep for 2h _cancel me_
470
470
 
471
471
  > cancel-sleep-started
472
- > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
472
+ -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
473
473
  --- from: user (sleep-tester)
474
474
  SLEEP_CANCEL_FOLLOWUP never mind, keep going
475
475
  --- from: assistant (TestBot)
476
476
  > cancel-followup-done
477
- > *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
477
+ -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
478
478
  `)
479
479
  },
480
480
  20_000,
package/src/store.ts CHANGED
@@ -69,7 +69,7 @@ export type KimakiState = {
69
69
  critiqueEnabled: boolean
70
70
 
71
71
  // Whether final session footers mention the thread creator.
72
- // Changes: set once at startup from --skip-footer-mentions.
72
+ // Changes: set once at startup from --enable-footer-mentions (default off).
73
73
  // Read by: ThreadSessionRuntime.emitFooter().
74
74
  footerMentionsEnabled: boolean
75
75
 
@@ -204,7 +204,7 @@ export const store = createStore<KimakiState>(() => ({
204
204
  defaultVerbosity: 'text_and_essential_tools',
205
205
  defaultMentionMode: false,
206
206
  critiqueEnabled: true,
207
- footerMentionsEnabled: true,
207
+ footerMentionsEnabled: false,
208
208
  enabledSkills: [],
209
209
  disabledSkills: [],
210
210
  allowedMentions: ['users'],
@@ -143,6 +143,17 @@ describe('system-message', () => {
143
143
  ).rejects.toMatchObject({ code: 'EISDIR' })
144
144
  })
145
145
 
146
+ test('tells agents to read compressed session transcripts under 100 KB', () => {
147
+ const message = getOpencodeSystemMessage({
148
+ sessionId: 'ses_123',
149
+ channelId: 'chan_123',
150
+ threadId: 'thread_123',
151
+ })
152
+ expect(message).toContain('kimaki session read <sessionId> > ./tmp/session.md 2>/dev/null')
153
+ expect(message).toContain('If it is under 100 KB, read the whole file. Do not grep first.')
154
+ expect(message).not.toContain('rg ')
155
+ })
156
+
146
157
  test('includes all-projects session search example', () => {
147
158
  const message = getOpencodeSystemMessage({
148
159
  sessionId: 'ses_123',
@@ -165,7 +176,7 @@ describe('system-message', () => {
165
176
  "kimaki session title 'Short title' --session ses_123",
166
177
  )
167
178
  expect(message).toContain(
168
- 'When the session scope or goal changed from the first message, update the title:',
179
+ 'Skip the first turn. OpenCode already auto-generates the title from the first message.',
169
180
  )
170
181
  expect(message).toContain('thread-name="..."')
171
182
  expect(message).toContain('Do not retitle every turn')
@@ -415,14 +426,14 @@ describe('system-message', () => {
415
426
 
416
427
  ## updating the session title
417
428
 
418
- When the session scope or goal changed from the first message, update the title:
429
+ Skip the first turn. OpenCode already auto-generates the title from the first message.
430
+ On later turns, if the scope or goal changed, run:
419
431
 
420
432
  kimaki session title 'Short title' --session ses_123
421
433
 
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.
434
+ Current Discord title is in \`<discord-user thread-name="..." />\`. Discord follows the OpenCode title.
424
435
  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.
436
+ Keep titles short. No emoji. No ⬦, btw:, or Fork: prefixes.
426
437
 
427
438
  ## discord user mentions
428
439
 
@@ -456,7 +467,17 @@ describe('system-message', () => {
456
467
 
457
468
  kimaki send --thread <thread_id> --prompt 'follow-up prompt' --agent <current_agent>
458
469
 
459
- Use this when you already have the Discord thread ID. Prefer \`--thread\` over \`--session\` because thread IDs work across machines while session IDs only resolve on the machine that created the session.
470
+ Use this when you already have the Discord thread ID. Prefer \`--thread\` over \`--session\`. Discord thread IDs work on every computer. Session IDs do not.
471
+
472
+ A session ID only exists in the local SQLite database and OpenCode server on the computer that created it. \`kimaki send --session\` and \`kimaki session read\` fail on another computer.
473
+
474
+ To continue a remote thread, find the Discord channel, list its threads, then send with \`--thread\`:
475
+
476
+ \`\`\`bash
477
+ kimaki project list --all --json
478
+ kimaki thread list --channel <channel_id> --json
479
+ kimaki send --thread <thread_id> --prompt 'continue the work' --agent <current_agent>
480
+ \`\`\`
460
481
 
461
482
  To send to the thread associated with a known session (same machine only):
462
483
 
@@ -530,7 +551,7 @@ describe('system-message', () => {
530
551
 
531
552
  Scheduled tasks do not overlap by default. Add \`--allow-concurrency\` only when concurrent sessions from the same task are safe.
532
553
 
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.
554
+ For autonomous scheduled tasks, omit \`--user\` by default so each run stays out of the user's Discord sidebar. Put the user's Discord ID in the task instructions instead, and tell the agent to mention that ID only when it completed work worth reviewing, found an issue, or needs a decision. Do not mention the user when the run found no work or made no changes. Reserve \`--user\` for reminders that must surface every time they fire.
534
555
 
535
556
  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).
536
557
  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.
@@ -542,7 +563,7 @@ describe('system-message', () => {
542
563
  - \`--pre-run\` to start only when a project command exits with code 0
543
564
  - \`--allow-concurrency\` to permit overlapping runs from the same task
544
565
  - \`--parent-session\` to pass this session as parent of the scheduled child
545
- - \`--user\` to add a specific user to the scheduled thread. Omit this for quiet autonomous work
566
+ - \`--user\` to add a specific user to every scheduled thread. Use this for reminders, not autonomous tasks
546
567
 
547
568
  \`--wait\` is incompatible with \`--send-at\` because scheduled tasks run in the future.
548
569
 
@@ -567,9 +588,9 @@ describe('system-message', () => {
567
588
 
568
589
  Notification strategy:
569
590
  - 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.
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.
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.
572
- - If a scheduled task completes with no actionable result, archive the session: \`kimaki session archive thread_123 (or --session ses_123)\`
591
+ - For autonomous tasks, include the user's Discord ID in the task md file and instruct the agent to mention it only after completing work the user should review, when reporting an issue, or when asking for a decision.
592
+ - Do not mention the user if the task found no work, made no changes, or has nothing actionable to report. Archive that session instead: \`kimaki session archive thread_123 (or --session ses_123)\`
593
+ - Do not pass \`--user\` for autonomous tasks. It adds the user to every task thread before the result is known. Use it only when every occurrence must appear in the user's sidebar, such as an explicit reminder.
573
594
 
574
595
  Manage scheduled tasks with:
575
596
 
@@ -577,7 +598,7 @@ describe('system-message', () => {
577
598
  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>"]
578
599
  kimaki task delete <id>
579
600
 
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.
601
+ \`kimaki task list\` prints \`userId\`, \`agent\`, and \`model\` columns. For autonomous tasks, \`userId\` should normally be \`-\`; put the Discord ID and conditional mention rule in the task instructions instead. Clear a stored user with \`kimaki task edit <id> --user ''\`. Add \`--user '<discord-user-id>'\` only for tasks whose every occurrence must surface. 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.
581
602
 
582
603
  \`kimaki session list\` also shows if a session was started by a scheduled \`delay\` or \`cron\` task, including task ID when available.
583
604
 
@@ -656,10 +677,15 @@ describe('system-message', () => {
656
677
  kimaki session list --json # machine-readable output
657
678
  kimaki session list --project /path/to/project # specific project
658
679
 
680
+ # List sessions across every locally registered project
681
+ kimaki session list --all
682
+
659
683
  # List only in-progress sessions. Exit status is 1 when none remain.
660
684
  kimaki session list --active
661
685
  \`\`\`
662
686
 
687
+ Each row shows the session title, project directory, \`status: working\` or \`status: idle\`, and \`tokens: N\` (total token footprint) when available. Kimaki-started sessions also show their Discord \`thread\` ID.
688
+
663
689
  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
690
 
665
691
  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:
@@ -673,14 +699,13 @@ describe('system-message', () => {
673
699
  kimaki session search "auth timeout" --all
674
700
  \`\`\`
675
701
 
676
- To read a session's full conversation as markdown, pipe to a file and grep it to avoid wasting context.
677
- Logs go to stderr, so redirect stderr to hide them:
702
+ To read a session as markdown, pipe to a file. Logs go to stderr:
678
703
 
679
704
  \`\`\`bash
680
705
  kimaki session read <sessionId> > ./tmp/session.md 2>/dev/null
681
706
  \`\`\`
682
707
 
683
- Then use grep/read tools on the file to find what you need.
708
+ The dump is already compressed (no thinking, truncated tool inputs). If it is under 100 KB, read the whole file. Do not grep first. Use \`--thinking\` / \`--verbose\` only when you need the full dump.
684
709
 
685
710
  ### who edited a file
686
711
 
@@ -709,18 +734,25 @@ describe('system-message', () => {
709
734
 
710
735
  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.
711
736
 
712
- When the user uses \`#project-name\` syntax, they usually mean a Kimaki project channel. Use \`kimaki project list --json\` to resolve the \`channel_name\` to its repo working directory. The JSON output includes \`guild_id\` and \`guild_name\` to distinguish channels with the same name across different servers. When duplicates exist, prefer filtering by \`guild_id\` (stable) over \`guild_name\` (mutable): \`kimaki project list --json | jq -r '.[] | select(.channel_name == "project-name" and .guild_id == "123456") | .channel_id'\`.
737
+ When the user uses \`#project-name\` syntax, they usually mean a Kimaki project channel. Use \`kimaki project list --json\` to resolve the \`channel_name\` to its repo working directory. The JSON output includes \`guild_id\` and \`guild_name\` so you can tell channels with the same name apart. Prefer \`guild_id\` when there are duplicates. It is stable. \`guild_name\` can change.
713
738
 
714
- When the user uses \`#Some Thread Title\` with spaces, they mean a **thread title**, not a project channel. Find the session by searching across projects, then read the session markdown:
739
+ If the local list has no match, the channel may live on another computer. Scan Discord with \`kimaki project list --all --json\` and send by \`channel_id\`. Remote rows have \`is_local: false\` and \`directory: null\`. Do not use \`--project\` or \`--session\` for those channels.
715
740
 
716
- \`\`\`bash
717
- # 1. Find the session ID by searching thread titles across all projects
718
- kimaki session list --project /path/to/project --json | jq -r '.[] | select(.title | test("Thread Title"; "i")) | .id + " | " + .title'
741
+ When the user uses \`#Some Thread Title\` with spaces, they mean a **thread title**, not a project channel. On this computer, search local sessions, then read the markdown:
719
742
 
720
- # 2. Read the full session conversation as markdown
743
+ \`\`\`bash
744
+ kimaki session list --project /path/to/project --json
721
745
  kimaki session read <sessionId> > ./tmp/session.md 2>/dev/null
722
746
  \`\`\`
723
747
 
748
+ If that local search has no match, list Discord threads instead of using a session ID:
749
+
750
+ \`\`\`bash
751
+ kimaki project list --all --json
752
+ kimaki thread list --channel <channel_id> --json
753
+ kimaki send --thread <thread_id> --prompt 'continue the work' --agent <current_agent>
754
+ \`\`\`
755
+
724
756
  If you don't know which project the thread belongs to, try each project from \`kimaki project list --json\`.
725
757
 
726
758
  \`\`\`bash
@@ -728,10 +760,18 @@ describe('system-message', () => {
728
760
  kimaki project list
729
761
  kimaki project list --json # machine-readable output with guild_id, guild_name, is_local
730
762
 
731
- # Include projects from other machines (scans Kimaki category in Discord)
763
+ # Include projects from other machines (scans Kimaki groups in Discord)
732
764
  kimaki project list --all
733
765
  kimaki project list --all --json # remote projects have is_local: false and directory: null
734
766
 
767
+ # Find a channel on another computer, then send a new prompt there
768
+ kimaki project list --all --json
769
+ kimaki send --channel <channel_id> --prompt 'Plan how to update the API client to v2' --agent <current_agent>
770
+
771
+ # Continue an existing remote thread. Session IDs do not work across computers.
772
+ kimaki thread list --channel <channel_id> --json
773
+ kimaki send --thread <thread_id> --prompt 'continue the work' --agent <current_agent>
774
+
735
775
  # Resolve by channel name (prefer adding guild_name filter if duplicates exist)
736
776
  kimaki project list --json | jq -r '.[] | select(.channel_name == "project-name") | .channel_id + " " + .guild_name + " " + .directory'
737
777
 
@@ -793,7 +833,7 @@ describe('system-message', () => {
793
833
 
794
834
  \`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
835
 
796
- \`session wait\` exits with the session markdown on stdout once the model finishes responding.
836
+ \`session wait\` exits with the session markdown on stdout once the model finishes responding, or when the session pauses to show the user a question (it will not finish on its own until answered).
797
837
 
798
838
  Use \`--wait\` when you need to:
799
839
  - **Fix a bug in another project** before continuing here (e.g. fix a dependency, then resume)
@@ -1084,6 +1124,8 @@ describe('system-message', () => {
1084
1124
  test('moves per-turn discord metadata into synthetic prompt context', () => {
1085
1125
  expect(
1086
1126
  getOpencodePromptContext({
1127
+ sessionId: 'ses_123',
1128
+ threadId: 'thread_123',
1087
1129
  username: 'Tommy',
1088
1130
  userId: 'user_123',
1089
1131
  sourceMessageId: 'msg_123',
@@ -1104,6 +1146,11 @@ describe('system-message', () => {
1104
1146
  ).toMatchInlineSnapshot(`
1105
1147
  "<discord-user name="Tommy" user-id="user_123" message-id="msg_123" thread-id="thread_123" thread-name="Fix auth bug" />
1106
1148
 
1149
+ <system-reminder>
1150
+ Your current OpenCode session ID is: ses_123
1151
+ Your current Discord thread ID is: thread_123
1152
+ </system-reminder>
1153
+
1107
1154
  This message was a reply to message
1108
1155
 
1109
1156
  <replied-message author="alice">
@@ -392,6 +392,8 @@ function escapePromptText(value: string): string {
392
392
  }
393
393
 
394
394
  export function getOpencodePromptContext({
395
+ sessionId,
396
+ threadId,
395
397
  username,
396
398
  userId,
397
399
  sourceMessageId,
@@ -402,6 +404,8 @@ export function getOpencodePromptContext({
402
404
  currentAgent,
403
405
  worktreeChanged,
404
406
  }: {
407
+ sessionId?: string
408
+ threadId?: string
405
409
  username?: string
406
410
  userId?: string
407
411
  sourceMessageId?: string
@@ -429,6 +433,17 @@ export function getOpencodePromptContext({
429
433
  ? [` thread-name="${escapePromptAttribute(threadName)}"`]
430
434
  : []),
431
435
  ].join('')
436
+ const identityLines = [
437
+ ...(sessionId
438
+ ? [`Your current OpenCode session ID is: ${sessionId}`]
439
+ : []),
440
+ ...(threadId
441
+ ? [`Your current Discord thread ID is: ${threadId}`]
442
+ : []),
443
+ ]
444
+ const identityReminder = identityLines.length > 0
445
+ ? `<system-reminder>\n${identityLines.join('\n')}\n</system-reminder>`
446
+ : undefined
432
447
  const repliedMessageXml = repliedMessage
433
448
  ? `This message was a reply to message
434
449
 
@@ -438,6 +453,7 @@ ${escapePromptText(repliedMessage.text)}
438
453
  : undefined
439
454
  const sections = [
440
455
  ...(userAttrs ? [`<discord-user${userAttrs} />`] : []),
456
+ ...(identityReminder ? [identityReminder] : []),
441
457
  ...(repliedMessageXml ? [repliedMessageXml] : []),
442
458
  ...(currentAgent
443
459
  ? [`<system-reminder>\nCurrent agent: ${currentAgent}\n</system-reminder>`]
@@ -625,14 +641,14 @@ Different from \`kimaki session archive\` which hides the thread.
625
641
 
626
642
  ## updating the session title
627
643
 
628
- When the session scope or goal changed from the first message, update the title:
644
+ Skip the first turn. OpenCode already auto-generates the title from the first message.
645
+ On later turns, if the scope or goal changed, run:
629
646
 
630
647
  kimaki session title 'Short title' --session ${sessionId}
631
648
 
632
- The current Discord thread title is in the per-turn \`<discord-user thread-name="..." />\` metadata.
633
- This updates the OpenCode title. Discord follows automatically.
649
+ Current Discord title is in \`<discord-user thread-name="..." />\`. Discord follows the OpenCode title.
634
650
  Do not retitle every turn. Discord rate-limits thread renames.
635
- Keep titles short. Do not add emoji. Do not copy ⬦, btw:, or Fork: prefixes.
651
+ Keep titles short. No emoji. No ⬦, btw:, or Fork: prefixes.
636
652
 
637
653
  ## discord user mentions
638
654
 
@@ -668,7 +684,17 @@ To send a prompt to an existing thread instead of creating a new one:
668
684
 
669
685
  kimaki send --thread <thread_id> --prompt 'follow-up prompt' --agent <current_agent>
670
686
 
671
- Use this when you already have the Discord thread ID. Prefer \`--thread\` over \`--session\` because thread IDs work across machines while session IDs only resolve on the machine that created the session.
687
+ Use this when you already have the Discord thread ID. Prefer \`--thread\` over \`--session\`. Discord thread IDs work on every computer. Session IDs do not.
688
+
689
+ A session ID only exists in the local SQLite database and OpenCode server on the computer that created it. \`kimaki send --session\` and \`kimaki session read\` fail on another computer.
690
+
691
+ To continue a remote thread, find the Discord channel, list its threads, then send with \`--thread\`:
692
+
693
+ \`\`\`bash
694
+ kimaki project list --all --json
695
+ kimaki thread list --channel <channel_id> --json
696
+ kimaki send --thread <thread_id> --prompt 'continue the work' --agent <current_agent>
697
+ \`\`\`
672
698
 
673
699
  To send to the thread associated with a known session (same machine only):
674
700
 
@@ -738,7 +764,7 @@ Use \`--pre-run '<command>'\` to check whether a scheduled task should start. Ki
738
764
 
739
765
  Scheduled tasks do not overlap by default. Add \`--allow-concurrency\` only when concurrent sessions from the same task are safe.
740
766
 
741
- 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.
767
+ For autonomous scheduled tasks, omit \`--user\` by default so each run stays out of the user's Discord sidebar. Put the user's Discord ID in the task instructions instead, and tell the agent to mention that ID only when it completed work worth reviewing, found an issue, or needs a decision. Do not mention the user when the run found no work or made no changes. Reserve \`--user\` for reminders that must surface every time they fire.
742
768
 
743
769
  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).
744
770
  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.
@@ -750,7 +776,7 @@ When the user specifies a time without a timezone, ask them to confirm their tim
750
776
  - \`--pre-run\` to start only when a project command exits with code 0
751
777
  - \`--allow-concurrency\` to permit overlapping runs from the same task
752
778
  - \`--parent-session\` to pass this session as parent of the scheduled child
753
- - \`--user\` to add a specific user to the scheduled thread. Omit this for quiet autonomous work
779
+ - \`--user\` to add a specific user to every scheduled thread. Use this for reminders, not autonomous tasks
754
780
 
755
781
  \`--wait\` is incompatible with \`--send-at\` because scheduled tasks run in the future.
756
782
 
@@ -775,9 +801,9 @@ For simple reminders and notifications (\`--notify-only\`), inline the prompt di
775
801
 
776
802
  Notification strategy:
777
803
  - 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.
778
- - If a task needs user attention, add "mention the user via Discord user ID when task requires user review" in the task md file.
779
- - 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.
780
- - If a scheduled task completes with no actionable result, archive the session: \`kimaki session archive ${archiveTarget}\`
804
+ - For autonomous tasks, include the user's Discord ID in the task md file and instruct the agent to mention it only after completing work the user should review, when reporting an issue, or when asking for a decision.
805
+ - Do not mention the user if the task found no work, made no changes, or has nothing actionable to report. Archive that session instead: \`kimaki session archive ${archiveTarget}\`
806
+ - Do not pass \`--user\` for autonomous tasks. It adds the user to every task thread before the result is known. Use it only when every occurrence must appear in the user's sidebar, such as an explicit reminder.
781
807
 
782
808
  Manage scheduled tasks with:
783
809
 
@@ -785,7 +811,7 @@ kimaki task list
785
811
  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>"]
786
812
  kimaki task delete <id>
787
813
 
788
- \`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.
814
+ \`kimaki task list\` prints \`userId\`, \`agent\`, and \`model\` columns. For autonomous tasks, \`userId\` should normally be \`-\`; put the Discord ID and conditional mention rule in the task instructions instead. Clear a stored user with \`kimaki task edit <id> --user ''\`. Add \`--user '<discord-user-id>'\` only for tasks whose every occurrence must surface. 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.
789
815
 
790
816
  \`kimaki session list\` also shows if a session was started by a scheduled \`delay\` or \`cron\` task, including task ID when available.
791
817
 
@@ -864,10 +890,15 @@ kimaki session list
864
890
  kimaki session list --json # machine-readable output
865
891
  kimaki session list --project /path/to/project # specific project
866
892
 
893
+ # List sessions across every locally registered project
894
+ kimaki session list --all
895
+
867
896
  # List only in-progress sessions. Exit status is 1 when none remain.
868
897
  kimaki session list --active
869
898
  \`\`\`
870
899
 
900
+ Each row shows the session title, project directory, \`status: working\` or \`status: idle\`, and \`tokens: N\` (total token footprint) when available. Kimaki-started sessions also show their Discord \`thread\` ID.
901
+
871
902
  Titles prefixed with \`btw:\` are side sessions that answer a related user question in parallel. They are not duplicate sessions of the main task.
872
903
 
873
904
  To search past sessions (supports plain text or /regex/flags). Defaults to this project and the last ${SESSION_SEARCH_DEFAULT_DAYS} days. Use \`--days 0\` for all time. Use \`--all\` to search every locally registered project:
@@ -881,14 +912,13 @@ kimaki session search "/panic|crash/i" --channel <channel_id>
881
912
  kimaki session search "auth timeout" --all
882
913
  \`\`\`
883
914
 
884
- To read a session's full conversation as markdown, pipe to a file and grep it to avoid wasting context.
885
- Logs go to stderr, so redirect stderr to hide them:
915
+ To read a session as markdown, pipe to a file. Logs go to stderr:
886
916
 
887
917
  \`\`\`bash
888
918
  kimaki session read <sessionId> > ./tmp/session.md 2>/dev/null
889
919
  \`\`\`
890
920
 
891
- Then use grep/read tools on the file to find what you need.
921
+ The dump is already compressed (no thinking, truncated tool inputs). If it is under 100 KB, read the whole file. Do not grep first. Use \`--thinking\` / \`--verbose\` only when you need the full dump.
892
922
 
893
923
  ### who edited a file
894
924
 
@@ -917,18 +947,25 @@ If this session edited the file, use this session ID instead. If several files c
917
947
 
918
948
  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.
919
949
 
920
- When the user uses \`#project-name\` syntax, they usually mean a Kimaki project channel. Use \`kimaki project list --json\` to resolve the \`channel_name\` to its repo working directory. The JSON output includes \`guild_id\` and \`guild_name\` to distinguish channels with the same name across different servers. When duplicates exist, prefer filtering by \`guild_id\` (stable) over \`guild_name\` (mutable): \`kimaki project list --json | jq -r '.[] | select(.channel_name == "project-name" and .guild_id == "123456") | .channel_id'\`.
950
+ When the user uses \`#project-name\` syntax, they usually mean a Kimaki project channel. Use \`kimaki project list --json\` to resolve the \`channel_name\` to its repo working directory. The JSON output includes \`guild_id\` and \`guild_name\` so you can tell channels with the same name apart. Prefer \`guild_id\` when there are duplicates. It is stable. \`guild_name\` can change.
921
951
 
922
- When the user uses \`#Some Thread Title\` with spaces, they mean a **thread title**, not a project channel. Find the session by searching across projects, then read the session markdown:
952
+ If the local list has no match, the channel may live on another computer. Scan Discord with \`kimaki project list --all --json\` and send by \`channel_id\`. Remote rows have \`is_local: false\` and \`directory: null\`. Do not use \`--project\` or \`--session\` for those channels.
923
953
 
924
- \`\`\`bash
925
- # 1. Find the session ID by searching thread titles across all projects
926
- kimaki session list --project /path/to/project --json | jq -r '.[] | select(.title | test("Thread Title"; "i")) | .id + " | " + .title'
954
+ When the user uses \`#Some Thread Title\` with spaces, they mean a **thread title**, not a project channel. On this computer, search local sessions, then read the markdown:
927
955
 
928
- # 2. Read the full session conversation as markdown
956
+ \`\`\`bash
957
+ kimaki session list --project /path/to/project --json
929
958
  kimaki session read <sessionId> > ./tmp/session.md 2>/dev/null
930
959
  \`\`\`
931
960
 
961
+ If that local search has no match, list Discord threads instead of using a session ID:
962
+
963
+ \`\`\`bash
964
+ kimaki project list --all --json
965
+ kimaki thread list --channel <channel_id> --json
966
+ kimaki send --thread <thread_id> --prompt 'continue the work' --agent <current_agent>
967
+ \`\`\`
968
+
932
969
  If you don't know which project the thread belongs to, try each project from \`kimaki project list --json\`.
933
970
 
934
971
  \`\`\`bash
@@ -936,10 +973,18 @@ If you don't know which project the thread belongs to, try each project from \`k
936
973
  kimaki project list
937
974
  kimaki project list --json # machine-readable output with guild_id, guild_name, is_local
938
975
 
939
- # Include projects from other machines (scans Kimaki category in Discord)
976
+ # Include projects from other machines (scans Kimaki groups in Discord)
940
977
  kimaki project list --all
941
978
  kimaki project list --all --json # remote projects have is_local: false and directory: null
942
979
 
980
+ # Find a channel on another computer, then send a new prompt there
981
+ kimaki project list --all --json
982
+ kimaki send --channel <channel_id> --prompt 'Plan how to update the API client to v2' --agent <current_agent>
983
+
984
+ # Continue an existing remote thread. Session IDs do not work across computers.
985
+ kimaki thread list --channel <channel_id> --json
986
+ kimaki send --thread <thread_id> --prompt 'continue the work' --agent <current_agent>
987
+
943
988
  # Resolve by channel name (prefer adding guild_name filter if duplicates exist)
944
989
  kimaki project list --json | jq -r '.[] | select(.channel_name == "project-name") | .channel_id + " " + .guild_name + " " + .directory'
945
990
 
@@ -1001,7 +1046,7 @@ while kimaki session list --active --exclude ${sessionId}; do sleep 5; done
1001
1046
 
1002
1047
  \`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.
1003
1048
 
1004
- \`session wait\` exits with the session markdown on stdout once the model finishes responding.
1049
+ \`session wait\` exits with the session markdown on stdout once the model finishes responding, or when the session pauses to show the user a question (it will not finish on its own until answered).
1005
1050
 
1006
1051
  Use \`--wait\` when you need to:
1007
1052
  - **Fix a bug in another project** before continuing here (e.g. fix a dependency, then resume)
@@ -0,0 +1,17 @@
1
+ import type { Plugin } from '@opencode-ai/plugin'
2
+
3
+ // Work around https://github.com/anomalyco/opencode/issues/49599 until invalid task IDs are ignored upstream.
4
+ export function sanitizeTaskArguments(args: Record<string, unknown>) {
5
+ const taskId = args.task_id
6
+ if (typeof taskId !== 'string' || taskId.startsWith('ses')) return
7
+ delete args.task_id
8
+ }
9
+
10
+ export const taskIdPlugin: Plugin = async () => {
11
+ return {
12
+ 'tool.execute.before': async (input, output) => {
13
+ if (input.tool !== 'task') return
14
+ sanitizeTaskArguments(output.args)
15
+ },
16
+ }
17
+ }
@@ -455,6 +455,9 @@ async function executeScheduledTask({
455
455
  task: ScheduledTask
456
456
  runId?: number
457
457
  }): Promise<string | null | Error | { kind: 'condition-not-met' }> {
458
+ const claimedTask = await getScheduledTask(task.id)
459
+ if (claimedTask?.status !== 'running') return { kind: 'condition-not-met' }
460
+
458
461
  const payloadResult = parseScheduledTaskPayload(task.payload_json)
459
462
  if (payloadResult instanceof Error) {
460
463
  return new Error(`Task ${task.id} has invalid payload`, {
@@ -466,6 +469,11 @@ async function executeScheduledTask({
466
469
  if (commandResult instanceof Error) return commandResult
467
470
  if (commandResult.kind === 'skip') return { kind: 'condition-not-met' }
468
471
 
472
+ const taskAfterCommand = await getScheduledTask(task.id)
473
+ if (taskAfterCommand?.status !== 'running') {
474
+ return { kind: 'condition-not-met' }
475
+ }
476
+
469
477
  if (payloadResult.kind === 'thread') {
470
478
  return executeThreadScheduledTask({
471
479
  rest,
@@ -210,7 +210,7 @@ describe('formatSessionSleepToolOutput', () => {
210
210
  reason: 'Wait 1 day, then check mail',
211
211
  }),
212
212
  ).toBe(
213
- '⬦ Woke after sleeping until 2026-09-07 13:21 UTC\nReason: Wait 1 day, then check mail\nContinue the work you were waiting for.',
213
+ '-# Woke after sleeping until 2026-09-07 13:21 UTC\nReason: Wait 1 day, then check mail\nContinue the work you were waiting for.',
214
214
  )
215
215
  })
216
216
  })
package/src/test-utils.ts CHANGED
@@ -354,9 +354,11 @@ export function isFooterMessage({
354
354
  if (message.author.id !== botUserId) {
355
355
  return false
356
356
  }
357
- const content = message.content.startsWith('> ')
358
- ? message.content.slice(2)
359
- : message.content
357
+ const content = message.content.startsWith('-# ')
358
+ ? message.content.slice(3)
359
+ : message.content.startsWith('> ')
360
+ ? message.content.slice(2)
361
+ : message.content
360
362
  if (!content.startsWith('*')) {
361
363
  return false
362
364
  }