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,7 @@ import path from 'node:path'
25
25
  import { createLogger, LogPrefix } from '../logger.js'
26
26
  import { notifyError } from '../sentry.js'
27
27
  import { NOTIFY_MESSAGE_FLAGS, sendThreadMessage } from '../discord-utils.js'
28
+ import { QUEUE_PREFIX } from '../message-formatting.js'
28
29
 
29
30
  const logger = createLogger(LogPrefix.FILE_UPLOAD)
30
31
 
@@ -325,7 +326,7 @@ export async function handleFileUploadModalSubmit(
325
326
  const username = interaction.user.globalName || interaction.user.username
326
327
  await sendThreadMessage(
327
328
  context.thread,
328
- `» **${username}:** Uploaded ${fileNames.join(', ')}`,
329
+ `${QUEUE_PREFIX}**${username}:** Uploaded ${fileNames.join(', ')}`,
329
330
  )
330
331
  }
331
332
 
@@ -18,6 +18,7 @@ import {
18
18
  resolveTextChannel,
19
19
  resolveWorkingDirectory,
20
20
  sendThreadMessage,
21
+ sendSessionPartBatches,
21
22
  } from '../discord-utils.js'
22
23
  import {
23
24
  collectSessionChunks,
@@ -240,9 +241,7 @@ export async function handleForkSubagentSelectMenu(
240
241
  limit: 30,
241
242
  })
242
243
  const batched = batchChunksForDiscord(chunks)
243
- for (const batch of batched) {
244
- await sendThreadMessage(forkedThread, batch.content)
245
- }
244
+ await sendSessionPartBatches({ thread: forkedThread, batches: batched })
246
245
  }
