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
@@ -2,32 +2,12 @@
2
2
  // Backed by the `libsql` npm package (better-sqlite3 API).
3
3
  // Binds to the fixed lock port for single-instance enforcement.
4
4
  //
5
- // Serves POST /v2/pipeline (Hrana v2 JSON), GET /v2, and GET /health.
6
- // The @libsql/client HTTP driver and @prisma/adapter-libsql connect here.
5
+ // Protocol logic is implemented in the `libsqlproxy` package.
6
+ // This file handles: server lifecycle, single-instance enforcement,
7
+ // auth, and kimaki-specific endpoints (/kimaki/wake, /health).
7
8
  //
8
9
  // Hrana v2 protocol spec ("Hrana over HTTP"):
9
10
  // https://github.com/tursodatabase/libsql/blob/main/docs/HTTP_V2_SPEC.md
10
- //
11
- // The protocol exposes stateful streams over HTTP. Each stream corresponds
12
- // to a SQLite connection. Requests on the same stream are tied together
13
- // via a "baton" — the server returns a baton in every response, and the
14
- // client includes it in the next request. Stream-scoped state includes
15
- // SQL text cached via store_sql (referenced by sql_id in later stmts).
16
- //
17
- // Request types implemented:
18
- // execute — run a single SQL statement, return cols/rows/changes
19
- // batch — run multiple steps with conditional execution (ok/not/and/or)
20
- // sequence — split raw SQL by semicolons, execute each (no results)
21
- // store_sql — cache SQL text under a numeric sql_id for the stream
22
- // close_sql — remove a cached sql_id
23
- // close — close the stream (baton becomes null)
24
- //
25
- // Value encoding (SQLite → Hrana JSON):
26
- // INTEGER → {"type":"integer","value":"42"} (string, not number)
27
- // REAL → {"type":"float","value":3.14}
28
- // TEXT → {"type":"text","value":"hello"}
29
- // BLOB → {"type":"blob","base64":"..."}
30
- // NULL → {"type":"null"}
31
11
 
32
12
  import fs from 'node:fs'
33
13
  import http from 'node:http'
@@ -35,6 +15,11 @@ import path from 'node:path'
35
15
  import crypto from 'node:crypto'
36
16
  import Database from 'libsql'
37
17
  import * as errore from 'errore'
18
+ import {
19
+ createLibsqlHandler,
20
+ createLibsqlNodeHandler,
21
+ libsqlExecutor,
22
+ } from 'libsqlproxy'
38
23
  import { createLogger, LogPrefix } from './logger.js'
39
24
  import { ServerStartError, FetchError } from './errors.js'
40
25
  import { getLockPort } from './config.js'
