kimaki 1.3.0 → 1.5.0
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/assets/fonts/Geist-Bold.ttf +0 -0
- package/assets/fonts/Geist-Regular.ttf +0 -0
- package/assets/fonts/GeistMono-Regular.ttf +0 -0
- package/assets/fonts/OFL.txt +92 -0
- package/dist/agent-ui.e2e.test.js +27 -13
- package/dist/agent-ui.e2e.test.js.map +1 -1
- package/dist/agent-ui.js +2 -1
- package/dist/agent-ui.js.map +1 -1
- package/dist/analytics.e2e.test.js +1 -1
- package/dist/analytics.e2e.test.js.map +1 -1
- package/dist/autostart.d.ts +4 -0
- package/dist/autostart.js +55 -10
- package/dist/autostart.js.map +1 -1
- package/dist/bot.d.ts +13 -6
- package/dist/bot.js +17 -15
- package/dist/bot.js.map +1 -1
- package/dist/btw.e2e.test.js +3 -3
- package/dist/builtin-skills.d.ts +3 -0
- package/dist/builtin-skills.js +29 -0
- package/dist/builtin-skills.js.map +1 -1
- package/dist/builtin-skills.test.js +72 -1
- package/dist/builtin-skills.test.js.map +1 -1
- package/dist/catalog-refresh.e2e.test.js +2 -2
- package/dist/cli/bot.d.ts +1 -1
- package/dist/cli/bot.js +33 -18
- package/dist/cli/bot.js.map +1 -1
- package/dist/cli/discord.js +32 -6
- package/dist/cli/discord.js.map +1 -1
- package/dist/cli/schedule.js +23 -9
- package/dist/cli/schedule.js.map +1 -1
- package/dist/cli/session.js +37 -12
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/shared.d.ts +5 -3
- package/dist/cli/shared.js +45 -10
- package/dist/cli/shared.js.map +1 -1
- package/dist/cli-discord.e2e.test.js +13 -8
- package/dist/cli-discord.e2e.test.js.map +1 -1
- package/dist/cli-lock.e2e.test.js +87 -20
- package/dist/cli-lock.e2e.test.js.map +1 -1
- package/dist/cli-read.e2e.test.js +18 -9
- package/dist/cli-read.e2e.test.js.map +1 -1
- package/dist/cli-sessions.e2e.test.js +4 -3
- package/dist/cli-sessions.e2e.test.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/preference-commands.d.ts +4 -0
- package/dist/commands/preference-commands.js +38 -4
- package/dist/commands/preference-commands.js.map +1 -1
- package/dist/commands/session-commands.js +11 -8
- package/dist/commands/session-commands.js.map +1 -1
- package/dist/commands/thread-commands.js +94 -24
- package/dist/commands/thread-commands.js.map +1 -1
- package/dist/commands/worktree-commands.d.ts +5 -6
- package/dist/commands/worktree-commands.js +8 -7
- package/dist/commands/worktree-commands.js.map +1 -1
- package/dist/commands.e2e.test.js +25 -21
- package/dist/commands.e2e.test.js.map +1 -1
- package/dist/coordinator-agents.e2e.test.d.ts +1 -0
- package/dist/coordinator-agents.e2e.test.js +113 -0
- package/dist/coordinator-agents.e2e.test.js.map +1 -0
- package/dist/coordinator-idle.e2e.test.d.ts +1 -0
- package/dist/coordinator-idle.e2e.test.js +100 -0
- package/dist/coordinator-idle.e2e.test.js.map +1 -0
- package/dist/coordinator-queue.e2e.test.d.ts +1 -0
- package/dist/coordinator-queue.e2e.test.js +244 -0
- package/dist/coordinator-queue.e2e.test.js.map +1 -0
- package/dist/coordinator-warmup.e2e.test.d.ts +1 -0
- package/dist/coordinator-warmup.e2e.test.js +131 -0
- package/dist/coordinator-warmup.e2e.test.js.map +1 -0
- package/dist/coordinator.d.ts +6 -0
- package/dist/coordinator.e2e.test.d.ts +1 -0
- package/dist/coordinator.e2e.test.js +505 -0
- package/dist/coordinator.e2e.test.js.map +1 -0
- package/dist/coordinator.js +164 -0
- package/dist/coordinator.js.map +1 -0
- package/dist/db.d.ts +78 -3
- package/dist/db.test.js +1 -0
- package/dist/db.test.js.map +1 -1
- package/dist/diagram.d.ts +11 -0
- package/dist/diagram.js +232 -0
- package/dist/diagram.js.map +1 -0
- package/dist/diagram.test.d.ts +1 -0
- package/dist/diagram.test.js +52 -0
- package/dist/diagram.test.js.map +1 -0
- package/dist/duration.js +13 -5
- package/dist/duration.js.map +1 -1
- package/dist/effects.d.ts +6 -1
- package/dist/effects.js +97 -26
- package/dist/effects.js.map +1 -1
- package/dist/event-loop.d.ts +2 -3
- package/dist/event-loop.js +74 -44
- package/dist/event-loop.js.map +1 -1
- package/dist/format-parts.d.ts +21 -4
- package/dist/format-parts.js +40 -23
- package/dist/format-parts.js.map +1 -1
- package/dist/gateway-replay.e2e.test.js +4 -4
- package/dist/harness.e2e.test.js +2 -2
- package/dist/harness.e2e.test.js.map +1 -1
- package/dist/hello-thread.e2e.test.js +56 -5
- package/dist/hello-thread.e2e.test.js.map +1 -1
- package/dist/ingress.js +47 -12
- package/dist/ingress.js.map +1 -1
- package/dist/interaction-context.d.ts +4 -4
- package/dist/interaction-context.js +9 -7
- package/dist/interaction-context.js.map +1 -1
- package/dist/interrupt.e2e.test.js +8 -5
- package/dist/interrupt.e2e.test.js.map +1 -1
- package/dist/legacy.e2e.test.js +1 -1
- package/dist/lock-routes.d.ts +83 -31
- package/dist/lock-routes.js +68 -8
- package/dist/lock-routes.js.map +1 -1
- package/dist/lock-server.d.ts +3 -2
- package/dist/lock-server.js +23 -7
- package/dist/lock-server.js.map +1 -1
- package/dist/main.d.ts +6 -3
- package/dist/main.js +14 -4
- package/dist/main.js.map +1 -1
- package/dist/mainmast-idle.e2e.test.d.ts +1 -0
- package/dist/mainmast-idle.e2e.test.js +99 -0
- package/dist/mainmast-idle.e2e.test.js.map +1 -0
- package/dist/mainmast-queue.e2e.test.d.ts +1 -0
- package/dist/mainmast-queue.e2e.test.js +244 -0
- package/dist/mainmast-queue.e2e.test.js.map +1 -0
- package/dist/mainmast-warmup.e2e.test.d.ts +1 -0
- package/dist/mainmast-warmup.e2e.test.js +130 -0
- package/dist/mainmast-warmup.e2e.test.js.map +1 -0
- package/dist/mainmast.d.ts +6 -0
- package/dist/mainmast.e2e.test.d.ts +1 -0
- package/dist/mainmast.e2e.test.js +428 -0
- package/dist/mainmast.e2e.test.js.map +1 -0
- package/dist/mainmast.js +156 -0
- package/dist/mainmast.js.map +1 -0
- package/dist/markdown/markdown.test.js +14 -4
- package/dist/markdown/markdown.test.js.map +1 -1
- package/dist/markdown/render-markdown.js +9 -6
- package/dist/markdown/render-markdown.js.map +1 -1
- package/dist/onboarding-cli.e2e.test.js +7 -6
- package/dist/onboarding-cli.e2e.test.js.map +1 -1
- package/dist/onboarding.d.ts +3 -10
- package/dist/onboarding.e2e.test.js +8 -10
- package/dist/onboarding.e2e.test.js.map +1 -1
- package/dist/onboarding.js +14 -24
- package/dist/onboarding.js.map +1 -1
- package/dist/permissions.e2e.test.js +3 -3
- package/dist/plugin/coordinator.d.ts +4 -0
- package/dist/plugin/coordinator.js +389 -0
- package/dist/plugin/coordinator.js.map +1 -0
- package/dist/plugin/index.js +93 -61
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/mainmast.d.ts +4 -0
- package/dist/plugin/mainmast.js +379 -0
- package/dist/plugin/mainmast.js.map +1 -0
- package/dist/plugin.e2e.test.js +1 -1
- package/dist/preference-commands.e2e.test.js +103 -24
- package/dist/preference-commands.e2e.test.js.map +1 -1
- package/dist/project.d.ts +1 -1
- package/dist/prompt.d.ts +22 -9
- package/dist/prompt.js +64 -48
- package/dist/prompt.js.map +1 -1
- package/dist/questions.e2e.test.js +13 -13
- package/dist/queue-sources.e2e.test.d.ts +1 -0
- package/dist/queue-sources.e2e.test.js +168 -0
- package/dist/queue-sources.e2e.test.js.map +1 -0
- package/dist/queue.d.ts +9 -1
- package/dist/queue.e2e.test.js +48 -30
- package/dist/queue.e2e.test.js.map +1 -1
- package/dist/queue.js +97 -24
- package/dist/queue.js.map +1 -1
- package/dist/queue.test.d.ts +1 -0
- package/dist/queue.test.js +57 -0
- package/dist/queue.test.js.map +1 -0
- package/dist/remote-send.e2e.test.js +6 -6
- package/dist/remote-send.js +2 -1
- package/dist/remote-send.js.map +1 -1
- package/dist/renderer.e2e.test.js +7 -7
- package/dist/restart.e2e.test.js +2 -2
- package/dist/routines.e2e.test.js +2 -2
- package/dist/scheduler.d.ts +3 -3
- package/dist/scheduler.e2e.test.js +43 -20
- package/dist/scheduler.e2e.test.js.map +1 -1
- package/dist/scheduler.js +4 -2
- package/dist/scheduler.js.map +1 -1
- package/dist/schema-sql.d.ts +1 -1
- package/dist/schema-sql.js +1 -1
- package/dist/schema-sql.js.map +1 -1
- package/dist/schema.d.ts +152 -6
- package/dist/schema.js +12 -0
- package/dist/schema.js.map +1 -1
- package/dist/session-commands.e2e.test.js +36 -11
- package/dist/session-commands.e2e.test.js.map +1 -1
- package/dist/session-events.d.ts +18 -6
- package/dist/session-events.js +175 -27
- package/dist/session-events.js.map +1 -1
- package/dist/session-ops.e2e.test.js +63 -12
- package/dist/session-ops.e2e.test.js.map +1 -1
- package/dist/sessions.d.ts +44 -21
- package/dist/sessions.js +177 -65
- package/dist/sessions.js.map +1 -1
- package/dist/shell.e2e.test.js +4 -4
- package/dist/slash-commands.js +6 -4
- package/dist/slash-commands.js.map +1 -1
- package/dist/slash-commands.test.js +2 -0
- package/dist/slash-commands.test.js.map +1 -1
- package/dist/sleep.e2e.test.js +29 -14
- package/dist/sleep.e2e.test.js.map +1 -1
- package/dist/sleeps.d.ts +2 -1
- package/dist/sleeps.js +21 -3
- package/dist/sleeps.js.map +1 -1
- package/dist/store.d.ts +1 -0
- package/dist/store.js +3 -2
- package/dist/store.js.map +1 -1
- package/dist/system-prompt.d.ts +5 -15
- package/dist/system-prompt.js +180 -131
- package/dist/system-prompt.js.map +1 -1
- package/dist/test/generate-log-plugin/index.d.ts +3 -0
- package/dist/test/generate-log-plugin/index.js +15 -0
- package/dist/test/generate-log-plugin/index.js.map +1 -0
- package/dist/test/harness.d.ts +20 -2
- package/dist/test/harness.js +51 -11
- package/dist/test/harness.js.map +1 -1
- package/dist/test/replay.js +11 -8
- package/dist/test/replay.js.map +1 -1
- package/dist/thread-reducer.d.ts +29 -6
- package/dist/thread-reducer.fixtures.test.js +58 -51
- package/dist/thread-reducer.fixtures.test.js.map +1 -1
- package/dist/thread-reducer.js +109 -50
- package/dist/thread-reducer.js.map +1 -1
- package/dist/thread-reducer.test.js +190 -48
- package/dist/thread-reducer.test.js.map +1 -1
- package/dist/tunnel.d.ts +9 -0
- package/dist/tunnel.js +117 -28
- package/dist/tunnel.js.map +1 -1
- package/dist/tunnel.test.js +81 -5
- package/dist/tunnel.test.js.map +1 -1
- package/dist/voice-attachments.e2e.test.js +108 -18
- package/dist/voice-attachments.e2e.test.js.map +1 -1
- package/dist/voice-call.e2e.test.js +9 -9
- package/dist/voice-call.js +6 -6
- package/dist/voice-call.js.map +1 -1
- package/dist/voice-openai.e2e.test.js +59 -23
- package/dist/voice-openai.e2e.test.js.map +1 -1
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +72 -24
- package/dist/voice.js.map +1 -1
- package/dist/working-directory.e2e.test.js +11 -11
- package/dist/working-directory.e2e.test.js.map +1 -1
- package/package.json +11 -5
- package/skills/kimaki-tunnel/SKILL.md +3 -2
- package/src/agent-ui.e2e.test.ts +27 -13
- package/src/agent-ui.ts +2 -1
- package/src/analytics.e2e.test.ts +1 -1
- package/src/autostart.ts +55 -10
- package/src/bot.ts +36 -19
- package/src/btw.e2e.test.ts +3 -3
- package/src/builtin-skills.test.ts +73 -1
- package/src/builtin-skills.ts +33 -0
- package/src/catalog-refresh.e2e.test.ts +2 -2
- package/src/cli/bot.ts +30 -19
- package/src/cli/discord.ts +30 -6
- package/src/cli/schedule.ts +23 -9
- package/src/cli/session.ts +35 -12
- package/src/cli/shared.ts +43 -8
- package/src/cli-discord.e2e.test.ts +13 -8
- package/src/cli-lock.e2e.test.ts +88 -20
- package/src/cli-read.e2e.test.ts +17 -8
- package/src/cli-sessions.e2e.test.ts +4 -3
- package/src/cli.ts +2 -0
- package/src/commands/preference-commands.ts +38 -4
- package/src/commands/session-commands.ts +12 -7
- package/src/commands/thread-commands.ts +92 -24
- package/src/commands/worktree-commands.ts +10 -9
- package/src/commands.e2e.test.ts +25 -21
- package/src/coordinator-agents.e2e.test.ts +145 -0
- package/src/coordinator-idle.e2e.test.ts +122 -0
- package/src/coordinator-queue.e2e.test.ts +279 -0
- package/src/coordinator-warmup.e2e.test.ts +158 -0
- package/src/coordinator.e2e.test.ts +545 -0
- package/src/coordinator.ts +168 -0
- package/src/db.test.ts +1 -0
- package/src/diagram.test.ts +56 -0
- package/src/diagram.ts +243 -0
- package/src/duration.ts +13 -5
- package/src/effects.ts +99 -27
- package/src/event-loop.ts +71 -45
- package/src/format-parts.ts +53 -22
- package/src/gateway-replay.e2e.test.ts +4 -4
- package/src/harness.e2e.test.ts +2 -2
- package/src/hello-thread.e2e.test.ts +59 -5
- package/src/ingress.ts +44 -11
- package/src/interaction-context.ts +12 -11
- package/src/interrupt.e2e.test.ts +8 -5
- package/src/legacy.e2e.test.ts +1 -1
- package/src/lock-routes.ts +62 -8
- package/src/lock-server.ts +23 -7
- package/src/main.ts +26 -10
- package/src/markdown/markdown.test.ts +15 -4
- package/src/markdown/render-markdown.ts +9 -6
- package/src/onboarding-cli.e2e.test.ts +7 -6
- package/src/onboarding.e2e.test.ts +8 -10
- package/src/onboarding.ts +13 -29
- package/src/permissions.e2e.test.ts +3 -3
- package/src/plugin/coordinator.ts +401 -0
- package/src/plugin/index.ts +86 -59
- package/src/plugin.e2e.test.ts +1 -1
- package/src/preference-commands.e2e.test.ts +103 -23
- package/src/prompt.ts +87 -43
- package/src/questions.e2e.test.ts +13 -13
- package/src/queue-sources.e2e.test.ts +197 -0
- package/src/queue.e2e.test.ts +48 -30
- package/src/queue.test.ts +59 -0
- package/src/queue.ts +104 -24
- package/src/remote-send.e2e.test.ts +6 -6
- package/src/remote-send.ts +2 -1
- package/src/renderer.e2e.test.ts +7 -7
- package/src/restart.e2e.test.ts +2 -2
- package/src/routines.e2e.test.ts +2 -2
- package/src/scheduler.e2e.test.ts +45 -19
- package/src/scheduler.ts +4 -2
- package/src/schema-sql.ts +1 -1
- package/src/schema.ts +13 -0
- package/src/session-commands.e2e.test.ts +38 -11
- package/src/session-events.ts +168 -25
- package/src/session-ops.e2e.test.ts +64 -11
- package/src/sessions.ts +217 -67
- package/src/shell.e2e.test.ts +4 -4
- package/src/slash-commands.test.ts +2 -0
- package/src/slash-commands.ts +6 -4
- package/src/sleep.e2e.test.ts +30 -13
- package/src/sleeps.ts +20 -3
- package/src/store.ts +5 -2
- package/src/system-prompt.ts +190 -134
- package/src/test/generate-log-plugin/index.ts +16 -0
- package/src/test/harness.ts +77 -17
- package/src/test/replay.ts +12 -7
- package/src/thread-reducer.fixtures.test.ts +58 -51
- package/src/thread-reducer.test.ts +199 -49
- package/src/thread-reducer.ts +135 -66
- package/src/tunnel.test.ts +81 -5
- package/src/tunnel.ts +124 -25
- package/src/voice-attachments.e2e.test.ts +110 -18
- package/src/voice-call.e2e.test.ts +9 -9
- package/src/voice-call.ts +6 -6
- package/src/voice-openai.e2e.test.ts +64 -24
- package/src/voice.ts +93 -26
- package/src/working-directory.e2e.test.ts +11 -11
- package/src/message-tool.e2e.test.ts +0 -45
package/src/main.ts
CHANGED
|
@@ -8,20 +8,22 @@ import * as errore from 'errore'
|
|
|
8
8
|
|
|
9
9
|
import { createAgentUi } from './agent-ui.ts'
|
|
10
10
|
import type { Analytics } from './analytics.ts'
|
|
11
|
-
import { oc, type Bot } from './bot.ts'
|
|
11
|
+
import { oc, type Bot, type WarmupTimers } from './bot.ts'
|
|
12
|
+
import type { PromptSection } from './builtin-skills.ts'
|
|
12
13
|
import { openDb } from './db.ts'
|
|
13
14
|
import { createEffectsRunner } from './effects.ts'
|
|
14
15
|
import { DiscordError } from './errors.ts'
|
|
15
|
-
import { createEventLoop } from './event-loop.ts'
|
|
16
|
+
import { createEventLoop, type RunEnded } from './event-loop.ts'
|
|
16
17
|
import { registerIngress } from './ingress.ts'
|
|
17
18
|
import { runLockRoute } from './lock-routes.ts'
|
|
18
19
|
import { installShim, type LockServer } from './lock-server.ts'
|
|
19
20
|
import { createLogger } from './logger.ts'
|
|
21
|
+
import { createCoordinatorNotes } from './coordinator.ts'
|
|
20
22
|
import { installPluginShim, watchOpencode, type OpencodeEndpoint } from './opencode-server.ts'
|
|
21
23
|
import { countUserProjects } from './project.ts'
|
|
22
24
|
import { createScheduler, systemClock, type Clock } from './scheduler.ts'
|
|
23
25
|
import { createEventRecorder, syncSessionTranscript } from './session-events.ts'
|
|
24
|
-
import { createPluginWait, refreshCliContext } from './sessions.ts'
|
|
26
|
+
import { channelDefaults, createPluginWait, refreshCliContext } from './sessions.ts'
|
|
25
27
|
import { createInteractionRegistry, registerSlashCommands } from './slash-commands.ts'
|
|
26
28
|
import { createSleepLock } from './sleeps.ts'
|
|
27
29
|
import { createBotStore } from './store.ts'
|
|
@@ -52,8 +54,9 @@ export type StartBotOptions = {
|
|
|
52
54
|
// OpenCode's SQLite file (opencodeDbFile()): voice transcription reads the ChatGPT credential from it.
|
|
53
55
|
// Required, so tests never read the developer's real credentials.
|
|
54
56
|
opencodeDbFile: string
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
+
// Command that runs this Kimaki install, for the <dataDir>/bin/kimaki shim agents call.
|
|
58
|
+
// No --data-dir: the shim sets KIMAKI_DATA_DIR, and not every command takes the flag.
|
|
59
|
+
kimakiArgv: readonly string[]
|
|
57
60
|
// Product analytics sink (createAnalytics or disabledAnalytics).
|
|
58
61
|
analytics: Analytics
|
|
59
62
|
// Voice transcription API base URLs; tests point Gemini at a local fake.
|
|
@@ -68,8 +71,12 @@ export type StartBotOptions = {
|
|
|
68
71
|
allowAllUsers?: boolean
|
|
69
72
|
// Built-in skill ids denied in new sessions (builtin-skills.ts).
|
|
70
73
|
disabledSkills?: readonly string[]
|
|
71
|
-
//
|
|
72
|
-
|
|
74
|
+
// Static instruction parts left out of new sessions (system-prompt.ts).
|
|
75
|
+
disabledPrompt?: readonly PromptSection[]
|
|
76
|
+
// `--coordinator`: coordinator mode for channels without a /toggle-coordinator choice (ingress.ts). Default: on.
|
|
77
|
+
coordinator?: boolean
|
|
78
|
+
// Default: the plugin's prompt cache warmup windows (plugin/coordinator.ts). Tests pass short ones.
|
|
79
|
+
warmupTimers?: WarmupTimers
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
export type BotHandle = Bot & {
|
|
@@ -140,6 +147,7 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
140
147
|
const result = await oc({ opencode }, 'mark session viewed', (client) => client.session.view({ sessionID: sessionId, idle }))
|
|
141
148
|
if (result instanceof Error) return result
|
|
142
149
|
},
|
|
150
|
+
footerDefaults: (channelId) => channelDefaults({ db, opencode }, channelId),
|
|
143
151
|
})
|
|
144
152
|
cleanup.defer(() => effects.stop())
|
|
145
153
|
const voiceCalls = createVoiceCalls({ opencodeConfigDir: options.opencodeConfigDir })
|
|
@@ -148,7 +156,12 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
148
156
|
if (result instanceof Error) logger.warn(`transcript of ${sessionId} not synced`, result)
|
|
149
157
|
})
|
|
150
158
|
}
|
|
151
|
-
const
|
|
159
|
+
const coordinatorNotes = createCoordinatorNotes()
|
|
160
|
+
const onRunEnded = (run: RunEnded) => {
|
|
161
|
+
voiceCalls.runEnded(run)
|
|
162
|
+
coordinatorNotes.runEnded(run)
|
|
163
|
+
}
|
|
164
|
+
const eventLoop = createEventLoop({ store, db, discord, effects, recorder, analytics: options.analytics, onRunEnded, onTranscriptDue })
|
|
152
165
|
const loaded = await eventLoop.load()
|
|
153
166
|
if (loaded instanceof Error) return loaded
|
|
154
167
|
// Before the service is used, so a service started by ensure() loads it at once.
|
|
@@ -162,7 +175,7 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
162
175
|
onDisconnect: eventLoop.onDisconnect,
|
|
163
176
|
})
|
|
164
177
|
cleanup.defer(() => opencode.stop())
|
|
165
|
-
const shim = await installShim({ dataDir: options.dataDir,
|
|
178
|
+
const shim = await installShim({ dataDir: options.dataDir, argv: options.kimakiArgv })
|
|
166
179
|
if (shim instanceof Error) return shim
|
|
167
180
|
// The features that non-interaction code reaches through bot.features.
|
|
168
181
|
const agentUi = createAgentUi({ store, eventLoop, opencode })
|
|
@@ -187,7 +200,9 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
187
200
|
autoWorktrees: options.autoWorktrees ?? false,
|
|
188
201
|
allowAllUsers: options.allowAllUsers ?? false,
|
|
189
202
|
disabledSkills: options.disabledSkills ?? [],
|
|
190
|
-
|
|
203
|
+
disabledPrompt: options.disabledPrompt ?? [],
|
|
204
|
+
coordinatorDefault: options.coordinator ?? true,
|
|
205
|
+
warmupTimers: options.warmupTimers ?? null,
|
|
191
206
|
features: { withSleepLock: createSleepLock(), waitForPlugin: createPluginWait({ opencode }), agentUi, voiceCalls },
|
|
192
207
|
}
|
|
193
208
|
const scheduler = createScheduler({
|
|
@@ -200,6 +215,7 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
200
215
|
// Before login: gateway-proxy replays missed messages right after READY.
|
|
201
216
|
registerIngress(bot)
|
|
202
217
|
voiceCalls.register(bot)
|
|
218
|
+
coordinatorNotes.register(bot)
|
|
203
219
|
// Before discord.destroy() (registered earlier, so disposed later): leaving voice needs the gateway.
|
|
204
220
|
cleanup.defer(() => voiceCalls.stop(bot))
|
|
205
221
|
|
|
@@ -344,10 +344,10 @@ test('long paragraph splits by sentences, tight paragraph-list spacing is kept',
|
|
|
344
344
|
const text = `Steps:\n- one\n- two\n\n${sentence.repeat(30)}`
|
|
345
345
|
expect(renderMarkdown(text, { limit: 500 }).map((segment) => segment.kind === 'text' && segment.markdown.length)).toMatchInlineSnapshot(`
|
|
346
346
|
[
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
347
|
+
500,
|
|
348
|
+
480,
|
|
349
|
+
480,
|
|
350
|
+
480,
|
|
351
351
|
480,
|
|
352
352
|
]
|
|
353
353
|
`)
|
|
@@ -412,3 +412,14 @@ test('edge cases: 2500-char code line, fence info line inside a fence, oversized
|
|
|
412
412
|
|
|
413
413
|
expect(markdownPayloads('<callout accent="#ef4444"></callout>')).toMatchInlineSnapshot(`[]`)
|
|
414
414
|
})
|
|
415
|
+
|
|
416
|
+
test('empty blockquote lines get a trailing space so Discord keeps the quote', () => {
|
|
417
|
+
const text = '**1. To a@b.com**\n\n> Hi Mohammad,\n>\n> Your subscription is cancelled.\n>\n> Tommy\n\nDone.'
|
|
418
|
+
const segments = renderMarkdown(text)
|
|
419
|
+
expect(segments.map((segment) => ('markdown' in segment ? JSON.stringify(segment.markdown.split('\n')) : segment.kind)))
|
|
420
|
+
.toMatchInlineSnapshot(`
|
|
421
|
+
[
|
|
422
|
+
"["**1. To a@b.com**","","> Hi Mohammad,","> ","> Your subscription is cancelled.","> ","> Tommy","","Done."]",
|
|
423
|
+
]
|
|
424
|
+
`)
|
|
425
|
+
})
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
// ├─ table ──▶ table segment (Components V2 later)
|
|
6
6
|
// ├─ heading depth > 3 ──▶ clamped to ###
|
|
7
7
|
// ├─ list with code items ──▶ code lifted after its item
|
|
8
|
+
// ├─ blockquote ──▶ bare ">" lines become "> "
|
|
8
9
|
// └─ other nodes ──▶ original source slices, packed into ≤ limit
|
|
9
10
|
//
|
|
10
11
|
// Text is sliced from the original source by node offsets, never
|
|
11
12
|
// re-serialized, so the model's markdown reaches Discord unchanged. Only
|
|
12
13
|
// oversized nodes are split: code by lines (each piece a valid fence), lists
|
|
13
|
-
// by items, paragraphs by sentences, then words.
|
|
14
|
+
// by items, paragraphs by lines, sentences, then words.
|
|
14
15
|
|
|
15
16
|
import type { Code, List, ListItem, Root, RootContent, Table } from 'mdast'
|
|
16
17
|
import { fromMarkdown } from 'mdast-util-from-markdown'
|
|
@@ -162,16 +163,17 @@ function codePieces(source: string, code: Code, limit: number): string[] {
|
|
|
162
163
|
return codeBlocks({ fence, lang: code.lang ?? '', value: code.value, limit })
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
//
|
|
166
|
+
// Lines first, then sentences, then words, then a hard cut.
|
|
166
167
|
function splitText(text: string, limit: number): string[] {
|
|
167
168
|
if (text.length <= limit) return [text]
|
|
168
|
-
|
|
169
|
-
const
|
|
169
|
+
// Every unit keeps its trailing whitespace, so packing joins them as they were.
|
|
170
|
+
const units = text.split(/(?<=\n)/).flatMap((line) => (line.length <= limit ? [line] : line.split(/(?<=[.!?]\s+)(?=\S)/)))
|
|
171
|
+
const byWords = units.flatMap((unit) => (unit.length <= limit ? [unit] : unit.split(/(?<=\s)(?=\S)/)))
|
|
170
172
|
const parts = byWords.flatMap((unit) => {
|
|
171
173
|
if (unit.length <= limit) return [unit]
|
|
172
174
|
return Array.from({ length: Math.ceil(unit.length / limit) }, (_, index) => unit.slice(index * limit, (index + 1) * limit))
|
|
173
175
|
})
|
|
174
|
-
return pack(parts, limit, '
|
|
176
|
+
return pack(parts, limit, '')
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
function pack(parts: string[], limit: number, separator: string): string[] {
|
|
@@ -217,7 +219,8 @@ function nodePieces(source: string, node: RootContent, limit: number): string[]
|
|
|
217
219
|
return unnestList(source, node, limit).flatMap((piece) => (piece.length <= limit ? [piece] : splitText(piece, limit)))
|
|
218
220
|
}
|
|
219
221
|
if (node.type === 'code') return codePieces(source, node, limit)
|
|
220
|
-
|
|
222
|
+
// Discord quotes need "> " plus a space; a bare ">" line shows as a literal ">".
|
|
223
|
+
const text = node.type === 'blockquote' ? slice(source, node).replace(/^( *>)$/gm, '$1 ') : slice(source, node)
|
|
221
224
|
if (text.length <= limit) return [text]
|
|
222
225
|
if (node.type === 'list') {
|
|
223
226
|
const items = node.children.map((item) => slice(source, item))
|
|
@@ -161,15 +161,16 @@ test('kimaki --gateway without a TTY installs, onboards and reports ready on std
|
|
|
161
161
|
`)
|
|
162
162
|
|
|
163
163
|
const kimakiChannel = channels.find((channel) => channel.name === 'kimaki')!
|
|
164
|
-
|
|
165
|
-
await waitForFooter({ discord: twin.discord, threadId:
|
|
166
|
-
expect(await twin.discord.
|
|
164
|
+
// Coordinator mode is on by default: onboarding answers in the channel.
|
|
165
|
+
await waitForFooter({ discord: twin.discord, threadId: kimakiChannel.id })
|
|
166
|
+
expect(await twin.discord.channel(kimakiChannel.id).text()).toMatchInlineSnapshot(`
|
|
167
167
|
"--- from: assistant (TestBot)
|
|
168
|
-
**Kimaki** lets you code from Discord. Each project channel is linked to a folder on your computer.
|
|
169
|
-
Reply
|
|
168
|
+
**Kimaki** lets you code from Discord. Each project channel is linked to a folder on your computer. Write in the channel: one long session answers there and starts threads for the work.
|
|
169
|
+
Reply here to add channels for your projects. <@200000000000000001>
|
|
170
170
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
171
|
+
-# Coordinator mode is on: this channel is one session that starts and manages project threads for you. Use /new-session for a thread without it. Turn it off with /toggle-coordinator or start kimaki with \`--coordinator false\`.
|
|
171
172
|
Want channels for your projects?
|
|
172
|
-
-# *
|
|
173
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
173
174
|
`)
|
|
174
175
|
|
|
175
176
|
// `kimaki restart`: the supervisor starts a new bot process, which reports ready again.
|
|
@@ -13,7 +13,8 @@ import { ChannelType } from 'discord.js'
|
|
|
13
13
|
import { afterAll, beforeAll, expect, test } from 'vitest'
|
|
14
14
|
|
|
15
15
|
import type { BotHandle } from './main.ts'
|
|
16
|
-
import {
|
|
16
|
+
import { shellQuote } from './lock-server.ts'
|
|
17
|
+
import { runOnboarding } from './onboarding.ts'
|
|
17
18
|
import {
|
|
18
19
|
TEST_USER_ID,
|
|
19
20
|
scriptedTurn,
|
|
@@ -40,9 +41,7 @@ beforeAll(async () => {
|
|
|
40
41
|
dataDir = tempDataDir()
|
|
41
42
|
twin = await startTwin({ gateway: true })
|
|
42
43
|
const require = createRequire(import.meta.url)
|
|
43
|
-
kimaki =
|
|
44
|
-
command: [process.execPath, require.resolve('tsx/cli'), path.join(import.meta.dirname, 'cli.ts')],
|
|
45
|
-
})
|
|
44
|
+
kimaki = [process.execPath, require.resolve('tsx/cli'), path.join(import.meta.dirname, 'cli.ts')].map(shellQuote).join(' ')
|
|
46
45
|
otherProject = path.join(dataDir, 'other-project')
|
|
47
46
|
fs.mkdirSync(otherProject)
|
|
48
47
|
execFileSync('git', ['init', '-q'], { cwd: otherProject })
|
|
@@ -90,7 +89,7 @@ async function guildChannels() {
|
|
|
90
89
|
test('onboarding thread adds a project channel through the kimaki CLI, which answers at once', async () => {
|
|
91
90
|
const { discord } = twin
|
|
92
91
|
const guild = bot.discord.guilds.cache.get(discord.guildId)!
|
|
93
|
-
const onboarded = await runOnboarding({ bot, dataDir, guild,
|
|
92
|
+
const onboarded = await runOnboarding({ bot, dataDir, guild, gateway: true, installerId: TEST_USER_ID, machine: 'test-machine' })
|
|
94
93
|
if (!onboarded || onboarded instanceof Error) throw new Error(`onboarding failed: ${onboarded?.message}`, { cause: onboarded })
|
|
95
94
|
await waitForFooter({ discord, threadId: onboarded.threadId })
|
|
96
95
|
|
|
@@ -108,14 +107,14 @@ test('onboarding thread adds a project channel through the kimaki CLI, which ans
|
|
|
108
107
|
Reply in the thread below to add channels for your projects. <@200000000000000001>
|
|
109
108
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
110
109
|
Want channels for your projects? I can search for git repositories.
|
|
111
|
-
-# *
|
|
110
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
112
111
|
--- from: user (tommy)
|
|
113
112
|
yes, add-other-project
|
|
114
113
|
--- from: assistant (TestBot)
|
|
115
114
|
-# ┣ shell _add project channel_
|
|
116
115
|
|
|
117
116
|
Added the channel for other-project.
|
|
118
|
-
-# *
|
|
117
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
119
118
|
`)
|
|
120
119
|
expect(await guildChannels()).toMatchInlineSnapshot(`
|
|
121
120
|
[
|
|
@@ -143,7 +142,7 @@ test('onboarding thread adds a project channel through the kimaki CLI, which ans
|
|
|
143
142
|
await waitForFooter({ discord, threadId: thread.id })
|
|
144
143
|
|
|
145
144
|
// Second start: nothing is created again.
|
|
146
|
-
expect(await runOnboarding({ bot, dataDir, guild,
|
|
145
|
+
expect(await runOnboarding({ bot, dataDir, guild, gateway: true, machine: 'test-machine' })).toBe(null)
|
|
147
146
|
await waitFor({ label: 'no extra channels', check: async () => (await guildChannels()).length === 6 })
|
|
148
147
|
|
|
149
148
|
// An empty default channel means an earlier onboarding failed: retry in the same channel.
|
|
@@ -151,7 +150,7 @@ test('onboarding thread adds a project channel through the kimaki CLI, which ans
|
|
|
151
150
|
const channel = await bot.discord.channels.fetch(defaultChannel.channel_id)
|
|
152
151
|
if (channel?.type !== ChannelType.GuildText) throw new Error('default channel is not a text channel')
|
|
153
152
|
for (const message of (await channel.messages.fetch()).values()) await message.delete()
|
|
154
|
-
const retried = await runOnboarding({ bot, dataDir, guild,
|
|
153
|
+
const retried = await runOnboarding({ bot, dataDir, guild, gateway: true, installerId: TEST_USER_ID, machine: 'test-machine' })
|
|
155
154
|
if (retried instanceof Error || !retried) throw new Error(`expected a retried onboarding, got ${retried}`)
|
|
156
155
|
expect(retried.channelId).toBe(defaultChannel.channel_id)
|
|
157
156
|
await waitForFooter({ discord, threadId: retried.threadId })
|
|
@@ -163,7 +162,6 @@ test('an upgraded setup with project mappings skips onboarding without selecting
|
|
|
163
162
|
expect(await runOnboarding({
|
|
164
163
|
bot,
|
|
165
164
|
dataDir: path.join(dataDir, 'upgraded-layout'),
|
|
166
|
-
kimaki,
|
|
167
165
|
gateway: true,
|
|
168
166
|
machine: 'test-machine',
|
|
169
167
|
})).toBe(null)
|
package/src/onboarding.ts
CHANGED
|
@@ -25,8 +25,8 @@ import { ConfigError, DbError, DiscordError, OpenCodeMissingError, type OpenCode
|
|
|
25
25
|
import { NOTIFY_MESSAGE_FLAGS } from './format-parts.ts'
|
|
26
26
|
import { createLogger } from './logger.ts'
|
|
27
27
|
import { checkOpencode, findOpencodeBinary, installedOpencodeBinary, OPENCODE_INSTALL_COMMAND } from './opencode-server.ts'
|
|
28
|
-
import type
|
|
29
|
-
import { startSession } from './sessions.ts'
|
|
28
|
+
import { isCoordinatorChannel, type Bot } from './bot.ts'
|
|
29
|
+
import { startCoordinator, startSession } from './sessions.ts'
|
|
30
30
|
import { addProjectChannel, defaultChannelName, defaultProjectDirectory } from './project.ts'
|
|
31
31
|
|
|
32
32
|
const logger = createLogger('ONBOARD')
|
|
@@ -130,11 +130,7 @@ export function startCaffeinate(): void {
|
|
|
130
130
|
child.unref()
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
export function
|
|
134
|
-
return /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function onboardingPrompt({ kimaki }: { kimaki: string }): string {
|
|
133
|
+
export function onboardingPrompt(): string {
|
|
138
134
|
return dedent`
|
|
139
135
|
This is the Kimaki onboarding thread. Kimaki maps Discord channels to project folders on this computer:
|
|
140
136
|
a message in a project channel starts a coding session in that folder.
|
|
@@ -145,21 +141,15 @@ export function onboardingPrompt({ kimaki }: { kimaki: string }): string {
|
|
|
145
141
|
searching for git repositories on this computer.
|
|
146
142
|
2. If they agree, search. Only list folders that exist, most recently changed first, at most 15:
|
|
147
143
|
\`find ~/Documents ~/Projects ~/projects ~/code ~/dev ~/src ~/repos ~/GitHub ~/github -maxdepth 3 -name .git -prune 2>/dev/null\`
|
|
148
|
-
Skip folders that already have a channel:
|
|
144
|
+
Skip folders that already have a channel: \`kimaki project list\`
|
|
149
145
|
3. Show the folders as a numbered list and ask the user to reply with the numbers to add.
|
|
150
|
-
4. Add each chosen folder with its absolute path:
|
|
146
|
+
4. Add each chosen folder with its absolute path: \`kimaki project add <absolute-directory>\`
|
|
151
147
|
The command prints the new channel as a mention like <#123>. Reply with the list of new channels.
|
|
152
148
|
|
|
153
149
|
Never add a channel the user did not choose. Keep every message short.
|
|
154
150
|
`
|
|
155
151
|
}
|
|
156
152
|
|
|
157
|
-
// The shell command the agent runs to call this Kimaki install.
|
|
158
|
-
// No --data-dir: agent shells get KIMAKI_DATA_DIR from the plugin, and not every command takes the flag.
|
|
159
|
-
export function kimakiShellCommand({ command }: { command: readonly string[] }): string {
|
|
160
|
-
return command.map(shellQuote).join(' ')
|
|
161
|
-
}
|
|
162
|
-
|
|
163
153
|
class GitInitError extends errore.createTaggedError({
|
|
164
154
|
name: 'GitInitError',
|
|
165
155
|
message: 'Could not create $directory',
|
|
@@ -191,7 +181,6 @@ export async function runOnboarding({
|
|
|
191
181
|
guild,
|
|
192
182
|
guildId,
|
|
193
183
|
installUrl,
|
|
194
|
-
kimaki,
|
|
195
184
|
gateway,
|
|
196
185
|
installerId,
|
|
197
186
|
machine,
|
|
@@ -201,8 +190,6 @@ export async function runOnboarding({
|
|
|
201
190
|
guild?: Guild
|
|
202
191
|
guildId?: string
|
|
203
192
|
installUrl?: string
|
|
204
|
-
// Shell command for `kimaki`, from kimakiShellCommand().
|
|
205
|
-
kimaki: string
|
|
206
193
|
gateway: boolean
|
|
207
194
|
// --machine-name, else defaultMachineName().
|
|
208
195
|
machine: string
|
|
@@ -266,11 +253,12 @@ export async function runOnboarding({
|
|
|
266
253
|
.fetch(installerId ?? selectedGuild.ownerId)
|
|
267
254
|
.catch((e) => new DiscordError({ operation: 'fetch installer', cause: e }))
|
|
268
255
|
if (owner instanceof Error) return owner
|
|
256
|
+
const coordinator = isCoordinatorChannel(bot, channel.channelId)
|
|
269
257
|
const welcome = await textChannel
|
|
270
258
|
.send({
|
|
271
259
|
content: dedent`
|
|
272
|
-
**Kimaki** lets you code from Discord. Each project channel is linked to a folder on your computer. A message there starts an AI coding session in that folder.
|
|
273
|
-
Reply in the thread below to add channels for your projects. <@${owner.id}>
|
|
260
|
+
**Kimaki** lets you code from Discord. Each project channel is linked to a folder on your computer. ${coordinator ? 'Write in the channel: one long session answers there and starts threads for the work.' : 'A message there starts an AI coding session in that folder.'}
|
|
261
|
+
Reply ${coordinator ? 'here' : 'in the thread below'} to add channels for your projects. <@${owner.id}>
|
|
274
262
|
`,
|
|
275
263
|
allowedMentions: { users: [owner.id] },
|
|
276
264
|
flags: NOTIFY_MESSAGE_FLAGS,
|
|
@@ -278,15 +266,11 @@ export async function runOnboarding({
|
|
|
278
266
|
.catch((e) => new DiscordError({ operation: 'send welcome message', cause: e }))
|
|
279
267
|
if (welcome instanceof Error) return welcome
|
|
280
268
|
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
messageId: welcome.id,
|
|
287
|
-
threadName: 'Kimaki onboarding',
|
|
288
|
-
worktree: false,
|
|
289
|
-
})
|
|
269
|
+
const author = { id: owner.id, username: owner.user.username }
|
|
270
|
+
const route = { kind: 'steer' as const, text: onboardingPrompt() }
|
|
271
|
+
const session = coordinator
|
|
272
|
+
? await startCoordinator(bot, { channel: textChannel, route, author, messageId: welcome.id })
|
|
273
|
+
: await startSession(bot, { channelId: channel.channelId, directory, route, author, messageId: welcome.id, threadName: 'Kimaki onboarding', worktree: false })
|
|
290
274
|
if (session instanceof Error) {
|
|
291
275
|
// Leaves the channel empty, so the next start onboards again.
|
|
292
276
|
await welcome.delete().catch((e) => logger.warn('delete welcome message', e))
|
|
@@ -132,7 +132,7 @@ test('Accept runs the command', async () => {
|
|
|
132
132
|
✓ _Accepted_
|
|
133
133
|
|
|
134
134
|
accept turn done
|
|
135
|
-
-# *
|
|
135
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
136
136
|
`)
|
|
137
137
|
})
|
|
138
138
|
|
|
@@ -163,7 +163,7 @@ test('Deny stops the run, the next message is answered', async () => {
|
|
|
163
163
|
Go on after-deny
|
|
164
164
|
--- from: assistant (TestBot)
|
|
165
165
|
continued after deny
|
|
166
|
-
-# *
|
|
166
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
167
167
|
`)
|
|
168
168
|
})
|
|
169
169
|
|
|
@@ -186,6 +186,6 @@ test('a subagent permission shows in the parent thread', async () => {
|
|
|
186
186
|
✓ _Accepted always_
|
|
187
187
|
|
|
188
188
|
parent done
|
|
189
|
-
-# *
|
|
189
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
190
190
|
`)
|
|
191
191
|
})
|