kimaki 1.4.0 → 1.6.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 +10 -1
- package/dist/bot.js +14 -6
- package/dist/bot.js.map +1 -1
- package/dist/btw.e2e.test.js +3 -3
- package/dist/builtin-skills.d.ts +1 -1
- package/dist/builtin-skills.js +1 -1
- package/dist/builtin-skills.js.map +1 -1
- package/dist/builtin-skills.test.js +3 -4
- package/dist/builtin-skills.test.js.map +1 -1
- package/dist/catalog-refresh.e2e.test.js +2 -2
- package/dist/cli/bot.js +25 -14
- 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 +4 -5
- 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.js +135 -0
- package/dist/coordinator-agents.e2e.test.js.map +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.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 +75 -0
- 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 +1 -1
- package/dist/event-loop.js +73 -43
- 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 +68 -13
- 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 +68 -16
- 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 +4 -2
- package/dist/main.js +13 -3
- package/dist/main.js.map +1 -1
- package/dist/markdown/markdown.test.js +4 -4
- package/dist/markdown/render-markdown.js +6 -5
- 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 +76 -17
- package/dist/plugin/index.js.map +1 -1
- 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/prompt.d.ts +21 -8
- 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 +1 -1
- 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 +146 -0
- 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 +19 -7
- package/dist/session-events.js +166 -22
- package/dist/session-events.js.map +1 -1
- package/dist/session-ops.e2e.test.js +88 -11
- package/dist/session-ops.e2e.test.js.map +1 -1
- package/dist/sessions.d.ts +41 -18
- package/dist/sessions.js +174 -63
- package/dist/sessions.js.map +1 -1
- package/dist/shell.e2e.test.js +4 -4
- package/dist/slash-commands.js +12 -6
- 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 +1 -0
- 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 +3 -1
- package/dist/system-prompt.js +138 -64
- package/dist/system-prompt.js.map +1 -1
- 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 -1
- package/dist/test/harness.js +51 -10
- package/dist/test/harness.js.map +1 -1
- package/dist/test/replay.js +11 -7
- package/dist/test/replay.js.map +1 -1
- package/dist/thread-reducer.d.ts +29 -3
- package/dist/thread-reducer.fixtures.test.js +58 -51
- package/dist/thread-reducer.fixtures.test.js.map +1 -1
- package/dist/thread-reducer.js +112 -25
- package/dist/thread-reducer.js.map +1 -1
- package/dist/thread-reducer.test.js +197 -24
- package/dist/thread-reducer.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.js +80 -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 +12 -6
- 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 +29 -5
- package/src/btw.e2e.test.ts +3 -3
- package/src/builtin-skills.test.ts +3 -4
- package/src/builtin-skills.ts +1 -1
- package/src/catalog-refresh.e2e.test.ts +2 -2
- package/src/cli/bot.ts +23 -14
- 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 +168 -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 -42
- 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 +62 -12
- 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 +22 -7
- package/src/markdown/markdown.test.ts +4 -4
- package/src/markdown/render-markdown.ts +6 -5
- 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 +68 -15
- 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 +160 -21
- package/src/session-ops.e2e.test.ts +90 -10
- package/src/sessions.ts +214 -64
- package/src/shell.e2e.test.ts +4 -4
- package/src/slash-commands.test.ts +2 -0
- package/src/slash-commands.ts +12 -6
- 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 +143 -63
- package/src/test/generate-log-plugin/index.ts +16 -0
- package/src/test/harness.ts +77 -14
- package/src/test/replay.ts +12 -6
- package/src/thread-reducer.fixtures.test.ts +58 -51
- package/src/thread-reducer.test.ts +207 -26
- package/src/thread-reducer.ts +138 -37
- 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 +101 -26
- package/src/working-directory.e2e.test.ts +11 -11
- package/dist/message-tool.e2e.test.js +0 -39
- package/dist/message-tool.e2e.test.js.map +0 -1
- package/dist/plugin/read-media.d.ts +0 -23
- package/dist/plugin/read-media.js +0 -72
- package/dist/plugin/read-media.js.map +0 -1
- package/dist/plugin/session-id/index.js +0 -18
- package/dist/plugin/session-id/index.js.map +0 -1
- package/dist/probe-live.tmp.js +0 -82
- package/dist/probe-live.tmp.js.map +0 -1
- package/dist/tasks.e2e.test.js +0 -173
- package/dist/tasks.e2e.test.js.map +0 -1
- /package/dist/{message-tool.e2e.test.d.ts → coordinator-agents.e2e.test.d.ts} +0 -0
- /package/dist/{probe-live.tmp.d.ts → coordinator-idle.e2e.test.d.ts} +0 -0
- /package/dist/{tasks.e2e.test.d.ts → coordinator-queue.e2e.test.d.ts} +0 -0
- /package/dist/{plugin/session-id → test/generate-log-plugin}/index.d.ts +0 -0
package/src/main.ts
CHANGED
|
@@ -8,21 +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
12
|
import type { PromptSection } from './builtin-skills.ts'
|
|
13
13
|
import { openDb } from './db.ts'
|
|
14
14
|
import { createEffectsRunner } from './effects.ts'
|
|
15
15
|
import { DiscordError } from './errors.ts'
|
|
16
|
-
import { createEventLoop } from './event-loop.ts'
|
|
16
|
+
import { createEventLoop, type RunEnded } from './event-loop.ts'
|
|
17
17
|
import { registerIngress } from './ingress.ts'
|
|
18
18
|
import { runLockRoute } from './lock-routes.ts'
|
|
19
19
|
import { installShim, type LockServer } from './lock-server.ts'
|
|
20
20
|
import { createLogger } from './logger.ts'
|
|
21
|
+
import { createCoordinatorNotes } from './coordinator.ts'
|
|
21
22
|
import { installPluginShim, watchOpencode, type OpencodeEndpoint } from './opencode-server.ts'
|
|
22
23
|
import { countUserProjects } from './project.ts'
|
|
23
24
|
import { createScheduler, systemClock, type Clock } from './scheduler.ts'
|
|
24
25
|
import { createEventRecorder, syncSessionTranscript } from './session-events.ts'
|
|
25
|
-
import { createPluginWait, refreshCliContext } from './sessions.ts'
|
|
26
|
+
import { channelDefaults, createPluginWait, refreshCliContext } from './sessions.ts'
|
|
26
27
|
import { createInteractionRegistry, registerSlashCommands } from './slash-commands.ts'
|
|
27
28
|
import { createSleepLock } from './sleeps.ts'
|
|
28
29
|
import { createBotStore } from './store.ts'
|
|
@@ -53,8 +54,9 @@ export type StartBotOptions = {
|
|
|
53
54
|
// OpenCode's SQLite file (opencodeDbFile()): voice transcription reads the ChatGPT credential from it.
|
|
54
55
|
// Required, so tests never read the developer's real credentials.
|
|
55
56
|
opencodeDbFile: string
|
|
56
|
-
//
|
|
57
|
-
|
|
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[]
|
|
58
60
|
// Product analytics sink (createAnalytics or disabledAnalytics).
|
|
59
61
|
analytics: Analytics
|
|
60
62
|
// Voice transcription API base URLs; tests point Gemini at a local fake.
|
|
@@ -71,6 +73,10 @@ export type StartBotOptions = {
|
|
|
71
73
|
disabledSkills?: readonly string[]
|
|
72
74
|
// Static instruction parts left out of new sessions (system-prompt.ts).
|
|
73
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
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
export type BotHandle = Bot & {
|
|
@@ -141,6 +147,7 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
141
147
|
const result = await oc({ opencode }, 'mark session viewed', (client) => client.session.view({ sessionID: sessionId, idle }))
|
|
142
148
|
if (result instanceof Error) return result
|
|
143
149
|
},
|
|
150
|
+
footerDefaults: (channelId) => channelDefaults({ db, opencode }, channelId),
|
|
144
151
|
})
|
|
145
152
|
cleanup.defer(() => effects.stop())
|
|
146
153
|
const voiceCalls = createVoiceCalls({ opencodeConfigDir: options.opencodeConfigDir })
|
|
@@ -149,7 +156,12 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
149
156
|
if (result instanceof Error) logger.warn(`transcript of ${sessionId} not synced`, result)
|
|
150
157
|
})
|
|
151
158
|
}
|
|
152
|
-
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 })
|
|
153
165
|
const loaded = await eventLoop.load()
|
|
154
166
|
if (loaded instanceof Error) return loaded
|
|
155
167
|
// Before the service is used, so a service started by ensure() loads it at once.
|
|
@@ -163,7 +175,7 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
163
175
|
onDisconnect: eventLoop.onDisconnect,
|
|
164
176
|
})
|
|
165
177
|
cleanup.defer(() => opencode.stop())
|
|
166
|
-
const shim = await installShim({ dataDir: options.dataDir,
|
|
178
|
+
const shim = await installShim({ dataDir: options.dataDir, argv: options.kimakiArgv })
|
|
167
179
|
if (shim instanceof Error) return shim
|
|
168
180
|
// The features that non-interaction code reaches through bot.features.
|
|
169
181
|
const agentUi = createAgentUi({ store, eventLoop, opencode })
|
|
@@ -189,6 +201,8 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
189
201
|
allowAllUsers: options.allowAllUsers ?? false,
|
|
190
202
|
disabledSkills: options.disabledSkills ?? [],
|
|
191
203
|
disabledPrompt: options.disabledPrompt ?? [],
|
|
204
|
+
coordinatorDefault: options.coordinator ?? true,
|
|
205
|
+
warmupTimers: options.warmupTimers ?? null,
|
|
192
206
|
features: { withSleepLock: createSleepLock(), waitForPlugin: createPluginWait({ opencode }), agentUi, voiceCalls },
|
|
193
207
|
}
|
|
194
208
|
const scheduler = createScheduler({
|
|
@@ -201,6 +215,7 @@ export async function startBot(options: StartBotOptions): Promise<Error | BotHan
|
|
|
201
215
|
// Before login: gateway-proxy replays missed messages right after READY.
|
|
202
216
|
registerIngress(bot)
|
|
203
217
|
voiceCalls.register(bot)
|
|
218
|
+
coordinatorNotes.register(bot)
|
|
204
219
|
// Before discord.destroy() (registered earlier, so disposed later): leaving voice needs the gateway.
|
|
205
220
|
cleanup.defer(() => voiceCalls.stop(bot))
|
|
206
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
|
`)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// Text is sliced from the original source by node offsets, never
|
|
12
12
|
// re-serialized, so the model's markdown reaches Discord unchanged. Only
|
|
13
13
|
// oversized nodes are split: code by lines (each piece a valid fence), lists
|
|
14
|
-
// by items, paragraphs by sentences, then words.
|
|
14
|
+
// by items, paragraphs by lines, sentences, then words.
|
|
15
15
|
|
|
16
16
|
import type { Code, List, ListItem, Root, RootContent, Table } from 'mdast'
|
|
17
17
|
import { fromMarkdown } from 'mdast-util-from-markdown'
|
|
@@ -163,16 +163,17 @@ function codePieces(source: string, code: Code, limit: number): string[] {
|
|
|
163
163
|
return codeBlocks({ fence, lang: code.lang ?? '', value: code.value, limit })
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
//
|
|
166
|
+
// Lines first, then sentences, then words, then a hard cut.
|
|
167
167
|
function splitText(text: string, limit: number): string[] {
|
|
168
168
|
if (text.length <= limit) return [text]
|
|
169
|
-
|
|
170
|
-
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)/)))
|
|
171
172
|
const parts = byWords.flatMap((unit) => {
|
|
172
173
|
if (unit.length <= limit) return [unit]
|
|
173
174
|
return Array.from({ length: Math.ceil(unit.length / limit) }, (_, index) => unit.slice(index * limit, (index + 1) * limit))
|
|
174
175
|
})
|
|
175
|
-
return pack(parts, limit, '
|
|
176
|
+
return pack(parts, limit, '')
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
function pack(parts: string[], limit: number, separator: string): string[] {
|
|
@@ -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
|
})
|