kimaki 0.4.82 → 0.4.84

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 (264) hide show
  1. package/LICENSE +21 -0
  2. package/dist/anthropic-auth-plugin.js +7 -0
  3. package/dist/cli.js +51 -7
  4. package/dist/commands/abort.js +5 -16
  5. package/dist/commands/action-buttons.js +3 -3
  6. package/dist/commands/add-project.js +1 -1
  7. package/dist/commands/ask-question.js +3 -3
  8. package/dist/commands/context-usage.js +1 -1
  9. package/dist/commands/create-new-project.js +1 -1
  10. package/dist/commands/fork.js +11 -8
  11. package/dist/commands/merge-worktree.js +1 -1
  12. package/dist/commands/new-worktree.js +63 -44
  13. package/dist/commands/remove-project.js +1 -1
  14. package/dist/commands/resume.js +11 -8
  15. package/dist/commands/screenshare.js +14 -6
  16. package/dist/commands/screenshare.test.js +20 -0
  17. package/dist/commands/session.js +1 -1
  18. package/dist/commands/undo-redo.js +91 -7
  19. package/dist/commands/user-command.js +1 -1
  20. package/dist/config.js +16 -1
  21. package/dist/database.js +53 -2
  22. package/dist/db.js +6 -0
  23. package/dist/discord-bot.js +48 -85
  24. package/dist/discord-command-registration.js +1 -1
  25. package/dist/external-opencode-sync.js +515 -0
  26. package/dist/external-opencode-sync.test.js +151 -0
  27. package/dist/gateway-proxy.e2e.test.js +8 -5
  28. package/dist/genai.js +1 -1
  29. package/dist/generated/enums.js +4 -0
  30. package/dist/generated/internal/class.js +4 -4
  31. package/dist/generated/internal/prismaNamespace.js +1 -0
  32. package/dist/generated/internal/prismaNamespaceBrowser.js +1 -0
  33. package/dist/generated/models/external_session_pending_prompts.js +1 -0
  34. package/dist/hrana-server.js +14 -285
  35. package/dist/hrana-server.test.js +4 -2
  36. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +7 -0
  37. package/dist/kimaki-opencode-plugin.js +2 -0
  38. package/dist/kitty-graphics-parser.js +3 -0
  39. package/dist/kitty-graphics-parser.test.js +276 -0
  40. package/dist/kitty-graphics-plugin.js +3 -0
  41. package/dist/markdown.js +4 -4
  42. package/dist/markdown.test.js +1 -1
  43. package/dist/message-formatting.js +54 -15
  44. package/dist/onboarding-tutorial.js +1 -1
  45. package/dist/openai-realtime.js +9 -13
  46. package/dist/opencode.js +28 -5
  47. package/dist/queue-advanced-e2e-setup.js +89 -0
  48. package/dist/queue-advanced-permissions-typing.e2e.test.js +5 -5
  49. package/dist/queue-advanced-typing.e2e.test.js +9 -22
  50. package/dist/queue-question-select-drain.e2e.test.js +117 -0
  51. package/dist/session-handler/event-stream-state.js +101 -7
  52. package/dist/session-handler/event-stream-state.test.js +7 -3
  53. package/dist/session-handler/thread-session-runtime.js +120 -9
  54. package/dist/store.js +1 -0
  55. package/dist/system-message.js +22 -4
  56. package/dist/system-message.test.js +19 -0
  57. package/dist/task-runner.js +1 -1
  58. package/dist/thread-message-queue.e2e.test.js +8 -14
  59. package/dist/tools.js +1 -1
  60. package/dist/undo-redo.e2e.test.js +20 -25
  61. package/package.json +10 -6
  62. package/schema.prisma +6 -0
  63. package/skills/errore/SKILL.md +40 -13
  64. package/skills/goke/SKILL.md +12 -0
  65. package/skills/lintcn/SKILL.md +868 -0
  66. package/skills/npm-package/SKILL.md +1 -0
  67. package/skills/proxyman/SKILL.md +215 -0
  68. package/skills/spiceflow/SKILL.md +1 -1
  69. package/skills/usecomputer/SKILL.md +339 -0
  70. package/src/ai-tool-to-genai.ts +1 -0
  71. package/src/anthropic-auth-plugin.ts +7 -0
  72. package/src/cli.ts +59 -6
  73. package/src/commands/abort.ts +6 -16
  74. package/src/commands/action-buttons.ts +5 -1
  75. package/src/commands/add-project.ts +1 -1
  76. package/src/commands/ask-question.ts +5 -2
  77. package/src/commands/context-usage.ts +1 -1
  78. package/src/commands/create-new-project.ts +1 -1
  79. package/src/commands/fork.ts +12 -11
  80. package/src/commands/merge-worktree.ts +1 -1
  81. package/src/commands/new-worktree.ts +74 -55
  82. package/src/commands/remove-project.ts +1 -1
  83. package/src/commands/resume.ts +12 -10
  84. package/src/commands/screenshare.test.ts +30 -0
  85. package/src/commands/screenshare.ts +18 -6
  86. package/src/commands/session.ts +1 -1
  87. package/src/commands/undo-redo.ts +108 -10
  88. package/src/commands/user-command.ts +1 -1
  89. package/src/config.ts +19 -1
  90. package/src/database.ts +72 -3
  91. package/src/db.ts +8 -0
  92. package/src/discord-bot.ts +58 -93
  93. package/src/discord-command-registration.ts +1 -1
  94. package/src/external-opencode-sync.ts +729 -0
  95. package/src/gateway-proxy.e2e.test.ts +9 -5
  96. package/src/genai.ts +3 -3
  97. package/src/generated/commonInputTypes.ts +34 -0
  98. package/src/generated/enums.ts +8 -0
  99. package/src/generated/internal/class.ts +4 -4
  100. package/src/generated/internal/prismaNamespace.ts +8 -0
  101. package/src/generated/internal/prismaNamespaceBrowser.ts +1 -0
  102. package/src/generated/models/thread_sessions.ts +53 -1
  103. package/src/hrana-server.test.ts +8 -2
  104. package/src/hrana-server.ts +18 -390
  105. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +7 -0
  106. package/src/kimaki-opencode-plugin.ts +2 -0
  107. package/src/markdown.test.ts +1 -1
  108. package/src/markdown.ts +4 -4
  109. package/src/message-formatting.ts +66 -17
  110. package/src/onboarding-tutorial.ts +1 -1
  111. package/src/openai-realtime.ts +6 -10
  112. package/src/opencode.ts +31 -7
  113. package/src/queue-advanced-e2e-setup.ts +92 -0
  114. package/src/queue-advanced-permissions-typing.e2e.test.ts +5 -5
  115. package/src/queue-advanced-typing.e2e.test.ts +9 -22
  116. package/src/queue-question-select-drain.e2e.test.ts +149 -0
  117. package/src/schema.sql +1 -0
  118. package/src/session-handler/event-stream-state.test.ts +7 -2
  119. package/src/session-handler/event-stream-state.ts +128 -7
  120. package/src/session-handler/thread-runtime-state.ts +5 -0
  121. package/src/session-handler/thread-session-runtime.ts +153 -11
  122. package/src/store.ts +8 -0
  123. package/src/system-message.ts +27 -4
  124. package/src/task-runner.ts +1 -1
  125. package/src/thread-message-queue.e2e.test.ts +8 -14
  126. package/src/tools.ts +1 -1
  127. package/src/undo-redo.e2e.test.ts +28 -26
  128. package/skills/jitter/node_modules/.bin/esbuild +0 -21
  129. package/skills/jitter/node_modules/.bin/tsc +0 -21
  130. package/skills/jitter/node_modules/.bin/tsserver +0 -21
  131. package/skills/jitter/node_modules/typescript/LICENSE.txt +0 -55
  132. package/skills/jitter/node_modules/typescript/README.md +0 -50
  133. package/skills/jitter/node_modules/typescript/SECURITY.md +0 -41
  134. package/skills/jitter/node_modules/typescript/ThirdPartyNoticeText.txt +0 -193
  135. package/skills/jitter/node_modules/typescript/bin/tsc +0 -2
  136. package/skills/jitter/node_modules/typescript/bin/tsserver +0 -2
  137. package/skills/jitter/node_modules/typescript/lib/_tsc.js +0 -133792
  138. package/skills/jitter/node_modules/typescript/lib/_tsserver.js +0 -659
  139. package/skills/jitter/node_modules/typescript/lib/_typingsInstaller.js +0 -222
  140. package/skills/jitter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
  141. package/skills/jitter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
  142. package/skills/jitter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
  143. package/skills/jitter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
  144. package/skills/jitter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
  145. package/skills/jitter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
  146. package/skills/jitter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
  147. package/skills/jitter/node_modules/typescript/lib/lib.d.ts +0 -22
  148. package/skills/jitter/node_modules/typescript/lib/lib.decorators.d.ts +0 -384
  149. package/skills/jitter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +0 -22
  150. package/skills/jitter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +0 -41
  151. package/skills/jitter/node_modules/typescript/lib/lib.dom.d.ts +0 -39429
  152. package/skills/jitter/node_modules/typescript/lib/lib.dom.iterable.d.ts +0 -571
  153. package/skills/jitter/node_modules/typescript/lib/lib.es2015.collection.d.ts +0 -147
  154. package/skills/jitter/node_modules/typescript/lib/lib.es2015.core.d.ts +0 -597
  155. package/skills/jitter/node_modules/typescript/lib/lib.es2015.d.ts +0 -28
  156. package/skills/jitter/node_modules/typescript/lib/lib.es2015.generator.d.ts +0 -77
  157. package/skills/jitter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +0 -605
  158. package/skills/jitter/node_modules/typescript/lib/lib.es2015.promise.d.ts +0 -81
  159. package/skills/jitter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +0 -128
  160. package/skills/jitter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +0 -144
  161. package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +0 -46
  162. package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +0 -326
  163. package/skills/jitter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +0 -116
  164. package/skills/jitter/node_modules/typescript/lib/lib.es2016.d.ts +0 -21
  165. package/skills/jitter/node_modules/typescript/lib/lib.es2016.full.d.ts +0 -23
  166. package/skills/jitter/node_modules/typescript/lib/lib.es2016.intl.d.ts +0 -31
  167. package/skills/jitter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +0 -21
  168. package/skills/jitter/node_modules/typescript/lib/lib.es2017.d.ts +0 -26
  169. package/skills/jitter/node_modules/typescript/lib/lib.es2017.date.d.ts +0 -31
  170. package/skills/jitter/node_modules/typescript/lib/lib.es2017.full.d.ts +0 -23
  171. package/skills/jitter/node_modules/typescript/lib/lib.es2017.intl.d.ts +0 -44
  172. package/skills/jitter/node_modules/typescript/lib/lib.es2017.object.d.ts +0 -49
  173. package/skills/jitter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +0 -135
  174. package/skills/jitter/node_modules/typescript/lib/lib.es2017.string.d.ts +0 -45
  175. package/skills/jitter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +0 -53
  176. package/skills/jitter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +0 -77
  177. package/skills/jitter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +0 -53
  178. package/skills/jitter/node_modules/typescript/lib/lib.es2018.d.ts +0 -24
  179. package/skills/jitter/node_modules/typescript/lib/lib.es2018.full.d.ts +0 -24
  180. package/skills/jitter/node_modules/typescript/lib/lib.es2018.intl.d.ts +0 -83
  181. package/skills/jitter/node_modules/typescript/lib/lib.es2018.promise.d.ts +0 -30
  182. package/skills/jitter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +0 -37
  183. package/skills/jitter/node_modules/typescript/lib/lib.es2019.array.d.ts +0 -79
  184. package/skills/jitter/node_modules/typescript/lib/lib.es2019.d.ts +0 -24
  185. package/skills/jitter/node_modules/typescript/lib/lib.es2019.full.d.ts +0 -24
  186. package/skills/jitter/node_modules/typescript/lib/lib.es2019.intl.d.ts +0 -23
  187. package/skills/jitter/node_modules/typescript/lib/lib.es2019.object.d.ts +0 -33
  188. package/skills/jitter/node_modules/typescript/lib/lib.es2019.string.d.ts +0 -37
  189. package/skills/jitter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +0 -24
  190. package/skills/jitter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +0 -765
  191. package/skills/jitter/node_modules/typescript/lib/lib.es2020.d.ts +0 -27
  192. package/skills/jitter/node_modules/typescript/lib/lib.es2020.date.d.ts +0 -42
  193. package/skills/jitter/node_modules/typescript/lib/lib.es2020.full.d.ts +0 -24
  194. package/skills/jitter/node_modules/typescript/lib/lib.es2020.intl.d.ts +0 -474
  195. package/skills/jitter/node_modules/typescript/lib/lib.es2020.number.d.ts +0 -28
  196. package/skills/jitter/node_modules/typescript/lib/lib.es2020.promise.d.ts +0 -47
  197. package/skills/jitter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +0 -99
  198. package/skills/jitter/node_modules/typescript/lib/lib.es2020.string.d.ts +0 -44
  199. package/skills/jitter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +0 -41
  200. package/skills/jitter/node_modules/typescript/lib/lib.es2021.d.ts +0 -23
  201. package/skills/jitter/node_modules/typescript/lib/lib.es2021.full.d.ts +0 -24
  202. package/skills/jitter/node_modules/typescript/lib/lib.es2021.intl.d.ts +0 -166
  203. package/skills/jitter/node_modules/typescript/lib/lib.es2021.promise.d.ts +0 -48
  204. package/skills/jitter/node_modules/typescript/lib/lib.es2021.string.d.ts +0 -33
  205. package/skills/jitter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +0 -78
  206. package/skills/jitter/node_modules/typescript/lib/lib.es2022.array.d.ts +0 -121
  207. package/skills/jitter/node_modules/typescript/lib/lib.es2022.d.ts +0 -25
  208. package/skills/jitter/node_modules/typescript/lib/lib.es2022.error.d.ts +0 -75
  209. package/skills/jitter/node_modules/typescript/lib/lib.es2022.full.d.ts +0 -24
  210. package/skills/jitter/node_modules/typescript/lib/lib.es2022.intl.d.ts +0 -145
  211. package/skills/jitter/node_modules/typescript/lib/lib.es2022.object.d.ts +0 -26
  212. package/skills/jitter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +0 -39
  213. package/skills/jitter/node_modules/typescript/lib/lib.es2022.string.d.ts +0 -25
  214. package/skills/jitter/node_modules/typescript/lib/lib.es2023.array.d.ts +0 -924
  215. package/skills/jitter/node_modules/typescript/lib/lib.es2023.collection.d.ts +0 -21
  216. package/skills/jitter/node_modules/typescript/lib/lib.es2023.d.ts +0 -22
  217. package/skills/jitter/node_modules/typescript/lib/lib.es2023.full.d.ts +0 -24
  218. package/skills/jitter/node_modules/typescript/lib/lib.es2023.intl.d.ts +0 -56
  219. package/skills/jitter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +0 -65
  220. package/skills/jitter/node_modules/typescript/lib/lib.es2024.collection.d.ts +0 -29
  221. package/skills/jitter/node_modules/typescript/lib/lib.es2024.d.ts +0 -26
  222. package/skills/jitter/node_modules/typescript/lib/lib.es2024.full.d.ts +0 -24
  223. package/skills/jitter/node_modules/typescript/lib/lib.es2024.object.d.ts +0 -29
  224. package/skills/jitter/node_modules/typescript/lib/lib.es2024.promise.d.ts +0 -35
  225. package/skills/jitter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +0 -25
  226. package/skills/jitter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +0 -68
  227. package/skills/jitter/node_modules/typescript/lib/lib.es2024.string.d.ts +0 -29
  228. package/skills/jitter/node_modules/typescript/lib/lib.es5.d.ts +0 -4601
  229. package/skills/jitter/node_modules/typescript/lib/lib.es6.d.ts +0 -23
  230. package/skills/jitter/node_modules/typescript/lib/lib.esnext.array.d.ts +0 -35
  231. package/skills/jitter/node_modules/typescript/lib/lib.esnext.collection.d.ts +0 -96
  232. package/skills/jitter/node_modules/typescript/lib/lib.esnext.d.ts +0 -29
  233. package/skills/jitter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +0 -28
  234. package/skills/jitter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +0 -193
  235. package/skills/jitter/node_modules/typescript/lib/lib.esnext.error.d.ts +0 -24
  236. package/skills/jitter/node_modules/typescript/lib/lib.esnext.float16.d.ts +0 -443
  237. package/skills/jitter/node_modules/typescript/lib/lib.esnext.full.d.ts +0 -24
  238. package/skills/jitter/node_modules/typescript/lib/lib.esnext.intl.d.ts +0 -21
  239. package/skills/jitter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +0 -148
  240. package/skills/jitter/node_modules/typescript/lib/lib.esnext.promise.d.ts +0 -34
  241. package/skills/jitter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +0 -25
  242. package/skills/jitter/node_modules/typescript/lib/lib.scripthost.d.ts +0 -322
  243. package/skills/jitter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +0 -41
  244. package/skills/jitter/node_modules/typescript/lib/lib.webworker.d.ts +0 -13150
  245. package/skills/jitter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +0 -23
  246. package/skills/jitter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +0 -340
  247. package/skills/jitter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
  248. package/skills/jitter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
  249. package/skills/jitter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
  250. package/skills/jitter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
  251. package/skills/jitter/node_modules/typescript/lib/tsc.js +0 -8
  252. package/skills/jitter/node_modules/typescript/lib/tsserver.js +0 -8
  253. package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.d.ts +0 -17
  254. package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.js +0 -21
  255. package/skills/jitter/node_modules/typescript/lib/typesMap.json +0 -497
  256. package/skills/jitter/node_modules/typescript/lib/typescript.d.ts +0 -11438
  257. package/skills/jitter/node_modules/typescript/lib/typescript.js +0 -200253
  258. package/skills/jitter/node_modules/typescript/lib/typingsInstaller.js +0 -8
  259. package/skills/jitter/node_modules/typescript/lib/watchGuard.js +0 -53
  260. package/skills/jitter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
  261. package/skills/jitter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
  262. package/skills/jitter/node_modules/typescript/node_modules/.bin/tsc +0 -21
  263. package/skills/jitter/node_modules/typescript/node_modules/.bin/tsserver +0 -21
  264. package/skills/jitter/node_modules/typescript/package.json +0 -120
