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
@@ -19,6 +19,7 @@ import { execAsync } from '../worktrees.js'
19
19
  import type { WebSocketServer } from 'ws'
20
20
 
21
21
  const logger = createLogger('SCREEN')
22
+ const SECURE_REPLY_FLAGS = MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS
22
23
 
23
24
  export type ScreenshareSession = {
24
25
  tunnelClient: TunnelClient
@@ -37,8 +38,10 @@ export type ScreenshareSession = {
37
38
  const activeSessions = new Map<string, ScreenshareSession>()
38
39
 
39
40
  const VNC_PORT = 5900
40
- const MAX_SESSION_MS = 60 * 60 * 1000 // 1 hour
41
+ const MAX_SESSION_MINUTES = 30
42
+ const MAX_SESSION_MS = MAX_SESSION_MINUTES * 60 * 1000
41
43
  const TUNNEL_BASE_DOMAIN = 'kimaki.xyz'
44
+ const SCREENSHARE_TUNNEL_ID_BYTES = 16
42
45
 
43
46
  // Public noVNC client — we point it at our tunnel URL
44
47
  export function buildNoVncUrl({ tunnelHost }: { tunnelHost: string }): string {
@@ -53,6 +56,10 @@ export function buildNoVncUrl({ tunnelHost }: { tunnelHost: string }): string {
53
56
  return `https://novnc.com/noVNC/vnc.html?${params.toString()}`
54
57
  }
55
58
 
59
+ export function createScreenshareTunnelId(): string {
60
+ return crypto.randomBytes(SCREENSHARE_TUNNEL_ID_BYTES).toString('hex')
61
+ }
62
+
56
63
  // macOS has two separate services:
57
64
  // - "Screen Sharing" = view-only VNC (com.apple.screensharing)
58
65
  // - "Remote Management" = full control VNC with mouse/keyboard (ARDAgent)
@@ -212,7 +219,7 @@ export async function startScreenshare({
212
219
  }
213
220
 
214
221
  // Step 3: create tunnel
215
- const tunnelId = crypto.randomBytes(8).toString('hex')
222
+ const tunnelId = createScreenshareTunnelId()
216
223
  const tunnelClient = new TunnelClient({
217
224
  localPort: wsInstance.port,
218
225
  tunnelId,
@@ -241,9 +248,11 @@ export async function startScreenshare({
241
248
  const tunnelUrl = `https://${tunnelHost}`
242
249
  const noVncUrl = buildNoVncUrl({ tunnelHost })
243
250
 
244
- // Auto-kill after 1 hour
251
+ // Auto-kill after a short session so a leaked URL does not stay usable all day.
245
252
  const timeoutTimer = setTimeout(() => {
246
- logger.log(`Screen share auto-stopped after 1 hour (key: ${sessionKey})`)
253
+ logger.log(
254
+ `Screen share auto-stopped after ${MAX_SESSION_MINUTES} minutes (key: ${sessionKey})`,
255
+ )
247
256
  stopScreenshare({ sessionKey })
248
257
  }, MAX_SESSION_MS)
249
258
  // Don't keep the process alive just for this timer
@@ -292,7 +301,7 @@ export async function handleScreenshareCommand({
292
301
  return
293
302
  }
294
303
 
295
- await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
304
+ await command.deferReply({ flags: SECURE_REPLY_FLAGS })
296
305
 
297
306
  try {
298
307
  const session = await startScreenshare({
@@ -300,7 +309,10 @@ export async function handleScreenshareCommand({
300
309
  startedBy: command.user.tag,
301
310
  })
302
311
  await command.editReply({
303
- content: `Screen sharing started\n${session.noVncUrl}`,
312
+ content:
313
+ `Screen sharing started. This reply is private and the URL uses a high-entropy tunnel id. ` +
314
+ `It will auto-stop after ${MAX_SESSION_MINUTES} minutes. Use /screenshare-stop to stop sooner.\n` +
315
+ `${session.noVncUrl}`,
304
316
  })
305
317
  } catch (err) {
306
318
  logger.error('Failed to start screen share:', err)
@@ -17,7 +17,7 @@ export async function handleSessionCommand({
17
17
  command,
18
18
  appId,
19
19
  }: CommandContext): Promise<void> {
20
- await command.deferReply({ ephemeral: false })
20
+ await command.deferReply()
21
21
 
22
22
  const prompt = command.options.getString('prompt', true)
23
23
  const filesString = command.options.getString('files') || ''
@@ -6,6 +6,7 @@ import {
6
6
  type TextChannel,
7
7
  type ThreadChannel,
8
8
  } from 'discord.js'
9
+ import type { OpencodeClient } from '@opencode-ai/sdk/v2'
9
10
  import type { CommandContext } from './types.js'
10
11
  import { getThreadSession } from '../database.js'
11
12
  import { initializeOpencodeForDirectory } from '../opencode.js'
@@ -17,6 +18,30 @@ import { createLogger, LogPrefix } from '../logger.js'
17
18
 
18
19
  const logger = createLogger(LogPrefix.UNDO_REDO)
19
20
 
21
+ async function waitForSessionIdle({
22
+ client,
23
+ sessionId,
24
+ directory,
25
+ timeoutMs = 2_000,
26
+ }: {
27
+ client: OpencodeClient
28
+ sessionId: string
29
+ directory: string
30
+ timeoutMs?: number
31
+ }): Promise<void> {
32
+ const deadline = Date.now() + timeoutMs
33
+ while (Date.now() < deadline) {
34
+ const statusResponse = await client.session.status({ directory })
35
+ const sessionStatus = statusResponse.data?.[sessionId]
36
+ if (!sessionStatus || sessionStatus.type === 'idle') {
37
+ return
38
+ }
39
+ await new Promise((resolve) => {
40
+ setTimeout(resolve, 50)
41
+ })
42
+ }
43
+ }
44
+
20
45
  export async function handleUndoCommand({
21
46
  command,
22
47
  }: CommandContext): Promise<void> {
@@ -57,7 +82,7 @@ export async function handleUndoCommand({
57
82
  return
58
83
  }
59
84
 
60
- const { projectDirectory } = resolved
85
+ const { projectDirectory, workingDirectory } = resolved
61
86
 
62
87
  const sessionId = await getThreadSession(channel.id)
63
88
 
@@ -79,18 +104,41 @@ export async function handleUndoCommand({
79
104
 
80
105
  try {
81
106
  const client = getClient()
82
-
83
107
  // Fetch session to check existing revert state
84
108
  const sessionResponse = await client.session.get({
85
109
  sessionID: sessionId,
110
+ directory: workingDirectory,
86
111
  })
87
112
  if (sessionResponse.error) {
88
113
  await command.editReply(`Failed to undo: ${JSON.stringify(sessionResponse.error)}`)
89
114
  return
90
115
  }
91
116
 
117
+ // Abort if session is busy before reverting, matching TUI behavior
118
+ // (use-session-commands.tsx always aborts non-idle sessions before revert).
119
+ // session.status() returns a sparse map — only non-idle sessions have entries,
120
+ // so a missing key means idle.
121
+ const statusResponse = await client.session.status({
122
+ directory: workingDirectory,
123
+ })
124
+ const sessionStatus = statusResponse.data?.[sessionId]
125
+ if (sessionStatus && sessionStatus.type !== 'idle') {
126
+ await client.session.abort({
127
+ sessionID: sessionId,
128
+ directory: workingDirectory,
129
+ }).catch((error) => {
130
+ logger.warn(`[UNDO] abort failed for ${sessionId}`, error)
131
+ })
132
+ await waitForSessionIdle({
133
+ client,
134
+ sessionId,
135
+ directory: workingDirectory,
136
+ })
137
+ }
138
+
92
139
  const messagesResponse = await client.session.messages({
93
140
  sessionID: sessionId,
141
+ directory: workingDirectory,
94
142
  })
95
143
  if (messagesResponse.error) {
96
144
  await command.editReply(`Failed to undo: ${JSON.stringify(messagesResponse.error)}`)
@@ -119,21 +167,44 @@ export async function handleUndoCommand({
119
167
  return
120
168
  }
121
169
 
170
+ const targetAssistantMessage = [...messagesResponse.data].reverse().find((m) => {
171
+ return m.info.role === 'assistant' && m.info.parentID === targetUserMessage.info.id
172
+ })
173
+ const revertMessageId = targetAssistantMessage?.info.id || targetUserMessage.info.id
174
+
122
175
  // session.revert() reverts filesystem patches (file edits, writes) and
123
176
  // marks the session with revert.messageID. Messages are NOT deleted — they
124
177
  // get cleaned up automatically on the next promptAsync() call via
125
178
  // SessionRevert.cleanup(). The model only sees messages before the revert
126
179
  // point when processing the next prompt.
127
- const response = await client.session.revert({
180
+ logger.log(`[UNDO] session.revert start messageId=${revertMessageId}`)
181
+ let response = await client.session.revert({
128
182
  sessionID: sessionId,
129
- messageID: targetUserMessage.info.id,
183
+ directory: workingDirectory,
184
+ messageID: revertMessageId,
130
185
  })
186
+ logger.log(`[UNDO] session.revert done error=${Boolean(response.error)}`)
131
187
 
132
188
  if (response.error) {
133
- await command.editReply(
134
- `Failed to undo: ${JSON.stringify(response.error)}`,
135
- )
136
- return
189
+ logger.log('[UNDO] retry wait idle before revert retry')
190
+ await waitForSessionIdle({
191
+ client,
192
+ sessionId,
193
+ directory: workingDirectory,
194
+ })
195
+ logger.log('[UNDO] retry revert start')
196
+ response = await client.session.revert({
197
+ sessionID: sessionId,
198
+ directory: workingDirectory,
199
+ messageID: revertMessageId,
200
+ })
201
+ logger.log(`[UNDO] retry revert done error=${Boolean(response.error)}`)
202
+ if (response.error) {
203
+ await command.editReply(
204
+ `Failed to undo: ${JSON.stringify(response.error)}`,
205
+ )
206
+ return
207
+ }
137
208
  }
138
209
 
139
210
  const diffInfo = response.data?.revert?.diff
@@ -142,7 +213,7 @@ export async function handleUndoCommand({
142
213
 
143
214
  await command.editReply(`Undone - reverted last assistant message${diffInfo}`)
144
215
  logger.log(
145
- `Session ${sessionId} reverted to before user message ${targetUserMessage.info.id}`,
216
+ `Session ${sessionId} reverted at message ${revertMessageId}`,
146
217
  )
147
218
  } catch (error) {
148
219
  logger.error('[UNDO] Error:', error)
@@ -192,7 +263,7 @@ export async function handleRedoCommand({
192
263
  return
193
264
  }
194
265
 
195
- const { projectDirectory } = resolved
266
+ const { projectDirectory, workingDirectory } = resolved
196
267
 
197
268
  const sessionId = await getThreadSession(channel.id)
198
269
 
@@ -218,6 +289,7 @@ export async function handleRedoCommand({
218
289
  // Fetch session to check existing revert state
219
290
  const sessionResponse = await client.session.get({
220
291
  sessionID: sessionId,
292
+ directory: workingDirectory,
221
293
  })
222
294
  if (sessionResponse.error) {
223
295
  await command.editReply(`Failed to redo: ${JSON.stringify(sessionResponse.error)}`)
@@ -230,12 +302,36 @@ export async function handleRedoCommand({
230
302
  return
231
303
  }
232
304
 
305
+ // Abort if session is busy before reverting/unreverting — both enforce
306
+ // assertNotBusy in OpenCode and would fail with "Session is busy"
307
+ const redoStatusResponse = await client.session.status({
308
+ directory: workingDirectory,
309
+ })
310
+ const redoSessionStatus = redoStatusResponse.data?.[sessionId]
311
+ if (redoSessionStatus && redoSessionStatus.type !== 'idle') {
312
+ await client.session.abort({
313
+ sessionID: sessionId,
314
+ directory: workingDirectory,
315
+ }).catch((error) => {
316
+ logger.warn(`[REDO] abort failed for ${sessionId}`, error)
317
+ })
318
+ await waitForSessionIdle({
319
+ client,
320
+ sessionId,
321
+ directory: workingDirectory,
322
+ })
323
+ }
324
+ await new Promise((resolve) => {
325
+ setTimeout(resolve, 500)
326
+ })
327
+
233
328
  // Follow the same approach as the OpenCode TUI (use-session-commands.tsx):
234
329
  // find the next user message after the current revert point. If one exists,
235
330
  // move the revert cursor forward to it (one step redo). If none exists,
236
331
  // fully unrevert — we're at the end of the message history.
237
332
  const messagesResponse = await client.session.messages({
238
333
  sessionID: sessionId,
334
+ directory: workingDirectory,
239
335
  })
240
336
  if (messagesResponse.error) {
241
337
  await command.editReply(`Failed to redo: ${JSON.stringify(messagesResponse.error)}`)
@@ -252,6 +348,7 @@ export async function handleRedoCommand({
252
348
  // No more messages after revert point — fully unrevert
253
349
  const response = await client.session.unrevert({
254
350
  sessionID: sessionId,
351
+ directory: workingDirectory,
255
352
  })
256
353
  if (response.error) {
257
354
  await command.editReply(
@@ -267,6 +364,7 @@ export async function handleRedoCommand({
267
364
  // Move revert cursor forward one step to the next user message
268
365
  const response = await client.session.revert({
269
366
  sessionID: sessionId,
367
+ directory: workingDirectory,
270
368
  messageID: nextMessage.info.id,
271
369
  })
272
370
 
@@ -109,7 +109,7 @@ export const handleUserCommand: CommandHandler = async ({
109
109
  return
110
110
  }
111
111
 
112
- await command.deferReply({ ephemeral: false })
112
+ await command.deferReply()
113
113
 
114
114
  try {
115
115
  // Use the dedicated session.command API instead of formatting as text prompt
package/src/config.ts CHANGED
@@ -49,12 +49,30 @@ export function setDataDir(dir: string): void {
49
49
 
50
50
  /**
51
51
  * Get the projects directory path (for /create-new-project command).
52
- * Returns <dataDir>/projects
52
+ * Returns the custom --projects-dir if set, otherwise <dataDir>/projects.
53
53
  */
54
54
  export function getProjectsDir(): string {
55
+ const custom = store.getState().projectsDir
56
+ if (custom) {
57
+ return custom
58
+ }
55
59
  return path.join(getDataDir(), 'projects')
56
60
  }
57
61
 
62
+ /**
63
+ * Set a custom projects directory path (from --projects-dir CLI flag).
64
+ * Creates the directory if it doesn't exist.
65
+ */
66
+ export function setProjectsDir(dir: string): void {
67
+ const resolvedDir = path.resolve(dir)
68
+
69
+ if (!fs.existsSync(resolvedDir)) {
70
+ fs.mkdirSync(resolvedDir, { recursive: true })
71
+ }
72
+
73
+ store.setState({ projectsDir: resolvedDir })
74
+ }
75
+
58
76
  export type { RegisteredUserCommand } from './store.js'
59
77
 
60
78
  const DEFAULT_LOCK_PORT = 29988
package/src/database.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // API keys, and model preferences in <dataDir>/discord-sessions.db.
4
4
 
5
5
  import { getPrisma, closePrisma } from './db.js'
6
- import type { Prisma, session_events, BotMode, VerbosityLevel, WorktreeStatus, ChannelType as PrismaChannelType } from './generated/client.js'
6
+ import type { Prisma, session_events, BotMode, VerbosityLevel, WorktreeStatus, ChannelType as PrismaChannelType, ThreadSessionSource } from './generated/client.js'
7
7
  import crypto from 'node:crypto'
8
8
 
9
9
  import { store } from './store.js'
@@ -1015,14 +1015,48 @@ export async function setThreadSession(
1015
1015
  threadId: string,
1016
1016
  sessionId: string,
1017
1017
  ): Promise<void> {
1018
+ await upsertThreadSession({
1019
+ threadId,
1020
+ sessionId,
1021
+ source: 'kimaki',
1022
+ })
1023
+ }
1024
+
1025
+ export async function upsertThreadSession({
1026
+ threadId,
1027
+ sessionId,
1028
+ source,
1029
+ }: {
1030
+ threadId: string
1031
+ sessionId: string
1032
+ source: ThreadSessionSource
1033
+ }): Promise<void> {
1018
1034
  const prisma = await getPrisma()
1019
1035
  await prisma.thread_sessions.upsert({
1020
1036
  where: { thread_id: threadId },
1021
- create: { thread_id: threadId, session_id: sessionId },
1022
- update: { session_id: sessionId },
1037
+ create: {
1038
+ thread_id: threadId,
1039
+ session_id: sessionId,
1040
+ source,
1041
+ },
1042
+ update: {
1043
+ session_id: sessionId,
1044
+ source,
1045
+ },
1023
1046
  })
1024
1047
  }
1025
1048
 
1049
+ export async function getThreadSessionSource(
1050
+ threadId: string,
1051
+ ): Promise<ThreadSessionSource | undefined> {
1052
+ const prisma = await getPrisma()
1053
+ const row = await prisma.thread_sessions.findUnique({
1054
+ where: { thread_id: threadId },
1055
+ select: { source: true },
1056
+ })
1057
+ return row?.source
1058
+ }
1059
+
1026
1060
  /**
1027
1061
  * Get the thread ID for a session.
1028
1062
  */
@@ -1564,6 +1598,17 @@ export async function getAllTextChannelDirectories(): Promise<string[]> {
1564
1598
  return rows.map((row) => row.directory)
1565
1599
  }
1566
1600
 
1601
+ export async function listTrackedTextChannels(): Promise<
1602
+ Array<{ channel_id: string; directory: string; created_at: Date | null }>
1603
+ > {
1604
+ const prisma = await getPrisma()
1605
+ return prisma.channel_directories.findMany({
1606
+ where: { channel_type: 'text' },
1607
+ orderBy: [{ created_at: 'asc' }, { channel_id: 'asc' }],
1608
+ select: { channel_id: true, directory: true, created_at: true },
1609
+ })
1610
+ }
1611
+
1567
1612
  /**
1568
1613
  * Delete all channel directories for a specific directory.
1569
1614
  */
@@ -1576,6 +1621,30 @@ export async function deleteChannelDirectoriesByDirectory(
1576
1621
  })
1577
1622
  }
1578
1623
 
1624
+ /**
1625
+ * Delete a single channel_directories row and all its child rows
1626
+ * (channel_models, channel_agents, channel_worktrees, channel_verbosity,
1627
+ * channel_mention_mode) in a single transaction. scheduled_tasks has
1628
+ * onDelete:SetNull so Prisma handles it automatically.
1629
+ */
1630
+ export async function deleteChannelDirectoryById(
1631
+ channelId: string,
1632
+ ): Promise<boolean> {
1633
+ const prisma = await getPrisma()
1634
+ const deletedCount = await prisma.$transaction(async (tx) => {
1635
+ await tx.channel_models.deleteMany({ where: { channel_id: channelId } })
1636
+ await tx.channel_agents.deleteMany({ where: { channel_id: channelId } })
1637
+ await tx.channel_worktrees.deleteMany({ where: { channel_id: channelId } })
1638
+ await tx.channel_verbosity.deleteMany({ where: { channel_id: channelId } })
1639
+ await tx.channel_mention_mode.deleteMany({ where: { channel_id: channelId } })
1640
+ const result = await tx.channel_directories.deleteMany({
1641
+ where: { channel_id: channelId },
1642
+ })
1643
+ return result.count
1644
+ })
1645
+ return deletedCount > 0
1646
+ }
1647
+
1579
1648
  /**
1580
1649
  * Get the directory for a voice channel.
1581
1650
  */
package/src/db.ts CHANGED
@@ -191,6 +191,14 @@ async function migrateSchema(prisma: PrismaClient): Promise<void> {
191
191
  }
192
192
  }
193
193
 
194
+ try {
195
+ await prisma.$executeRawUnsafe(
196
+ "ALTER TABLE thread_sessions ADD COLUMN source TEXT DEFAULT 'kimaki'",
197
+ )
198
+ } catch {
199
+ // Column already exists
200
+ }
201
+
194
202
  // Migration: move session_thinking data into session_models.variant.
195
203
  // session_thinking table is left in place (not dropped) so older kimaki versions
196
204
  // that still reference it won't crash on the same database.