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
@@ -6,7 +6,7 @@ import { afterAll, afterEach, describe, expect, test } from 'vitest';
6
6
  import { completeScheduledTaskRunsForSession, createScheduledTask, createScheduledTaskRun, getActiveScheduledTaskRuns, getScheduledTask, startScheduledTaskRunSession, } from './database.js';
7
7
  import { closeDb } from './db.js';
8
8
  import { runTaskCommand } from './task-runner.js';
9
- import { appendTaskCommandOutput, parseScheduledTaskPayload, parseSendAtValue, } from './task-schedule.js';
9
+ import { appendTaskCommandOutput, applyScheduledTaskUserEdit, parseScheduledTaskPayload, formatSessionSleepToolOutput, formatSessionSleepWakePrompt, parseSendAtValue, parseSleepWakeAt, } from './task-schedule.js';
10
10
  describe('parseSendAtValue', () => {
11
11
  test('accepts UTC ISO date ending with Z', () => {
12
12
  const now = new Date('2026-02-22T13:00:00Z');
@@ -75,6 +75,101 @@ describe('parseSendAtValue', () => {
75
75
  expect(result.nextRunAt.toISOString()).toBe('2026-02-23T09:00:00.000Z');
76
76
  });
77
77
  });
78
+ describe('parseSleepWakeAt', () => {
79
+ const now = new Date('2026-08-19T12:00:00Z');
80
+ test('adds 2h to now', () => {
81
+ const result = parseSleepWakeAt({ duration: '2h', now });
82
+ expect(result).not.toBeInstanceOf(Error);
83
+ if (result instanceof Error)
84
+ throw result;
85
+ expect(result.toISOString()).toBe('2026-08-19T14:00:00.000Z');
86
+ });
87
+ test('adds 30s and 1d', () => {
88
+ const thirtySeconds = parseSleepWakeAt({ duration: '30s', now });
89
+ expect(thirtySeconds).not.toBeInstanceOf(Error);
90
+ if (thirtySeconds instanceof Error)
91
+ throw thirtySeconds;
92
+ expect(thirtySeconds.toISOString()).toBe('2026-08-19T12:00:30.000Z');
93
+ const oneDay = parseSleepWakeAt({ duration: '1d', now });
94
+ expect(oneDay).not.toBeInstanceOf(Error);
95
+ if (oneDay instanceof Error)
96
+ throw oneDay;
97
+ expect(oneDay.toISOString()).toBe('2026-08-20T12:00:00.000Z');
98
+ });
99
+ test('accepts until UTC ISO date ending with Z', () => {
100
+ const result = parseSleepWakeAt({
101
+ until: '2026-08-20T09:00:00Z',
102
+ now,
103
+ });
104
+ expect(result).not.toBeInstanceOf(Error);
105
+ if (result instanceof Error)
106
+ throw result;
107
+ expect(result.toISOString()).toBe('2026-08-20T09:00:00.000Z');
108
+ });
109
+ test('rejects past until', () => {
110
+ const result = parseSleepWakeAt({
111
+ until: '2026-08-19T11:59:59Z',
112
+ now,
113
+ });
114
+ expect(result).toBeInstanceOf(Error);
115
+ if (result instanceof Error) {
116
+ expect(result.message).toContain('until must be in the future (UTC)');
117
+ }
118
+ });
119
+ test('rejects both duration and until', () => {
120
+ const result = parseSleepWakeAt({
121
+ duration: '1h',
122
+ until: '2026-08-20T09:00:00Z',
123
+ now,
124
+ });
125
+ expect(result).toBeInstanceOf(Error);
126
+ if (result instanceof Error) {
127
+ expect(result.message).toContain('either duration or until');
128
+ }
129
+ });
130
+ test('rejects neither duration nor until', () => {
131
+ const result = parseSleepWakeAt({ now });
132
+ expect(result).toBeInstanceOf(Error);
133
+ if (result instanceof Error) {
134
+ expect(result.message).toContain('duration or until');
135
+ }
136
+ });
137
+ test('rejects until with a non-UTC offset', () => {
138
+ const result = parseSleepWakeAt({
139
+ until: '2026-08-20T09:00:00+01:00',
140
+ now,
141
+ });
142
+ expect(result).toBeInstanceOf(Error);
143
+ if (result instanceof Error) {
144
+ expect(result.message).toContain('UTC ISO format ending with Z');
145
+ }
146
+ });
147
+ });
148
+ describe('formatSessionSleepToolOutput', () => {
149
+ const wakeAt = new Date('2026-09-07T13:21:00Z');
150
+ test('starts with Sleeping until and tells the model to wait', () => {
151
+ const output = formatSessionSleepToolOutput({
152
+ wakeAt,
153
+ reason: 'Wait 1 day, then check mail',
154
+ });
155
+ expect(output).toBe('Sleeping until 2026-09-07 13:21 UTC. Reason: Wait 1 day, then check mail. This tool result is not a wake. Do not continue the waited work. Do not call more tools. Reply with one short line that you are waiting until that time, then stop. The real wake is a later Discord message that starts with "Woke after sleeping until". Only then continue the wait reason. A new user message in this thread cancels the sleep. If you still need that later wake after answering, call kimaki_sleep again with until set to the same UTC time.');
156
+ expect(output.startsWith('Sleeping until ')).toBe(true);
157
+ expect(output).toContain('This tool result is not a wake');
158
+ expect(output).not.toContain('You will be woken');
159
+ expect(output).not.toContain('Stop now');
160
+ });
161
+ test('omits reason when none is passed', () => {
162
+ const output = formatSessionSleepToolOutput({ wakeAt });
163
+ expect(output.startsWith('Sleeping until 2026-09-07 13:21 UTC.')).toBe(true);
164
+ expect(output).not.toContain('Reason:');
165
+ });
166
+ test('wake prompt stays a separate later message', () => {
167
+ expect(formatSessionSleepWakePrompt({
168
+ wakeAt,
169
+ reason: 'Wait 1 day, then check mail',
170
+ })).toBe('⬦ Woke after sleeping until 2026-09-07 13:21 UTC\nReason: Wait 1 day, then check mail\nContinue the work you were waiting for.');
171
+ });
172
+ });
78
173
  describe('scheduled task execution options', () => {
79
174
  const tempDirectories = [];
80
175
  afterEach(() => {
@@ -105,6 +200,59 @@ describe('scheduled task execution options', () => {
105
200
  }
106
201
  `);
107
202
  });
203
+ test('clears the stored user when task edit passes an empty --user', () => {
204
+ const payload = parseScheduledTaskPayload(JSON.stringify({
205
+ kind: 'thread',
206
+ threadId: 'thread-1',
207
+ prompt: 'Handle support requests',
208
+ userId: '535922349652836367',
209
+ username: 'Tommy',
210
+ }));
211
+ expect(payload).not.toBeInstanceOf(Error);
212
+ if (payload instanceof Error)
213
+ throw payload;
214
+ expect(applyScheduledTaskUserEdit({
215
+ payload,
216
+ userOption: '',
217
+ })).toMatchInlineSnapshot(`
218
+ {
219
+ "agent": null,
220
+ "allowConcurrency": false,
221
+ "injectionGuardPatterns": null,
222
+ "kind": "thread",
223
+ "model": null,
224
+ "parentSessionId": null,
225
+ "permissions": null,
226
+ "preRunCommand": null,
227
+ "prompt": "Handle support requests",
228
+ "threadId": "thread-1",
229
+ "userId": null,
230
+ "username": null,
231
+ }
232
+ `);
233
+ expect(applyScheduledTaskUserEdit({
234
+ payload,
235
+ userOption: ' ',
236
+ }).userId).toBeNull();
237
+ });
238
+ test('sets the stored user when task edit passes a resolved --user', () => {
239
+ const payload = parseScheduledTaskPayload(JSON.stringify({
240
+ kind: 'channel',
241
+ channelId: 'chan-1',
242
+ prompt: 'Daily digest',
243
+ }));
244
+ expect(payload).not.toBeInstanceOf(Error);
245
+ if (payload instanceof Error)
246
+ throw payload;
247
+ expect(applyScheduledTaskUserEdit({
248
+ payload,
249
+ userOption: '535922349652836367',
250
+ resolvedUser: { id: '535922349652836367', username: 'Tommy' },
251
+ })).toMatchObject({
252
+ userId: '535922349652836367',
253
+ username: 'Tommy',
254
+ });
255
+ });
108
256
  test('adds command stdout to the prompt', () => {
109
257
  expect(appendTaskCommandOutput({
110
258
  prompt: 'Handle support requests',
@@ -67,6 +67,8 @@ export function initTestGitRepo(directory) {
67
67
  execSync('git config user.name "Test"', { cwd: directory, stdio: 'pipe' });
68
68
  execSync('git commit --allow-empty -m "init"', { cwd: directory, stdio: 'pipe' });
69
69
  }
70
+ import { getMessageVisibleText, } from 'discord-digital-twin/src';
71
+ export { getMessageVisibleText };
70
72
  import { getOpencodeClient, initializeOpencodeForDirectory, } from './opencode.js';
71
73
  import { getThreadState, } from './session-handler/thread-runtime-state.js';
72
74
  const MAX_VITEST_WAIT_TIMEOUT_MS = 10_000;
@@ -155,7 +157,7 @@ export async function waitForBotReplyAfterUserMessage({ discord, threadId, userI
155
157
  const messages = await discord.thread(threadId).getMessages();
156
158
  const userMessageIndex = messages.findIndex((message) => {
157
159
  return (message.author.id === userId &&
158
- message.content.includes(userMessageIncludes));
160
+ getMessageVisibleText(message).includes(userMessageIncludes));
159
161
  });
160
162
  const botReplyIndex = messages.findIndex((message, index) => {
161
163
  return index > userMessageIndex && message.author.id === discord.botUserId;
@@ -189,7 +191,7 @@ export async function waitForBotMessageContaining({ discord, threadId, userId, t
189
191
  if (afterUserMessageIncludes && userId) {
190
192
  return messages.findLastIndex((message) => {
191
193
  return (message.author.id === userId &&
192
- message.content.includes(afterUserMessageIncludes));
194
+ getMessageVisibleText(message).includes(afterUserMessageIncludes));
193
195
  });
194
196
  }
195
197
  return -1;
@@ -207,7 +209,7 @@ export async function waitForBotMessageContaining({ discord, threadId, userId, t
207
209
  return false;
208
210
  }
209
211
  return (message.author.id === discord.botUserId &&
210
- message.content.includes(text));
212
+ getMessageVisibleText(message).includes(text));
211
213
  });
212
214
  if (match) {
213
215
  return messages;
@@ -220,7 +222,7 @@ export async function waitForBotMessageContaining({ discord, threadId, userId, t
220
222
  .slice(-12)
221
223
  .map((message) => {
222
224
  const role = message.author.id === discord.botUserId ? 'bot' : 'user';
223
- return `${role}: ${message.content.slice(0, 120)}`;
225
+ return `${role}: ${getMessageVisibleText(message).slice(0, 120)}`;
224
226
  })
225
227
  .join('\n');
226
228
  throw new Error(`Timed out waiting for bot message containing "${text}" in thread ${threadId}. Recent messages:\n${recent}`);
@@ -243,14 +245,20 @@ export async function waitForMessageById({ discord, threadId, messageId, timeout
243
245
  }
244
246
  throw new Error(`Timed out waiting for message ${messageId} in thread ${threadId}`);
245
247
  }
246
- function isFooterMessage({ message, botUserId, }) {
248
+ export function isFooterMessage({ message, botUserId, }) {
247
249
  if (message.author.id !== botUserId) {
248
250
  return false;
249
251
  }
250
- if (!message.content.startsWith('*')) {
252
+ const content = message.content.startsWith('> ')
253
+ ? message.content.slice(2)
254
+ : message.content;
255
+ if (!content.startsWith('*')) {
251
256
  return false;
252
257
  }
253
- return message.content.includes('⋅');
258
+ if (content.startsWith('*using ')) {
259
+ return false;
260
+ }
261
+ return content.includes('⋅');
254
262
  }
255
263
  /**
256
264
  * Poll until a footer message appears, optionally after an anchor message.
@@ -265,7 +273,7 @@ export async function waitForFooterMessage({ discord, threadId, timeout, afterMe
265
273
  lastMessages = messages;
266
274
  const afterIndex = afterMessageIncludes
267
275
  ? messages.findLastIndex((message) => {
268
- if (!message.content.includes(afterMessageIncludes)) {
276
+ if (!getMessageVisibleText(message).includes(afterMessageIncludes)) {
269
277
  return false;
270
278
  }
271
279
  if (!afterAuthorId) {
@@ -297,7 +305,7 @@ export async function waitForFooterMessage({ discord, threadId, timeout, afterMe
297
305
  .slice(-12)
298
306
  .map((message) => {
299
307
  const role = message.author.id === discord.botUserId ? 'bot' : 'user';
300
- return `${role}: ${message.content.slice(0, 120)}`;
308
+ return `${role}: ${getMessageVisibleText(message).slice(0, 120)}`;
301
309
  })
302
310
  .join('\n');
303
311
  const anchorText = afterMessageIncludes || 'start';
@@ -33,3 +33,13 @@ export function matchThinkingValue({ requestedValue, availableValues, }) {
33
33
  return availableValue.toLowerCase() === normalizedRequestedValue;
34
34
  });
35
35
  }
36
+ export function resolveRequestedThinkingVariant({ requestedValue, providers, providerId, modelId, }) {
37
+ return matchThinkingValue({
38
+ requestedValue,
39
+ availableValues: getThinkingValuesForModel({
40
+ providers,
41
+ providerId,
42
+ modelId,
43
+ }),
44
+ });
45
+ }
@@ -0,0 +1,35 @@
1
+ // Tests thinking-level variant matching used by /xxx-agent variant.
2
+ import { describe, expect, test } from 'vitest';
3
+ import { getThinkingValuesForModel, matchThinkingValue, } from './thinking-utils.js';
4
+ describe('matchThinkingValue', () => {
5
+ const providers = [
6
+ {
7
+ id: 'anthropic',
8
+ models: {
9
+ 'claude-opus-4-6': {
10
+ variants: {
11
+ high: {},
12
+ max: {},
13
+ },
14
+ },
15
+ },
16
+ },
17
+ ];
18
+ const availableValues = getThinkingValuesForModel({
19
+ providers,
20
+ providerId: 'anthropic',
21
+ modelId: 'claude-opus-4-6',
22
+ });
23
+ test('matches a supported thinking level case-insensitively', () => {
24
+ expect(matchThinkingValue({
25
+ requestedValue: 'HIGH',
26
+ availableValues,
27
+ })).toBe('high');
28
+ });
29
+ test('returns undefined for an unsupported thinking level', () => {
30
+ expect(matchThinkingValue({
31
+ requestedValue: 'not-a-real-level',
32
+ availableValues,
33
+ })).toBeUndefined();
34
+ });
35
+ });