kimaki 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/fonts/Geist-Bold.ttf +0 -0
- package/assets/fonts/Geist-Regular.ttf +0 -0
- package/assets/fonts/GeistMono-Regular.ttf +0 -0
- package/assets/fonts/OFL.txt +92 -0
- package/dist/agent-ui.e2e.test.js +27 -13
- package/dist/agent-ui.e2e.test.js.map +1 -1
- package/dist/agent-ui.js +2 -1
- package/dist/agent-ui.js.map +1 -1
- package/dist/analytics.e2e.test.js +1 -1
- package/dist/analytics.e2e.test.js.map +1 -1
- package/dist/autostart.d.ts +4 -0
- package/dist/autostart.js +55 -10
- package/dist/autostart.js.map +1 -1
- package/dist/bot.d.ts +13 -6
- package/dist/bot.js +17 -15
- package/dist/bot.js.map +1 -1
- package/dist/btw.e2e.test.js +3 -3
- package/dist/builtin-skills.d.ts +3 -0
- package/dist/builtin-skills.js +29 -0
- package/dist/builtin-skills.js.map +1 -1
- package/dist/builtin-skills.test.js +72 -1
- package/dist/builtin-skills.test.js.map +1 -1
- package/dist/catalog-refresh.e2e.test.js +2 -2
- package/dist/cli/bot.d.ts +1 -1
- package/dist/cli/bot.js +33 -18
- package/dist/cli/bot.js.map +1 -1
- package/dist/cli/discord.js +32 -6
- package/dist/cli/discord.js.map +1 -1
- package/dist/cli/schedule.js +23 -9
- package/dist/cli/schedule.js.map +1 -1
- package/dist/cli/session.js +37 -12
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/shared.d.ts +5 -3
- package/dist/cli/shared.js +45 -10
- package/dist/cli/shared.js.map +1 -1
- package/dist/cli-discord.e2e.test.js +13 -8
- package/dist/cli-discord.e2e.test.js.map +1 -1
- package/dist/cli-lock.e2e.test.js +87 -20
- package/dist/cli-lock.e2e.test.js.map +1 -1
- package/dist/cli-read.e2e.test.js +18 -9
- package/dist/cli-read.e2e.test.js.map +1 -1
- package/dist/cli-sessions.e2e.test.js +4 -3
- package/dist/cli-sessions.e2e.test.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/preference-commands.d.ts +4 -0
- package/dist/commands/preference-commands.js +38 -4
- package/dist/commands/preference-commands.js.map +1 -1
- package/dist/commands/session-commands.js +11 -8
- package/dist/commands/session-commands.js.map +1 -1
- package/dist/commands/thread-commands.js +94 -24
- package/dist/commands/thread-commands.js.map +1 -1
- package/dist/commands/worktree-commands.d.ts +5 -6
- package/dist/commands/worktree-commands.js +8 -7
- package/dist/commands/worktree-commands.js.map +1 -1
- package/dist/commands.e2e.test.js +25 -21
- package/dist/commands.e2e.test.js.map +1 -1
- package/dist/coordinator-agents.e2e.test.d.ts +1 -0
- package/dist/coordinator-agents.e2e.test.js +113 -0
- package/dist/coordinator-agents.e2e.test.js.map +1 -0
- package/dist/coordinator-idle.e2e.test.d.ts +1 -0
- package/dist/coordinator-idle.e2e.test.js +100 -0
- package/dist/coordinator-idle.e2e.test.js.map +1 -0
- package/dist/coordinator-queue.e2e.test.d.ts +1 -0
- package/dist/coordinator-queue.e2e.test.js +244 -0
- package/dist/coordinator-queue.e2e.test.js.map +1 -0
- package/dist/coordinator-warmup.e2e.test.d.ts +1 -0
- package/dist/coordinator-warmup.e2e.test.js +131 -0
- package/dist/coordinator-warmup.e2e.test.js.map +1 -0
- package/dist/coordinator.d.ts +6 -0
- package/dist/coordinator.e2e.test.d.ts +1 -0
- package/dist/coordinator.e2e.test.js +505 -0
- package/dist/coordinator.e2e.test.js.map +1 -0
- package/dist/coordinator.js +164 -0
- package/dist/coordinator.js.map +1 -0
- package/dist/db.d.ts +78 -3
- package/dist/db.test.js +1 -0
- package/dist/db.test.js.map +1 -1
- package/dist/diagram.d.ts +11 -0
- package/dist/diagram.js +232 -0
- package/dist/diagram.js.map +1 -0
- package/dist/diagram.test.d.ts +1 -0
- package/dist/diagram.test.js +52 -0
- package/dist/diagram.test.js.map +1 -0
- package/dist/duration.js +13 -5
- package/dist/duration.js.map +1 -1
- package/dist/effects.d.ts +6 -1
- package/dist/effects.js +97 -26
- package/dist/effects.js.map +1 -1
- package/dist/event-loop.d.ts +2 -3
- package/dist/event-loop.js +74 -44
- package/dist/event-loop.js.map +1 -1
- package/dist/format-parts.d.ts +21 -4
- package/dist/format-parts.js +40 -23
- package/dist/format-parts.js.map +1 -1
- package/dist/gateway-replay.e2e.test.js +4 -4
- package/dist/harness.e2e.test.js +2 -2
- package/dist/harness.e2e.test.js.map +1 -1
- package/dist/hello-thread.e2e.test.js +56 -5
- package/dist/hello-thread.e2e.test.js.map +1 -1
- package/dist/ingress.js +47 -12
- package/dist/ingress.js.map +1 -1
- package/dist/interaction-context.d.ts +4 -4
- package/dist/interaction-context.js +9 -7
- package/dist/interaction-context.js.map +1 -1
- package/dist/interrupt.e2e.test.js +8 -5
- package/dist/interrupt.e2e.test.js.map +1 -1
- package/dist/legacy.e2e.test.js +1 -1
- package/dist/lock-routes.d.ts +83 -31
- package/dist/lock-routes.js +68 -8
- package/dist/lock-routes.js.map +1 -1
- package/dist/lock-server.d.ts +3 -2
- package/dist/lock-server.js +23 -7
- package/dist/lock-server.js.map +1 -1
- package/dist/main.d.ts +6 -3
- package/dist/main.js +14 -4
- package/dist/main.js.map +1 -1
- package/dist/mainmast-idle.e2e.test.d.ts +1 -0
- package/dist/mainmast-idle.e2e.test.js +99 -0
- package/dist/mainmast-idle.e2e.test.js.map +1 -0
- package/dist/mainmast-queue.e2e.test.d.ts +1 -0
- package/dist/mainmast-queue.e2e.test.js +244 -0
- package/dist/mainmast-queue.e2e.test.js.map +1 -0
- package/dist/mainmast-warmup.e2e.test.d.ts +1 -0
- package/dist/mainmast-warmup.e2e.test.js +130 -0
- package/dist/mainmast-warmup.e2e.test.js.map +1 -0
- package/dist/mainmast.d.ts +6 -0
- package/dist/mainmast.e2e.test.d.ts +1 -0
- package/dist/mainmast.e2e.test.js +428 -0
- package/dist/mainmast.e2e.test.js.map +1 -0
- package/dist/mainmast.js +156 -0
- package/dist/mainmast.js.map +1 -0
- package/dist/markdown/markdown.test.js +14 -4
- package/dist/markdown/markdown.test.js.map +1 -1
- package/dist/markdown/render-markdown.js +9 -6
- package/dist/markdown/render-markdown.js.map +1 -1
- package/dist/onboarding-cli.e2e.test.js +7 -6
- package/dist/onboarding-cli.e2e.test.js.map +1 -1
- package/dist/onboarding.d.ts +3 -10
- package/dist/onboarding.e2e.test.js +8 -10
- package/dist/onboarding.e2e.test.js.map +1 -1
- package/dist/onboarding.js +14 -24
- package/dist/onboarding.js.map +1 -1
- package/dist/permissions.e2e.test.js +3 -3
- package/dist/plugin/coordinator.d.ts +4 -0
- package/dist/plugin/coordinator.js +389 -0
- package/dist/plugin/coordinator.js.map +1 -0
- package/dist/plugin/index.js +93 -61
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/mainmast.d.ts +4 -0
- package/dist/plugin/mainmast.js +379 -0
- package/dist/plugin/mainmast.js.map +1 -0
- package/dist/plugin.e2e.test.js +1 -1
- package/dist/preference-commands.e2e.test.js +103 -24
- package/dist/preference-commands.e2e.test.js.map +1 -1
- package/dist/project.d.ts +1 -1
- package/dist/prompt.d.ts +22 -9
- package/dist/prompt.js +64 -48
- package/dist/prompt.js.map +1 -1
- package/dist/questions.e2e.test.js +13 -13
- package/dist/queue-sources.e2e.test.d.ts +1 -0
- package/dist/queue-sources.e2e.test.js +168 -0
- package/dist/queue-sources.e2e.test.js.map +1 -0
- package/dist/queue.d.ts +9 -1
- package/dist/queue.e2e.test.js +48 -30
- package/dist/queue.e2e.test.js.map +1 -1
- package/dist/queue.js +97 -24
- package/dist/queue.js.map +1 -1
- package/dist/queue.test.d.ts +1 -0
- package/dist/queue.test.js +57 -0
- package/dist/queue.test.js.map +1 -0
- package/dist/remote-send.e2e.test.js +6 -6
- package/dist/remote-send.js +2 -1
- package/dist/remote-send.js.map +1 -1
- package/dist/renderer.e2e.test.js +7 -7
- package/dist/restart.e2e.test.js +2 -2
- package/dist/routines.e2e.test.js +2 -2
- package/dist/scheduler.d.ts +3 -3
- package/dist/scheduler.e2e.test.js +43 -20
- package/dist/scheduler.e2e.test.js.map +1 -1
- package/dist/scheduler.js +4 -2
- package/dist/scheduler.js.map +1 -1
- package/dist/schema-sql.d.ts +1 -1
- package/dist/schema-sql.js +1 -1
- package/dist/schema-sql.js.map +1 -1
- package/dist/schema.d.ts +152 -6
- package/dist/schema.js +12 -0
- package/dist/schema.js.map +1 -1
- package/dist/session-commands.e2e.test.js +36 -11
- package/dist/session-commands.e2e.test.js.map +1 -1
- package/dist/session-events.d.ts +18 -6
- package/dist/session-events.js +175 -27
- package/dist/session-events.js.map +1 -1
- package/dist/session-ops.e2e.test.js +63 -12
- package/dist/session-ops.e2e.test.js.map +1 -1
- package/dist/sessions.d.ts +44 -21
- package/dist/sessions.js +177 -65
- package/dist/sessions.js.map +1 -1
- package/dist/shell.e2e.test.js +4 -4
- package/dist/slash-commands.js +6 -4
- package/dist/slash-commands.js.map +1 -1
- package/dist/slash-commands.test.js +2 -0
- package/dist/slash-commands.test.js.map +1 -1
- package/dist/sleep.e2e.test.js +29 -14
- package/dist/sleep.e2e.test.js.map +1 -1
- package/dist/sleeps.d.ts +2 -1
- package/dist/sleeps.js +21 -3
- package/dist/sleeps.js.map +1 -1
- package/dist/store.d.ts +1 -0
- package/dist/store.js +3 -2
- package/dist/store.js.map +1 -1
- package/dist/system-prompt.d.ts +5 -15
- package/dist/system-prompt.js +180 -131
- package/dist/system-prompt.js.map +1 -1
- package/dist/test/generate-log-plugin/index.d.ts +3 -0
- package/dist/test/generate-log-plugin/index.js +15 -0
- package/dist/test/generate-log-plugin/index.js.map +1 -0
- package/dist/test/harness.d.ts +20 -2
- package/dist/test/harness.js +51 -11
- package/dist/test/harness.js.map +1 -1
- package/dist/test/replay.js +11 -8
- package/dist/test/replay.js.map +1 -1
- package/dist/thread-reducer.d.ts +29 -6
- package/dist/thread-reducer.fixtures.test.js +58 -51
- package/dist/thread-reducer.fixtures.test.js.map +1 -1
- package/dist/thread-reducer.js +109 -50
- package/dist/thread-reducer.js.map +1 -1
- package/dist/thread-reducer.test.js +190 -48
- package/dist/thread-reducer.test.js.map +1 -1
- package/dist/tunnel.d.ts +9 -0
- package/dist/tunnel.js +117 -28
- package/dist/tunnel.js.map +1 -1
- package/dist/tunnel.test.js +81 -5
- package/dist/tunnel.test.js.map +1 -1
- package/dist/voice-attachments.e2e.test.js +108 -18
- package/dist/voice-attachments.e2e.test.js.map +1 -1
- package/dist/voice-call.e2e.test.js +9 -9
- package/dist/voice-call.js +6 -6
- package/dist/voice-call.js.map +1 -1
- package/dist/voice-openai.e2e.test.js +59 -23
- package/dist/voice-openai.e2e.test.js.map +1 -1
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +72 -24
- package/dist/voice.js.map +1 -1
- package/dist/working-directory.e2e.test.js +11 -11
- package/dist/working-directory.e2e.test.js.map +1 -1
- package/package.json +11 -5
- package/skills/kimaki-tunnel/SKILL.md +3 -2
- package/src/agent-ui.e2e.test.ts +27 -13
- package/src/agent-ui.ts +2 -1
- package/src/analytics.e2e.test.ts +1 -1
- package/src/autostart.ts +55 -10
- package/src/bot.ts +36 -19
- package/src/btw.e2e.test.ts +3 -3
- package/src/builtin-skills.test.ts +73 -1
- package/src/builtin-skills.ts +33 -0
- package/src/catalog-refresh.e2e.test.ts +2 -2
- package/src/cli/bot.ts +30 -19
- package/src/cli/discord.ts +30 -6
- package/src/cli/schedule.ts +23 -9
- package/src/cli/session.ts +35 -12
- package/src/cli/shared.ts +43 -8
- package/src/cli-discord.e2e.test.ts +13 -8
- package/src/cli-lock.e2e.test.ts +88 -20
- package/src/cli-read.e2e.test.ts +17 -8
- package/src/cli-sessions.e2e.test.ts +4 -3
- package/src/cli.ts +2 -0
- package/src/commands/preference-commands.ts +38 -4
- package/src/commands/session-commands.ts +12 -7
- package/src/commands/thread-commands.ts +92 -24
- package/src/commands/worktree-commands.ts +10 -9
- package/src/commands.e2e.test.ts +25 -21
- package/src/coordinator-agents.e2e.test.ts +145 -0
- package/src/coordinator-idle.e2e.test.ts +122 -0
- package/src/coordinator-queue.e2e.test.ts +279 -0
- package/src/coordinator-warmup.e2e.test.ts +158 -0
- package/src/coordinator.e2e.test.ts +545 -0
- package/src/coordinator.ts +168 -0
- package/src/db.test.ts +1 -0
- package/src/diagram.test.ts +56 -0
- package/src/diagram.ts +243 -0
- package/src/duration.ts +13 -5
- package/src/effects.ts +99 -27
- package/src/event-loop.ts +71 -45
- package/src/format-parts.ts +53 -22
- package/src/gateway-replay.e2e.test.ts +4 -4
- package/src/harness.e2e.test.ts +2 -2
- package/src/hello-thread.e2e.test.ts +59 -5
- package/src/ingress.ts +44 -11
- package/src/interaction-context.ts +12 -11
- package/src/interrupt.e2e.test.ts +8 -5
- package/src/legacy.e2e.test.ts +1 -1
- package/src/lock-routes.ts +62 -8
- package/src/lock-server.ts +23 -7
- package/src/main.ts +26 -10
- package/src/markdown/markdown.test.ts +15 -4
- package/src/markdown/render-markdown.ts +9 -6
- package/src/onboarding-cli.e2e.test.ts +7 -6
- package/src/onboarding.e2e.test.ts +8 -10
- package/src/onboarding.ts +13 -29
- package/src/permissions.e2e.test.ts +3 -3
- package/src/plugin/coordinator.ts +401 -0
- package/src/plugin/index.ts +86 -59
- package/src/plugin.e2e.test.ts +1 -1
- package/src/preference-commands.e2e.test.ts +103 -23
- package/src/prompt.ts +87 -43
- package/src/questions.e2e.test.ts +13 -13
- package/src/queue-sources.e2e.test.ts +197 -0
- package/src/queue.e2e.test.ts +48 -30
- package/src/queue.test.ts +59 -0
- package/src/queue.ts +104 -24
- package/src/remote-send.e2e.test.ts +6 -6
- package/src/remote-send.ts +2 -1
- package/src/renderer.e2e.test.ts +7 -7
- package/src/restart.e2e.test.ts +2 -2
- package/src/routines.e2e.test.ts +2 -2
- package/src/scheduler.e2e.test.ts +45 -19
- package/src/scheduler.ts +4 -2
- package/src/schema-sql.ts +1 -1
- package/src/schema.ts +13 -0
- package/src/session-commands.e2e.test.ts +38 -11
- package/src/session-events.ts +168 -25
- package/src/session-ops.e2e.test.ts +64 -11
- package/src/sessions.ts +217 -67
- package/src/shell.e2e.test.ts +4 -4
- package/src/slash-commands.test.ts +2 -0
- package/src/slash-commands.ts +6 -4
- package/src/sleep.e2e.test.ts +30 -13
- package/src/sleeps.ts +20 -3
- package/src/store.ts +5 -2
- package/src/system-prompt.ts +190 -134
- package/src/test/generate-log-plugin/index.ts +16 -0
- package/src/test/harness.ts +77 -17
- package/src/test/replay.ts +12 -7
- package/src/thread-reducer.fixtures.test.ts +58 -51
- package/src/thread-reducer.test.ts +199 -49
- package/src/thread-reducer.ts +135 -66
- package/src/tunnel.test.ts +81 -5
- package/src/tunnel.ts +124 -25
- package/src/voice-attachments.e2e.test.ts +110 -18
- package/src/voice-call.e2e.test.ts +9 -9
- package/src/voice-call.ts +6 -6
- package/src/voice-openai.e2e.test.ts +64 -24
- package/src/voice.ts +93 -26
- package/src/working-directory.e2e.test.ts +11 -11
- package/src/message-tool.e2e.test.ts +0 -45
package/src/cli-lock.e2e.test.ts
CHANGED
|
@@ -34,16 +34,23 @@ test('CLI send starts and continues a session through the authenticated bot API'
|
|
|
34
34
|
const first = await cli(['send', '--channel', twin.channelId, '--prompt', 'CLI first'])
|
|
35
35
|
const ids = JSON.parse(first.stdout) as { threadId: string; sessionId: string }
|
|
36
36
|
await waitForFooter({ discord: twin.discord, threadId: ids.threadId })
|
|
37
|
+
// Follow-ups into an idle thread have no Discord message: the thread must still show them.
|
|
37
38
|
await cli(['send', '--thread', ids.threadId, '--prompt', 'CLI second. queue'])
|
|
38
39
|
await waitForFooter({ discord: twin.discord, threadId: ids.threadId, count: 2 })
|
|
40
|
+
await cli(['send', '--thread', ids.threadId, '--prompt', 'CLI third'])
|
|
41
|
+
await waitForFooter({ discord: twin.discord, threadId: ids.threadId, count: 3 })
|
|
39
42
|
expect(await twin.discord.thread(ids.threadId).text()).toMatchInlineSnapshot(`
|
|
40
43
|
"--- from: assistant (TestBot)
|
|
41
|
-
» **CLI:** CLI first
|
|
44
|
+
-# » **CLI:** CLI first
|
|
42
45
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
43
46
|
ok
|
|
44
|
-
-# *
|
|
47
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
48
|
+
-# » **CLI:** CLI second
|
|
45
49
|
ok
|
|
46
|
-
-# *
|
|
50
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
51
|
+
-# » **CLI:** CLI third
|
|
52
|
+
ok
|
|
53
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*"
|
|
47
54
|
`)
|
|
48
55
|
expect(ids.sessionId).toMatch(/^ses_/)
|
|
49
56
|
const tokenFile = path.join(dataDir, 'lock-token')
|
|
@@ -57,12 +64,58 @@ test('CLI send starts and continues a session through the authenticated bot API'
|
|
|
57
64
|
expect(invalid.status).toBe(400)
|
|
58
65
|
})
|
|
59
66
|
|
|
67
|
+
test('CLI send shows a prompt over 2000 chars in full, split into messages', async () => {
|
|
68
|
+
const prompt = Array.from({ length: 30 }, (_, index) => `line ${String(index).padStart(2, '0')}: ${'x'.repeat(70)}`).join('\n')
|
|
69
|
+
const sent = await cli(['send', '--channel', twin.channelId, '--prompt', prompt])
|
|
70
|
+
const ids = JSON.parse(sent.stdout) as { threadId: string; sessionId: string }
|
|
71
|
+
await waitForFooter({ discord: twin.discord, threadId: ids.threadId })
|
|
72
|
+
expect(await twin.discord.thread(ids.threadId).text()).toMatchInlineSnapshot(`
|
|
73
|
+
"--- from: assistant (TestBot)
|
|
74
|
+
-# » **CLI:** line 00: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
75
|
+
-# line 01: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
76
|
+
-# line 02: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
77
|
+
-# line 03: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
78
|
+
-# line 04: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
79
|
+
-# line 05: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
80
|
+
-# line 06: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
81
|
+
-# line 07: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
82
|
+
-# line 08: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
83
|
+
-# line 09: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
84
|
+
-# line 10: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
85
|
+
-# line 11: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
86
|
+
-# line 12: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
87
|
+
-# line 13: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
88
|
+
-# line 14: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
89
|
+
-# line 15: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
90
|
+
-# line 16: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
91
|
+
-# line 17: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
92
|
+
-# line 18: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
93
|
+
-# line 19: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
94
|
+
-# line 20: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
95
|
+
-# line 21: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
96
|
+
-# line 22: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
97
|
+
-# line 23: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
98
|
+
-# line 24: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
99
|
+
-# line 25: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
100
|
+
-# line 26: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
101
|
+
-# line 27: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
102
|
+
-# line 28: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
103
|
+
-# line 29: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
104
|
+
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
105
|
+
ok
|
|
106
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*"
|
|
107
|
+
`)
|
|
108
|
+
const messages = await twin.discord.thread(ids.threadId).getMessages()
|
|
109
|
+
expect(messages.every((message) => message.content.length <= 2000)).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
60
112
|
test('CLI help documents the supported P7 commands', async () => {
|
|
61
113
|
const output = await cli(['--help'])
|
|
62
114
|
expect(output.stdout).toContain('send')
|
|
63
115
|
expect(output.stdout).toContain('upload-request')
|
|
64
|
-
|
|
65
|
-
|
|
116
|
+
// The header is `kimaki/<version>`; keep the snapshot stable across releases.
|
|
117
|
+
expect(output.stdout.replace(/^kimaki\/[^\n]+/, 'kimaki/<version>')).toMatchInlineSnapshot(`
|
|
118
|
+
"kimaki/<version>
|
|
66
119
|
|
|
67
120
|
Usage:
|
|
68
121
|
$ kimaki [options]
|
|
@@ -96,9 +149,9 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
96
149
|
--rebuild Write transcripts again, also current ones
|
|
97
150
|
--json Output as JSON
|
|
98
151
|
|
|
99
|
-
session wait
|
|
152
|
+
session wait [...ids] Wait until every session is idle or waits for the user, then print each one and why it returned
|
|
100
153
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
101
|
-
--timeout <duration> Timeout, for example 30m or 2h
|
|
154
|
+
--timeout <duration> Timeout, for example 30m or 2h; exit 1 when a session still runs
|
|
102
155
|
|
|
103
156
|
session editors <file> List sessions that last edited a file, newest first
|
|
104
157
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
@@ -134,6 +187,9 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
134
187
|
session archive [id] Archive a session thread
|
|
135
188
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
136
189
|
|
|
190
|
+
session compact [id] Summarize the session history after its current turn (default: this session)
|
|
191
|
+
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
192
|
+
|
|
137
193
|
session title <title> Rename the session and its Discord thread
|
|
138
194
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
139
195
|
-s, --session <id> Session ID, Discord thread ID or thread URL (default: OPENCODE_SESSION_ID)
|
|
@@ -182,7 +238,7 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
182
238
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
183
239
|
-c, --channel <id> Destination channel (default: channel of the session folder)
|
|
184
240
|
|
|
185
|
-
buttons Show 1-3 action buttons.
|
|
241
|
+
buttons Show 1-3 action buttons. Explain them in your text
|
|
186
242
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
187
243
|
-s, --session <id> Session ID, Discord thread ID or thread URL (default: OPENCODE_SESSION_ID)
|
|
188
244
|
-b, --button <spec> Repeatable: Label[:white|blue|green|red]
|
|
@@ -212,7 +268,7 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
212
268
|
-d, --project <path> Project directory of the new thread
|
|
213
269
|
--thread <id> Continue this thread (ID or URL)
|
|
214
270
|
-s, --session <id> Continue this session: session ID, thread ID or thread URL
|
|
215
|
-
-p, --prompt <text> Prompt; thread suffixes . queue and . btw are supported
|
|
271
|
+
-p, --prompt <text> Prompt; thread suffixes . queue and . btw are supported (required)
|
|
216
272
|
-f, --file <path> Attach a local file (repeatable)
|
|
217
273
|
-n, --name <text> Thread name
|
|
218
274
|
--agent <name> Agent ID
|
|
@@ -224,7 +280,7 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
224
280
|
--parent-session <id> Record the parent session in session metadata
|
|
225
281
|
--permission <rule> Repeatable: tool[:pattern]:allow|deny|ask
|
|
226
282
|
--notify-only Post a notification thread without a model turn
|
|
227
|
-
--wait
|
|
283
|
+
--wait [seconds] Wait until idle or input is needed (at most this many seconds, default 1 day), then print the session
|
|
228
284
|
--send-at <when> Schedule: UTC ISO date ending in Z, or cron expression (UTC)
|
|
229
285
|
--pre-run <command> Scheduled only: run first in the project. Exit 0 starts, stdout is appended
|
|
230
286
|
--allow-concurrency Scheduled only: allow overlapping runs of this routine
|
|
@@ -232,10 +288,11 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
232
288
|
session events <id> Print the recorded OpenCode events of a thread as JSONL (root + subagents)
|
|
233
289
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
234
290
|
|
|
235
|
-
session read <id> Print the
|
|
291
|
+
session read <id> Print the prompts and final answers of a session as markdown
|
|
236
292
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
237
|
-
--
|
|
238
|
-
--
|
|
293
|
+
--full Every message with tool calls
|
|
294
|
+
--thinking Full output with reasoning
|
|
295
|
+
--verbose Full output with tool inputs and outputs
|
|
239
296
|
--json Print raw OpenCode messages
|
|
240
297
|
|
|
241
298
|
session cwd [directory] Show or change the working directory at a native safe boundary
|
|
@@ -295,7 +352,7 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
295
352
|
routine run <routineId> Run a routine now
|
|
296
353
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
297
354
|
|
|
298
|
-
sleep Wake this session later with a new message in the same thread. Run it last, after your text. To monitor slow events (email replies, PR reviews), wait 2h or more; never poll every few minutes
|
|
355
|
+
sleep Wake this session later with a new message in the same thread. Run it last, after your text. To monitor slow events (email replies, PR reviews), wait 2h or more; never poll every few minutes. When blocked on a user action outside the chat (a sudo or permission dialog, a login), ask for it, assume they will do it, and sleep 5m, then check and continue on your own
|
|
299
356
|
--data-dir <path> Data directory (default: ~/.kimaki)
|
|
300
357
|
--duration <duration> Relative wait, e.g. 30m, 2h, 1d
|
|
301
358
|
--until <date> UTC ISO date ending in Z
|
|
@@ -320,6 +377,10 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
320
377
|
-s, --session <id> Session ID, Discord thread ID or thread URL (default: OPENCODE_SESSION_ID)
|
|
321
378
|
-c, --channel <id> Kimaki voice channel to post in (default in voice calls: KIMAKI_CHANNEL_ID)
|
|
322
379
|
|
|
380
|
+
diagram <input> Render a Mermaid diagram (file path, or - for stdin) to a light themed PNG. Upload it with upload-to-discord
|
|
381
|
+
-o, --output <path> PNG file to write (default: input path with .png, or a temp file for stdin)
|
|
382
|
+
--dark Dark theme instead of light
|
|
383
|
+
|
|
323
384
|
Tools:
|
|
324
385
|
tunnel Run a command and expose its local port with a public opentunnel.xyz URL. The child gets KIMAKI_TUNNEL_URL
|
|
325
386
|
-p, --port <port> Local port (default: read from the command output)
|
|
@@ -376,12 +437,15 @@ test('CLI help documents the supported P7 commands', async () => {
|
|
|
376
437
|
--restart-onboarding Choose credentials again
|
|
377
438
|
--worktrees Use a fresh Git worktree for new sessions unless the channel overrides it
|
|
378
439
|
--allow-all-users Every server member can use the bot (role "no-kimaki" still denies; provider login stays admin-only)
|
|
379
|
-
--message-tool Discord shows only a concise reply the agent sends with a send_message tool; plain text stays visible in the OpenCode TUI
|
|
380
440
|
--voice <name> Voice of voice calls, saved for later starts (e.g. cove, juniper, sol). "default" resets it
|
|
381
441
|
--disable-skills <list> Built-in Kimaki skills to turn off: "all" or a comma separated list (kimaki-debug,kimaki-tunnel)
|
|
442
|
+
--disable-prompt <section> System prompt section to remove from new sessions (repeatable): discord-permissions, kimaki-debugging, file-upload-requests, action-buttons, session-sleep, session-abort, cross-project-commands, waiting-sessions, critique-diffs, markdown-formatting, discord-callouts, search-result-urls, diagrams
|
|
382
443
|
--no-analytics Disable anonymous usage analytics (same as KIMAKI_STRADA_ENABLED=0)
|
|
444
|
+
--coordinator [enabled] Channel messages go to one long coordinator session that starts threads for the work. "false" turns it off (/toggle-coordinator sets one channel) (default: true)
|
|
445
|
+
--no-autostart Do not open a terminal with the bot when OpenCode starts (for bots run by launchd, systemd or pm2)
|
|
383
446
|
--if-not-running Start only if no bot runs; never stop the running one (used by autostart)
|
|
384
447
|
-h, --help Display this message
|
|
448
|
+
-V, --version Display version number
|
|
385
449
|
"
|
|
386
450
|
`)
|
|
387
451
|
})
|
|
@@ -408,16 +472,20 @@ test('CLI registry shares thread actions, preferences, title and archive', async
|
|
|
408
472
|
await cli(['session', 'queue', 'add', 'Registry queued', '--session', started.threadId])
|
|
409
473
|
await expect(cli(['session', 'queue', 'list', '--session', 'not-an-id'])).rejects.toThrow('Not a session ID, Discord thread ID or thread URL: not-an-id')
|
|
410
474
|
await waitForFooter({ discord: twin.discord, threadId: started.threadId, count: 2 })
|
|
475
|
+
// The deterministic model writes no summary template, so the compaction runs and fails.
|
|
476
|
+
expect(JSON.parse((await cli(['session', 'compact', started.threadId])).stdout)).toEqual({ sessionId: started.sessionId, queued: true })
|
|
411
477
|
await cli(['session', 'abort', started.sessionId])
|
|
412
478
|
await cli(['session', 'archive', started.threadId])
|
|
413
479
|
expect(await twin.discord.thread(started.threadId).text()).toMatchInlineSnapshot(`
|
|
414
480
|
"--- from: assistant (TestBot)
|
|
415
|
-
» **CLI:** Registry first
|
|
481
|
+
-# » **CLI:** Registry first
|
|
416
482
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
417
483
|
ok
|
|
418
|
-
-# *
|
|
484
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
485
|
+
-# » **CLI:** Registry queued
|
|
419
486
|
ok
|
|
420
|
-
-# *
|
|
487
|
+
-# *main ⋅ Ns ⋅ deterministic-v2 ⋅ build*
|
|
488
|
+
✗ compaction failed: Compaction summary did not match the required template"
|
|
421
489
|
`)
|
|
422
490
|
expect((await twin.discord.channel(twin.channelId).getThreads()).find((thread) => thread.id === started.threadId)?.name).toBe('CLI renamed')
|
|
423
491
|
})
|
|
@@ -430,10 +498,10 @@ test('send settings reach native session creation; notifications create no sessi
|
|
|
430
498
|
await waitForFooter({ discord: twin.discord, threadId: ids.threadId })
|
|
431
499
|
expect(await twin.discord.thread(ids.threadId).text()).toMatchInlineSnapshot(`
|
|
432
500
|
"--- from: assistant (TestBot)
|
|
433
|
-
» **CLI:** Send settings
|
|
501
|
+
-# » **CLI:** Send settings
|
|
434
502
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
435
503
|
ok
|
|
436
|
-
-# *subfolder ⋅ main ⋅ Ns ⋅ deterministic-v2*"
|
|
504
|
+
-# *subfolder ⋅ main ⋅ Ns ⋅ deterministic-v2 ⋅ build*"
|
|
437
505
|
`)
|
|
438
506
|
const info = await (await server.client()).session.get({ sessionID: ids.sessionId })
|
|
439
507
|
expect(info.location.directory).toBe(subdir)
|
package/src/cli-read.e2e.test.ts
CHANGED
|
@@ -43,10 +43,10 @@ test('session read, list, sync, wait, url and events read real session history',
|
|
|
43
43
|
const events = await cli(['session', 'events', first.sessionId])
|
|
44
44
|
expect(await twin.discord.thread(first.threadId).text()).toMatchInlineSnapshot(`
|
|
45
45
|
"--- from: assistant (TestBot)
|
|
46
|
-
» **CLI:** Reader content marker
|
|
46
|
+
-# » **CLI:** Reader content marker
|
|
47
47
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
48
48
|
ok
|
|
49
|
-
-# *
|
|
49
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*"
|
|
50
50
|
`)
|
|
51
51
|
expect(list.stdout).toContain(first.sessionId)
|
|
52
52
|
expect(sync.written + sync.skipped).toBeGreaterThan(0)
|
|
@@ -54,6 +54,7 @@ test('session read, list, sync, wait, url and events read real session history',
|
|
|
54
54
|
expect(fs.readFileSync(path.join(dataDir, 'transcripts', `${first.sessionId}.md`), 'utf8')).toContain('## user')
|
|
55
55
|
expect(fs.readFileSync(path.join(dataDir, 'transcripts', `${first.sessionId}.md`), 'utf8')).toContain('Reader content marker')
|
|
56
56
|
expect(read.stdout).toContain('Reader content marker')
|
|
57
|
+
expect(read.stdout).toMatch(/\ncontext: \d+k? tokens, last run took \S+( \S+)?, last active \S+( \S+)? ago \(\d{4}-/)
|
|
57
58
|
expect(wait.stdout).toContain('Reader content marker')
|
|
58
59
|
expect(url.stdout).toContain(first.threadId)
|
|
59
60
|
expect(events.stdout).toContain('session.execution.succeeded')
|
|
@@ -61,19 +62,27 @@ test('session read, list, sync, wait, url and events read real session history',
|
|
|
61
62
|
expect(none instanceof Error && none.code).toBe(1)
|
|
62
63
|
})
|
|
63
64
|
|
|
64
|
-
test('session wait
|
|
65
|
-
const first = await
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
test('session wait waits for many sessions and says a question paused one; active discovery errors use exit 64', async () => {
|
|
66
|
+
const [first, done] = await Promise.all([
|
|
67
|
+
send(bot, { channelId: twin.channelId, prompt: 'wait-form-marker', permissions: ['question:allow'] }),
|
|
68
|
+
send(bot, { channelId: twin.channelId, prompt: 'Second waited session' }),
|
|
69
|
+
])
|
|
70
|
+
if (first instanceof Error || !first.sessionId || done instanceof Error || !done.sessionId) throw new Error('Expected sessions')
|
|
71
|
+
await Promise.all([waitForSelectMenu({ discord: twin.discord, channelId: first.threadId, prefix: 'form:' }), waitForFooter({ discord: twin.discord, threadId: done.threadId })])
|
|
72
|
+
const result = await cli(['session', 'wait', first.sessionId, done.threadId, '--timeout', '8s'])
|
|
69
73
|
expect(await twin.discord.thread(first.threadId).text()).toMatchInlineSnapshot(`
|
|
70
74
|
"--- from: assistant (TestBot)
|
|
71
|
-
» **CLI:** wait-form-marker
|
|
75
|
+
-# » **CLI:** wait-form-marker
|
|
72
76
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
73
77
|
**Next**
|
|
74
78
|
Continue?"
|
|
75
79
|
`)
|
|
76
80
|
expect(result.stdout).toContain('Continue?')
|
|
81
|
+
expect(result.stdout.split('\n').filter((line) => line.startsWith('==='))).toEqual([
|
|
82
|
+
`=== ${first.sessionId} is paused: it waits for the user to answer a question, so its work is not done ===`,
|
|
83
|
+
`=== ${done.sessionId} was already idle: its last run ended before the wait started ===`,
|
|
84
|
+
])
|
|
85
|
+
expect(result.stdout.indexOf('Second waited session')).toBeGreaterThan(result.stdout.indexOf('Continue?'))
|
|
77
86
|
const failure = await exec(process.execPath, ['--import', 'tsx', path.resolve('src/cli.ts'), 'session', 'list', '--active'], {
|
|
78
87
|
env: { ...process.env, KIMAKI_OPENCODE_SERVICE_FILE: path.join(server.root, 'missing-service.json') },
|
|
79
88
|
}).catch((error: Error & { code: number }) => error)
|
|
@@ -87,12 +87,13 @@ test('session command, named fork, and resume without a channel use the session
|
|
|
87
87
|
const resumed = JSON.parse((await cli(['session', 'resume', source.sessionId])).stdout)
|
|
88
88
|
expect(await twin.discord.thread(source.threadId).text()).toMatchInlineSnapshot(`
|
|
89
89
|
"--- from: assistant (TestBot)
|
|
90
|
-
» **CLI:** command-source
|
|
90
|
+
-# » **CLI:** command-source
|
|
91
91
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
92
92
|
ok
|
|
93
|
-
-# *
|
|
93
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
94
|
+
-# » **CLI:** /review focus --focus security
|
|
94
95
|
reviewed with passthrough
|
|
95
|
-
-# *
|
|
96
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
96
97
|
`)
|
|
97
98
|
expect((await twin.discord.thread(forked.threadId).getChannel())?.name).toBe('named fork')
|
|
98
99
|
expect(resumed.sessionId).toBe(source.sessionId)
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Startup must stay light: bot modules (main.ts, onboarding.ts, scheduler.ts)
|
|
5
5
|
// are imported lazily inside the commands that need them.
|
|
6
6
|
|
|
7
|
+
import { createRequire } from 'node:module'
|
|
7
8
|
import { goke } from 'goke'
|
|
8
9
|
|
|
9
10
|
import { registerBotCommands, registerLoginCommands, registerStartCommand, registerToolCommands } from './cli/bot.ts'
|
|
@@ -48,4 +49,5 @@ cli.section('Bot')
|
|
|
48
49
|
registerBotCommands(cli)
|
|
49
50
|
|
|
50
51
|
cli.help()
|
|
52
|
+
cli.version(createRequire(import.meta.url)('../package.json').version, '-V, --version')
|
|
51
53
|
void cli.parse()
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// session in OpenCode (switchAgent / switchModel, applied from the next
|
|
3
3
|
// step); in a project channel they set the default for new sessions
|
|
4
4
|
// (channel_agents / channel_models). /model can also set the default of
|
|
5
|
-
// channels without their own model (global_models). Verbosity
|
|
5
|
+
// channels without their own model (global_models). Verbosity and coordinator
|
|
6
|
+
// mode are always per channel.
|
|
6
7
|
//
|
|
7
8
|
// /agent ─▶ agent select ─▶ session or channel
|
|
8
9
|
// /<agent>-agent ─▶ same, without the select
|
|
@@ -10,6 +11,7 @@
|
|
|
10
11
|
// └▶ reset buttons: session model ─▶ channel default, channel model ─▶ removed
|
|
11
12
|
// /model-variant ─▶ variant of the current model ─▶ scope
|
|
12
13
|
// /verbosity ─▶ text | tools for the channel
|
|
14
|
+
// /toggle-coordinator ─▶ coordinator mode on | off for the channel
|
|
13
15
|
//
|
|
14
16
|
// The /model wizard keeps its picks in the createPreferenceRoutes closure
|
|
15
17
|
// keyed by a short hash (custom IDs max 100 chars), dropped after 10 minutes.
|
|
@@ -25,7 +27,7 @@ import {
|
|
|
25
27
|
type StringSelectMenuInteraction,
|
|
26
28
|
} from 'discord.js'
|
|
27
29
|
|
|
28
|
-
import { globalModel, oc, parseModel, type Bot, type ModelChoice } from '../bot.ts'
|
|
30
|
+
import { globalModel, isCoordinatorChannel, oc, parseModel, type Bot, type ModelChoice } from '../bot.ts'
|
|
29
31
|
import { verbosityFromV1, verbosityToV1, type Verbosity } from '../db.ts'
|
|
30
32
|
import { ConfigError, DbError } from '../errors.ts'
|
|
31
33
|
import { button, buttonRow, paginate, selectedPage, selectRow, type UiMessage } from '../format-parts.ts'
|
|
@@ -111,6 +113,19 @@ export async function setVerbosity(bot: Bot, { channelId, verbosity }: { channel
|
|
|
111
113
|
bot.store.setState((current) => ({ verbosity: { ...current.verbosity, [channelId]: verbosity } }))
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
// Coordinator mode of a project channel. Turning it off keeps the coordinator
|
|
117
|
+
// binding: turning it on again resumes that session (/reset-coordinator drops it).
|
|
118
|
+
export async function setCoordinatorMode(bot: Bot, { channelId, enabled }: { channelId: string; enabled: boolean }): Promise<DbError | void> {
|
|
119
|
+
const value = enabled ? 1 : 0
|
|
120
|
+
const result = await bot.db
|
|
121
|
+
.insert(schema.channel_coordinator)
|
|
122
|
+
.values({ channel_id: channelId, enabled: value })
|
|
123
|
+
.onConflictDoUpdate({ target: schema.channel_coordinator.channel_id, set: { enabled: value } })
|
|
124
|
+
.catch((cause) => new DbError({ operation: 'write channel_coordinator', cause }))
|
|
125
|
+
if (result instanceof Error) return result
|
|
126
|
+
bot.store.setState((current) => ({ coordinator: { ...current.coordinator, [channelId]: enabled } }))
|
|
127
|
+
}
|
|
128
|
+
|
|
114
129
|
export async function setChannelAgent(bot: Bot, { channelId, agent }: { channelId: string; agent: string }): Promise<DbError | void> {
|
|
115
130
|
const result = await bot.db
|
|
116
131
|
.insert(schema.channel_agents)
|
|
@@ -264,7 +279,7 @@ async function setAgent(bot: Bot, { target, agent, model }: { target: Interactio
|
|
|
264
279
|
if (before instanceof Error) return before
|
|
265
280
|
const sessionId = target.sessionId
|
|
266
281
|
const selected = sessionId
|
|
267
|
-
? await switchAgent(bot, { sessionId, directory: target.directory, agent, model })
|
|
282
|
+
? await switchAgent(bot, { sessionId, directory: target.directory, agent, model, reselect: true })
|
|
268
283
|
: await agentSelection(bot, { directory: target.directory, agent })
|
|
269
284
|
if (selected instanceof Error) return selected
|
|
270
285
|
if (!sessionId) {
|
|
@@ -479,9 +494,24 @@ async function handleVerbositySelect(bot: Bot, interaction: StringSelectMenuInte
|
|
|
479
494
|
})
|
|
480
495
|
}
|
|
481
496
|
|
|
497
|
+
// --- /toggle-coordinator
|
|
498
|
+
|
|
499
|
+
async function toggleCoordinatorCommand(bot: Bot, interaction: ChatInputCommandInteraction) {
|
|
500
|
+
const target = await resolveTarget(bot, interaction.channelId)
|
|
501
|
+
if (target instanceof Error) return replyError(interaction, target)
|
|
502
|
+
const enabled = !isCoordinatorChannel(bot, target.channelId)
|
|
503
|
+
const result = await setCoordinatorMode(bot, { channelId: target.channelId, enabled })
|
|
504
|
+
if (result instanceof Error) return replyError(interaction, result)
|
|
505
|
+
const resumes = enabled && bot.store.getState().roots[target.channelId] ? ' It continues the coordinator session this channel had before; /reset-coordinator starts a fresh one.' : ''
|
|
506
|
+
const content = enabled
|
|
507
|
+
? `Coordinator mode **on** for this channel. Channel messages go to one long coordinator session that starts threads for the work.${resumes}`
|
|
508
|
+
: 'Coordinator mode **off** for this channel. Each channel message starts a new thread.'
|
|
509
|
+
await interaction.reply({ content })
|
|
510
|
+
}
|
|
511
|
+
|
|
482
512
|
// --- Routes
|
|
483
513
|
|
|
484
|
-
// /agent, /model, /model-variant, /verbosity and their selects. One per bot:
|
|
514
|
+
// /agent, /model, /model-variant, /verbosity, /toggle-coordinator and their selects. One per bot:
|
|
485
515
|
// the /model wizard picks stay in this closure.
|
|
486
516
|
export function createPreferenceRoutes(): InteractionRoutes {
|
|
487
517
|
const wizards = new Map<string, ModelWizard>()
|
|
@@ -592,6 +622,10 @@ export function createPreferenceRoutes(): InteractionRoutes {
|
|
|
592
622
|
run: variantMenu,
|
|
593
623
|
},
|
|
594
624
|
verbosity: { definition: new SlashCommandBuilder().setName('verbosity').setDescription('Set what the bot shows in this channel'), run: verbosityMenu },
|
|
625
|
+
'toggle-coordinator': {
|
|
626
|
+
definition: new SlashCommandBuilder().setName('toggle-coordinator').setDescription('Turn coordinator mode on or off: one long session in this channel that starts threads'),
|
|
627
|
+
run: toggleCoordinatorCommand,
|
|
628
|
+
},
|
|
595
629
|
},
|
|
596
630
|
buttons: { [MODEL_RESET_PREFIX]: modelReset },
|
|
597
631
|
selects: { [AGENT_PREFIX]: handleAgentSelect, [MODEL_PREFIX]: modelSelect, [VERBOSITY_PREFIX]: handleVerbositySelect },
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// /fork, /fork-subagent. Selects carry only the source session ID; the
|
|
3
3
|
// source thread comes from the interaction channel.
|
|
4
4
|
//
|
|
5
|
-
// /new-session prompt files? agent?
|
|
5
|
+
// /new-session prompt? files? agent? ─▶ startSession (new thread, no start message;
|
|
6
|
+
// no prompt: run: false, the first thread message runs it)
|
|
6
7
|
// /resume session (autocomplete) ─▶ resume (binding moves to the new thread)
|
|
7
8
|
// /fork ─▶ select a user message ─▶ fork({ before })
|
|
8
9
|
// /fork-subagent ─▶ select a child ─▶ fork(child)
|
|
@@ -60,12 +61,13 @@ export function parseFileList({ value, directory }: { value: string; directory:
|
|
|
60
61
|
async function newSession(bot: Bot, interaction: ChatInputCommandInteraction) {
|
|
61
62
|
const target = await resolveTarget(bot, interaction.channelId)
|
|
62
63
|
if (target instanceof Error) return replyError(interaction, target)
|
|
63
|
-
const text = interaction.options.getString('prompt'
|
|
64
|
-
if (!text) return replyError(interaction, new ConfigError({ reason: 'Write a prompt for the new session' }))
|
|
64
|
+
const text = interaction.options.getString('prompt')?.trim() ?? ''
|
|
65
65
|
const files = parseFileList({ value: interaction.options.getString('files') ?? '', directory: target.directory })
|
|
66
66
|
if (files instanceof Error) return replyError(interaction, files)
|
|
67
|
+
if (!text && files.length > 0) return replyError(interaction, new ConfigError({ reason: 'Write a prompt to attach files' }))
|
|
67
68
|
const agent = interaction.options.getString('agent')?.trim()
|
|
68
69
|
await interaction.deferReply()
|
|
70
|
+
// No prompt: the session waits in the thread for its first message.
|
|
69
71
|
const started = await startSession(bot, {
|
|
70
72
|
channelId: target.channelId,
|
|
71
73
|
directory: target.directory,
|
|
@@ -74,6 +76,7 @@ async function newSession(bot: Bot, interaction: ChatInputCommandInteraction) {
|
|
|
74
76
|
messageId: interaction.id,
|
|
75
77
|
startMessageId: null,
|
|
76
78
|
files,
|
|
79
|
+
run: Boolean(text),
|
|
77
80
|
})
|
|
78
81
|
if (started instanceof Error) return replyError(interaction, started)
|
|
79
82
|
await interaction.editReply({ content: `Created new session in <#${started.threadId}>` })
|
|
@@ -82,7 +85,7 @@ async function newSession(bot: Bot, interaction: ChatInputCommandInteraction) {
|
|
|
82
85
|
async function resumeCommand(bot: Bot, interaction: ChatInputCommandInteraction) {
|
|
83
86
|
const target = await resolveTarget(bot, interaction.channelId)
|
|
84
87
|
if (target instanceof Error) return replyError(interaction, target)
|
|
85
|
-
if (target.
|
|
88
|
+
if (target.conversation?.isThread()) {
|
|
86
89
|
return replyError(interaction, new ConfigError({ reason: 'This command can only be used in project channels, not threads' }))
|
|
87
90
|
}
|
|
88
91
|
await interaction.deferReply()
|
|
@@ -173,7 +176,7 @@ async function forkSelected(bot: Bot, { interaction, subagents }: { interaction:
|
|
|
173
176
|
const selected = interaction.values[0]
|
|
174
177
|
const target = await resolveTarget(bot, interaction.channelId)
|
|
175
178
|
if (target instanceof Error) return replyError(interaction, target)
|
|
176
|
-
if (!selected || !target.
|
|
179
|
+
if (!selected || !target.conversation || target.sessionId !== sourceSessionId) {
|
|
177
180
|
await interaction.update({ content: 'This selection expired. Run the command again.', components: [] })
|
|
178
181
|
return
|
|
179
182
|
}
|
|
@@ -186,7 +189,7 @@ async function forkSelected(bot: Bot, { interaction, subagents }: { interaction:
|
|
|
186
189
|
})()
|
|
187
190
|
if (subagent instanceof Error) return replyError(interaction, subagent)
|
|
188
191
|
const result = await fork(bot, {
|
|
189
|
-
|
|
192
|
+
source: target.conversation,
|
|
190
193
|
sessionId: subagents ? selected : sourceSessionId,
|
|
191
194
|
...(!subagents && { before: selected }),
|
|
192
195
|
...(subagent && { subagent }),
|
|
@@ -258,7 +261,9 @@ export const sessionRoutes: InteractionRoutes = {
|
|
|
258
261
|
definition: new SlashCommandBuilder()
|
|
259
262
|
.setName('new-session')
|
|
260
263
|
.setDescription('Start a new OpenCode session')
|
|
261
|
-
.addStringOption((option) =>
|
|
264
|
+
.addStringOption((option) =>
|
|
265
|
+
option.setName('prompt').setDescription('Prompt content for the session; empty waits for the first message in the thread'),
|
|
266
|
+
)
|
|
262
267
|
.addStringOption((option) =>
|
|
263
268
|
option.setName('files').setDescription('Files to attach (comma separated; autocomplete)').setAutocomplete(true).setMaxLength(6000),
|
|
264
269
|
)
|