247
246
  } catch (error) {
248
247
  forkLogger.error('Error replaying forked subagent history:', error)
@@ -20,6 +20,7 @@ import {
20
20
  resolveWorkingDirectory,
21
21
  resolveTextChannel,
22
22
  sendThreadMessage,
23
+ sendSessionPartBatches,
23
24
  } from '../discord-utils.js'
24
25
  import {
25
26
  collectSessionChunks,
@@ -356,12 +357,12 @@ export async function handleForkSelectMenu(
356
357
  limit: 30,
357
358
  })
358
359
  const batched = batchChunksForDiscord(chunks)
359
- for (const batch of batched) {
360
- const discordMessage = await sendThreadMessage(thread, batch.content)
360
+ const sent = await sendSessionPartBatches({ thread, batches: batched })
361
+ for (const batch of sent) {
361
362
  await setPartMessagesBatch(
362
363
  batch.partIds.map((partId) => ({
363
364
  partId,
364
- messageId: discordMessage.id,
365
+ messageId: batch.message.id,
365
366
  threadId: thread.id,
366
367
  })),
367
368
  )
@@ -148,8 +148,14 @@ export async function handleLastSessionsCommand({
148
148
  const tableMarkdown = buildSessionTable({ rows })
149
149
  const segments = splitTablesFromMarkdown(tableMarkdown)
150
150
 
151
- const allComponents: APIMessageTopLevelComponent[] = segments.flatMap(
152
- (segment) => {
151
+ const btwNote: APITextDisplayComponent = {
152
+ type: ComponentType.TextDisplay,
153
+ content:
154
+ 'Titles prefixed `btw:` are side sessions that answer a related question. They are not duplicate sessions.',
155
+ }
156
+ const allComponents: APIMessageTopLevelComponent[] = [
157
+ btwNote,
158
+ ...segments.flatMap((segment) => {
153
159
  if (segment.type === 'components') {
154
160
  return segment.components
155
161
  }
@@ -158,8 +164,8 @@ export async function handleLastSessionsCommand({
158
164
  content: segment.text,
159
165
  }
160
166
  return [textDisplay]
161
- },
162
- )
167
+ }),
168
+ ]
163
169
 
164
170
  const { components } = truncateComponents(allComponents)
165
171
 
@@ -34,6 +34,7 @@ import {
34
34
  getOpencodeServerAuthHeaders,
35
35
  } from '../opencode.js'
36
36
  import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js'
37
+ import { clearModelListCache } from '../session-handler/model-utils.js'
37
38
  import { createLogger, LogPrefix } from '../logger.js'
38
39
  import { buildPaginatedOptions, parsePaginationValue } from './paginated-select.js'
39
40
 
@@ -122,7 +123,9 @@ function createContextHash(context: LoginContext): string {
122
123
  // ── Provider popularity order ───────────────────────────────────
123
124
  // Discord select menus cap at 25 options, so we show popular ones first.
124
125
  // IDs sourced from opencode's provider.list() API (scripts/list-providers.ts).
126
+ // `subrouter` leads: it pools every subscription and cycles across them.
125
127
  const PROVIDER_POPULARITY_ORDER: string[] = [
128
+ 'subrouter',
126
129
  'anthropic',
127
130
  'openai',
128
131
  'google',
@@ -937,6 +940,7 @@ export async function handleOAuthCodeModalSubmit(
937
940
  }
938
941
 
939
942
  await getClient().instance.dispose({ directory: ctx.dir })
943
+ clearModelListCache()
940
944
  pendingLoginContexts.delete(hash)
941
945
 
942
946
  await interaction.editReply({
@@ -993,6 +997,7 @@ export async function handleApiKeyModalSubmit(
993
997
 
994
998
  // Dispose to refresh provider state so new credentials are recognized
995
999
  await getClient().instance.dispose({ directory: ctx.dir })
1000
+ clearModelListCache()
996
1001
 
997
1002
  await interaction.editReply({
998
1003
  content: `✅ **Successfully authenticated with ${ctx.providerName}!**\n\nYou can now use models from this provider.`,
@@ -1159,6 +1164,7 @@ async function startOAuthFlow(
1159
1164
  }
1160
1165
 
1161
1166
  await getClient().instance.dispose({ directory: ctx.dir })
1167
+ clearModelListCache()
1162
1168
  pendingLoginContexts.delete(hash)
1163
1169
 
1164
1170
  await interaction.editReply({
@@ -1,7 +1,5 @@
1
- // /merge-worktree command - Merge worktree commits into default branch.
2
- // Pipeline: rebase worktree commits onto target -> local fast-forward push.
3
- // Preserves all commits (no squash). On rebase conflicts, asks the AI model
4
- // in the thread to resolve them.
1
+ // /merge-worktree command - Rebase or squash worktree commits into a target branch.
2
+ // On rebase conflicts, asks the AI model in the thread to resolve them.
5
3
 
6
4
  import { type TextChannel, type ThreadChannel } from 'discord.js'
7
5
  import type { AutocompleteContext, CommandContext } from './types.js'
@@ -12,7 +10,12 @@ import {
12
10
  } from '../database.js'
13
11
  import { createLogger, LogPrefix } from '../logger.js'
14
12
  import { notifyError } from '../sentry.js'
15
- import { mergeWorktree, listBranchesByLastCommit, validateBranchRef } from '../worktrees.js'
13
+ import {
14
+ mergeWorktree,
15
+ listBranchesByLastCommit,
16
+ validateBranchRef,
17
+ formatMergeWorktreeError,
18
+ } from '../worktrees.js'
16
19
  import {
17
20
  sendThreadMessage,
18
21
  resolveWorkingDirectory,
@@ -27,19 +30,29 @@ import {
27
30
  TargetDirtyWorktreeError,
28
31
  NothingToMergeError,
29
32
  } from '../errors.js'
33
+ import { WORKTREE_PREFIX } from '../message-formatting.js'
30
34
 
31
35
  const logger = createLogger(LogPrefix.WORKTREE)
32
36
 
33
- /** Worktree thread title prefix - indicates unmerged worktree */
34
- export const WORKTREE_PREFIX = '⬦ '
37
+ function quoteShellArg(value: string): string {
38
+ return `'${value.replaceAll("'", "'\\''")}'`
39
+ }
40
+
41
+ export { WORKTREE_PREFIX }
42
+
43
+ function worktreePrefixLength(name: string) {
44
+ if (name.startsWith(WORKTREE_PREFIX)) return WORKTREE_PREFIX.length
45
+ return 0
46
+ }
35
47
 
36
48
  async function removeWorktreePrefixFromTitle(
37
49
  thread: ThreadChannel,
38
50
  ): Promise<void> {
39
- if (!thread.name.startsWith(WORKTREE_PREFIX)) {
51
+ const prefixLength = worktreePrefixLength(thread.name)
52
+ if (!prefixLength) {
40
53
  return
41
54
  }
42
- const newName = thread.name.slice(WORKTREE_PREFIX.length)
55
+ const newName = thread.name.slice(prefixLength)
43
56
  const timeoutMs = 5000
44
57
  await Promise.race([
45
58
  thread.setName(newName).catch((e) => {
@@ -122,6 +135,15 @@ export async function handleMergeWorktreeCommand({
122
135
  return
123
136
  }
124
137
 
138
+ const strategyOption = command.options.getString('strategy') || 'rebase'
139
+ if (strategyOption !== 'rebase' && strategyOption !== 'squash') {
140
+ await command.editReply(`Invalid merge strategy: \`${strategyOption}\``)
141
+ return
142
+ }
143
+ const strategyName = strategyOption === 'squash'
144
+ ? 'Squash into one commit'
145
+ : 'Keep commits (rebase)'
146
+
125
147
  const rawTargetBranch = command.options.getString('target-branch') || undefined
126
148
  let targetBranch = rawTargetBranch
127
149
  if (targetBranch) {
@@ -141,6 +163,7 @@ export async function handleMergeWorktreeCommand({
141
163
  mainRepoDir: info.project_directory,
142
164
  worktreeName: info.workspace_name,
143
165
  targetBranch,
166
+ strategy: strategyOption,
144
167
  onProgress: (msg) => {
145
168
  logger.log(`[merge] ${msg}`)
146
169
  },
@@ -163,13 +186,24 @@ export async function handleMergeWorktreeCommand({
163
186
 
164
187
  if (result instanceof NothingToMergeError) {
165
188
  void removeWorktreePrefixFromTitle(thread)
166
- await command.editReply(`Merge failed: ${result.message}`)
189
+ await command.editReply(formatMergeWorktreeError(result))
167
190
  return
168
191
  }
169
192
 
170
193
  if (result instanceof RebaseConflictError) {
194
+ const prefixLength = worktreePrefixLength(thread.name)
195
+ const mergedThreadName = prefixLength
196
+ ? thread.name.slice(prefixLength)
197
+ : thread.name
198
+ const mergeCommand = [
199
+ 'kimaki merge-worktree',
200
+ `--strategy ${strategyOption}`,
201
+ `--target-branch ${quoteShellArg(String(result.target))}`,
202
+ `--thread ${quoteShellArg(thread.id)}`,
203
+ `--thread-name ${quoteShellArg(mergedThreadName)}`,
204
+ ].join(' ')
171
205
  await command.editReply(
172
- 'Rebase conflict detected. Asking the model to resolve...',
206
+ `Rebase conflict detected. Asking the model to resolve it and retry the merge with **${strategyName}**.`,
173
207
  )
174
208
  await sendPromptToModel({
175
209
  prompt: [
@@ -184,7 +218,8 @@ export async function handleMergeWorktreeCommand({
184
218
  '6. Stage resolved files with `git add`',
185
219
  '7. Continue the rebase with `git rebase --continue`',
186
220
  '8. If git reports more conflicts, repeat steps 1-7 until the rebase finishes (no more rebase in progress, `git status` is clean)',
187
- '9. Once the rebase is fully complete, tell me so I can run `/merge-worktree` again',
221
+ `9. Once the rebase is fully complete, run \`${mergeCommand}\` yourself`,
222
+ '10. If that command fails because the target changed or is not ready, stop and report the failure. The user can run it again later.',
188
223
  ].join('\n'),
189
224
  thread,
190
225
  projectDirectory: info.project_directory,
@@ -194,14 +229,17 @@ export async function handleMergeWorktreeCommand({
194
229
  return
195
230
  }
196
231
 
197
- await command.editReply(`Merge failed: ${result.message}`)
232
+ const formatted = formatMergeWorktreeError(result)
233
+ logger.error(formatted)
234
+ await command.editReply(formatted)
198
235
  return
199
236
  }
200
237
 
201
238
  void removeWorktreePrefixFromTitle(thread)
202
- await command.editReply(
203
- `Merged \`${result.branchName}\` into \`${result.defaultBranch}\` @ ${result.shortSha} (${result.commitCount} commit${result.commitCount === 1 ? '' : 's'})\nWorktree now at detached HEAD.`,
204
- )
239
+ const mergeSummary = strategyOption === 'squash'
240
+ ? `Squashed \`${result.branchName}\` into \`${result.defaultBranch}\` @ ${result.shortSha} (${result.commitCount} source commit${result.commitCount === 1 ? '' : 's'} → 1)`
241
+ : `Merged \`${result.branchName}\` into \`${result.defaultBranch}\` @ ${result.shortSha} (${result.commitCount} commit${result.commitCount === 1 ? '' : 's'})`
242
+ await command.editReply(`${mergeSummary}\nWorktree now at detached HEAD.`)
205
243
  }
206
244
 
207
245
  /**
@@ -34,6 +34,7 @@ import {
34
34
  } from './model.js'
35
35
 
36
36
  import { getThinkingValuesForModel } from '../thinking-utils.js'
37
+ import { resolveDisplayedModelId } from '../session-handler/model-utils.js'
37
38
  import { createLogger, LogPrefix } from '../logger.js'
38
39
 
39
40
  const logger = createLogger(LogPrefix.MODEL)
@@ -214,6 +215,13 @@ export async function showModelVariantPicker({
214
215
  const { providerID, modelID, model: fullModelId } = currentModelInfo
215
216
  const sourceLabel = formatSourceLabel(currentModelInfo)
216
217
  const variantLabel = cascadeVariant ? ` (${cascadeVariant})` : ''
218
+ const displayedModelId =
219
+ (await resolveDisplayedModelId({
220
+ providers: providersResponse.data.all,
221
+ providerID,
222
+ modelID,
223
+ sessionID: sessionId,
224
+ })) ?? fullModelId
217
225
 
218
226
  const provider = providersResponse.data.all.find((p) => {
219
227
  return p.id === providerID
@@ -226,7 +234,7 @@ export async function showModelVariantPicker({
226
234
  modelId: modelID,
227
235
  })
228
236
 
229
- const statusText = `**Current model:** \`${fullModelId}\`${variantLabel} — ${sourceLabel}`
237
+ const statusText = `**Current model:** \`${displayedModelId}\`${variantLabel} — ${sourceLabel}`
230
238
 
231
239
  if (variants.length === 0) {
232
240
  await editReply({
@@ -28,7 +28,10 @@ import {
28
28
  } from '../database.js'
29
29
  import { initializeOpencodeForDirectory } from '../opencode.js'
30
30
  import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js'
31
- import { getDefaultModel } from '../session-handler/model-utils.js'
31
+ import {
32
+ getDefaultModel,
33
+ resolveDisplayedModelId,
34
+ } from '../session-handler/model-utils.js'
32
35
  import { getRuntime } from '../session-handler/thread-session-runtime.js'
33
36
  import { getThinkingValuesForModel } from '../thinking-utils.js'
34
37
  import {
@@ -544,22 +547,30 @@ export async function handleModelCommand({
544
547
  return
545
548
  }
546
549
 
547
- const currentModelText = (() => {
550
+ const currentModelText = await (async () => {
551
+ if (currentModelInfo.type === 'none') {
552
+ return '**Current:** none'
553
+ }
554
+ const displayed =
555
+ (await resolveDisplayedModelId({
556
+ providers: allProviders,
557
+ providerID: currentModelInfo.providerID,
558
+ modelID: currentModelInfo.modelID,
559
+ sessionID: sessionId,
560
+ })) ?? currentModelInfo.model
548
561
  switch (currentModelInfo.type) {
549
562
  case 'session':
550
- return `**Current (this thread):** \`${currentModelInfo.model}\``
563
+ return `**Current (this thread):** \`${displayed}\``
551
564
  case 'agent':
552
- return `**Current (agent "${currentModelInfo.agentName}"):** \`${currentModelInfo.model}\``
565
+ return `**Current (agent "${currentModelInfo.agentName}"):** \`${displayed}\``
553
566
  case 'channel':
554
- return `**Current (channel override):** \`${currentModelInfo.model}\``
567
+ return `**Current (channel override):** \`${displayed}\``
555
568
  case 'global':
556
- return `**Current (global default):** \`${currentModelInfo.model}\``
569
+ return `**Current (global default):** \`${displayed}\``
557
570
  case 'opencode-config':
558
571
  case 'opencode-recent':
559
572
  case 'opencode-provider-default':
560
- return `**Current (opencode default):** \`${currentModelInfo.model}\``
561
- case 'none':
562
- return '**Current:** none'
573
+ return `**Current (opencode default):** \`${displayed}\``
563
574
  }
564
575
  })()
565
576
 
@@ -1,3 +1,4 @@
1
+ // LEGACY rotation, superseded by @subrouter/opencode. See oauth-rotation-shared.ts.
1
2
  /**
2
3
  * CLI commands for multi-provider OAuth account management.
3
4
  * Mounted via goke .use() in cli.ts under the `multioauth` namespace.