kimaki 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/commands/worktree-commands.d.ts +2 -2
  2. package/dist/coordinator-agents.e2e.test.js +24 -2
  3. package/dist/coordinator-agents.e2e.test.js.map +1 -1
  4. package/dist/db.d.ts +3 -3
  5. package/dist/ingress.js +23 -3
  6. package/dist/ingress.js.map +1 -1
  7. package/dist/lock-routes.d.ts +33 -33
  8. package/dist/project.d.ts +1 -1
  9. package/dist/prompt.d.ts +1 -1
  10. package/dist/scheduler.d.ts +3 -3
  11. package/dist/schema.d.ts +6 -6
  12. package/dist/session-events.d.ts +1 -1
  13. package/dist/session-ops.e2e.test.js +26 -0
  14. package/dist/session-ops.e2e.test.js.map +1 -1
  15. package/dist/sessions.d.ts +9 -9
  16. package/dist/slash-commands.js +6 -2
  17. package/dist/slash-commands.js.map +1 -1
  18. package/dist/sleeps.d.ts +1 -1
  19. package/dist/voice.d.ts +1 -1
  20. package/dist/voice.js +10 -2
  21. package/dist/voice.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/coordinator-agents.e2e.test.ts +25 -2
  24. package/src/ingress.ts +19 -2
  25. package/src/session-ops.e2e.test.ts +27 -0
  26. package/src/slash-commands.ts +6 -2
  27. package/src/voice.ts +10 -2
  28. package/dist/mainmast-idle.e2e.test.d.ts +0 -1
  29. package/dist/mainmast-idle.e2e.test.js +0 -99
  30. package/dist/mainmast-idle.e2e.test.js.map +0 -1
  31. package/dist/mainmast-queue.e2e.test.d.ts +0 -1
  32. package/dist/mainmast-queue.e2e.test.js +0 -244
  33. package/dist/mainmast-queue.e2e.test.js.map +0 -1
  34. package/dist/mainmast-warmup.e2e.test.d.ts +0 -1
  35. package/dist/mainmast-warmup.e2e.test.js +0 -130
  36. package/dist/mainmast-warmup.e2e.test.js.map +0 -1
  37. package/dist/mainmast.d.ts +0 -6
  38. package/dist/mainmast.e2e.test.d.ts +0 -1
  39. package/dist/mainmast.e2e.test.js +0 -428
  40. package/dist/mainmast.e2e.test.js.map +0 -1
  41. package/dist/mainmast.js +0 -156
  42. package/dist/mainmast.js.map +0 -1
  43. package/dist/message-tool.e2e.test.d.ts +0 -1
  44. package/dist/message-tool.e2e.test.js +0 -39
  45. package/dist/message-tool.e2e.test.js.map +0 -1
  46. package/dist/plugin/mainmast.d.ts +0 -4
  47. package/dist/plugin/mainmast.js +0 -379
  48. package/dist/plugin/mainmast.js.map +0 -1
  49. package/dist/plugin/read-media.d.ts +0 -23
  50. package/dist/plugin/read-media.js +0 -72
  51. package/dist/plugin/read-media.js.map +0 -1
  52. package/dist/plugin/session-id/index.d.ts +0 -3
  53. package/dist/plugin/session-id/index.js +0 -18
  54. package/dist/plugin/session-id/index.js.map +0 -1
  55. package/dist/probe-live.tmp.d.ts +0 -1
  56. package/dist/probe-live.tmp.js +0 -82
  57. package/dist/probe-live.tmp.js.map +0 -1
  58. package/dist/tasks.e2e.test.d.ts +0 -1
  59. package/dist/tasks.e2e.test.js +0 -173
  60. package/dist/tasks.e2e.test.js.map +0 -1
@@ -1,5 +1,5 @@
1
- // Agent picks in a coordinator channel: `/<agent>-agent prompt:` and a spoken
2
- // agent start a new thread with that agent, and `kimaki send --agent --model`
1
+ // Agent picks in a coordinator channel: `/<agent>-agent` (with or without a prompt)
2
+ // and a spoken agent start a new thread with that agent, and `kimaki send --agent --model`
3
3
  // to the coordinator (workers pass --agent <current_agent>) keeps its agent and model.
4
4
 
5
5
  import fs from 'node:fs'
@@ -36,6 +36,7 @@ const matchers: DeterministicMatcher[] = [
36
36
  reply('hello-marker', 'hello'),
37
37
  reply('slash-agent-marker', 'slash agent ok'),
38
38
  reply('voice-agent-marker', 'voice agent ok'),
39
+ reply('empty-agent-marker', 'empty agent ok'),
39
40
  reply('worker-note-marker', 'IDLE'),
40
41
  ]
41
42
 
@@ -107,6 +108,28 @@ test('/<agent>-agent with a prompt starts a thread with that agent; the coordina
107
108
  expect(await coordinator()).toEqual(initial)
108
109
  })
109
110
 