@@ -156,9 +141,11 @@ export async function startHranaServer({
156
141
  database.exec('PRAGMA busy_timeout = 5000')
157
142
  db = database
158
143
 
159
- const hranaHandler = createHranaHandler(database)
144
+ // Create the Hrana handler using libsqlproxy
145
+ const hranaFetchHandler = createLibsqlHandler(libsqlExecutor(database))
146
+ const hranaNodeHandler = createLibsqlNodeHandler(hranaFetchHandler)
160
147
 
161
- // Combined handler: all control/data routes require the same service auth token.
148
+ // Combined handler: kimaki-specific endpoints + hrana protocol
162
149
  const handler: http.RequestListener = async (req, res) => {
163
150
  const pathname = new URL(req.url || '/', 'http://localhost').pathname
164
151
  if (pathname === '/kimaki/wake') {
@@ -182,18 +169,20 @@ export async function startHranaServer({
182
169
  res.end(JSON.stringify({ ready: true }))
183
170
  return
184
171
  }
185
- // Hrana routes: /health, /v2, /v2/pipeline
172
+ // Health check no auth required
186
173
  if (pathname === '/health') {
187
- hranaHandler(req, res)
174
+ res.writeHead(200, { 'content-type': 'application/json' })
175
+ res.end(JSON.stringify({ status: 'ok', pid: process.pid }))
188
176
  return
189
177
  }
178
+ // Hrana routes: /v2, /v2/pipeline — require auth
190
179
  if (pathname === '/v2' || pathname === '/v2/pipeline') {
191
180
  if (!isAuthorizedRequest(req)) {
192
181
  res.writeHead(401, { 'content-type': 'application/json' })
193
182
  res.end(JSON.stringify({ error: 'unauthorized' }))
194
183
  return
195
184
  }
196
- hranaHandler(req, res)
185
+ hranaNodeHandler(req, res)
197
186
  return
198
187
  }
199
188
  res.writeHead(404)
@@ -253,368 +242,7 @@ export async function stopHranaServer() {
253
242
  hranaLogger.log('Hrana server stopped')
254
243
  }
255
244
 
256
- // ── Hrana v2 protocol types ──────────────────────────────────────────────
257
-
258
- type HranaValue =
259
- | { type: 'null' }
260
- | { type: 'integer'; value: string }
261
- | { type: 'float'; value: number }
262
- | { type: 'text'; value: string }
263
- | { type: 'blob'; base64: string }
264
-
265
- interface HranaStmt {
266
- sql?: string
267
- sql_id?: number
268
- args?: HranaValue[]
269
- named_args?: Array<{ name: string; value: HranaValue }>
270
- want_rows?: boolean
271
- }
272
-
273
- interface HranaCondition {
274
- type: 'ok' | 'not' | 'and' | 'or'
275
- step?: number
276
- cond?: HranaCondition
277
- conds?: HranaCondition[]
278
- }
279
-
280
- interface HranaBatchStep {
281
- stmt: HranaStmt
282
- condition?: HranaCondition | null
283
- }
284
-
285
- interface HranaRequest {
286
- type: string
287
- stmt?: HranaStmt
288
- batch?: { steps: HranaBatchStep[] }
289
- sql?: string
290
- sql_id?: number
291
- }
292
-
293
- interface HranaPipelineRequest {
294
- baton: string | null
295
- requests: HranaRequest[]
296
- }
297
-
298
- interface HranaColInfo {
299
- name: string
300
- decltype: string | null
301
- }
302
-
303
- interface HranaExecuteResult {
304
- cols: HranaColInfo[]
305
- rows: HranaValue[][]
306
- affected_row_count: number
307
- last_insert_rowid: string | null
308
- }
309
-
310
- // ── Value encoding/decoding ──────────────────────────────────────────────
311
-
312
- function encodeValue(val: unknown): HranaValue {
313
- if (val === null || val === undefined) return { type: 'null' }
314
- if (typeof val === 'bigint') return { type: 'integer', value: val.toString() }
315
- if (typeof val === 'number') {
316
- if (Number.isInteger(val)) return { type: 'integer', value: val.toString() }
317
- return { type: 'float', value: val }
318
- }
319
- if (typeof val === 'string') return { type: 'text', value: val }
320
- if (Buffer.isBuffer(val))
321
- return { type: 'blob', base64: val.toString('base64') }
322
- if (val instanceof Uint8Array)
323
- return { type: 'blob', base64: Buffer.from(val).toString('base64') }
324
- return { type: 'text', value: String(val) }
325
- }
326
-
327
- function decodeValue(val: HranaValue): unknown {
328
- if (val.type === 'null') return null
329
- if (val.type === 'integer') {
330
- const n = Number(val.value)
331
- return Number.isSafeInteger(n) ? n : BigInt(val.value)
332
- }
333
- if (val.type === 'float') return val.value
334
- if (val.type === 'text') return val.value
335
- if (val.type === 'blob') return Buffer.from(val.base64, 'base64')
336
- return null
337
- }
338
-
339
- // ── Statement execution ──────────────────────────────────────────────────
340
-
341
- // SqliteError from libsql has a `code` property but catch gives Error.
342
- function getSqliteErrorCode(err: Error): string {
343
- return (err as unknown as { code?: string }).code ?? 'SQLITE_ERROR'
344
- }
345
-
346
- function resolveStmtSql(
347
- stmt: HranaStmt,
348
- sqlStore: Map<number, string>,
349
- ): string {
350
- if (stmt.sql != null) return stmt.sql
351
- if (stmt.sql_id != null) return sqlStore.get(stmt.sql_id) ?? ''
352
- return ''
353
- }
354
-
355
- function bindParams(stmt: HranaStmt): unknown[] {
356
- if (stmt.named_args && stmt.named_args.length > 0) {
357
- const named: Record<string, unknown> = {}
358
- for (const na of stmt.named_args) {
359
- named[na.name] = decodeValue(na.value)
360
- }
361
- return [named]
362
- }
363
- return (stmt.args ?? []).map(decodeValue)
364
- }
365
-
366
- function executeStmt(
367
- database: Database.Database,
368
- stmt: HranaStmt,
369
- sqlStore: Map<number, string>,
370
- ): HranaExecuteResult {
371
- const sql = resolveStmtSql(stmt, sqlStore)
372
- const prepared = database.prepare(sql)
373
- const params = bindParams(stmt)
374
-
375
- if (prepared.reader) {
376
- const cols = prepared.columns()
377
- const rows = prepared.all(...params) as Record<string, unknown>[]
378
- return {
379
- cols: cols.map((c) => ({ name: c.name, decltype: c.type })),
380
- rows: rows.map((row) => cols.map((c) => encodeValue(row[c.name]))),
381
- affected_row_count: 0,
382
- last_insert_rowid: null,
383
- }
384
- }
385
-
386
- const result = prepared.run(...params)
387
- return {
388
- cols: [],
389
- rows: [],
390
- affected_row_count: result.changes,
391
- last_insert_rowid:
392
- result.lastInsertRowid != null ? result.lastInsertRowid.toString() : null,
393
- }
394
- }
395
-
396
- // ── Batch condition evaluation ───────────────────────────────────────────
397
-
398
- function evaluateCondition(
399
- cond: HranaCondition | null | undefined,
400
- stepResults: Array<HranaExecuteResult | null>,
401
- stepErrors: Array<{ message: string; code: string } | null>,
402
- ): boolean {
403
- if (!cond) return true
404
- if (cond.type === 'ok')
405
- return stepErrors[cond.step!] === null && stepResults[cond.step!] !== null
406
- if (cond.type === 'not')
407
- return !evaluateCondition(cond.cond, stepResults, stepErrors)
408
- if (cond.type === 'and')
409
- return (cond.conds ?? []).every((c) =>
410
- evaluateCondition(c, stepResults, stepErrors),
411
- )
412
- if (cond.type === 'or')
413
- return (cond.conds ?? []).some((c) =>
414
- evaluateCondition(c, stepResults, stepErrors),
415
- )
416
- return true
417
- }
418
-
419
- // ── Request handlers ─────────────────────────────────────────────────────
420
-
421
- function handleExecute(
422
- database: Database.Database,
423
- req: HranaRequest,
424
- sqlStore: Map<number, string>,
425
- ) {
426
- if (!req.stmt)
427
- return {
428
- type: 'error' as const,
429
- error: { message: 'Missing stmt', code: 'HRANA_PROTO_ERROR' },
430
- }
431
- const result = errore.try({
432
- try: () => executeStmt(database, req.stmt!, sqlStore),
433
- catch: (e) => e as Error,
434
- })
435
- if (result instanceof Error) {
436
- return {
437
- type: 'error' as const,
438
- error: { message: result.message, code: getSqliteErrorCode(result) },
439
- }
440
- }
441
- return { type: 'ok' as const, response: { type: 'execute', result } }
442
- }
443
-
444
- function handleBatch(
445
- database: Database.Database,
446
- req: HranaRequest,
447
- sqlStore: Map<number, string>,
448
- ) {
449
- const steps = req.batch?.steps ?? []
450
- const stepResults: Array<HranaExecuteResult | null> = []
451
- const stepErrors: Array<{ message: string; code: string } | null> = []
452
-
453
- for (const step of steps) {
454
- if (!evaluateCondition(step.condition, stepResults, stepErrors)) {
455
- stepResults.push(null)
456
- stepErrors.push(null)
457
- continue
458
- }
459
- const result = errore.try({
460
- try: () => executeStmt(database, step.stmt, sqlStore),
461
- catch: (e) => e as Error,
462
- })
463
- if (result instanceof Error) {
464
- stepResults.push(null)
465
- stepErrors.push({
466
- message: result.message,
467
- code: getSqliteErrorCode(result),
468
- })
469
- } else {
470
- stepResults.push(result)
471
- stepErrors.push(null)
472
- }
473
- }
474
-
475
- return {
476
- type: 'ok' as const,
477
- response: {
478
- type: 'batch',
479
- result: { step_results: stepResults, step_errors: stepErrors },
480
- },
481
- }
482
- }
483
-
484
- function handleSequence(
485
- database: Database.Database,
486
- req: HranaRequest,
487
- sqlStore: Map<number, string>,
488
- ) {
489
- const sql = req.sql ?? (req.sql_id != null ? sqlStore.get(req.sql_id) : null)
490
- if (!sql) return { type: 'ok' as const, response: { type: 'sequence' } }
491
- const result = errore.try({
492
- try: () => {
493
- database.exec(sql)
494
- },
495
- catch: (e) => e as Error,
496
- })
497
- if (result instanceof Error) {
498
- return {
499
- type: 'error' as const,
500
- error: { message: result.message, code: getSqliteErrorCode(result) },
501
- }
502
- }
503
- return { type: 'ok' as const, response: { type: 'sequence' } }
504
- }
505
-
506
- function processRequest(
507
- database: Database.Database,
508
- req: HranaRequest,
509
- sqlStore: Map<number, string>,
510
- ) {
511
- if (req.type === 'execute') return handleExecute(database, req, sqlStore)
512
- if (req.type === 'batch') return handleBatch(database, req, sqlStore)
513
- if (req.type === 'sequence') return handleSequence(database, req, sqlStore)
514
- if (req.type === 'close')
515
- return { type: 'ok' as const, response: { type: 'close' } }
516
- if (req.type === 'store_sql') {
517
- if (req.sql_id != null && req.sql != null) sqlStore.set(req.sql_id, req.sql)
518
- return { type: 'ok' as const, response: { type: 'store_sql' } }
519
- }
520
- if (req.type === 'close_sql') {
521
- if (req.sql_id != null) sqlStore.delete(req.sql_id)
522
- return { type: 'ok' as const, response: { type: 'close_sql' } }
523
- }
524
- return {
525
- type: 'error' as const,
526
- error: {
527
- message: `Unknown request type: ${req.type}`,
528
- code: 'HRANA_PROTO_ERROR',
529
- },
530
- }
531
- }
532
-
533
- // ── HTTP handler ─────────────────────────────────────────────────────────
534
-
535
- // @libsql/client HTTP driver uses batons to keep streams alive across
536
- // pipeline requests (needed for interactive transactions). Each stream has
537
- // its own SQL store for store_sql/close_sql scoping.
538
-
539
- let batonCounter = 0
540
- const streamStores = new Map<string, Map<number, string>>()
541
-
542
- export function createHranaHandler(
543
- database: Database.Database,
544
- ): http.RequestListener {
545
- return (req, res) => {
546
- const requestUrl = new URL(req.url || '/', 'http://127.0.0.1')
547
-
548
- if (req.method === 'GET' && requestUrl.pathname === '/health') {
549
- res.writeHead(200, { 'content-type': 'application/json' })
550
- res.end(JSON.stringify({ status: 'ok', pid: process.pid }))
551
- return
552
- }
553
- if (req.method === 'GET' && requestUrl.pathname === '/v2') {
554
- res.writeHead(200, { 'content-type': 'application/json' })
555
- res.end('{"version":"hrana-v2"}')
556
- return
557
- }
558
- if (req.method === 'POST' && requestUrl.pathname === '/v2/pipeline') {
559
- const chunks: Buffer[] = []
560
- let aborted = false
561
- req.on('error', () => {
562
- aborted = true
563
- res.destroy()
564
- })
565
- req.on('data', (chunk: Buffer) => {
566
- chunks.push(chunk)
567
- })
568
- req.on('end', () => {
569
- if (aborted) return
570
- const parseResult = errore.try({
571
- try: () =>
572
- JSON.parse(
573
- Buffer.concat(chunks).toString(),
574
- ) as HranaPipelineRequest,
575
- catch: (e) => e as Error,
576
- })
577
- if (parseResult instanceof Error) {
578
- res.writeHead(400, { 'content-type': 'application/json' })
579
- res.end(
580
- JSON.stringify({
581
- error: {
582
- message: parseResult.message,
583
- code: 'HRANA_PROTO_ERROR',
584
- },
585
- }),
586
- )
587
- return
588
- }
589
-
590
- // Resolve or create per-stream SQL store keyed by baton
591
- const incoming = parseResult.baton
592
- const sqlStore =
593
- (incoming ? streamStores.get(incoming) : undefined) ??
594
- new Map<number, string>()
595
- if (incoming) streamStores.delete(incoming)
596
-
597
- const results = (parseResult.requests ?? []).map((r) =>
598
- processRequest(database, r, sqlStore),
599
- )
600
- const hasClose = (parseResult.requests ?? []).some(
601
- (r) => r.type === 'close',
602
- )
603
-
604
- const baton = hasClose ? null : `b${++batonCounter}`
605
- if (baton) streamStores.set(baton, sqlStore)
606
-
607
- res.writeHead(200, { 'content-type': 'application/json' })
608
- res.end(JSON.stringify({ baton, base_url: null, results }))
609
- })
610
- return
611
- }
612
- res.writeHead(404)
613
- res.end()
614
- }
615
- }
616
-
617
- // ── Single-instance enforcement ──────────────────────────────────────────
245
+ // ── Single-instance enforcement ──────────────────────────────────────
618
246
 
619
247
  /**
620
248
  * Evict a previous kimaki instance on the lock port.
@@ -46,6 +46,7 @@ test(
46
46
  const port = chooseLockPort({ key: 'opencode-plugin-loading-e2e' })
47
47
  const pluginPath = new URL('../src/kimaki-opencode-plugin.ts', import.meta.url).href
48
48
  const stderrLines: string[] = []
49
+ const isolatedOpencodeRoot = path.join(projectDir, 'opencode-test-home')
49
50
 
50
51
  const {
51
52
  command,
@@ -68,6 +69,12 @@ test(
68
69
  formatter: false,
69
70
  plugin: [pluginPath],
70
71
  }),
72
+ OPENCODE_TEST_HOME: isolatedOpencodeRoot,
73
+ OPENCODE_CONFIG_DIR: path.join(isolatedOpencodeRoot, '.opencode-kimaki'),
74
+ XDG_CONFIG_HOME: path.join(isolatedOpencodeRoot, '.config'),
75
+ XDG_DATA_HOME: path.join(isolatedOpencodeRoot, '.local', 'share'),
76
+ XDG_CACHE_HOME: path.join(isolatedOpencodeRoot, '.cache'),
77
+ XDG_STATE_HOME: path.join(isolatedOpencodeRoot, '.local', 'state'),
71
78
  },
72
79
  })
73
80
 
@@ -7,8 +7,10 @@
7
7
  // - ipc-tools-plugin: file upload + action buttons (IPC-based Discord tools)
8
8
  // - context-awareness-plugin: branch, pwd, memory, time gap, onboarding tutorial
9
9
  // - opencode-interrupt-plugin: interrupt queued messages at step boundaries
10
+ // - kitty-graphics-plugin: extract Kitty Graphics Protocol images from bash output
10
11
 
11
12
  export { ipcToolsPlugin } from './ipc-tools-plugin.js'
12
13
  export { contextAwarenessPlugin } from './context-awareness-plugin.js'
13
14
  export { interruptOpencodeSessionOnUserMessage } from './opencode-interrupt-plugin.js'
14
15
  export { anthropicAuthPlugin } from './anthropic-auth-plugin.js'
16
+ export { kittyGraphicsPlugin } from 'kitty-graphics-agent'
@@ -131,7 +131,7 @@ beforeAll(async () => {
131
131
  const msgs = await client.session.messages({ sessionID })
132
132
  const assistantMsg = msgs.data?.find((m) => m.info.role === 'assistant')
133
133
  const hasTextParts = assistantMsg?.parts?.some((p) => {
134
- return p.type === 'text' && 'text' in p && p.text && !('synthetic' in p && p.synthetic)
134
+ return p.type === 'text' && p.text && !p.synthetic
135
135
  })
136
136
  if (hasTextParts) {
137
137
  // Extra wait for step-start and other parts to be flushed
package/src/markdown.ts CHANGED
@@ -315,8 +315,8 @@ export function getCompactSessionContext({
315
315
  for (const msg of recentMessages) {
316
316
  if (msg.info.role === 'user') {
317
317
  const textParts = (msg.parts || [])
318
- .filter((p) => p.type === 'text' && 'text' in p)
319
- .map((p) => ('text' in p ? extractNonXmlContent(p.text || '') : ''))
318
+ .filter((p) => p.type === 'text')
319
+ .map((p) => (p.type === 'text' ? extractNonXmlContent(p.text || '') : ''))
320
320
  .filter(Boolean)
321
321
  if (textParts.length > 0) {
322
322
  lines.push(`[User]: ${textParts.join(' ').slice(0, 1000)}`)
@@ -326,9 +326,9 @@ export function getCompactSessionContext({
326
326
  // Get assistant text parts (non-synthetic, non-empty)
327
327
  const textParts = (msg.parts || [])
328
328
  .filter(
329
- (p) => p.type === 'text' && 'text' in p && !p.synthetic && p.text,
329
+ (p) => p.type === 'text' && !p.synthetic && p.text,
330
330
  )
331
- .map((p) => ('text' in p ? p.text : ''))
331
+ .map((p) => (p.type === 'text' ? p.text : ''))
332
332
  .filter(Boolean)
333
333
  if (textParts.length > 0) {
334
334
  lines.push(`[Assistant]: ${textParts.join(' ').slice(0, 1000)}`)
@@ -71,36 +71,85 @@ function normalizeWhitespace(text: string): string {
71
71
  return text.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ')
72
72
  }
73
73
 
74
+ // A chunk of formatted content with associated part IDs, ready to be
75
+ // batched into as few Discord messages as possible.
76
+ export type SessionChunk = {
77
+ partIds: string[]
78
+ content: string
79
+ }
80
+
74
81
  /**
75
- * Collects and formats the last N assistant parts from session messages.
76
- * Used by both /resume and /fork to show recent assistant context.
82
+ * Collect renderable assistant parts from session messages as SessionChunks.
83
+ * Each non-empty formatted part becomes one chunk. Caller can batch them
84
+ * with batchChunksForDiscord() before sending.
85
+ *
86
+ * - skipPartIds: parts already synced (external sync). Skipped parts are
87
+ * not included in the result.
88
+ * - limit: max parts to include (from the end). Older parts are counted
89
+ * in skippedCount.
77
90
  */
78
- export function collectLastAssistantParts({
91
+ export function collectSessionChunks({
79
92
  messages,
80
- limit = 30,
93
+ skipPartIds,
94
+ limit,
81
95
  }: {
82
96
  messages: GenericSessionMessage[]
97
+ skipPartIds?: Set<string>
83
98
  limit?: number
84
- }): { partIds: string[]; content: string; skippedCount: number } {
85
- const allAssistantParts: { id: string; content: string }[] = []
99
+ }): { chunks: SessionChunk[]; skippedCount: number } {
100
+ const allChunks: SessionChunk[] = []
86
101
 
87
102
  for (const message of messages) {
88
- if (message.info.role === 'assistant') {
89
- for (const part of message.parts) {
90
- const content = formatPart(part)
91
- if (content.trim()) {
92
- allAssistantParts.push({ id: part.id, content: content.trimEnd() })
93
- }
103
+ if (message.info.role !== 'assistant') {
104
+ continue
105
+ }
106
+ for (const part of message.parts) {
107
+ if (skipPartIds?.has(part.id)) {
108
+ continue
109
+ }
110
+ const content = formatPart(part)
111
+ if (!content.trim()) {
112
+ continue
94
113
  }
114
+ allChunks.push({ partIds: [part.id], content: content.trimEnd() })
95
115
  }
96
116
  }
97
117
 
98
- const partsToRender = allAssistantParts.slice(-limit)
99
- const partIds = partsToRender.map((p) => p.id)
100
- const content = partsToRender.map((p) => p.content).join('\n')
101
- const skippedCount = allAssistantParts.length - partsToRender.length
118
+ if (limit !== undefined && allChunks.length > limit) {
119
+ return {
120
+ chunks: allChunks.slice(-limit),
121
+ skippedCount: allChunks.length - limit,
122
+ }
123
+ }
124
+ return { chunks: allChunks, skippedCount: 0 }
125
+ }
102
126
 
103
- return { partIds, content, skippedCount }
127
+ // Merge consecutive SessionChunks into as few Discord messages as possible,
128
+ // respecting the 2000 char limit.
129
+ const DISCORD_BATCH_MAX_LENGTH = 2000
130
+
131
+ export function batchChunksForDiscord(chunks: SessionChunk[]): SessionChunk[] {
132
+ if (chunks.length === 0) {
133
+ return []
134
+ }
135
+ const batched: SessionChunk[] = []
136
+ let current: SessionChunk = { partIds: [...chunks[0]!.partIds], content: chunks[0]!.content }
137
+
138
+ for (let i = 1; i < chunks.length; i++) {
139
+ const next = chunks[i]!
140
+ const merged = current.content + '\n' + next.content
141
+ if (merged.length <= DISCORD_BATCH_MAX_LENGTH) {
142
+ current = {
143
+ partIds: [...current.partIds, ...next.partIds],
144
+ content: merged,
145
+ }
146
+ } else {
147
+ batched.push(current)
148
+ current = { partIds: [...next.partIds], content: next.content }
149
+ }
150
+ }
151
+ batched.push(current)
152
+ return batched
104
153
  }
105
154
 
106
155
  export const TEXT_MIME_TYPES = [
@@ -146,7 +146,7 @@ ${backticks}bash
146
146
  PORT=$((RANDOM % 6000 + 3000))
147
147
  tmux kill-session -t game-dev 2>/dev/null
148
148
  tmux new-session -d -s game-dev -c "$PWD"
149
- tmux send-keys -t game-dev "PORT=$PORT kimaki tunnel -p $PORT -- bun run server.ts" Enter
149
+ tmux send-keys -t game-dev "PORT=$PORT kimaki tunnel --kill -p $PORT -- bun run server.ts" Enter
150
150
  ${backticks}
151
151
 
152
152
  Wait a moment, then get the tunnel URL:
@@ -250,15 +250,13 @@ export async function startGenAiSession({
250
250
  'conversation.item.created',
251
251
  ({ item }: { item: ConversationItem }) => {
252
252
  if (
253
- 'role' in item &&
254
253
  item.role === 'assistant' &&
255
254
  item.type === 'message'
256
255
  ) {
257
256
  // Check if this is the first audio content
258
257
  const hasAudio =
259
- 'content' in item &&
260
258
  Array.isArray(item.content) &&
261
- item.content.some((c) => 'type' in c && c.type === 'audio')
259
+ item.content.some((c) => c.type === 'audio')
262
260
  if (hasAudio && !isAssistantSpeaking && onAssistantStartSpeaking) {
263
261
  isAssistantSpeaking = true
264
262
  onAssistantStartSpeaking()
@@ -277,7 +275,7 @@ export async function startGenAiSession({
277
275
  delta: ConversationEventDelta | null
278
276
  }) => {
279
277
  // Handle audio chunks
280
- if (delta?.audio && 'role' in item && item.role === 'assistant') {
278
+ if (delta?.audio && item.role === 'assistant') {
281
279
  if (!isAssistantSpeaking && onAssistantStartSpeaking) {
282
280
  isAssistantSpeaking = true
283
281
  onAssistantStartSpeaking()
@@ -301,12 +299,10 @@ export async function startGenAiSession({
301
299
 
302
300
  // Handle transcriptions
303
301
  if (delta?.transcript) {
304
- if ('role' in item) {
305
- if (item.role === 'user') {
306
- openaiLogger.log('User transcription:', delta.transcript)
307
- } else if (item.role === 'assistant') {
308
- openaiLogger.log('Assistant transcription:', delta.transcript)
309
- }
302
+ if (item.role === 'user') {
303
+ openaiLogger.log('User transcription:', delta.transcript)
304
+ } else if (item.role === 'assistant') {
305
+ openaiLogger.log('Assistant transcription:', delta.transcript)
310
306
  }
311
307
  }
312
308
  },