kimaki 1.4.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 +10 -1
- package/dist/bot.js +14 -6
- package/dist/bot.js.map +1 -1
- package/dist/btw.e2e.test.js +3 -3
- package/dist/builtin-skills.d.ts +1 -1
- package/dist/builtin-skills.js +1 -1
- package/dist/builtin-skills.js.map +1 -1
- package/dist/builtin-skills.test.js +3 -4
- package/dist/builtin-skills.test.js.map +1 -1
- package/dist/catalog-refresh.e2e.test.js +2 -2
- package/dist/cli/bot.js +25 -14
- package/dist/cli/bot.js.map +1 -1
- package/dist/cli/discord.js +32 -6
- package/dist/cli/discord.js.map +1 -1
- package/dist/cli/schedule.js +23 -9
- package/dist/cli/schedule.js.map +1 -1
- package/dist/cli/session.js +37 -12
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/shared.d.ts +5 -3
- package/dist/cli/shared.js +45 -10
- package/dist/cli/shared.js.map +1 -1
- package/dist/cli-discord.e2e.test.js +13 -8
- package/dist/cli-discord.e2e.test.js.map +1 -1
- package/dist/cli-lock.e2e.test.js +87 -20
- package/dist/cli-lock.e2e.test.js.map +1 -1
- package/dist/cli-read.e2e.test.js +18 -9
- package/dist/cli-read.e2e.test.js.map +1 -1
- package/dist/cli-sessions.e2e.test.js +4 -3
- package/dist/cli-sessions.e2e.test.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/preference-commands.d.ts +4 -0
- package/dist/commands/preference-commands.js +38 -4
- package/dist/commands/preference-commands.js.map +1 -1
- package/dist/commands/session-commands.js +11 -8
- package/dist/commands/session-commands.js.map +1 -1
- package/dist/commands/thread-commands.js +94 -24
- package/dist/commands/thread-commands.js.map +1 -1
- package/dist/commands/worktree-commands.d.ts +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 +1 -1
- package/dist/event-loop.js +73 -43
- package/dist/event-loop.js.map +1 -1
- package/dist/format-parts.d.ts +21 -4
- package/dist/format-parts.js +40 -23
- package/dist/format-parts.js.map +1 -1
- package/dist/gateway-replay.e2e.test.js +4 -4
- package/dist/harness.e2e.test.js +2 -2
- package/dist/harness.e2e.test.js.map +1 -1
- package/dist/hello-thread.e2e.test.js +56 -5
- package/dist/hello-thread.e2e.test.js.map +1 -1
- package/dist/ingress.js +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 +4 -2
- package/dist/main.js +13 -3
- 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 +4 -4
- package/dist/markdown/render-markdown.js +6 -5
- package/dist/markdown/render-markdown.js.map +1 -1
- package/dist/onboarding-cli.e2e.test.js +7 -6
- package/dist/onboarding-cli.e2e.test.js.map +1 -1
- package/dist/onboarding.d.ts +3 -10
- package/dist/onboarding.e2e.test.js +8 -10
- package/dist/onboarding.e2e.test.js.map +1 -1
- package/dist/onboarding.js +14 -24
- package/dist/onboarding.js.map +1 -1
- package/dist/permissions.e2e.test.js +3 -3
- package/dist/plugin/coordinator.d.ts +4 -0
- package/dist/plugin/coordinator.js +389 -0
- package/dist/plugin/coordinator.js.map +1 -0
- package/dist/plugin/index.js +76 -17
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/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 +166 -22
- 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 +174 -63
- 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 +3 -1
- package/dist/system-prompt.js +138 -64
- 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 -1
- package/dist/test/harness.js +51 -10
- package/dist/test/harness.js.map +1 -1
- package/dist/test/replay.js +11 -7
- package/dist/test/replay.js.map +1 -1
- package/dist/thread-reducer.d.ts +29 -3
- package/dist/thread-reducer.fixtures.test.js +58 -51
- package/dist/thread-reducer.fixtures.test.js.map +1 -1
- package/dist/thread-reducer.js +112 -25
- package/dist/thread-reducer.js.map +1 -1
- package/dist/thread-reducer.test.js +197 -24
- package/dist/thread-reducer.test.js.map +1 -1
- package/dist/voice-attachments.e2e.test.js +108 -18
- package/dist/voice-attachments.e2e.test.js.map +1 -1
- package/dist/voice-call.e2e.test.js +9 -9
- package/dist/voice-call.js +6 -6
- package/dist/voice-call.js.map +1 -1
- package/dist/voice-openai.e2e.test.js +59 -23
- package/dist/voice-openai.e2e.test.js.map +1 -1
- package/dist/voice.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 +12 -6
- package/src/agent-ui.e2e.test.ts +27 -13
- package/src/agent-ui.ts +2 -1
- package/src/analytics.e2e.test.ts +1 -1
- package/src/autostart.ts +55 -10
- package/src/bot.ts +29 -5
- package/src/btw.e2e.test.ts +3 -3
- package/src/builtin-skills.test.ts +3 -4
- package/src/builtin-skills.ts +1 -1
- package/src/catalog-refresh.e2e.test.ts +2 -2
- package/src/cli/bot.ts +23 -14
- package/src/cli/discord.ts +30 -6
- package/src/cli/schedule.ts +23 -9
- package/src/cli/session.ts +35 -12
- package/src/cli/shared.ts +43 -8
- package/src/cli-discord.e2e.test.ts +13 -8
- package/src/cli-lock.e2e.test.ts +88 -20
- package/src/cli-read.e2e.test.ts +17 -8
- package/src/cli-sessions.e2e.test.ts +4 -3
- package/src/cli.ts +2 -0
- package/src/commands/preference-commands.ts +38 -4
- package/src/commands/session-commands.ts +12 -7
- package/src/commands/thread-commands.ts +92 -24
- package/src/commands/worktree-commands.ts +10 -9
- package/src/commands.e2e.test.ts +25 -21
- package/src/coordinator-agents.e2e.test.ts +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 -42
- package/src/format-parts.ts +53 -22
- package/src/gateway-replay.e2e.test.ts +4 -4
- package/src/harness.e2e.test.ts +2 -2
- package/src/hello-thread.e2e.test.ts +59 -5
- package/src/ingress.ts +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 +22 -7
- package/src/markdown/markdown.test.ts +4 -4
- package/src/markdown/render-markdown.ts +6 -5
- package/src/onboarding-cli.e2e.test.ts +7 -6
- package/src/onboarding.e2e.test.ts +8 -10
- package/src/onboarding.ts +13 -29
- package/src/permissions.e2e.test.ts +3 -3
- package/src/plugin/coordinator.ts +401 -0
- package/src/plugin/index.ts +68 -15
- package/src/plugin.e2e.test.ts +1 -1
- package/src/preference-commands.e2e.test.ts +103 -23
- package/src/prompt.ts +87 -43
- package/src/questions.e2e.test.ts +13 -13
- package/src/queue-sources.e2e.test.ts +197 -0
- package/src/queue.e2e.test.ts +48 -30
- package/src/queue.test.ts +59 -0
- package/src/queue.ts +104 -24
- package/src/remote-send.e2e.test.ts +6 -6
- package/src/remote-send.ts +2 -1
- package/src/renderer.e2e.test.ts +7 -7
- package/src/restart.e2e.test.ts +2 -2
- package/src/routines.e2e.test.ts +2 -2
- package/src/scheduler.e2e.test.ts +45 -19
- package/src/scheduler.ts +4 -2
- package/src/schema-sql.ts +1 -1
- package/src/schema.ts +13 -0
- package/src/session-commands.e2e.test.ts +38 -11
- package/src/session-events.ts +160 -21
- package/src/session-ops.e2e.test.ts +64 -11
- package/src/sessions.ts +214 -64
- package/src/shell.e2e.test.ts +4 -4
- package/src/slash-commands.test.ts +2 -0
- package/src/slash-commands.ts +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 +143 -63
- package/src/test/generate-log-plugin/index.ts +16 -0
- package/src/test/harness.ts +77 -14
- package/src/test/replay.ts +12 -6
- package/src/thread-reducer.fixtures.test.ts +58 -51
- package/src/thread-reducer.test.ts +207 -26
- package/src/thread-reducer.ts +138 -37
- package/src/voice-attachments.e2e.test.ts +110 -18
- package/src/voice-call.e2e.test.ts +9 -9
- package/src/voice-call.ts +6 -6
- package/src/voice-openai.e2e.test.ts +64 -24
- package/src/voice.ts +93 -26
- package/src/working-directory.e2e.test.ts +11 -11
|
@@ -9,6 +9,7 @@ import { ComponentType } from 'discord-api-types/v10'
|
|
|
9
9
|
import { afterAll, beforeAll, expect, onTestFinished, test } from 'vitest'
|
|
10
10
|
|
|
11
11
|
import type { BotHandle } from './main.ts'
|
|
12
|
+
import { send } from './prompt.ts'
|
|
12
13
|
import * as schema from './schema.ts'
|
|
13
14
|
import {
|
|
14
15
|
TEST_USER_ID,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
startTwin,
|
|
20
21
|
tempDataDir,
|
|
21
22
|
waitFor,
|
|
23
|
+
waitForBotMessageContaining,
|
|
22
24
|
waitForFooter,
|
|
23
25
|
waitForSelectMenu,
|
|
24
26
|
warmUp,
|
|
@@ -144,14 +146,15 @@ test('/agent switches the session agent in a thread and the default in a channel
|
|
|
144
146
|
--- from: assistant (TestBot)
|
|
145
147
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
146
148
|
ok
|
|
147
|
-
-# *
|
|
149
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
148
150
|
Switched to **plan** agent for this session
|
|
149
151
|
The agent changes from the next step.
|
|
152
|
+
-# Switched agent to Plan
|
|
150
153
|
--- from: user (tommy)
|
|
151
154
|
Next message
|
|
152
155
|
--- from: assistant (TestBot)
|
|
153
156
|
ok
|
|
154
|
-
-# *
|
|
157
|
+
-# *main ⋅ Ns ⋅ deterministic-v2 ⋅ plan*"
|
|
155
158
|
`)
|
|
156
159
|
expect((await sessionModel(thread.id)).agent).toBe('plan')
|
|
157
160
|
|
|
@@ -228,26 +231,29 @@ test('/model switches the session model from the next step, /model-variant its t
|
|
|
228
231
|
"variant": "low",
|
|
229
232
|
}
|
|
230
233
|
`)
|
|
234
|
+
await waitForBotMessageContaining({ discord, threadId: thread.id, text: 'Switched model to Thinker (low)' })
|
|
231
235
|
expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
|
|
232
236
|
"--- from: user (tommy)
|
|
233
237
|
Model thread
|
|
234
238
|
--- from: assistant (TestBot)
|
|
235
239
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
236
240
|
ok
|
|
237
|
-
-# *
|
|
241
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
238
242
|
Model set for this session:
|
|
239
243
|
**Deterministic Provider** / **deterministic-thinker** (high)
|
|
240
244
|
\`deterministic-provider/deterministic-thinker (high)\`
|
|
241
245
|
Applies from the next step.
|
|
246
|
+
-# Switched model to Thinker (high)
|
|
242
247
|
--- from: user (tommy)
|
|
243
248
|
With the new model
|
|
244
249
|
--- from: assistant (TestBot)
|
|
245
250
|
ok
|
|
246
|
-
-# *
|
|
251
|
+
-# *main ⋅ Ns ⋅ deterministic-thinker*
|
|
247
252
|
Model set for this session:
|
|
248
253
|
**Deterministic Provider** / **deterministic-thinker** (low)
|
|
249
254
|
\`deterministic-provider/deterministic-thinker (low)\`
|
|
250
|
-
Applies from the next step.
|
|
255
|
+
Applies from the next step.
|
|
256
|
+
-# Switched model to Thinker (low)"
|
|
251
257
|
`)
|
|
252
258
|
|
|
253
259
|
await user.runSlashCommand({ name: 'model' })
|
|
@@ -256,7 +262,8 @@ test('/model switches the session model from the next step, /model-variant its t
|
|
|
256
262
|
"Reset session model",
|
|
257
263
|
]
|
|
258
264
|
`)
|
|
259
|
-
|
|
265
|
+
await waitForBotMessageContaining({ discord, threadId: thread.id, text: 'Switched model to deterministic-v2' })
|
|
266
|
+
expect((await discord.thread(thread.id).getMessages()).at(-2)?.content).toMatchInlineSnapshot(`
|
|
260
267
|
"Session model reset to the channel default:
|
|
261
268
|
\`deterministic-provider/deterministic-v2\`
|
|
262
269
|
Applies from the next step."
|
|
@@ -294,7 +301,7 @@ test('/model with channel scope sets the model of new sessions', async () => {
|
|
|
294
301
|
--- from: assistant (TestBot)
|
|
295
302
|
-# *using deterministic-provider/deterministic-thinker ⋅ plan*
|
|
296
303
|
ok
|
|
297
|
-
-# *
|
|
304
|
+
-# *main ⋅ Ns ⋅ deterministic-thinker*"
|
|
298
305
|
`)
|
|
299
306
|
const row = await bot.db.query.channel_models.findFirst({ where: { channel_id: channelId } })
|
|
300
307
|
expect({ model: row?.model_id, variant: row?.variant }).toMatchInlineSnapshot(`
|
|
@@ -348,12 +355,12 @@ test('/verbosity applies to running sessions of the channel at once', async () =
|
|
|
348
355
|
-# ┣ shell _Say hi_
|
|
349
356
|
|
|
350
357
|
tools done
|
|
351
|
-
-# *
|
|
358
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
352
359
|
--- from: user (tommy)
|
|
353
360
|
Again tools-marker
|
|
354
361
|
--- from: assistant (TestBot)
|
|
355
362
|
tools done
|
|
356
|
-
-# *
|
|
363
|
+
-# *main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
357
364
|
`)
|
|
358
365
|
expect(await discord.channel(channelId).text()).toMatchInlineSnapshot(`
|
|
359
366
|
"--- from: user (tommy)
|
|
@@ -411,10 +418,12 @@ test('/<agent>-agent variant: sets the thinking level with the agent, per sessio
|
|
|
411
418
|
--- from: assistant (TestBot)
|
|
412
419
|
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
413
420
|
ok
|
|
414
|
-
-# *
|
|
415
|
-
» **tommy:** (plan, deep) Think hard
|
|
421
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
422
|
+
-# » **tommy:** (plan, deep) Think hard
|
|
423
|
+
-# Switched model to deterministic-v2 (deep)
|
|
424
|
+
-# Switched agent to Plan
|
|
416
425
|
ok
|
|
417
|
-
-# *
|
|
426
|
+
-# *main ⋅ Ns ⋅ deterministic-v2 ⋅ plan*
|
|
418
427
|
\`deterministic-provider/deterministic-v2\` has no thinking level \`nope\`. Available: \`fast\`, \`deep\`"
|
|
419
428
|
`)
|
|
420
429
|
|
|
@@ -454,16 +463,20 @@ test('agent selection replaces a stale model and uses the agent model for varian
|
|
|
454
463
|
await client.session.switchModel({ sessionID, model: { providerID: 'missing', id: 'stale-model' } })
|
|
455
464
|
await user.runSlashCommand({ name: 'agent' })
|
|
456
465
|
await pick({ channelId: thread.id, prefix: 'agent:', value: 'thinker' })
|
|
466
|
+
await waitForBotMessageContaining({ discord, threadId: thread.id, text: 'Switched agent from Build to Thinker' })
|
|
457
467
|
expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
|
|
458
468
|
"--- from: user (tommy)
|
|
459
469
|
Agent model thread
|
|
460
470
|
--- from: assistant (TestBot)
|
|
461
471
|
-# *using deterministic-provider/deterministic-v2 fast ⋅ build*
|
|
462
472
|
ok
|
|
463
|
-
-# *
|
|
473
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
474
|
+
-# Switched model to missing/stale-model
|
|
464
475
|
Switched to **thinker** agent for this session (was **build**)
|
|
465
476
|
Model: \`deterministic-provider/deterministic-thinker (high)\`
|
|
466
|
-
The agent changes from the next step.
|
|
477
|
+
The agent changes from the next step.
|
|
478
|
+
-# Switched model to Thinker (high)
|
|
479
|
+
-# Switched agent from Build to Thinker"
|
|
467
480
|
`)
|
|
468
481
|
expect((await sessionModel(thread.id)).model).toEqual({ providerID: 'deterministic-provider', id: 'deterministic-thinker', variant: 'high' })
|
|
469
482
|
|
|
@@ -482,15 +495,21 @@ test('agent selection replaces a stale model and uses the agent model for varian
|
|
|
482
495
|
--- from: assistant (TestBot)
|
|
483
496
|
-# *using deterministic-provider/deterministic-v2 fast ⋅ build*
|
|
484
497
|
ok
|
|
485
|
-
-# *
|
|
498
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
499
|
+
-# Switched model to missing/stale-model
|
|
486
500
|
Switched to **thinker** agent for this session (was **build**)
|
|
487
501
|
Model: \`deterministic-provider/deterministic-thinker (high)\`
|
|
488
502
|
The agent changes from the next step.
|
|
503
|
+
-# Switched model to Thinker (high)
|
|
504
|
+
-# Switched agent from Build to Thinker
|
|
489
505
|
Switched to **build** agent for this session (was **thinker**)
|
|
490
506
|
The agent changes from the next step.
|
|
491
|
-
|
|
507
|
+
-# Switched agent from Thinker to Build
|
|
508
|
+
-# » **tommy:** (thinker, low) Use the agent model
|
|
509
|
+
-# Switched model to Thinker (low)
|
|
510
|
+
-# Switched agent from Build to Thinker
|
|
492
511
|
ok
|
|
493
|
-
-# *
|
|
512
|
+
-# *main ⋅ Ns ⋅ deterministic-thinker ⋅ thinker*"
|
|
494
513
|
`)
|
|
495
514
|
expect((await sessionModel(thread.id)).model).toEqual({ providerID: 'deterministic-provider', id: 'deterministic-thinker', variant: 'low' })
|
|
496
515
|
|
|
@@ -502,15 +521,22 @@ test('agent selection replaces a stale model and uses the agent model for varian
|
|
|
502
521
|
--- from: assistant (TestBot)
|
|
503
522
|
-# *using deterministic-provider/deterministic-v2 fast ⋅ build*
|
|
504
523
|
ok
|
|
505
|
-
-# *
|
|
524
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
525
|
+
-# Switched model to missing/stale-model
|
|
506
526
|
Switched to **thinker** agent for this session (was **build**)
|
|
507
527
|
Model: \`deterministic-provider/deterministic-thinker (high)\`
|
|
508
528
|
The agent changes from the next step.
|
|
529
|
+
-# Switched model to Thinker (high)
|
|
530
|
+
-# Switched agent from Build to Thinker
|
|
509
531
|
Switched to **build** agent for this session (was **thinker**)
|
|
510
532
|
The agent changes from the next step.
|
|
511
|
-
|
|
533
|
+
-# Switched agent from Thinker to Build
|
|
534
|
+
-# » **tommy:** (thinker, low) Use the agent model
|
|
535
|
+
-# Switched model to Thinker (low)
|
|
536
|
+
-# Switched agent from Build to Thinker
|
|
512
537
|
ok
|
|
513
|
-
-# *
|
|
538
|
+
-# *main ⋅ Ns ⋅ deterministic-thinker ⋅ thinker*
|
|
539
|
+
-# Switched model to Thinker (high)
|
|
514
540
|
Using **thinker** agent for this session
|
|
515
541
|
Model: \`deterministic-provider/deterministic-thinker (high)\`
|
|
516
542
|
The agent changes from the next step."
|
|
@@ -525,7 +551,7 @@ test('agent selection replaces a stale model and uses the agent model for varian
|
|
|
525
551
|
--- from: assistant (TestBot)
|
|
526
552
|
-# *using deterministic-provider/deterministic-thinker high ⋅ thinker*
|
|
527
553
|
ok
|
|
528
|
-
-# *
|
|
554
|
+
-# *main ⋅ Ns ⋅ deterministic-thinker*"
|
|
529
555
|
`)
|
|
530
556
|
expect((await sessionModel(fresh.id)).model).toEqual({ providerID: 'deterministic-provider', id: 'deterministic-thinker', variant: 'high' })
|
|
531
557
|
|
|
@@ -538,7 +564,7 @@ test('agent selection replaces a stale model and uses the agent model for varian
|
|
|
538
564
|
--- from: assistant (TestBot)
|
|
539
565
|
-# *using deterministic-provider/deterministic-thinker low ⋅ thinker*
|
|
540
566
|
ok
|
|
541
|
-
-# *
|
|
567
|
+
-# *main ⋅ Ns ⋅ deterministic-thinker*"
|
|
542
568
|
`)
|
|
543
569
|
expect((await sessionModel(low.id)).model).toEqual({ providerID: 'deterministic-provider', id: 'deterministic-thinker', variant: 'low' })
|
|
544
570
|
})
|
|
@@ -576,7 +602,7 @@ test('/model with all channels scope sets the model of channels without their ow
|
|
|
576
602
|
--- from: assistant (TestBot)
|
|
577
603
|
-# *using deterministic-provider/deterministic-thinker low ⋅ build*
|
|
578
604
|
ok
|
|
579
|
-
-# *
|
|
605
|
+
-# *main ⋅ Ns ⋅ deterministic-thinker*"
|
|
580
606
|
`)
|
|
581
607
|
expect((await sessionModel(thread.id)).model).toMatchInlineSnapshot(`
|
|
582
608
|
{
|
|
@@ -595,3 +621,57 @@ test('/model with all channels scope sets the model of channels without their ow
|
|
|
595
621
|
Select a provider:"
|
|
596
622
|
`)
|
|
597
623
|
})
|
|
624
|
+
|
|
625
|
+
// https://github.com/remorses/kimaki/issues/245: agents pass --agent <current_agent> on every send.
|
|
626
|
+
test('naming the current agent again keeps the session model, and --model wins over the agent model', async () => {
|
|
627
|
+
const { discord, channelId } = twin
|
|
628
|
+
const thread = await newThread(channelId, 'Model kept thread')
|
|
629
|
+
const sessionId = bot.store.getState().roots[thread.id]!
|
|
630
|
+
const sendAndWait = async (input: { prompt: string; agent: string; model?: string }, count: number) => {
|
|
631
|
+
const sent = await send(bot, { sessionId, ...input })
|
|
632
|
+
if (sent instanceof Error) throw sent
|
|
633
|
+
await waitForFooter({ discord, threadId: thread.id, count })
|
|
634
|
+
return sessionModel(thread.id)
|
|
635
|
+
}
|
|
636
|
+
const explicit = await sendAndWait({ prompt: 'Explicit model', agent: 'thinker', model: 'deterministic-provider/deterministic-v2' }, 2)
|
|
637
|
+
// Like /model in the thread.
|
|
638
|
+
await (await server.client()).session.switchModel({ sessionID: sessionId, model: { providerID: 'deterministic-provider', id: 'deterministic-thinker', variant: 'low' } })
|
|
639
|
+
const kept = await sendAndWait({ prompt: 'Same agent again', agent: 'thinker' }, 3)
|
|
640
|
+
expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
|
|
641
|
+
"--- from: user (tommy)
|
|
642
|
+
Model kept thread
|
|
643
|
+
--- from: assistant (TestBot)
|
|
644
|
+
-# *using deterministic-provider/deterministic-v2 ⋅ build*
|
|
645
|
+
ok
|
|
646
|
+
-# *main ⋅ Ns ⋅ deterministic-v2*
|
|
647
|
+
-# Switched model to deterministic-v2
|
|
648
|
+
-# Switched agent to Thinker
|
|
649
|
+
-# » **CLI:** Explicit model
|
|
650
|
+
ok
|
|
651
|
+
-# *main ⋅ Ns ⋅ deterministic-v2 ⋅ thinker*
|
|
652
|
+
-# Switched model to Thinker (low)
|
|
653
|
+
-# » **CLI:** Same agent again
|
|
654
|
+
ok
|
|
655
|
+
-# *main ⋅ Ns ⋅ deterministic-thinker ⋅ thinker*"
|
|
656
|
+
`)
|
|
657
|
+
expect({ explicit, kept }).toMatchInlineSnapshot(`
|
|
658
|
+
{
|
|
659
|
+
"explicit": {
|
|
660
|
+
"agent": "thinker",
|
|
661
|
+
"model": {
|
|
662
|
+
"id": "deterministic-v2",
|
|
663
|
+
"providerID": "deterministic-provider",
|
|
664
|
+
"variant": "default",
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
"kept": {
|
|
668
|
+
"agent": "thinker",
|
|
669
|
+
"model": {
|
|
670
|
+
"id": "deterministic-thinker",
|
|
671
|
+
"providerID": "deterministic-provider",
|
|
672
|
+
"variant": "low",
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
}
|
|
676
|
+
`)
|
|
677
|
+
})
|
package/src/prompt.ts
CHANGED
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
import fs from 'node:fs'
|
|
16
16
|
import path from 'node:path'
|
|
17
17
|
import crypto from 'node:crypto'
|
|
18
|
-
import { Events, MessageFlags, type Message
|
|
18
|
+
import { Events, MessageFlags, type Message } from 'discord.js'
|
|
19
19
|
import * as errore from 'errore'
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
-
|
|
22
|
+
fetchConversation,
|
|
23
23
|
oc,
|
|
24
24
|
parseModel,
|
|
25
|
+
projectChannelOf,
|
|
25
26
|
projectOf,
|
|
26
27
|
rootSession,
|
|
27
28
|
routeText,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
threadOfSession,
|
|
30
31
|
type Author,
|
|
31
32
|
type Bot,
|
|
33
|
+
type Conversation,
|
|
32
34
|
type ModelChoice,
|
|
33
35
|
type PromptFile,
|
|
34
36
|
} from './bot.ts'
|
|
@@ -37,11 +39,11 @@ import { ConfigError, DbError, DiscordError, OpenCodeError, OpenCodeUnavailableE
|
|
|
37
39
|
import { formatError, NOTIFY_MESSAGE_FLAGS } from './format-parts.ts'
|
|
38
40
|
import { createLogger } from './logger.ts'
|
|
39
41
|
import type { SendInput } from './lock-routes.ts'
|
|
40
|
-
import { formatEcho } from './queue.ts'
|
|
42
|
+
import { discordMessageId, formatEcho } from './queue.ts'
|
|
41
43
|
import { noAnswerError, parseRemoteResult, remoteEnvelope, promptFilePath, REMOTE_TIMEOUT_MS, type RemoteResult } from './remote-send.ts'
|
|
42
44
|
import { parseTextMessage, type Route } from './routes.ts'
|
|
43
45
|
import { allMessages } from './session-events.ts'
|
|
44
|
-
import { ensureSessionMarker, forkBtw, startSession, switchAgent,
|
|
46
|
+
import { ensureSessionMarker, forkBtw, startSession, switchAgent, conversationProject } from './sessions.ts'
|
|
45
47
|
import { cancelSleep } from './sleeps.ts'
|
|
46
48
|
import type { ScheduledRun } from './system-prompt.ts'
|
|
47
49
|
import { attachmentsBlock, type PromptAttachment, type TurnContext } from './system-prompt.ts'
|
|
@@ -51,6 +53,16 @@ import { resolveWorkingDirectory } from './worktrees.ts'
|
|
|
51
53
|
const logger = createLogger('PROMPT')
|
|
52
54
|
|
|
53
55
|
|
|
56
|
+
// Who sent a prompt that no person typed in Discord: `kimaki send`, routines,
|
|
57
|
+
// remote sends, sleep wakes. Stored in the prompt metadata; at the run end
|
|
58
|
+
// coordinator.ts reads it: only a run a coordinator prompted wakes that coordinator.
|
|
59
|
+
export type Sender = {
|
|
60
|
+
// The session whose shell ran `kimaki send` (OPENCODE_SESSION_ID).
|
|
61
|
+
sessionId?: string
|
|
62
|
+
// `kimaki send --wait`: until this time (epoch ms) the sender waits for the result itself, so a run that ends before it sends no note.
|
|
63
|
+
waitUntil?: number
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
// Prompt IDs map a Discord message to its inbox item without stored state (spec 9.2.2).
|
|
55
67
|
export function promptIdForMessage(messageId: string): string {
|
|
56
68
|
return `msg_discord_${messageId}`
|
|
@@ -70,6 +82,7 @@ export async function prompt(
|
|
|
70
82
|
skills = [],
|
|
71
83
|
id = promptIdForMessage(messageId),
|
|
72
84
|
echo,
|
|
85
|
+
sender,
|
|
73
86
|
}: {
|
|
74
87
|
sessionId: string
|
|
75
88
|
threadId: string
|
|
@@ -86,6 +99,8 @@ export async function prompt(
|
|
|
86
99
|
// Line the thread shows when OpenCode takes the prompt (queue.ts), for
|
|
87
100
|
// inputs that have no Discord message: wakes, scheduled runs.
|
|
88
101
|
echo?: string
|
|
102
|
+
// Unset: a person typed it in Discord.
|
|
103
|
+
sender?: Sender
|
|
89
104
|
},
|
|
90
105
|
): Promise<OpenCodeUnavailableError | OpenCodeError | void> {
|
|
91
106
|
const { attached, attachments } = await splitPromptFiles(files)
|
|
@@ -100,7 +115,7 @@ export async function prompt(
|
|
|
100
115
|
files: attached,
|
|
101
116
|
...(skills.length > 0 && { skills: skills.map((skill) => ({ id: skill })) }),
|
|
102
117
|
delivery,
|
|
103
|
-
metadata: { discord: { ...discord, ...(echo && { echo }) } },
|
|
118
|
+
metadata: { discord: { ...discord, ...(echo && { echo }) }, ...(sender && { sender }) },
|
|
104
119
|
}),
|
|
105
120
|
)
|
|
106
121
|
if (result instanceof Error) return result
|
|
@@ -163,6 +178,7 @@ export async function steer(
|
|
|
163
178
|
files?: readonly PromptFile[]
|
|
164
179
|
skills?: readonly string[]
|
|
165
180
|
echo?: string
|
|
181
|
+
sender?: Sender
|
|
166
182
|
},
|
|
167
183
|
): Promise<OpenCodeUnavailableError | OpenCodeError | void> {
|
|
168
184
|
const cancelled = await cancelPendingUi(bot, input.threadId)
|
|
@@ -283,6 +299,7 @@ async function command(
|
|
|
283
299
|
author: Author
|
|
284
300
|
messageId: string
|
|
285
301
|
files: readonly PromptFile[]
|
|
302
|
+
echo?: string
|
|
286
303
|
},
|
|
287
304
|
): Promise<OpenCodeUnavailableError | OpenCodeError | void> {
|
|
288
305
|
const { route } = input
|
|
@@ -301,7 +318,7 @@ async function command(
|
|
|
301
318
|
}
|
|
302
319
|
const { attached, attachments } = await splitPromptFiles(input.files)
|
|
303
320
|
const block = attachmentsBlock(attachments)
|
|
304
|
-
bot.eventLoop.dispatch(input.threadId, { type: 'kimaki.command', text: typed, username: input.author.username })
|
|
321
|
+
bot.eventLoop.dispatch(input.threadId, { type: 'kimaki.command', text: typed, username: input.author.username, messageId: discordMessageId(input.messageId), echo: input.echo ?? null })
|
|
305
322
|
const result = await oc(bot, 'session.command', (client) =>
|
|
306
323
|
client.session.command({
|
|
307
324
|
sessionID: input.sessionId,
|
|
@@ -330,6 +347,7 @@ export async function runInSession(
|
|
|
330
347
|
messageId,
|
|
331
348
|
files,
|
|
332
349
|
echo,
|
|
350
|
+
sender,
|
|
333
351
|
model,
|
|
334
352
|
}: {
|
|
335
353
|
sessionId: string
|
|
@@ -341,10 +359,11 @@ export async function runInSession(
|
|
|
341
359
|
messageId: string
|
|
342
360
|
files: readonly PromptFile[]
|
|
343
361
|
echo?: string
|
|
362
|
+
sender?: Sender
|
|
344
363
|
model?: ModelChoice | null
|
|
345
364
|
},
|
|
346
365
|
) {
|
|
347
|
-
const base = { sessionId, threadId, threadName, author, messageId, files, echo }
|
|
366
|
+
const base = { sessionId, threadId, threadName, author, messageId, files, echo, sender }
|
|
348
367
|
switch (route.kind) {
|
|
349
368
|
case 'shell':
|
|
350
369
|
return shell(bot, { threadId, sessionId, command: route.command })
|
|
@@ -371,32 +390,37 @@ export async function runInSession(
|
|
|
371
390
|
export async function dispatch(
|
|
372
391
|
bot: Bot,
|
|
373
392
|
{
|
|
374
|
-
|
|
393
|
+
conversation,
|
|
375
394
|
route,
|
|
376
395
|
author,
|
|
377
396
|
messageId,
|
|
378
397
|
files = [],
|
|
379
398
|
echo,
|
|
399
|
+
sender,
|
|
380
400
|
model,
|
|
401
|
+
parentSessionId,
|
|
381
402
|
}: {
|
|
382
|
-
|
|
403
|
+
conversation: Conversation
|
|
383
404
|
route: Route
|
|
384
405
|
author: Author
|
|
385
406
|
messageId: string
|
|
386
407
|
files?: readonly PromptFile[]
|
|
387
408
|
echo?: string
|
|
409
|
+
sender?: Sender
|
|
388
410
|
model?: ModelChoice | null
|
|
411
|
+
// `kimaki send --parent-session`: only a btw fork records it, the session itself exists.
|
|
412
|
+
parentSessionId?: string
|
|
389
413
|
},
|
|
390
414
|
) {
|
|
391
|
-
const sessionId = rootSession(bot,
|
|
415
|
+
const sessionId = rootSession(bot, conversation.id)
|
|
392
416
|
if (sessionId instanceof Error) return sessionId
|
|
393
417
|
// New input supersedes a pending `kimaki sleep` of this session.
|
|
394
418
|
await cancelSleep(bot, sessionId)
|
|
395
|
-
const project = await
|
|
419
|
+
const project = await conversationProject(bot, conversation)
|
|
396
420
|
if (project instanceof Error) return project
|
|
397
421
|
const marked = await ensureSessionMarker(bot, {
|
|
398
422
|
sessionId,
|
|
399
|
-
|
|
423
|
+
conversation,
|
|
400
424
|
channelId: project.channelId,
|
|
401
425
|
directory: project.directory,
|
|
402
426
|
userId: author.id,
|
|
@@ -404,9 +428,9 @@ export async function dispatch(
|
|
|
404
428
|
if (marked instanceof Error) return marked
|
|
405
429
|
switch (route.kind) {
|
|
406
430
|
case 'btw':
|
|
407
|
-
return forkBtw(bot, {
|
|
431
|
+
return forkBtw(bot, { source: conversation, text: route.text, author, messageId, files, agent: route.agent, parentSessionId, sender })
|
|
408
432
|
case 'new-session':
|
|
409
|
-
// A fresh
|
|
433
|
+
// A fresh conversation in the same channel, with no history.
|
|
410
434
|
return startSession(bot, {
|
|
411
435
|
channelId: project.channelId,
|
|
412
436
|
directory: project.directory,
|
|
@@ -415,20 +439,26 @@ export async function dispatch(
|
|
|
415
439
|
messageId,
|
|
416
440
|
startMessageId: null,
|
|
417
441
|
files,
|
|
442
|
+
sender,
|
|
418
443
|
})
|
|
419
|
-
default:
|
|
444
|
+
default: {
|
|
445
|
+
// The coordinator keeps its agent and model: a worker's `kimaki send --agent <current_agent>` names the worker's agent.
|
|
446
|
+
const coordinator = !conversation.isThread()
|
|
447
|
+
if (coordinator && route.kind === 'steer' && route.agent) logger.log(`ignoring agent ${route.agent} of a prompt to coordinator ${conversation.id}`)
|
|
420
448
|
return runInSession(bot, {
|
|
421
449
|
sessionId,
|
|
422
|
-
threadId:
|
|
423
|
-
threadName:
|
|
450
|
+
threadId: conversation.id,
|
|
451
|
+
threadName: conversation.name,
|
|
424
452
|
directory: project.directory,
|
|
425
|
-
route,
|
|
453
|
+
route: coordinator && route.kind === 'steer' ? { kind: 'steer', text: route.text } : route,
|
|
426
454
|
author,
|
|
427
455
|
messageId,
|
|
428
456
|
files,
|
|
429
457
|
echo,
|
|
430
|
-
|
|
458
|
+
sender,
|
|
459
|
+
model: coordinator ? null : model,
|
|
431
460
|
})
|
|
461
|
+
}
|
|
432
462
|
}
|
|
433
463
|
}
|
|
434
464
|
|
|
@@ -528,7 +558,13 @@ async function remoteSend(bot: Bot, input: SendInput) {
|
|
|
528
558
|
|
|
529
559
|
// `localOnly`: never forward to another machine (remote envelopes, scheduled runs).
|
|
530
560
|
// `task`: a scheduled run; its input shows as "» routine #N: prompt".
|
|
531
|
-
|
|
561
|
+
// `shownIn`: a Discord message that already shows the prompt (remote envelope, clicked buttons).
|
|
562
|
+
// Without it the thread shows the prompt as an echo.
|
|
563
|
+
export async function send(
|
|
564
|
+
bot: Bot,
|
|
565
|
+
input: SendInput,
|
|
566
|
+
{ localOnly = false, task, shownIn }: { localOnly?: boolean; task?: ScheduledRun; shownIn?: string } = {},
|
|
567
|
+
) {
|
|
532
568
|
if (input.worktree !== undefined && (input.cwd || input.threadId || input.sessionId || input.notifyOnly)) {
|
|
533
569
|
return new ConfigError({ reason: '--worktree requires a new session without --cwd or --notify-only.' })
|
|
534
570
|
}
|
|
@@ -538,11 +574,16 @@ export async function send(bot: Bot, input: SendInput, { localOnly = false, task
|
|
|
538
574
|
}
|
|
539
575
|
const userIds = (input.user ?? []).map((user) => user.replace(/[<@!>]/g, ''))
|
|
540
576
|
const author = { id: userIds[0] ?? bot.discord.user!.id, username: task ? `routine #${task.id}` : 'CLI' }
|
|
541
|
-
// Every --user joins the thread; startSession adds
|
|
542
|
-
|
|
543
|
-
|
|
577
|
+
// Every --user joins the thread; startSession adds them to a new session itself.
|
|
578
|
+
// Not awaited: the prompt does not wait for Discord round trips.
|
|
579
|
+
const addMembers = (thread: Conversation, ids: readonly string[]) => {
|
|
580
|
+
// A coordinator channel has no thread members to add.
|
|
581
|
+
if (!thread.isThread()) return
|
|
582
|
+
for (const id of ids) void thread.members.add(id).catch((error: Error) => logger.warn(`add thread member ${id}`, error))
|
|
544
583
|
}
|
|
545
|
-
const messageId = crypto.randomUUID()
|
|
584
|
+
const messageId = shownIn ?? crypto.randomUUID()
|
|
585
|
+
// Also set without a session (routines, a terminal): it marks the prompt as not typed by a person.
|
|
586
|
+
const sender: Sender = { ...(input.senderSessionId && { sessionId: input.senderSessionId }), ...(input.wait !== undefined && { waitUntil: Date.now() + input.wait * 1000 }) }
|
|
546
587
|
const route = parseTextMessage({ content: input.prompt })
|
|
547
588
|
if (!route) return new ConfigError({ reason: 'Prompt is empty' })
|
|
548
589
|
if (input.agent && (route.kind === 'steer' || route.kind === 'btw')) route.agent = input.agent
|
|
@@ -550,23 +591,28 @@ export async function send(bot: Bot, input: SendInput, { localOnly = false, task
|
|
|
550
591
|
if (input.sessionId || input.threadId) {
|
|
551
592
|
const threadId = input.threadId ?? threadOfSession(bot, input.sessionId!)
|
|
552
593
|
if (!threadId) return new ConfigError({ reason: 'No local thread for this session. Use --thread on its owning machine.' })
|
|
553
|
-
const thread = await
|
|
594
|
+
const thread = await fetchConversation(bot, threadId)
|
|
554
595
|
if (thread instanceof Error) return thread
|
|
555
596
|
const sessionId = bot.store.getState().roots[thread.id]
|
|
556
597
|
if (!sessionId) {
|
|
557
|
-
const
|
|
598
|
+
const channelId = projectChannelOf(thread)
|
|
599
|
+
const project = channelId ? await projectOf(bot, channelId) : null
|
|
558
600
|
if (project instanceof Error) return project
|
|
559
601
|
return !project && !localOnly ? remoteSend(bot, input) : new ConfigError({ reason: 'No local session for this thread' })
|
|
560
602
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
603
|
+
addMembers(thread, userIds)
|
|
604
|
+
const explicit = input.model ? parseModel(input.model, null) : null
|
|
605
|
+
if (input.model && !explicit) return new ConfigError({ reason: 'Use --model provider/model' })
|
|
606
|
+
// The coordinator keeps its model; /model in its channel changes it.
|
|
607
|
+
const model = explicit && thread.isThread() ? { ...explicit, variant: null } : null
|
|
608
|
+
if (explicit && !model) logger.log(`ignoring --model ${input.model} of a prompt to coordinator ${thread.id}`)
|
|
609
|
+
if (model) {
|
|
610
|
+
const switched = await switchModel(bot, { sessionId, model })
|
|
566
611
|
if (switched instanceof Error) return switched
|
|
567
612
|
}
|
|
568
|
-
const echo =
|
|
569
|
-
|
|
613
|
+
const echo = shownIn ? undefined : formatEcho({ username: author.username, text: routeText(route) })
|
|
614
|
+
// --model reaches the agent switch too, so an agent with its own model cannot replace it.
|
|
615
|
+
const result = await dispatch(bot, { conversation: thread, route, author, messageId, files: input.files, echo, sender, parentSessionId: input.parentSessionId, model })
|
|
570
616
|
if (result instanceof Error) return result
|
|
571
617
|
return result ?? { threadId, sessionId: bot.store.getState().roots[threadId]! }
|
|
572
618
|
}
|
|
@@ -593,7 +639,7 @@ export async function send(bot: Bot, input: SendInput, { localOnly = false, task
|
|
|
593
639
|
})
|
|
594
640
|
.catch((cause) => new DiscordError({ operation: 'post notification', cause }))
|
|
595
641
|
if (shown instanceof Error) return shown
|
|
596
|
-
|
|
642
|
+
addMembers(thread, userIds)
|
|
597
643
|
return { threadId: thread.id, sessionId: null }
|
|
598
644
|
}
|
|
599
645
|
|
|
@@ -618,27 +664,24 @@ export async function send(bot: Bot, input: SendInput, { localOnly = false, task
|
|
|
618
664
|
: task && input.worktree
|
|
619
665
|
? `${input.worktree.slice(0, 40)}-${crypto.randomBytes(4).toString('hex')}`
|
|
620
666
|
: input.worktree
|
|
621
|
-
|
|
667
|
+
return startSession(bot, {
|
|
622
668
|
channelId: project.channel_id,
|
|
623
669
|
directory,
|
|
624
670
|
route: first,
|
|
625
671
|
author,
|
|
672
|
+
members: userIds.slice(1),
|
|
626
673
|
messageId,
|
|
627
674
|
startMessageId: null,
|
|
628
675
|
threadName: input.name,
|
|
629
676
|
files: input.files,
|
|
630
677
|
permissions,
|
|
631
678
|
parentSessionId: input.parentSessionId,
|
|
679
|
+
sender,
|
|
632
680
|
task,
|
|
633
681
|
worktree,
|
|
634
682
|
baseBranch: input.baseBranch,
|
|
635
683
|
...(model && { model: { ...model, variant: null } }),
|
|
636
684
|
})
|
|
637
|
-
if (started instanceof Error || userIds.length < 2) return started
|
|
638
|
-
const thread = await fetchThread(bot, started.threadId)
|
|
639
|
-
if (thread instanceof Error) logger.warn(`add thread members`, thread)
|
|
640
|
-
else await addMembers(thread, userIds.slice(1))
|
|
641
|
-
return started
|
|
642
685
|
}
|
|
643
686
|
|
|
644
687
|
// `kimaki upload-to-discord`: posts local files into the session's thread (or the voice channel chat),
|
|
@@ -658,7 +701,8 @@ export async function upload(bot: Bot, { id, files }: { id: string; files: Array
|
|
|
658
701
|
if (!stat?.isFile()) return new ConfigError({ reason: `File not found: ${file.path}` })
|
|
659
702
|
sized.push({ ...file, size: stat.size })
|
|
660
703
|
}
|
|
661
|
-
const
|
|
662
|
-
if (
|
|
663
|
-
|
|
704
|
+
const urls = await new Promise<Error | string[]>((settle) => bot.effects.run(threadId, [{ type: 'attachments', files: sized, settle }]))
|
|
705
|
+
if (urls instanceof Error) return urls
|
|
706
|
+
// Discord keeps attachments in upload order. The URLs let a coordinator repost the files.
|
|
707
|
+
return { uploaded: files.map((file, index) => ({ name: file.name, url: urls[index] ?? null })) }
|
|
664
708
|
}
|