111
+ test('/<agent>-agent without a prompt starts an idle thread with that agent; the coordinator keeps its agent and model', async () => {
112
+ const { discord, channelId } = twin
113
+ const before = new Set((await discord.channel(channelId).getThreads()).map((thread) => thread.id))
114
+ await discord.channel(channelId).user(TEST_USER_ID).runSlashCommand({ name: 'thinker-agent' })
115
+ const thread = await discord.channel(channelId).waitForThread({ timeout: 8_000, predicate: (candidate) => !before.has(candidate.id) })
116
+ await waitForBotMessageContaining({ discord, threadId: thread.id, text: 'Send a message to start working.' })
117
+ expect(await coordinator()).toEqual(initial)
118
+ await discord.thread(thread.id).user(TEST_USER_ID).sendMessage({ content: 'Think empty-agent-marker' })
119
+ await waitForFooter({ discord, threadId: thread.id })
120
+ expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
121
+ "--- from: assistant (TestBot)
122
+ Send a message to start working.
123
+ --- from: user (tommy)
124
+ Think empty-agent-marker
125
+ --- from: assistant (TestBot)
126
+ -# *using deterministic-provider/deterministic-thinker ⋅ thinker*
127
+ empty agent ok
128
+ -# *main ⋅ Ns ⋅ N% ⋅ deterministic-thinker ⋅ thinker*"
129
+ `)
130
+ expect(await coordinator()).toEqual(initial)
131
+ })
132
+
110
133
  test('a spoken agent starts a thread with that agent; the coordinator keeps its agent and model', async () => {
111
134
  const thread = await newThread(() =>
112
135
  twin.discord.channel(twin.channelId).user(TEST_USER_ID).sendVoiceMessage({ url: voiceUrl({ transcription: 'Think voice-agent-marker', route: 'steer', agent: 'thinker' }) }),
package/src/ingress.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // Discord ingress (spec 4): gates every messageCreate, parses it into a Route
2
2
  // and calls actions. Gates, in order: webhooks and our own messages (except
3
- // remote send envelopes), channel ownership (only channels mapped in this
3
+ // remote send envelopes), system messages (a thread rename only sets the
4
+ // session title), channel ownership (only channels mapped in this
4
5
  // machine's SQLite), permission (other bots need the Kimaki role).
5
6
  // Messages of one channel are handled in arrival order, after OpenCode is ready.
6
7
  //
@@ -21,6 +22,7 @@ import {
21
22
  Events,
22
23
  GuildMember,
23
24
  MessageFlags,
25
+ MessageType,
24
26
  PermissionFlagsBits,
25
27
  type Attachment,
26
28
  type Guild,
@@ -28,7 +30,7 @@ import {
28
30
  } from 'discord.js'
29
31
  import * as errore from 'errore'
30
32
 
31
- import { isCoordinatorChannel, projectOf, sessionDirectory, type Bot, type PromptFile } from './bot.ts'
33
+ import { isCoordinatorChannel, oc, projectOf, sessionDirectory, type Bot, type PromptFile } from './bot.ts'
32
34
  import { formatError, NOTIFY_MESSAGE_FLAGS, SILENT_MESSAGE_FLAGS } from './format-parts.ts'
33
35
  import { createLogger } from './logger.ts'
34
36
  import { parseInput, sendInput } from './lock-routes.ts'
@@ -320,6 +322,18 @@ async function handleMessage(bot: Bot, message: Message) {
320
322
  await message.reply({ content: note, allowedMentions: { repliedUser: false }, flags: SILENT_MESSAGE_FLAGS }).catch(() => undefined)
321
323
  }
322
324
 
325
+ // A rename in the Discord UI posts a CHANNEL_NAME_CHANGE message with the new
326
+ // name as content (issue #258): the session title follows it, nothing is prompted.
327
+ async function handleThreadRename(bot: Bot, message: Message) {
328
+ if (!message.inGuild() || !message.channel.isThread()) return
329
+ const sessionId = bot.store.getState().roots[message.channel.id]
330
+ if (!sessionId || !(await authorCanUseKimaki(bot, message))) return
331
+ const title = message.content
332
+ const renamed = await oc(bot, 'session.update', (client) => client.session.update({ sessionID: sessionId, title }))
333
+ if (renamed instanceof Error) return logger.warn(`title of ${sessionId} from thread rename`, renamed)
334
+ logger.log(`session ${sessionId} renamed to "${title}" by ${message.author.username}`)
335
+ }
336
+
323
337
  // Registers the message listeners. Messages of one channel are handled in arrival order:
324
338
  // a text sent while a voice message is transcribed waits for it (voice-attachments.e2e.test.ts).
325
339
  export function registerIngress(bot: Bot) {
@@ -341,6 +355,9 @@ export function registerIngress(bot: Bot) {
341
355
  // Webhooks have no member to check. Own messages only matter as remote send envelopes.
342
356
  if (message.webhookId) return
343
357
  if (message.author.id === bot.discord.user?.id && !message.embeds[0]?.footer?.text.startsWith(REMOTE_SEND_PREFIX)) return
358
+ if (message.type === MessageType.ChannelNameChange) return serialize(message.channelId, () => handleThreadRename(bot, message))
359
+ // Pins, joins, boosts and other system messages are never prompts.
360
+ if (message.type !== MessageType.Default && message.type !== MessageType.Reply) return
344
361
  serialize(message.channelId, () => handleMessage(bot, message))
345
362
  })
346
363
  // Deleting a queued Discord message removes it from the queue.
@@ -164,6 +164,33 @@ test('/compact summarizes the history without a footer', async () => {
164
164
  `)
165
165
  })
