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
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
// Mainmast channels: the channel is the conversation of one long session, it
|
|
2
|
+
// starts worker threads with `kimaki send --parent-session`, and a finished
|
|
3
|
+
// worker wakes it with a <session-finished> note.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { afterAll, beforeAll, expect, test } from 'vitest';
|
|
7
|
+
import { send } from "./prompt.js";
|
|
8
|
+
import { readSessionMarkdown } from "./session-events.js";
|
|
9
|
+
import { TEST_USER_ID, scriptedTurn, seedProjectChannel, startOpencodeTestServer, startTestBot, startTwin, tempDataDir, textParts, waitFor, waitForFooter, warmUp, } from "./test/harness.js";
|
|
10
|
+
let server;
|
|
11
|
+
let twin;
|
|
12
|
+
let bot;
|
|
13
|
+
const dataDir = tempDataDir();
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
// The twin first: the delegating matcher needs its channel ID.
|
|
16
|
+
twin = await startTwin();
|
|
17
|
+
server = await startOpencodeTestServer({
|
|
18
|
+
matchers: [
|
|
19
|
+
// The role text reaches the model once per request, also when the plugin checks the request.
|
|
20
|
+
{ id: 'role-twice', priority: 500, when: { latestUserTextIncludes: 'once-marker', rawPromptRegex: '## mainmast: you coordinate[\\s\\S]*## mainmast: you coordinate' }, then: { parts: textParts('Role text twice.') } },
|
|
21
|
+
{ id: 'role-missing', priority: 400, when: { latestUserTextIncludes: 'once-marker', rawPromptRegex: '^(?![\\s\\S]*## mainmast: you coordinate)' }, then: { parts: textParts('Role text missing.') } },
|
|
22
|
+
{ id: 'hello', priority: 100, when: { latestUserTextIncludes: 'hello-marker' }, then: { parts: textParts('Hello from the mainmast.') } },
|
|
23
|
+
// The mainmast delegates: one shell call to `kimaki send`, then IDLE (the new thread already shows).
|
|
24
|
+
...scriptedTurn({
|
|
25
|
+
marker: 'delegate-marker',
|
|
26
|
+
steps: [{
|
|
27
|
+
id: 'call-send-worker',
|
|
28
|
+
tool: 'shell',
|
|
29
|
+
input: {
|
|
30
|
+
command: `kimaki send --channel ${twin.channelId} --name 'Worker task' --prompt 'worker-marker do the job' --parent-session "$OPENCODE_SESSION_ID"`,
|
|
31
|
+
description: 'Start the worker thread',
|
|
32
|
+
hasSideEffect: true,
|
|
33
|
+
},
|
|
34
|
+
}],
|
|
35
|
+
finalText: 'IDLE',
|
|
36
|
+
}),
|
|
37
|
+
// The default: the mainmast waits for the worker in the same shell call.
|
|
38
|
+
...scriptedTurn({
|
|
39
|
+
marker: 'waitjob-marker',
|
|
40
|
+
steps: [{
|
|
41
|
+
id: 'call-send-wait',
|
|
42
|
+
tool: 'shell',
|
|
43
|
+
input: {
|
|
44
|
+
command: `kimaki send --channel ${twin.channelId} --name 'Waited task' --prompt 'waited-worker-marker do the job' --parent-session "$OPENCODE_SESSION_ID" --wait`,
|
|
45
|
+
description: 'Run the worker and wait',
|
|
46
|
+
hasSideEffect: true,
|
|
47
|
+
},
|
|
48
|
+
}],
|
|
49
|
+
finalText: 'The waited worker is done.',
|
|
50
|
+
}),
|
|
51
|
+
{ id: 'waited-worker', priority: 100, when: { latestUserTextIncludes: 'waited-worker-marker' }, then: { parts: textParts('Waited job done.') } },
|
|
52
|
+
// Slow enough that the delegating run ends first: the note then wakes an idle mainmast.
|
|
53
|
+
{ id: 'worker', priority: 100, when: { latestUserTextIncludes: 'worker-marker' }, then: { parts: textParts('Job done in the worker.'), defaultPartDelayMs: 300 } },
|
|
54
|
+
// A worker that uploads a screenshot; session read lists its URL, the note does not.
|
|
55
|
+
...scriptedTurn({
|
|
56
|
+
marker: 'upload-marker',
|
|
57
|
+
steps: [{ id: 'call-upload', tool: 'shell', input: { command: 'kimaki upload-to-discord shot.png', description: 'Upload the screenshot', hasSideEffect: true } }],
|
|
58
|
+
finalText: 'Took the screenshot.',
|
|
59
|
+
}),
|
|
60
|
+
{ id: 'woken-with-media', priority: 320, when: { latestUserTextRegex: '<session-finished[\\s\\S]*Took the screenshot' }, then: { parts: textParts('Here is the screenshot.') } },
|
|
61
|
+
{ id: 'user-steer', priority: 100, when: { latestUserTextIncludes: 'user-steer-marker' }, then: { parts: textParts('Steered job done.') } },
|
|
62
|
+
{ id: 'follow-up', priority: 100, when: { latestUserTextIncludes: 'follow-up-marker' }, then: { parts: textParts('Follow-up done.') } },
|
|
63
|
+
{ id: 'woken-by-follow-up', priority: 340, when: { latestUserTextRegex: '<session-finished[\\s\\S]*Follow-up done' }, then: { parts: textParts('The follow-up finished.') } },
|
|
64
|
+
{ id: 'fork', priority: 100, when: { latestUserTextIncludes: 'fork-marker' }, then: { parts: textParts('Fork job done.') } },
|
|
65
|
+
{ id: 'slow-job', priority: 100, when: { latestUserTextIncludes: 'slow-job-marker' }, then: { parts: textParts('Slow job done.'), defaultPartDelayMs: 500 } },
|
|
66
|
+
{ id: 'woken-by-slow-job', priority: 330, when: { latestUserTextRegex: '<session-finished[\\s\\S]*Slow job done' }, then: { parts: textParts('The slow job finished.') } },
|
|
67
|
+
{ id: 'woken-by-fork', priority: 310, when: { latestUserTextIncludes: '<session-finished', rawPromptIncludes: 'Fork job done.' }, then: { parts: textParts('The fork finished the job.') } },
|
|
68
|
+
{ id: 'btw', priority: 100, when: { latestUserTextIncludes: 'btw-marker' }, then: { parts: textParts('Side answer.') } },
|
|
69
|
+
{ id: 'side', priority: 100, when: { latestUserTextIncludes: 'side-marker' }, then: { parts: textParts('Side check done.') } },
|
|
70
|
+
// The silent note of the side thread is in the history only when it arrived.
|
|
71
|
+
{ id: 'recap-seen', priority: 200, when: { latestUserTextIncludes: 'recap-marker', rawPromptIncludes: 'Side check done.' }, then: { parts: textParts('A side thread finished meanwhile.') } },
|
|
72
|
+
{ id: 'recap-unseen', priority: 150, when: { latestUserTextIncludes: 'recap-marker' }, then: { parts: textParts('Nothing new.') } },
|
|
73
|
+
{ id: 'woken', priority: 300, when: { latestUserTextIncludes: '<session-finished' }, then: { parts: textParts('The worker finished the job.') } },
|
|
74
|
+
// A wake with a stale role text in the history: the plugin adds the current one to this request.
|
|
75
|
+
{ id: 'stale-wake-current', priority: 400, when: { latestUserTextRegex: 'stale-wake-marker[\\s\\S]*## mainmast: you coordinate this channel' }, then: { parts: textParts('Current role text present.') } },
|
|
76
|
+
{ id: 'stale-wake-old', priority: 350, when: { latestUserTextIncludes: 'stale-wake-marker' }, then: { parts: textParts('Current role text missing.') } },
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
await seedProjectChannel({ dataDir, channelId: twin.channelId, guildId: twin.discord.guildId, directory: server.projectDirectory });
|
|
80
|
+
await warmUp({ server });
|
|
81
|
+
bot = await startTestBot({ dataDir, twin, server, mainmast: true });
|
|
82
|
+
}, 60_000);
|
|
83
|
+
// Role texts the kimaki plugin stored in a session (plugin/mainmast.ts), oldest first.
|
|
84
|
+
async function roleTexts(sessionID) {
|
|
85
|
+
const client = await server.client();
|
|
86
|
+
const messages = await client.message.list({ sessionID, order: 'desc', limit: 100 });
|
|
87
|
+
return [...messages.data].reverse().flatMap((message) => (message.type === 'synthetic' && message.text.includes('<kimaki-mainmast>') ? [message.text] : []));
|
|
88
|
+
}
|
|
89
|
+
afterAll(async () => {
|
|
90
|
+
await bot?.stop();
|
|
91
|
+
await twin?.stop();
|
|
92
|
+
await server?.stop();
|
|
93
|
+
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
94
|
+
});
|
|
95
|
+
test('channel messages go to one session that replies in the channel', async () => {
|
|
96
|
+
const { discord, channelId } = twin;
|
|
97
|
+
await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Say hi hello-marker' });
|
|
98
|
+
await waitForFooter({ discord, threadId: channelId });
|
|
99
|
+
await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Again hello-marker once-marker' });
|
|
100
|
+
await waitForFooter({ discord, threadId: channelId, count: 2 });
|
|
101
|
+
expect(await discord.channel(channelId).text({ showReplies: true })).toMatchInlineSnapshot(`
|
|
102
|
+
"--- from: user (tommy)
|
|
103
|
+
Say hi hello-marker
|
|
104
|
+
--- from: assistant (TestBot)
|
|
105
|
+
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
106
|
+
[reply to tommy: Say hi hello-marker]
|
|
107
|
+
Hello from the mainmast.
|
|
108
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
109
|
+
--- from: user (tommy)
|
|
110
|
+
Again hello-marker once-marker
|
|
111
|
+
--- from: assistant (TestBot)
|
|
112
|
+
[reply to tommy: Again hello-marker once-marker]
|
|
113
|
+
Hello from the mainmast.
|
|
114
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
115
|
+
`);
|
|
116
|
+
expect(await discord.channel(channelId).getThreads()).toHaveLength(0);
|
|
117
|
+
// The coordinator role comes from the plugin, once: not from the frozen instructions entry.
|
|
118
|
+
const root = bot.store.getState().roots[channelId];
|
|
119
|
+
const roles = await roleTexts(root);
|
|
120
|
+
expect(roles).toHaveLength(1);
|
|
121
|
+
expect(roles[0]).toContain('## mainmast: you coordinate this channel');
|
|
122
|
+
expect(roles[0]).toContain(`--parent-session ${root}`);
|
|
123
|
+
const entries = await (await server.client()).session.instructions.entry.list({ sessionID: root });
|
|
124
|
+
expect(JSON.stringify(entries)).not.toContain('## mainmast');
|
|
125
|
+
});
|
|
126
|
+
test('a worker started with --parent-session gets a thread and wakes the mainmast when done', async () => {
|
|
127
|
+
const { discord, channelId } = twin;
|
|
128
|
+
const footers = (await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project'));
|
|
129
|
+
await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Fix it delegate-marker' });
|
|
130
|
+
const thread = await discord.channel(channelId).waitForThread({ timeout: 8_000 });
|
|
131
|
+
await waitForFooter({ discord, threadId: thread.id });
|
|
132
|
+
await waitFor({
|
|
133
|
+
label: 'woken reply',
|
|
134
|
+
check: async () => (await discord.channel(channelId).text()).includes('The worker finished the job.') || null,
|
|
135
|
+
});
|
|
136
|
+
// The dispatch turn answered IDLE after its tool line: only the woken report gets a footer.
|
|
137
|
+
await waitForFooter({ discord, threadId: channelId, count: footers.length + 1 });
|
|
138
|
+
// Only the first answer text of a user prompt replies to it: never tool lines, the wake answer or a worker thread.
|
|
139
|
+
expect((await discord.channel(channelId).text({ showReplies: true })).replaceAll(thread.id, 'worker-thread')).toMatchInlineSnapshot(`
|
|
140
|
+
"--- from: user (tommy)
|
|
141
|
+
Say hi hello-marker
|
|
142
|
+
--- from: assistant (TestBot)
|
|
143
|
+
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
144
|
+
[reply to tommy: Say hi hello-marker]
|
|
145
|
+
Hello from the mainmast.
|
|
146
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
147
|
+
--- from: user (tommy)
|
|
148
|
+
Again hello-marker once-marker
|
|
149
|
+
--- from: assistant (TestBot)
|
|
150
|
+
[reply to tommy: Again hello-marker once-marker]
|
|
151
|
+
Hello from the mainmast.
|
|
152
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
153
|
+
--- from: user (tommy)
|
|
154
|
+
Fix it delegate-marker
|
|
155
|
+
--- from: assistant (TestBot)
|
|
156
|
+
-# ┣ shell _Start the worker thread_
|
|
157
|
+
The worker finished the job.
|
|
158
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
159
|
+
`);
|
|
160
|
+
expect(await discord.thread(thread.id).text({ showReplies: true })).toMatchInlineSnapshot(`
|
|
161
|
+
"--- from: assistant (TestBot)
|
|
162
|
+
-# » **CLI:** worker-marker do the job
|
|
163
|
+
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
164
|
+
Job done in the worker.
|
|
165
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
166
|
+
`);
|
|
167
|
+
expect(thread.name).toBe('Worker task');
|
|
168
|
+
const workerRoles = await roleTexts(bot.store.getState().roots[thread.id]);
|
|
169
|
+
expect(workerRoles).toHaveLength(1);
|
|
170
|
+
expect(workerRoles[0]).toContain(`## worker of a mainmast`);
|
|
171
|
+
expect(workerRoles[0]).toContain(`kimaki session read ${channelId}`);
|
|
172
|
+
});
|
|
173
|
+
test('a btw fork of a worker sent with --parent-session wakes the mainmast when done', async () => {
|
|
174
|
+
const { discord, channelId } = twin;
|
|
175
|
+
const root = bot.store.getState().roots[channelId];
|
|
176
|
+
const worker = (await discord.channel(channelId).getThreads()).find((candidate) => candidate.name === 'Worker task');
|
|
177
|
+
if (!worker)
|
|
178
|
+
throw new Error('no Worker task thread');
|
|
179
|
+
const footers = (await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project')).length;
|
|
180
|
+
// What the coordinator runs: kimaki send --thread <worker> --prompt '... btw' --parent-session <mainmast>
|
|
181
|
+
const forked = await send(bot, { threadId: worker.id, prompt: 'fork-marker related job. btw', parentSessionId: root, senderSessionId: root });
|
|
182
|
+
if (forked instanceof Error)
|
|
183
|
+
throw forked;
|
|
184
|
+
await waitForFooter({ discord, threadId: forked.threadId });
|
|
185
|
+
await waitForFooter({ discord, threadId: channelId, count: footers + 1 });
|
|
186
|
+
const text = await discord.channel(channelId).text();
|
|
187
|
+
expect(text.slice(text.lastIndexOf('The worker finished the job.'))).toMatchInlineSnapshot(`
|
|
188
|
+
"The worker finished the job.
|
|
189
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
190
|
+
The fork finished the job.
|
|
191
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
192
|
+
`);
|
|
193
|
+
const fork = await (await server.client()).session.get({ sessionID: forked.sessionId });
|
|
194
|
+
expect(Reflect.get(Object(fork.metadata?.['kimaki']), 'parentSessionId')).toBe(root);
|
|
195
|
+
expect((await roleTexts(fork.id)).at(-1)).toContain('## worker of a mainmast');
|
|
196
|
+
});
|
|
197
|
+
test('session read lists the Discord URL of a file a worker uploaded; its note does not', async () => {
|
|
198
|
+
const { discord, channelId } = twin;
|
|
199
|
+
const root = bot.store.getState().roots[channelId];
|
|
200
|
+
fs.writeFileSync(path.join(server.projectDirectory, 'shot.png'), 'png');
|
|
201
|
+
const footers = (await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project')).length;
|
|
202
|
+
const started = await send(bot, { channelId, name: 'Screenshot task', prompt: 'upload-marker take a screenshot', parentSessionId: root, senderSessionId: root });
|
|
203
|
+
if (started instanceof Error)
|
|
204
|
+
throw started;
|
|
205
|
+
await waitForFooter({ discord, threadId: started.threadId });
|
|
206
|
+
await waitForFooter({ discord, threadId: channelId, count: footers + 1 });
|
|
207
|
+
const text = await discord.channel(channelId).text();
|
|
208
|
+
expect(text.slice(text.lastIndexOf('-# *project', text.lastIndexOf('-# *project') - 1))).toMatchInlineSnapshot(`
|
|
209
|
+
"-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
210
|
+
Here is the screenshot.
|
|
211
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
212
|
+
`);
|
|
213
|
+
const client = await server.client();
|
|
214
|
+
const messages = await client.message.list({ sessionID: root, order: 'desc', limit: 10 });
|
|
215
|
+
const note = messages.data.find((message) => message.type === 'synthetic' && message.text.includes('Screenshot task'));
|
|
216
|
+
expect(note?.type === 'synthetic' && note.text.replaceAll(started.sessionId, 'ses_worker').replaceAll(started.threadId, 'worker-thread')).toMatchInlineSnapshot(`
|
|
217
|
+
"<session-finished sessionID="ses_worker" threadID="worker-thread" state="completed" title="Screenshot task" wake="true">
|
|
218
|
+
Took the screenshot.
|
|
219
|
+
More: kimaki session read ses_worker
|
|
220
|
+
The user sees nothing else about this. When it is work they asked for, a failure or a decision they must make, tell them the outcome in a few plain sentences, with the thread link <#worker-thread> once. Otherwise reply exactly IDLE.
|
|
221
|
+
</session-finished>"
|
|
222
|
+
`);
|
|
223
|
+
const read = await readSessionMarkdown({ client, sessionId: started.sessionId });
|
|
224
|
+
expect(read instanceof Error ? read : read.slice(read.indexOf('Took the screenshot.'), read.indexOf('Only prompts'))).toMatchInlineSnapshot(`
|
|
225
|
+
"Took the screenshot.
|
|
226
|
+
|
|
227
|
+
Media uploaded to Discord:
|
|
228
|
+
- data:application/octet-stream;base64,cG5n
|
|
229
|
+
|
|
230
|
+
"
|
|
231
|
+
`);
|
|
232
|
+
// --full and transcripts hide tool outputs but keep the uploaded media on the tool line.
|
|
233
|
+
const full = await readSessionMarkdown({ client, sessionId: started.sessionId, full: true, thinking: true });
|
|
234
|
+
expect(full instanceof Error ? full : full.slice(full.indexOf('tool shell'), full.indexOf('finish: tool-calls')).trim()).toMatchInlineSnapshot(`
|
|
235
|
+
"tool shell [completed] {"command":"kimaki upload-to-discord shot.png","description":"Upload the screenshot","hasSideEffect":true}
|
|
236
|
+
Media uploaded to Discord:
|
|
237
|
+
- data:application/octet-stream;base64,cG5n"
|
|
238
|
+
`);
|
|
239
|
+
});
|
|
240
|
+
test('a run the user prompts in a worker thread is a silent note; a follow-up the mainmast sends wakes it', async () => {
|
|
241
|
+
const { discord, channelId } = twin;
|
|
242
|
+
const root = bot.store.getState().roots[channelId];
|
|
243
|
+
const client = await server.client();
|
|
244
|
+
const worker = (await discord.channel(channelId).getThreads()).find((candidate) => candidate.name === 'Worker task');
|
|
245
|
+
if (!worker)
|
|
246
|
+
throw new Error('no Worker task thread');
|
|
247
|
+
const footers = () => discord.channel(channelId).getMessages().then((messages) => messages.filter((message) => message.content.startsWith('-# *project')).length);
|
|
248
|
+
const before = await footers();
|
|
249
|
+
const workerFooters = (await discord.thread(worker.id).getMessages()).filter((message) => message.content.startsWith('-# *project')).length;
|
|
250
|
+
await discord.thread(worker.id).user(TEST_USER_ID).sendMessage({ content: 'Also check the logs user-steer-marker' });
|
|
251
|
+
await waitForFooter({ discord, threadId: worker.id, count: workerFooters + 1 });
|
|
252
|
+
const note = await waitFor({
|
|
253
|
+
label: 'silent note of the steered run',
|
|
254
|
+
check: async () => {
|
|
255
|
+
const inbox = await client.session.inbox.list({ sessionID: root });
|
|
256
|
+
const item = inbox.find((candidate) => candidate.type === 'synthetic' && candidate.payload.text.includes('Steered job done.'));
|
|
257
|
+
return item?.type === 'synthetic' ? item.payload.text : null;
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
expect(note.replaceAll(worker.id, 'worker-thread').replace(/ses_\w+/g, 'ses_worker')).toMatchInlineSnapshot(`
|
|
261
|
+
"<session-finished sessionID="ses_worker" threadID="worker-thread" state="completed" title="Worker task" wake="false">
|
|
262
|
+
<user-prompt username="tommy">
|
|
263
|
+
Also check the logs user-steer-marker
|
|
264
|
+
</user-prompt>
|
|
265
|
+
Steered job done.
|
|
266
|
+
More: kimaki session read ses_worker
|
|
267
|
+
</session-finished>"
|
|
268
|
+
`);
|
|
269
|
+
// Not woken: no new footer in the channel.
|
|
270
|
+
for (let i = 0; i < 10; i++) {
|
|
271
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
272
|
+
expect(await footers()).toBe(before);
|
|
273
|
+
}
|
|
274
|
+
// What the coordinator runs: kimaki send --thread <worker> --prompt '...', from its own shell.
|
|
275
|
+
const followed = await send(bot, { threadId: worker.id, prompt: 'Now the follow-up-marker', senderSessionId: root });
|
|
276
|
+
if (followed instanceof Error)
|
|
277
|
+
throw followed;
|
|
278
|
+
await waitForFooter({ discord, threadId: channelId, count: before + 1 });
|
|
279
|
+
const text = await discord.channel(channelId).text();
|
|
280
|
+
expect(text.slice(text.lastIndexOf('-# *project', text.lastIndexOf('-# *project') - 1))).toMatchInlineSnapshot(`
|
|
281
|
+
"-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
282
|
+
The follow-up finished.
|
|
283
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
284
|
+
`);
|
|
285
|
+
});
|
|
286
|
+
test('a worker started with --wait answers through the wait, with no note', async () => {
|
|
287
|
+
const { discord, channelId } = twin;
|
|
288
|
+
const footers = (await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project'));
|
|
289
|
+
const threads = (await discord.channel(channelId).getThreads()).length;
|
|
290
|
+
await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Fix it waitjob-marker' });
|
|
291
|
+
await waitForFooter({ discord, threadId: channelId, count: footers.length + 1 });
|
|
292
|
+
const thread = (await discord.channel(channelId).getThreads()).find((candidate) => candidate.name === 'Waited task');
|
|
293
|
+
if (!thread)
|
|
294
|
+
throw new Error('no Waited task thread');
|
|
295
|
+
await waitForFooter({ discord, threadId: thread.id });
|
|
296
|
+
const root = bot.store.getState().roots[channelId];
|
|
297
|
+
const client = await server.client();
|
|
298
|
+
// No note arrives: not in the inbox, no wake, no "finished" line.
|
|
299
|
+
for (let i = 0; i < 10; i++) {
|
|
300
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
301
|
+
const inbox = await client.session.inbox.list({ sessionID: root });
|
|
302
|
+
expect(inbox.filter((item) => item.type === 'synthetic' && item.payload.text.includes('Waited task'))).toHaveLength(0);
|
|
303
|
+
}
|
|
304
|
+
const text = (await discord.channel(channelId).text()).replaceAll(thread.id, 'worker-thread');
|
|
305
|
+
// The answer of the busy prompt, then the queued one starts: its IDLE answer posts nothing.
|
|
306
|
+
expect(text.slice(text.lastIndexOf('[reply to tommy: Take a while'))).toMatchInlineSnapshot(`"*"`);
|
|
307
|
+
expect(await discord.channel(channelId).getThreads()).toHaveLength(threads + 1);
|
|
308
|
+
const messages = await client.message.list({ sessionID: root, order: 'desc', limit: 20 });
|
|
309
|
+
expect(messages.data.some((message) => message.type === 'synthetic' && JSON.stringify(message).includes('Waited task'))).toBe(false);
|
|
310
|
+
});
|
|
311
|
+
test('another thread of the channel leaves a silent note for the next prompt', async () => {
|
|
312
|
+
const { discord, channelId } = twin;
|
|
313
|
+
const footers = (await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project'));
|
|
314
|
+
const started = await send(bot, { channelId, prompt: 'side-marker check something' });
|
|
315
|
+
if (started instanceof Error)
|
|
316
|
+
throw started;
|
|
317
|
+
await waitForFooter({ discord, threadId: started.threadId });
|
|
318
|
+
const client = await server.client();
|
|
319
|
+
const root = bot.store.getState().roots[channelId];
|
|
320
|
+
await waitFor({
|
|
321
|
+
label: 'silent note in the mainmast',
|
|
322
|
+
check: async () => {
|
|
323
|
+
// Not delivered yet: a note without wake waits in the inbox for the next run.
|
|
324
|
+
const inbox = await client.session.inbox.list({ sessionID: root });
|
|
325
|
+
return inbox.some((item) => item.type === 'synthetic' && item.payload.text.includes('wake="false"')) || null;
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
// Not woken: no new footer in the channel until the user writes.
|
|
329
|
+
expect((await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project'))).toHaveLength(footers.length);
|
|
330
|
+
await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'What happened? recap-marker' });
|
|
331
|
+
await waitForFooter({ discord, threadId: channelId, count: footers.length + 1 });
|
|
332
|
+
const text = await discord.channel(channelId).text();
|
|
333
|
+
expect(text.slice(text.lastIndexOf('--- from: user'))).toMatchInlineSnapshot(`
|
|
334
|
+
"--- from: user (tommy)
|
|
335
|
+
What happened? recap-marker
|
|
336
|
+
--- from: assistant (TestBot)
|
|
337
|
+
A side thread finished meanwhile.
|
|
338
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
339
|
+
`);
|
|
340
|
+
});
|
|
341
|
+
test('a btw fork of the mainmast is a worker: no coordinator timers', async () => {
|
|
342
|
+
const { discord, channelId } = twin;
|
|
343
|
+
const before = (await discord.channel(channelId).getThreads()).length;
|
|
344
|
+
await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Why that choice btw-marker. btw' });
|
|
345
|
+
const thread = await waitFor({
|
|
346
|
+
label: 'btw thread',
|
|
347
|
+
check: async () => (await discord.channel(channelId).getThreads()).slice(before).find((candidate) => candidate.name?.startsWith('btw:')) ?? null,
|
|
348
|
+
});
|
|
349
|
+
await waitForFooter({ discord, threadId: thread.id });
|
|
350
|
+
const client = await server.client();
|
|
351
|
+
const fork = await client.session.get({ sessionID: bot.store.getState().roots[thread.id] });
|
|
352
|
+
expect(Reflect.get(Object(fork.metadata?.['kimaki']), 'mainmast')).toBeUndefined();
|
|
353
|
+
// The fork copied the coordinator role; its own worker role comes after it.
|
|
354
|
+
const roles = await roleTexts(fork.id);
|
|
355
|
+
expect(roles.map((text) => (text.includes('## worker of a mainmast') ? 'worker' : 'coordinator'))).toEqual(['coordinator', 'worker']);
|
|
356
|
+
});
|
|
357
|
+
test('a stale coordinator role text is replaced: on a wake for that request, then stored at the next prompt', async () => {
|
|
358
|
+
const { discord, channelId } = twin;
|
|
359
|
+
const root = bot.store.getState().roots[channelId];
|
|
360
|
+
const client = await server.client();
|
|
361
|
+
const current = (await roleTexts(root)).at(-1);
|
|
362
|
+
const footers = (await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project')).length;
|
|
363
|
+
// Like a role text of older plugin code, as the latest one in the history, waking the session.
|
|
364
|
+
await client.session.synthetic({ sessionID: root, text: '<system-reminder>\n<kimaki-mainmast>\nold role text stale-wake-marker\n</kimaki-mainmast>\n</system-reminder>' });
|
|
365
|
+
await waitForFooter({ discord, threadId: channelId, count: footers + 1 });
|
|
366
|
+
await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Again hello-marker' });
|
|
367
|
+
await waitForFooter({ discord, threadId: channelId, count: footers + 2 });
|
|
368
|
+
const text = await discord.channel(channelId).text();
|
|
369
|
+
expect(text.slice(text.lastIndexOf('Session forked!')).replace(/<#\d+>/, '<#btw-thread>')).toMatchInlineSnapshot(`
|
|
370
|
+
"Session forked! Continue in <#btw-thread>
|
|
371
|
+
Current role text present.
|
|
372
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
373
|
+
--- from: user (tommy)
|
|
374
|
+
Again hello-marker
|
|
375
|
+
--- from: assistant (TestBot)
|
|
376
|
+
Hello from the mainmast.
|
|
377
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
378
|
+
`);
|
|
379
|
+
const roles = await roleTexts(root);
|
|
380
|
+
expect(roles.at(-1)).toBe(current);
|
|
381
|
+
expect(roles.at(-2)).toContain('stale-wake-marker');
|
|
382
|
+
});
|
|
383
|
+
test('the mainmast shows typing while a worker runs in the background', async () => {
|
|
384
|
+
const { discord, channelId } = twin;
|
|
385
|
+
const root = bot.store.getState().roots[channelId];
|
|
386
|
+
const footers = (await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project')).length;
|
|
387
|
+
discord.channel(channelId).clearTypingEvents();
|
|
388
|
+
const started = await send(bot, { channelId, name: 'Slow task', prompt: 'slow-job-marker take your time', parentSessionId: root, senderSessionId: root });
|
|
389
|
+
if (started instanceof Error)
|
|
390
|
+
throw started;
|
|
391
|
+
await discord.channel(channelId).waitForTypingEvent();
|
|
392
|
+
// Read when typing starts: it must come from the running worker, not from the wake after it.
|
|
393
|
+
const workerAtTyping = await discord.thread(started.threadId).text();
|
|
394
|
+
await waitForFooter({ discord, threadId: started.threadId });
|
|
395
|
+
await waitForFooter({ discord, threadId: channelId, count: footers + 1 });
|
|
396
|
+
const text = await discord.channel(channelId).text();
|
|
397
|
+
expect(text.slice(text.lastIndexOf('-# *project', text.lastIndexOf('-# *project') - 1))).toMatchInlineSnapshot(`
|
|
398
|
+
"-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
399
|
+
The slow job finished.
|
|
400
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
401
|
+
`);
|
|
402
|
+
expect(workerAtTyping).not.toContain('-# *project');
|
|
403
|
+
});
|
|
404
|
+
test('the mainmast survives a bot restart; /new-mainmast starts a fresh one', async () => {
|
|
405
|
+
const { discord, channelId } = twin;
|
|
406
|
+
const root = bot.store.getState().roots[channelId];
|
|
407
|
+
await bot.stop();
|
|
408
|
+
bot = await startTestBot({ dataDir, twin, server, mainmast: true });
|
|
409
|
+
expect(bot.store.getState().roots[channelId]).toBe(root);
|
|
410
|
+
const { id: interactionId } = await discord.channel(channelId).user(TEST_USER_ID).runSlashCommand({ name: 'new-mainmast' });
|
|
411
|
+
await discord.channel(channelId).waitForInteractionAck({ interactionId, timeout: 4_000 });
|
|
412
|
+
expect(bot.store.getState().roots[channelId]).toBeUndefined();
|
|
413
|
+
const footers = (await discord.channel(channelId).getMessages()).filter((message) => message.content.startsWith('-# *project')).length;
|
|
414
|
+
await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Fresh start hello-marker' });
|
|
415
|
+
await waitForFooter({ discord, threadId: channelId, count: footers + 1 });
|
|
416
|
+
const fresh = bot.store.getState().roots[channelId];
|
|
417
|
+
expect(fresh && fresh !== root).toBe(true);
|
|
418
|
+
const text = await discord.channel(channelId).text();
|
|
419
|
+
expect(text.slice(text.lastIndexOf('--- from: user'))).toMatchInlineSnapshot(`
|
|
420
|
+
"--- from: user (tommy)
|
|
421
|
+
Fresh start hello-marker
|
|
422
|
+
--- from: assistant (TestBot)
|
|
423
|
+
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
424
|
+
Hello from the mainmast.
|
|
425
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
426
|
+
`);
|
|
427
|
+
});
|
|
428
|
+
//# sourceMappingURL=mainmast.e2e.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mainmast.e2e.test.js","sourceRoot":"","sources":["../src/mainmast.e2e.test.ts"],"names":[],"mappings":"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"}
|
package/dist/mainmast.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Mainmast notes: when a thread session finishes a run, its mainmast learns
|
|
2
|
+
// about it, so the coordinator always knows what happened in its project.
|
|
3
|
+
//
|
|
4
|
+
// thread run ends ─▶ `kimaki send --wait` prompt, target still busy? ─▶ no note: the wait prints the answer
|
|
5
|
+
// ├─▶ run prompt sent by a mainmast (`sender`)? ─▶ note + wake that mainmast
|
|
6
|
+
// └─▶ else (user steered, routine), channel mainmast? ─▶ silent note (read at next prompt)
|
|
7
|
+
//
|
|
8
|
+
// Who sent the prompt is in its metadata (`sender`, prompt.ts), read from the
|
|
9
|
+
// run's messages: no state of its own. The note is a synthetic inbox item, like
|
|
10
|
+
// OpenCode's own subagent completion (core/src/session/subagent-completion.ts):
|
|
11
|
+
// `resume` decides if it wakes the session.
|
|
12
|
+
import { oc, projectOf, readMarker } from "./bot.js";
|
|
13
|
+
import { createLogger } from "./logger.js";
|
|
14
|
+
import { IDLE_REPLY } from "./plugin/mainmast.js";
|
|
15
|
+
import { finalAnswer } from "./session-events.js";
|
|
16
|
+
import { escapeAttribute } from "./system-prompt.js";
|
|
17
|
+
const logger = createLogger('MAINMAST');
|
|
18
|
+
// A woken coordinator reads the whole answer; a silent note only says what happened.
|
|
19
|
+
const WAKE_ANSWER_CHARS = 2_000;
|
|
20
|
+
const NOTE_ANSWER_CHARS = 400;
|
|
21
|
+
const USER_PROMPT_CHARS = 1_000;
|
|
22
|
+
const RUN_PAGE_MESSAGES = 30;
|
|
23
|
+
// A run longer than this only loses uploads from its first steps.
|
|
24
|
+
const MAX_RUN_PAGES = 10;
|
|
25
|
+
// The messages of the last run, oldest first: pages back until its user prompt.
|
|
26
|
+
async function lastRunMessages(bot, sessionId) {
|
|
27
|
+
const messages = [];
|
|
28
|
+
let cursor;
|
|
29
|
+
for (let page = 0; page < MAX_RUN_PAGES; page++) {
|
|
30
|
+
const result = await oc(bot, 'message.list', (client) => client.message.list({ sessionID: sessionId, limit: RUN_PAGE_MESSAGES, ...(cursor ? { cursor } : { order: 'desc' }) }));
|
|
31
|
+
if (result instanceof Error)
|
|
32
|
+
return result;
|
|
33
|
+
messages.push(...result.data);
|
|
34
|
+
cursor = result.cursor.next ?? undefined;
|
|
35
|
+
if (!cursor || result.data.some((message) => message.type === 'user'))
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
return messages.reverse();
|
|
39
|
+
}
|
|
40
|
+
// Who wrote the run's prompt, from its metadata (prompt.ts): a sender
|
|
41
|
+
// (`kimaki send`, routine, sleep wake) or a person in Discord.
|
|
42
|
+
function runPrompt(messages) {
|
|
43
|
+
const prompt = messages.findLast((message) => message.type === 'user');
|
|
44
|
+
if (prompt?.type !== 'user')
|
|
45
|
+
return { sender: null, user: null };
|
|
46
|
+
const sender = prompt.metadata?.['sender'];
|
|
47
|
+
if (sender && typeof sender === 'object') {
|
|
48
|
+
const sessionId = Reflect.get(sender, 'sessionId');
|
|
49
|
+
return { sender: { ...(typeof sessionId === 'string' && { sessionId }), waited: Reflect.get(sender, 'waited') === true }, user: null };
|
|
50
|
+
}
|
|
51
|
+
const discord = prompt.metadata?.['discord'];
|
|
52
|
+
const username = discord && typeof discord === 'object' ? Reflect.get(discord, 'username') : null;
|
|
53
|
+
return { sender: null, user: typeof username === 'string' ? { username, text: prompt.text } : null };
|
|
54
|
+
}
|
|
55
|
+
// The mainmast a session belongs to: the root of its conversation when that is
|
|
56
|
+
// a mainmast channel. The sender may be a subagent of that mainmast.
|
|
57
|
+
async function mainmastOf(bot, sessionId) {
|
|
58
|
+
const { sessionThreads, roots } = bot.store.getState();
|
|
59
|
+
const conversation = sessionThreads[sessionId];
|
|
60
|
+
if (!conversation || !(await isMainmastChannel(bot, conversation)))
|
|
61
|
+
return null;
|
|
62
|
+
return roots[conversation] ?? null;
|
|
63
|
+
}
|
|
64
|
+
function cut(text, max) {
|
|
65
|
+
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
66
|
+
}
|
|
67
|
+
// A conversation that is a project channel holds a mainmast.
|
|
68
|
+
async function isMainmastChannel(bot, conversationId) {
|
|
69
|
+
const project = await projectOf(bot, conversationId);
|
|
70
|
+
return !(project instanceof Error) && project !== null;
|
|
71
|
+
}
|
|
72
|
+
// The project channel of a session thread, from its view or from Discord.
|
|
73
|
+
async function channelOfThread(bot, threadId) {
|
|
74
|
+
const view = bot.store.getState().threads[threadId];
|
|
75
|
+
if (view)
|
|
76
|
+
return view.channelId;
|
|
77
|
+
const channel = await bot.discord.channels.fetch(threadId).catch(() => null);
|
|
78
|
+
return channel?.isThread() ? channel.parentId : null;
|
|
79
|
+
}
|
|
80
|
+
// The run's prompt came from `kimaki send --wait` and the target still runs:
|
|
81
|
+
// it is waiting in that shell call, which prints the answer. A target that is
|
|
82
|
+
// idle lost its wait (shell timeout, interrupt), so it gets the note.
|
|
83
|
+
async function isWaiting(bot, target) {
|
|
84
|
+
const active = await oc(bot, 'session.active', (client) => client.session.active());
|
|
85
|
+
// On a failed check the note goes out: a duplicate is better than a lost result.
|
|
86
|
+
if (active instanceof Error) {
|
|
87
|
+
logger.warn(`wait check of ${target}`, active);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return target in active;
|
|
91
|
+
}
|
|
92
|
+
async function noteRunEnded(bot, run) {
|
|
93
|
+
const channelId = await channelOfThread(bot, run.threadId);
|
|
94
|
+
// The mainmast's own runs, and threads outside any project channel.
|
|
95
|
+
if (!channelId || channelId === run.threadId)
|
|
96
|
+
return;
|
|
97
|
+
const [info, messages] = await Promise.all([
|
|
98
|
+
oc(bot, 'session.get', (client) => client.session.get({ sessionID: run.sessionId })),
|
|
99
|
+
lastRunMessages(bot, run.sessionId),
|
|
100
|
+
]);
|
|
101
|
+
if (info instanceof Error)
|
|
102
|
+
return logger.warn(`note of ${run.sessionId}`, info);
|
|
103
|
+
if (messages instanceof Error)
|
|
104
|
+
return logger.warn(`answer of ${run.sessionId}`, messages);
|
|
105
|
+
const marker = readMarker(info.metadata);
|
|
106
|
+
const { sender, user } = runPrompt(messages);
|
|
107
|
+
// TODO: also note sessions on other machines (remote `kimaki send`, OpenCode workspaces).
|
|
108
|
+
// Their bot cannot reach this OpenCode server: relay the note through a Discord envelope
|
|
109
|
+
// in the mainmast channel, like remote-send.ts does for prompts.
|
|
110
|
+
// Only a run a mainmast prompted wakes it. The user steering the thread, routines and
|
|
111
|
+
// other senders leave a silent note in the channel's mainmast.
|
|
112
|
+
const sentBy = sender?.sessionId ? await mainmastOf(bot, sender.sessionId) : null;
|
|
113
|
+
const wake = sentBy !== null;
|
|
114
|
+
const target = sentBy ?? bot.store.getState().roots[channelId];
|
|
115
|
+
if (!target || target === run.sessionId)
|
|
116
|
+
return;
|
|
117
|
+
if (sender?.waited && (await isWaiting(bot, target)))
|
|
118
|
+
return;
|
|
119
|
+
const answer = run.error ?? finalAnswer(messages) ?? 'Finished without a text answer.';
|
|
120
|
+
const state = run.error ? 'error' : 'completed';
|
|
121
|
+
const title = info.title || 'Untitled';
|
|
122
|
+
const routine = marker?.task ? ` routine="#${marker.task.id}"` : '';
|
|
123
|
+
const text = [
|
|
124
|
+
`<session-finished sessionID="${run.sessionId}" threadID="${run.threadId}" state="${state}" title="${escapeAttribute(title)}"${routine} wake="${wake}">`,
|
|
125
|
+
// The user steered this thread directly: the coordinator must know what they asked.
|
|
126
|
+
...(user ? [`<user-prompt username="${escapeAttribute(user.username)}">\n${cut(user.text, USER_PROMPT_CHARS)}\n</user-prompt>`] : []),
|
|
127
|
+
cut(answer, wake ? WAKE_ANSWER_CHARS : NOTE_ANSWER_CHARS),
|
|
128
|
+
`More: kimaki session read ${run.sessionId}`,
|
|
129
|
+
...(wake ? [`The user sees nothing else about this. When it is work they asked for, a failure or a decision they must make, tell them the outcome in a few plain sentences, with the thread link <#${run.threadId}> once. Otherwise reply exactly ${IDLE_REPLY}.`] : []),
|
|
130
|
+
'</session-finished>',
|
|
131
|
+
].join('\n');
|
|
132
|
+
const sent = await oc(bot, 'session.synthetic', (client) => client.session.synthetic({
|
|
133
|
+
sessionID: target,
|
|
134
|
+
text,
|
|
135
|
+
description: `${title} finished`,
|
|
136
|
+
metadata: { source: 'kimaki-session', sessionID: run.sessionId, threadID: run.threadId, title, state, wake },
|
|
137
|
+
resume: wake,
|
|
138
|
+
}));
|
|
139
|
+
if (sent instanceof Error)
|
|
140
|
+
return logger.warn(`note of ${run.sessionId} to ${target}`, sent);
|
|
141
|
+
logger.log(`${wake ? 'woke' : 'noted'} mainmast ${target}: ${run.sessionId} ${state}`);
|
|
142
|
+
}
|
|
143
|
+
export function createMainmastNotes() {
|
|
144
|
+
const registered = { bot: null };
|
|
145
|
+
return {
|
|
146
|
+
register(bot) {
|
|
147
|
+
registered.bot = bot;
|
|
148
|
+
},
|
|
149
|
+
runEnded(run) {
|
|
150
|
+
const bot = registered.bot;
|
|
151
|
+
if (bot?.mainmast)
|
|
152
|
+
void noteRunEnded(bot, run);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=mainmast.js.map
|