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
package/src/cli.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
getScheduledTask,
|
|
56
56
|
updateScheduledTask,
|
|
57
57
|
getSessionStartSourcesBySessionIds,
|
|
58
|
+
deleteChannelDirectoryById,
|
|
58
59
|
} from './database.js'
|
|
59
60
|
import { ShareMarkdown } from './markdown.js'
|
|
60
61
|
import {
|
|
@@ -102,6 +103,7 @@ import { spawn, execSync, type ExecSyncOptions } from 'node:child_process'
|
|
|
102
103
|
|
|
103
104
|
import {
|
|
104
105
|
setDataDir,
|
|
106
|
+
setProjectsDir,
|
|
105
107
|
getDataDir,
|
|
106
108
|
getProjectsDir,
|
|
107
109
|
} from './config.js'
|
|
@@ -1834,6 +1836,10 @@ cli
|
|
|
1834
1836
|
'--data-dir <path>',
|
|
1835
1837
|
'Data directory for config and database (default: ~/.kimaki)',
|
|
1836
1838
|
)
|
|
1839
|
+
.option(
|
|
1840
|
+
'--projects-dir <path>',
|
|
1841
|
+
'Directory where new projects are created (default: <data-dir>/projects)',
|
|
1842
|
+
)
|
|
1837
1843
|
.option('--install-url', 'Print the bot install URL and exit')
|
|
1838
1844
|
.option(
|
|
1839
1845
|
'--use-worktrees',
|
|
@@ -1877,6 +1883,7 @@ cli
|
|
|
1877
1883
|
restartOnboarding?: boolean
|
|
1878
1884
|
addChannels?: boolean
|
|
1879
1885
|
dataDir?: string
|
|
1886
|
+
projectsDir?: string
|
|
1880
1887
|
installUrl?: boolean
|
|
1881
1888
|
useWorktrees?: boolean
|
|
1882
1889
|
enableVoiceChannels?: boolean
|
|
@@ -1908,6 +1915,11 @@ cli
|
|
|
1908
1915
|
cliLogger.log(`Using data directory: ${getDataDir()}`)
|
|
1909
1916
|
}
|
|
1910
1917
|
|
|
1918
|
+
if (options.projectsDir) {
|
|
1919
|
+
setProjectsDir(options.projectsDir)
|
|
1920
|
+
cliLogger.log(`Using projects directory: ${getProjectsDir()}`)
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1911
1923
|
// Initialize file logging to <dataDir>/kimaki.log
|
|
1912
1924
|
initLogFile(getDataDir())
|
|
1913
1925
|
|
|
@@ -3336,7 +3348,8 @@ cli
|
|
|
3336
3348
|
'List all registered projects with their Discord channels',
|
|
3337
3349
|
)
|
|
3338
3350
|
.option('--json', 'Output as JSON')
|
|
3339
|
-
.
|
|
3351
|
+
.option('--prune', 'Remove stale entries whose Discord channel no longer exists')
|
|
3352
|
+
.action(async (options: { json?: boolean; prune?: boolean }) => {
|
|
3340
3353
|
await initDatabase()
|
|
3341
3354
|
|
|
3342
3355
|
const prisma = await getPrisma()
|
|
@@ -3357,26 +3370,62 @@ cli
|
|
|
3357
3370
|
const enriched = await Promise.all(
|
|
3358
3371
|
channels.map(async (ch) => {
|
|
3359
3372
|
let channelName = ''
|
|
3373
|
+
let deleted = false
|
|
3360
3374
|
if (rest) {
|
|
3361
3375
|
try {
|
|
3362
3376
|
const data = (await rest.get(Routes.channel(ch.channel_id))) as {
|
|
3363
3377
|
name?: string
|
|
3364
3378
|
}
|
|
3365
3379
|
channelName = data.name || ''
|
|
3366
|
-
} catch {
|
|
3367
|
-
//
|
|
3380
|
+
} catch (error) {
|
|
3381
|
+
// Only mark as deleted for Unknown Channel (10003) or 404,
|
|
3382
|
+
// not transient errors like rate limits or 5xx
|
|
3383
|
+
const isUnknownChannel =
|
|
3384
|
+
error instanceof Error &&
|
|
3385
|
+
'code' in error &&
|
|
3386
|
+
'status' in error &&
|
|
3387
|
+
((error as { code: number | string }).code === 10003 ||
|
|
3388
|
+
(error as { status: number }).status === 404)
|
|
3389
|
+
deleted = isUnknownChannel
|
|
3368
3390
|
}
|
|
3369
3391
|
}
|
|
3370
|
-
return { ...ch, channelName }
|
|
3392
|
+
return { ...ch, channelName, deleted }
|
|
3371
3393
|
}),
|
|
3372
3394
|
)
|
|
3373
3395
|
|
|
3396
|
+
// Prune stale entries if requested
|
|
3397
|
+
if (options.prune) {
|
|
3398
|
+
const stale = enriched.filter((ch) => {
|
|
3399
|
+
return ch.deleted
|
|
3400
|
+
})
|
|
3401
|
+
if (stale.length === 0) {
|
|
3402
|
+
cliLogger.log('No stale channels to prune')
|
|
3403
|
+
} else {
|
|
3404
|
+
for (const ch of stale) {
|
|
3405
|
+
await deleteChannelDirectoryById(ch.channel_id)
|
|
3406
|
+
cliLogger.log(`Pruned stale channel ${ch.channel_id} (${path.basename(ch.directory)})`)
|
|
3407
|
+
}
|
|
3408
|
+
cliLogger.log(`Pruned ${stale.length} stale channel(s)`)
|
|
3409
|
+
}
|
|
3410
|
+
// Re-filter to only show live entries after pruning
|
|
3411
|
+
const live = enriched.filter((ch) => {
|
|
3412
|
+
return !ch.deleted
|
|
3413
|
+
})
|
|
3414
|
+
if (live.length === 0) {
|
|
3415
|
+
cliLogger.log('No projects registered')
|
|
3416
|
+
process.exit(0)
|
|
3417
|
+
}
|
|
3418
|
+
enriched.length = 0
|
|
3419
|
+
enriched.push(...live)
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3374
3422
|
if (options.json) {
|
|
3375
3423
|
const output = enriched.map((ch) => ({
|
|
3376
3424
|
channel_id: ch.channel_id,
|
|
3377
3425
|
channel_name: ch.channelName,
|
|
3378
3426
|
directory: ch.directory,
|
|
3379
3427
|
folder_name: path.basename(ch.directory),
|
|
3428
|
+
deleted: ch.deleted,
|
|
3380
3429
|
}))
|
|
3381
3430
|
console.log(JSON.stringify(output, null, 2))
|
|
3382
3431
|
process.exit(0)
|
|
@@ -3384,8 +3433,9 @@ cli
|
|
|
3384
3433
|
|
|
3385
3434
|
for (const ch of enriched) {
|
|
3386
3435
|
const folderName = path.basename(ch.directory)
|
|
3436
|
+
const deletedTag = ch.deleted ? ' (deleted from Discord)' : ''
|
|
3387
3437
|
const channelLabel = ch.channelName ? `#${ch.channelName}` : ch.channel_id
|
|
3388
|
-
console.log(`\n${channelLabel}`)
|
|
3438
|
+
console.log(`\n${channelLabel}${deletedTag}`)
|
|
3389
3439
|
console.log(` Folder: ${folderName}`)
|
|
3390
3440
|
console.log(` Directory: ${ch.directory}`)
|
|
3391
3441
|
console.log(` Channel ID: ${ch.channel_id}`)
|
|
@@ -3642,12 +3692,14 @@ cli
|
|
|
3642
3692
|
)
|
|
3643
3693
|
.option('-h, --host [host]', 'Local host (default: localhost)')
|
|
3644
3694
|
.option('-s, --server [url]', 'Tunnel server URL')
|
|
3695
|
+
.option('-k, --kill', 'Kill any existing process on the port before starting')
|
|
3645
3696
|
.action(
|
|
3646
3697
|
async (options: {
|
|
3647
3698
|
port?: string
|
|
3648
3699
|
tunnelId?: string
|
|
3649
3700
|
host?: string
|
|
3650
3701
|
server?: string
|
|
3702
|
+
kill?: boolean
|
|
3651
3703
|
}) => {
|
|
3652
3704
|
const { runTunnel, parseCommandFromArgv, CLI_NAME } = await import(
|
|
3653
3705
|
'traforo/run-tunnel'
|
|
@@ -3675,6 +3727,7 @@ cli
|
|
|
3675
3727
|
baseDomain: 'kimaki.xyz',
|
|
3676
3728
|
serverUrl: options.server,
|
|
3677
3729
|
command: command.length > 0 ? command : undefined,
|
|
3730
|
+
kill: options.kill,
|
|
3678
3731
|
})
|
|
3679
3732
|
},
|
|
3680
3733
|
)
|
|
@@ -3682,7 +3735,7 @@ cli
|
|
|
3682
3735
|
cli
|
|
3683
3736
|
.command(
|
|
3684
3737
|
'screenshare',
|
|
3685
|
-
'Share your screen via VNC tunnel. Auto-stops after
|
|
3738
|
+
'Share your screen via VNC tunnel. Auto-stops after 30 minutes. Runs until Ctrl+C. Use tmux to run in background.',
|
|
3686
3739
|
)
|
|
3687
3740
|
.action(async () => {
|
|
3688
3741
|
const { startScreenshare } = await import(
|
package/src/commands/abort.ts
CHANGED
|
@@ -46,15 +46,14 @@ export async function handleAbortCommand({
|
|
|
46
46
|
return
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
|
|
50
|
+
|
|
49
51
|
const resolved = await resolveWorkingDirectory({
|
|
50
52
|
channel: channel as TextChannel | ThreadChannel,
|
|
51
53
|
})
|
|
52
54
|
|
|
53
55
|
if (!resolved) {
|
|
54
|
-
await command.
|
|
55
|
-
content: 'Could not determine project directory for this channel',
|
|
56
|
-
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
57
|
-
})
|
|
56
|
+
await command.editReply('Could not determine project directory for this channel')
|
|
58
57
|
return
|
|
59
58
|
}
|
|
60
59
|
|
|
@@ -63,10 +62,7 @@ export async function handleAbortCommand({
|
|
|
63
62
|
const sessionId = await getThreadSession(channel.id)
|
|
64
63
|
|
|
65
64
|
if (!sessionId) {
|
|
66
|
-
await command.
|
|
67
|
-
content: 'No active session in this thread',
|
|
68
|
-
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
69
|
-
})
|
|
65
|
+
await command.editReply('No active session in this thread')
|
|
70
66
|
return
|
|
71
67
|
}
|
|
72
68
|
|
|
@@ -78,10 +74,7 @@ export async function handleAbortCommand({
|
|
|
78
74
|
// No runtime but session exists ā fall back to direct API abort
|
|
79
75
|
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
80
76
|
if (getClient instanceof Error) {
|
|
81
|
-
await command.
|
|
82
|
-
content: `Failed to abort: ${getClient.message}`,
|
|
83
|
-
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
84
|
-
})
|
|
77
|
+
await command.editReply(`Failed to abort: ${getClient.message}`)
|
|
85
78
|
return
|
|
86
79
|
}
|
|
87
80
|
try {
|
|
@@ -91,9 +84,6 @@ export async function handleAbortCommand({
|
|
|
91
84
|
}
|
|
92
85
|
}
|
|
93
86
|
|
|
94
|
-
await command.
|
|
95
|
-
content: `Request **aborted**`,
|
|
96
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
97
|
-
})
|
|
87
|
+
await command.editReply('Request **aborted**')
|
|
98
88
|
logger.log(`Session ${sessionId} aborted by user`)
|
|
99
89
|
}
|
|
@@ -14,6 +14,7 @@ import crypto from 'node:crypto'
|
|
|
14
14
|
import { getThreadSession } from '../database.js'
|
|
15
15
|
import {
|
|
16
16
|
NOTIFY_MESSAGE_FLAGS,
|
|
17
|
+
SILENT_MESSAGE_FLAGS,
|
|
17
18
|
resolveWorkingDirectory,
|
|
18
19
|
sendThreadMessage,
|
|
19
20
|
} from '../discord-utils.js'
|
|
@@ -185,11 +186,14 @@ export async function showActionButtons({
|
|
|
185
186
|
sessionId,
|
|
186
187
|
directory,
|
|
187
188
|
buttons,
|
|
189
|
+
silent,
|
|
188
190
|
}: {
|
|
189
191
|
thread: ThreadChannel
|
|
190
192
|
sessionId: string
|
|
191
193
|
directory: string
|
|
192
194
|
buttons: ActionButtonOption[]
|
|
195
|
+
/** Suppress notification when queue has pending items */
|
|
196
|
+
silent?: boolean
|
|
193
197
|
}): Promise<void> {
|
|
194
198
|
const safeButtons = buttons
|
|
195
199
|
.slice(0, 3)
|
|
@@ -242,7 +246,7 @@ export async function showActionButtons({
|
|
|
242
246
|
const message = await thread.send({
|
|
243
247
|
content: '**Action Required**',
|
|
244
248
|
components: [row],
|
|
245
|
-
flags: NOTIFY_MESSAGE_FLAGS,
|
|
249
|
+
flags: silent ? SILENT_MESSAGE_FLAGS : NOTIFY_MESSAGE_FLAGS,
|
|
246
250
|
})
|
|
247
251
|
|
|
248
252
|
context.messageId = message.id
|
|
@@ -18,7 +18,7 @@ const logger = createLogger(LogPrefix.ADD_PROJECT)
|
|
|
18
18
|
export async function handleAddProjectCommand({
|
|
19
19
|
command,
|
|
20
20
|
}: CommandContext): Promise<void> {
|
|
21
|
-
await command.deferReply(
|
|
21
|
+
await command.deferReply()
|
|
22
22
|
|
|
23
23
|
const projectId = command.options.getString('project', true)
|
|
24
24
|
const guild = command.guild
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
MessageFlags,
|
|
11
11
|
} from 'discord.js'
|
|
12
12
|
import crypto from 'node:crypto'
|
|
13
|
-
import { sendThreadMessage, NOTIFY_MESSAGE_FLAGS } from '../discord-utils.js'
|
|
13
|
+
import { sendThreadMessage, NOTIFY_MESSAGE_FLAGS, SILENT_MESSAGE_FLAGS } from '../discord-utils.js'
|
|
14
14
|
import { getOpencodeClient } from '../opencode.js'
|
|
15
15
|
import { createLogger, LogPrefix } from '../logger.js'
|
|
16
16
|
|
|
@@ -59,12 +59,15 @@ export async function showAskUserQuestionDropdowns({
|
|
|
59
59
|
directory,
|
|
60
60
|
requestId,
|
|
61
61
|
input,
|
|
62
|
+
silent,
|
|
62
63
|
}: {
|
|
63
64
|
thread: ThreadChannel
|
|
64
65
|
sessionId: string
|
|
65
66
|
directory: string
|
|
66
67
|
requestId: string // OpenCode question request ID
|
|
67
68
|
input: AskUserQuestionInput
|
|
69
|
+
/** Suppress notification when queue has pending items */
|
|
70
|
+
silent?: boolean
|
|
68
71
|
}): Promise<void> {
|
|
69
72
|
const contextHash = crypto.randomBytes(8).toString('hex')
|
|
70
73
|
|
|
@@ -144,7 +147,7 @@ export async function showAskUserQuestionDropdowns({
|
|
|
144
147
|
await thread.send({
|
|
145
148
|
content: `**${(q.header || '').slice(0, 200)}**\n${q.question.slice(0, 1700)}`,
|
|
146
149
|
components: [actionRow],
|
|
147
|
-
flags: NOTIFY_MESSAGE_FLAGS,
|
|
150
|
+
flags: silent ? SILENT_MESSAGE_FLAGS : NOTIFY_MESSAGE_FLAGS,
|
|
148
151
|
})
|
|
149
152
|
}
|
|
150
153
|
|
|
@@ -117,7 +117,7 @@ export async function handleContextUsageCommand({
|
|
|
117
117
|
if (m.info.role !== 'assistant') {
|
|
118
118
|
return false
|
|
119
119
|
}
|
|
120
|
-
if (!
|
|
120
|
+
if (!m.info.tokens) {
|
|
121
121
|
return false
|
|
122
122
|
}
|
|
123
123
|
return getTokenTotal(m.info.tokens) > 0
|
|
@@ -90,7 +90,7 @@ export async function handleCreateNewProjectCommand({
|
|
|
90
90
|
command,
|
|
91
91
|
appId,
|
|
92
92
|
}: CommandContext): Promise<void> {
|
|
93
|
-
await command.deferReply(
|
|
93
|
+
await command.deferReply()
|
|
94
94
|
|
|
95
95
|
const projectName = command.options.getString('name', true)
|
|
96
96
|
const guild = command.guild
|
package/src/commands/fork.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
resolveTextChannel,
|
|
22
22
|
sendThreadMessage,
|
|
23
23
|
} from '../discord-utils.js'
|
|
24
|
-
import {
|
|
24
|
+
import { collectSessionChunks, batchChunksForDiscord } from '../message-formatting.js'
|
|
25
25
|
import { createLogger, LogPrefix } from '../logger.js'
|
|
26
26
|
import * as errore from 'errore'
|
|
27
27
|
|
|
@@ -200,7 +200,7 @@ export async function handleForkSelectMenu(
|
|
|
200
200
|
return
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
await interaction.deferReply(
|
|
203
|
+
await interaction.deferReply()
|
|
204
204
|
|
|
205
205
|
const threadChannel = interaction.channel
|
|
206
206
|
if (!threadChannel) {
|
|
@@ -265,11 +265,13 @@ export async function handleForkSelectMenu(
|
|
|
265
265
|
reason: `Forked from session ${sessionId}`,
|
|
266
266
|
})
|
|
267
267
|
|
|
268
|
+
// Claim the forked session immediately so external polling does not race
|
|
269
|
+
// and create a duplicate Sync thread before the rest of this setup runs.
|
|
270
|
+
await setThreadSession(thread.id, forkedSession.id)
|
|
271
|
+
|
|
268
272
|
// Add user to thread so it appears in their sidebar
|
|
269
273
|
await thread.members.add(interaction.user.id)
|
|
270
274
|
|
|
271
|
-
await setThreadSession(thread.id, forkedSession.id)
|
|
272
|
-
|
|
273
275
|
sessionLogger.log(
|
|
274
276
|
`Created forked session ${forkedSession.id} in thread ${thread.id}`,
|
|
275
277
|
)
|
|
@@ -285,16 +287,15 @@ export async function handleForkSelectMenu(
|
|
|
285
287
|
})
|
|
286
288
|
|
|
287
289
|
if (messagesResponse.data) {
|
|
288
|
-
const {
|
|
290
|
+
const { chunks } = collectSessionChunks({
|
|
289
291
|
messages: messagesResponse.data,
|
|
292
|
+
limit: 30,
|
|
290
293
|
})
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const discordMessage = await sendThreadMessage(thread, content)
|
|
294
|
-
|
|
295
|
-
// Store part-message mappings atomically
|
|
294
|
+
const batched = batchChunksForDiscord(chunks)
|
|
295
|
+
for (const batch of batched) {
|
|
296
|
+
const discordMessage = await sendThreadMessage(thread, batch.content)
|
|
296
297
|
await setPartMessagesBatch(
|
|
297
|
-
partIds.map((partId) => ({
|
|
298
|
+
batch.partIds.map((partId) => ({
|
|
298
299
|
partId,
|
|
299
300
|
messageId: discordMessage.id,
|
|
300
301
|
threadId: thread.id,
|
|
@@ -92,7 +92,7 @@ export async function handleMergeWorktreeCommand({
|
|
|
92
92
|
command,
|
|
93
93
|
appId,
|
|
94
94
|
}: CommandContext): Promise<void> {
|
|
95
|
-
await command.deferReply(
|
|
95
|
+
await command.deferReply()
|
|
96
96
|
|
|
97
97
|
const channel = command.channel
|
|
98
98
|
if (!channel || !channel.isThread()) {
|
|
@@ -37,6 +37,11 @@ import * as errore from 'errore'
|
|
|
37
37
|
|
|
38
38
|
const logger = createLogger(LogPrefix.WORKTREE)
|
|
39
39
|
|
|
40
|
+
/** Status message shown while a worktree is being created. */
|
|
41
|
+
export function worktreeCreatingMessage(worktreeName: string): string {
|
|
42
|
+
return `š³ **Creating worktree: ${worktreeName}**\nā³ Setting up...`
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
class WorktreeError extends Error {
|
|
41
46
|
constructor(message: string, options?: { cause?: unknown }) {
|
|
42
47
|
super(message, options)
|
|
@@ -105,9 +110,18 @@ async function getProjectDirectoryFromChannel(
|
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
/**
|
|
108
|
-
* Create worktree
|
|
113
|
+
* Create worktree and update the status message when done.
|
|
114
|
+
* Handles the full lifecycle: pending DB entry, git creation, DB ready/error,
|
|
115
|
+
* tree emoji reaction, and editing the status message.
|
|
116
|
+
*
|
|
117
|
+
* starterMessage is optional ā if omitted, status edits are skipped (creation
|
|
118
|
+
* still proceeds). This keeps worktree creation independent of Discord message
|
|
119
|
+
* delivery, so a transient send failure never silently skips the worktree.
|
|
120
|
+
*
|
|
121
|
+
* Returns the worktree directory on success, or an Error on failure.
|
|
122
|
+
* Never throws ā all internal errors are caught and returned as Error values.
|
|
109
123
|
*/
|
|
110
|
-
async function createWorktreeInBackground({
|
|
124
|
+
export async function createWorktreeInBackground({
|
|
111
125
|
thread,
|
|
112
126
|
starterMessage,
|
|
113
127
|
worktreeName,
|
|
@@ -116,50 +130,69 @@ async function createWorktreeInBackground({
|
|
|
116
130
|
rest,
|
|
117
131
|
}: {
|
|
118
132
|
thread: ThreadChannel
|
|
119
|
-
starterMessage
|
|
133
|
+
starterMessage?: Message
|
|
120
134
|
worktreeName: string
|
|
121
135
|
projectDirectory: string
|
|
122
136
|
baseBranch?: string
|
|
123
137
|
rest: REST
|
|
124
|
-
}): Promise<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
}): Promise<string | Error> {
|
|
139
|
+
return errore.tryAsync({
|
|
140
|
+
try: async () => {
|
|
141
|
+
logger.log(
|
|
142
|
+
`Creating worktree "${worktreeName}" for project ${projectDirectory}${baseBranch ? ` from ${baseBranch}` : ''}`,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
await createPendingWorktree({
|
|
146
|
+
threadId: thread.id,
|
|
147
|
+
worktreeName,
|
|
148
|
+
projectDirectory,
|
|
149
|
+
})
|
|
133
150
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
`š³ **Worktree: ${worktreeName}**\nā ${errorMsg}`,
|
|
140
|
-
)
|
|
141
|
-
return
|
|
142
|
-
}
|
|
151
|
+
const worktreeResult = await createWorktreeWithSubmodules({
|
|
152
|
+
directory: projectDirectory,
|
|
153
|
+
name: worktreeName,
|
|
154
|
+
baseBranch,
|
|
155
|
+
})
|
|
143
156
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
if (worktreeResult instanceof Error) {
|
|
158
|
+
const errorMsg = worktreeResult.message
|
|
159
|
+
logger.error('[WORKTREE] Creation failed:', worktreeResult)
|
|
160
|
+
await setWorktreeError({ threadId: thread.id, errorMessage: errorMsg })
|
|
161
|
+
await starterMessage
|
|
162
|
+
?.edit(`š³ **Worktree: ${worktreeName}**\nā ${errorMsg}`)
|
|
163
|
+
.catch(() => {})
|
|
164
|
+
return worktreeResult
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Success - update database and edit starter message
|
|
168
|
+
await setWorktreeReady({
|
|
169
|
+
threadId: thread.id,
|
|
170
|
+
worktreeDirectory: worktreeResult.directory,
|
|
171
|
+
})
|
|
149
172
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
// React with tree emoji to mark as worktree thread
|
|
174
|
+
await reactToThread({
|
|
175
|
+
rest,
|
|
176
|
+
threadId: thread.id,
|
|
177
|
+
channelId: thread.parentId || undefined,
|
|
178
|
+
emoji: 'š³',
|
|
179
|
+
})
|
|
157
180
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
181
|
+
await starterMessage
|
|
182
|
+
?.edit(
|
|
183
|
+
`š³ **Worktree: ${worktreeName}**\n` +
|
|
184
|
+
`š \`${worktreeResult.directory}\`\n` +
|
|
185
|
+
`šæ Branch: \`${worktreeResult.branch}\``,
|
|
186
|
+
)
|
|
187
|
+
.catch(() => {})
|
|
188
|
+
|
|
189
|
+
return worktreeResult.directory
|
|
190
|
+
},
|
|
191
|
+
catch: (e) => {
|
|
192
|
+
logger.error('[WORKTREE] Unexpected error in createWorktreeInBackground:', e)
|
|
193
|
+
return new Error(`Worktree creation failed: ${e instanceof Error ? e.message : String(e)}`, { cause: e })
|
|
194
|
+
},
|
|
195
|
+
})
|
|
163
196
|
}
|
|
164
197
|
|
|
165
198
|
async function findExistingWorktreePath({
|
|
@@ -201,7 +234,7 @@ async function findExistingWorktreePath({
|
|
|
201
234
|
export async function handleNewWorktreeCommand({
|
|
202
235
|
command,
|
|
203
236
|
}: CommandContext): Promise<void> {
|
|
204
|
-
await command.deferReply(
|
|
237
|
+
await command.deferReply()
|
|
205
238
|
|
|
206
239
|
const channel = command.channel
|
|
207
240
|
if (!channel) {
|
|
@@ -289,7 +322,7 @@ export async function handleNewWorktreeCommand({
|
|
|
289
322
|
const result = await errore.tryAsync({
|
|
290
323
|
try: async () => {
|
|
291
324
|
const starterMessage = await textChannel.send({
|
|
292
|
-
content:
|
|
325
|
+
content: worktreeCreatingMessage(worktreeName),
|
|
293
326
|
flags: SILENT_MESSAGE_FLAGS,
|
|
294
327
|
})
|
|
295
328
|
|
|
@@ -315,13 +348,6 @@ export async function handleNewWorktreeCommand({
|
|
|
315
348
|
|
|
316
349
|
const { thread, starterMessage } = result
|
|
317
350
|
|
|
318
|
-
// Store pending worktree in database
|
|
319
|
-
await createPendingWorktree({
|
|
320
|
-
threadId: thread.id,
|
|
321
|
-
worktreeName,
|
|
322
|
-
projectDirectory,
|
|
323
|
-
})
|
|
324
|
-
|
|
325
351
|
await command.editReply(`Creating worktree in ${thread.toString()}`)
|
|
326
352
|
|
|
327
353
|
// Create worktree in background (don't await)
|
|
@@ -412,16 +438,9 @@ async function handleWorktreeInThread({
|
|
|
412
438
|
return
|
|
413
439
|
}
|
|
414
440
|
|
|
415
|
-
// Store pending worktree in database for this existing thread
|
|
416
|
-
await createPendingWorktree({
|
|
417
|
-
threadId: thread.id,
|
|
418
|
-
worktreeName,
|
|
419
|
-
projectDirectory,
|
|
420
|
-
})
|
|
421
|
-
|
|
422
441
|
// Send status message in thread
|
|
423
442
|
const statusMessage = await thread.send({
|
|
424
|
-
content:
|
|
443
|
+
content: worktreeCreatingMessage(worktreeName),
|
|
425
444
|
flags: SILENT_MESSAGE_FLAGS,
|
|
426
445
|
})
|
|
427
446
|
|
|
@@ -17,7 +17,7 @@ export async function handleRemoveProjectCommand({
|
|
|
17
17
|
command,
|
|
18
18
|
appId,
|
|
19
19
|
}: CommandContext): Promise<void> {
|
|
20
|
-
await command.deferReply(
|
|
20
|
+
await command.deferReply()
|
|
21
21
|
|
|
22
22
|
const directory = command.options.getString('project', true)
|
|
23
23
|
const guild = command.guild
|
package/src/commands/resume.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
resolveProjectDirectoryFromAutocomplete,
|
|
21
21
|
NOTIFY_MESSAGE_FLAGS,
|
|
22
22
|
} from '../discord-utils.js'
|
|
23
|
-
import {
|
|
23
|
+
import { collectSessionChunks, batchChunksForDiscord } from '../message-formatting.js'
|
|
24
24
|
import { createLogger, LogPrefix } from '../logger.js'
|
|
25
25
|
import * as errore from 'errore'
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ const logger = createLogger(LogPrefix.RESUME)
|
|
|
29
29
|
export async function handleResumeCommand({
|
|
30
30
|
command,
|
|
31
31
|
}: CommandContext): Promise<void> {
|
|
32
|
-
await command.deferReply(
|
|
32
|
+
await command.deferReply()
|
|
33
33
|
|
|
34
34
|
const sessionId = command.options.getString('session', true)
|
|
35
35
|
const channel = command.channel
|
|
@@ -95,11 +95,13 @@ export async function handleResumeCommand({
|
|
|
95
95
|
reason: `Resuming session ${sessionId}`,
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
+
// Claim the resumed session immediately so external polling does not race
|
|
99
|
+
// and create a duplicate Sync thread before the rest of this setup runs.
|
|
100
|
+
await setThreadSession(thread.id, sessionId)
|
|
101
|
+
|
|
98
102
|
// Add user to thread so it appears in their sidebar
|
|
99
103
|
await thread.members.add(command.user.id)
|
|
100
104
|
|
|
101
|
-
await setThreadSession(thread.id, sessionId)
|
|
102
|
-
|
|
103
105
|
logger.log(`[RESUME] Created thread ${thread.id} for session ${sessionId}`)
|
|
104
106
|
|
|
105
107
|
const messagesResponse = await getClient().session.messages({
|
|
@@ -122,8 +124,9 @@ export async function handleResumeCommand({
|
|
|
122
124
|
)
|
|
123
125
|
|
|
124
126
|
try {
|
|
125
|
-
const {
|
|
127
|
+
const { chunks, skippedCount } = collectSessionChunks({
|
|
126
128
|
messages,
|
|
129
|
+
limit: 30,
|
|
127
130
|
})
|
|
128
131
|
|
|
129
132
|
if (skippedCount > 0) {
|
|
@@ -133,12 +136,11 @@ export async function handleResumeCommand({
|
|
|
133
136
|
)
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Store part-message mappings atomically
|
|
139
|
+
const batched = batchChunksForDiscord(chunks)
|
|
140
|
+
for (const batch of batched) {
|
|
141
|
+
const discordMessage = await sendThreadMessage(thread, batch.content)
|
|
140
142
|
await setPartMessagesBatch(
|
|
141
|
-
partIds.map((partId) => ({
|
|
143
|
+
batch.partIds.map((partId) => ({
|
|
142
144
|
partId,
|
|
143
145
|
messageId: discordMessage.id,
|
|
144
146
|
threadId: thread.id,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import { buildNoVncUrl, createScreenshareTunnelId } from './screenshare.js'
|
|
3
|
+
|
|
4
|
+
describe('screenshare security defaults', () => {
|
|
5
|
+
test('generates a 128-bit tunnel id', () => {
|
|
6
|
+
const ids = new Set(
|
|
7
|
+
Array.from({ length: 32 }, () => {
|
|
8
|
+
return createScreenshareTunnelId()
|
|
9
|
+
}),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
expect(ids.size).toBe(32)
|
|
13
|
+
for (const id of ids) {
|
|
14
|
+
expect(id).toMatch(/^[0-9a-f]{32}$/)
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('builds a secure noVNC URL', () => {
|
|
19
|
+
const url = new URL(
|
|
20
|
+
buildNoVncUrl({ tunnelHost: '0123456789abcdef-tunnel.kimaki.xyz' }),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
expect(url.origin).toBe('https://novnc.com')
|
|
24
|
+
expect(url.searchParams.get('host')).toBe(
|
|
25
|
+
'0123456789abcdef-tunnel.kimaki.xyz',
|
|
26
|
+
)
|
|
27
|
+
expect(url.searchParams.get('port')).toBe('443')
|
|
28
|
+
expect(url.searchParams.get('encrypt')).toBe('1')
|
|
29
|
+
})
|
|
30
|
+
})
|