@@ -99,6 +99,7 @@ export function hasAssistantMessageCompletedBefore({ events, sessionId, messageI
99
99
  }
100
100
  export function getLatestUserMessage({ events, sessionId, upToIndex, }) {
101
101
  const end = upToIndex ?? events.length - 1;
102
+ let latestUserMessage;
102
103
  for (let i = end; i >= 0; i--) {
103
104
  const entry = events[i];
104
105
  if (!entry) {
@@ -112,9 +113,15 @@ export function getLatestUserMessage({ events, sessionId, upToIndex, }) {
112
113
  if (info.sessionID !== sessionId || info.role !== 'user') {
113
114
  continue;
114
115
  }
115
- return info;
116
+ if (!latestUserMessage) {
117
+ latestUserMessage = info;
118
+ continue;
119
+ }
120
+ if (info.time.created > latestUserMessage.time.created) {
121
+ latestUserMessage = info;
122
+ }
116
123
  }
117
- return undefined;
124
+ return latestUserMessage;
118
125
  }
119
126
  export function getCurrentTurnStartTime({ events, sessionId, upToIndex, }) {
120
127
  const latestUserMessage = getLatestUserMessage({
@@ -202,6 +209,7 @@ export function getLatestAssistantMessageIdForLatestUserTurn({ events, sessionId
202
209
  return undefined;
203
210
  }
204
211
  const end = upToIndex ?? events.length - 1;
212
+ let latestAssistantMessage;
205
213
  for (let i = end; i >= 0; i--) {
206
214
  const entry = events[i];
207
215
  if (!entry) {
@@ -215,11 +223,74 @@ export function getLatestAssistantMessageIdForLatestUserTurn({ events, sessionId
215
223
  if (info.sessionID !== sessionId || info.role !== 'assistant') {
216
224
  continue;
217
225
  }
218
- if (info.parentID === latestUserMessage.id) {
219
- return info.id;
226
+ if (info.parentID !== latestUserMessage.id) {
227
+ continue;
228
+ }
229
+ if (!latestAssistantMessage) {
230
+ latestAssistantMessage = info;
231
+ continue;
232
+ }
233
+ if (info.time.created > latestAssistantMessage.time.created) {
234
+ latestAssistantMessage = info;
220
235
  }
221
236
  }
222
- return undefined;
237
+ return latestAssistantMessage?.id;
238
+ }
239
+ function hasRenderablePartSummary(message) {
240
+ if (!('partsSummary' in message) || !Array.isArray(message.partsSummary)) {
241
+ return false;
242
+ }
243
+ return message.partsSummary.some((part) => {
244
+ return part.type === 'text' || part.type === 'tool';
245
+ });
246
+ }
247
+ function hasAssistantPartEvidence({ events, sessionId, messageId, upToIndex, }) {
248
+ const end = upToIndex ?? events.length - 1;
249
+ for (let i = end; i >= 0; i--) {
250
+ const entry = events[i];
251
+ if (!entry) {
252
+ continue;
253
+ }
254
+ const event = entry.event;
255
+ if (event.type === 'message.updated') {
256
+ const info = event.properties.info;
257
+ if (info.sessionID !== sessionId || info.role !== 'assistant' || info.id !== messageId) {
258
+ continue;
259
+ }
260
+ if (hasRenderablePartSummary(info)) {
261
+ return true;
262
+ }
263
+ continue;
264
+ }
265
+ if (event.type !== 'message.part.updated') {
266
+ continue;
267
+ }
268
+ const { part } = event.properties;
269
+ if (part.messageID !== messageId) {
270
+ continue;
271
+ }
272
+ if (part.type === 'text' || part.type === 'tool') {
273
+ return true;
274
+ }
275
+ }
276
+ return false;
277
+ }
278
+ function hasAssistantStepFinished({ events, messageId, upToIndex, }) {
279
+ const end = upToIndex ?? events.length - 1;
280
+ for (let i = end; i >= 0; i--) {
281
+ const entry = events[i];
282
+ if (!entry || entry.event.type !== 'message.part.updated') {
283
+ continue;
284
+ }
285
+ const { part } = entry.event.properties;
286
+ if (part.messageID !== messageId) {
287
+ continue;
288
+ }
289
+ if (part.type === 'step-finish') {
290
+ return true;
291
+ }
292
+ }
293
+ return false;
223
294
  }
224
295
  export function doesLatestUserTurnHaveNaturalCompletion({ events, sessionId, upToIndex, }) {
225
296
  const latestAssistantMessageId = getLatestAssistantMessageIdForLatestUserTurn({
@@ -231,6 +302,7 @@ export function doesLatestUserTurnHaveNaturalCompletion({ events, sessionId, upT
231
302
  return false;
232
303
  }
233
304
  const end = upToIndex ?? events.length - 1;
305
+ let latestAssistantMessage;
234
306
  for (let i = end; i >= 0; i--) {
235
307
  const entry = events[i];
236
308
  if (!entry) {
@@ -247,9 +319,31 @@ export function doesLatestUserTurnHaveNaturalCompletion({ events, sessionId, upT
247
319
  if (info.id !== latestAssistantMessageId) {
248
320
  continue;
249
321
  }
250
- return isAssistantMessageNaturalCompletion({ message: info });
322
+ latestAssistantMessage = info;
323
+ if (isAssistantMessageNaturalCompletion({ message: info })) {
324
+ return true;
325
+ }
326
+ break;
251
327
  }
252
- return false;
328
+ if (!latestAssistantMessage) {
329
+ return false;
330
+ }
331
+ if (latestAssistantMessage.error) {
332
+ return false;
333
+ }
334
+ if (latestAssistantMessage.finish === 'tool-calls') {
335
+ return false;
336
+ }
337
+ return hasAssistantStepFinished({
338
+ events,
339
+ messageId: latestAssistantMessageId,
340
+ upToIndex,
341
+ }) && hasAssistantPartEvidence({
342
+ events,
343
+ sessionId,
344
+ messageId: latestAssistantMessageId,
345
+ upToIndex,
346
+ });
253
347
  }
254
348
  export function isAssistantMessageInLatestUserTurn({ events, sessionId, messageId, upToIndex, }) {
255
349
  const assistantMessageIds = getAssistantMessageIdsForLatestUserTurn({
@@ -4,7 +4,7 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { describe, expect, test } from 'vitest';
6
6
  import { getOpencodeEventSessionId, } from './opencode-session-event-log.js';
7
- import { getAssistantMessageIdsForLatestUserTurn, getCurrentTurnStartTime, getDerivedSubtaskIndex, getLatestAssistantMessageIdForLatestUserTurn, getLatestRunInfo, hasAssistantMessageCompletedBefore, isAssistantMessageInLatestUserTurn, isAssistantMessageNaturalCompletion, isSessionBusy, } from './event-stream-state.js';
7
+ import { getAssistantMessageIdsForLatestUserTurn, getCurrentTurnStartTime, getDerivedSubtaskIndex, getLatestAssistantMessageIdForLatestUserTurn, getLatestRunInfo, hasAssistantMessageCompletedBefore, doesLatestUserTurnHaveNaturalCompletion, isAssistantMessageInLatestUserTurn, isAssistantMessageNaturalCompletion, isSessionBusy, } from './event-stream-state.js';
8
8
  const fixturesDir = path.join(import.meta.dirname, 'event-stream-fixtures');
9
9
  function loadFixture(filename) {
10
10
  const content = fs.readFileSync(path.join(fixturesDir, filename), 'utf8');
@@ -187,7 +187,11 @@ describe('session-concurrent-messages-serialized', () => {
187
187
  events,
188
188
  sessionId,
189
189
  });
190
- test('fixture ends idle and latest assistant completed naturally', () => {
190
+ test('fixture latest turn is still incomplete even though an older turn completed', () => {
191
+ expect(doesLatestUserTurnHaveNaturalCompletion({
192
+ events,
193
+ sessionId,
194
+ })).toBe(false);
191
195
  if (!latestAssistantMessageId) {
192
196
  throw new Error('Expected latest assistant message');
193
197
  }
@@ -196,7 +200,7 @@ describe('session-concurrent-messages-serialized', () => {
196
200
  sessionId,
197
201
  messageId: latestAssistantMessageId,
198
202
  });
199
- expect(isAssistantMessageNaturalCompletion({ message })).toBe(true);
203
+ expect(message.id).toBe(latestAssistantMessageId);
200
204
  });
201
205
  });
202
206
  describe('session-tool-call-noisy-stream', () => {
@@ -194,7 +194,7 @@ function getTokenTotal(tokens) {
194
194
  tokens.cache.write);
195
195
  }
196
196
  /** Check if a tool part is "essential" (shown in text-and-essential-tools mode). */
197
- function isEssentialToolName(toolName) {
197
+ export function isEssentialToolName(toolName) {
198
198
  const essentialTools = [
199
199
  'edit',
200
200
  'write',
@@ -213,7 +213,7 @@ function isEssentialToolName(toolName) {
213
213
  return toolName === name || toolName.endsWith(`_${name}`);
214
214
  });
215
215
  }
216
- function isEssentialToolPart(part) {
216
+ export function isEssentialToolPart(part) {
217
217
  if (part.type !== 'tool') {
218
218
  return false;
219
219
  }
@@ -630,10 +630,26 @@ export class ThreadSessionRuntime {
630
630
  // with every tool call and was the primary OOM vector — 1000 buffer entries
631
631
  // each carrying the full cumulative parts array reached 4GB+.
632
632
  const info = compacted.properties.info;
633
+ const partsSummary = Array.isArray(info.parts)
634
+ ? info.parts.flatMap((part) => {
635
+ if (!part || typeof part !== 'object') {
636
+ return [];
637
+ }
638
+ const candidate = part;
639
+ if (typeof candidate.id !== 'string'
640
+ || typeof candidate.type !== 'string') {
641
+ return [];
642
+ }
643
+ return [{ id: candidate.id, type: candidate.type }];
644
+ })
645
+ : [];
633
646
  delete info.system;
634
647
  delete info.summary;
635
648
  delete info.tools;
636
649
  delete info.parts;
650
+ if (partsSummary.length > 0) {
651
+ info.partsSummary = partsSummary;
652
+ }
637
653
  return this.finalizeCompactedEventForEventBuffer(compacted);
638
654
  }
639
655
  if (compacted.type !== 'message.part.updated') {
@@ -1442,6 +1458,7 @@ export class ThreadSessionRuntime {
1442
1458
  sessionId: request.sessionId,
1443
1459
  directory: request.directory,
1444
1460
  buttons: request.buttons,
1461
+ silent: this.getQueueLength() > 0,
1445
1462
  });
1446
1463
  });
1447
1464
  if (showResult instanceof Error) {
@@ -1775,6 +1792,7 @@ export class ThreadSessionRuntime {
1775
1792
  directory: this.projectDirectory,
1776
1793
  requestId: questionRequest.id,
1777
1794
  input: { questions: questionRequest.questions },
1795
+ silent: this.getQueueLength() > 0,
1778
1796
  });
1779
1797
  },
1780
1798
  });
@@ -1787,6 +1805,49 @@ export class ThreadSessionRuntime {
1787
1805
  return;
1788
1806
  }
1789
1807
  this.onInteractiveUiStateChanged();
1808
+ // When a question is answered and the local queue has items, the model may
1809
+ // continue the same run without ever reaching the local-queue idle gate.
1810
+ // Hand the queued items to OpenCode's own prompt queue immediately instead
1811
+ // of waiting for tryDrainQueue() to see an idle session.
1812
+ if (this.getQueueLength() > 0 && !this.questionReplyQueueHandoffPromise) {
1813
+ logger.log(`[QUESTION REPLIED] Queue has ${this.getQueueLength()} items, handing off to opencode queue`);
1814
+ this.questionReplyQueueHandoffPromise = this.handoffQueuedItemsAfterQuestionReply({
1815
+ sessionId,
1816
+ }).catch((error) => {
1817
+ logger.error('[QUESTION REPLIED] Failed to hand off queued messages:', error);
1818
+ if (error instanceof Error) {
1819
+ void notifyError(error, 'Failed to hand off queued messages after question reply');
1820
+ }
1821
+ }).finally(() => {
1822
+ this.questionReplyQueueHandoffPromise = null;
1823
+ });
1824
+ }
1825
+ }
1826
+ // Detached helper promise for the "question answered while local queue has
1827
+ // items" flow. Prevents starting two overlapping local->opencode queue
1828
+ // handoff sequences when multiple question replies land close together.
1829
+ questionReplyQueueHandoffPromise = null;
1830
+ async handoffQueuedItemsAfterQuestionReply({ sessionId, }) {
1831
+ if (this.listenerAborted) {
1832
+ return;
1833
+ }
1834
+ if (this.state?.sessionId !== sessionId) {
1835
+ logger.log(`[QUESTION REPLIED] Session changed before queue handoff for thread ${this.threadId}`);
1836
+ return;
1837
+ }
1838
+ while (this.state?.sessionId === sessionId) {
1839
+ const next = threadState.dequeueItem(this.threadId);
1840
+ if (!next) {
1841
+ return;
1842
+ }
1843
+ const displayText = next.command
1844
+ ? `/${next.command.name}`
1845
+ : `${next.prompt.slice(0, 150)}${next.prompt.length > 150 ? '...' : ''}`;
1846
+ if (displayText.trim()) {
1847
+ await sendThreadMessage(this.thread, `» **${next.username}:** ${displayText}`);
1848
+ }
1849
+ await this.submitViaOpencodeQueue(next);
1850
+ }
1790
1851
  }
1791
1852
  async handleSessionStatus(properties) {
1792
1853
  const sessionId = this.state?.sessionId;
@@ -2004,7 +2065,9 @@ export class ThreadSessionRuntime {
2004
2065
  })();
2005
2066
  let syntheticContext = '';
2006
2067
  if (input.username) {
2007
- syntheticContext += `<discord-user name="${input.username}" />`;
2068
+ const msgAttr = input.sourceMessageId ? ` message-id="${input.sourceMessageId}"` : '';
2069
+ const thrAttr = input.sourceThreadId ? ` thread-id="${input.sourceThreadId}"` : '';
2070
+ syntheticContext += `<discord-user name="${input.username}"${msgAttr}${thrAttr} />`;
2008
2071
  }
2009
2072
  const parts = [
2010
2073
  { type: 'text', text: promptWithImagePaths },
@@ -2112,6 +2175,8 @@ export class ThreadSessionRuntime {
2112
2175
  agent: input.agent,
2113
2176
  model: input.model,
2114
2177
  permissions: input.permissions,
2178
+ sourceMessageId: input.sourceMessageId,
2179
+ sourceThreadId: input.sourceThreadId,
2115
2180
  sessionStartScheduleKind: input.sessionStartSource?.scheduleKind,
2116
2181
  sessionStartScheduledTaskId: input.sessionStartSource?.scheduledTaskId,
2117
2182
  };
@@ -2281,10 +2346,50 @@ export class ThreadSessionRuntime {
2281
2346
  return this.tryDrainQueue({ showIndicator: true });
2282
2347
  });
2283
2348
  }
2349
+ async abortActiveRunAndWait({ reason, timeoutMs = 2_000, }) {
2350
+ const state = this.state;
2351
+ const sessionId = state?.sessionId;
2352
+ if (!sessionId) {
2353
+ return;
2354
+ }
2355
+ let needsIdleWait = false;
2356
+ const waitSinceTimestamp = Date.now();
2357
+ const abortResult = await errore.tryAsync(() => {
2358
+ return this.dispatchAction(async () => {
2359
+ needsIdleWait = this.isMainSessionBusy();
2360
+ const outcome = this.abortActiveRunInternal({ reason });
2361
+ if (outcome.apiAbortPromise) {
2362
+ void outcome.apiAbortPromise;
2363
+ }
2364
+ });
2365
+ });
2366
+ if (abortResult instanceof Error) {
2367
+ logger.error(`[ABORT WAIT] Failed to abort active run: ${abortResult.message}`);
2368
+ return;
2369
+ }
2370
+ if (!needsIdleWait) {
2371
+ return;
2372
+ }
2373
+ await this.waitForEvent({
2374
+ predicate: (event) => {
2375
+ return event.type === 'session.idle'
2376
+ && event.properties.sessionID === sessionId;
2377
+ },
2378
+ sinceTimestamp: waitSinceTimestamp,
2379
+ timeoutMs,
2380
+ });
2381
+ }
2284
2382
  /** Number of messages waiting in the queue. */
2285
2383
  getQueueLength() {
2286
2384
  return this.state?.queueItems.length ?? 0;
2287
2385
  }
2386
+ /** NOTIFY_MESSAGE_FLAGS unless queue has a next item, then SILENT.
2387
+ * Permissions should NOT use this — they always notify. */
2388
+ getNotifyFlags() {
2389
+ return this.getQueueLength() > 0
2390
+ ? SILENT_MESSAGE_FLAGS
2391
+ : NOTIFY_MESSAGE_FLAGS;
2392
+ }
2288
2393
  /** Clear all queued messages. */
2289
2394
  clearQueue() {
2290
2395
  threadState.clearQueueItems(this.threadId);
@@ -2501,7 +2606,9 @@ export class ThreadSessionRuntime {
2501
2606
  })();
2502
2607
  let syntheticContext = '';
2503
2608
  if (input.username) {
2504
- syntheticContext += `<discord-user name="${input.username}" />`;
2609
+ const msgAttr = input.sourceMessageId ? ` message-id="${input.sourceMessageId}"` : '';
2610
+ const thrAttr = input.sourceThreadId ? ` thread-id="${input.sourceThreadId}"` : '';
2611
+ syntheticContext += `<discord-user name="${input.username}"${msgAttr}${thrAttr} />`;
2505
2612
  }
2506
2613
  const parts = [
2507
2614
  { type: 'text', text: promptWithImagePaths },
@@ -2706,7 +2813,6 @@ export class ThreadSessionRuntime {
2706
2813
  }
2707
2814
  }
2708
2815
  if (!session) {
2709
- const sessionTitle = prompt.length > 80 ? prompt.slice(0, 77) + '...' : prompt.slice(0, 80);
2710
2816
  // Pass per-session external_directory permissions so this session can
2711
2817
  // access its own project directory (and worktree origin if applicable)
2712
2818
  // without prompts. These override the server-level 'ask' default via
@@ -2720,12 +2826,18 @@ export class ThreadSessionRuntime {
2720
2826
  }),
2721
2827
  ...parsePermissionRules(permissions ?? []),
2722
2828
  ];
2829
+ // Omit title so OpenCode auto-generates a summary from the conversation
2723
2830
  const sessionResponse = await getClient().session.create({
2724
- title: sessionTitle,
2725
2831
  directory: this.sdkDirectory,
2726
2832
  permission: sessionPermissions,
2727
2833
  });
2728
2834
  session = sessionResponse.data;
2835
+ // Insert DB row immediately so the external-sync poller sees
2836
+ // source='kimaki' before the next poll tick and skips this session.
2837
+ // The upsert at the end of ensureSession is kept for the reuse path.
2838
+ if (session) {
2839
+ await setThreadSession(this.thread.id, session.id);
2840
+ }
2729
2841
  createdNewSession = true;
2730
2842
  }
2731
2843
  if (!session) {
@@ -2813,7 +2925,7 @@ export class ThreadSessionRuntime {
2813
2925
  if (m.info.role !== 'assistant') {
2814
2926
  return false;
2815
2927
  }
2816
- if (!('tokens' in m.info) || !m.info.tokens) {
2928
+ if (!m.info.tokens) {
2817
2929
  return false;
2818
2930
  }
2819
2931
  return getTokenTotal(m.info.tokens) > 0;
@@ -2852,9 +2964,8 @@ export class ThreadSessionRuntime {
2852
2964
  this.stopTyping();
2853
2965
  // Skip notification if there's a queued message next — the user only
2854
2966
  // needs to be notified when the entire queue finishes.
2855
- const queuedNext = (threadState.getThreadState(this.threadId)?.queueItems.length ?? 0) > 0;
2856
2967
  await sendThreadMessage(this.thread, footerText, {
2857
- flags: queuedNext ? SILENT_MESSAGE_FLAGS : NOTIFY_MESSAGE_FLAGS,
2968
+ flags: this.getNotifyFlags(),
2858
2969
  });
2859
2970
  logger.log(`DURATION: Session completed in ${sessionDuration}, model ${runInfo.model}, tokens ${runInfo.tokensUsed}`);
2860
2971
  }
package/dist/store.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { createStore } from 'zustand/vanilla';
6
6
  export const store = createStore(() => ({
7
7
  dataDir: null,
8
+ projectsDir: null,
8
9
  defaultVerbosity: 'text_and_essential_tools',
9
10
  defaultMentionMode: false,
10
11
  critiqueEnabled: true,
@@ -54,6 +54,18 @@ bunx critique --web "Short title describing the changes" --filter "src/config.ts
54
54
 
55
55
  The string after \`--web\` becomes the diff page title — make it reflect what the changes do (e.g. "Add retry logic to API client", "Fix auth timeout bug").
56
56
 
57
+ ### fetching user comments from critique diffs
58
+
59
+ Users can add line-level comments (annotations) on any critique diff page via the Agentation widget (bottom-right corner of the diff page). To read those comments:
60
+
61
+ \`\`\`bash
62
+ curl https://critique.work/v/<id>/annotations
63
+ \`\`\`
64
+
65
+ Returns \`text/markdown\` with each annotation showing the file, line, and comment text.
66
+ Use this when the user says they left comments on a critique diff and you need to read them.
67
+ You can also use WebFetch on \`https://critique.work/v/<id>/annotations\` to get the markdown directly.
68
+
57
69
  ### about critique
58
70
 
59
71
  critique is an open source tool (MIT license) at https://github.com/remorses/critique.
@@ -131,7 +143,7 @@ Use random tunnel IDs by default. Only pass \`-t\` when exposing a service that
131
143
  tmux new-session -d -s myapp-dev
132
144
 
133
145
  # Run the dev server with kimaki tunnel inside the session
134
- tmux send-keys -t myapp-dev "kimaki tunnel -p 3000 -- pnpm dev" Enter
146
+ tmux send-keys -t myapp-dev "kimaki tunnel --kill -p 3000 -- pnpm dev" Enter
135
147
  \`\`\`
136
148
 
137
149
  ### getting the tunnel URL
@@ -146,15 +158,15 @@ tmux capture-pane -t myapp-dev -p | grep -i "tunnel"
146
158
  \`\`\`bash
147
159
  # Next.js project
148
160
  tmux new-session -d -s projectname-nextjs-dev-3000
149
- tmux send-keys -t nextjs-dev "kimaki tunnel -p 3000 -- pnpm dev" Enter
161
+ tmux send-keys -t nextjs-dev "kimaki tunnel --kill -p 3000 -- pnpm dev" Enter
150
162
 
151
163
  # Vite project on port 5173
152
164
  tmux new-session -d -s vite-dev-5173
153
- tmux send-keys -t vite-dev "kimaki tunnel -p 5173 -- pnpm dev" Enter
165
+ tmux send-keys -t vite-dev "kimaki tunnel --kill -p 5173 -- pnpm dev" Enter
154
166
 
155
167
  # Custom tunnel ID (only for intentionally public-safe services)
156
168
  tmux new-session -d -s holocron-dev
157
- tmux send-keys -t holocron-dev "kimaki tunnel -p 3000 -t holocron -- pnpm dev" Enter
169
+ tmux send-keys -t holocron-dev "kimaki tunnel --kill -p 3000 -t holocron -- pnpm dev" Enter
158
170
  \`\`\`
159
171
 
160
172
  ### stopping the dev server
@@ -173,6 +185,12 @@ tmux kill-session -t myapp-dev
173
185
  tmux list-sessions
174
186
  \`\`\`
175
187
  `;
188
+ export function isInjectedPromptMarker({ marker, }) {
189
+ if (!marker) {
190
+ return false;
191
+ }
192
+ return Boolean(marker.cliThreadPrompt || marker.start);
193
+ }
176
194
  export function getOpencodeSystemMessage({ sessionId, channelId, guildId, threadId, worktree, channelTopic, username, userId, agents, currentAgent, }) {
177
195
  const agentFlag = currentAgent ? ` --agent ${currentAgent}` : '';
178
196
  const topicContext = channelTopic?.trim()
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { isInjectedPromptMarker } from './system-message.js';
3
+ describe('isInjectedPromptMarker', () => {
4
+ test('treats start markers as injected prompts', () => {
5
+ expect(isInjectedPromptMarker({
6
+ marker: { start: true },
7
+ })).toBe(true);
8
+ });
9
+ test('treats cli thread prompt markers as injected prompts', () => {
10
+ expect(isInjectedPromptMarker({
11
+ marker: { cliThreadPrompt: true },
12
+ })).toBe(true);
13
+ });
14
+ test('ignores messages without an injection marker', () => {
15
+ expect(isInjectedPromptMarker({
16
+ marker: { scheduledKind: 'cron', scheduledTaskId: 1 },
17
+ })).toBe(false);
18
+ });
19
+ });
@@ -21,7 +21,7 @@ function parseMessageId(value) {
21
21
  }
22
22
  async function executeThreadScheduledTask({ rest, task, payload, }) {
23
23
  const marker = {
24
- cliThreadPrompt: true,
24
+ start: true,
25
25
  scheduledKind: task.schedule_kind,
26
26
  scheduledTaskId: task.id,
27
27
  ...(payload.agent ? { agent: payload.agent } : {}),
@@ -394,20 +394,15 @@ e2eTest('thread message queue ordering', () => {
394
394
  await waitForFooterMessage({
395
395
  discord,
396
396
  threadId: thread.id,
397
- timeout: 4_000,
397
+ timeout: 8_000,
398
398
  afterMessageIncludes: 'beta',
399
399
  afterAuthorId: TEST_USER_ID,
400
400
  });
401
- expect(await th.text()).toMatchInlineSnapshot(`
402
- "--- from: user (queue-tester)
403
- Reply with exactly: alpha
404
- --- from: assistant (TestBot)
405
- ok
406
- --- from: user (queue-tester)
407
- Reply with exactly: beta
408
- --- from: assistant (TestBot)
409
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
410
- `);
401
+ const timeline = await th.text();
402
+ expect(timeline).toContain('Reply with exactly: alpha');
403
+ expect(timeline).toContain('Reply with exactly: beta');
404
+ expect(timeline).toContain('⬥ ok');
405
+ expect(timeline).toContain('*project ⋅ main ⋅');
411
406
  // User B's message must appear before the new bot response
412
407
  const userBIndex = after.findIndex((m) => {
413
408
  return (m.author.id === TEST_USER_ID &&
@@ -422,7 +417,7 @@ e2eTest('thread message queue ordering', () => {
422
417
  // New bot response has non-empty content
423
418
  const newBotReply = afterBotMessages[afterBotMessages.length - 1];
424
419
  expect(newBotReply.content.trim().length).toBeGreaterThan(0);
425
- }, 8_000);
420
+ }, 12_000);
426
421
  test('two rapid text messages in thread — both processed in order', async () => {
427
422
  // 1. Send initial message to text channel → thread + session established
428
423
  await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
@@ -820,11 +815,10 @@ e2eTest('thread message queue ordering', () => {
820
815
  Reply with exactly: echo
821
816
  --- from: assistant (TestBot)
822
817
  *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
818
+ ⬥ ok
823
819
  --- from: user (queue-tester)
824
820
  Reply with exactly: foxtrot
825
821
  --- from: assistant (TestBot)
826
- ⬥ ok
827
- ⬥ ok
828
822
  *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
829
823
  `);
830
824
  expect(userEchoIndex).toBeGreaterThan(-1);
package/dist/tools.js CHANGED
@@ -106,7 +106,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
106
106
  }
107
107
  try {
108
108
  const session = await getClient().session.create({
109
- title: title || message.slice(0, 50),
109
+ ...(title ? { title } : {}),
110
110
  });
111
111
  if (!session.data) {
112
112
  throw new Error('Failed to create session');