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.
- package/LICENSE +21 -0
- package/dist/anthropic-auth-plugin.js +7 -0
- package/dist/cli.js +51 -7
- package/dist/commands/abort.js +5 -16
- package/dist/commands/action-buttons.js +3 -3
- package/dist/commands/add-project.js +1 -1
- package/dist/commands/ask-question.js +3 -3
- package/dist/commands/context-usage.js +1 -1
- package/dist/commands/create-new-project.js +1 -1
- package/dist/commands/fork.js +11 -8
- package/dist/commands/merge-worktree.js +1 -1
- package/dist/commands/new-worktree.js +63 -44
- package/dist/commands/remove-project.js +1 -1
- package/dist/commands/resume.js +11 -8
- package/dist/commands/screenshare.js +14 -6
- package/dist/commands/screenshare.test.js +20 -0
- package/dist/commands/session.js +1 -1
- package/dist/commands/undo-redo.js +91 -7
- package/dist/commands/user-command.js +1 -1
- package/dist/config.js +16 -1
- package/dist/database.js +53 -2
- package/dist/db.js +6 -0
- package/dist/discord-bot.js +48 -85
- package/dist/discord-command-registration.js +1 -1
- package/dist/external-opencode-sync.js +515 -0
- package/dist/external-opencode-sync.test.js +151 -0
- package/dist/gateway-proxy.e2e.test.js +8 -5
- package/dist/genai.js +1 -1
- package/dist/generated/enums.js +4 -0
- package/dist/generated/internal/class.js +4 -4
- package/dist/generated/internal/prismaNamespace.js +1 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +1 -0
- package/dist/generated/models/external_session_pending_prompts.js +1 -0
- package/dist/hrana-server.js +14 -285
- package/dist/hrana-server.test.js +4 -2
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +7 -0
- package/dist/kimaki-opencode-plugin.js +2 -0
- package/dist/kitty-graphics-parser.js +3 -0
- package/dist/kitty-graphics-parser.test.js +276 -0
- package/dist/kitty-graphics-plugin.js +3 -0
- package/dist/markdown.js +4 -4
- package/dist/markdown.test.js +1 -1
- package/dist/message-formatting.js +54 -15
- package/dist/onboarding-tutorial.js +1 -1
- package/dist/openai-realtime.js +9 -13
- package/dist/opencode.js +28 -5
- package/dist/queue-advanced-e2e-setup.js +89 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +5 -5
- package/dist/queue-advanced-typing.e2e.test.js +9 -22
- package/dist/queue-question-select-drain.e2e.test.js +117 -0
- package/dist/session-handler/event-stream-state.js +101 -7
- package/dist/session-handler/event-stream-state.test.js +7 -3
- package/dist/session-handler/thread-session-runtime.js +120 -9
- package/dist/store.js +1 -0
- package/dist/system-message.js +22 -4
- package/dist/system-message.test.js +19 -0
- package/dist/task-runner.js +1 -1
- package/dist/thread-message-queue.e2e.test.js +8 -14
- package/dist/tools.js +1 -1
- package/dist/undo-redo.e2e.test.js +20 -25
- package/package.json +10 -6
- package/schema.prisma +6 -0
- package/skills/errore/SKILL.md +40 -13
- package/skills/goke/SKILL.md +12 -0
- package/skills/lintcn/SKILL.md +868 -0
- package/skills/npm-package/SKILL.md +1 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/spiceflow/SKILL.md +1 -1
- package/skills/usecomputer/SKILL.md +339 -0
- package/src/ai-tool-to-genai.ts +1 -0
- package/src/anthropic-auth-plugin.ts +7 -0
- package/src/cli.ts +59 -6
- package/src/commands/abort.ts +6 -16
- package/src/commands/action-buttons.ts +5 -1
- package/src/commands/add-project.ts +1 -1
- package/src/commands/ask-question.ts +5 -2
- package/src/commands/context-usage.ts +1 -1
- package/src/commands/create-new-project.ts +1 -1
- package/src/commands/fork.ts +12 -11
- package/src/commands/merge-worktree.ts +1 -1
- package/src/commands/new-worktree.ts +74 -55
- package/src/commands/remove-project.ts +1 -1
- package/src/commands/resume.ts +12 -10
- package/src/commands/screenshare.test.ts +30 -0
- package/src/commands/screenshare.ts +18 -6
- package/src/commands/session.ts +1 -1
- package/src/commands/undo-redo.ts +108 -10
- package/src/commands/user-command.ts +1 -1
- package/src/config.ts +19 -1
- package/src/database.ts +72 -3
- package/src/db.ts +8 -0
- package/src/discord-bot.ts +58 -93
- package/src/discord-command-registration.ts +1 -1
- package/src/external-opencode-sync.ts +729 -0
- package/src/gateway-proxy.e2e.test.ts +9 -5
- package/src/genai.ts +3 -3
- package/src/generated/commonInputTypes.ts +34 -0
- package/src/generated/enums.ts +8 -0
- package/src/generated/internal/class.ts +4 -4
- package/src/generated/internal/prismaNamespace.ts +8 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +1 -0
- package/src/generated/models/thread_sessions.ts +53 -1
- package/src/hrana-server.test.ts +8 -2
- package/src/hrana-server.ts +18 -390
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +7 -0
- package/src/kimaki-opencode-plugin.ts +2 -0
- package/src/markdown.test.ts +1 -1
- package/src/markdown.ts +4 -4
- package/src/message-formatting.ts +66 -17
- package/src/onboarding-tutorial.ts +1 -1
- package/src/openai-realtime.ts +6 -10
- package/src/opencode.ts +31 -7
- package/src/queue-advanced-e2e-setup.ts +92 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +5 -5
- package/src/queue-advanced-typing.e2e.test.ts +9 -22
- package/src/queue-question-select-drain.e2e.test.ts +149 -0
- package/src/schema.sql +1 -0
- package/src/session-handler/event-stream-state.test.ts +7 -2
- package/src/session-handler/event-stream-state.ts +128 -7
- package/src/session-handler/thread-runtime-state.ts +5 -0
- package/src/session-handler/thread-session-runtime.ts +153 -11
- package/src/store.ts +8 -0
- package/src/system-message.ts +27 -4
- package/src/task-runner.ts +1 -1
- package/src/thread-message-queue.e2e.test.ts +8 -14
- package/src/tools.ts +1 -1
- package/src/undo-redo.e2e.test.ts +28 -26
- package/skills/jitter/node_modules/.bin/esbuild +0 -21
- package/skills/jitter/node_modules/.bin/tsc +0 -21
- package/skills/jitter/node_modules/.bin/tsserver +0 -21
- package/skills/jitter/node_modules/typescript/LICENSE.txt +0 -55
- package/skills/jitter/node_modules/typescript/README.md +0 -50
- package/skills/jitter/node_modules/typescript/SECURITY.md +0 -41
- package/skills/jitter/node_modules/typescript/ThirdPartyNoticeText.txt +0 -193
- package/skills/jitter/node_modules/typescript/bin/tsc +0 -2
- package/skills/jitter/node_modules/typescript/bin/tsserver +0 -2
- package/skills/jitter/node_modules/typescript/lib/_tsc.js +0 -133792
- package/skills/jitter/node_modules/typescript/lib/_tsserver.js +0 -659
- package/skills/jitter/node_modules/typescript/lib/_typingsInstaller.js +0 -222
- package/skills/jitter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/lib.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.decorators.d.ts +0 -384
- package/skills/jitter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.dom.d.ts +0 -39429
- package/skills/jitter/node_modules/typescript/lib/lib.dom.iterable.d.ts +0 -571
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.collection.d.ts +0 -147
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.core.d.ts +0 -597
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.generator.d.ts +0 -77
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +0 -605
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.promise.d.ts +0 -81
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +0 -128
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +0 -144
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +0 -46
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +0 -326
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +0 -116
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.full.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.intl.d.ts +0 -31
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.date.d.ts +0 -31
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.full.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.intl.d.ts +0 -44
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.object.d.ts +0 -49
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +0 -135
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.string.d.ts +0 -45
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +0 -53
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +0 -77
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +0 -53
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.intl.d.ts +0 -83
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.promise.d.ts +0 -30
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +0 -37
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.array.d.ts +0 -79
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.intl.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.object.d.ts +0 -33
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.string.d.ts +0 -37
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +0 -765
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.d.ts +0 -27
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.date.d.ts +0 -42
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.intl.d.ts +0 -474
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.number.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.promise.d.ts +0 -47
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +0 -99
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.string.d.ts +0 -44
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.intl.d.ts +0 -166
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.promise.d.ts +0 -48
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.string.d.ts +0 -33
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +0 -78
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.array.d.ts +0 -121
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.error.d.ts +0 -75
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.intl.d.ts +0 -145
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.object.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +0 -39
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.string.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.array.d.ts +0 -924
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.collection.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.intl.d.ts +0 -56
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +0 -65
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.collection.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.object.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.promise.d.ts +0 -35
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +0 -68
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.string.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es5.d.ts +0 -4601
- package/skills/jitter/node_modules/typescript/lib/lib.es6.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.array.d.ts +0 -35
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.collection.d.ts +0 -96
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +0 -193
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.error.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.float16.d.ts +0 -443
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.intl.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +0 -148
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.promise.d.ts +0 -34
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.scripthost.d.ts +0 -322
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.d.ts +0 -13150
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +0 -340
- package/skills/jitter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/tsc.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/tsserver.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.d.ts +0 -17
- package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.js +0 -21
- package/skills/jitter/node_modules/typescript/lib/typesMap.json +0 -497
- package/skills/jitter/node_modules/typescript/lib/typescript.d.ts +0 -11438
- package/skills/jitter/node_modules/typescript/lib/typescript.js +0 -200253
- package/skills/jitter/node_modules/typescript/lib/typingsInstaller.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/watchGuard.js +0 -53
- package/skills/jitter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/node_modules/.bin/tsc +0 -21
- package/skills/jitter/node_modules/typescript/node_modules/.bin/tsserver +0 -21
- 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
|
|
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 =
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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)
|
package/src/commands/session.ts
CHANGED
|
@@ -17,7 +17,7 @@ export async function handleSessionCommand({
|
|
|
17
17
|
command,
|
|
18
18
|
appId,
|
|
19
19
|
}: CommandContext): Promise<void> {
|
|
20
|
-
await command.deferReply(
|
|
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
|
-
|
|
180
|
+
logger.log(`[UNDO] session.revert start messageId=${revertMessageId}`)
|
|
181
|
+
let response = await client.session.revert({
|
|
128
182
|
sessionID: sessionId,
|
|
129
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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(
|
|
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: {
|
|
1022
|
-
|
|
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.
|