166
166
 
167
+ // Issue #258: the rename's CHANNEL_NAME_CHANGE message holds only the new name.
168
+ test('renaming the thread in Discord sets the session title without a prompt', async () => {
169
+ const thread = await newThread('Hello rename-marker')
170
+ const sessionId = bot.store.getState().roots[thread.id]!
171
+ const client = await server.client()
172
+ const requests = server.model.requests.length
173
+ await twin.discord.thread(thread.id).user(TEST_USER_ID).renameThread({ name: 'Renamed by hand' })
174
+ await waitFor({
175
+ label: 'session title from the thread name',
176
+ check: async () => (await client.session.get({ sessionID: sessionId })).title === 'Renamed by hand' || null,
177
+ })
178
+ for (let i = 0; i < 10; i++) {
179
+ await new Promise((resolve) => setTimeout(resolve, 20))
180
+ expect(server.model.requests.length).toBe(requests)
181
+ }
182
+ expect(await twin.discord.thread(thread.id).text()).toMatchInlineSnapshot(`
183
+ "--- from: user (tommy)
184
+ Hello rename-marker
185
+ --- from: assistant (TestBot)
186
+ -# *using deterministic-provider/deterministic-v2 ⋅ build*
187
+ ok
188
+ -# *main ⋅ Ns ⋅ deterministic-v2*
189
+ --- from: user (tommy)
190
+ [renamed thread to: Renamed by hand]"
191
+ `)
192
+ })
193
+
167
194
  test('/undo hides the last turn but keeps its files, /redo restores the turn', async () => {
168
195
  const file = path.join(server.projectDirectory, 'undo.txt')
169
196
  const thread = await newThread('Create a file write-marker')
@@ -49,7 +49,7 @@ import {
49
49
  } from 'discord.js'
50
50
 
51
51
  import { agentUiRoutes } from './agent-ui.ts'
52
- import { oc, projectOf, type Bot, type ModelChoice } from './bot.ts'
52
+ import { isCoordinatorChannel, oc, projectOf, routeText, type Bot, type ModelChoice } from './bot.ts'
53
53
  import { createLoginRoutes } from './commands/login-commands.ts'
54
54
  import { applyAgent, createPreferenceRoutes, switchModel, variantChoices, variantModel } from './commands/preference-commands.ts'
55
55
  import { sessionRoutes } from './commands/session-commands.ts'
@@ -348,6 +348,8 @@ async function sendInput(
348
348
  messageId: interaction.id,
349
349
  startMessageId: null,
350
350
  ...(model && { model }),
351
+ // An agent pick without a prompt: the session waits in the thread for its first message.
352
+ run: Boolean(routeText(route)),
351
353
  })
352
354
  if (started instanceof Error) return replyError(interaction, started)
353
355
  await interaction.editReply({ content: `Started a new session in <#${started.threadId}>` })
@@ -361,7 +363,9 @@ async function handleDynamic(bot: Bot, { interaction, target }: { interaction: C
361
363
  if (where instanceof Error) return replyError(interaction, where)
362
364
  const model = variant ? await variantModel(bot, { target: where, variant, agent: target.name }) : null
363
365
  if (model instanceof Error) return replyError(interaction, model)
364
- if (!text) return applyAgent(bot, { interaction, target: where, agent: target.name, model })
366
+ // In a coordinator channel the pick starts a new thread, with or without a prompt (sendInput).
367
+ const coordinatorChannel = !where.conversation?.isThread() && isCoordinatorChannel(bot, where.channelId)
368
+ if (!text && !coordinatorChannel) return applyAgent(bot, { interaction, target: where, agent: target.name, model })
365
369
  const label = model ? `${target.name}, ${model.variant}` : target.name
366
370
  return sendInput(bot, { interaction, route: { kind: 'steer', text, agent: target.name }, echo: `(${label}) ${text}`, model })
367
371
  }
package/src/voice.ts CHANGED
@@ -51,6 +51,13 @@ const RETRY_DELAY_MS = 500
51
51
  // Later messages of the conversation wait for the transcription (ingress.ts): cap all providers and retries.
52
52
  const TRANSCRIPTION_TIMEOUT_MS = 2 * 60_000
53
53
 
54
+ // KIMAKI_WHISPER_TIMEOUT (seconds) raises the cap for a slow local Whisper server (CPU only).
55
+ function transcriptionTimeoutMs(entries: Transcriber[]) {
56
+ const seconds = Number(process.env['KIMAKI_WHISPER_TIMEOUT'])
57
+ if (!entries.some((entry) => entry.kind === 'local-whisper') || !(seconds > 0)) return TRANSCRIPTION_TIMEOUT_MS
58
+ return seconds * 1000
59
+ }
60
+
54
61
  export class TranscriptionError extends errore.createTaggedError({
55
62
  name: 'TranscriptionError',
56
63
  message: 'Transcription failed: $reason',
@@ -699,7 +706,8 @@ export async function transcribe({
699
706
  const prompt = list.some((entry) => entry.kind === 'openai' || entry.kind === 'gemini')
700
707
  ? transcriptionPrompt({ fileTree: await projectFileTree(directory), agents })
701
708
  : ''
702
- const signal = AbortSignal.timeout(TRANSCRIPTION_TIMEOUT_MS)
709
+ const timeoutMs = transcriptionTimeoutMs(list)
710
+ const signal = AbortSignal.timeout(timeoutMs)
703
711
  const run = (entry: Transcriber) => {
704
712
  if (entry.kind === 'gateway') return transcribeViaGateway({ audio, mediaType, ...entry, signal })
705
713
  if (entry.kind === 'local-whisper') return withRetries({ signal, attempt: () => transcribeViaLocalWhisper({ whisper: entry.whisper, audio, mediaType, signal }) })
@@ -712,7 +720,7 @@ export async function transcribe({
712
720
  }
713
721
  let last: TranscriptionFailure | NoTranscriptionKeyError = new NoTranscriptionKeyError()
714
722
  for (const entry of list) {
715
- if (signal.aborted) return new TranscriptionError({ reason: `no answer within ${TRANSCRIPTION_TIMEOUT_MS / 60_000} minutes` })
723
+ if (signal.aborted) return new TranscriptionError({ reason: `no answer within ${timeoutMs / 60_000} minutes` })
716
724
  const result = await run(entry)
717
725
  if (!(result instanceof Error) || result instanceof EmptyTranscriptionError) return result
718
726
  logger.warn(`${entry.kind} transcription failed${entry === list.at(-1) ? '' : ', trying the next provider'}`, result)
@@ -1 +0,0 @@
1
- export {};
@@ -1,99 +0,0 @@
1
- // The mainmast plugin warmup (plugin/mainmast.ts) with a short window: an idle
2
- // mainmast with no running threads ends its warmup at the first check and is compacted.
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { afterAll, beforeAll, expect, test } from 'vitest';
6
- import { TEST_MODEL, TEST_USER_ID, seedProjectChannel, startOpencodeTestServer, startTestBot, startTwin, tempDataDir, textParts, waitFor, waitForFooter, warmUp, } from "./test/harness.js";
7
- let server;
8
- let twin;
9
- let bot;
10
- const dataDir = tempDataDir();
11
- // A large context, so the compaction is worth it (plugin/mainmast.ts MIN_COMPACT_TOKENS),
12
- // in a 1M window like Claude Opus: only 12% of it, far below automatic compaction.
13
- const bigUsage = { inputTokens: 120_000, outputTokens: 1, totalTokens: 120_001 };
14
- beforeAll(async () => {
15
- ;
16
- [server, twin] = await Promise.all([
17
- startOpencodeTestServer({
18
- models: { [TEST_MODEL]: { name: TEST_MODEL, limit: { context: 1_000_000, output: 32_000 } } },
19
- matchers: [
20
- // OpenCode rejects a summary without the template headings.
21
- { id: 'compaction-summary', priority: 900, when: { latestUserTextIncludes: 'You MUST use this format for your response' }, then: { parts: textParts('## Objective\n- Keep coordinating') } },
22
- {
23
- id: 'hello',
24
- priority: 100,
25
- when: { latestUserTextIncludes: 'idle-marker' },
26
- then: { parts: [{ type: 'text-start', id: 't' }, { type: 'text-delta', id: 't', delta: 'Hi there.' }, { type: 'text-end', id: 't' }, { type: 'finish', finishReason: 'stop', usage: bigUsage }] },
27
- },
28
- ],
29
- }),
30
- startTwin(),
31
- ]);
32
- await seedProjectChannel({ dataDir, channelId: twin.channelId, guildId: twin.discord.guildId, directory: server.projectDirectory });
33
- await warmUp({ server });
34
- bot = await startTestBot({ dataDir, twin, server, mainmast: true, mainmastTimers: { warmIntervalMs: 1_200, warmMaxMs: 60_000 } });
35
- }, 60_000);
36
- afterAll(async () => {
37
- await bot?.stop();
38
- await twin?.stop();
39
- await server?.stop();
40
- fs.rmSync(dataDir, { recursive: true, force: true });
41
- });
42
- test('an idle mainmast gets compacted when its warmup ends', async () => {
43
- const { discord, channelId } = twin;
44
- await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Hello idle-marker' });
45
- await waitForFooter({ discord, threadId: channelId });
46
- const client = await server.client();
47
- const root = bot.store.getState().roots[channelId];
48
- await waitFor({
49
- label: 'compaction of the idle mainmast',
50
- check: async () => (await client.message.list({ sessionID: root, order: 'desc', limit: 10 })).data.some((message) => message.type === 'compaction') || null,
51
- });
52
- expect(await discord.channel(channelId).text()).toMatchInlineSnapshot(`
53
- "--- from: user (tommy)
54
- Hello idle-marker
55
- --- from: assistant (TestBot)
56
- -# *using deterministic-provider/deterministic-v2 ⋅ build*
57
- Hi there.
58
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
59
- `);
60
- const messages = await client.message.list({ sessionID: root, order: 'asc', limit: 20 });
61
- expect(messages.data.map((message) => message.type)).toMatchInlineSnapshot(`
62
- [
63
- "synthetic",
64
- "synthetic",
65
- "user",
66
- "assistant",
67
- "idle",
68
- "compaction",
69
- "idle",
70
- ]
71
- `);
72
- });
73
- // OpenCode reloads the plugin when its files change (every Kimaki build or upgrade).
74
- test('a plugin reload keeps the pending compaction', async () => {
75
- const { discord, channelId } = twin;
76
- const client = await server.client();
77
- const root = bot.store.getState().roots[channelId];
78
- const compactions = async () => (await client.message.list({ sessionID: root, order: 'desc', limit: 20 })).data.filter((message) => message.type === 'compaction').length;
79
- expect(await compactions()).toBe(1);
80
- await discord.channel(channelId).user(TEST_USER_ID).sendMessage({ content: 'Again idle-marker' });
81
- await waitForFooter({ discord, threadId: channelId, count: 2 });
82
- fs.appendFileSync(path.join(server.configDir, 'plugins', 'kimaki', 'index.js'), '// reload\n');
83
- await waitFor({ label: 'compaction after the plugin reload', check: async () => (await compactions()) === 2 || null });
84
- const messages = await client.message.list({ sessionID: root, order: 'asc', limit: 30 });
85
- const firstCompaction = messages.data.findIndex((message) => message.type === 'compaction');
86
- expect(messages.data.slice(firstCompaction + 1).map((message) => message.type)).toMatchInlineSnapshot(`
87
- [
88
- "idle",
89
- "synthetic",
90
- "synthetic",
91
- "user",
92
- "assistant",
93
- "idle",
94
- "compaction",
95
- "idle",
96
- ]
97
- `);
98
- });
99
- //# sourceMappingURL=mainmast-idle.e2e.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mainmast-idle.e2e.test.js","sourceRoot":"","sources":["../src/mainmast-idle.e2e.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,244 +0,0 @@
1
- // The queue in a mainmast channel: the channel itself is the conversation, so
2
- // acks and "Queued message sent" replies land in the channel. Every queued
3
- // prompt that got an ack is marked when it starts, also when the coordinator
4
- // answers IDLE; its answer is then not a second reply to the same message.
5
- import { execFile } from 'node:child_process';
6
- import fs from 'node:fs';
7
- import path from 'node:path';
8
- import { promisify } from 'node:util';
9
- import { afterAll, beforeAll, expect, test } from 'vitest';
10
- import { TEST_USER_ID, seedProjectChannel, slowTextMatcher, startOpencodeTestServer, startTestBot, startTwin, tempDataDir, textParts, waitFor, waitForBotMessageContaining, waitForFooter, warmUp, } from "./test/harness.js";
11
- const exec = promisify(execFile);
12
- function reply(marker, text) {
13
- return { id: marker, priority: 50, when: { latestUserTextIncludes: marker }, then: { parts: textParts(text) } };
14
- }
15
- const matchers = [
16
- slowTextMatcher({ marker: 'slow-marker', text: 'slow-done', delayMs: 1_200 }),
17
- reply('queued-idle', 'IDLE'),
18
- reply('queued-one', 'queued one ok'),
19
- reply('queued-two', 'queued two ok'),
20
- reply('steer-marker', 'steer ok'),
21
- reply('idle-marker', 'idle ok'),
22
- reply('hello-marker', 'hello'),
23
- ];
24
- let server;
25
- let twin;
26
- let bot;
27
- const dataDir = tempDataDir();
28
- beforeAll(async () => {
29
- ;
30
- [server, twin] = await Promise.all([startOpencodeTestServer({ matchers }), startTwin()]);
31
- await seedProjectChannel({ dataDir, channelId: twin.channelId, guildId: twin.discord.guildId, directory: server.projectDirectory });
32
- await warmUp({ server });
33
- bot = await startTestBot({ dataDir, twin, server, mainmast: true });
34
- // The mainmast session exists before any test: its banner is not part of one test's snapshot.
35
- await twin.discord.channel(twin.channelId).user(TEST_USER_ID).sendMessage({ content: 'Start hello-marker' });
36
- await waitForFooter({ discord: twin.discord, threadId: twin.channelId });
37
- }, 60_000);
38
- afterAll(async () => {
39
- await bot?.stop();
40
- await twin?.stop();
41
- await server?.stop();
42
- fs.rmSync(dataDir, { recursive: true, force: true });
43
- });
44
- // The channel holds every test's turns: the text from the user message that starts with `content`.
45
- async function channelTextFrom(content) {
46
- const text = await twin.discord.channel(twin.channelId).text({ showReplies: true });
47
- return text.slice(text.lastIndexOf('--- from:', text.lastIndexOf(`\n${content}`)));
48
- }
49
- function footers() {
50
- return twin.discord.channel(twin.channelId).getMessages().then((messages) => messages.filter((message) => message.content.startsWith('-# *project')).length);
51
- }
52
- // Sends a slow prompt and waits until the coordinator runs it.
53
- async function startSlowTurn(content) {
54
- const channel = twin.discord.channel(twin.channelId);
55
- channel.clearTypingEvents();
56
- await channel.user(TEST_USER_ID).sendMessage({ content: `${content} slow-marker` });
57
- await channel.waitForTypingEvent();
58
- }
59
- async function waitForIdle() {
60
- await waitFor({ label: 'mainmast idle', check: async () => !bot.store.getState().threads[twin.channelId]?.turn || null });
61
- }
62
- function rootSession() {
63
- const sessionID = bot.store.getState().roots[twin.channelId];
64
- if (!sessionID)
65
- throw new Error('no mainmast session');
66
- return sessionID;
67
- }
68
- test('. queue in a busy mainmast: each queued message gets a reply when it starts, also when it answers IDLE', async () => {
69
- const before = await footers();
70
- await startSlowTurn('First a long one');
71
- const user = twin.discord.channel(twin.channelId).user(TEST_USER_ID);
72
- const silent = await user.sendMessage({ content: 'Start a worker queued-idle. queue' });
73
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'position 1' });
74
- const answered = await user.sendMessage({ content: 'Then answer queued-two. queue' });
75
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'queued two ok' });
76
- // The IDLE turn posts no footer: slow-done and queued two get one each.
77
- await waitForFooter({ discord: twin.discord, threadId: twin.channelId, count: before + 2 });
78
- expect(await channelTextFrom('First a long one')).toMatchInlineSnapshot(`
79
- "--- from: user (tommy)
80
- First a long one slow-marker
81
- Start a worker queued-idle. queue
82
- --- from: assistant (TestBot)
83
- [reply to tommy: Start a worker queued-idle. queue]
84
- -# Queued
85
- --- from: user (tommy)
86
- Then answer queued-two. queue
87
- --- from: assistant (TestBot)
88
- [reply to tommy: Then answer queued-two. queue]
89
- -# Queued
90
- [reply to tommy: First a long one slow-marker]
91
- slow-done
92
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
93
- [reply to tommy: Start a worker queued-idle. queue]
94
- -# Queued message sent
95
- [reply to tommy: Then answer queued-two. queue]
96
- -# Queued message sent
97
- queued two ok
98
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
99
- `);
100
- const messages = await twin.discord.channel(twin.channelId).getMessages();
101
- const sent = messages.filter((message) => message.content === '-# Queued message sent');
102
- expect(sent.map((message) => message.message_reference?.message_id)).toEqual([silent.id, answered.id]);
103
- });
104
- test('/queue and /clear-queue in a mainmast', async () => {
105
- const before = await footers();
106
- await startSlowTurn('Slash queue here');
107
- const user = twin.discord.channel(twin.channelId).user(TEST_USER_ID);
108
- await user.runSlashCommand({ name: 'queue', options: [{ name: 'message', type: 3, value: 'Slash queued-one' }] });
109
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'position 1' });
110
- await user.runSlashCommand({ name: 'queue', options: [{ name: 'message', type: 3, value: 'Slash queued-two' }] });
111
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'position 2' });
112
- await user.runSlashCommand({ name: 'clear-queue', options: [{ name: 'position', type: 4, value: 1 }] });
113
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'queued two ok' });
114
- await waitForFooter({ discord: twin.discord, threadId: twin.channelId, count: before + 2 });
115
- expect(await channelTextFrom('Slash queue here')).toMatchInlineSnapshot(`
116
- "--- from: user (tommy)
117
- Slash queue here slow-marker
118
- --- from: assistant (TestBot)
119
- -# » **tommy:** Slash queued-one
120
- [reply to TestBot: -# » **tommy:** Slash queued-one]
121
- -# Removed from queue
122
- -# » **tommy:** Slash queued-two
123
- [reply to TestBot: -# » **tommy:** Slash queued-two]
124
- -# Queued
125
- -# Cleared 1 queued message
126
- [reply to tommy: Slash queue here slow-marker]
127
- slow-done
128
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
129
- [reply to TestBot: -# » **tommy:** Slash queued-two]
130
- -# Queued message sent
131
- queued two ok
132
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
133
- `);
134
- });
135
- test('deleting a queued mainmast message removes it from the queue', async () => {
136
- const before = await footers();
137
- await startSlowTurn('Delete in mainmast');
138
- const user = twin.discord.channel(twin.channelId).user(TEST_USER_ID);
139
- const queued = await user.sendMessage({ content: 'Never mind queued-one. queue' });
140
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'position 1' });
141
- await user.deleteMessage({ messageId: queued.id });
142
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'Removed from queue' });
143
- await waitForFooter({ discord: twin.discord, threadId: twin.channelId, count: before + 1 });
144
- await waitForIdle();
145
- expect(await channelTextFrom('Delete in mainmast')).toMatchInlineSnapshot(`
146
- "--- from: user (tommy)
147
- Delete in mainmast slow-marker
148
- --- from: assistant (TestBot)
149
- [reply to unknown message]
150
- -# Removed from queue
151
- [reply to tommy: Delete in mainmast slow-marker]
152
- slow-done
153
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
154
- `);
155
- });
156
- test('kimaki send . queue and a TUI prompt queued on the mainmast show their text, then a reply when they run', async () => {
157
- const before = await footers();
158
- await startSlowTurn('Other sources');
159
- await exec(process.execPath, ['--import', 'tsx', path.resolve('src/cli.ts'), 'send', '--session', rootSession(), '--prompt', 'From the CLI queued-one. queue', '--data-dir', dataDir], {
160
- env: { ...process.env, KIMAKI_LOCK_PORT: String(bot.lock.port) },
161
- });
162
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'position 1' });
163
- await (await server.client()).session.prompt({ sessionID: rootSession(), text: 'From the TUI queued-two', delivery: 'queue' });
164
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'queued two ok' });
165
- await waitForFooter({ discord: twin.discord, threadId: twin.channelId, count: before + 3 });
166
- expect(await channelTextFrom('Other sources')).toMatchInlineSnapshot(`
167
- "--- from: user (tommy)
168
- Other sources slow-marker
169
- --- from: assistant (TestBot)
170
- -# » **CLI:** From the CLI queued-one
171
- -# » **user:** From the TUI queued-two
172
- [reply to tommy: Other sources slow-marker]
173
- slow-done
174
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
175
- [reply to TestBot: -# » **CLI:** From the CLI queued-one]
176
- -# Queued message sent
177
- queued one ok
178
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
179
- [reply to TestBot: -# » **user:** From the TUI queued-two]
180
- -# Queued message sent
181
- queued two ok
182
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
183
- `);
184
- });
185
- test('a plain message in a busy mainmast runs first, then the queued one gets its reply', async () => {
186
- const before = await footers();
187
- await startSlowTurn('Steer the mainmast');
188
- const user = twin.discord.channel(twin.channelId).user(TEST_USER_ID);
189
- await user.sendMessage({ content: 'Afterwards queued-one. queue' });
190
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'position 1' });
191
- await user.sendMessage({ content: 'Right now steer-marker' });
192
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'queued one ok' });
193
- await waitForFooter({ discord: twin.discord, threadId: twin.channelId, count: before + 2 });
194
- expect(await channelTextFrom('Steer the mainmast')).toMatchInlineSnapshot(`
195
- "--- from: user (tommy)
196
- Steer the mainmast slow-marker
197
- Afterwards queued-one. queue
198
- --- from: assistant (TestBot)
199
- [reply to tommy: Afterwards queued-one. queue]
200
- -# Queued
201
- --- from: user (tommy)
202
- Right now steer-marker
203
- --- from: assistant (TestBot)
204
- [reply to tommy: Right now steer-marker]
205
- steer ok
206
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
207
- [reply to tommy: Afterwards queued-one. queue]
208
- -# Queued message sent
209
- queued one ok
210
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
211
- `);
212
- });
213
- test('/abort in a mainmast stops the run and clears the queue', async () => {
214
- await startSlowTurn('Abort the mainmast');
215
- const user = twin.discord.channel(twin.channelId).user(TEST_USER_ID);
216
- await user.sendMessage({ content: 'Never runs queued-one. queue' });
217
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'position 1' });
218
- await user.runSlashCommand({ name: 'abort' });
219
- await waitForBotMessageContaining({ discord: twin.discord, threadId: twin.channelId, text: 'Removed from queue' });
220
- await waitForIdle();
221
- expect(await channelTextFrom('Abort the mainmast')).toMatchInlineSnapshot(`
222
- "--- from: user (tommy)
223
- Abort the mainmast slow-marker
224
- Never runs queued-one. queue
225
- --- from: assistant (TestBot)
226
- [reply to tommy: Never runs queued-one. queue]
227
- -# Removed from queue
228
- Request **aborted**, cleared 1 queued message"
229
- `);
230
- });
231
- test('. queue on an idle mainmast runs at once: the answer replies to the message', async () => {
232
- const before = await footers();
233
- await twin.discord.channel(twin.channelId).user(TEST_USER_ID).sendMessage({ content: 'Nothing waits idle-marker. queue' });
234
- await waitForFooter({ discord: twin.discord, threadId: twin.channelId, count: before + 1 });
235
- expect(await channelTextFrom('Nothing waits idle-marker')).toMatchInlineSnapshot(`
236
- "--- from: user (tommy)
237
- Nothing waits idle-marker. queue
238
- --- from: assistant (TestBot)
239
- [reply to tommy: Nothing waits idle-marker. queue]
240
- idle ok
241
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
242
- `);
243
- });
244
- //# sourceMappingURL=mainmast-queue.e2e.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mainmast-queue.e2e.test.js","sourceRoot":"","sources":["../src/mainmast-queue.e2e.test.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,2EAA2E;AAC3E,6EAA6E;AAC7E,2EAA2E;AAE3E,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAG1D,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,SAAS,EACT,WAAW,EACX,SAAS,EACT,OAAO,EACP,2BAA2B,EAC3B,aAAa,EACb,MAAM,GAGP,MAAM,mBAAmB,CAAA;AAE1B,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;AAEhC,SAAS,KAAK,CAAC,MAAc,EAAE,IAAY;IACzC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,sBAAsB,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,CAAA;AACjH,CAAC;AAED,MAAM,QAAQ,GAA2B;IACvC,eAAe,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC7E,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,YAAY,EAAE,eAAe,CAAC;IACpC,KAAK,CAAC,YAAY,EAAE,eAAe,CAAC;IACpC,KAAK,CAAC,cAAc,EAAE,UAAU,CAAC;IACjC,KAAK,CAAC,aAAa,EAAE,SAAS,CAAC;IAC/B,KAAK,CAAC,cAAc,EAAE,OAAO,CAAC;CAC/B,CAAA;AAED,IAAI,MAA0B,CAAA;AAC9B,IAAI,IAAc,CAAA;AAClB,IAAI,GAAc,CAAA;AAClB,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;AAE7B,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,CAAC;IAAA,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,uBAAuB,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;IACzF,MAAM,kBAAkB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAA;IACnI,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;IACxB,GAAG,GAAG,MAAM,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IACnE,8FAA8F;IAC9F,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAA;IAC5G,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;AAC1E,CAAC,EAAE,MAAM,CAAC,CAAA;AAEV,QAAQ,CAAC,KAAK,IAAI,EAAE;IAClB,MAAM,GAAG,EAAE,IAAI,EAAE,CAAA;IACjB,MAAM,IAAI,EAAE,IAAI,EAAE,CAAA;IAClB,MAAM,MAAM,EAAE,IAAI,EAAE,CAAA;IACpB,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACtD,CAAC,CAAC,CAAA;AAEF,mGAAmG;AACnG,KAAK,UAAU,eAAe,CAAC,OAAe;IAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;IACnF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;AACpF,CAAC;AAED,SAAS,OAAO;IACd,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AAC9J,CAAC;AAED,+DAA+D;AAC/D,KAAK,UAAU,aAAa,CAAC,OAAe;IAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACpD,OAAO,CAAC,iBAAiB,EAAE,CAAA;IAC3B,MAAM,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,cAAc,EAAE,CAAC,CAAA;IACnF,MAAM,OAAO,CAAC,kBAAkB,EAAE,CAAA;AACpC,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,MAAM,OAAO,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC,CAAA;AAC3H,CAAC;AAED,SAAS,WAAW;IAClB,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IAC5D,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAA;IACtD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,IAAI,CAAC,wGAAwG,EAAE,KAAK,IAAI,EAAE;IACxH,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;IAC9B,MAAM,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACpE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,mCAAmC,EAAE,CAAC,CAAA;IACvF,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAC1G,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC,CAAA;IACrF,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAA;IAC7G,wEAAwE;IACxE,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;IAC3F,MAAM,CAAC,MAAM,eAAe,CAAC,kBAAkB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;GAqBvE,CAAC,CAAA;IACF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAA;IACzE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,KAAK,wBAAwB,CAAC,CAAA;IACvF,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,iBAAiB,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;AACxG,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;IACvD,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;IAC9B,MAAM,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACpE,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAA;IACjH,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAC1G,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAA;IACjH,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAC1G,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IACvG,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAA;IAC7G,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;IAC3F,MAAM,CAAC,MAAM,eAAe,CAAC,kBAAkB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;GAkBvE,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;IAC9E,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;IAC9B,MAAM,aAAa,CAAC,oBAAoB,CAAC,CAAA;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACpE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC,CAAA;IAClF,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAC1G,MAAM,IAAI,CAAC,aAAa,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;IAClD,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,CAAA;IAClH,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;IAC3F,MAAM,WAAW,EAAE,CAAA;IACnB,MAAM,CAAC,MAAM,eAAe,CAAC,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;GASzE,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,yGAAyG,EAAE,KAAK,IAAI,EAAE;IACzH,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;IAC9B,MAAM,aAAa,CAAC,eAAe,CAAC,CAAA;IACpC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,UAAU,EAAE,gCAAgC,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE;QACrL,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;KACjE,CAAC,CAAA;IACF,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAC1G,MAAM,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;IAC9H,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAA;IAC7G,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;IAC3F,MAAM,CAAC,MAAM,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;GAiBpE,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;IACnG,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;IAC9B,MAAM,aAAa,CAAC,oBAAoB,CAAC,CAAA;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACpE,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC,CAAA;IACnE,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAC1G,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAA;IAC7D,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAA;IAC7G,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;IAC3F,MAAM,CAAC,MAAM,eAAe,CAAC,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;GAiBzE,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;IACzE,MAAM,aAAa,CAAC,oBAAoB,CAAC,CAAA;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACpE,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC,CAAA;IACnE,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAC1G,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;IAC7C,MAAM,2BAA2B,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,CAAA;IAClH,MAAM,WAAW,EAAE,CAAA;IACnB,MAAM,CAAC,MAAM,eAAe,CAAC,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;GAQzE,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,6EAA6E,EAAE,KAAK,IAAI,EAAE;IAC7F,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;IAC9B,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC,CAAA;IAC1H,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;IAC3F,MAAM,CAAC,MAAM,eAAe,CAAC,2BAA2B,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;GAOhF,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -1 +0,0 @@
1
- export {};