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
|
@@ -47,6 +47,7 @@ test('catalog names are sanitized, keep their suffix, skip collisions and MCP pr
|
|
|
47
47
|
"fork-subagent",
|
|
48
48
|
"btw",
|
|
49
49
|
"abort",
|
|
50
|
+
"reset-coordinator",
|
|
50
51
|
"queue",
|
|
51
52
|
"clear-queue",
|
|
52
53
|
"queue-command",
|
|
@@ -60,6 +61,7 @@ test('catalog names are sanitized, keep their suffix, skip collisions and MCP pr
|
|
|
60
61
|
"model",
|
|
61
62
|
"model-variant",
|
|
62
63
|
"verbosity",
|
|
64
|
+
"toggle-coordinator",
|
|
63
65
|
"routines",
|
|
64
66
|
"command",
|
|
65
67
|
"skill",
|
package/src/slash-commands.ts
CHANGED
|
@@ -322,25 +322,27 @@ async function sendInput(
|
|
|
322
322
|
const target = await resolveTarget(bot, interaction.channelId)
|
|
323
323
|
if (target instanceof Error) return replyError(interaction, target)
|
|
324
324
|
const author = authorOf(interaction)
|
|
325
|
-
|
|
325
|
+
// In a coordinator channel an agent pick starts a new thread: the coordinator keeps its own agent and model.
|
|
326
|
+
const newThread = route.kind === 'steer' && Boolean(route.agent) && target.conversation !== null && !target.conversation.isThread()
|
|
327
|
+
if (target.conversation && target.sessionId && !newThread) {
|
|
326
328
|
const reply = await replyWithEcho(interaction, { author, text: echo })
|
|
327
329
|
if (reply instanceof Error) return replyError(interaction, reply)
|
|
328
330
|
if (model && !(route.kind === 'steer' && route.agent)) {
|
|
329
331
|
const switched = await switchModel(bot, { sessionId: target.sessionId, model })
|
|
330
332
|
if (switched instanceof Error) return replyError(interaction, switched)
|
|
331
333
|
}
|
|
332
|
-
const result = await dispatch(bot, {
|
|
334
|
+
const result = await dispatch(bot, { conversation: target.conversation, route, author, messageId: reply.id, model })
|
|
333
335
|
if (result instanceof Error) return replyError(interaction, result)
|
|
334
336
|
return
|
|
335
337
|
}
|
|
336
|
-
if (target.
|
|
338
|
+
if (target.conversation && !newThread) {
|
|
337
339
|
await interaction.reply({ content: 'Use this command in a thread with a Kimaki session', flags: MessageFlags.Ephemeral })
|
|
338
340
|
return
|
|
339
341
|
}
|
|
340
342
|
await interaction.deferReply()
|
|
341
343
|
const started = await startSession(bot, {
|
|
342
344
|
channelId: target.channelId,
|
|
343
|
-
directory: target.
|
|
345
|
+
directory: target.projectDirectory,
|
|
344
346
|
route,
|
|
345
347
|
author,
|
|
346
348
|
messageId: interaction.id,
|
package/src/sleep.e2e.test.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// `kimaki sleep` end to end (spec 10.5, 12.1, Phase 8): the agent runs the
|
|
2
2
|
// command through the shim, the bot stores the wake with its clock, the
|
|
3
3
|
// scheduler wakes the same thread, and a user message cancels the sleep.
|
|
4
|
+
// Sleeps of at most 45 minutes keep the prompt cache warm (plugin/coordinator.ts).
|
|
4
5
|
|
|
5
6
|
import fs from 'node:fs'
|
|
7
|
+
import path from 'node:path'
|
|
6
8
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
7
9
|
import type { DeterministicMatcher } from 'opencode-deterministic-provider'
|
|
8
10
|
import { afterAll, beforeAll, expect, test } from 'vitest'
|
|
@@ -18,12 +20,15 @@ import {
|
|
|
18
20
|
tempDataDir,
|
|
19
21
|
textParts,
|
|
20
22
|
toolParts,
|
|
23
|
+
waitFor,
|
|
21
24
|
waitForFooter,
|
|
22
25
|
warmUp,
|
|
23
26
|
type OpencodeTestServer,
|
|
24
27
|
type TestTwin,
|
|
25
28
|
} from './test/harness.ts'
|
|
26
29
|
|
|
30
|
+
const WARM_INTERVAL_MS = 200
|
|
31
|
+
|
|
27
32
|
function sleepTurn({ marker, command }: { marker: string; command: string }): DeterministicMatcher[] {
|
|
28
33
|
const callId = `${marker}-call`
|
|
29
34
|
return [
|
|
@@ -44,16 +49,16 @@ let bot: BotHandle
|
|
|
44
49
|
|
|
45
50
|
beforeAll(async () => {
|
|
46
51
|
;[server, twin] = await Promise.all([
|
|
47
|
-
startOpencodeTestServer({ matchers: [
|
|
48
|
-
...sleepTurn({ marker: 'sleep-marker', command: "kimaki sleep --duration
|
|
49
|
-
...sleepTurn({ marker: 'cancel-marker', command: 'kimaki sleep --until 2030-01-
|
|
52
|
+
startOpencodeTestServer({ plugins: [path.resolve('src/test/generate-log-plugin')], matchers: [
|
|
53
|
+
...sleepTurn({ marker: 'sleep-marker', command: "kimaki sleep --duration 30m --reason 'waiting for the deploy'" }),
|
|
54
|
+
...sleepTurn({ marker: 'cancel-marker', command: 'kimaki sleep --until 2030-01-01T10:30:00Z' }),
|
|
50
55
|
{ id: 'wake', priority: 200, when: { latestUserTextIncludes: 'Woke after sleeping until' }, then: { parts: textParts('Awake, checking the deploy.') } },
|
|
51
56
|
{ id: 'never-mind', priority: 200, when: { latestUserTextIncludes: 'Never mind' }, then: { parts: textParts('Noted, no wake needed.') } },
|
|
52
57
|
] }),
|
|
53
58
|
startTwin(),
|
|
54
59
|
])
|
|
55
60
|
await seedProjectChannel({ dataDir, channelId: twin.channelId, guildId: twin.discord.guildId, directory: server.projectDirectory })
|
|
56
|
-
bot = await startTestBot({ dataDir, twin, server, clock, schedulerIntervalMs: null })
|
|
61
|
+
bot = await startTestBot({ dataDir, twin, server, clock, schedulerIntervalMs: null, warmupTimers: { intervalMs: WARM_INTERVAL_MS, maxMs: 60_000 } })
|
|
57
62
|
await warmUp({ server })
|
|
58
63
|
}, 60_000)
|
|
59
64
|
|
|
@@ -72,17 +77,26 @@ async function start(prompt: string) {
|
|
|
72
77
|
return { threadId: thread.id, sessionId: bot.store.getState().roots[thread.id]! }
|
|
73
78
|
}
|
|
74
79
|
|
|
80
|
+
// Keep-alive requests of a session, from the test plugin.
|
|
81
|
+
function keepAlives(sessionId: string): number {
|
|
82
|
+
const file = path.join(server.root, 'generate.log')
|
|
83
|
+
if (!fs.existsSync(file)) return 0
|
|
84
|
+
return fs.readFileSync(file, 'utf8').split('\n').filter((line) => line === sessionId).length
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
async function sleepRow(sessionId: string) {
|
|
76
88
|
return bot.db.query.session_sleeps.findFirst({ where: { session_id: sessionId } })
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
test('kimaki sleep wakes the same thread at the clock time', async () => {
|
|
80
92
|
const { threadId, sessionId } = await start('Deploy and then sleep-marker')
|
|
81
|
-
expect((await sleepRow(sessionId))?.wake_at.toISOString()).toBe('2030-01-
|
|
93
|
+
expect((await sleepRow(sessionId))?.wake_at.toISOString()).toBe('2030-01-01T09:30:00.000Z')
|
|
94
|
+
// A 30 minute sleep stays warm until the wake.
|
|
95
|
+
await waitFor({ label: 'keep-alive while sleeping', check: async () => keepAlives(sessionId) > 0 || null })
|
|
82
96
|
|
|
83
|
-
clock.set(Date.parse('2030-01-
|
|
97
|
+
clock.set(Date.parse('2030-01-01T09:29:59Z'))
|
|
84
98
|
await bot.scheduler.runDueTasks()
|
|
85
|
-
clock.set(Date.parse('2030-01-
|
|
99
|
+
clock.set(Date.parse('2030-01-01T09:30:00Z'))
|
|
86
100
|
await bot.scheduler.runDueTasks()
|
|
87
101
|
await waitForFooter({ discord: twin.discord, threadId, count: 2 })
|
|
88
102
|
expect(await twin.discord.thread(threadId).text()).toMatchInlineSnapshot(`
|
|
@@ -95,10 +109,10 @@ test('kimaki sleep wakes the same thread at the clock time', async () => {
|
|
|
95
109
|
-# ┣ shell _Sleep until the deploy is done_
|
|
96
110
|
|
|
97
111
|
Sleeping until the deploy is done.
|
|
98
|
-
-# *
|
|
99
|
-
-# Woke after sleeping until 2030-01-01
|
|
112
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
113
|
+
-# Woke after sleeping until 2030-01-01 09:30 UTC. Reason: waiting for the deploy
|
|
100
114
|
Awake, checking the deploy.
|
|
101
|
-
-# *
|
|
115
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
102
116
|
`)
|
|
103
117
|
const row = await sleepRow(sessionId)
|
|
104
118
|
expect(row?.status).toBe('consumed')
|
|
@@ -116,11 +130,14 @@ test('kimaki sleep wakes the same thread at the clock time', async () => {
|
|
|
116
130
|
test('a user message cancels the sleep', async () => {
|
|
117
131
|
const { threadId, sessionId } = await start('Deploy and then cancel-marker')
|
|
118
132
|
expect((await sleepRow(sessionId))?.status).toBe('planned')
|
|
133
|
+
// The clock is at 09:30 after the first test: a 1 hour sleep is too long to keep warm, no keep-alive after a few intervals.
|
|
134
|
+
await sleep(WARM_INTERVAL_MS * 3)
|
|
135
|
+
expect(keepAlives(sessionId)).toBe(0)
|
|
119
136
|
await twin.discord.thread(threadId).user(TEST_USER_ID).sendMessage({ content: 'Never mind, it is deployed' })
|
|
120
137
|
await waitForFooter({ discord: twin.discord, threadId, count: 2 })
|
|
121
138
|
expect((await sleepRow(sessionId))?.status).toBe('cancelled')
|
|
122
139
|
|
|
123
|
-
clock.set(Date.parse('2030-01-
|
|
140
|
+
clock.set(Date.parse('2030-01-01T10:30:00Z'))
|
|
124
141
|
await bot.scheduler.runDueTasks()
|
|
125
142
|
// Nothing may appear: poll briefly instead of a wait helper.
|
|
126
143
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
@@ -138,11 +155,11 @@ test('a user message cancels the sleep', async () => {
|
|
|
138
155
|
-# ┣ shell _Sleep until the deploy is done_
|
|
139
156
|
|
|
140
157
|
Sleeping until the deploy is done.
|
|
141
|
-
-# *
|
|
158
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
142
159
|
--- from: user (tommy)
|
|
143
160
|
Never mind, it is deployed
|
|
144
161
|
--- from: assistant (TestBot)
|
|
145
162
|
Noted, no wake needed.
|
|
146
|
-
-# *
|
|
163
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
147
164
|
`)
|
|
148
165
|
})
|
package/src/sleeps.ts
CHANGED
|
@@ -9,7 +9,7 @@ import crypto from 'node:crypto'
|
|
|
9
9
|
import * as orm from 'drizzle-orm'
|
|
10
10
|
import dedent from 'string-dedent'
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { fetchConversation, threadOfSession, type Bot } from './bot.ts'
|
|
13
13
|
import { parseDuration } from './duration.ts'
|
|
14
14
|
import { ConfigError, DbError } from './errors.ts'
|
|
15
15
|
import { asSubtext } from './format-parts.ts'
|
|
@@ -21,6 +21,8 @@ const logger = createLogger('TASK')
|
|
|
21
21
|
|
|
22
22
|
const WAKE_RETRY_MS = 30_000
|
|
23
23
|
const WAKE_MAX_ATTEMPTS = 5
|
|
24
|
+
// Longer sleeps let the prompt cache expire: past ~45 minutes, keep-alives (0.1x input each) cost more than one cache write at the wake (1.25x).
|
|
25
|
+
const WARM_SLEEP_MAX_MS = 45 * 60_000
|
|
24
26
|
|
|
25
27
|
type SleepRow = typeof schema.session_sleeps.$inferSelect
|
|
26
28
|
|
|
@@ -58,7 +60,7 @@ export function sleepOutput({ wakeAt, reason }: { wakeAt: number; reason: string
|
|
|
58
60
|
return [
|
|
59
61
|
`Sleeping until ${utcLabel(wakeAt)}.${reason ? ` Reason: ${reason}.` : ''}`,
|
|
60
62
|
'This result is not a wake. Do not continue the waited work. Do not run more commands.',
|
|
61
|
-
'
|
|
63
|
+
'If this turn has no text yet that answers the user, write your full answer now: text you only planned in your reasoning never reaches Discord. Then write one short line that you are waiting until that time, and stop.',
|
|
62
64
|
'The real wake is a later message that starts with "Woke after sleeping until". Only then continue the wait reason.',
|
|
63
65
|
'A new user message in this thread cancels the sleep. If you still need that later wake after answering, run kimaki sleep again with --until set to the same UTC time.',
|
|
64
66
|
'When monitoring something slow (email replies, PR reviews), wait 2h or more and never post a status report on a wake where nothing changed.',
|
|
@@ -144,6 +146,19 @@ export async function createSleep(
|
|
|
144
146
|
return { sessionId: root, threadId, wakeAt: new Date(wakeAt).toISOString(), output: sleepOutput({ wakeAt, reason: why }) }
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
// The session waits for a wake at most 45 minutes after its sleep started: the plugin
|
|
150
|
+
// keeps its prompt cache warm until then (plugin/coordinator.ts, `session.warmup` route).
|
|
151
|
+
export async function sleepsWarm(bot: Bot, sessionId: string): Promise<DbError | boolean> {
|
|
152
|
+
const row = await bot.db.query.session_sleeps
|
|
153
|
+
.findFirst({ where: { session_id: sessionId, status: 'planned' } })
|
|
154
|
+
.catch((cause) => new DbError({ operation: 'read session_sleeps', cause }))
|
|
155
|
+
if (row instanceof Error) return row
|
|
156
|
+
// Sleeps imported from V1 may have no start time.
|
|
157
|
+
if (!row?.created_at) return false
|
|
158
|
+
const wakeAt = row.wake_at.getTime()
|
|
159
|
+
return wakeAt > bot.clock.now() && wakeAt - row.created_at.getTime() <= WARM_SLEEP_MAX_MS
|
|
160
|
+
}
|
|
161
|
+
|
|
147
162
|
// Delivers a due wake if it is still the planned sleep and its retry delay
|
|
148
163
|
// passed. The prompt ID comes from the delivery ID, so a retried wake cannot
|
|
149
164
|
// deliver twice: OpenCode returns the existing inbox item for a repeated ID
|
|
@@ -184,7 +199,7 @@ async function wake(bot: Bot, row: SleepRow): Promise<Error | void> {
|
|
|
184
199
|
return settle('failed')
|
|
185
200
|
}
|
|
186
201
|
const sent = await (async () => {
|
|
187
|
-
const thread = await
|
|
202
|
+
const thread = await fetchConversation(bot, threadId)
|
|
188
203
|
if (thread instanceof Error) return thread
|
|
189
204
|
return prompt(bot, {
|
|
190
205
|
sessionId,
|
|
@@ -196,6 +211,8 @@ async function wake(bot: Bot, row: SleepRow): Promise<Error | void> {
|
|
|
196
211
|
author: { id: bot.discord.user!.id, username: 'kimaki' },
|
|
197
212
|
messageId: deliveryId,
|
|
198
213
|
id: `msg_sleep_${deliveryId}`,
|
|
214
|
+
// The session asked for this wake itself.
|
|
215
|
+
sender: { sessionId },
|
|
199
216
|
})
|
|
200
217
|
})()
|
|
201
218
|
if (!(sent instanceof Error)) return settle('consumed')
|
package/src/store.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The only shared mutable in-memory state of the bot (spec 27.4). One zustand
|
|
2
|
-
// atom. The event loop writes bindings and views; /verbosity writes verbosity
|
|
2
|
+
// atom. The event loop writes bindings and views; /verbosity writes verbosity,
|
|
3
|
+
// /toggle-coordinator writes coordinator.
|
|
3
4
|
// State private to one module (held events, wizard picks, upload waits) stays
|
|
4
5
|
// in that module's closure.
|
|
5
6
|
|
|
@@ -17,10 +18,12 @@ export type State = {
|
|
|
17
18
|
threads: Readonly<Record<string, ThreadView>>
|
|
18
19
|
// channelId -> verbosity, from channel_verbosity (SQLite). Missing: the default.
|
|
19
20
|
verbosity: Readonly<Record<string, Verbosity>>
|
|
21
|
+
// channelId -> coordinator mode, from channel_coordinator (SQLite). Missing: bot.coordinatorDefault.
|
|
22
|
+
coordinator: Readonly<Record<string, boolean>>
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export function createBotStore() {
|
|
23
|
-
return createStore<State>(() => ({ roots: {}, sessionThreads: {}, threads: {}, verbosity: {} }))
|
|
26
|
+
return createStore<State>(() => ({ roots: {}, sessionThreads: {}, threads: {}, verbosity: {}, coordinator: {} }))
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export type BotStore = ReturnType<typeof createBotStore>
|